@openrouter/sdk 0.4.0 → 0.5.1
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/lib/config.d.ts +8 -2
- package/esm/lib/config.js +2 -2
- package/esm/lib/model-result.d.ts +4 -3
- package/esm/lib/model-result.js +68 -9
- package/esm/lib/sdks.d.ts +1 -1
- package/esm/lib/sdks.js +28 -8
- package/esm/lib/stream-transformers.js +7 -7
- package/esm/lib/stream-type-guards.d.ts +1 -1
- package/esm/lib/stream-type-guards.js +1 -1
- package/esm/lib/tool-executor.js +11 -14
- package/esm/lib/tool-orchestrator.js +2 -2
- package/esm/lib/tool-types.d.ts +1 -1
- package/jsr.json +1 -1
- package/package.json +2 -2
package/esm/lib/config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Hook } from "../hooks/types.js";
|
|
1
2
|
import { HTTPClient } from "./http.js";
|
|
2
3
|
import { Logger } from "./logger.js";
|
|
3
4
|
import { RetryConfig } from "./retries.js";
|
|
@@ -40,13 +41,18 @@ export type SDKOptions = {
|
|
|
40
41
|
retryConfig?: RetryConfig;
|
|
41
42
|
timeoutMs?: number;
|
|
42
43
|
debugLogger?: Logger;
|
|
44
|
+
/**
|
|
45
|
+
* Hooks for request/response lifecycle events.
|
|
46
|
+
* Can be a single hook object or an array of hooks.
|
|
47
|
+
*/
|
|
48
|
+
hooks?: Hook | Hook[];
|
|
43
49
|
};
|
|
44
50
|
export declare function serverURLFromOptions(options: SDKOptions): URL | null;
|
|
45
51
|
export declare const SDK_METADATA: {
|
|
46
52
|
readonly language: "typescript";
|
|
47
53
|
readonly openapiDocVersion: "1.0.0";
|
|
48
|
-
readonly sdkVersion: "0.
|
|
54
|
+
readonly sdkVersion: "0.5.1";
|
|
49
55
|
readonly genVersion: "2.788.4";
|
|
50
|
-
readonly userAgent: "speakeasy-sdk/typescript 0.
|
|
56
|
+
readonly userAgent: "speakeasy-sdk/typescript 0.5.1 2.788.4 1.0.0 @openrouter/sdk";
|
|
51
57
|
};
|
|
52
58
|
//# sourceMappingURL=config.d.ts.map
|
package/esm/lib/config.js
CHANGED
|
@@ -26,8 +26,8 @@ export function serverURLFromOptions(options) {
|
|
|
26
26
|
export const SDK_METADATA = {
|
|
27
27
|
language: "typescript",
|
|
28
28
|
openapiDocVersion: "1.0.0",
|
|
29
|
-
sdkVersion: "0.
|
|
29
|
+
sdkVersion: "0.5.1",
|
|
30
30
|
genVersion: "2.788.4",
|
|
31
|
-
userAgent: "speakeasy-sdk/typescript 0.
|
|
31
|
+
userAgent: "speakeasy-sdk/typescript 0.5.1 2.788.4 1.0.0 @openrouter/sdk",
|
|
32
32
|
};
|
|
33
33
|
//# sourceMappingURL=config.js.map
|
|
@@ -267,10 +267,11 @@ export declare class ModelResult<TTools extends readonly Tool[]> {
|
|
|
267
267
|
*
|
|
268
268
|
* Stream incremental message updates as content is added in responses format.
|
|
269
269
|
* Each iteration yields an updated version of the message with new content.
|
|
270
|
-
* Also yields OpenResponsesFunctionCallOutput after tool execution completes.
|
|
271
|
-
* Returns ResponsesOutputMessage or OpenResponsesFunctionCallOutput
|
|
270
|
+
* Also yields function_call items and OpenResponsesFunctionCallOutput after tool execution completes.
|
|
271
|
+
* Returns ResponsesOutputMessage, ResponsesOutputItemFunctionCall, or OpenResponsesFunctionCallOutput
|
|
272
|
+
* compatible with OpenAI Responses API format.
|
|
272
273
|
*/
|
|
273
|
-
getNewMessagesStream(): AsyncIterableIterator<models.ResponsesOutputMessage | models.OpenResponsesFunctionCallOutput>;
|
|
274
|
+
getNewMessagesStream(): AsyncIterableIterator<models.ResponsesOutputMessage | models.OpenResponsesFunctionCallOutput | models.ResponsesOutputItemFunctionCall>;
|
|
274
275
|
/**
|
|
275
276
|
* Stream only reasoning deltas as they arrive.
|
|
276
277
|
* This filters the full event stream to only yield reasoning content.
|
package/esm/lib/model-result.js
CHANGED
|
@@ -8,7 +8,7 @@ import { executeTool } from './tool-executor.js';
|
|
|
8
8
|
import { executeNextTurnParamsFunctions, applyNextTurnParamsToRequest } from './next-turn-params.js';
|
|
9
9
|
import { hasExecuteFunction } from './tool-types.js';
|
|
10
10
|
import { isStopConditionMet, stepCountIs } from './stop-conditions.js';
|
|
11
|
-
import { isOutputMessage,
|
|
11
|
+
import { isOutputMessage, isFunctionCallItem, isReasoningOutputItem, isWebSearchCallOutputItem, isFileSearchCallOutputItem, isImageGenerationCallOutputItem, hasTypeProperty, } from './stream-type-guards.js';
|
|
12
12
|
/**
|
|
13
13
|
* Default maximum number of tool execution steps if no stopWhen is specified.
|
|
14
14
|
* This prevents infinite loops in tool execution.
|
|
@@ -305,6 +305,32 @@ export class ModelResult {
|
|
|
305
305
|
const tool = this.options.tools?.find((t) => t.function.name === toolCall.name);
|
|
306
306
|
if (!tool || !hasExecuteFunction(tool))
|
|
307
307
|
continue;
|
|
308
|
+
// Check if arguments failed to parse (remained as string instead of object)
|
|
309
|
+
// This happens when the model returns invalid JSON for tool call arguments
|
|
310
|
+
// We use 'unknown' cast because the type system doesn't know arguments can be a string
|
|
311
|
+
// when JSON parsing fails in stream-transformers.ts
|
|
312
|
+
const args = toolCall.arguments;
|
|
313
|
+
if (typeof args === 'string') {
|
|
314
|
+
const rawArgs = args;
|
|
315
|
+
const errorMessage = `Failed to parse tool call arguments for "${toolCall.name}": The model provided invalid JSON. ` +
|
|
316
|
+
`Raw arguments received: "${rawArgs}". ` +
|
|
317
|
+
`Please provide valid JSON arguments for this tool call.`;
|
|
318
|
+
// Emit error event if broadcaster exists
|
|
319
|
+
if (this.toolEventBroadcaster) {
|
|
320
|
+
this.toolEventBroadcaster.push({
|
|
321
|
+
type: 'tool_result',
|
|
322
|
+
toolCallId: toolCall.id,
|
|
323
|
+
result: { error: errorMessage },
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
toolResults.push({
|
|
327
|
+
type: 'function_call_output',
|
|
328
|
+
id: `output_${toolCall.id}`,
|
|
329
|
+
callId: toolCall.id,
|
|
330
|
+
output: JSON.stringify({ error: errorMessage }),
|
|
331
|
+
});
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
308
334
|
// Track preliminary results for this specific tool call
|
|
309
335
|
const preliminaryResultsForCall = [];
|
|
310
336
|
// Create callback for real-time preliminary results
|
|
@@ -352,7 +378,13 @@ export class ModelResult {
|
|
|
352
378
|
async resolveAsyncFunctionsForTurn(turnContext) {
|
|
353
379
|
if (hasAsyncFunctions(this.options.request)) {
|
|
354
380
|
const resolved = await resolveAsyncFunctions(this.options.request, turnContext);
|
|
355
|
-
|
|
381
|
+
// Preserve accumulated input from previous turns
|
|
382
|
+
const preservedInput = this.resolvedRequest?.input;
|
|
383
|
+
this.resolvedRequest = {
|
|
384
|
+
...resolved,
|
|
385
|
+
stream: false,
|
|
386
|
+
...(preservedInput !== undefined && { input: preservedInput }),
|
|
387
|
+
};
|
|
356
388
|
}
|
|
357
389
|
}
|
|
358
390
|
/**
|
|
@@ -379,8 +411,15 @@ export class ModelResult {
|
|
|
379
411
|
* @returns The new response from the API
|
|
380
412
|
*/
|
|
381
413
|
async makeFollowupRequest(currentResponse, toolResults) {
|
|
382
|
-
// Build new input
|
|
414
|
+
// Build new input preserving original conversation + tool results
|
|
415
|
+
const originalInput = this.resolvedRequest?.input;
|
|
416
|
+
const normalizedOriginalInput = Array.isArray(originalInput)
|
|
417
|
+
? originalInput
|
|
418
|
+
: originalInput
|
|
419
|
+
? [{ role: 'user', content: originalInput }]
|
|
420
|
+
: [];
|
|
383
421
|
const newInput = [
|
|
422
|
+
...normalizedOriginalInput,
|
|
384
423
|
...(Array.isArray(currentResponse.output)
|
|
385
424
|
? currentResponse.output
|
|
386
425
|
: [currentResponse.output]),
|
|
@@ -389,9 +428,13 @@ export class ModelResult {
|
|
|
389
428
|
if (!this.resolvedRequest) {
|
|
390
429
|
throw new Error('Request not initialized');
|
|
391
430
|
}
|
|
392
|
-
|
|
431
|
+
// Update resolvedRequest.input with accumulated conversation for next turn
|
|
432
|
+
this.resolvedRequest = {
|
|
393
433
|
...this.resolvedRequest,
|
|
394
434
|
input: newInput,
|
|
435
|
+
};
|
|
436
|
+
const newRequest = {
|
|
437
|
+
...this.resolvedRequest,
|
|
395
438
|
stream: false,
|
|
396
439
|
};
|
|
397
440
|
const newResult = await betaResponsesSend(this.options.client, newRequest, this.options.options);
|
|
@@ -890,8 +933,16 @@ export class ModelResult {
|
|
|
890
933
|
yield* buildItemsStream(this.reusableStream);
|
|
891
934
|
// Execute tools if needed
|
|
892
935
|
await this.executeToolsIfNeeded();
|
|
893
|
-
// Yield function
|
|
936
|
+
// Yield function calls and outputs for each tool round
|
|
894
937
|
for (const round of this.allToolExecutionRounds) {
|
|
938
|
+
// Round 0's function_calls already yielded via buildItemsStream
|
|
939
|
+
if (round.round > 0) {
|
|
940
|
+
for (const item of round.response.output) {
|
|
941
|
+
if (isFunctionCallItem(item)) {
|
|
942
|
+
yield item;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
895
946
|
for (const toolResult of round.toolResults) {
|
|
896
947
|
yield toolResult;
|
|
897
948
|
}
|
|
@@ -900,7 +951,7 @@ export class ModelResult {
|
|
|
900
951
|
if (this.finalResponse && this.allToolExecutionRounds.length > 0) {
|
|
901
952
|
for (const item of this.finalResponse.output) {
|
|
902
953
|
if (isOutputMessage(item) ||
|
|
903
|
-
|
|
954
|
+
isFunctionCallItem(item) ||
|
|
904
955
|
isReasoningOutputItem(item) ||
|
|
905
956
|
isWebSearchCallOutputItem(item) ||
|
|
906
957
|
isFileSearchCallOutputItem(item) ||
|
|
@@ -918,8 +969,9 @@ export class ModelResult {
|
|
|
918
969
|
*
|
|
919
970
|
* Stream incremental message updates as content is added in responses format.
|
|
920
971
|
* Each iteration yields an updated version of the message with new content.
|
|
921
|
-
* Also yields OpenResponsesFunctionCallOutput after tool execution completes.
|
|
922
|
-
* Returns ResponsesOutputMessage or OpenResponsesFunctionCallOutput
|
|
972
|
+
* Also yields function_call items and OpenResponsesFunctionCallOutput after tool execution completes.
|
|
973
|
+
* Returns ResponsesOutputMessage, ResponsesOutputItemFunctionCall, or OpenResponsesFunctionCallOutput
|
|
974
|
+
* compatible with OpenAI Responses API format.
|
|
923
975
|
*/
|
|
924
976
|
getNewMessagesStream() {
|
|
925
977
|
return async function* () {
|
|
@@ -931,8 +983,15 @@ export class ModelResult {
|
|
|
931
983
|
yield* buildResponsesMessageStream(this.reusableStream);
|
|
932
984
|
// Execute tools if needed
|
|
933
985
|
await this.executeToolsIfNeeded();
|
|
934
|
-
// Yield function
|
|
986
|
+
// Yield function calls and their outputs for each executed tool
|
|
935
987
|
for (const round of this.allToolExecutionRounds) {
|
|
988
|
+
// First yield the function_call items from the response that triggered tool execution
|
|
989
|
+
for (const item of round.response.output) {
|
|
990
|
+
if (isFunctionCallItem(item)) {
|
|
991
|
+
yield item;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
// Then yield the function_call_output results
|
|
936
995
|
for (const toolResult of round.toolResults) {
|
|
937
996
|
yield toolResult;
|
|
938
997
|
}
|
package/esm/lib/sdks.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SDKHooks } from "../hooks/hooks.js";
|
|
2
|
-
import { HookContext } from "../hooks/types.js";
|
|
2
|
+
import type { HookContext } from "../hooks/types.js";
|
|
3
3
|
import { ConnectionError, InvalidRequestError, RequestAbortedError, RequestTimeoutError, UnexpectedClientError } from "../models/errors/httpclienterrors.js";
|
|
4
4
|
import { Result } from "../types/fp.js";
|
|
5
5
|
import { SDKOptions } from "./config.js";
|
package/esm/lib/sdks.js
CHANGED
|
@@ -13,7 +13,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
13
13
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
14
14
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
15
15
|
};
|
|
16
|
-
var _ClientSDK_httpClient, _ClientSDK_hooks, _ClientSDK_logger;
|
|
16
|
+
var _ClientSDK_instances, _ClientSDK_httpClient, _ClientSDK_hooks, _ClientSDK_logger, _ClientSDK_registerHook;
|
|
17
17
|
import { SDKHooks } from "../hooks/hooks.js";
|
|
18
18
|
import { ConnectionError, InvalidRequestError, RequestAbortedError, RequestTimeoutError, UnexpectedClientError, } from "../models/errors/httpclienterrors.js";
|
|
19
19
|
import { ERR, OK } from "../types/fp.js";
|
|
@@ -33,18 +33,22 @@ const isBrowserLike = webWorkerLike
|
|
|
33
33
|
|| (typeof window === "object" && typeof window.document !== "undefined");
|
|
34
34
|
export class ClientSDK {
|
|
35
35
|
constructor(options = {}) {
|
|
36
|
+
_ClientSDK_instances.add(this);
|
|
36
37
|
_ClientSDK_httpClient.set(this, void 0);
|
|
37
38
|
_ClientSDK_hooks.set(this, void 0);
|
|
38
39
|
_ClientSDK_logger.set(this, void 0);
|
|
39
|
-
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
&& "hooks" in opt
|
|
43
|
-
&& opt.hooks instanceof SDKHooks) {
|
|
44
|
-
__classPrivateFieldSet(this, _ClientSDK_hooks, opt.hooks, "f");
|
|
40
|
+
// Reuse existing SDKHooks if passed (for sub-SDKs)
|
|
41
|
+
if (options.hooks instanceof SDKHooks) {
|
|
42
|
+
__classPrivateFieldSet(this, _ClientSDK_hooks, options.hooks, "f");
|
|
45
43
|
}
|
|
46
44
|
else {
|
|
47
45
|
__classPrivateFieldSet(this, _ClientSDK_hooks, new SDKHooks(), "f");
|
|
46
|
+
if (options.hooks) {
|
|
47
|
+
const hooksArray = Array.isArray(options.hooks) ? options.hooks : [options.hooks];
|
|
48
|
+
for (const hook of hooksArray) {
|
|
49
|
+
__classPrivateFieldGet(this, _ClientSDK_instances, "m", _ClientSDK_registerHook).call(this, hook);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
48
52
|
}
|
|
49
53
|
const defaultHttpClient = new HTTPClient();
|
|
50
54
|
options.httpClient = options.httpClient || defaultHttpClient;
|
|
@@ -185,7 +189,23 @@ export class ClientSDK {
|
|
|
185
189
|
});
|
|
186
190
|
}
|
|
187
191
|
}
|
|
188
|
-
_ClientSDK_httpClient = new WeakMap(), _ClientSDK_hooks = new WeakMap(), _ClientSDK_logger = new WeakMap()
|
|
192
|
+
_ClientSDK_httpClient = new WeakMap(), _ClientSDK_hooks = new WeakMap(), _ClientSDK_logger = new WeakMap(), _ClientSDK_instances = new WeakSet(), _ClientSDK_registerHook = function _ClientSDK_registerHook(hook) {
|
|
193
|
+
if ("sdkInit" in hook) {
|
|
194
|
+
__classPrivateFieldGet(this, _ClientSDK_hooks, "f").registerSDKInitHook(hook);
|
|
195
|
+
}
|
|
196
|
+
if ("beforeCreateRequest" in hook) {
|
|
197
|
+
__classPrivateFieldGet(this, _ClientSDK_hooks, "f").registerBeforeCreateRequestHook(hook);
|
|
198
|
+
}
|
|
199
|
+
if ("beforeRequest" in hook) {
|
|
200
|
+
__classPrivateFieldGet(this, _ClientSDK_hooks, "f").registerBeforeRequestHook(hook);
|
|
201
|
+
}
|
|
202
|
+
if ("afterSuccess" in hook) {
|
|
203
|
+
__classPrivateFieldGet(this, _ClientSDK_hooks, "f").registerAfterSuccessHook(hook);
|
|
204
|
+
}
|
|
205
|
+
if ("afterError" in hook) {
|
|
206
|
+
__classPrivateFieldGet(this, _ClientSDK_hooks, "f").registerAfterErrorHook(hook);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
189
209
|
const jsonLikeContentTypeRE = /(application|text)\/.*?\+*json.*/;
|
|
190
210
|
const jsonlLikeContentTypeRE = /(application|text)\/(.*?\+*\bjsonl\b.*|.*?\+*\bx-ndjson\b.*)/;
|
|
191
211
|
async function logRequest(logger, req) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isOutputTextDeltaEvent, isReasoningDeltaEvent, isFunctionCallArgumentsDeltaEvent, isOutputItemAddedEvent, isOutputItemDoneEvent, isResponseCompletedEvent, isResponseFailedEvent, isResponseIncompleteEvent, isFunctionCallArgumentsDoneEvent, isOutputMessage,
|
|
1
|
+
import { isOutputTextDeltaEvent, isReasoningDeltaEvent, isFunctionCallArgumentsDeltaEvent, isOutputItemAddedEvent, isOutputItemDoneEvent, isResponseCompletedEvent, isResponseFailedEvent, isResponseIncompleteEvent, isFunctionCallArgumentsDoneEvent, isOutputMessage, isFunctionCallItem, isReasoningOutputItem, isWebSearchCallOutputItem, isFileSearchCallOutputItem, isImageGenerationCallOutputItem, isOutputTextPart, isRefusalPart, isFileCitationAnnotation, isURLCitationAnnotation, isFilePathAnnotation, } from './stream-type-guards.js';
|
|
2
2
|
/**
|
|
3
3
|
* Extract text deltas from responses stream events
|
|
4
4
|
*/
|
|
@@ -149,7 +149,7 @@ function handleOutputItemAdded(event, itemsInProgress) {
|
|
|
149
149
|
content: [],
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
|
-
if (
|
|
152
|
+
if (isFunctionCallItem(item)) {
|
|
153
153
|
// Use item.id if available (matches itemId in delta events), fall back to callId
|
|
154
154
|
const itemKey = item.id ?? item.callId;
|
|
155
155
|
itemsInProgress.set(itemKey, {
|
|
@@ -276,7 +276,7 @@ function handleOutputItemDone(event, itemsInProgress) {
|
|
|
276
276
|
itemsInProgress.delete(item.id);
|
|
277
277
|
return item;
|
|
278
278
|
}
|
|
279
|
-
if (
|
|
279
|
+
if (isFunctionCallItem(item)) {
|
|
280
280
|
// Use item.id if available (matches itemId in delta events), fall back to callId
|
|
281
281
|
itemsInProgress.delete(item.id ?? item.callId);
|
|
282
282
|
return item;
|
|
@@ -438,7 +438,7 @@ export function extractTextFromResponse(response) {
|
|
|
438
438
|
export function extractToolCallsFromResponse(response) {
|
|
439
439
|
const toolCalls = [];
|
|
440
440
|
for (const item of response.output) {
|
|
441
|
-
if (
|
|
441
|
+
if (isFunctionCallItem(item)) {
|
|
442
442
|
try {
|
|
443
443
|
const parsedArguments = JSON.parse(item.arguments);
|
|
444
444
|
toolCalls.push({
|
|
@@ -474,7 +474,7 @@ export async function* buildToolCallStream(stream) {
|
|
|
474
474
|
}
|
|
475
475
|
switch (event.type) {
|
|
476
476
|
case 'response.output_item.added': {
|
|
477
|
-
if (isOutputItemAddedEvent(event) && event.item &&
|
|
477
|
+
if (isOutputItemAddedEvent(event) && event.item && isFunctionCallItem(event.item)) {
|
|
478
478
|
toolCallsInProgress.set(event.item.callId, {
|
|
479
479
|
id: event.item.callId,
|
|
480
480
|
name: event.item.name,
|
|
@@ -521,7 +521,7 @@ export async function* buildToolCallStream(stream) {
|
|
|
521
521
|
break;
|
|
522
522
|
}
|
|
523
523
|
case 'response.output_item.done': {
|
|
524
|
-
if (isOutputItemDoneEvent(event) && event.item &&
|
|
524
|
+
if (isOutputItemDoneEvent(event) && event.item && isFunctionCallItem(event.item)) {
|
|
525
525
|
// Yield final tool call if we haven't already
|
|
526
526
|
if (toolCallsInProgress.has(event.item.callId)) {
|
|
527
527
|
try {
|
|
@@ -709,7 +709,7 @@ export function convertToClaudeMessage(response) {
|
|
|
709
709
|
break;
|
|
710
710
|
}
|
|
711
711
|
case 'function_call': {
|
|
712
|
-
if (
|
|
712
|
+
if (isFunctionCallItem(item)) {
|
|
713
713
|
let parsedInput;
|
|
714
714
|
try {
|
|
715
715
|
parsedInput = JSON.parse(item.arguments);
|
|
@@ -13,7 +13,7 @@ export declare function isResponseFailedEvent(event: models.OpenResponsesStreamE
|
|
|
13
13
|
export declare function isResponseIncompleteEvent(event: models.OpenResponsesStreamEvent): event is models.OpenResponsesStreamEventResponseIncomplete;
|
|
14
14
|
export declare function isFunctionCallArgumentsDoneEvent(event: models.OpenResponsesStreamEvent): event is models.OpenResponsesStreamEventResponseFunctionCallArgumentsDone;
|
|
15
15
|
export declare function isOutputMessage(item: unknown): item is models.ResponsesOutputMessage;
|
|
16
|
-
export declare function
|
|
16
|
+
export declare function isFunctionCallItem(item: unknown): item is models.ResponsesOutputItemFunctionCall;
|
|
17
17
|
export declare function isReasoningOutputItem(item: unknown): item is models.ResponsesOutputItemReasoning;
|
|
18
18
|
export declare function isWebSearchCallOutputItem(item: unknown): item is models.ResponsesWebSearchCallOutput;
|
|
19
19
|
export declare function isFileSearchCallOutputItem(item: unknown): item is models.ResponsesOutputItemFileSearchCall;
|
|
@@ -37,7 +37,7 @@ export function isOutputMessage(item) {
|
|
|
37
37
|
'type' in item &&
|
|
38
38
|
item.type === 'message');
|
|
39
39
|
}
|
|
40
|
-
export function
|
|
40
|
+
export function isFunctionCallItem(item) {
|
|
41
41
|
return (typeof item === 'object' &&
|
|
42
42
|
item !== null &&
|
|
43
43
|
'type' in item &&
|
package/esm/lib/tool-executor.js
CHANGED
|
@@ -158,43 +158,40 @@ export async function executeGeneratorTool(tool, toolCall, context, onPreliminar
|
|
|
158
158
|
// Validate input - the schema validation ensures type safety at runtime
|
|
159
159
|
// The inputSchema's inferred type matches the execute function's parameter type by construction
|
|
160
160
|
const validatedInput = validateToolInput(tool.function.inputSchema, toolCall.arguments);
|
|
161
|
-
// Stream preliminary results in realtime
|
|
162
|
-
// Final result is identified by: matches outputSchema BUT NOT eventSchema
|
|
163
|
-
// All other yields are preliminary results (validated against eventSchema)
|
|
164
|
-
// If no explicit final result is found, the last emitted value is used as the final result
|
|
165
161
|
const preliminaryResults = [];
|
|
166
162
|
let finalResult = undefined;
|
|
167
163
|
let hasFinalResult = false;
|
|
168
164
|
let lastEmittedValue = undefined;
|
|
169
165
|
let hasEmittedValue = false;
|
|
170
|
-
|
|
166
|
+
const iterator = tool.function.execute(validatedInput, context);
|
|
167
|
+
let iterResult = await iterator.next();
|
|
168
|
+
while (!iterResult.done) {
|
|
169
|
+
const event = iterResult.value;
|
|
171
170
|
lastEmittedValue = event;
|
|
172
171
|
hasEmittedValue = true;
|
|
173
|
-
// Try to determine if this is the final result:
|
|
174
|
-
// It must match outputSchema but NOT match eventSchema
|
|
175
172
|
const matchesOutputSchema = tryValidate(tool.function.outputSchema, event);
|
|
176
173
|
const matchesEventSchema = tryValidate(tool.function.eventSchema, event);
|
|
177
174
|
if (matchesOutputSchema && !matchesEventSchema && !hasFinalResult) {
|
|
178
|
-
// This is the final result - matches output but not event schema
|
|
179
175
|
finalResult = validateToolOutput(tool.function.outputSchema, event);
|
|
180
176
|
hasFinalResult = true;
|
|
181
177
|
}
|
|
182
178
|
else {
|
|
183
|
-
// This is a preliminary result - validate against eventSchema and emit in realtime
|
|
184
179
|
const validatedPreliminary = validateToolOutput(tool.function.eventSchema, event);
|
|
185
180
|
preliminaryResults.push(validatedPreliminary);
|
|
186
181
|
if (onPreliminaryResult) {
|
|
187
182
|
onPreliminaryResult(toolCall.id, validatedPreliminary);
|
|
188
183
|
}
|
|
189
184
|
}
|
|
185
|
+
iterResult = await iterator.next();
|
|
190
186
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
187
|
+
if (iterResult.value !== undefined) {
|
|
188
|
+
finalResult = validateToolOutput(tool.function.outputSchema, iterResult.value);
|
|
189
|
+
hasFinalResult = true;
|
|
194
190
|
}
|
|
195
|
-
// If no explicit final result was found (no yield matched outputSchema but not eventSchema),
|
|
196
|
-
// use the last emitted value as the final result
|
|
197
191
|
if (!hasFinalResult) {
|
|
192
|
+
if (!hasEmittedValue) {
|
|
193
|
+
throw new Error(`Generator tool "${toolCall.name}" completed without emitting any values or returning a result`);
|
|
194
|
+
}
|
|
198
195
|
finalResult = validateToolOutput(tool.function.outputSchema, lastEmittedValue);
|
|
199
196
|
}
|
|
200
197
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { extractToolCallsFromResponse, responseHasToolCalls } from './stream-transformers.js';
|
|
2
|
-
import {
|
|
2
|
+
import { isFunctionCallItem } from './stream-type-guards.js';
|
|
3
3
|
import { executeTool, findToolByName } from './tool-executor.js';
|
|
4
4
|
import { hasExecuteFunction } from './tool-types.js';
|
|
5
5
|
import { buildTurnContext } from './turn-context.js';
|
|
@@ -61,7 +61,7 @@ export async function executeToolLoop(sendRequest, initialInput, initialRequest,
|
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
63
|
// Find the raw tool call from the response output
|
|
64
|
-
const rawToolCall = currentResponse.output.find((item) =>
|
|
64
|
+
const rawToolCall = currentResponse.output.find((item) => isFunctionCallItem(item) && item.callId === toolCall.id);
|
|
65
65
|
if (!rawToolCall) {
|
|
66
66
|
throw new Error(`Could not find raw tool call for ${toolCall.id}`);
|
|
67
67
|
}
|
package/esm/lib/tool-types.d.ts
CHANGED
|
@@ -103,7 +103,7 @@ export interface ToolFunctionWithExecute<TInput extends $ZodObject<$ZodShape>, T
|
|
|
103
103
|
export interface ToolFunctionWithGenerator<TInput extends $ZodObject<$ZodShape>, TEvent extends $ZodType = $ZodType<unknown>, TOutput extends $ZodType = $ZodType<unknown>> extends BaseToolFunction<TInput> {
|
|
104
104
|
eventSchema: TEvent;
|
|
105
105
|
outputSchema: TOutput;
|
|
106
|
-
execute: (params: zodInfer<TInput>, context?: TurnContext) => AsyncGenerator<zodInfer<TEvent> | zodInfer<TOutput
|
|
106
|
+
execute: (params: zodInfer<TInput>, context?: TurnContext) => AsyncGenerator<zodInfer<TEvent> | zodInfer<TOutput>, zodInfer<TOutput> | void>;
|
|
107
107
|
}
|
|
108
108
|
/**
|
|
109
109
|
* Manual tool without execute function - requires manual handling by developer
|
package/jsr.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openrouter/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"author": "OpenRouter",
|
|
5
5
|
"description": "The OpenRouter TypeScript SDK is a type-safe toolkit for building AI applications with access to 300+ language models through a unified API.",
|
|
6
6
|
"keywords": [
|
|
@@ -89,4 +89,4 @@
|
|
|
89
89
|
"zod": "^3.25.0 || ^4.0.0"
|
|
90
90
|
},
|
|
91
91
|
"packageManager": "pnpm@10.22.0"
|
|
92
|
-
}
|
|
92
|
+
}
|