Skip to content

Commit 81fa1dd

Browse files
mattappersonclaude
andcommitted
feat: add typed tool creation helpers and type inference
- Add tool() function for creating tools with full type inference from Zod schemas - Auto-detect tool type based on configuration (generator, regular, or manual) - Add type inference helpers: InferToolInput, InferToolOutput, InferToolEvent - Add TypedToolCall and TypedToolCallUnion for typed tool call handling - Add generic type parameters to event types for typed streaming - Export all tool types and helpers from main index - Add comprehensive unit tests for create-tool - Add typed-tool-calling example demonstrating the new API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 3e7f10c commit 81fa1dd

File tree

5 files changed

+803
-10
lines changed

5 files changed

+803
-10
lines changed
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* Example: Typed Tool Calling with callModel
3+
*
4+
* This example demonstrates how to use the tool() function for
5+
* fully-typed tool definitions where execute params, return types, and event
6+
* types are automatically inferred from Zod schemas.
7+
*
8+
* Tool types are auto-detected based on configuration:
9+
* - Generator tool: When `eventSchema` is provided
10+
* - Regular tool: When `execute` is a function (no `eventSchema`)
11+
* - Manual tool: When `execute: false` is set
12+
*
13+
* To run this example from the examples directory:
14+
* npm run build && npx tsx callModel-typed-tool-calling.example.ts
15+
*/
16+
17+
import dotenv from "dotenv";
18+
dotenv.config();
19+
20+
import { OpenRouter, tool } from "../src/index.js";
21+
import z from "zod";
22+
23+
const openRouter = new OpenRouter({
24+
apiKey: process.env["OPENROUTER_API_KEY"] ?? "",
25+
});
26+
27+
// Create a typed regular tool using tool()
28+
// The execute function params are automatically typed as z.infer<typeof inputSchema>
29+
// The return type is enforced based on outputSchema
30+
const weatherTool = tool({
31+
name: "get_weather",
32+
description: "Get the current weather for a location",
33+
inputSchema: z.object({
34+
location: z.string().describe("The city and country, e.g. San Francisco, CA"),
35+
}),
36+
outputSchema: z.object({
37+
temperature: z.number(),
38+
description: z.string(),
39+
}),
40+
// params is automatically typed as { location: string }
41+
execute: async (params) => {
42+
console.log(`Getting weather for: ${params.location}`);
43+
// Return type is enforced as { temperature: number; description: string }
44+
return {
45+
temperature: 20,
46+
description: "Sunny",
47+
};
48+
},
49+
});
50+
51+
// Create a generator tool with typed progress events by providing eventSchema
52+
// The eventSchema triggers generator mode - execute becomes an async generator
53+
const searchTool = tool({
54+
name: "search_database",
55+
description: "Search database with progress updates",
56+
inputSchema: z.object({
57+
query: z.string().describe("The search query"),
58+
}),
59+
eventSchema: z.object({
60+
progress: z.number(),
61+
message: z.string(),
62+
}),
63+
outputSchema: z.object({
64+
results: z.array(z.string()),
65+
totalFound: z.number(),
66+
}),
67+
// execute is a generator that yields typed progress events
68+
execute: async function* (params) {
69+
console.log(`Searching for: ${params.query}`);
70+
// Each yield is typed as { progress: number; message: string }
71+
yield { progress: 25, message: "Searching..." };
72+
yield { progress: 50, message: "Processing results..." };
73+
yield { progress: 75, message: "Almost done..." };
74+
// Final result is typed as { results: string[]; totalFound: number }
75+
yield { progress: 100, message: "Complete!" };
76+
},
77+
});
78+
79+
async function main() {
80+
console.log("=== Typed Tool Calling Example ===\n");
81+
82+
// Use 'as const' to enable full type inference for tool calls
83+
const result = openRouter.callModel({
84+
instructions: "You are a helpful assistant. Your name is Mark",
85+
model: "openai/gpt-4o-mini",
86+
input: "Hello! What is the weather in San Francisco?",
87+
tools: [weatherTool] as const,
88+
});
89+
90+
// Get text response (tools are auto-executed)
91+
const text = await result.getText();
92+
console.log("Response:", text);
93+
94+
console.log("\n=== Getting Tool Calls ===\n");
95+
96+
// Create a fresh request for demonstrating getToolCalls
97+
const result2 = openRouter.callModel({
98+
model: "openai/gpt-4o-mini",
99+
input: "What's the weather like in Paris?",
100+
tools: [weatherTool] as const,
101+
maxToolRounds: 0, // Don't auto-execute, just get the tool calls
102+
});
103+
104+
// Tool calls are now typed based on the tool definitions!
105+
const toolCalls = await result2.getToolCalls();
106+
107+
for (const toolCall of toolCalls) {
108+
console.log(`Tool: ${toolCall.name}`);
109+
// toolCall.arguments is typed as { location: string }
110+
console.log(`Arguments:`, toolCall.arguments);
111+
}
112+
113+
console.log("\n=== Streaming Tool Calls ===\n");
114+
115+
// Create another request for demonstrating streaming
116+
const result3 = openRouter.callModel({
117+
model: "openai/gpt-4o-mini",
118+
input: "What's the weather in Tokyo?",
119+
tools: [weatherTool] as const,
120+
maxToolRounds: 0,
121+
});
122+
123+
// Stream tool calls with typed arguments
124+
for await (const toolCall of result3.getToolCallsStream()) {
125+
console.log(`Streamed tool: ${toolCall.name}`);
126+
// toolCall.arguments is typed based on tool definitions
127+
console.log(`Streamed arguments:`, toolCall.arguments);
128+
}
129+
130+
console.log("\n=== Generator Tool with Typed Events ===\n");
131+
132+
// Use generator tool with typed progress events
133+
const result4 = openRouter.callModel({
134+
model: "openai/gpt-4o-mini",
135+
input: "Search for documents about TypeScript",
136+
tools: [searchTool] as const,
137+
});
138+
139+
// Stream events from getToolStream - events are fully typed!
140+
for await (const event of result4.getToolStream()) {
141+
if (event.type === "preliminary_result") {
142+
// event.result is typed as { progress: number; message: string }
143+
console.log(`Progress: ${event.result.progress}% - ${event.result.message}`);
144+
} else if (event.type === "delta") {
145+
// Tool argument deltas
146+
process.stdout.write(event.content);
147+
}
148+
}
149+
150+
console.log("\n=== Mixed Tools with Typed Events ===\n");
151+
152+
// Use both regular and generator tools together
153+
const result5 = openRouter.callModel({
154+
model: "openai/gpt-4o-mini",
155+
input: "First search for weather data, then get the weather in Seattle",
156+
tools: [weatherTool, searchTool] as const,
157+
});
158+
159+
// Events are a union of all generator tool event types
160+
for await (const event of result5.getToolStream()) {
161+
if (event.type === "preliminary_result") {
162+
// event.result is typed as { progress: number; message: string }
163+
// (only searchTool has eventSchema, so that's the event type)
164+
console.log(`Event:`, event.result);
165+
}
166+
}
167+
}
168+
169+
main().catch(console.error);

