@ai-sdk/open-responses 0.0.0-6b196531-20260710185421

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.
@@ -0,0 +1,10 @@
1
+ import type { FetchFunction } from '@ai-sdk/provider-utils';
2
+
3
+ export type OpenResponsesConfig = {
4
+ provider: string;
5
+ providerOptionsName: string;
6
+ url: string;
7
+ headers?: () => Record<string, string | undefined>;
8
+ fetch?: FetchFunction;
9
+ generateId: () => string;
10
+ };
@@ -0,0 +1,22 @@
1
+ import {
2
+ lazySchema,
3
+ zodSchema,
4
+ type InferSchema,
5
+ } from '@ai-sdk/provider-utils';
6
+ import { z } from 'zod/v4';
7
+
8
+ export const openResponsesLanguageModelOptions = lazySchema(() =>
9
+ zodSchema(
10
+ z.object({
11
+ /**
12
+ * Controls reasoning summary output from the model.
13
+ * Valid values: 'concise', 'detailed', 'auto'.
14
+ */
15
+ reasoningSummary: z.enum(['concise', 'detailed', 'auto']).nullish(),
16
+ }),
17
+ ),
18
+ );
19
+
20
+ export type OpenResponsesLanguageModelOptions = InferSchema<
21
+ typeof openResponsesLanguageModelOptions
22
+ >;
@@ -0,0 +1,557 @@
1
+ import type {
2
+ LanguageModelV4,
3
+ LanguageModelV4CallOptions,
4
+ LanguageModelV4Content,
5
+ LanguageModelV4FinishReason,
6
+ LanguageModelV4GenerateResult,
7
+ LanguageModelV4StreamPart,
8
+ LanguageModelV4StreamResult,
9
+ LanguageModelV4Usage,
10
+ SharedV4Warning,
11
+ } from '@ai-sdk/provider';
12
+ import {
13
+ combineHeaders,
14
+ createEventSourceResponseHandler,
15
+ createJsonErrorResponseHandler,
16
+ createJsonResponseHandler,
17
+ isCustomReasoning,
18
+ jsonSchema,
19
+ mapReasoningToProviderEffort,
20
+ parseProviderOptions,
21
+ postJsonToApi,
22
+ serializeModelOptions,
23
+ WORKFLOW_SERIALIZE,
24
+ WORKFLOW_DESERIALIZE,
25
+ type ParseResult,
26
+ } from '@ai-sdk/provider-utils';
27
+ import { z } from 'zod/v4';
28
+ import { convertToOpenResponsesInput } from './convert-to-open-responses-input';
29
+ import {
30
+ openResponsesErrorSchema,
31
+ type FunctionToolParam,
32
+ type OpenResponsesRequestBody,
33
+ type OpenResponsesResponseBody,
34
+ type OpenResponsesChunk,
35
+ type ToolChoiceParam,
36
+ } from './open-responses-api';
37
+ import { mapOpenResponsesFinishReason } from './map-open-responses-finish-reason';
38
+ import type { OpenResponsesConfig } from './open-responses-config';
39
+ import { openResponsesLanguageModelOptions } from './open-responses-language-model-options';
40
+
41
+ export class OpenResponsesLanguageModel implements LanguageModelV4 {
42
+ readonly specificationVersion = 'v4';
43
+
44
+ readonly modelId: string;
45
+
46
+ private readonly config: OpenResponsesConfig;
47
+
48
+ static [WORKFLOW_SERIALIZE](model: OpenResponsesLanguageModel) {
49
+ return serializeModelOptions({
50
+ modelId: model.modelId,
51
+ config: model.config,
52
+ });
53
+ }
54
+
55
+ static [WORKFLOW_DESERIALIZE](options: {
56
+ modelId: string;
57
+ config: OpenResponsesConfig;
58
+ }) {
59
+ return new OpenResponsesLanguageModel(options.modelId, options.config);
60
+ }
61
+
62
+ constructor(modelId: string, config: OpenResponsesConfig) {
63
+ this.modelId = modelId;
64
+ this.config = config;
65
+ }
66
+
67
+ readonly supportedUrls: Record<string, RegExp[]> = {
68
+ 'image/*': [/^https?:\/\/.*$/],
69
+ };
70
+
71
+ get provider(): string {
72
+ return this.config.provider;
73
+ }
74
+
75
+ private async getArgs({
76
+ maxOutputTokens,
77
+ temperature,
78
+ stopSequences,
79
+ topP,
80
+ topK,
81
+ presencePenalty,
82
+ frequencyPenalty,
83
+ seed,
84
+ reasoning,
85
+ prompt,
86
+ providerOptions,
87
+ tools,
88
+ toolChoice,
89
+ responseFormat,
90
+ }: LanguageModelV4CallOptions): Promise<{
91
+ body: Omit<OpenResponsesRequestBody, 'stream' | 'stream_options'>;
92
+ warnings: SharedV4Warning[];
93
+ }> {
94
+ const warnings: SharedV4Warning[] = [];
95
+
96
+ if (stopSequences != null) {
97
+ warnings.push({ type: 'unsupported', feature: 'stopSequences' });
98
+ }
99
+
100
+ if (topK != null) {
101
+ warnings.push({ type: 'unsupported', feature: 'topK' });
102
+ }
103
+
104
+ if (seed != null) {
105
+ warnings.push({ type: 'unsupported', feature: 'seed' });
106
+ }
107
+
108
+ const {
109
+ input,
110
+ instructions,
111
+ warnings: inputWarnings,
112
+ } = await convertToOpenResponsesInput({
113
+ prompt,
114
+ });
115
+
116
+ warnings.push(...inputWarnings);
117
+
118
+ // Convert function tools to the Open Responses format
119
+ const functionTools: FunctionToolParam[] | undefined = tools
120
+ ?.filter(tool => tool.type === 'function')
121
+ .map(tool => ({
122
+ type: 'function' as const,
123
+ name: tool.name,
124
+ description: tool.description,
125
+ parameters: tool.inputSchema,
126
+ ...(tool.strict != null ? { strict: tool.strict } : {}),
127
+ }));
128
+
129
+ // Convert tool choice to the Open Responses format
130
+ const convertedToolChoice: ToolChoiceParam | undefined =
131
+ toolChoice == null
132
+ ? undefined
133
+ : toolChoice.type === 'tool'
134
+ ? { type: 'function', name: toolChoice.toolName }
135
+ : toolChoice.type; // 'auto' | 'none' | 'required'
136
+
137
+ const textFormat =
138
+ responseFormat?.type === 'json'
139
+ ? {
140
+ type: 'json_schema' as const,
141
+ ...(responseFormat.schema != null
142
+ ? {
143
+ name: responseFormat.name ?? 'response',
144
+ description: responseFormat.description,
145
+ schema: responseFormat.schema,
146
+ strict: true,
147
+ }
148
+ : {}),
149
+ }
150
+ : undefined;
151
+
152
+ const openResponsesOptions = await parseProviderOptions({
153
+ provider: this.config.providerOptionsName,
154
+ providerOptions,
155
+ schema: openResponsesLanguageModelOptions,
156
+ });
157
+
158
+ const resolvedReasoningEffort = isCustomReasoning(reasoning)
159
+ ? reasoning === 'none'
160
+ ? 'none'
161
+ : mapReasoningToProviderEffort({
162
+ reasoning,
163
+ effortMap: {
164
+ minimal: 'low',
165
+ low: 'low',
166
+ medium: 'medium',
167
+ high: 'high',
168
+ xhigh: 'xhigh',
169
+ },
170
+ warnings,
171
+ })
172
+ : undefined;
173
+
174
+ return {
175
+ body: {
176
+ model: this.modelId,
177
+ input,
178
+ instructions,
179
+ max_output_tokens: maxOutputTokens,
180
+ temperature,
181
+ top_p: topP,
182
+ presence_penalty: presencePenalty,
183
+ frequency_penalty: frequencyPenalty,
184
+ reasoning:
185
+ resolvedReasoningEffort != null ||
186
+ openResponsesOptions?.reasoningSummary != null
187
+ ? {
188
+ ...(resolvedReasoningEffort != null && {
189
+ effort: resolvedReasoningEffort,
190
+ }),
191
+ ...(openResponsesOptions?.reasoningSummary != null && {
192
+ summary: openResponsesOptions.reasoningSummary,
193
+ }),
194
+ }
195
+ : undefined,
196
+ tools: functionTools?.length ? functionTools : undefined,
197
+ tool_choice: convertedToolChoice,
198
+ ...(textFormat != null && { text: { format: textFormat } }),
199
+ },
200
+ warnings,
201
+ };
202
+ }
203
+
204
+ async doGenerate(
205
+ options: LanguageModelV4CallOptions,
206
+ ): Promise<LanguageModelV4GenerateResult> {
207
+ const { body, warnings } = await this.getArgs(options);
208
+
209
+ const {
210
+ responseHeaders,
211
+ value: response,
212
+ rawValue: rawResponse,
213
+ } = await postJsonToApi({
214
+ url: this.config.url,
215
+ headers: combineHeaders(this.config.headers?.(), options.headers),
216
+ body,
217
+ failedResponseHandler: createJsonErrorResponseHandler({
218
+ errorSchema: openResponsesErrorSchema,
219
+ errorToMessage: error => error.error.message,
220
+ }),
221
+ successfulResponseHandler: createJsonResponseHandler(
222
+ // do not validate the response body, only apply types to the response body
223
+ jsonSchema<OpenResponsesResponseBody>(() => {
224
+ throw new Error('json schema not implemented');
225
+ }),
226
+ ),
227
+ abortSignal: options.abortSignal,
228
+ fetch: this.config.fetch,
229
+ });
230
+
231
+ const content: Array<LanguageModelV4Content> = [];
232
+ let hasToolCalls = false;
233
+
234
+ for (const part of response.output!) {
235
+ switch (part.type) {
236
+ // TODO AI SDK 7 adjust reasoning in the specification to better support the reasoning structure from open responses.
237
+ case 'reasoning': {
238
+ for (const contentPart of part.content ?? []) {
239
+ content.push({
240
+ type: 'reasoning',
241
+ text: contentPart.text,
242
+ });
243
+ }
244
+ break;
245
+ }
246
+
247
+ case 'message': {
248
+ for (const contentPart of part.content) {
249
+ content.push({
250
+ type: 'text',
251
+ text: contentPart.text,
252
+ });
253
+ }
254
+
255
+ break;
256
+ }
257
+
258
+ case 'function_call': {
259
+ hasToolCalls = true;
260
+ content.push({
261
+ type: 'tool-call',
262
+ toolCallId: part.call_id,
263
+ toolName: part.name,
264
+ input: part.arguments,
265
+ });
266
+ break;
267
+ }
268
+ }
269
+ }
270
+
271
+ const usage = response.usage;
272
+ const inputTokens = usage?.input_tokens;
273
+ const cachedInputTokens = usage?.input_tokens_details?.cached_tokens;
274
+ const outputTokens = usage?.output_tokens;
275
+ const reasoningTokens = usage?.output_tokens_details?.reasoning_tokens;
276
+
277
+ return {
278
+ content,
279
+ finishReason: {
280
+ unified: mapOpenResponsesFinishReason({
281
+ finishReason: response.incomplete_details?.reason,
282
+ hasToolCalls,
283
+ }),
284
+ raw: response.incomplete_details?.reason ?? undefined,
285
+ },
286
+ usage: {
287
+ inputTokens: {
288
+ total: inputTokens,
289
+ noCache: (inputTokens ?? 0) - (cachedInputTokens ?? 0),
290
+ cacheRead: cachedInputTokens,
291
+ cacheWrite: undefined,
292
+ },
293
+ outputTokens: {
294
+ total: outputTokens,
295
+ text: (outputTokens ?? 0) - (reasoningTokens ?? 0),
296
+ reasoning: reasoningTokens,
297
+ },
298
+ raw: response.usage,
299
+ },
300
+ request: { body },
301
+ response: {
302
+ id: response.id,
303
+ timestamp: new Date(response.created_at! * 1000),
304
+ modelId: response.model,
305
+ headers: responseHeaders,
306
+ body: rawResponse,
307
+ },
308
+ providerMetadata: undefined,
309
+ warnings,
310
+ };
311
+ }
312
+
313
+ async doStream(
314
+ options: LanguageModelV4CallOptions,
315
+ ): Promise<LanguageModelV4StreamResult> {
316
+ const { body, warnings } = await this.getArgs(options);
317
+
318
+ const { responseHeaders, value: response } = await postJsonToApi({
319
+ url: this.config.url,
320
+ headers: combineHeaders(this.config.headers?.(), options.headers),
321
+ body: {
322
+ ...body,
323
+ stream: true,
324
+ } satisfies OpenResponsesRequestBody,
325
+ failedResponseHandler: createJsonErrorResponseHandler({
326
+ errorSchema: openResponsesErrorSchema,
327
+ errorToMessage: error => error.error.message,
328
+ }),
329
+ successfulResponseHandler: createEventSourceResponseHandler(z.any()),
330
+ abortSignal: options.abortSignal,
331
+ fetch: this.config.fetch,
332
+ });
333
+
334
+ const usage: LanguageModelV4Usage = {
335
+ inputTokens: {
336
+ total: undefined,
337
+ noCache: undefined,
338
+ cacheRead: undefined,
339
+ cacheWrite: undefined,
340
+ },
341
+ outputTokens: {
342
+ total: undefined,
343
+ text: undefined,
344
+ reasoning: undefined,
345
+ },
346
+ };
347
+
348
+ const updateUsage = (
349
+ responseUsage?: OpenResponsesResponseBody['usage'],
350
+ ) => {
351
+ if (!responseUsage) {
352
+ return;
353
+ }
354
+
355
+ const inputTokens = responseUsage.input_tokens;
356
+ const cachedInputTokens =
357
+ responseUsage.input_tokens_details?.cached_tokens;
358
+ const outputTokens = responseUsage.output_tokens;
359
+ const reasoningTokens =
360
+ responseUsage.output_tokens_details?.reasoning_tokens;
361
+
362
+ usage.inputTokens = {
363
+ total: inputTokens,
364
+ noCache: (inputTokens ?? 0) - (cachedInputTokens ?? 0),
365
+ cacheRead: cachedInputTokens,
366
+ cacheWrite: undefined,
367
+ };
368
+ usage.outputTokens = {
369
+ total: outputTokens,
370
+ text: (outputTokens ?? 0) - (reasoningTokens ?? 0),
371
+ reasoning: reasoningTokens,
372
+ };
373
+ usage.raw = responseUsage;
374
+ };
375
+
376
+ let isActiveReasoning = false;
377
+ let hasToolCalls = false;
378
+ let finishReason: LanguageModelV4FinishReason = {
379
+ unified: 'other',
380
+ raw: undefined,
381
+ };
382
+ const toolCallsByItemId = new Map<
383
+ string,
384
+ { toolName?: string; toolCallId?: string; arguments?: string }
385
+ >();
386
+
387
+ return {
388
+ stream: response.pipeThrough(
389
+ new TransformStream<
390
+ ParseResult<OpenResponsesChunk>,
391
+ LanguageModelV4StreamPart
392
+ >({
393
+ start(controller) {
394
+ controller.enqueue({ type: 'stream-start', warnings });
395
+ },
396
+
397
+ transform(parseResult, controller) {
398
+ if (options.includeRawChunks) {
399
+ controller.enqueue({
400
+ type: 'raw',
401
+ rawValue: parseResult.rawValue,
402
+ });
403
+ }
404
+
405
+ if (!parseResult.success) {
406
+ controller.enqueue({ type: 'error', error: parseResult.error });
407
+ return;
408
+ }
409
+
410
+ const chunk = parseResult.value;
411
+
412
+ // Tool call events (single-shot tool-call when complete)
413
+ if (
414
+ chunk.type === 'response.output_item.added' &&
415
+ chunk.item.type === 'function_call'
416
+ ) {
417
+ toolCallsByItemId.set(chunk.item.id, {
418
+ toolName: chunk.item.name,
419
+ toolCallId: chunk.item.call_id,
420
+ arguments: chunk.item.arguments,
421
+ });
422
+ } else if (
423
+ chunk.type === 'response.function_call_arguments.delta'
424
+ ) {
425
+ const functionCallChunk = chunk;
426
+ const toolCall = toolCallsByItemId.get(functionCallChunk.item_id);
427
+
428
+ if (toolCall == null) {
429
+ toolCallsByItemId.set(functionCallChunk.item_id, {
430
+ arguments: functionCallChunk.delta,
431
+ });
432
+ return;
433
+ }
434
+
435
+ toolCall.arguments =
436
+ (toolCall.arguments ?? '') + functionCallChunk.delta;
437
+ } else if (chunk.type === 'response.function_call_arguments.done') {
438
+ const functionCallChunk = chunk;
439
+ const toolCall = toolCallsByItemId.get(functionCallChunk.item_id);
440
+
441
+ if (toolCall == null) {
442
+ toolCallsByItemId.set(functionCallChunk.item_id, {
443
+ arguments: functionCallChunk.arguments,
444
+ });
445
+ return;
446
+ }
447
+
448
+ toolCall.arguments = functionCallChunk.arguments;
449
+ } else if (
450
+ chunk.type === 'response.output_item.done' &&
451
+ chunk.item.type === 'function_call'
452
+ ) {
453
+ const toolCall = toolCallsByItemId.get(chunk.item.id);
454
+ const toolName = toolCall?.toolName ?? chunk.item.name;
455
+ const toolCallId = toolCall?.toolCallId ?? chunk.item.call_id;
456
+ const input = toolCall?.arguments ?? chunk.item.arguments ?? '';
457
+
458
+ controller.enqueue({
459
+ type: 'tool-call',
460
+ toolCallId,
461
+ toolName,
462
+ input,
463
+ });
464
+ hasToolCalls = true;
465
+
466
+ toolCallsByItemId.delete(chunk.item.id);
467
+ }
468
+
469
+ // Reasoning events (note: response.reasoning_text.delta is an LM Studio extension, not in official spec)
470
+ else if (
471
+ chunk.type === 'response.output_item.added' &&
472
+ chunk.item.type === 'reasoning'
473
+ ) {
474
+ controller.enqueue({
475
+ type: 'reasoning-start',
476
+ id: chunk.item.id,
477
+ });
478
+ isActiveReasoning = true;
479
+ } else if (
480
+ (chunk as { type: string }).type ===
481
+ 'response.reasoning_text.delta'
482
+ ) {
483
+ const reasoningChunk = chunk as {
484
+ item_id: string;
485
+ delta: string;
486
+ };
487
+ controller.enqueue({
488
+ type: 'reasoning-delta',
489
+ id: reasoningChunk.item_id,
490
+ delta: reasoningChunk.delta,
491
+ });
492
+ } else if (
493
+ chunk.type === 'response.output_item.done' &&
494
+ chunk.item.type === 'reasoning'
495
+ ) {
496
+ controller.enqueue({ type: 'reasoning-end', id: chunk.item.id });
497
+ isActiveReasoning = false;
498
+ }
499
+
500
+ // Text events
501
+ else if (
502
+ chunk.type === 'response.output_item.added' &&
503
+ chunk.item.type === 'message'
504
+ ) {
505
+ controller.enqueue({ type: 'text-start', id: chunk.item.id });
506
+ } else if (chunk.type === 'response.output_text.delta') {
507
+ controller.enqueue({
508
+ type: 'text-delta',
509
+ id: chunk.item_id,
510
+ delta: chunk.delta,
511
+ });
512
+ } else if (
513
+ chunk.type === 'response.output_item.done' &&
514
+ chunk.item.type === 'message'
515
+ ) {
516
+ controller.enqueue({ type: 'text-end', id: chunk.item.id });
517
+ } else if (
518
+ chunk.type === 'response.completed' ||
519
+ chunk.type === 'response.incomplete'
520
+ ) {
521
+ const reason = chunk.response.incomplete_details?.reason;
522
+ finishReason = {
523
+ unified: mapOpenResponsesFinishReason({
524
+ finishReason: reason,
525
+ hasToolCalls,
526
+ }),
527
+ raw: reason ?? undefined,
528
+ };
529
+ updateUsage(chunk.response.usage);
530
+ } else if (chunk.type === 'response.failed') {
531
+ finishReason = {
532
+ unified: 'error',
533
+ raw: chunk.response.error?.code ?? chunk.response.status,
534
+ };
535
+ updateUsage(chunk.response.usage);
536
+ }
537
+ },
538
+
539
+ flush(controller) {
540
+ if (isActiveReasoning) {
541
+ controller.enqueue({ type: 'reasoning-end', id: 'reasoning-0' });
542
+ }
543
+
544
+ controller.enqueue({
545
+ type: 'finish',
546
+ finishReason,
547
+ usage,
548
+ providerMetadata: undefined,
549
+ });
550
+ },
551
+ }),
552
+ ),
553
+ request: { body },
554
+ response: { headers: responseHeaders },
555
+ };
556
+ }
557
+ }
package/src/version.ts ADDED
@@ -0,0 +1,6 @@
1
+ // Version string of this package injected at build time.
2
+ declare const __PACKAGE_VERSION__: string | undefined;
3
+ export const VERSION: string =
4
+ typeof __PACKAGE_VERSION__ !== 'undefined'
5
+ ? __PACKAGE_VERSION__
6
+ : '0.0.0-test';