@depup/ai-sdk__openai 3.0.41-depup.0

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.
Files changed (74) hide show
  1. package/CHANGELOG.md +3101 -0
  2. package/LICENSE +13 -0
  3. package/README.md +25 -0
  4. package/changes.json +5 -0
  5. package/dist/index.d.mts +1107 -0
  6. package/dist/index.d.ts +1107 -0
  7. package/dist/index.js +6408 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +6493 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/dist/internal/index.d.mts +1137 -0
  12. package/dist/internal/index.d.ts +1137 -0
  13. package/dist/internal/index.js +6256 -0
  14. package/dist/internal/index.js.map +1 -0
  15. package/dist/internal/index.mjs +6306 -0
  16. package/dist/internal/index.mjs.map +1 -0
  17. package/docs/03-openai.mdx +2396 -0
  18. package/internal.d.ts +1 -0
  19. package/package.json +96 -0
  20. package/src/chat/convert-openai-chat-usage.ts +57 -0
  21. package/src/chat/convert-to-openai-chat-messages.ts +225 -0
  22. package/src/chat/get-response-metadata.ts +15 -0
  23. package/src/chat/map-openai-finish-reason.ts +19 -0
  24. package/src/chat/openai-chat-api.ts +198 -0
  25. package/src/chat/openai-chat-language-model.ts +703 -0
  26. package/src/chat/openai-chat-options.ts +192 -0
  27. package/src/chat/openai-chat-prepare-tools.ts +84 -0
  28. package/src/chat/openai-chat-prompt.ts +70 -0
  29. package/src/completion/convert-openai-completion-usage.ts +46 -0
  30. package/src/completion/convert-to-openai-completion-prompt.ts +93 -0
  31. package/src/completion/get-response-metadata.ts +15 -0
  32. package/src/completion/map-openai-finish-reason.ts +19 -0
  33. package/src/completion/openai-completion-api.ts +81 -0
  34. package/src/completion/openai-completion-language-model.ts +336 -0
  35. package/src/completion/openai-completion-options.ts +61 -0
  36. package/src/embedding/openai-embedding-api.ts +13 -0
  37. package/src/embedding/openai-embedding-model.ts +95 -0
  38. package/src/embedding/openai-embedding-options.ts +30 -0
  39. package/src/image/openai-image-api.ts +35 -0
  40. package/src/image/openai-image-model.ts +349 -0
  41. package/src/image/openai-image-options.ts +31 -0
  42. package/src/index.ts +23 -0
  43. package/src/internal/index.ts +19 -0
  44. package/src/openai-config.ts +18 -0
  45. package/src/openai-error.ts +22 -0
  46. package/src/openai-language-model-capabilities.ts +52 -0
  47. package/src/openai-provider.ts +270 -0
  48. package/src/openai-tools.ts +126 -0
  49. package/src/responses/convert-openai-responses-usage.ts +53 -0
  50. package/src/responses/convert-to-openai-responses-input.ts +735 -0
  51. package/src/responses/map-openai-responses-finish-reason.ts +22 -0
  52. package/src/responses/openai-responses-api.ts +1260 -0
  53. package/src/responses/openai-responses-language-model.ts +2098 -0
  54. package/src/responses/openai-responses-options.ts +299 -0
  55. package/src/responses/openai-responses-prepare-tools.ts +408 -0
  56. package/src/responses/openai-responses-provider-metadata.ts +62 -0
  57. package/src/speech/openai-speech-api.ts +38 -0
  58. package/src/speech/openai-speech-model.ts +137 -0
  59. package/src/speech/openai-speech-options.ts +26 -0
  60. package/src/tool/apply-patch.ts +141 -0
  61. package/src/tool/code-interpreter.ts +104 -0
  62. package/src/tool/custom.ts +64 -0
  63. package/src/tool/file-search.ts +145 -0
  64. package/src/tool/image-generation.ts +126 -0
  65. package/src/tool/local-shell.ts +72 -0
  66. package/src/tool/mcp.ts +125 -0
  67. package/src/tool/shell.ts +203 -0
  68. package/src/tool/web-search-preview.ts +141 -0
  69. package/src/tool/web-search.ts +181 -0
  70. package/src/transcription/openai-transcription-api.ts +37 -0
  71. package/src/transcription/openai-transcription-model.ts +232 -0
  72. package/src/transcription/openai-transcription-options.ts +53 -0
  73. package/src/transcription/transcription-test.mp3 +0 -0
  74. package/src/version.ts +6 -0