src/index.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,33 @@ export * from "./sdk/sdk.js";
1111
// Message format compatibility helpers
1212
export { fromClaudeMessages, toClaudeMessage } from "./lib/anthropic-compat.js";
1313
export { fromChatMessages, toChatMessage } from "./lib/chat-compat.js";
14+
15+
// Tool creation helpers
16+
export { tool, createTool, createGeneratorTool, createManualTool } from "./lib/create-tool.js";
17+
18+
// Tool types
19+
export type {
20+
Tool,
21+
ToolWithExecute,
22+
ToolWithGenerator,
23+
ManualTool,
24+
TurnContext,
25+
InferToolInput,
26+
InferToolOutput,
27+
InferToolEvent,
28+
InferToolEventsUnion,
29+
TypedToolCall,
30+
TypedToolCallUnion,
31+
ToolStreamEvent,
32+
ChatStreamEvent,
33+
EnhancedResponseStreamEvent,
34+
ToolPreliminaryResultEvent,
35+
} from "./lib/tool-types.js";
36+
37+
export {
38+
ToolType,
39+
hasExecuteFunction,
40+
isGeneratorTool,
41+
isRegularExecuteTool,
42+
isToolPreliminaryResultEvent,
43+
} from "./lib/tool-types.js";

0 commit comments

Comments
 (0)