@openrouter/sdk 0.1.18 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/funcs/callModel.d.ts +78 -0
- package/esm/funcs/callModel.js +101 -0
- package/esm/lib/config.d.ts +3 -3
- package/esm/lib/config.js +3 -3
- package/esm/lib/response-wrapper.d.ts +122 -0
- package/esm/lib/response-wrapper.js +471 -0
- package/esm/lib/reusable-stream.d.ts +39 -0
- package/esm/lib/reusable-stream.js +173 -0
- package/esm/lib/stream-transformers.d.ts +47 -0
- package/esm/lib/stream-transformers.js +280 -0
- package/esm/lib/tool-executor.d.ts +53 -0
- package/esm/lib/tool-executor.js +181 -0
- package/esm/lib/tool-orchestrator.d.ts +50 -0
- package/esm/lib/tool-orchestrator.js +132 -0
- package/esm/lib/tool-types.d.ts +199 -0
- package/esm/lib/tool-types.js +32 -0
- package/esm/models/chatstreamingresponsechunk.js +2 -1
- package/esm/models/operations/createresponses.js +2 -1
- package/esm/sdk/sdk.d.ts +10 -0
- package/esm/sdk/sdk.js +9 -0
- package/esm/types/enums.d.ts +1 -8
- package/esm/types/enums.js +1 -18
- package/esm/types/index.d.ts +2 -1
- package/esm/types/index.js +1 -0
- package/esm/types/unrecognized.d.ts +10 -0
- package/esm/types/unrecognized.js +23 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/vitest.config.ts +4 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as models from "../models/index.js";
|
|
2
|
+
import { ReusableReadableStream } from "./reusable-stream.js";
|
|
3
|
+
import { ParsedToolCall } from "./tool-types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Extract text deltas from responses stream events
|
|
6
|
+
*/
|
|
7
|
+
export declare function extractTextDeltas(stream: ReusableReadableStream<models.OpenResponsesStreamEvent>): AsyncIterableIterator<string>;
|
|
8
|
+
/**
|
|
9
|
+
* Extract reasoning deltas from responses stream events
|
|
10
|
+
*/
|
|
11
|
+
export declare function extractReasoningDeltas(stream: ReusableReadableStream<models.OpenResponsesStreamEvent>): AsyncIterableIterator<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Extract tool call argument deltas from responses stream events
|
|
14
|
+
*/
|
|
15
|
+
export declare function extractToolDeltas(stream: ReusableReadableStream<models.OpenResponsesStreamEvent>): AsyncIterableIterator<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Build incremental message updates from responses stream events
|
|
18
|
+
* Returns AssistantMessage (chat format) instead of ResponsesOutputMessage
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildMessageStream(stream: ReusableReadableStream<models.OpenResponsesStreamEvent>): AsyncIterableIterator<models.AssistantMessage>;
|
|
21
|
+
/**
|
|
22
|
+
* Consume stream until completion and return the complete response
|
|
23
|
+
*/
|
|
24
|
+
export declare function consumeStreamForCompletion(stream: ReusableReadableStream<models.OpenResponsesStreamEvent>): Promise<models.OpenResponsesNonStreamingResponse>;
|
|
25
|
+
/**
|
|
26
|
+
* Extract the first message from a completed response
|
|
27
|
+
*/
|
|
28
|
+
export declare function extractMessageFromResponse(response: models.OpenResponsesNonStreamingResponse): models.AssistantMessage;
|
|
29
|
+
/**
|
|
30
|
+
* Extract text from a response, either from outputText or by concatenating message content
|
|
31
|
+
*/
|
|
32
|
+
export declare function extractTextFromResponse(response: models.OpenResponsesNonStreamingResponse): string;
|
|
33
|
+
/**
|
|
34
|
+
* Extract all tool calls from a completed response
|
|
35
|
+
* Returns parsed tool calls with arguments as objects (not JSON strings)
|
|
36
|
+
*/
|
|
37
|
+
export declare function extractToolCallsFromResponse(response: models.OpenResponsesNonStreamingResponse): ParsedToolCall[];
|
|
38
|
+
/**
|
|
39
|
+
* Build incremental tool call updates from responses stream events
|
|
40
|
+
* Yields structured tool call objects as they're built from deltas
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildToolCallStream(stream: ReusableReadableStream<models.OpenResponsesStreamEvent>): AsyncIterableIterator<ParsedToolCall>;
|
|
43
|
+
/**
|
|
44
|
+
* Check if a response contains any tool calls
|
|
45
|
+
*/
|
|
46
|
+
export declare function responseHasToolCalls(response: models.OpenResponsesNonStreamingResponse): boolean;
|
|
47
|
+
//# sourceMappingURL=stream-transformers.d.ts.map
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract text deltas from responses stream events
|
|
3
|
+
*/
|
|
4
|
+
export async function* extractTextDeltas(stream) {
|
|
5
|
+
const consumer = stream.createConsumer();
|
|
6
|
+
for await (const event of consumer) {
|
|
7
|
+
if ("type" in event && event.type === "response.output_text.delta") {
|
|
8
|
+
const deltaEvent = event;
|
|
9
|
+
if (deltaEvent.delta) {
|
|
10
|
+
yield deltaEvent.delta;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Extract reasoning deltas from responses stream events
|
|
17
|
+
*/
|
|
18
|
+
export async function* extractReasoningDeltas(stream) {
|
|
19
|
+
const consumer = stream.createConsumer();
|
|
20
|
+
for await (const event of consumer) {
|
|
21
|
+
if ("type" in event && event.type === "response.reasoning_text.delta") {
|
|
22
|
+
const deltaEvent = event;
|
|
23
|
+
if (deltaEvent.delta) {
|
|
24
|
+
yield deltaEvent.delta;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Extract tool call argument deltas from responses stream events
|
|
31
|
+
*/
|
|
32
|
+
export async function* extractToolDeltas(stream) {
|
|
33
|
+
const consumer = stream.createConsumer();
|
|
34
|
+
for await (const event of consumer) {
|
|
35
|
+
if ("type" in event && event.type === "response.function_call_arguments.delta") {
|
|
36
|
+
const deltaEvent = event;
|
|
37
|
+
if (deltaEvent.delta) {
|
|
38
|
+
yield deltaEvent.delta;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build incremental message updates from responses stream events
|
|
45
|
+
* Returns AssistantMessage (chat format) instead of ResponsesOutputMessage
|
|
46
|
+
*/
|
|
47
|
+
export async function* buildMessageStream(stream) {
|
|
48
|
+
const consumer = stream.createConsumer();
|
|
49
|
+
// Track the accumulated text
|
|
50
|
+
let currentText = "";
|
|
51
|
+
let hasStarted = false;
|
|
52
|
+
for await (const event of consumer) {
|
|
53
|
+
if (!("type" in event))
|
|
54
|
+
continue;
|
|
55
|
+
switch (event.type) {
|
|
56
|
+
case "response.output_item.added": {
|
|
57
|
+
const itemEvent = event;
|
|
58
|
+
if (itemEvent.item && "type" in itemEvent.item && itemEvent.item.type === "message") {
|
|
59
|
+
hasStarted = true;
|
|
60
|
+
currentText = "";
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case "response.output_text.delta": {
|
|
65
|
+
const deltaEvent = event;
|
|
66
|
+
if (hasStarted && deltaEvent.delta) {
|
|
67
|
+
currentText += deltaEvent.delta;
|
|
68
|
+
// Yield updated message
|
|
69
|
+
yield {
|
|
70
|
+
role: "assistant",
|
|
71
|
+
content: currentText,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case "response.output_item.done": {
|
|
77
|
+
const itemDoneEvent = event;
|
|
78
|
+
if (itemDoneEvent.item && "type" in itemDoneEvent.item && itemDoneEvent.item.type === "message") {
|
|
79
|
+
// Yield final complete message
|
|
80
|
+
const outputMessage = itemDoneEvent.item;
|
|
81
|
+
yield convertToAssistantMessage(outputMessage);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Consume stream until completion and return the complete response
|
|
90
|
+
*/
|
|
91
|
+
export async function consumeStreamForCompletion(stream) {
|
|
92
|
+
const consumer = stream.createConsumer();
|
|
93
|
+
for await (const event of consumer) {
|
|
94
|
+
if (!("type" in event))
|
|
95
|
+
continue;
|
|
96
|
+
if (event.type === "response.completed") {
|
|
97
|
+
const completedEvent = event;
|
|
98
|
+
return completedEvent.response;
|
|
99
|
+
}
|
|
100
|
+
if (event.type === "response.failed") {
|
|
101
|
+
const failedEvent = event;
|
|
102
|
+
// The failed event contains the full response with error information
|
|
103
|
+
throw new Error(`Response failed: ${JSON.stringify(failedEvent.response.error)}`);
|
|
104
|
+
}
|
|
105
|
+
if (event.type === "response.incomplete") {
|
|
106
|
+
const incompleteEvent = event;
|
|
107
|
+
// Return the incomplete response
|
|
108
|
+
return incompleteEvent.response;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
throw new Error("Stream ended without completion event");
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Convert ResponsesOutputMessage to AssistantMessage (chat format)
|
|
115
|
+
*/
|
|
116
|
+
function convertToAssistantMessage(outputMessage) {
|
|
117
|
+
// Extract text content
|
|
118
|
+
const textContent = outputMessage.content
|
|
119
|
+
.filter((part) => "type" in part && part.type === "output_text")
|
|
120
|
+
.map((part) => part.text)
|
|
121
|
+
.join("");
|
|
122
|
+
return {
|
|
123
|
+
role: "assistant",
|
|
124
|
+
content: textContent || null,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Extract the first message from a completed response
|
|
129
|
+
*/
|
|
130
|
+
export function extractMessageFromResponse(response) {
|
|
131
|
+
const messageItem = response.output.find((item) => "type" in item && item.type === "message");
|
|
132
|
+
if (!messageItem) {
|
|
133
|
+
throw new Error("No message found in response output");
|
|
134
|
+
}
|
|
135
|
+
return convertToAssistantMessage(messageItem);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Extract text from a response, either from outputText or by concatenating message content
|
|
139
|
+
*/
|
|
140
|
+
export function extractTextFromResponse(response) {
|
|
141
|
+
// Use pre-concatenated outputText if available
|
|
142
|
+
if (response.outputText) {
|
|
143
|
+
return response.outputText;
|
|
144
|
+
}
|
|
145
|
+
// Otherwise, extract from the first message (convert to AssistantMessage which has string content)
|
|
146
|
+
const message = extractMessageFromResponse(response);
|
|
147
|
+
// AssistantMessage.content is string | Array | null | undefined
|
|
148
|
+
if (typeof message.content === "string") {
|
|
149
|
+
return message.content;
|
|
150
|
+
}
|
|
151
|
+
return "";
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Extract all tool calls from a completed response
|
|
155
|
+
* Returns parsed tool calls with arguments as objects (not JSON strings)
|
|
156
|
+
*/
|
|
157
|
+
export function extractToolCallsFromResponse(response) {
|
|
158
|
+
const toolCalls = [];
|
|
159
|
+
for (const item of response.output) {
|
|
160
|
+
if ("type" in item && item.type === "function_call") {
|
|
161
|
+
const functionCallItem = item;
|
|
162
|
+
try {
|
|
163
|
+
const parsedArguments = JSON.parse(functionCallItem.arguments);
|
|
164
|
+
toolCalls.push({
|
|
165
|
+
id: functionCallItem.callId,
|
|
166
|
+
name: functionCallItem.name,
|
|
167
|
+
arguments: parsedArguments,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.error(`Failed to parse tool call arguments for ${functionCallItem.name}:`, error);
|
|
172
|
+
// Include the tool call with unparsed arguments
|
|
173
|
+
toolCalls.push({
|
|
174
|
+
id: functionCallItem.callId,
|
|
175
|
+
name: functionCallItem.name,
|
|
176
|
+
arguments: functionCallItem.arguments, // Keep as string if parsing fails
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return toolCalls;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Build incremental tool call updates from responses stream events
|
|
185
|
+
* Yields structured tool call objects as they're built from deltas
|
|
186
|
+
*/
|
|
187
|
+
export async function* buildToolCallStream(stream) {
|
|
188
|
+
const consumer = stream.createConsumer();
|
|
189
|
+
// Track tool calls being built
|
|
190
|
+
const toolCallsInProgress = new Map();
|
|
191
|
+
for await (const event of consumer) {
|
|
192
|
+
if (!("type" in event))
|
|
193
|
+
continue;
|
|
194
|
+
switch (event.type) {
|
|
195
|
+
case "response.output_item.added": {
|
|
196
|
+
const itemEvent = event;
|
|
197
|
+
if (itemEvent.item &&
|
|
198
|
+
"type" in itemEvent.item &&
|
|
199
|
+
itemEvent.item.type === "function_call") {
|
|
200
|
+
const functionCallItem = itemEvent.item;
|
|
201
|
+
toolCallsInProgress.set(functionCallItem.callId, {
|
|
202
|
+
id: functionCallItem.callId,
|
|
203
|
+
name: functionCallItem.name,
|
|
204
|
+
argumentsAccumulated: "",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case "response.function_call_arguments.delta": {
|
|
210
|
+
const deltaEvent = event;
|
|
211
|
+
const toolCall = toolCallsInProgress.get(deltaEvent.itemId);
|
|
212
|
+
if (toolCall && deltaEvent.delta) {
|
|
213
|
+
toolCall.argumentsAccumulated += deltaEvent.delta;
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case "response.function_call_arguments.done": {
|
|
218
|
+
const doneEvent = event;
|
|
219
|
+
const toolCall = toolCallsInProgress.get(doneEvent.itemId);
|
|
220
|
+
if (toolCall) {
|
|
221
|
+
// Parse complete arguments
|
|
222
|
+
try {
|
|
223
|
+
const parsedArguments = JSON.parse(doneEvent.arguments);
|
|
224
|
+
yield {
|
|
225
|
+
id: toolCall.id,
|
|
226
|
+
name: doneEvent.name,
|
|
227
|
+
arguments: parsedArguments,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
// Yield with unparsed arguments if parsing fails
|
|
232
|
+
yield {
|
|
233
|
+
id: toolCall.id,
|
|
234
|
+
name: doneEvent.name,
|
|
235
|
+
arguments: doneEvent.arguments,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
// Clean up
|
|
239
|
+
toolCallsInProgress.delete(doneEvent.itemId);
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
case "response.output_item.done": {
|
|
244
|
+
const itemDoneEvent = event;
|
|
245
|
+
if (itemDoneEvent.item &&
|
|
246
|
+
"type" in itemDoneEvent.item &&
|
|
247
|
+
itemDoneEvent.item.type === "function_call") {
|
|
248
|
+
const functionCallItem = itemDoneEvent.item;
|
|
249
|
+
// Yield final tool call if we haven't already
|
|
250
|
+
if (toolCallsInProgress.has(functionCallItem.callId)) {
|
|
251
|
+
try {
|
|
252
|
+
const parsedArguments = JSON.parse(functionCallItem.arguments);
|
|
253
|
+
yield {
|
|
254
|
+
id: functionCallItem.callId,
|
|
255
|
+
name: functionCallItem.name,
|
|
256
|
+
arguments: parsedArguments,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
yield {
|
|
261
|
+
id: functionCallItem.callId,
|
|
262
|
+
name: functionCallItem.name,
|
|
263
|
+
arguments: functionCallItem.arguments,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
toolCallsInProgress.delete(functionCallItem.callId);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Check if a response contains any tool calls
|
|
276
|
+
*/
|
|
277
|
+
export function responseHasToolCalls(response) {
|
|
278
|
+
return response.output.some((item) => "type" in item && item.type === "function_call");
|
|
279
|
+
}
|
|
280
|
+
//# sourceMappingURL=stream-transformers.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type ZodType } from "zod/v4";
|
|
2
|
+
import { EnhancedTool, ToolExecutionResult, ParsedToolCall, APITool, TurnContext } from "./tool-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Convert a Zod schema to JSON Schema using Zod v4's toJSONSchema function
|
|
5
|
+
*/
|
|
6
|
+
export declare function convertZodToJsonSchema(zodSchema: ZodType): Record<string, any>;
|
|
7
|
+
/**
|
|
8
|
+
* Convert enhanced tools to OpenRouter API format
|
|
9
|
+
*/
|
|
10
|
+
export declare function convertEnhancedToolsToAPIFormat(tools: EnhancedTool[]): APITool[];
|
|
11
|
+
/**
|
|
12
|
+
* Validate tool input against Zod schema
|
|
13
|
+
* @throws ZodError if validation fails
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateToolInput<T>(schema: ZodType<T>, args: unknown): T;
|
|
16
|
+
/**
|
|
17
|
+
* Validate tool output against Zod schema
|
|
18
|
+
* @throws ZodError if validation fails
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateToolOutput<T>(schema: ZodType<T>, result: unknown): T;
|
|
21
|
+
/**
|
|
22
|
+
* Parse tool call arguments from JSON string
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseToolCallArguments(argumentsString: string): unknown;
|
|
25
|
+
/**
|
|
26
|
+
* Execute a regular (non-generator) tool
|
|
27
|
+
*/
|
|
28
|
+
export declare function executeRegularTool(tool: EnhancedTool, toolCall: ParsedToolCall, context: TurnContext): Promise<ToolExecutionResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Execute a generator tool and collect preliminary and final results
|
|
31
|
+
* - Intermediate yields are validated against eventSchema (preliminary events)
|
|
32
|
+
* - Last yield is validated against outputSchema (final result sent to model)
|
|
33
|
+
* - Generator must emit at least one value
|
|
34
|
+
*/
|
|
35
|
+
export declare function executeGeneratorTool(tool: EnhancedTool, toolCall: ParsedToolCall, context: TurnContext, onPreliminaryResult?: (toolCallId: string, result: unknown) => void): Promise<ToolExecutionResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Execute a tool call
|
|
38
|
+
* Automatically detects if it's a regular or generator tool
|
|
39
|
+
*/
|
|
40
|
+
export declare function executeTool(tool: EnhancedTool, toolCall: ParsedToolCall, context: TurnContext, onPreliminaryResult?: (toolCallId: string, result: unknown) => void): Promise<ToolExecutionResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Find a tool by name in the tools array
|
|
43
|
+
*/
|
|
44
|
+
export declare function findToolByName(tools: EnhancedTool[], name: string): EnhancedTool | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* Format tool execution result as a string for sending to the model
|
|
47
|
+
*/
|
|
48
|
+
export declare function formatToolResultForModel(result: ToolExecutionResult): string;
|
|
49
|
+
/**
|
|
50
|
+
* Create a user-friendly error message for tool execution errors
|
|
51
|
+
*/
|
|
52
|
+
export declare function formatToolExecutionError(error: Error, toolCall: ParsedToolCall): string;
|
|
53
|
+
//# sourceMappingURL=tool-executor.d.ts.map
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { ZodError, toJSONSchema } from "zod/v4";
|
|
2
|
+
import { hasExecuteFunction, isGeneratorTool, isRegularExecuteTool, } from "./tool-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Convert a Zod schema to JSON Schema using Zod v4's toJSONSchema function
|
|
5
|
+
*/
|
|
6
|
+
export function convertZodToJsonSchema(zodSchema) {
|
|
7
|
+
const jsonSchema = toJSONSchema(zodSchema, {
|
|
8
|
+
target: "openapi-3.0",
|
|
9
|
+
});
|
|
10
|
+
return jsonSchema;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Convert enhanced tools to OpenRouter API format
|
|
14
|
+
*/
|
|
15
|
+
export function convertEnhancedToolsToAPIFormat(tools) {
|
|
16
|
+
return tools.map((tool) => ({
|
|
17
|
+
type: "function",
|
|
18
|
+
name: tool.function.name,
|
|
19
|
+
description: tool.function.description || null,
|
|
20
|
+
strict: null,
|
|
21
|
+
parameters: convertZodToJsonSchema(tool.function.inputSchema),
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Validate tool input against Zod schema
|
|
26
|
+
* @throws ZodError if validation fails
|
|
27
|
+
*/
|
|
28
|
+
export function validateToolInput(schema, args) {
|
|
29
|
+
return schema.parse(args);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Validate tool output against Zod schema
|
|
33
|
+
* @throws ZodError if validation fails
|
|
34
|
+
*/
|
|
35
|
+
export function validateToolOutput(schema, result) {
|
|
36
|
+
return schema.parse(result);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Parse tool call arguments from JSON string
|
|
40
|
+
*/
|
|
41
|
+
export function parseToolCallArguments(argumentsString) {
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(argumentsString);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
throw new Error(`Failed to parse tool call arguments: ${error instanceof Error ? error.message : String(error)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Execute a regular (non-generator) tool
|
|
51
|
+
*/
|
|
52
|
+
export async function executeRegularTool(tool, toolCall, context) {
|
|
53
|
+
if (!isRegularExecuteTool(tool)) {
|
|
54
|
+
throw new Error(`Tool "${toolCall.name}" is not a regular execute tool or has no execute function`);
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
// Validate input
|
|
58
|
+
const validatedInput = validateToolInput(tool.function.inputSchema, toolCall.arguments);
|
|
59
|
+
// Execute tool with context
|
|
60
|
+
const result = await Promise.resolve(tool.function.execute(validatedInput, context));
|
|
61
|
+
// Validate output if schema is provided
|
|
62
|
+
if (tool.function.outputSchema) {
|
|
63
|
+
const validatedOutput = validateToolOutput(tool.function.outputSchema, result);
|
|
64
|
+
return {
|
|
65
|
+
toolCallId: toolCall.id,
|
|
66
|
+
toolName: toolCall.name,
|
|
67
|
+
result: validatedOutput,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
toolCallId: toolCall.id,
|
|
72
|
+
toolName: toolCall.name,
|
|
73
|
+
result,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return {
|
|
78
|
+
toolCallId: toolCall.id,
|
|
79
|
+
toolName: toolCall.name,
|
|
80
|
+
result: null,
|
|
81
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Execute a generator tool and collect preliminary and final results
|
|
87
|
+
* - Intermediate yields are validated against eventSchema (preliminary events)
|
|
88
|
+
* - Last yield is validated against outputSchema (final result sent to model)
|
|
89
|
+
* - Generator must emit at least one value
|
|
90
|
+
*/
|
|
91
|
+
export async function executeGeneratorTool(tool, toolCall, context, onPreliminaryResult) {
|
|
92
|
+
if (!isGeneratorTool(tool)) {
|
|
93
|
+
throw new Error(`Tool "${toolCall.name}" is not a generator tool`);
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
// Validate input
|
|
97
|
+
const validatedInput = validateToolInput(tool.function.inputSchema, toolCall.arguments);
|
|
98
|
+
// Execute generator and collect all results
|
|
99
|
+
const preliminaryResults = [];
|
|
100
|
+
let lastEmittedValue = null;
|
|
101
|
+
let hasEmittedValue = false;
|
|
102
|
+
for await (const event of tool.function.execute(validatedInput, context)) {
|
|
103
|
+
hasEmittedValue = true;
|
|
104
|
+
// Validate event against eventSchema
|
|
105
|
+
const validatedEvent = validateToolOutput(tool.function.eventSchema, event);
|
|
106
|
+
preliminaryResults.push(validatedEvent);
|
|
107
|
+
lastEmittedValue = validatedEvent;
|
|
108
|
+
// Emit preliminary result via callback
|
|
109
|
+
if (onPreliminaryResult) {
|
|
110
|
+
onPreliminaryResult(toolCall.id, validatedEvent);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Generator must emit at least one value
|
|
114
|
+
if (!hasEmittedValue) {
|
|
115
|
+
throw new Error(`Generator tool "${toolCall.name}" completed without emitting any values`);
|
|
116
|
+
}
|
|
117
|
+
// Validate the last emitted value against outputSchema (this is the final result)
|
|
118
|
+
const finalResult = validateToolOutput(tool.function.outputSchema, lastEmittedValue);
|
|
119
|
+
// Remove last item from preliminaryResults since it's the final output
|
|
120
|
+
preliminaryResults.pop();
|
|
121
|
+
return {
|
|
122
|
+
toolCallId: toolCall.id,
|
|
123
|
+
toolName: toolCall.name,
|
|
124
|
+
result: finalResult,
|
|
125
|
+
preliminaryResults,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
return {
|
|
130
|
+
toolCallId: toolCall.id,
|
|
131
|
+
toolName: toolCall.name,
|
|
132
|
+
result: null,
|
|
133
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Execute a tool call
|
|
139
|
+
* Automatically detects if it's a regular or generator tool
|
|
140
|
+
*/
|
|
141
|
+
export async function executeTool(tool, toolCall, context, onPreliminaryResult) {
|
|
142
|
+
if (!hasExecuteFunction(tool)) {
|
|
143
|
+
throw new Error(`Tool "${toolCall.name}" has no execute function. Use manual tool execution.`);
|
|
144
|
+
}
|
|
145
|
+
if (isGeneratorTool(tool)) {
|
|
146
|
+
return executeGeneratorTool(tool, toolCall, context, onPreliminaryResult);
|
|
147
|
+
}
|
|
148
|
+
return executeRegularTool(tool, toolCall, context);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Find a tool by name in the tools array
|
|
152
|
+
*/
|
|
153
|
+
export function findToolByName(tools, name) {
|
|
154
|
+
return tools.find((tool) => tool.function.name === name);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Format tool execution result as a string for sending to the model
|
|
158
|
+
*/
|
|
159
|
+
export function formatToolResultForModel(result) {
|
|
160
|
+
if (result.error) {
|
|
161
|
+
return JSON.stringify({
|
|
162
|
+
error: result.error.message,
|
|
163
|
+
toolName: result.toolName,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return JSON.stringify(result.result);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Create a user-friendly error message for tool execution errors
|
|
170
|
+
*/
|
|
171
|
+
export function formatToolExecutionError(error, toolCall) {
|
|
172
|
+
if (error instanceof ZodError) {
|
|
173
|
+
const issues = error.issues.map((issue) => ({
|
|
174
|
+
path: issue.path.join("."),
|
|
175
|
+
message: issue.message,
|
|
176
|
+
}));
|
|
177
|
+
return `Tool "${toolCall.name}" validation error:\n${JSON.stringify(issues, null, 2)}`;
|
|
178
|
+
}
|
|
179
|
+
return `Tool "${toolCall.name}" execution error: ${error.message}`;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=tool-executor.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as models from "../models/index.js";
|
|
2
|
+
import { EnhancedTool, ToolExecutionResult } from "./tool-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Options for tool execution
|
|
5
|
+
*/
|
|
6
|
+
export interface ToolExecutionOptions {
|
|
7
|
+
maxRounds?: number;
|
|
8
|
+
onPreliminaryResult?: (toolCallId: string, result: unknown) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Result of the tool execution loop
|
|
12
|
+
*/
|
|
13
|
+
export interface ToolOrchestrationResult {
|
|
14
|
+
finalResponse: models.OpenResponsesNonStreamingResponse;
|
|
15
|
+
allResponses: models.OpenResponsesNonStreamingResponse[];
|
|
16
|
+
toolExecutionResults: ToolExecutionResult[];
|
|
17
|
+
conversationInput: models.OpenResponsesInput;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Execute tool calls and manage multi-turn conversations
|
|
21
|
+
* This orchestrates the loop of: request -> tool calls -> execute -> send results -> repeat
|
|
22
|
+
*
|
|
23
|
+
* @param sendRequest - Function to send a request and get a response
|
|
24
|
+
* @param initialInput - Starting input for the conversation
|
|
25
|
+
* @param tools - Enhanced tools with Zod schemas and execute functions
|
|
26
|
+
* @param apiTools - Converted tools in API format (JSON Schema)
|
|
27
|
+
* @param options - Execution options
|
|
28
|
+
* @returns Result containing final response and all execution data
|
|
29
|
+
*/
|
|
30
|
+
export declare function executeToolLoop(sendRequest: (input: models.OpenResponsesInput, tools: any[]) => Promise<models.OpenResponsesNonStreamingResponse>, initialInput: models.OpenResponsesInput, tools: EnhancedTool[], apiTools: any[], options?: ToolExecutionOptions): Promise<ToolOrchestrationResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Convert tool execution results to a map for easy lookup
|
|
33
|
+
*/
|
|
34
|
+
export declare function toolResultsToMap(results: ToolExecutionResult[]): Map<string, {
|
|
35
|
+
result: unknown;
|
|
36
|
+
preliminaryResults?: unknown[];
|
|
37
|
+
}>;
|
|
38
|
+
/**
|
|
39
|
+
* Build a summary of tool executions for debugging/logging
|
|
40
|
+
*/
|
|
41
|
+
export declare function summarizeToolExecutions(results: ToolExecutionResult[]): string;
|
|
42
|
+
/**
|
|
43
|
+
* Check if any tool executions had errors
|
|
44
|
+
*/
|
|
45
|
+
export declare function hasToolExecutionErrors(results: ToolExecutionResult[]): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Get all tool execution errors
|
|
48
|
+
*/
|
|
49
|
+
export declare function getToolExecutionErrors(results: ToolExecutionResult[]): Error[];
|
|
50
|
+
//# sourceMappingURL=tool-orchestrator.d.ts.map
|