Skip to content

Commit a4e2b15

Browse files
Matt Appersonclaude
andcommitted
Add TurnContext parameter to tool execute functions
- Added TurnContext type with numberOfTurns (1-indexed), messageHistory, model/models - Updated tool execute function signatures to accept optional context parameter - Context is built in response-wrapper.ts during tool execution loop - Updated all tests and examples to demonstrate context usage - Context parameter is optional for backward compatibility - Exported TurnContext type in public API This allows tools to access conversation state including turn number, message history, and model information during execution. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent c58edfe commit a4e2b15

File tree

11 files changed

+2143
-53
lines changed

11 files changed

+2143
-53
lines changed

examples/tools-example.ts

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
/**
2+
* OpenRouter SDK - Enhanced Tool Support Examples
3+
*
4+
* This file demonstrates the automatic tool execution feature.
5+
* When you provide tools with `execute` functions, they are automatically:
6+
* 1. Validated using Zod schemas
7+
* 2. Executed when the model calls them
8+
* 3. Results sent back to the model
9+
* 4. Process repeats until no more tool calls (up to maxToolRounds)
10+
*
11+
* The API is simple: just call getResponse() with tools, and await the result.
12+
* Tools are executed transparently before getMessage() or getText() returns!
13+
*/
14+
15+
import { OpenRouter } from "../src/index.js";
16+
import { z } from "zod/v4";
17+
import * as dotenv from "dotenv";
18+
19+
dotenv.config();
20+
21+
const client = new OpenRouter({
22+
apiKey: process.env.OPENROUTER_API_KEY || "",
23+
});
24+
25+
/**
26+
* Example 1: Basic Tool with Execute Function
27+
* A simple weather tool that returns mock data
28+
* Note: The context parameter is optional for backward compatibility
29+
*/
30+
async function basicToolExample() {
31+
console.log("\n=== Example 1: Basic Tool with Execute Function ===\n");
32+
33+
const weatherTool = {
34+
type: "function" as const,
35+
function: {
36+
name: "get_weather",
37+
description: "Get current weather for a location",
38+
inputSchema: z.object({
39+
location: z.string().describe("City and country (e.g., San Francisco, CA)"),
40+
}),
41+
outputSchema: z.object({
42+
temperature: z.number(),
43+
description: z.string(),
44+
humidity: z.number(),
45+
}),
46+
execute: async (params: { location: string }, context) => {
47+
console.log(`Executing get_weather for: ${params.location}`);
48+
console.log(`Turn ${context.numberOfTurns} - Model: ${context.model || context.models?.join(", ")}`);
49+
// In real usage, you would call a weather API here
50+
return {
51+
temperature: 72,
52+
description: "Sunny",
53+
humidity: 45,
54+
};
55+
},
56+
},
57+
};
58+
59+
const response = client.getResponse({
60+
model: "openai/gpt-4o",
61+
input: "What's the weather like in San Francisco?",
62+
tools: [weatherTool],
63+
});
64+
65+
// Tools are automatically executed! Just get the final message
66+
const message = await response.getMessage();
67+
console.log("\nFinal message after automatic tool execution:", message.content);
68+
69+
// You can also check what tool calls were made initially
70+
const toolCalls = await response.getToolCalls();
71+
console.log("\nInitial tool calls:", JSON.stringify(toolCalls, null, 2));
72+
}
73+
74+
/**
75+
* Example 2: Generator Tool with Preliminary Results
76+
* Shows how to use async generators for streaming intermediate results
77+
*/
78+
async function generatorToolExample() {
79+
console.log("\n=== Example 2: Generator Tool with Preliminary Results ===\n");
80+
81+
const processingTool = {
82+
type: "function" as const,
83+
function: {
84+
name: "process_data",
85+
description: "Process data with progress updates",
86+
inputSchema: z.object({
87+
data: z.string().describe("Data to process"),
88+
}),
89+
eventSchema: z.object({
90+
type: z.enum(["start", "progress", "complete"]),
91+
message: z.string(),
92+
progress: z.number().min(0).max(100).optional(),
93+
}),
94+
execute: async function* (params: { data: string }, context) {
95+
console.log(`Generator tool - Turn ${context.numberOfTurns}`);
96+
// Preliminary result 1
97+
yield {
98+
type: "start" as const,
99+
message: `Started processing: ${params.data}`,
100+
progress: 0,
101+
};
102+
103+
await new Promise((resolve) => setTimeout(resolve, 500));
104+
105+
// Preliminary result 2
106+
yield {
107+
type: "progress" as const,
108+
message: "Processing halfway done",
109+
progress: 50,
110+
};
111+
112+
await new Promise((resolve) => setTimeout(resolve, 500));
113+
114+
// Final result (last yield)
115+
yield {
116+
type: "complete" as const,
117+
message: `Completed processing: ${params.data.toUpperCase()}`,
118+
progress: 100,
119+
};
120+
},
121+
},
122+
};
123+
124+
const response = client.getResponse({
125+
model: "openai/gpt-4o",
126+
input: "Process this data: hello world",
127+
tools: [processingTool],
128+
});
129+
130+
// Stream preliminary results as they arrive
131+
console.log("Streaming tool events including preliminary results:\n");
132+
for await (const event of response.getToolStream()) {
133+
if (event.type === "preliminary_result") {
134+
console.log(`Preliminary result from ${event.toolCallId}:`, event.result);
135+
} else if (event.type === "delta") {
136+
process.stdout.write(event.content);
137+
}
138+
}
139+
140+
// Tools are automatically executed with preliminary results available
141+
const message = await response.getMessage();
142+
console.log("\n\nFinal message:", message.content);
143+
}
144+
145+
/**
146+
* Example 3: Manual Tool Execution
147+
* Define a tool without execute function for manual handling
148+
*/
149+
async function manualToolExample() {
150+
console.log("\n=== Example 3: Manual Tool Execution ===\n");
151+
152+
const calculatorTool = {
153+
type: "function" as const,
154+
function: {
155+
name: "calculate",
156+
description: "Perform mathematical calculations",
157+
inputSchema: z.object({
158+
expression: z.string().describe("Math expression to evaluate"),
159+
}),
160+
outputSchema: z.object({
161+
result: z.number(),
162+
}),
163+
// No execute function - tool calls are returned but not executed
164+
},
165+
};
166+
167+
const response = client.getResponse({
168+
model: "openai/gpt-4o",
169+
input: "What is 25 * 4 + 10?",
170+
tools: [calculatorTool],
171+
});
172+
173+
// Since there's no execute function, tool calls are returned but not executed
174+
const toolCalls = await response.getToolCalls();
175+
console.log("Tool calls (not auto-executed):", toolCalls);
176+
177+
// You can manually handle tool execution here
178+
for (const toolCall of toolCalls) {
179+
if (toolCall.name === "calculate") {
180+
const expression = (toolCall.arguments as { expression: string }).expression;
181+
console.log(`Manually executing calculation: ${expression}`);
182+
183+
// In a real app, you would safely evaluate this
184+
// For demo purposes only - don't use eval in production!
185+
try {
186+
const result = eval(expression);
187+
console.log(`Result: ${result}`);
188+
} catch (error) {
189+
console.error("Calculation error:", error);
190+
}
191+
}
192+
}
193+
194+
// Then you would need to make a new request with the tool results
195+
// (This example just shows the manual detection, not the full loop)
196+
}
197+
198+
/**
199+
* Example 4: Streaming Tool Calls
200+
* Show how to stream structured tool call objects as they arrive
201+
* Note: This tool doesn't use context - demonstrating backward compatibility
202+
*/
203+
async function streamingToolCallsExample() {
204+
console.log("\n=== Example 4: Streaming Tool Calls ===\n");
205+
206+
const searchTool = {
207+
type: "function" as const,
208+
function: {
209+
name: "search",
210+
description: "Search for information",
211+
inputSchema: z.object({
212+
query: z.string().describe("Search query"),
213+
}),
214+
outputSchema: z.object({
215+
results: z.array(z.string()),
216+
}),
217+
execute: async (params: { query: string }) => {
218+
// Context parameter is optional - this tool doesn't need it
219+
return {
220+
results: [
221+
`Result 1 for "${params.query}"`,
222+
`Result 2 for "${params.query}"`,
223+
],
224+
};
225+
},
226+
},
227+
};
228+
229+
const response = client.getResponse({
230+
model: "openai/gpt-4o",
231+
input: "Search for information about TypeScript",
232+
tools: [searchTool],
233+
});
234+
235+
console.log("Streaming tool calls as they arrive:\n");
236+
237+
// Stream structured tool call objects
238+
for await (const toolCall of response.getToolCallsStream()) {
239+
console.log("Tool call:", JSON.stringify(toolCall, null, 2));
240+
}
241+
}
242+
243+
/**
244+
* Example 5: Multiple Tools
245+
* Use multiple tools in a single request
246+
* Note: Shows mixing tools with and without context parameter
247+
*/
248+
async function multipleToolsExample() {
249+
console.log("\n=== Example 5: Multiple Tools ===\n");
250+
251+
const tools = [
252+
{
253+
type: "function" as const,
254+
function: {
255+
name: "get_time",
256+
description: "Get current time",
257+
inputSchema: z.object({
258+
timezone: z.string().optional(),
259+
}),
260+
outputSchema: z.object({
261+
time: z.string(),
262+
timezone: z.string(),
263+
}),
264+
execute: async (params: { timezone?: string }, context) => {
265+
return {
266+
time: new Date().toISOString(),
267+
timezone: params.timezone || "UTC",
268+
};
269+
},
270+
},
271+
},
272+
{
273+
type: "function" as const,
274+
function: {
275+
name: "get_weather",
276+
description: "Get weather information",
277+
inputSchema: z.object({
278+
location: z.string(),
279+
}),
280+
outputSchema: z.object({
281+
temperature: z.number(),
282+
description: z.string(),
283+
}),
284+
execute: async (params: { location: string }) => {
285+
// This tool doesn't need context
286+
return {
287+
temperature: 68,
288+
description: "Partly cloudy",
289+
};
290+
},
291+
},
292+
},
293+
];
294+
295+
const response = client.getResponse({
296+
model: "openai/gpt-4o",
297+
input: "What time is it and what's the weather in New York?",
298+
tools,
299+
});
300+
301+
// Tools are automatically executed!
302+
const message = await response.getMessage();
303+
console.log("Final message:", message.content);
304+
305+
// You can check which tools were called
306+
const toolCalls = await response.getToolCalls();
307+
console.log("\nTools that were called:", toolCalls.map(tc => tc.name));
308+
}
309+
310+
// Run examples
311+
async function main() {
312+
try {
313+
await basicToolExample();
314+
await generatorToolExample();
315+
await manualToolExample();
316+
await streamingToolCallsExample();
317+
await multipleToolsExample();
318+
} catch (error) {
319+
console.error("Error running examples:", error);
320+
}
321+
}
322+
323+
// Only run if this file is executed directly
324+
if (import.meta.url === `file://${process.argv[1]}`) {
325+
main();
326+
}
327+
328+
export {
329+
basicToolExample,
330+
generatorToolExample,
331+
manualToolExample,
332+
streamingToolCallsExample,
333+
multipleToolsExample,
334+
};

0 commit comments

Comments
 (0)