@openrouter/sdk 0.3.11 → 0.3.12

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/index.d.ts CHANGED
@@ -17,5 +17,6 @@ export { extractUnsupportedContent, getUnsupportedContentSummary, hasUnsupported
17
17
  export { tool } from './lib/tool.js';
18
18
  export { hasExecuteFunction, isGeneratorTool, isRegularExecuteTool, isToolPreliminaryResultEvent, ToolType, } from './lib/tool-types.js';
19
19
  export { buildTurnContext, normalizeInputToArray } from './lib/turn-context.js';
20
+ export { ToolEventBroadcaster } from './lib/tool-event-broadcaster.js';
20
21
  export * from './sdk/sdk.js';
21
22
  //# sourceMappingURL=index.d.ts.map
package/esm/index.js CHANGED
@@ -21,5 +21,7 @@ export { tool } from './lib/tool.js';
21
21
  export { hasExecuteFunction, isGeneratorTool, isRegularExecuteTool, isToolPreliminaryResultEvent, ToolType, } from './lib/tool-types.js';
22
22
  // Turn context helpers
23
23
  export { buildTurnContext, normalizeInputToArray } from './lib/turn-context.js';
24
+ // Real-time tool event broadcasting
25
+ export { ToolEventBroadcaster } from './lib/tool-event-broadcaster.js';
24
26
  export * from './sdk/sdk.js';
25
27
  //# sourceMappingURL=index.js.map
@@ -47,8 +47,8 @@ export declare function serverURLFromOptions(options: SDKOptions): URL | null;
47
47
  export declare const SDK_METADATA: {
48
48
  readonly language: "typescript";
49
49
  readonly openapiDocVersion: "1.0.0";
50
- readonly sdkVersion: "0.3.11";
50
+ readonly sdkVersion: "0.3.12";
51
51
  readonly genVersion: "2.788.4";
52
- readonly userAgent: "speakeasy-sdk/typescript 0.3.11 2.788.4 1.0.0 @openrouter/sdk";
52
+ readonly userAgent: "speakeasy-sdk/typescript 0.3.12 2.788.4 1.0.0 @openrouter/sdk";
53
53
  };
54
54
  //# 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.3.11",
29
+ sdkVersion: "0.3.12",
30
30
  genVersion: "2.788.4",
31
- userAgent: "speakeasy-sdk/typescript 0.3.11 2.788.4 1.0.0 @openrouter/sdk",
31
+ userAgent: "speakeasy-sdk/typescript 0.3.12 2.788.4 1.0.0 @openrouter/sdk",
32
32
  };
33
33
  //# sourceMappingURL=config.js.map