@@ -0,0 +1,2098 @@
1
+ import {
2
+ APICallError,
3
+ JSONValue,
4
+ LanguageModelV3,
5
+ LanguageModelV3Prompt,
6
+ LanguageModelV3CallOptions,
7
+ LanguageModelV3Content,
8
+ LanguageModelV3FinishReason,
9
+ LanguageModelV3GenerateResult,
10
+ LanguageModelV3ProviderTool,
11
+ LanguageModelV3StreamPart,
12
+ LanguageModelV3StreamResult,
13
+ LanguageModelV3ToolApprovalRequest,
14
+ SharedV3ProviderMetadata,
15
+ SharedV3Warning,
16
+ } from '@ai-sdk/provider';
17
+ import {
18
+ combineHeaders,
19
+ createEventSourceResponseHandler,
20
+ createJsonResponseHandler,
21
+ createToolNameMapping,
22
+ generateId,
23
+ InferSchema,
24
+ parseProviderOptions,
25
+ ParseResult,
26
+ postJsonToApi,
27
+ } from '@ai-sdk/provider-utils';
28
+ import { OpenAIConfig } from '../openai-config';
29
+ import { openaiFailedResponseHandler } from '../openai-error';
30
+ import { getOpenAILanguageModelCapabilities } from '../openai-language-model-capabilities';
31
+ import { applyPatchInputSchema } from '../tool/apply-patch';
32
+ import {
33
+ codeInterpreterInputSchema,
34
+ codeInterpreterOutputSchema,
35
+ } from '../tool/code-interpreter';
36
+ import { fileSearchOutputSchema } from '../tool/file-search';
37
+ import { imageGenerationOutputSchema } from '../tool/image-generation';
38
+ import { localShellInputSchema } from '../tool/local-shell';
39
+ import { mcpOutputSchema } from '../tool/mcp';
40
+ import { shellInputSchema, shellOutputSchema } from '../tool/shell';
41
+ import { webSearchOutputSchema } from '../tool/web-search';
42
+ import {
43
+ convertOpenAIResponsesUsage,
44
+ OpenAIResponsesUsage,
45
+ } from './convert-openai-responses-usage';
46
+ import { convertToOpenAIResponsesInput } from './convert-to-openai-responses-input';
47
+ import { mapOpenAIResponseFinishReason } from './map-openai-responses-finish-reason';
48
+ import {
49
+ OpenAIResponsesChunk,
50
+ openaiResponsesChunkSchema,
51
+ OpenAIResponsesIncludeOptions,
52
+ OpenAIResponsesIncludeValue,
53
+ OpenAIResponsesLogprobs,
54
+ openaiResponsesResponseSchema,
55
+ OpenAIResponsesWebSearchAction,
56
+ OpenAIResponsesApplyPatchOperationDiffDeltaChunk,
57
+ OpenAIResponsesApplyPatchOperationDiffDoneChunk,
58
+ } from './openai-responses-api';
59
+ import {
60
+ OpenAIResponsesModelId,
61
+ openaiLanguageModelResponsesOptionsSchema,
62
+ TOP_LOGPROBS_MAX,
63
+ } from './openai-responses-options';
64
+ import { prepareResponsesTools } from './openai-responses-prepare-tools';
65
+ import {
66
+ ResponsesProviderMetadata,
67
+ ResponsesReasoningProviderMetadata,
68
+ ResponsesSourceDocumentProviderMetadata,
69
+ ResponsesTextProviderMetadata,
70
+ } from './openai-responses-provider-metadata';
71
+
72
+ /**
73
+ * Extracts a mapping from MCP approval request IDs to their corresponding tool call IDs
74
+ * from the prompt. When an MCP tool requires approval, we generate a tool call ID to track
75
+ * the pending approval in our system. When the user responds to the approval (and we
76
+ * continue the conversation), we need to map the approval request ID back to our tool call ID
77
+ * so that tool results reference the correct tool call.
78
+ */
79
+ function extractApprovalRequestIdToToolCallIdMapping(
80
+ prompt: LanguageModelV3Prompt,
81
+ ): Record<string, string> {
82
+ const mapping: Record<string, string> = {};
83
+ for (const message of prompt) {
84
+ if (message.role !== 'assistant') continue;
85
+ for (const part of message.content) {
86
+ if (part.type !== 'tool-call') continue;
87
+ const approvalRequestId = part.providerOptions?.openai
88
+ ?.approvalRequestId as string | undefined;
89
+ if (approvalRequestId != null) {
90
+ mapping[approvalRequestId] = part.toolCallId;
91
+ }
92
+ }
93
+ }
94
+ return mapping;
95
+ }
96
+
97
+ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
98
+ readonly specificationVersion = 'v3';
99
+
100
+ readonly modelId: OpenAIResponsesModelId;
101
+
102
+ private readonly config: OpenAIConfig;
103
+
104
+ constructor(modelId: OpenAIResponsesModelId, config: OpenAIConfig) {
105
+ this.modelId = modelId;
106
+ this.config = config;
107
+ }
108
+
109
+ readonly supportedUrls: Record<string, RegExp[]> = {
110
+ 'image/*': [/^https?:\/\/.*$/],
111
+ 'application/pdf': [/^https?:\/\/.*$/],
112
+ };
113
+
114
+ get provider(): string {
115
+ return this.config.provider;
116
+ }
117
+
118
+ private async getArgs({
119
+ maxOutputTokens,
120
+ temperature,
121
+ stopSequences,
122
+ topP,
123
+ topK,
124
+ presencePenalty,
125
+ frequencyPenalty,
126
+ seed,
127
+ prompt,
128
+ providerOptions,
129
+ tools,
130
+ toolChoice,
131
+ responseFormat,
132
+ }: LanguageModelV3CallOptions) {
133
+ const warnings: SharedV3Warning[] = [];
134
+ const modelCapabilities = getOpenAILanguageModelCapabilities(this.modelId);
135
+
136
+ if (topK != null) {
137
+ warnings.push({ type: 'unsupported', feature: 'topK' });
138
+ }
139
+
140
+ if (seed != null) {
141
+ warnings.push({ type: 'unsupported', feature: 'seed' });
142
+ }
143
+
144
+ if (presencePenalty != null) {
145
+ warnings.push({ type: 'unsupported', feature: 'presencePenalty' });
146
+ }
147
+
148
+ if (frequencyPenalty != null) {
149
+ warnings.push({ type: 'unsupported', feature: 'frequencyPenalty' });
150
+ }
151
+
152
+ if (stopSequences != null) {
153
+ warnings.push({ type: 'unsupported', feature: 'stopSequences' });
154
+ }
155
+
156
+ const providerOptionsName = this.config.provider.includes('azure')
157
+ ? 'azure'
158
+ : 'openai';
159
+ let openaiOptions = await parseProviderOptions({
160
+ provider: providerOptionsName,
161
+ providerOptions,
162
+ schema: openaiLanguageModelResponsesOptionsSchema,
163
+ });
164
+
165
+ if (openaiOptions == null && providerOptionsName !== 'openai') {
166
+ openaiOptions = await parseProviderOptions({
167
+ provider: 'openai',
168
+ providerOptions,
169
+ schema: openaiLanguageModelResponsesOptionsSchema,
170
+ });
171
+ }
172
+
173
+ const isReasoningModel =
174
+ openaiOptions?.forceReasoning ?? modelCapabilities.isReasoningModel;
175
+
176
+ if (openaiOptions?.conversation && openaiOptions?.previousResponseId) {
177
+ warnings.push({
178
+ type: 'unsupported',
179
+ feature: 'conversation',
180
+ details: 'conversation and previousResponseId cannot be used together',
181
+ });
182
+ }
183
+
184
+ const toolNameMapping = createToolNameMapping({
185
+ tools,
186
+ providerToolNames: {
187
+ 'openai.code_interpreter': 'code_interpreter',
188
+ 'openai.file_search': 'file_search',
189
+ 'openai.image_generation': 'image_generation',
190
+ 'openai.local_shell': 'local_shell',
191
+ 'openai.shell': 'shell',
192
+ 'openai.web_search': 'web_search',
193
+ 'openai.web_search_preview': 'web_search_preview',
194
+ 'openai.mcp': 'mcp',
195
+ 'openai.apply_patch': 'apply_patch',
196
+ },
197
+ resolveProviderToolName: tool =>
198
+ tool.id === 'openai.custom'
199
+ ? (tool.args as { name?: string }).name
200
+ : undefined,
201
+ });
202
+
203
+ const customProviderToolNames = new Set<string>();
204
+ const {
205
+ tools: openaiTools,
206
+ toolChoice: openaiToolChoice,
207
+ toolWarnings,
208
+ } = await prepareResponsesTools({
209
+ tools,
210
+ toolChoice,
211
+ toolNameMapping,
212
+ customProviderToolNames,
213
+ });
214
+
215
+ const { input, warnings: inputWarnings } =
216
+ await convertToOpenAIResponsesInput({
217
+ prompt,
218
+ toolNameMapping,
219
+ systemMessageMode:
220
+ openaiOptions?.systemMessageMode ??
221
+ (isReasoningModel
222
+ ? 'developer'
223
+ : modelCapabilities.systemMessageMode),
224
+ providerOptionsName,
225
+ fileIdPrefixes: this.config.fileIdPrefixes,
226
+ store: openaiOptions?.store ?? true,
227
+ hasConversation: openaiOptions?.conversation != null,
228
+ hasLocalShellTool: hasOpenAITool('openai.local_shell'),
229
+ hasShellTool: hasOpenAITool('openai.shell'),
230
+ hasApplyPatchTool: hasOpenAITool('openai.apply_patch'),
231
+ customProviderToolNames:
232
+ customProviderToolNames.size > 0
233
+ ? customProviderToolNames
234
+ : undefined,
235
+ });
236
+
237
+ warnings.push(...inputWarnings);
238
+
239
+ const strictJsonSchema = openaiOptions?.strictJsonSchema ?? true;
240
+
241
+ let include: OpenAIResponsesIncludeOptions = openaiOptions?.include;
242
+
243
+ function addInclude(key: OpenAIResponsesIncludeValue) {
244
+ if (include == null) {
245
+ include = [key];
246
+ } else if (!include.includes(key)) {
247
+ include = [...include, key];
248
+ }
249
+ }
250
+
251
+ function hasOpenAITool(id: string) {
252
+ return (
253
+ tools?.find(tool => tool.type === 'provider' && tool.id === id) != null
254
+ );
255
+ }
256
+
257
+ // when logprobs are requested, automatically include them:
258
+ const topLogprobs =
259
+ typeof openaiOptions?.logprobs === 'number'
260
+ ? openaiOptions?.logprobs
261
+ : openaiOptions?.logprobs === true
262
+ ? TOP_LOGPROBS_MAX
263
+ : undefined;
264
+
265
+ if (topLogprobs) {
266
+ addInclude('message.output_text.logprobs');
267
+ }
268
+
269
+ // when a web search tool is present, automatically include the sources:
270
+ const webSearchToolName = (
271
+ tools?.find(
272
+ tool =>
273
+ tool.type === 'provider' &&
274
+ (tool.id === 'openai.web_search' ||
275
+ tool.id === 'openai.web_search_preview'),
276
+ ) as LanguageModelV3ProviderTool | undefined
277
+ )?.name;
278
+
279
+ if (webSearchToolName) {
280
+ addInclude('web_search_call.action.sources');
281
+ }
282
+
283
+ // when a code interpreter tool is present, automatically include the outputs:
284
+ if (hasOpenAITool('openai.code_interpreter')) {
285
+ addInclude('code_interpreter_call.outputs');
286
+ }
287
+
288
+ const store = openaiOptions?.store;
289
+
290
+ // store defaults to true in the OpenAI responses API, so check for false exactly:
291
+ if (store === false && isReasoningModel) {
292
+ addInclude('reasoning.encrypted_content');
293
+ }
294
+
295
+ const baseArgs = {
296
+ model: this.modelId,
297
+ input,
298
+ temperature,
299
+ top_p: topP,
300
+ max_output_tokens: maxOutputTokens,
301
+
302
+ ...((responseFormat?.type === 'json' || openaiOptions?.textVerbosity) && {
303
+ text: {
304
+ ...(responseFormat?.type === 'json' && {
305
+ format:
306
+ responseFormat.schema != null
307
+ ? {
308
+ type: 'json_schema',
309
+ strict: strictJsonSchema,
310
+ name: responseFormat.name ?? 'response',
311
+ description: responseFormat.description,
312
+ schema: responseFormat.schema,
313
+ }
314
+ : { type: 'json_object' },
315
+ }),
316
+ ...(openaiOptions?.textVerbosity && {
317
+ verbosity: openaiOptions.textVerbosity,
318
+ }),
319
+ },
320
+ }),
321
+
322
+ // provider options:
323
+ conversation: openaiOptions?.conversation,
324
+ max_tool_calls: openaiOptions?.maxToolCalls,
325
+ metadata: openaiOptions?.metadata,
326
+ parallel_tool_calls: openaiOptions?.parallelToolCalls,
327
+ previous_response_id: openaiOptions?.previousResponseId,
328
+ store,
329
+ user: openaiOptions?.user,
330
+ instructions: openaiOptions?.instructions,
331
+ service_tier: openaiOptions?.serviceTier,
332
+ include,
333
+ prompt_cache_key: openaiOptions?.promptCacheKey,
334
+ prompt_cache_retention: openaiOptions?.promptCacheRetention,
335
+ safety_identifier: openaiOptions?.safetyIdentifier,
336
+ top_logprobs: topLogprobs,
337
+ truncation: openaiOptions?.truncation,
338
+
339
+ // model-specific settings:
340
+ ...(isReasoningModel &&
341
+ (openaiOptions?.reasoningEffort != null ||
342
+ openaiOptions?.reasoningSummary != null) && {
343
+ reasoning: {
344
+ ...(openaiOptions?.reasoningEffort != null && {
345
+ effort: openaiOptions.reasoningEffort,
346
+ }),
347
+ ...(openaiOptions?.reasoningSummary != null && {
348
+ summary: openaiOptions.reasoningSummary,
349
+ }),
350
+ },
351
+ }),
352
+ };
353
+
354
+ // remove unsupported settings for reasoning models
355
+ // see https://platform.openai.com/docs/guides/reasoning#limitations
356
+ if (isReasoningModel) {
357
+ // when reasoning effort is none, gpt-5.1 models allow temperature, topP, logprobs
358
+ // https://platform.openai.com/docs/guides/latest-model#gpt-5-1-parameter-compatibility
359
+ if (
360
+ !(
361
+ openaiOptions?.reasoningEffort === 'none' &&
362
+ modelCapabilities.supportsNonReasoningParameters
363
+ )
364
+ ) {
365
+ if (baseArgs.temperature != null) {
366
+ baseArgs.temperature = undefined;
367
+ warnings.push({
368
+ type: 'unsupported',
369
+ feature: 'temperature',
370
+ details: 'temperature is not supported for reasoning models',
371
+ });
372
+ }
373
+
374
+ if (baseArgs.top_p != null) {
375
+ baseArgs.top_p = undefined;
376
+ warnings.push({
377
+ type: 'unsupported',
378
+ feature: 'topP',
379
+ details: 'topP is not supported for reasoning models',
380
+ });
381
+ }
382
+ }
383
+ } else {
384
+ if (openaiOptions?.reasoningEffort != null) {
385
+ warnings.push({
386
+ type: 'unsupported',
387
+ feature: 'reasoningEffort',
388
+ details: 'reasoningEffort is not supported for non-reasoning models',
389
+ });
390
+ }
391
+
392
+ if (openaiOptions?.reasoningSummary != null) {
393
+ warnings.push({
394
+ type: 'unsupported',
395
+ feature: 'reasoningSummary',
396
+ details: 'reasoningSummary is not supported for non-reasoning models',
397
+ });
398
+ }
399
+ }
400
+
401
+ // Validate flex processing support
402
+ if (
403
+ openaiOptions?.serviceTier === 'flex' &&
404
+ !modelCapabilities.supportsFlexProcessing
405
+ ) {
406
+ warnings.push({
407
+ type: 'unsupported',
408
+ feature: 'serviceTier',
409
+ details:
410
+ 'flex processing is only available for o3, o4-mini, and gpt-5 models',
411
+ });
412
+ // Remove from args if not supported
413
+ delete (baseArgs as any).service_tier;
414
+ }
415
+
416
+ // Validate priority processing support
417
+ if (
418
+ openaiOptions?.serviceTier === 'priority' &&
419
+ !modelCapabilities.supportsPriorityProcessing
420
+ ) {
421
+ warnings.push({
422
+ type: 'unsupported',
423
+ feature: 'serviceTier',
424
+ details:
425
+ 'priority processing is only available for supported models (gpt-4, gpt-5, gpt-5-mini, o3, o4-mini) and requires Enterprise access. gpt-5-nano is not supported',
426
+ });
427
+ // Remove from args if not supported
428
+ delete (baseArgs as any).service_tier;
429
+ }
430
+
431
+ const shellToolEnvType = (
432
+ tools?.find(
433
+ tool => tool.type === 'provider' && tool.id === 'openai.shell',
434
+ ) as { args?: { environment?: { type?: string } } } | undefined
435
+ )?.args?.environment?.type;
436
+
437
+ const isShellProviderExecuted =
438
+ shellToolEnvType === 'containerAuto' ||
439
+ shellToolEnvType === 'containerReference';
440
+
441
+ return {
442
+ webSearchToolName,
443
+ args: {
444
+ ...baseArgs,
445
+ tools: openaiTools,
446
+ tool_choice: openaiToolChoice,
447
+ },
448
+ warnings: [...warnings, ...toolWarnings],
449
+ store,
450
+ toolNameMapping,
451
+ providerOptionsName,
452
+ isShellProviderExecuted,
453
+ };
454
+ }
455
+
456
+ async doGenerate(
457
+ options: LanguageModelV3CallOptions,
458
+ ): Promise<LanguageModelV3GenerateResult> {
459
+ const {
460
+ args: body,
461
+ warnings,
462
+ webSearchToolName,
463
+ toolNameMapping,
464
+ providerOptionsName,
465
+ isShellProviderExecuted,
466
+ } = await this.getArgs(options);
467
+ const url = this.config.url({
468
+ path: '/responses',
469
+ modelId: this.modelId,
470
+ });
471
+
472
+ const approvalRequestIdToDummyToolCallIdFromPrompt =
473
+ extractApprovalRequestIdToToolCallIdMapping(options.prompt);
474
+
475
+ const {
476
+ responseHeaders,
477
+ value: response,
478
+ rawValue: rawResponse,
479
+ } = await postJsonToApi({
480
+ url,
481
+ headers: combineHeaders(this.config.headers(), options.headers),
482
+ body,
483
+ failedResponseHandler: openaiFailedResponseHandler,
484
+ successfulResponseHandler: createJsonResponseHandler(
485
+ openaiResponsesResponseSchema,
486
+ ),
487
+ abortSignal: options.abortSignal,
488
+ fetch: this.config.fetch,
489
+ });
490
+
491
+ if (response.error) {
492
+ throw new APICallError({
493
+ message: response.error.message,
494
+ url,
495
+ requestBodyValues: body,
496
+ statusCode: 400,
497
+ responseHeaders,
498
+ responseBody: rawResponse as string,
499
+ isRetryable: false,
500
+ });
501
+ }
502
+
503
+ const content: Array<LanguageModelV3Content> = [];
504
+ const logprobs: Array<OpenAIResponsesLogprobs> = [];
505
+
506
+ // flag that checks if there have been client-side tool calls (not executed by openai)
507
+ let hasFunctionCall = false;
508
+
509
+ // map response content to content array (defined when there is no error)
510
+ for (const part of response.output!) {
511
+ switch (part.type) {
512
+ case 'reasoning': {
513
+ // when there are no summary parts, we need to add an empty reasoning part:
514
+ if (part.summary.length === 0) {
515
+ part.summary.push({ type: 'summary_text', text: '' });
516
+ }
517
+
518
+ for (const summary of part.summary) {
519
+ content.push({
520
+ type: 'reasoning' as const,
521
+ text: summary.text,
522
+ providerMetadata: {
523
+ [providerOptionsName]: {
524
+ itemId: part.id,
525
+ reasoningEncryptedContent: part.encrypted_content ?? null,
526
+ } satisfies ResponsesReasoningProviderMetadata,
527
+ },
528
+ });
529
+ }
530
+ break;
531
+ }
532
+
533
+ case 'image_generation_call': {
534
+ content.push({
535
+ type: 'tool-call',
536
+ toolCallId: part.id,
537
+ toolName: toolNameMapping.toCustomToolName('image_generation'),
538
+ input: '{}',
539
+ providerExecuted: true,
540
+ });
541
+
542
+ content.push({
543
+ type: 'tool-result',
544
+ toolCallId: part.id,
545
+ toolName: toolNameMapping.toCustomToolName('image_generation'),
546
+ result: {
547
+ result: part.result,
548
+ } satisfies InferSchema<typeof imageGenerationOutputSchema>,
549
+ });
550
+
551
+ break;
552
+ }
553
+
554
+ case 'local_shell_call': {
555
+ content.push({
556
+ type: 'tool-call',
557
+ toolCallId: part.call_id,
558
+ toolName: toolNameMapping.toCustomToolName('local_shell'),
559
+ input: JSON.stringify({
560
+ action: part.action,
561
+ } satisfies InferSchema<typeof localShellInputSchema>),
562
+ providerMetadata: {
563
+ [providerOptionsName]: {
564
+ itemId: part.id,
565
+ },
566
+ },
567
+ });
568
+
569
+ break;
570
+ }
571
+
572
+ case 'shell_call': {
573
+ content.push({
574
+ type: 'tool-call',
575
+ toolCallId: part.call_id,
576
+ toolName: toolNameMapping.toCustomToolName('shell'),
577
+ input: JSON.stringify({
578
+ action: {
579
+ commands: part.action.commands,
580
+ },
581
+ } satisfies InferSchema<typeof shellInputSchema>),
582
+ ...(isShellProviderExecuted && { providerExecuted: true }),
583
+ providerMetadata: {
584
+ [providerOptionsName]: {
585
+ itemId: part.id,
586
+ },
587
+ },
588
+ });
589
+
590
+ break;
591
+ }
592
+
593
+ case 'shell_call_output': {
594
+ content.push({
595
+ type: 'tool-result',
596
+ toolCallId: part.call_id,
597
+ toolName: toolNameMapping.toCustomToolName('shell'),
598
+ result: {
599
+ output: part.output.map(item => ({
600
+ stdout: item.stdout,
601
+ stderr: item.stderr,
602
+ outcome:
603
+ item.outcome.type === 'exit'
604
+ ? {
605
+ type: 'exit' as const,
606
+ exitCode: item.outcome.exit_code,
607
+ }
608
+ : { type: 'timeout' as const },
609
+ })),
610
+ } satisfies InferSchema<typeof shellOutputSchema>,
611
+ });
612
+ break;
613
+ }
614
+
615
+ case 'message': {
616
+ for (const contentPart of part.content) {
617
+ if (
618
+ options.providerOptions?.[providerOptionsName]?.logprobs &&
619
+ contentPart.logprobs
620
+ ) {
621
+ logprobs.push(contentPart.logprobs);
622
+ }
623
+
624
+ const providerMetadata: SharedV3ProviderMetadata[string] = {
625
+ itemId: part.id,
626
+ ...(part.phase != null && { phase: part.phase }),
627
+ ...(contentPart.annotations.length > 0 && {
628
+ annotations: contentPart.annotations,
629
+ }),
630
+ } satisfies ResponsesTextProviderMetadata;
631
+
632
+ content.push({
633
+ type: 'text',
634
+ text: contentPart.text,
635
+ providerMetadata: {
636
+ [providerOptionsName]: providerMetadata,
637
+ },
638
+ });
639
+
640
+ for (const annotation of contentPart.annotations) {
641
+ if (annotation.type === 'url_citation') {
642
+ content.push({
643
+ type: 'source',
644
+ sourceType: 'url',
645
+ id: this.config.generateId?.() ?? generateId(),
646
+ url: annotation.url,
647
+ title: annotation.title,
648
+ });
649
+ } else if (annotation.type === 'file_citation') {
650
+ content.push({
651
+ type: 'source',
652
+ sourceType: 'document',
653
+ id: this.config.generateId?.() ?? generateId(),
654
+ mediaType: 'text/plain',
655
+ title: annotation.filename,
656
+ filename: annotation.filename,
657
+ providerMetadata: {
658
+ [providerOptionsName]: {
659
+ type: annotation.type,
660
+ fileId: annotation.file_id,
661
+ index: annotation.index,
662
+ } satisfies Extract<
663
+ ResponsesSourceDocumentProviderMetadata,
664
+ { type: 'file_citation' }
665
+ >,
666
+ },
667
+ });
668
+ } else if (annotation.type === 'container_file_citation') {
669
+ content.push({
670
+ type: 'source',
671
+ sourceType: 'document',
672
+ id: this.config.generateId?.() ?? generateId(),
673
+ mediaType: 'text/plain',
674
+ title: annotation.filename,
675
+ filename: annotation.filename,
676
+ providerMetadata: {
677
+ [providerOptionsName]: {
678
+ type: annotation.type,
679
+ fileId: annotation.file_id,
680
+ containerId: annotation.container_id,
681
+ } satisfies Extract<
682
+ ResponsesSourceDocumentProviderMetadata,
683
+ { type: 'container_file_citation' }
684
+ >,
685
+ },
686
+ });
687
+ } else if (annotation.type === 'file_path') {
688
+ content.push({
689
+ type: 'source',
690
+ sourceType: 'document',
691
+ id: this.config.generateId?.() ?? generateId(),
692
+ mediaType: 'application/octet-stream',
693
+ title: annotation.file_id,
694
+ filename: annotation.file_id,
695
+ providerMetadata: {
696
+ [providerOptionsName]: {
697
+ type: annotation.type,
698
+ fileId: annotation.file_id,
699
+ index: annotation.index,
700
+ } satisfies Extract<
701
+ ResponsesSourceDocumentProviderMetadata,
702
+ { type: 'file_path' }
703
+ >,
704
+ },
705
+ });
706
+ }
707
+ }
708
+ }
709
+
710
+ break;
711
+ }
712
+
713
+ case 'function_call': {
714
+ hasFunctionCall = true;
715
+
716
+ content.push({
717
+ type: 'tool-call',
718
+ toolCallId: part.call_id,
719
+ toolName: part.name,
720
+ input: part.arguments,
721
+ providerMetadata: {
722
+ [providerOptionsName]: {
723
+ itemId: part.id,
724
+ },
725
+ },
726
+ });
727
+ break;
728
+ }
729
+
730
+ case 'custom_tool_call': {
731
+ hasFunctionCall = true;
732
+ const toolName = toolNameMapping.toCustomToolName(part.name);
733
+
734
+ content.push({
735
+ type: 'tool-call',
736
+ toolCallId: part.call_id,
737
+ toolName,
738
+ input: JSON.stringify(part.input),
739
+ providerMetadata: {
740
+ [providerOptionsName]: {
741
+ itemId: part.id,
742
+ },
743
+ },
744
+ });
745
+ break;
746
+ }
747
+
748
+ case 'web_search_call': {
749
+ content.push({
750
+ type: 'tool-call',
751
+ toolCallId: part.id,
752
+ toolName: toolNameMapping.toCustomToolName(
753
+ webSearchToolName ?? 'web_search',
754
+ ),
755
+ input: JSON.stringify({}),
756
+ providerExecuted: true,
757
+ });
758
+
759
+ content.push({
760
+ type: 'tool-result',
761
+ toolCallId: part.id,
762
+ toolName: toolNameMapping.toCustomToolName(
763
+ webSearchToolName ?? 'web_search',
764
+ ),
765
+ result: mapWebSearchOutput(part.action),
766
+ });
767
+
768
+ break;
769
+ }
770
+
771
+ case 'mcp_call': {
772
+ const toolCallId =
773
+ part.approval_request_id != null
774
+ ? (approvalRequestIdToDummyToolCallIdFromPrompt[
775
+ part.approval_request_id
776
+ ] ?? part.id)
777
+ : part.id;
778
+
779
+ const toolName = `mcp.${part.name}`;
780
+
781
+ content.push({
782
+ type: 'tool-call',
783
+ toolCallId,
784
+ toolName,
785
+ input: part.arguments,
786
+ providerExecuted: true,
787
+ dynamic: true,
788
+ });
789
+
790
+ content.push({
791
+ type: 'tool-result',
792
+ toolCallId,
793
+ toolName,
794
+ result: {
795
+ type: 'call',
796
+ serverLabel: part.server_label,
797
+ name: part.name,
798
+ arguments: part.arguments,
799
+ ...(part.output != null ? { output: part.output } : {}),
800
+ ...(part.error != null
801
+ ? { error: part.error as unknown as JSONValue }
802
+ : {}),
803
+ } satisfies InferSchema<typeof mcpOutputSchema>,
804
+ providerMetadata: {
805
+ [providerOptionsName]: {
806
+ itemId: part.id,
807
+ },
808
+ },
809
+ });
810
+ break;
811
+ }
812
+
813
+ case 'mcp_list_tools': {
814
+ // skip
815
+ break;
816
+ }
817
+
818
+ case 'mcp_approval_request': {
819
+ const approvalRequestId = part.approval_request_id ?? part.id;
820
+ const dummyToolCallId = this.config.generateId?.() ?? generateId();
821
+ const toolName = `mcp.${part.name}`;
822
+
823
+ content.push({
824
+ type: 'tool-call',
825
+ toolCallId: dummyToolCallId,
826
+ toolName,
827
+ input: part.arguments,
828
+ providerExecuted: true,
829
+ dynamic: true,
830
+ });
831
+
832
+ content.push({
833
+ type: 'tool-approval-request',
834
+ approvalId: approvalRequestId,
835
+ toolCallId: dummyToolCallId,
836
+ } satisfies LanguageModelV3ToolApprovalRequest);
837
+ break;
838
+ }
839
+
840
+ case 'computer_call': {
841
+ content.push({
842
+ type: 'tool-call',
843
+ toolCallId: part.id,
844
+ toolName: toolNameMapping.toCustomToolName('computer_use'),
845
+ input: '',
846
+ providerExecuted: true,
847
+ });
848
+
849
+ content.push({
850
+ type: 'tool-result',
851
+ toolCallId: part.id,
852
+ toolName: toolNameMapping.toCustomToolName('computer_use'),
853
+ result: {
854
+ type: 'computer_use_tool_result',
855
+ status: part.status || 'completed',
856
+ },
857
+ });
858
+ break;
859
+ }
860
+
861
+ case 'file_search_call': {
862
+ content.push({
863
+ type: 'tool-call',
864
+ toolCallId: part.id,
865
+ toolName: toolNameMapping.toCustomToolName('file_search'),
866
+ input: '{}',
867
+ providerExecuted: true,
868
+ });
869
+
870
+ content.push({
871
+ type: 'tool-result',
872
+ toolCallId: part.id,
873
+ toolName: toolNameMapping.toCustomToolName('file_search'),
874
+ result: {
875
+ queries: part.queries,
876
+ results:
877
+ part.results?.map(result => ({
878
+ attributes: result.attributes,
879
+ fileId: result.file_id,
880
+ filename: result.filename,
881
+ score: result.score,
882
+ text: result.text,
883
+ })) ?? null,
884
+ } satisfies InferSchema<typeof fileSearchOutputSchema>,
885
+ });
886
+ break;
887
+ }
888
+
889
+ case 'code_interpreter_call': {
890
+ content.push({
891
+ type: 'tool-call',
892
+ toolCallId: part.id,
893
+ toolName: toolNameMapping.toCustomToolName('code_interpreter'),
894
+ input: JSON.stringify({
895
+ code: part.code,
896
+ containerId: part.container_id,
897
+ } satisfies InferSchema<typeof codeInterpreterInputSchema>),
898
+ providerExecuted: true,
899
+ });
900
+
901
+ content.push({
902
+ type: 'tool-result',
903
+ toolCallId: part.id,
904
+ toolName: toolNameMapping.toCustomToolName('code_interpreter'),
905
+ result: {
906
+ outputs: part.outputs,
907
+ } satisfies InferSchema<typeof codeInterpreterOutputSchema>,
908
+ });
909
+ break;
910
+ }
911
+
912
+ case 'apply_patch_call': {
913
+ content.push({
914
+ type: 'tool-call',
915
+ toolCallId: part.call_id,
916
+ toolName: toolNameMapping.toCustomToolName('apply_patch'),
917
+ input: JSON.stringify({
918
+ callId: part.call_id,
919
+ operation: part.operation,
920
+ } satisfies InferSchema<typeof applyPatchInputSchema>),
921
+ providerMetadata: {
922
+ [providerOptionsName]: {
923
+ itemId: part.id,
924
+ },
925
+ },
926
+ });
927
+
928
+ break;
929
+ }
930
+ }
931
+ }
932
+
933
+ const providerMetadata: SharedV3ProviderMetadata = {
934
+ [providerOptionsName]: {
935
+ responseId: response.id,
936
+ ...(logprobs.length > 0 ? { logprobs } : {}),
937
+ ...(typeof response.service_tier === 'string'
938
+ ? { serviceTier: response.service_tier }
939
+ : {}),
940
+ } satisfies ResponsesProviderMetadata,
941
+ };
942
+
943
+ const usage = response.usage!; // defined when there is no error
944
+
945
+ return {
946
+ content,
947
+ finishReason: {
948
+ unified: mapOpenAIResponseFinishReason({
949
+ finishReason: response.incomplete_details?.reason,
950
+ hasFunctionCall,
951
+ }),
952
+ raw: response.incomplete_details?.reason ?? undefined,
953
+ },
954
+ usage: convertOpenAIResponsesUsage(usage),
955
+ request: { body },
956
+ response: {
957
+ id: response.id,
958
+ timestamp: new Date(response.created_at! * 1000),
959
+ modelId: response.model,
960
+ headers: responseHeaders,
961
+ body: rawResponse,
962
+ },
963
+ providerMetadata,
964
+ warnings,
965
+ };
966
+ }
967
+
968
+ async doStream(
969
+ options: LanguageModelV3CallOptions,
970
+ ): Promise<LanguageModelV3StreamResult> {
971
+ const {
972
+ args: body,
973
+ warnings,
974
+ webSearchToolName,
975
+ toolNameMapping,
976
+ store,
977
+ providerOptionsName,
978
+ isShellProviderExecuted,
979
+ } = await this.getArgs(options);
980
+
981
+ const { responseHeaders, value: response } = await postJsonToApi({
982
+ url: this.config.url({
983
+ path: '/responses',
984
+ modelId: this.modelId,
985
+ }),
986
+ headers: combineHeaders(this.config.headers(), options.headers),
987
+ body: {
988
+ ...body,
989
+ stream: true,
990
+ },
991
+ failedResponseHandler: openaiFailedResponseHandler,
992
+ successfulResponseHandler: createEventSourceResponseHandler(
993
+ openaiResponsesChunkSchema,
994
+ ),
995
+ abortSignal: options.abortSignal,
996
+ fetch: this.config.fetch,
997
+ });
998
+
999
+ const self = this;
1000
+
1001
+ const approvalRequestIdToDummyToolCallIdFromPrompt =
1002
+ extractApprovalRequestIdToToolCallIdMapping(options.prompt);
1003
+
1004
+ const approvalRequestIdToDummyToolCallIdFromStream = new Map<
1005
+ string,
1006
+ string
1007
+ >();
1008
+
1009
+ let finishReason: LanguageModelV3FinishReason = {
1010
+ unified: 'other',
1011
+ raw: undefined,
1012
+ };
1013
+ let usage: OpenAIResponsesUsage | undefined = undefined;
1014
+ const logprobs: Array<OpenAIResponsesLogprobs> = [];
1015
+ let responseId: string | null = null;
1016
+
1017
+ const ongoingToolCalls: Record<
1018
+ number,
1019
+ | {
1020
+ toolName: string;
1021
+ toolCallId: string;
1022
+ codeInterpreter?: {
1023
+ containerId: string;
1024
+ };
1025
+ applyPatch?: {
1026
+ hasDiff: boolean;
1027
+ endEmitted: boolean;
1028
+ };
1029
+ }
1030
+ | undefined
1031
+ > = {};
1032
+
1033
+ // set annotations in 'text-end' part providerMetadata.
1034
+ const ongoingAnnotations: Array<
1035
+ Extract<
1036
+ OpenAIResponsesChunk,
1037
+ { type: 'response.output_text.annotation.added' }
1038
+ >['annotation']
1039
+ > = [];
1040
+
1041
+ // track the phase of the current message being streamed
1042
+ let activeMessagePhase: 'commentary' | 'final_answer' | undefined;
1043
+
1044
+ // flag that checks if there have been client-side tool calls (not executed by openai)
1045
+ let hasFunctionCall = false;
1046
+
1047
+ const activeReasoning: Record<
1048
+ string,
1049
+ {
1050
+ encryptedContent?: string | null;
1051
+ // summary index as string to reasoning part state:
1052
+ summaryParts: Record<string, 'active' | 'can-conclude' | 'concluded'>;
1053
+ }
1054
+ > = {};
1055
+
1056
+ let serviceTier: string | undefined;
1057
+
1058
+ return {
1059
+ stream: response.pipeThrough(
1060
+ new TransformStream<
1061
+ ParseResult<OpenAIResponsesChunk>,
1062
+ LanguageModelV3StreamPart
1063
+ >({
1064
+ start(controller) {
1065
+ controller.enqueue({ type: 'stream-start', warnings });
1066
+ },
1067
+
1068
+ transform(chunk, controller) {
1069
+ if (options.includeRawChunks) {
1070
+ controller.enqueue({ type: 'raw', rawValue: chunk.rawValue });
1071
+ }
1072
+
1073
+ // handle failed chunk parsing / validation:
1074
+ if (!chunk.success) {
1075
+ finishReason = { unified: 'error', raw: undefined };
1076
+ controller.enqueue({ type: 'error', error: chunk.error });
1077
+ return;
1078
+ }
1079
+
1080
+ const value = chunk.value;
1081
+
1082
+ if (isResponseOutputItemAddedChunk(value)) {
1083
+ if (value.item.type === 'function_call') {
1084
+ ongoingToolCalls[value.output_index] = {
1085
+ toolName: value.item.name,
1086
+ toolCallId: value.item.call_id,
1087
+ };
1088
+
1089
+ controller.enqueue({
1090
+ type: 'tool-input-start',
1091
+ id: value.item.call_id,
1092
+ toolName: value.item.name,
1093
+ });
1094
+ } else if (value.item.type === 'custom_tool_call') {
1095
+ const toolName = toolNameMapping.toCustomToolName(
1096
+ value.item.name,
1097
+ );
1098
+ ongoingToolCalls[value.output_index] = {
1099
+ toolName,
1100
+ toolCallId: value.item.call_id,
1101
+ };
1102
+
1103
+ controller.enqueue({
1104
+ type: 'tool-input-start',
1105
+ id: value.item.call_id,
1106
+ toolName,
1107
+ });
1108
+ } else if (value.item.type === 'web_search_call') {
1109
+ ongoingToolCalls[value.output_index] = {
1110
+ toolName: toolNameMapping.toCustomToolName(
1111
+ webSearchToolName ?? 'web_search',
1112
+ ),
1113
+ toolCallId: value.item.id,
1114
+ };
1115
+
1116
+ controller.enqueue({
1117
+ type: 'tool-input-start',
1118
+ id: value.item.id,
1119
+ toolName: toolNameMapping.toCustomToolName(
1120
+ webSearchToolName ?? 'web_search',
1121
+ ),
1122
+ providerExecuted: true,
1123
+ });
1124
+
1125
+ controller.enqueue({
1126
+ type: 'tool-input-end',
1127
+ id: value.item.id,
1128
+ });
1129
+
1130
+ controller.enqueue({
1131
+ type: 'tool-call',
1132
+ toolCallId: value.item.id,
1133
+ toolName: toolNameMapping.toCustomToolName(
1134
+ webSearchToolName ?? 'web_search',
1135
+ ),
1136
+ input: JSON.stringify({}),
1137
+ providerExecuted: true,
1138
+ });
1139
+ } else if (value.item.type === 'computer_call') {
1140
+ ongoingToolCalls[value.output_index] = {
1141
+ toolName: toolNameMapping.toCustomToolName('computer_use'),
1142
+ toolCallId: value.item.id,
1143
+ };
1144
+
1145
+ controller.enqueue({
1146
+ type: 'tool-input-start',
1147
+ id: value.item.id,
1148
+ toolName: toolNameMapping.toCustomToolName('computer_use'),
1149
+ providerExecuted: true,
1150
+ });
1151
+ } else if (value.item.type === 'code_interpreter_call') {
1152
+ ongoingToolCalls[value.output_index] = {
1153
+ toolName:
1154
+ toolNameMapping.toCustomToolName('code_interpreter'),
1155
+ toolCallId: value.item.id,
1156
+ codeInterpreter: {
1157
+ containerId: value.item.container_id,
1158
+ },
1159
+ };
1160
+
1161
+ controller.enqueue({
1162
+ type: 'tool-input-start',
1163
+ id: value.item.id,
1164
+ toolName:
1165
+ toolNameMapping.toCustomToolName('code_interpreter'),
1166
+ providerExecuted: true,
1167
+ });
1168
+
1169
+ controller.enqueue({
1170
+ type: 'tool-input-delta',
1171
+ id: value.item.id,
1172
+ delta: `{"containerId":"${value.item.container_id}","code":"`,
1173
+ });
1174
+ } else if (value.item.type === 'file_search_call') {
1175
+ controller.enqueue({
1176
+ type: 'tool-call',
1177
+ toolCallId: value.item.id,
1178
+ toolName: toolNameMapping.toCustomToolName('file_search'),
1179
+ input: '{}',
1180
+ providerExecuted: true,
1181
+ });
1182
+ } else if (value.item.type === 'image_generation_call') {
1183
+ controller.enqueue({
1184
+ type: 'tool-call',
1185
+ toolCallId: value.item.id,
1186
+ toolName:
1187
+ toolNameMapping.toCustomToolName('image_generation'),
1188
+ input: '{}',
1189
+ providerExecuted: true,
1190
+ });
1191
+ } else if (
1192
+ value.item.type === 'mcp_call' ||
1193
+ value.item.type === 'mcp_list_tools' ||
1194
+ value.item.type === 'mcp_approval_request'
1195
+ ) {
1196
+ // Emit MCP tool-call/approval parts on output_item.done instead, so we can:
1197
+ // - alias mcp_call IDs when an approval_request_id is present
1198
+ // - emit a proper tool-approval-request part for MCP approvals
1199
+ } else if (value.item.type === 'apply_patch_call') {
1200
+ const { call_id: callId, operation } = value.item;
1201
+
1202
+ ongoingToolCalls[value.output_index] = {
1203
+ toolName: toolNameMapping.toCustomToolName('apply_patch'),
1204
+ toolCallId: callId,
1205
+ applyPatch: {
1206
+ // delete_file doesn't have diff
1207
+ hasDiff: operation.type === 'delete_file',
1208
+ endEmitted: operation.type === 'delete_file',
1209
+ },
1210
+ };
1211
+
1212
+ controller.enqueue({
1213
+ type: 'tool-input-start',
1214
+ id: callId,
1215
+ toolName: toolNameMapping.toCustomToolName('apply_patch'),
1216
+ });
1217
+
1218
+ if (operation.type === 'delete_file') {
1219
+ const inputString = JSON.stringify({
1220
+ callId,
1221
+ operation,
1222
+ } satisfies InferSchema<typeof applyPatchInputSchema>);
1223
+
1224
+ controller.enqueue({
1225
+ type: 'tool-input-delta',
1226
+ id: callId,
1227
+ delta: inputString,
1228
+ });
1229
+
1230
+ controller.enqueue({
1231
+ type: 'tool-input-end',
1232
+ id: callId,
1233
+ });
1234
+ } else {
1235
+ controller.enqueue({
1236
+ type: 'tool-input-delta',
1237
+ id: callId,
1238
+ delta: `{"callId":"${escapeJSONDelta(callId)}","operation":{"type":"${escapeJSONDelta(operation.type)}","path":"${escapeJSONDelta(operation.path)}","diff":"`,
1239
+ });
1240
+ }
1241
+ } else if (value.item.type === 'shell_call') {
1242
+ ongoingToolCalls[value.output_index] = {
1243
+ toolName: toolNameMapping.toCustomToolName('shell'),
1244
+ toolCallId: value.item.call_id,
1245
+ };
1246
+ } else if (value.item.type === 'shell_call_output') {
1247
+ // shell_call_output is handled in output_item.done
1248
+ } else if (value.item.type === 'message') {
1249
+ ongoingAnnotations.splice(0, ongoingAnnotations.length);
1250
+ activeMessagePhase = value.item.phase ?? undefined;
1251
+ controller.enqueue({
1252
+ type: 'text-start',
1253
+ id: value.item.id,
1254
+ providerMetadata: {
1255
+ [providerOptionsName]: {
1256
+ itemId: value.item.id,
1257
+ ...(value.item.phase != null && {
1258
+ phase: value.item.phase,
1259
+ }),
1260
+ },
1261
+ },
1262
+ });
1263
+ } else if (
1264
+ isResponseOutputItemAddedChunk(value) &&
1265
+ value.item.type === 'reasoning'
1266
+ ) {
1267
+ activeReasoning[value.item.id] = {
1268
+ encryptedContent: value.item.encrypted_content,
1269
+ summaryParts: { 0: 'active' },
1270
+ };
1271
+
1272
+ controller.enqueue({
1273
+ type: 'reasoning-start',
1274
+ id: `${value.item.id}:0`,
1275
+ providerMetadata: {
1276
+ [providerOptionsName]: {
1277
+ itemId: value.item.id,
1278
+ reasoningEncryptedContent:
1279
+ value.item.encrypted_content ?? null,
1280
+ } satisfies ResponsesReasoningProviderMetadata,
1281
+ },
1282
+ });
1283
+ }
1284
+ } else if (isResponseOutputItemDoneChunk(value)) {
1285
+ if (value.item.type === 'message') {
1286
+ const phase = value.item.phase ?? activeMessagePhase;
1287
+ activeMessagePhase = undefined;
1288
+ controller.enqueue({
1289
+ type: 'text-end',
1290
+ id: value.item.id,
1291
+ providerMetadata: {
1292
+ [providerOptionsName]: {
1293
+ itemId: value.item.id,
1294
+ ...(phase != null && { phase }),
1295
+ ...(ongoingAnnotations.length > 0 && {
1296
+ annotations: ongoingAnnotations,
1297
+ }),
1298
+ } satisfies ResponsesTextProviderMetadata,
1299
+ },
1300
+ });
1301
+ } else if (value.item.type === 'function_call') {
1302
+ ongoingToolCalls[value.output_index] = undefined;
1303
+ hasFunctionCall = true;
1304
+
1305
+ controller.enqueue({
1306
+ type: 'tool-input-end',
1307
+ id: value.item.call_id,
1308
+ });
1309
+
1310
+ controller.enqueue({
1311
+ type: 'tool-call',
1312
+ toolCallId: value.item.call_id,
1313
+ toolName: value.item.name,
1314
+ input: value.item.arguments,
1315
+ providerMetadata: {
1316
+ [providerOptionsName]: {
1317
+ itemId: value.item.id,
1318
+ },
1319
+ },
1320
+ });
1321
+ } else if (value.item.type === 'custom_tool_call') {
1322
+ ongoingToolCalls[value.output_index] = undefined;
1323
+ hasFunctionCall = true;
1324
+ const toolName = toolNameMapping.toCustomToolName(
1325
+ value.item.name,
1326
+ );
1327
+
1328
+ controller.enqueue({
1329
+ type: 'tool-input-end',
1330
+ id: value.item.call_id,
1331
+ });
1332
+
1333
+ controller.enqueue({
1334
+ type: 'tool-call',
1335
+ toolCallId: value.item.call_id,
1336
+ toolName,
1337
+ input: JSON.stringify(value.item.input),
1338
+ providerMetadata: {
1339
+ [providerOptionsName]: {
1340
+ itemId: value.item.id,
1341
+ },
1342
+ },
1343
+ });
1344
+ } else if (value.item.type === 'web_search_call') {
1345
+ ongoingToolCalls[value.output_index] = undefined;
1346
+
1347
+ controller.enqueue({
1348
+ type: 'tool-result',
1349
+ toolCallId: value.item.id,
1350
+ toolName: toolNameMapping.toCustomToolName(
1351
+ webSearchToolName ?? 'web_search',
1352
+ ),
1353
+ result: mapWebSearchOutput(value.item.action),
1354
+ });
1355
+ } else if (value.item.type === 'computer_call') {
1356
+ ongoingToolCalls[value.output_index] = undefined;
1357
+
1358
+ controller.enqueue({
1359
+ type: 'tool-input-end',
1360
+ id: value.item.id,
1361
+ });
1362
+
1363
+ controller.enqueue({
1364
+ type: 'tool-call',
1365
+ toolCallId: value.item.id,
1366
+ toolName: toolNameMapping.toCustomToolName('computer_use'),
1367
+ input: '',
1368
+ providerExecuted: true,
1369
+ });
1370
+
1371
+ controller.enqueue({
1372
+ type: 'tool-result',
1373
+ toolCallId: value.item.id,
1374
+ toolName: toolNameMapping.toCustomToolName('computer_use'),
1375
+ result: {
1376
+ type: 'computer_use_tool_result',
1377
+ status: value.item.status || 'completed',
1378
+ },
1379
+ });
1380
+ } else if (value.item.type === 'file_search_call') {
1381
+ ongoingToolCalls[value.output_index] = undefined;
1382
+
1383
+ controller.enqueue({
1384
+ type: 'tool-result',
1385
+ toolCallId: value.item.id,
1386
+ toolName: toolNameMapping.toCustomToolName('file_search'),
1387
+ result: {
1388
+ queries: value.item.queries,
1389
+ results:
1390
+ value.item.results?.map(result => ({
1391
+ attributes: result.attributes,
1392
+ fileId: result.file_id,
1393
+ filename: result.filename,
1394
+ score: result.score,
1395
+ text: result.text,
1396
+ })) ?? null,
1397
+ } satisfies InferSchema<typeof fileSearchOutputSchema>,
1398
+ });
1399
+ } else if (value.item.type === 'code_interpreter_call') {
1400
+ ongoingToolCalls[value.output_index] = undefined;
1401
+
1402
+ controller.enqueue({
1403
+ type: 'tool-result',
1404
+ toolCallId: value.item.id,
1405
+ toolName:
1406
+ toolNameMapping.toCustomToolName('code_interpreter'),
1407
+ result: {
1408
+ outputs: value.item.outputs,
1409
+ } satisfies InferSchema<typeof codeInterpreterOutputSchema>,
1410
+ });
1411
+ } else if (value.item.type === 'image_generation_call') {
1412
+ controller.enqueue({
1413
+ type: 'tool-result',
1414
+ toolCallId: value.item.id,
1415
+ toolName:
1416
+ toolNameMapping.toCustomToolName('image_generation'),
1417
+ result: {
1418
+ result: value.item.result,
1419
+ } satisfies InferSchema<typeof imageGenerationOutputSchema>,
1420
+ });
1421
+ } else if (value.item.type === 'mcp_call') {
1422
+ ongoingToolCalls[value.output_index] = undefined;
1423
+
1424
+ const approvalRequestId =
1425
+ value.item.approval_request_id ?? undefined;
1426
+
1427
+ // when MCP tools require approval, we track them with our own
1428
+ // tool call IDs and then map OpenAI's approval_request_id back to our ID so results match.
1429
+ const aliasedToolCallId =
1430
+ approvalRequestId != null
1431
+ ? (approvalRequestIdToDummyToolCallIdFromStream.get(
1432
+ approvalRequestId,
1433
+ ) ??
1434
+ approvalRequestIdToDummyToolCallIdFromPrompt[
1435
+ approvalRequestId
1436
+ ] ??
1437
+ value.item.id)
1438
+ : value.item.id;
1439
+
1440
+ const toolName = `mcp.${value.item.name}`;
1441
+
1442
+ controller.enqueue({
1443
+ type: 'tool-call',
1444
+ toolCallId: aliasedToolCallId,
1445
+ toolName,
1446
+ input: value.item.arguments,
1447
+ providerExecuted: true,
1448
+ dynamic: true,
1449
+ });
1450
+
1451
+ controller.enqueue({
1452
+ type: 'tool-result',
1453
+ toolCallId: aliasedToolCallId,
1454
+ toolName,
1455
+ result: {
1456
+ type: 'call',
1457
+ serverLabel: value.item.server_label,
1458
+ name: value.item.name,
1459
+ arguments: value.item.arguments,
1460
+ ...(value.item.output != null
1461
+ ? { output: value.item.output }
1462
+ : {}),
1463
+ ...(value.item.error != null
1464
+ ? { error: value.item.error as unknown as JSONValue }
1465
+ : {}),
1466
+ } satisfies InferSchema<typeof mcpOutputSchema>,
1467
+ providerMetadata: {
1468
+ [providerOptionsName]: {
1469
+ itemId: value.item.id,
1470
+ },
1471
+ },
1472
+ });
1473
+ } else if (value.item.type === 'mcp_list_tools') {
1474
+ // Skip listTools - we don't expose this to the UI or send it back
1475
+ ongoingToolCalls[value.output_index] = undefined;
1476
+
1477
+ // skip
1478
+ } else if (value.item.type === 'apply_patch_call') {
1479
+ const toolCall = ongoingToolCalls[value.output_index];
1480
+ if (
1481
+ toolCall?.applyPatch &&
1482
+ !toolCall.applyPatch.endEmitted &&
1483
+ value.item.operation.type !== 'delete_file'
1484
+ ) {
1485
+ if (!toolCall.applyPatch.hasDiff) {
1486
+ controller.enqueue({
1487
+ type: 'tool-input-delta',
1488
+ id: toolCall.toolCallId,
1489
+ delta: escapeJSONDelta(value.item.operation.diff),
1490
+ });
1491
+ }
1492
+
1493
+ controller.enqueue({
1494
+ type: 'tool-input-delta',
1495
+ id: toolCall.toolCallId,
1496
+ delta: '"}}',
1497
+ });
1498
+
1499
+ controller.enqueue({
1500
+ type: 'tool-input-end',
1501
+ id: toolCall.toolCallId,
1502
+ });
1503
+
1504
+ toolCall.applyPatch.endEmitted = true;
1505
+ }
1506
+
1507
+ // Emit the final tool-call with complete diff when status is 'completed'
1508
+ if (toolCall && value.item.status === 'completed') {
1509
+ controller.enqueue({
1510
+ type: 'tool-call',
1511
+ toolCallId: toolCall.toolCallId,
1512
+ toolName: toolNameMapping.toCustomToolName('apply_patch'),
1513
+ input: JSON.stringify({
1514
+ callId: value.item.call_id,
1515
+ operation: value.item.operation,
1516
+ } satisfies InferSchema<typeof applyPatchInputSchema>),
1517
+ providerMetadata: {
1518
+ [providerOptionsName]: {
1519
+ itemId: value.item.id,
1520
+ },
1521
+ },
1522
+ });
1523
+ }
1524
+
1525
+ ongoingToolCalls[value.output_index] = undefined;
1526
+ } else if (value.item.type === 'mcp_approval_request') {
1527
+ ongoingToolCalls[value.output_index] = undefined;
1528
+
1529
+ const dummyToolCallId =
1530
+ self.config.generateId?.() ?? generateId();
1531
+ const approvalRequestId =
1532
+ value.item.approval_request_id ?? value.item.id;
1533
+ approvalRequestIdToDummyToolCallIdFromStream.set(
1534
+ approvalRequestId,
1535
+ dummyToolCallId,
1536
+ );
1537
+
1538
+ const toolName = `mcp.${value.item.name}`;
1539
+
1540
+ controller.enqueue({
1541
+ type: 'tool-call',
1542
+ toolCallId: dummyToolCallId,
1543
+ toolName,
1544
+ input: value.item.arguments,
1545
+ providerExecuted: true,
1546
+ dynamic: true,
1547
+ });
1548
+
1549
+ controller.enqueue({
1550
+ type: 'tool-approval-request',
1551
+ approvalId: approvalRequestId,
1552
+ toolCallId: dummyToolCallId,
1553
+ });
1554
+ } else if (value.item.type === 'local_shell_call') {
1555
+ ongoingToolCalls[value.output_index] = undefined;
1556
+
1557
+ controller.enqueue({
1558
+ type: 'tool-call',
1559
+ toolCallId: value.item.call_id,
1560
+ toolName: toolNameMapping.toCustomToolName('local_shell'),
1561
+ input: JSON.stringify({
1562
+ action: {
1563
+ type: 'exec',
1564
+ command: value.item.action.command,
1565
+ timeoutMs: value.item.action.timeout_ms,
1566
+ user: value.item.action.user,
1567
+ workingDirectory: value.item.action.working_directory,
1568
+ env: value.item.action.env,
1569
+ },
1570
+ } satisfies InferSchema<typeof localShellInputSchema>),
1571
+ providerMetadata: {
1572
+ [providerOptionsName]: { itemId: value.item.id },
1573
+ },
1574
+ });
1575
+ } else if (value.item.type === 'shell_call') {
1576
+ ongoingToolCalls[value.output_index] = undefined;
1577
+
1578
+ controller.enqueue({
1579
+ type: 'tool-call',
1580
+ toolCallId: value.item.call_id,
1581
+ toolName: toolNameMapping.toCustomToolName('shell'),
1582
+ input: JSON.stringify({
1583
+ action: {
1584
+ commands: value.item.action.commands,
1585
+ },
1586
+ } satisfies InferSchema<typeof shellInputSchema>),
1587
+ ...(isShellProviderExecuted && {
1588
+ providerExecuted: true,
1589
+ }),
1590
+ providerMetadata: {
1591
+ [providerOptionsName]: { itemId: value.item.id },
1592
+ },
1593
+ });
1594
+ } else if (value.item.type === 'shell_call_output') {
1595
+ controller.enqueue({
1596
+ type: 'tool-result',
1597
+ toolCallId: value.item.call_id,
1598
+ toolName: toolNameMapping.toCustomToolName('shell'),
1599
+ result: {
1600
+ output: value.item.output.map(
1601
+ (item: {
1602
+ stdout: string;
1603
+ stderr: string;
1604
+ outcome:
1605
+ | { type: 'exit'; exit_code: number }
1606
+ | { type: 'timeout' };
1607
+ }) => ({
1608
+ stdout: item.stdout,
1609
+ stderr: item.stderr,
1610
+ outcome:
1611
+ item.outcome.type === 'exit'
1612
+ ? {
1613
+ type: 'exit' as const,
1614
+ exitCode: item.outcome.exit_code,
1615
+ }
1616
+ : { type: 'timeout' as const },
1617
+ }),
1618
+ ),
1619
+ } satisfies InferSchema<typeof shellOutputSchema>,
1620
+ });
1621
+ } else if (value.item.type === 'reasoning') {
1622
+ const activeReasoningPart = activeReasoning[value.item.id];
1623
+
1624
+ // get all active or can-conclude summary parts' ids
1625
+ // to conclude ongoing reasoning parts:
1626
+ const summaryPartIndices = Object.entries(
1627
+ activeReasoningPart.summaryParts,
1628
+ )
1629
+ .filter(
1630
+ ([_, status]) =>
1631
+ status === 'active' || status === 'can-conclude',
1632
+ )
1633
+ .map(([summaryIndex]) => summaryIndex);
1634
+
1635
+ for (const summaryIndex of summaryPartIndices) {
1636
+ controller.enqueue({
1637
+ type: 'reasoning-end',
1638
+ id: `${value.item.id}:${summaryIndex}`,
1639
+ providerMetadata: {
1640
+ [providerOptionsName]: {
1641
+ itemId: value.item.id,
1642
+ reasoningEncryptedContent:
1643
+ value.item.encrypted_content ?? null,
1644
+ } satisfies ResponsesReasoningProviderMetadata,
1645
+ },
1646
+ });
1647
+ }
1648
+
1649
+ delete activeReasoning[value.item.id];
1650
+ }
1651
+ } else if (isResponseFunctionCallArgumentsDeltaChunk(value)) {
1652
+ const toolCall = ongoingToolCalls[value.output_index];
1653
+
1654
+ if (toolCall != null) {
1655
+ controller.enqueue({
1656
+ type: 'tool-input-delta',
1657
+ id: toolCall.toolCallId,
1658
+ delta: value.delta,
1659
+ });
1660
+ }
1661
+ } else if (isResponseCustomToolCallInputDeltaChunk(value)) {
1662
+ const toolCall = ongoingToolCalls[value.output_index];
1663
+
1664
+ if (toolCall != null) {
1665
+ controller.enqueue({
1666
+ type: 'tool-input-delta',
1667
+ id: toolCall.toolCallId,
1668
+ delta: value.delta,
1669
+ });
1670
+ }
1671
+ } else if (isResponseApplyPatchCallOperationDiffDeltaChunk(value)) {
1672
+ const toolCall = ongoingToolCalls[value.output_index];
1673
+
1674
+ if (toolCall?.applyPatch) {
1675
+ controller.enqueue({
1676
+ type: 'tool-input-delta',
1677
+ id: toolCall.toolCallId,
1678
+ delta: escapeJSONDelta(value.delta),
1679
+ });
1680
+
1681
+ toolCall.applyPatch.hasDiff = true;
1682
+ }
1683
+ } else if (isResponseApplyPatchCallOperationDiffDoneChunk(value)) {
1684
+ const toolCall = ongoingToolCalls[value.output_index];
1685
+
1686
+ if (toolCall?.applyPatch && !toolCall.applyPatch.endEmitted) {
1687
+ if (!toolCall.applyPatch.hasDiff) {
1688
+ controller.enqueue({
1689
+ type: 'tool-input-delta',
1690
+ id: toolCall.toolCallId,
1691
+ delta: escapeJSONDelta(value.diff),
1692
+ });
1693
+
1694
+ toolCall.applyPatch.hasDiff = true;
1695
+ }
1696
+
1697
+ controller.enqueue({
1698
+ type: 'tool-input-delta',
1699
+ id: toolCall.toolCallId,
1700
+ delta: '"}}',
1701
+ });
1702
+
1703
+ controller.enqueue({
1704
+ type: 'tool-input-end',
1705
+ id: toolCall.toolCallId,
1706
+ });
1707
+
1708
+ toolCall.applyPatch.endEmitted = true;
1709
+ }
1710
+ } else if (isResponseImageGenerationCallPartialImageChunk(value)) {
1711
+ controller.enqueue({
1712
+ type: 'tool-result',
1713
+ toolCallId: value.item_id,
1714
+ toolName: toolNameMapping.toCustomToolName('image_generation'),
1715
+ result: {
1716
+ result: value.partial_image_b64,
1717
+ } satisfies InferSchema<typeof imageGenerationOutputSchema>,
1718
+ preliminary: true,
1719
+ });
1720
+ } else if (isResponseCodeInterpreterCallCodeDeltaChunk(value)) {
1721
+ const toolCall = ongoingToolCalls[value.output_index];
1722
+
1723
+ if (toolCall != null) {
1724
+ controller.enqueue({
1725
+ type: 'tool-input-delta',
1726
+ id: toolCall.toolCallId,
1727
+ delta: escapeJSONDelta(value.delta),
1728
+ });
1729
+ }
1730
+ } else if (isResponseCodeInterpreterCallCodeDoneChunk(value)) {
1731
+ const toolCall = ongoingToolCalls[value.output_index];
1732
+
1733
+ if (toolCall != null) {
1734
+ controller.enqueue({
1735
+ type: 'tool-input-delta',
1736
+ id: toolCall.toolCallId,
1737
+ delta: '"}',
1738
+ });
1739
+
1740
+ controller.enqueue({
1741
+ type: 'tool-input-end',
1742
+ id: toolCall.toolCallId,
1743
+ });
1744
+
1745
+ // immediately send the tool call after the input end:
1746
+ controller.enqueue({
1747
+ type: 'tool-call',
1748
+ toolCallId: toolCall.toolCallId,
1749
+ toolName:
1750
+ toolNameMapping.toCustomToolName('code_interpreter'),
1751
+ input: JSON.stringify({
1752
+ code: value.code,
1753
+ containerId: toolCall.codeInterpreter!.containerId,
1754
+ } satisfies InferSchema<typeof codeInterpreterInputSchema>),
1755
+ providerExecuted: true,
1756
+ });
1757
+ }
1758
+ } else if (isResponseCreatedChunk(value)) {
1759
+ responseId = value.response.id;
1760
+ controller.enqueue({
1761
+ type: 'response-metadata',
1762
+ id: value.response.id,
1763
+ timestamp: new Date(value.response.created_at * 1000),
1764
+ modelId: value.response.model,
1765
+ });
1766
+ } else if (isTextDeltaChunk(value)) {
1767
+ controller.enqueue({
1768
+ type: 'text-delta',
1769
+ id: value.item_id,
1770
+ delta: value.delta,
1771
+ });
1772
+
1773
+ if (
1774
+ options.providerOptions?.[providerOptionsName]?.logprobs &&
1775
+ value.logprobs
1776
+ ) {
1777
+ logprobs.push(value.logprobs);
1778
+ }
1779
+ } else if (value.type === 'response.reasoning_summary_part.added') {
1780
+ // the first reasoning start is pushed in isResponseOutputItemAddedReasoningChunk
1781
+ if (value.summary_index > 0) {
1782
+ const activeReasoningPart = activeReasoning[value.item_id]!;
1783
+
1784
+ activeReasoningPart.summaryParts[value.summary_index] =
1785
+ 'active';
1786
+
1787
+ // since there is a new active summary part, we can conclude all can-conclude summary parts
1788
+ for (const summaryIndex of Object.keys(
1789
+ activeReasoningPart.summaryParts,
1790
+ )) {
1791
+ if (
1792
+ activeReasoningPart.summaryParts[summaryIndex] ===
1793
+ 'can-conclude'
1794
+ ) {
1795
+ controller.enqueue({
1796
+ type: 'reasoning-end',
1797
+ id: `${value.item_id}:${summaryIndex}`,
1798
+ providerMetadata: {
1799
+ [providerOptionsName]: {
1800
+ itemId: value.item_id,
1801
+ } satisfies ResponsesReasoningProviderMetadata,
1802
+ },
1803
+ });
1804
+ activeReasoningPart.summaryParts[summaryIndex] =
1805
+ 'concluded';
1806
+ }
1807
+ }
1808
+
1809
+ controller.enqueue({
1810
+ type: 'reasoning-start',
1811
+ id: `${value.item_id}:${value.summary_index}`,
1812
+ providerMetadata: {
1813
+ [providerOptionsName]: {
1814
+ itemId: value.item_id,
1815
+ reasoningEncryptedContent:
1816
+ activeReasoning[value.item_id]?.encryptedContent ??
1817
+ null,
1818
+ } satisfies ResponsesReasoningProviderMetadata,
1819
+ },
1820
+ });
1821
+ }
1822
+ } else if (value.type === 'response.reasoning_summary_text.delta') {
1823
+ controller.enqueue({
1824
+ type: 'reasoning-delta',
1825
+ id: `${value.item_id}:${value.summary_index}`,
1826
+ delta: value.delta,
1827
+ providerMetadata: {
1828
+ [providerOptionsName]: {
1829
+ itemId: value.item_id,
1830
+ } satisfies ResponsesReasoningProviderMetadata,
1831
+ },
1832
+ });
1833
+ } else if (value.type === 'response.reasoning_summary_part.done') {
1834
+ // when OpenAI stores the message data, we can immediately conclude the reasoning part
1835
+ // since we do not need to send the encrypted content.
1836
+ if (store) {
1837
+ controller.enqueue({
1838
+ type: 'reasoning-end',
1839
+ id: `${value.item_id}:${value.summary_index}`,
1840
+ providerMetadata: {
1841
+ [providerOptionsName]: {
1842
+ itemId: value.item_id,
1843
+ } satisfies ResponsesReasoningProviderMetadata,
1844
+ },
1845
+ });
1846
+
1847
+ // mark the summary part as concluded
1848
+ activeReasoning[value.item_id]!.summaryParts[
1849
+ value.summary_index
1850
+ ] = 'concluded';
1851
+ } else {
1852
+ // mark the summary part as can-conclude only
1853
+ // because we need to have a final summary part with the encrypted content
1854
+ activeReasoning[value.item_id]!.summaryParts[
1855
+ value.summary_index
1856
+ ] = 'can-conclude';
1857
+ }
1858
+ } else if (isResponseFinishedChunk(value)) {
1859
+ finishReason = {
1860
+ unified: mapOpenAIResponseFinishReason({
1861
+ finishReason: value.response.incomplete_details?.reason,
1862
+ hasFunctionCall,
1863
+ }),
1864
+ raw: value.response.incomplete_details?.reason ?? undefined,
1865
+ };
1866
+ usage = value.response.usage;
1867
+ if (typeof value.response.service_tier === 'string') {
1868
+ serviceTier = value.response.service_tier;
1869
+ }
1870
+ } else if (isResponseAnnotationAddedChunk(value)) {
1871
+ ongoingAnnotations.push(value.annotation);
1872
+ if (value.annotation.type === 'url_citation') {
1873
+ controller.enqueue({
1874
+ type: 'source',
1875
+ sourceType: 'url',
1876
+ id: self.config.generateId?.() ?? generateId(),
1877
+ url: value.annotation.url,
1878
+ title: value.annotation.title,
1879
+ });
1880
+ } else if (value.annotation.type === 'file_citation') {
1881
+ controller.enqueue({
1882
+ type: 'source',
1883
+ sourceType: 'document',
1884
+ id: self.config.generateId?.() ?? generateId(),
1885
+ mediaType: 'text/plain',
1886
+ title: value.annotation.filename,
1887
+ filename: value.annotation.filename,
1888
+ providerMetadata: {
1889
+ [providerOptionsName]: {
1890
+ type: value.annotation.type,
1891
+ fileId: value.annotation.file_id,
1892
+ index: value.annotation.index,
1893
+ } satisfies Extract<
1894
+ ResponsesSourceDocumentProviderMetadata,
1895
+ { type: 'file_citation' }
1896
+ >,
1897
+ },
1898
+ });
1899
+ } else if (value.annotation.type === 'container_file_citation') {
1900
+ controller.enqueue({
1901
+ type: 'source',
1902
+ sourceType: 'document',
1903
+ id: self.config.generateId?.() ?? generateId(),
1904
+ mediaType: 'text/plain',
1905
+ title: value.annotation.filename,
1906
+ filename: value.annotation.filename,
1907
+ providerMetadata: {
1908
+ [providerOptionsName]: {
1909
+ type: value.annotation.type,
1910
+ fileId: value.annotation.file_id,
1911
+ containerId: value.annotation.container_id,
1912
+ } satisfies Extract<
1913
+ ResponsesSourceDocumentProviderMetadata,
1914
+ { type: 'container_file_citation' }
1915
+ >,
1916
+ },
1917
+ });
1918
+ } else if (value.annotation.type === 'file_path') {
1919
+ controller.enqueue({
1920
+ type: 'source',
1921
+ sourceType: 'document',
1922
+ id: self.config.generateId?.() ?? generateId(),
1923
+ mediaType: 'application/octet-stream',
1924
+ title: value.annotation.file_id,
1925
+ filename: value.annotation.file_id,
1926
+ providerMetadata: {
1927
+ [providerOptionsName]: {
1928
+ type: value.annotation.type,
1929
+ fileId: value.annotation.file_id,
1930
+ index: value.annotation.index,
1931
+ } satisfies Extract<
1932
+ ResponsesSourceDocumentProviderMetadata,
1933
+ { type: 'file_path' }
1934
+ >,
1935
+ },
1936
+ });
1937
+ }
1938
+ } else if (isErrorChunk(value)) {
1939
+ controller.enqueue({ type: 'error', error: value });
1940
+ }
1941
+ },
1942
+
1943
+ flush(controller) {
1944
+ const providerMetadata: SharedV3ProviderMetadata = {
1945
+ [providerOptionsName]: {
1946
+ responseId: responseId,
1947
+ ...(logprobs.length > 0 ? { logprobs } : {}),
1948
+ ...(serviceTier !== undefined ? { serviceTier } : {}),
1949
+ } satisfies ResponsesProviderMetadata,
1950
+ };
1951
+
1952
+ controller.enqueue({
1953
+ type: 'finish',
1954
+ finishReason,
1955
+ usage: convertOpenAIResponsesUsage(usage),
1956
+ providerMetadata,
1957
+ });
1958
+ },
1959
+ }),
1960
+ ),
1961
+ request: { body },
1962
+ response: { headers: responseHeaders },
1963
+ };
1964
+ }
1965
+ }
1966
+
1967
+ function isTextDeltaChunk(
1968
+ chunk: OpenAIResponsesChunk,
1969
+ ): chunk is OpenAIResponsesChunk & { type: 'response.output_text.delta' } {
1970
+ return chunk.type === 'response.output_text.delta';
1971
+ }
1972
+
1973
+ function isResponseOutputItemDoneChunk(
1974
+ chunk: OpenAIResponsesChunk,
1975
+ ): chunk is OpenAIResponsesChunk & { type: 'response.output_item.done' } {
1976
+ return chunk.type === 'response.output_item.done';
1977
+ }
1978
+
1979
+ function isResponseFinishedChunk(
1980
+ chunk: OpenAIResponsesChunk,
1981
+ ): chunk is OpenAIResponsesChunk & {
1982
+ type: 'response.completed' | 'response.incomplete';
1983
+ } {
1984
+ return (
1985
+ chunk.type === 'response.completed' || chunk.type === 'response.incomplete'
1986
+ );
1987
+ }
1988
+
1989
+ function isResponseCreatedChunk(
1990
+ chunk: OpenAIResponsesChunk,
1991
+ ): chunk is OpenAIResponsesChunk & { type: 'response.created' } {
1992
+ return chunk.type === 'response.created';
1993
+ }
1994
+
1995
+ function isResponseFunctionCallArgumentsDeltaChunk(
1996
+ chunk: OpenAIResponsesChunk,
1997
+ ): chunk is OpenAIResponsesChunk & {
1998
+ type: 'response.function_call_arguments.delta';
1999
+ } {
2000
+ return chunk.type === 'response.function_call_arguments.delta';
2001
+ }
2002
+
2003
+ function isResponseCustomToolCallInputDeltaChunk(
2004
+ chunk: OpenAIResponsesChunk,
2005
+ ): chunk is OpenAIResponsesChunk & {
2006
+ type: 'response.custom_tool_call_input.delta';
2007
+ } {
2008
+ return chunk.type === 'response.custom_tool_call_input.delta';
2009
+ }
2010
+
2011
+ function isResponseImageGenerationCallPartialImageChunk(
2012
+ chunk: OpenAIResponsesChunk,
2013
+ ): chunk is OpenAIResponsesChunk & {
2014
+ type: 'response.image_generation_call.partial_image';
2015
+ } {
2016
+ return chunk.type === 'response.image_generation_call.partial_image';
2017
+ }
2018
+
2019
+ function isResponseCodeInterpreterCallCodeDeltaChunk(
2020
+ chunk: OpenAIResponsesChunk,
2021
+ ): chunk is OpenAIResponsesChunk & {
2022
+ type: 'response.code_interpreter_call_code.delta';
2023
+ } {
2024
+ return chunk.type === 'response.code_interpreter_call_code.delta';
2025
+ }
2026
+
2027
+ function isResponseCodeInterpreterCallCodeDoneChunk(
2028
+ chunk: OpenAIResponsesChunk,
2029
+ ): chunk is OpenAIResponsesChunk & {
2030
+ type: 'response.code_interpreter_call_code.done';
2031
+ } {
2032
+ return chunk.type === 'response.code_interpreter_call_code.done';
2033
+ }
2034
+
2035
+ function isResponseApplyPatchCallOperationDiffDeltaChunk(
2036
+ chunk: OpenAIResponsesChunk,
2037
+ ): chunk is OpenAIResponsesApplyPatchOperationDiffDeltaChunk {
2038
+ return chunk.type === 'response.apply_patch_call_operation_diff.delta';
2039
+ }
2040
+
2041
+ function isResponseApplyPatchCallOperationDiffDoneChunk(
2042
+ chunk: OpenAIResponsesChunk,
2043
+ ): chunk is OpenAIResponsesApplyPatchOperationDiffDoneChunk {
2044
+ return chunk.type === 'response.apply_patch_call_operation_diff.done';
2045
+ }
2046
+
2047
+ function isResponseOutputItemAddedChunk(
2048
+ chunk: OpenAIResponsesChunk,
2049
+ ): chunk is OpenAIResponsesChunk & { type: 'response.output_item.added' } {
2050
+ return chunk.type === 'response.output_item.added';
2051
+ }
2052
+
2053
+ function isResponseAnnotationAddedChunk(
2054
+ chunk: OpenAIResponsesChunk,
2055
+ ): chunk is OpenAIResponsesChunk & {
2056
+ type: 'response.output_text.annotation.added';
2057
+ } {
2058
+ return chunk.type === 'response.output_text.annotation.added';
2059
+ }
2060
+
2061
+ function isErrorChunk(
2062
+ chunk: OpenAIResponsesChunk,
2063
+ ): chunk is OpenAIResponsesChunk & { type: 'error' } {
2064
+ return chunk.type === 'error';
2065
+ }
2066
+
2067
+ function mapWebSearchOutput(
2068
+ action: OpenAIResponsesWebSearchAction | null | undefined,
2069
+ ): InferSchema<typeof webSearchOutputSchema> {
2070
+ if (action == null) {
2071
+ return {};
2072
+ }
2073
+
2074
+ switch (action.type) {
2075
+ case 'search':
2076
+ return {
2077
+ action: { type: 'search', query: action.query ?? undefined },
2078
+ // include sources when provided by the Responses API (behind include flag)
2079
+ ...(action.sources != null && { sources: action.sources }),
2080
+ };
2081
+ case 'open_page':
2082
+ return { action: { type: 'openPage', url: action.url } };
2083
+ case 'find_in_page':
2084
+ return {
2085
+ action: {
2086
+ type: 'findInPage',
2087
+ url: action.url,
2088
+ pattern: action.pattern,
2089
+ },
2090
+ };
2091
+ }
2092
+ }
2093
+
2094
+ // The delta is embedded in a JSON string.
2095
+ // To escape it, we use JSON.stringify and slice to remove the outer quotes.
2096
+ function escapeJSONDelta(delta: string) {
2097
+ return JSON.stringify(delta).slice(1, -1);
2098
+ }