Skip to content

Commit aedcf10

Browse files
committed
chore: merge main and regenerate SDK with getResponse features
- Merged latest changes from main branch - Preserved getResponse feature files - Fixed eslint error in reusable-stream.ts - Regenerated SDK with speakeasy run
1 parent b65d547 commit aedcf10

17 files changed

+3496
-10
lines changed

.speakeasy/gen.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ management:
55
docVersion: 1.0.0
66
speakeasyVersion: 1.659.0
77
generationVersion: 2.755.9
8-
releaseVersion: 0.1.18
9-
configChecksum: ad5a220c1110e01ccea49eed2d7bb48c
8+
releaseVersion: 0.1.20
9+
configChecksum: 3b36d5eb8cadc98f73a43f78313bb65c
1010
repoURL: https://github.com/OpenRouterTeam/typescript-sdk.git
1111
installationURL: https://github.com/OpenRouterTeam/typescript-sdk
1212
published: true

.speakeasy/gen.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ generation:
3030
generateNewTests: true
3131
skipResponseBodyAssertions: false
3232
typescript:
33-
version: 0.1.18
33+
version: 0.1.20
3434
acceptHeaderEnum: false
3535
additionalDependencies:
3636
dependencies: {}

.speakeasy/workflow.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ targets:
1414
sourceRevisionDigest: sha256:ffe0e925561a55a1b403667fe33bb3158e05892ef1e66f56211544c9a890b301
1515
sourceBlobDigest: sha256:18aa7b22686c2f559af1062fea408a9f80146231027ed1fd62b68df38c71f65d
1616
codeSamplesNamespace: open-router-chat-completions-api-typescript-code-samples
17-
codeSamplesRevisionDigest: sha256:773f28292c3a6cff16578829c01a9ffb37f23d3c27b607e7e7e97f55cfd00f64
17+
codeSamplesRevisionDigest: sha256:db1e3aba5b14308995859d6339ac902dbbe644d474c4ac3a998db42bc0431453
1818
workflow:
1919
workflowVersion: 1.0.0
2020
speakeasyVersion: latest

examples/tools-example.ts

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

jsr.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
{
44
"name": "@openrouter/sdk",
5-
"version": "0.1.18",
5+
"version": "0.1.20",
66
"exports": {
77
".": "./src/index.ts",
88
"./models/errors": "./src/models/errors/index.ts",

0 commit comments

Comments
 (0)