@@ -37,10 +37,15 @@ export declare class ModelResult<TTools extends readonly Tool[]> {
37
37
  private initPromise;
38
38
  private toolExecutionPromise;
39
39
  private finalResponse;
40
- private preliminaryResults;
40
+ private toolEventBroadcaster;
41
41
  private allToolExecutionRounds;
42
42
  private resolvedRequest;
43
43
  constructor(options: GetResponseOptions<TTools>);
44
+ /**
45
+ * Get or create the tool event broadcaster (lazy initialization).
46
+ * Ensures only one broadcaster exists for the lifetime of this ModelResult.
47
+ */
48
+ private ensureBroadcaster;
44
49
  /**
45
50
  * Type guard to check if a value is a non-streaming response
46
51
  */
@@ -73,7 +78,7 @@ export declare class ModelResult<TTools extends readonly Tool[]> {
73
78
  /**
74
79
  * Stream all response events as they arrive.
75
80
  * Multiple consumers can iterate over this stream concurrently.
76
- * Includes preliminary tool result events after tool execution.
81
+ * Preliminary tool results are streamed in REAL-TIME as generator tools yield.
77
82
  */
78
83
  getFullResponsesStream(): AsyncIterableIterator<ResponseStreamEvent<InferToolEventsUnion<TTools>>>;
79
84
  /**
@@ -95,7 +100,7 @@ export declare class ModelResult<TTools extends readonly Tool[]> {
95
100
  getReasoningStream(): AsyncIterableIterator<string>;
96
101
  /**
97
102
  * Stream tool call argument deltas and preliminary results.
98
- * This filters the full event stream to yield:
103
+ * Preliminary results are streamed in REAL-TIME as generator tools yield.
99
104
  * - Tool call argument deltas as { type: "delta", content: string }
100
105
  * - Preliminary results as { type: "preliminary_result", toolCallId, result }
101
106
  */
@@ -1,3 +1,4 @@
1
+ import { ToolEventBroadcaster } from './tool-event-broadcaster.js';
1
2
  import { betaResponsesSend } from '../funcs/betaResponsesSend.js';
2
3
  import { hasAsyncFunctions, resolveAsyncFunctions, } from './async-params.js';
3
4
  import { ReusableReadableStream } from './reusable-stream.js';
@@ -51,12 +52,22 @@ export class ModelResult {
51
52
  this.initPromise = null;
52
53
  this.toolExecutionPromise = null;
53
54
  this.finalResponse = null;
54
- this.preliminaryResults = new Map();
55
+ this.toolEventBroadcaster = null;
55
56
  this.allToolExecutionRounds = [];
56
57
  // Track resolved request after async function resolution
57
58
  this.resolvedRequest = null;
58
59
  this.options = options;
59
60
  }
61
+ /**
62
+ * Get or create the tool event broadcaster (lazy initialization).
63
+ * Ensures only one broadcaster exists for the lifetime of this ModelResult.
64
+ */
65
+ ensureBroadcaster() {
66
+ if (!this.toolEventBroadcaster) {
67
+ this.toolEventBroadcaster = new ToolEventBroadcaster();
68
+ }
69
+ return this.toolEventBroadcaster;
70
+ }
60
71
  /**
61
72
  * Type guard to check if a value is a non-streaming response
62
73
  */
@@ -91,8 +102,7 @@ export class ModelResult {
91
102
  // Already resolved, extract non-function fields
92
103
  // Since request is CallModelInput, we need to filter out stopWhen
93
104
  // Note: tools are already in API format at this point (converted in callModel())
94
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
95
- const { stopWhen, ...rest } = this.options.request;
105
+ const { stopWhen: _, ...rest } = this.options.request;
96
106
  // Cast to ResolvedCallModelInput - we know it's resolved if hasAsyncFunctions returned false
97
107
  baseRequest = rest;
98
108
  }
@@ -215,11 +225,17 @@ export class ModelResult {
215
225
  if (!tool || !hasExecuteFunction(tool)) {
216
226
  continue;
217
227
  }
218
- const result = await executeTool(tool, toolCall, turnContext);
219
- // Store preliminary results
220
- if (result.preliminaryResults && result.preliminaryResults.length > 0) {
221
- this.preliminaryResults.set(toolCall.id, result.preliminaryResults);
222
- }
228
+ // Create callback for real-time preliminary results
229
+ const onPreliminaryResult = this.toolEventBroadcaster
230
+ ? (callId, resultValue) => {
231
+ this.toolEventBroadcaster?.push({
232
+ type: 'preliminary_result',
233
+ toolCallId: callId,
234
+ result: resultValue,
235
+ });
236
+ }
237
+ : undefined;
238
+ const result = await executeTool(tool, toolCall, turnContext, onPreliminaryResult);
223
239
  toolResults.push({
224
240
  type: 'function_call_output',
225
241
  id: `output_${toolCall.id}`,
@@ -335,7 +351,7 @@ export class ModelResult {
335
351
  /**
336
352
  * Stream all response events as they arrive.
337
353
  * Multiple consumers can iterate over this stream concurrently.
338
- * Includes preliminary tool result events after tool execution.
354
+ * Preliminary tool results are streamed in REAL-TIME as generator tools yield.
339
355
  */
340
356
  getFullResponsesStream() {
341
357
  return async function* () {
@@ -343,24 +359,29 @@ export class ModelResult {
343
359
  if (!this.reusableStream) {
344
360
  throw new Error('Stream not initialized');
345
361
  }
362
+ // Get or create broadcaster for real-time tool events (lazy init prevents race conditions)
363
+ const broadcaster = this.ensureBroadcaster();
364
+ const toolEventConsumer = broadcaster.createConsumer();
365
+ // Start tool execution in background (completes broadcaster when done)
366
+ const executionPromise = this.executeToolsIfNeeded().finally(() => {
367
+ broadcaster.complete();
368
+ });
346
369
  const consumer = this.reusableStream.createConsumer();
347
- // Yield original events directly
370
+ // Yield original API events
348
371
  for await (const event of consumer) {
349
372
  yield event;
350
373
  }
351
- // After stream completes, check if tools were executed and emit preliminary results
352
- await this.executeToolsIfNeeded();
353
- // Emit all preliminary results as new event types
354
- for (const [toolCallId, results] of this.preliminaryResults) {
355
- for (const result of results) {
356
- yield {
357
- type: 'tool.preliminary_result',
358
- toolCallId,
359
- result: result,
360
- timestamp: Date.now(),
361
- };
362
- }
374
+ // Yield tool preliminary results as they arrive (real-time!)
375
+ for await (const event of toolEventConsumer) {
376
+ yield {
377
+ type: 'tool.preliminary_result',
378
+ toolCallId: event.toolCallId,
379
+ result: event.result,
380
+ timestamp: Date.now(),
381
+ };
363
382
  }
383
+ // Ensure execution completed (handles errors)
384
+ await executionPromise;
364
385
  }.call(this);
365
386
  }
366
387
  /**
@@ -423,7 +444,7 @@ export class ModelResult {
423
444
  }
424
445
  /**
425
446
  * Stream tool call argument deltas and preliminary results.
426
- * This filters the full event stream to yield:
447
+ * Preliminary results are streamed in REAL-TIME as generator tools yield.
427
448
  * - Tool call argument deltas as { type: "delta", content: string }
428
449
  * - Preliminary results as { type: "preliminary_result", toolCallId, result }
429
450
  */
@@ -433,25 +454,26 @@ export class ModelResult {
433
454
  if (!this.reusableStream) {
434
455
  throw new Error('Stream not initialized');
435
456
  }
436
- // Yield tool deltas as structured events
457
+ // Get or create broadcaster for real-time tool events (lazy init prevents race conditions)
458
+ const broadcaster = this.ensureBroadcaster();
459
+ const toolEventConsumer = broadcaster.createConsumer();
460
+ // Start tool execution in background (completes broadcaster when done)
461
+ const executionPromise = this.executeToolsIfNeeded().finally(() => {
462
+ broadcaster.complete();
463
+ });
464
+ // Yield tool deltas from API stream
437
465
  for await (const delta of extractToolDeltas(this.reusableStream)) {
438
466
  yield {
439
467
  type: 'delta',
440
468
  content: delta,
441
469
  };
442
470
  }
443
- // After stream completes, check if tools were executed and emit preliminary results
444
- await this.executeToolsIfNeeded();
445
- // Emit all preliminary results
446
- for (const [toolCallId, results] of this.preliminaryResults) {
447
- for (const result of results) {
448
- yield {
449
- type: 'preliminary_result',
450
- toolCallId,
451
- result: result,
452
- };
453
- }
471
+ // Yield tool events as they arrive (real-time!)
472
+ for await (const event of toolEventConsumer) {
473
+ yield event;
454
474
  }
475
+ // Ensure execution completed (handles errors)
476
+ await executionPromise;
455
477
  }.call(this);
456
478
  }
457
479
  /**
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A push-based event broadcaster that supports multiple concurrent consumers.
3
+ * Similar to ReusableReadableStream but for push-based events from tool execution.
4
+ *
5
+ * Each consumer gets their own position in the buffer and receives all events
6
+ * from their join point onward. This enables real-time streaming of generator
7
+ * tool preliminary results to multiple consumers simultaneously.
8
+ *
9
+ * @template T - The event type being broadcast
10
+ */
11
+ export declare class ToolEventBroadcaster<T> {
12
+ private buffer;
13
+ private consumers;
14
+ private nextConsumerId;
15
+ private isComplete;
16
+ private completionError;
17
+ /**
18
+ * Push a new event to all consumers.
19
+ * Events are buffered so late-joining consumers can catch up.
20
+ */
21
+ push(event: T): void;
22
+ /**
23
+ * Mark the broadcaster as complete - no more events will be pushed.
24
+ * Optionally pass an error to signal failure to all consumers.
25
+ * Cleans up buffer and consumers after completion.
26
+ */
27
+ complete(error?: Error): void;
28
+ /**
29
+ * Clean up resources after all consumers have finished.
30
+ * Called automatically after complete(), but can be called manually.
31
+ */
32
+ private cleanup;
33
+ /**
34
+ * Create a new consumer that can independently iterate over events.
35
+ * Consumers can join at any time and will receive events from position 0.
36
+ * Multiple consumers can be created and will all receive the same events.
37
+ */
38
+ createConsumer(): AsyncIterableIterator<T>;
39
+ /**
40
+ * Notify all waiting consumers that new data is available or stream completed
41
+ */
42
+ private notifyWaitingConsumers;
43
+ }
44
+ //# sourceMappingURL=tool-event-broadcaster.d.ts.map
@@ -0,0 +1,146 @@
1
+ /**
2
+ * A push-based event broadcaster that supports multiple concurrent consumers.
3
+ * Similar to ReusableReadableStream but for push-based events from tool execution.
4
+ *
5
+ * Each consumer gets their own position in the buffer and receives all events
6
+ * from their join point onward. This enables real-time streaming of generator
7
+ * tool preliminary results to multiple consumers simultaneously.
8
+ *
9
+ * @template T - The event type being broadcast
10
+ */
11
+ export class ToolEventBroadcaster {
12
+ constructor() {
13
+ this.buffer = [];
14
+ this.consumers = new Map();
15
+ this.nextConsumerId = 0;
16
+ this.isComplete = false;
17
+ this.completionError = null;
18
+ }
19
+ /**
20
+ * Push a new event to all consumers.
21
+ * Events are buffered so late-joining consumers can catch up.
22
+ */
23
+ push(event) {
24
+ if (this.isComplete) {
25
+ return;
26
+ }
27
+ this.buffer.push(event);
28
+ this.notifyWaitingConsumers();
29
+ }
30
+ /**
31
+ * Mark the broadcaster as complete - no more events will be pushed.
32
+ * Optionally pass an error to signal failure to all consumers.
33
+ * Cleans up buffer and consumers after completion.
34
+ */
35
+ complete(error) {
36
+ this.isComplete = true;
37
+ this.completionError = error ?? null;
38
+ this.notifyWaitingConsumers();
39
+ // Schedule cleanup after consumers have processed completion
40
+ queueMicrotask(() => this.cleanup());
41
+ }
42
+ /**
43
+ * Clean up resources after all consumers have finished.
44
+ * Called automatically after complete(), but can be called manually.
45
+ */
46
+ cleanup() {
47
+ // Only cleanup if complete and all consumers are done
48
+ if (this.isComplete && this.consumers.size === 0) {
49
+ this.buffer = [];
50
+ }
51
+ }
52
+ /**
53
+ * Create a new consumer that can independently iterate over events.
54
+ * Consumers can join at any time and will receive events from position 0.
55
+ * Multiple consumers can be created and will all receive the same events.
56
+ */
57
+ createConsumer() {
58
+ const consumerId = this.nextConsumerId++;
59
+ const state = {
60
+ position: 0,
61
+ waitingPromise: null,
62
+ cancelled: false,
63
+ };
64
+ this.consumers.set(consumerId, state);
65
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
66
+ const self = this;
67
+ return {
68
+ async next() {
69
+ const consumer = self.consumers.get(consumerId);
70
+ if (!consumer) {
71
+ return { done: true, value: undefined };
72
+ }
73
+ if (consumer.cancelled) {
74
+ return { done: true, value: undefined };
75
+ }
76
+ // Return buffered event if available
77
+ if (consumer.position < self.buffer.length) {
78
+ const value = self.buffer[consumer.position];
79
+ consumer.position++;
80
+ return { done: false, value };
81
+ }
82
+ // If complete and caught up, we're done
83
+ if (self.isComplete) {
84
+ self.consumers.delete(consumerId);
85
+ self.cleanup();
86
+ if (self.completionError) {
87
+ throw self.completionError;
88
+ }
89
+ return { done: true, value: undefined };
90
+ }
91
+ // Set up waiting promise FIRST to avoid race condition
92
+ const waitPromise = new Promise((resolve, reject) => {
93
+ consumer.waitingPromise = { resolve, reject };
94
+ // Immediately check if we should resolve after setting up promise
95
+ if (self.isComplete ||
96
+ self.completionError ||
97
+ consumer.position < self.buffer.length) {
98
+ resolve();
99
+ }
100
+ });
101
+ await waitPromise;
102
+ consumer.waitingPromise = null;
103
+ // Recursively try again after waking up
104
+ return this.next();
105
+ },
106
+ async return() {
107
+ const consumer = self.consumers.get(consumerId);
108
+ if (consumer) {
109
+ consumer.cancelled = true;
110
+ self.consumers.delete(consumerId);
111
+ self.cleanup();
112
+ }
113
+ return { done: true, value: undefined };
114
+ },
115
+ async throw(e) {
116
+ const consumer = self.consumers.get(consumerId);
117
+ if (consumer) {
118
+ consumer.cancelled = true;
119
+ self.consumers.delete(consumerId);
120
+ self.cleanup();
121
+ }
122
+ throw e;
123
+ },
124
+ [Symbol.asyncIterator]() {
125
+ return this;
126
+ },
127
+ };
128
+ }
129
+ /**
130
+ * Notify all waiting consumers that new data is available or stream completed
131
+ */
132
+ notifyWaitingConsumers() {
133
+ for (const consumer of this.consumers.values()) {
134
+ if (consumer.waitingPromise) {
135
+ if (this.completionError) {
136
+ consumer.waitingPromise.reject(this.completionError);
137
+ }
138
+ else {
139
+ consumer.waitingPromise.resolve();
140
+ }
141
+ consumer.waitingPromise = null;
142
+ }
143
+ }
144
+ }
145
+ }
146
+ //# sourceMappingURL=tool-event-broadcaster.js.map
@@ -1,10 +1,25 @@
1
- import type { ZodType } from 'zod';
1
+ import type { $ZodType } from 'zod/v4/core';
2
2
  import type { APITool, Tool, ParsedToolCall, ToolExecutionResult, TurnContext } from './tool-types.js';
3
+ import * as z4 from 'zod/v4';
4
+ export declare const ZodError: z4.z.core.$constructor<z4.ZodError<unknown>, z4.z.core.$ZodIssue[]>;
3
5
  /**
4
- * Convert a Zod schema to JSON Schema using Zod v4's toJSONSchema function
5
- * Uses type assertion to bridge zod (user schemas) and zod/v4 (toJSONSchema)
6
+ * Recursively remove keys prefixed with ~ from an object.
7
+ * These are metadata properties (like ~standard from Standard Schema)
8
+ * that should not be sent to downstream providers.
9
+ * @see https://github.com/OpenRouterTeam/typescript-sdk/issues/131
10
+ *
11
+ * When given a Record<string, unknown>, returns Record<string, unknown>.
12
+ * When given unknown, returns unknown (preserves primitives, null, etc).
6
13
  */
7
- export declare function convertZodToJsonSchema(zodSchema: ZodType): Record<string, unknown>;
14
+ export declare function sanitizeJsonSchema(obj: Record<string, unknown>): Record<string, unknown>;
15
+ export declare function sanitizeJsonSchema(obj: unknown): unknown;
16
+ /**
17
+ * Convert a Zod schema to JSON Schema using Zod v4's toJSONSchema function.
18
+ * Accepts ZodType from the main zod package for user compatibility.
19
+ * The resulting schema is sanitized to remove metadata properties (like ~standard)
20
+ * that would cause 400 errors with downstream providers.
21
+ */
22
+ export declare function convertZodToJsonSchema(zodSchema: $ZodType): Record<string, unknown>;
8
23
  /**
9
24
  * Convert tools to OpenRouter API format
10
25
  * Accepts readonly arrays for better type compatibility
@@ -14,12 +29,12 @@ export declare function convertToolsToAPIFormat(tools: readonly Tool[]): APITool
14
29
  * Validate tool input against Zod schema
15
30
  * @throws ZodError if validation fails
16
31
  */
17
- export declare function validateToolInput<T>(schema: ZodType<T>, args: unknown): T;
32
+ export declare function validateToolInput<T>(schema: $ZodType<T>, args: unknown): T;
18
33
  /**
19
34
  * Validate tool output against Zod schema
20
35
  * @throws ZodError if validation fails
21
36
  */
22
- export declare function validateToolOutput<T>(schema: ZodType<T>, result: unknown): T;
37
+ export declare function validateToolOutput<T>(schema: $ZodType<T>, result: unknown): T;
23
38
  /**
24
39
  * Parse tool call arguments from JSON string
25
40
  */
@@ -1,15 +1,64 @@
1
- import { toJSONSchema, ZodError } from 'zod/v4';
1
+ import * as z4 from 'zod/v4';
2
2
  import { hasExecuteFunction, isGeneratorTool, isRegularExecuteTool } from './tool-types.js';
3
+ // Re-export ZodError for convenience
4
+ export const ZodError = z4.ZodError;
3
5
  /**
4
- * Convert a Zod schema to JSON Schema using Zod v4's toJSONSchema function
5
- * Uses type assertion to bridge zod (user schemas) and zod/v4 (toJSONSchema)
6
+ * Typeguard to check if a value is a non-null object (not an array).
7
+ */
8
+ function isNonNullObject(value) {
9
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
10
+ }
11
+ export function sanitizeJsonSchema(obj) {
12
+ if (obj === null || typeof obj !== 'object') {
13
+ return obj;
14
+ }
15
+ if (Array.isArray(obj)) {
16
+ return obj.map(sanitizeJsonSchema);
17
+ }
18
+ // At this point, obj is a non-null, non-array object
19
+ // Use typeguard to narrow the type for type-safe property access
20
+ if (!isNonNullObject(obj)) {
21
+ return obj;
22
+ }
23
+ const result = {};
24
+ for (const key of Object.keys(obj)) {
25
+ if (!key.startsWith('~')) {
26
+ result[key] = sanitizeJsonSchema(obj[key]);
27
+ }
28
+ }
29
+ return result;
30
+ }
31
+ /**
32
+ * Typeguard to check if a value is a valid Zod schema compatible with zod/v4.
33
+ * Zod schemas have a _zod property that contains schema metadata.
34
+ */
35
+ function isZodSchema(value) {
36
+ if (typeof value !== 'object' || value === null) {
37
+ return false;
38
+ }
39
+ if (!('_zod' in value)) {
40
+ return false;
41
+ }
42
+ // After the 'in' check, TypeScript knows value has _zod property
43
+ return typeof value._zod === 'object';
44
+ }
45
+ /**
46
+ * Convert a Zod schema to JSON Schema using Zod v4's toJSONSchema function.
47
+ * Accepts ZodType from the main zod package for user compatibility.
48
+ * The resulting schema is sanitized to remove metadata properties (like ~standard)
49
+ * that would cause 400 errors with downstream providers.
6
50
  */
7
51
  export function convertZodToJsonSchema(zodSchema) {
8
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
- const jsonSchema = toJSONSchema(zodSchema, {
52
+ if (!isZodSchema(zodSchema)) {
53
+ throw new Error('Invalid Zod schema provided');
54
+ }
55
+ // Use draft-7 as it's closest to OpenAPI 3.0's JSON Schema variant
56
+ const jsonSchema = z4.toJSONSchema(zodSchema, {
10
57
  target: 'draft-7',
11
58
  });
12
- return jsonSchema;
59
+ // jsonSchema is always a Record<string, unknown> from toJSONSchema
60
+ // The overloaded sanitizeJsonSchema preserves this type
61
+ return sanitizeJsonSchema(jsonSchema);
13
62
  }
14
63
  /**
15
64
  * Convert tools to OpenRouter API format
@@ -29,14 +78,14 @@ export function convertToolsToAPIFormat(tools) {
29
78
  * @throws ZodError if validation fails
30
79
  */
31
80
  export function validateToolInput(schema, args) {
32
- return schema.parse(args);
81
+ return z4.parse(schema, args);
33
82
  }
34
83
  /**
35
84
  * Validate tool output against Zod schema
36
85
  * @throws ZodError if validation fails
37
86
  */
38
87
  export function validateToolOutput(schema, result) {
39
- return schema.parse(result);
88
+ return z4.parse(schema, result);
40
89
  }
41
90
  /**
42
91
  * Parse tool call arguments from JSON string
@@ -1,4 +1,4 @@
1
- import type { ZodObject, ZodRawShape, ZodType, z } from 'zod';
1
+ import type { $ZodObject, $ZodShape, $ZodType, infer as zodInfer } from 'zod/v4/core';
2
2
  import type * as models from '../models/index.js';
3
3
  import type { OpenResponsesStreamEvent } from '../models/index.js';
4
4
  import type { ModelResult } from './model-result.js';
@@ -53,18 +53,18 @@ export type NextTurnParamsFunctions<TInput> = {
53
53
  /**
54
54
  * Base tool function interface with inputSchema
55
55
  */
56
- export interface BaseToolFunction<TInput extends ZodObject<ZodRawShape>> {
56
+ export interface BaseToolFunction<TInput extends $ZodObject<$ZodShape>> {
57
57
  name: string;
58
58
  description?: string;
59
59
  inputSchema: TInput;
60
- nextTurnParams?: NextTurnParamsFunctions<z.infer<TInput>>;
60
+ nextTurnParams?: NextTurnParamsFunctions<zodInfer<TInput>>;
61
61
  }
62
62
  /**
63
63
  * Regular tool with synchronous or asynchronous execute function and optional outputSchema
64
64
  */
65
- export interface ToolFunctionWithExecute<TInput extends ZodObject<ZodRawShape>, TOutput extends ZodType = ZodType<unknown>> extends BaseToolFunction<TInput> {
65
+ export interface ToolFunctionWithExecute<TInput extends $ZodObject<$ZodShape>, TOutput extends $ZodType = $ZodType<unknown>> extends BaseToolFunction<TInput> {
66
66
  outputSchema?: TOutput;
67
- execute: (params: z.infer<TInput>, context?: TurnContext) => Promise<z.infer<TOutput>> | z.infer<TOutput>;
67
+ execute: (params: zodInfer<TInput>, context?: TurnContext) => Promise<zodInfer<TOutput>> | zodInfer<TOutput>;
68
68
  }
69
69
  /**
70
70
  * Generator-based tool with async generator execute function
@@ -88,42 +88,42 @@ export interface ToolFunctionWithExecute<TInput extends ZodObject<ZodRawShape>,
88
88
  * }
89
89
  * ```
90
90
  */
91
- export interface ToolFunctionWithGenerator<TInput extends ZodObject<ZodRawShape>, TEvent extends ZodType = ZodType<unknown>, TOutput extends ZodType = ZodType<unknown>> extends BaseToolFunction<TInput> {
91
+ export interface ToolFunctionWithGenerator<TInput extends $ZodObject<$ZodShape>, TEvent extends $ZodType = $ZodType<unknown>, TOutput extends $ZodType = $ZodType<unknown>> extends BaseToolFunction<TInput> {
92
92
  eventSchema: TEvent;
93
93
  outputSchema: TOutput;
94
- execute: (params: z.infer<TInput>, context?: TurnContext) => AsyncGenerator<z.infer<TEvent> | z.infer<TOutput>>;
94
+ execute: (params: zodInfer<TInput>, context?: TurnContext) => AsyncGenerator<zodInfer<TEvent> | zodInfer<TOutput>>;
95
95
  }
96
96
  /**
97
97
  * Manual tool without execute function - requires manual handling by developer
98
98
  */
99
- export interface ManualToolFunction<TInput extends ZodObject<ZodRawShape>, TOutput extends ZodType = ZodType<unknown>> extends BaseToolFunction<TInput> {
99
+ export interface ManualToolFunction<TInput extends $ZodObject<$ZodShape>, TOutput extends $ZodType = $ZodType<unknown>> extends BaseToolFunction<TInput> {
100
100
  outputSchema?: TOutput;
101
101
  }
102
102
  /**
103
103
  * Tool with execute function (regular or generator)
104
104
  */
105
- export type ToolWithExecute<TInput extends ZodObject<ZodRawShape> = ZodObject<ZodRawShape>, TOutput extends ZodType = ZodType<unknown>> = {
105
+ export type ToolWithExecute<TInput extends $ZodObject<$ZodShape> = $ZodObject<$ZodShape>, TOutput extends $ZodType = $ZodType<unknown>> = {
106
106
  type: ToolType.Function;
107
107
  function: ToolFunctionWithExecute<TInput, TOutput>;
108
108
  };
109
109
  /**
110
110
  * Tool with generator execute function
111
111
  */
112
- export type ToolWithGenerator<TInput extends ZodObject<ZodRawShape> = ZodObject<ZodRawShape>, TEvent extends ZodType = ZodType<unknown>, TOutput extends ZodType = ZodType<unknown>> = {
112
+ export type ToolWithGenerator<TInput extends $ZodObject<$ZodShape> = $ZodObject<$ZodShape>, TEvent extends $ZodType = $ZodType<unknown>, TOutput extends $ZodType = $ZodType<unknown>> = {
113
113
  type: ToolType.Function;
114
114
  function: ToolFunctionWithGenerator<TInput, TEvent, TOutput>;
115
115
  };
116
116
  /**
117
117
  * Tool without execute function (manual handling)
118
118
  */
119
- export type ManualTool<TInput extends ZodObject<ZodRawShape> = ZodObject<ZodRawShape>, TOutput extends ZodType = ZodType<unknown>> = {
119
+ export type ManualTool<TInput extends $ZodObject<$ZodShape> = $ZodObject<$ZodShape>, TOutput extends $ZodType = $ZodType<unknown>> = {
120
120
  type: ToolType.Function;
121
121
  function: ManualToolFunction<TInput, TOutput>;
122
122
  };
123
123
  /**
124
124
  * Union type of all enhanced tool types
125
125
  */
126
- export type Tool = ToolWithExecute<ZodObject<ZodRawShape>, ZodType<unknown>> | ToolWithGenerator<ZodObject<ZodRawShape>, ZodType<unknown>, ZodType<unknown>> | ManualTool<ZodObject<ZodRawShape>, ZodType<unknown>>;
126
+ export type Tool = ToolWithExecute<$ZodObject<$ZodShape>, $ZodType<unknown>> | ToolWithGenerator<$ZodObject<$ZodShape>, $ZodType<unknown>, $ZodType<unknown>> | ManualTool<$ZodObject<$ZodShape>, $ZodType<unknown>>;
127
127
  /**
128
128
  * Extracts the input type from a tool definition
129
129
  */
@@ -131,7 +131,7 @@ export type InferToolInput<T> = T extends {
131
131
  function: {
132
132
  inputSchema: infer S;
133
133
  };
134
- } ? S extends ZodType ? z.infer<S> : unknown : unknown;
134
+ } ? S extends $ZodType ? zodInfer<S> : unknown : unknown;
135
135
  /**
136
136
  * Extracts the output type from a tool definition
137
137
  */
@@ -139,7 +139,7 @@ export type InferToolOutput<T> = T extends {
139
139
  function: {
140
140
  outputSchema: infer S;
141
141
  };
142
- } ? S extends ZodType ? z.infer<S> : unknown : unknown;
142
+ } ? S extends $ZodType ? zodInfer<S> : unknown : unknown;
143
143
  /**
144
144
  * A tool call with typed arguments based on the tool's inputSchema
145
145
  */
@@ -172,7 +172,7 @@ export type InferToolEvent<T> = T extends {
172
172
  function: {
173
173
  eventSchema: infer S;
174
174
  };
175
- } ? S extends ZodType ? z.infer<S> : never : never;
175
+ } ? S extends $ZodType ? zodInfer<S> : never : never;
176
176
  /**
177
177
  * Union of event types for all generator tools in a tuple
178
178
  * Filters out non-generator tools (which return `never`)
@@ -216,8 +216,8 @@ export interface ParsedToolCall<T extends Tool> {
216
216
  export interface ToolExecutionResult<T extends Tool> {
217
217
  toolCallId: string;
218
218
  toolName: string;
219
- result: T extends ToolWithExecute<any, infer O> | ToolWithGenerator<any, any, infer O> ? z.infer<O> : unknown;
220
- preliminaryResults?: T extends ToolWithGenerator<any, infer E, any> ? z.infer<E>[] : undefined;
219
+ result: T extends ToolWithExecute<any, infer O> | ToolWithGenerator<any, any, infer O> ? zodInfer<O> : unknown;
220
+ preliminaryResults?: T extends ToolWithGenerator<any, infer E, any> ? zodInfer<E>[] : undefined;
221
221
  error?: Error;
222
222
  }
223
223
  /**
package/esm/lib/tool.d.ts CHANGED
@@ -1,49 +1,49 @@
1
- import type { ZodObject, ZodRawShape, ZodType, z } from "zod";
1
+ import type { $ZodObject, $ZodShape, $ZodType, infer as zodInfer } from 'zod/v4/core';
2
2
  import { type TurnContext, type ToolWithExecute, type ToolWithGenerator, type ManualTool, type NextTurnParamsFunctions } from "./tool-types.js";
3
3
  /**
4
4
  * Configuration for a regular tool with outputSchema
5
5
  */
6
- type RegularToolConfigWithOutput<TInput extends ZodObject<ZodRawShape>, TOutput extends ZodType> = {
6
+ type RegularToolConfigWithOutput<TInput extends $ZodObject<$ZodShape>, TOutput extends $ZodType> = {
7
7
  name: string;
8
8
  description?: string;
9
9
  inputSchema: TInput;
10
10
  outputSchema: TOutput;
11
11
  eventSchema?: undefined;
12
- nextTurnParams?: NextTurnParamsFunctions<z.infer<TInput>>;
13
- execute: (params: z.infer<TInput>, context?: TurnContext) => Promise<z.infer<TOutput>> | z.infer<TOutput>;
12
+ nextTurnParams?: NextTurnParamsFunctions<zodInfer<TInput>>;
13
+ execute: (params: zodInfer<TInput>, context?: TurnContext) => Promise<zodInfer<TOutput>> | zodInfer<TOutput>;
14
14
  };
15
15
  /**
16
16
  * Configuration for a regular tool without outputSchema (infers return type from execute)
17
17
  */
18
- type RegularToolConfigWithoutOutput<TInput extends ZodObject<ZodRawShape>, TReturn> = {
18
+ type RegularToolConfigWithoutOutput<TInput extends $ZodObject<$ZodShape>, TReturn> = {
19
19
  name: string;
20
20
  description?: string;
21
21
  inputSchema: TInput;
22
22
  outputSchema?: undefined;
23
23
  eventSchema?: undefined;
24
- nextTurnParams?: NextTurnParamsFunctions<z.infer<TInput>>;
25
- execute: (params: z.infer<TInput>, context?: TurnContext) => Promise<TReturn> | TReturn;
24
+ nextTurnParams?: NextTurnParamsFunctions<zodInfer<TInput>>;
25
+ execute: (params: zodInfer<TInput>, context?: TurnContext) => Promise<TReturn> | TReturn;
26
26
  };
27
27
  /**
28
28
  * Configuration for a generator tool (with eventSchema)
29
29
  */
30
- type GeneratorToolConfig<TInput extends ZodObject<ZodRawShape>, TEvent extends ZodType, TOutput extends ZodType> = {
30
+ type GeneratorToolConfig<TInput extends $ZodObject<$ZodShape>, TEvent extends $ZodType, TOutput extends $ZodType> = {
31
31
  name: string;
32
32
  description?: string;
33
33
  inputSchema: TInput;
34
34
  eventSchema: TEvent;
35
35
  outputSchema: TOutput;
36
- nextTurnParams?: NextTurnParamsFunctions<z.infer<TInput>>;
37
- execute: (params: z.infer<TInput>, context?: TurnContext) => AsyncGenerator<z.infer<TEvent> | z.infer<TOutput>>;
36
+ nextTurnParams?: NextTurnParamsFunctions<zodInfer<TInput>>;
37
+ execute: (params: zodInfer<TInput>, context?: TurnContext) => AsyncGenerator<zodInfer<TEvent> | zodInfer<TOutput>>;
38
38
  };
39
39
  /**
40
40
  * Configuration for a manual tool (execute: false, no eventSchema or outputSchema)
41
41
  */
42
- type ManualToolConfig<TInput extends ZodObject<ZodRawShape>> = {
42
+ type ManualToolConfig<TInput extends $ZodObject<$ZodShape>> = {
43
43
  name: string;
44
44
  description?: string;
45
45
  inputSchema: TInput;
46
- nextTurnParams?: NextTurnParamsFunctions<z.infer<TInput>>;
46
+ nextTurnParams?: NextTurnParamsFunctions<zodInfer<TInput>>;
47
47
  execute: false;
48
48
  };
49
49
  /**
@@ -91,9 +91,9 @@ type ManualToolConfig<TInput extends ZodObject<ZodRawShape>> = {
91
91
  * });
92
92
  * ```
93
93
  */
94
- export declare function tool<TInput extends ZodObject<ZodRawShape>, TEvent extends ZodType, TOutput extends ZodType>(config: GeneratorToolConfig<TInput, TEvent, TOutput>): ToolWithGenerator<TInput, TEvent, TOutput>;
95
- export declare function tool<TInput extends ZodObject<ZodRawShape>>(config: ManualToolConfig<TInput>): ManualTool<TInput>;
96
- export declare function tool<TInput extends ZodObject<ZodRawShape>, TOutput extends ZodType>(config: RegularToolConfigWithOutput<TInput, TOutput>): ToolWithExecute<TInput, TOutput>;
97
- export declare function tool<TInput extends ZodObject<ZodRawShape>, TReturn>(config: RegularToolConfigWithoutOutput<TInput, TReturn>): ToolWithExecute<TInput, ZodType<TReturn>>;
94
+ export declare function tool<TInput extends $ZodObject<$ZodShape>, TEvent extends $ZodType, TOutput extends $ZodType>(config: GeneratorToolConfig<TInput, TEvent, TOutput>): ToolWithGenerator<TInput, TEvent, TOutput>;
95
+ export declare function tool<TInput extends $ZodObject<$ZodShape>>(config: ManualToolConfig<TInput>): ManualTool<TInput>;
96
+ export declare function tool<TInput extends $ZodObject<$ZodShape>, TOutput extends $ZodType>(config: RegularToolConfigWithOutput<TInput, TOutput>): ToolWithExecute<TInput, TOutput>;
97
+ export declare function tool<TInput extends $ZodObject<$ZodShape>, TReturn>(config: RegularToolConfigWithoutOutput<TInput, TReturn>): ToolWithExecute<TInput, $ZodType<TReturn>>;
98
98
  export {};
99
99
  //# sourceMappingURL=tool.d.ts.map
package/jsr.json CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  {
4
4
  "name": "@openrouter/sdk",
5
- "version": "0.3.11",
5
+ "version": "0.3.12",
6
6
  "exports": {
7
7
  ".": "./src/index.ts",
8
8
  "./models/errors": "./src/models/errors/index.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openrouter/sdk",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
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": [
@@ -69,11 +69,9 @@
69
69
  "build": "tsc",
70
70
  "typecheck": "tsc --noEmit",
71
71
  "prepublishOnly": "npm run build",
72
- "test": "vitest --run",
73
- "test:watch": "vitest"
74
- },
75
- "peerDependencies": {
76
-
72
+ "test": "vitest --run --project unit",
73
+ "test:e2e": "vitest --run --project e2e",
74
+ "test:watch": "vitest --watch --project unit"
77
75
  },
78
76
  "devDependencies": {
79
77
  "@eslint/js": "^9.19.0",
@@ -87,5 +85,6 @@
87
85
  },
88
86
  "dependencies": {
89
87
  "zod": "^3.25.0 || ^4.0.0"
90
- }
88
+ },
89
+ "packageManager": "pnpm@10.22.0"
91
90
  }
package/vitest.config.ts CHANGED
@@ -1,36 +1,45 @@
1
- import { dirname, join } from 'node:path';
2
- import { fileURLToPath } from 'node:url';
3
1
  import { config } from 'dotenv';
4
2
  import { defineConfig } from 'vitest/config';
5
3
 
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = dirname(__filename);
8
-
9
4
  // Load environment variables from .env file if it exists
10
5
  // This will not override existing environment variables
11
6
  config({
12
- path: join(__dirname, '.env'),
7
+ path: new URL('.env', import.meta.url),
13
8
  });
14
9
 
15
10
  export default defineConfig({
16
11
  test: {
17
12
  globals: true,
18
13
  environment: 'node',
19
- // Don't override env vars - just let them pass through from the system
20
- // The env object here will be merged with process.env
21
14
  env: {
22
15
  OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY,
23
16
  },
24
- include: [
25
- 'tests/**/*.test.ts',
26
- ],
27
- hookTimeout: 30000,
28
- testTimeout: 30000,
29
17
  typecheck: {
30
18
  enabled: true,
31
- include: [
32
- 'tests/**/*.test.ts',
33
- ],
34
19
  },
20
+ projects: [
21
+ {
22
+ extends: true,
23
+ test: {
24
+ name: 'unit',
25
+ include: [
26
+ 'tests/unit/**/*.test.ts',
27
+ 'tests/funcs/**/*.test.ts',
28
+ 'tests/sdk/**/*.test.ts'
29
+ ],
30
+ testTimeout: 10000,
31
+ hookTimeout: 10000,
32
+ },
33
+ },
34
+ {
35
+ extends: true,
36
+ test: {
37
+ name: 'e2e',
38
+ include: ['tests/e2e/**/*.test.ts'],
39
+ testTimeout: 30000,
40
+ hookTimeout: 30000,
41
+ },
42
+ },
43
+ ],
35
44
  },
36
45
  });
@@ -1,10 +0,0 @@
1
- {
2
- "tab_size": 2,
3
- "project_name": "@openrouter/sdk",
4
- "formatter": {
5
- "language_server": {
6
- "name": "eslint"
7
- }
8
- },
9
- "language_servers": ["!biome", "..."]
10
- }