@zhivex-ai/openai 0.7.2 → 0.8.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.
package/dist/index.js CHANGED
@@ -1,6 +1,11 @@
1
1
  import { toJSONSchema, z } from "zod";
2
2
  import { CallbackRealtimeSession, ConfigurationError, ProviderHTTPError, openWebSocketConnection, hostedTool, providerDataPart, UnsupportedFeatureError, createProviderAdapter, encodeAudioFrame, encodeMediaFrame, isCallableToolDefinition, isHostedToolDefinition, normalizeFinishReason, streamSSE, toToolSet, toolResultPayload, withRetry, withTimeoutSignal } from "@zhivex-ai/core";
3
3
  const openAIResponsesToolMetadataKey = "openai.responses_tool_type";
4
+ const openAIResponsesFunctionConfigMetadataKey = "openai.responses_function_config";
5
+ const openAIResponsesFunctionConfig = (tool) => {
6
+ const config = tool.metadata?.[openAIResponsesFunctionConfigMetadataKey];
7
+ return config && typeof config === "object" && !Array.isArray(config) ? config : undefined;
8
+ };
4
9
  const openAILocalResponsesToolType = (tool) => {
5
10
  const type = tool.metadata?.[openAIResponsesToolMetadataKey];
6
11
  return typeof type === "string" ? type : undefined;
@@ -53,30 +58,51 @@ const capabilities = {
53
58
  }
54
59
  };
55
60
  const normalizeModelId = (modelId) => modelId.trim().toLowerCase();
56
- const supportsOpenAIToolSearch = (modelId) => {
57
- const normalized = normalizeModelId(modelId);
58
- return /^gpt-5\.4(?:$|-20|-pro)/.test(normalized);
59
- };
60
- const supportsOpenAIComputerUse = (modelId) => {
61
- const normalized = normalizeModelId(modelId);
62
- return /^(?:gpt-5\.5|gpt-5\.4)(?:$|-20|-pro|-mini)/.test(normalized);
63
- };
64
- const supportsOpenAIHostedHarnessTools = (modelId) => /^gpt-5\.4(?:$|-)/.test(normalizeModelId(modelId));
61
+ const isOpenAIGpt56Model = (modelId) => /^gpt-5\.6(?:$|-(?:sol|terra|luna)(?:-|$)|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
62
+ const isOpenAIGpt55BaseModel = (modelId) => /^gpt-5\.5(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
63
+ const isOpenAIGpt55ProModel = (modelId) => /^gpt-5\.5-pro(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
64
+ const isOpenAIGpt54BaseModel = (modelId) => /^gpt-5\.4(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
65
+ const isOpenAIGpt54MiniModel = (modelId) => /^gpt-5\.4-mini(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
66
+ const isOpenAIGpt54NanoModel = (modelId) => /^gpt-5\.4-nano(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
67
+ const isOpenAIGpt54ProModel = (modelId) => /^gpt-5\.4-pro(?:$|-\d{4}-\d{2}-\d{2})/.test(normalizeModelId(modelId));
68
+ const supportsOpenAIToolSearch = (modelId) => isOpenAIGpt56Model(modelId) || isOpenAIGpt55BaseModel(modelId) || isOpenAIGpt54BaseModel(modelId) || isOpenAIGpt54MiniModel(modelId);
69
+ const supportsOpenAIComputerUse = (modelId) => isOpenAIGpt56Model(modelId) || isOpenAIGpt55BaseModel(modelId) || isOpenAIGpt54BaseModel(modelId) || isOpenAIGpt54MiniModel(modelId);
70
+ const supportsOpenAIShell = (modelId) => isOpenAIGpt56Model(modelId) ||
71
+ isOpenAIGpt55BaseModel(modelId) ||
72
+ isOpenAIGpt55ProModel(modelId) ||
73
+ isOpenAIGpt54BaseModel(modelId) ||
74
+ isOpenAIGpt54MiniModel(modelId) ||
75
+ isOpenAIGpt54NanoModel(modelId) ||
76
+ isOpenAIGpt54ProModel(modelId);
77
+ const supportsOpenAIApplyPatchAndSkills = (modelId) => isOpenAIGpt56Model(modelId) ||
78
+ isOpenAIGpt55BaseModel(modelId) ||
79
+ isOpenAIGpt54BaseModel(modelId) ||
80
+ isOpenAIGpt54MiniModel(modelId) ||
81
+ isOpenAIGpt54NanoModel(modelId);
65
82
  const supportsOpenAIChatAudio = (modelId) => {
66
83
  const normalized = normalizeModelId(modelId);
67
84
  return /^(?:gpt-audio(?:-|$)|gpt-4o(?:-mini)?-audio-preview(?:-|$))/.test(normalized);
68
85
  };
69
86
  const modelCapabilities = (modelId) => ({
70
87
  ...capabilities,
88
+ explicitPromptCaching: isOpenAIGpt56Model(modelId),
89
+ files: isOpenAIGpt56Model(modelId),
90
+ reasoningEfforts: isOpenAIGpt56Model(modelId)
91
+ ? ["none", "low", "medium", "high", "xhigh", "max"]
92
+ : undefined,
93
+ reasoningModes: isOpenAIGpt56Model(modelId) ? ["standard", "pro"] : undefined,
94
+ reasoningContexts: isOpenAIGpt56Model(modelId) ? ["auto", "current_turn", "all_turns"] : undefined,
71
95
  audioInput: supportsOpenAIChatAudio(modelId),
72
96
  audioOutput: supportsOpenAIChatAudio(modelId),
73
97
  agentCapabilities: {
74
98
  ...capabilities.agentCapabilities,
75
99
  computerUse: supportsOpenAIComputerUse(modelId),
76
- shell: supportsOpenAIHostedHarnessTools(modelId),
77
- applyPatch: supportsOpenAIHostedHarnessTools(modelId),
78
- skills: supportsOpenAIHostedHarnessTools(modelId),
79
- toolSearch: supportsOpenAIToolSearch(modelId)
100
+ shell: supportsOpenAIShell(modelId),
101
+ applyPatch: supportsOpenAIApplyPatchAndSkills(modelId),
102
+ skills: supportsOpenAIApplyPatchAndSkills(modelId),
103
+ toolSearch: supportsOpenAIToolSearch(modelId),
104
+ programmaticToolCalling: isOpenAIGpt56Model(modelId),
105
+ multiAgent: isOpenAIGpt56Model(modelId)
80
106
  }
81
107
  });
82
108
  const transcriptionCapabilities = {
@@ -152,6 +178,44 @@ const jsonHeaders = (apiKey) => ({
152
178
  "content-type": "application/json",
153
179
  authorization: `Bearer ${apiKey}`
154
180
  });
181
+ const resolveOpenAILanguageRequestOptions = (providerOptions, apiKey) => {
182
+ const bodyOptions = { ...(providerOptions ?? {}) };
183
+ const apiMode = bodyOptions.apiMode ?? "auto";
184
+ const customHeaders = { ...(bodyOptions.headers ?? {}) };
185
+ const betaValues = new Set(bodyOptions.betas ?? []);
186
+ const multiAgentEnabled = bodyOptions.multi_agent?.enabled === true;
187
+ if (multiAgentEnabled) {
188
+ betaValues.add("responses_multi_agent=v1");
189
+ }
190
+ const existingBetaHeaderKey = Object.keys(customHeaders).find((key) => key.toLowerCase() === "openai-beta");
191
+ if (existingBetaHeaderKey) {
192
+ for (const value of customHeaders[existingBetaHeaderKey]?.split(",") ?? []) {
193
+ if (value.trim()) {
194
+ betaValues.add(value.trim());
195
+ }
196
+ }
197
+ delete customHeaders[existingBetaHeaderKey];
198
+ }
199
+ delete bodyOptions.apiMode;
200
+ delete bodyOptions.headers;
201
+ delete bodyOptions.betas;
202
+ return {
203
+ apiMode,
204
+ bodyOptions,
205
+ multiAgentEnabled,
206
+ headers: {
207
+ ...jsonHeaders(apiKey),
208
+ ...customHeaders,
209
+ ...(betaValues.size ? { "OpenAI-Beta": [...betaValues].join(",") } : {})
210
+ }
211
+ };
212
+ };
213
+ const openAIResponsesBodyOptions = (options, modelId) => options.store === false && isOpenAIGpt56Model(modelId)
214
+ ? {
215
+ ...options,
216
+ include: [...new Set([...(options.include ?? []), "reasoning.encrypted_content"])]
217
+ }
218
+ : options;
155
219
  const getRequestOptions = (input) => withTimeoutSignal(input);
156
220
  const toUint8Array = (data) => {
157
221
  if (data instanceof Uint8Array) {
@@ -200,32 +264,84 @@ const parseJson = async (response) => {
200
264
  }
201
265
  return response.json();
202
266
  };
267
+ const isOpenAIPromptCacheBreakpointPart = (part) => part.type === "provider-data" &&
268
+ part.provider === "openai" &&
269
+ part.data !== null &&
270
+ typeof part.data === "object" &&
271
+ part.data.type === "prompt_cache_breakpoint";
272
+ const promptCacheBreakpointForContentPart = (part) => {
273
+ const metadata = part
274
+ .providerMetadata;
275
+ const openAIMetadata = metadata?.openai && typeof metadata.openai === "object" && !Array.isArray(metadata.openai)
276
+ ? metadata.openai
277
+ : metadata;
278
+ const breakpoint = openAIMetadata?.prompt_cache_breakpoint;
279
+ return breakpoint && typeof breakpoint === "object" && !Array.isArray(breakpoint)
280
+ ? { prompt_cache_breakpoint: { mode: "explicit" } }
281
+ : {};
282
+ };
283
+ const imageDetailForPart = (part) => {
284
+ const metadata = part.providerMetadata;
285
+ const detail = metadata?.openaiDetail ??
286
+ (metadata?.openai && typeof metadata.openai === "object" && !Array.isArray(metadata.openai)
287
+ ? metadata.openai.detail
288
+ : undefined);
289
+ return detail === "auto" || detail === "low" || detail === "high" || detail === "original" ? detail : undefined;
290
+ };
291
+ const markLastContentBlockForPromptCaching = (content) => {
292
+ const last = content.at(-1);
293
+ if (!last) {
294
+ throw new ConfigurationError("OpenAI prompt cache breakpoints must follow a cacheable content part.");
295
+ }
296
+ last.prompt_cache_breakpoint = { mode: "explicit" };
297
+ };
203
298
  const mapContentParts = (message) => {
204
- const textParts = message.parts.filter((part) => part.type === "text");
205
- const imageParts = message.parts.filter((part) => part.type === "image");
206
- const audioParts = message.parts.filter((part) => part.type === "audio");
207
- if (!imageParts.length && !audioParts.length) {
208
- return textParts.map((part) => part.text).join("");
299
+ const hasRichContent = message.parts.some((part) => part.type === "image" ||
300
+ part.type === "audio" ||
301
+ part.type === "file" ||
302
+ Object.keys(promptCacheBreakpointForContentPart(part)).length > 0 ||
303
+ isOpenAIPromptCacheBreakpointPart(part));
304
+ if (!hasRichContent) {
305
+ return message.parts
306
+ .filter((part) => part.type === "text")
307
+ .map((part) => part.text)
308
+ .join("");
309
+ }
310
+ const content = [];
311
+ for (const part of message.parts) {
312
+ if (part.type === "text") {
313
+ content.push({ type: "text", text: part.text, ...promptCacheBreakpointForContentPart(part) });
314
+ }
315
+ else if (part.type === "image") {
316
+ const detail = imageDetailForPart(part);
317
+ content.push({
318
+ type: "image_url",
319
+ image_url: { url: part.image, ...(detail ? { detail } : {}) },
320
+ ...promptCacheBreakpointForContentPart(part)
321
+ });
322
+ }
323
+ else if (part.type === "file") {
324
+ content.push({
325
+ type: "file",
326
+ file: { file_id: part.data },
327
+ ...promptCacheBreakpointForContentPart(part)
328
+ });
329
+ }
330
+ else if (part.type === "audio") {
331
+ content.push({
332
+ type: "input_audio",
333
+ input_audio: {
334
+ data: toBase64(part.data),
335
+ format: inferOpenAIAudioFormat(part.mediaType, part.format)
336
+ },
337
+ ...promptCacheBreakpointForContentPart(part)
338
+ });
339
+ }
340
+ else if (isOpenAIPromptCacheBreakpointPart(part)) {
341
+ markLastContentBlockForPromptCaching(content);
342
+ }
209
343
  }
210
- return [
211
- ...textParts.map((part) => ({
212
- type: "text",
213
- text: part.text
214
- })),
215
- ...imageParts.map((part) => ({
216
- type: "image_url",
217
- image_url: {
218
- url: part.image
219
- }
220
- })),
221
- ...audioParts.map((part) => ({
222
- type: "input_audio",
223
- input_audio: {
224
- data: toBase64(part.data),
225
- format: inferOpenAIAudioFormat(part.mediaType, part.format)
226
- }
227
- }))
228
- ];
344
+ return content;
229
345
  };
230
346
  const mapMessages = (messages) => messages.map((message) => {
231
347
  if (message.role === "tool") {
@@ -258,6 +374,13 @@ const mapMessages = (messages) => messages.map((message) => {
258
374
  return payload;
259
375
  });
260
376
  const hasResponsesOnlyTools = (tools) => Object.values(tools ?? {}).some((tool) => isHostedToolDefinition(tool) || (isCallableToolDefinition(tool) && openAILocalResponsesToolType(tool)));
377
+ const hasProgrammaticToolCalling = (tools) => Object.values(tools ?? {}).some((tool) => isHostedToolDefinition(tool)
378
+ ? tool.type === "programmatic_tool_calling"
379
+ : openAILocalResponsesToolType(tool) === "programmatic_tool_calling");
380
+ const localResponsesTools = (tools) => new Map(Object.values(tools ?? {})
381
+ .filter(isCallableToolDefinition)
382
+ .map((tool) => [openAILocalResponsesToolType(tool), tool.name])
383
+ .filter((entry) => Boolean(entry[0])));
261
384
  const assertResponsesToolsSupported = (modelId, tools) => {
262
385
  const currentCapabilities = modelCapabilities(modelId).agentCapabilities;
263
386
  for (const definition of Object.values(tools ?? {})) {
@@ -268,9 +391,26 @@ const assertResponsesToolsSupported = (modelId, tools) => {
268
391
  if (type === "computer_use_preview" && !currentCapabilities?.computerUse) {
269
392
  throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses computer_use tool.`);
270
393
  }
271
- if ((type === "shell" || type === "apply_patch") && !supportsOpenAIHostedHarnessTools(modelId)) {
394
+ if (type === "computer" && !currentCapabilities?.computerUse) {
395
+ throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses computer tool.`);
396
+ }
397
+ if (type === "shell" && !supportsOpenAIShell(modelId)) {
272
398
  throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses ${type} tool.`);
273
399
  }
400
+ if (type === "apply_patch" && !supportsOpenAIApplyPatchAndSkills(modelId)) {
401
+ throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses ${type} tool.`);
402
+ }
403
+ if (type === "skill" && !currentCapabilities?.skills) {
404
+ throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support the Responses skills tool.`);
405
+ }
406
+ if (type === "programmatic_tool_calling" && !isOpenAIGpt56Model(modelId)) {
407
+ throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support Programmatic Tool Calling.`);
408
+ }
409
+ }
410
+ };
411
+ const assertOpenAIResponsesOptionsSupported = (modelId, options) => {
412
+ if (options.multi_agent?.enabled && !isOpenAIGpt56Model(modelId)) {
413
+ throw new UnsupportedFeatureError(`Provider "openai" model "${modelId}" does not support Multi-agent.`);
274
414
  }
275
415
  };
276
416
  const normalizeWebSearchConfig = (config = {}) => ({
@@ -313,7 +453,8 @@ const mapResponsesTools = (input) => input
313
453
  type: "function",
314
454
  name: tool.name,
315
455
  description: tool.description,
316
- parameters: toJSONSchema(tool.schema)
456
+ parameters: toJSONSchema(tool.schema),
457
+ ...openAIResponsesFunctionConfig(tool)
317
458
  };
318
459
  }
319
460
  if (tool.provider && tool.provider !== "openai") {
@@ -339,6 +480,15 @@ const mapToolChoice = (toolChoice) => {
339
480
  }
340
481
  };
341
482
  };
483
+ const mapResponsesToolChoice = (toolChoice) => {
484
+ if (!toolChoice || typeof toolChoice === "string") {
485
+ return toolChoice;
486
+ }
487
+ return {
488
+ type: "function",
489
+ name: toolChoice.toolName
490
+ };
491
+ };
342
492
  const mapRealtimeProviderOptions = (providerOptions) => {
343
493
  if (!providerOptions) {
344
494
  return {};
@@ -592,18 +742,45 @@ const mapResponsesStructuredOutput = (input) => {
592
742
  }
593
743
  };
594
744
  };
595
- const mapReasoning = (input) => {
745
+ const getOpenAIReasoning = (input) => {
596
746
  if (!input.reasoning) {
597
- return {};
747
+ return undefined;
598
748
  }
599
749
  if (input.reasoning.budgetTokens !== undefined) {
600
750
  throw new UnsupportedFeatureError('Provider "openai" does not support "reasoning.budgetTokens".');
601
751
  }
752
+ return input.reasoning;
753
+ };
754
+ const mapChatReasoning = (input) => {
755
+ const reasoning = getOpenAIReasoning(input);
756
+ if (!reasoning) {
757
+ return {};
758
+ }
759
+ if (reasoning.mode !== undefined || reasoning.context !== undefined) {
760
+ throw new UnsupportedFeatureError('Provider "openai" only supports "reasoning.mode" and "reasoning.context" through the Responses API.');
761
+ }
602
762
  return {
603
- reasoning_effort: input.reasoning.effort,
763
+ ...(reasoning.effort !== undefined ? { reasoning_effort: reasoning.effort } : {}),
604
764
  max_completion_tokens: input.maxTokens
605
765
  };
606
766
  };
767
+ const mapResponsesReasoning = (input, providerReasoning) => {
768
+ const reasoning = getOpenAIReasoning(input);
769
+ if (!reasoning) {
770
+ return {};
771
+ }
772
+ return {
773
+ reasoning: {
774
+ ...(providerReasoning && typeof providerReasoning === "object" && !Array.isArray(providerReasoning)
775
+ ? providerReasoning
776
+ : {}),
777
+ ...(reasoning.effort !== undefined ? { effort: reasoning.effort } : {}),
778
+ ...(reasoning.mode !== undefined ? { mode: reasoning.mode } : {}),
779
+ ...(reasoning.context !== undefined ? { context: reasoning.context } : {}),
780
+ ...(reasoning.includeThoughts ? { summary: "auto" } : {})
781
+ }
782
+ };
783
+ };
607
784
  const getProviderResponseId = (messages) => {
608
785
  for (let index = messages.length - 1; index >= 0; index -= 1) {
609
786
  const message = messages[index];
@@ -627,20 +804,52 @@ const getProviderResponseId = (messages) => {
627
804
  const serializeToolOutput = (message) => message.parts
628
805
  .filter((part) => part.type === "tool-result")
629
806
  .map((part) => {
630
- if (part.toolResult.toolName === "shell") {
807
+ const responsesToolType = part.toolResult.providerMetadata?.responsesToolType;
808
+ if (part.toolResult.toolName === "shell" || responsesToolType === "shell") {
809
+ const rawOutput = part.toolResult.output;
810
+ const rawOutputs = Array.isArray(rawOutput)
811
+ ? rawOutput
812
+ : rawOutput && typeof rawOutput === "object" && Array.isArray(rawOutput.output)
813
+ ? rawOutput.output
814
+ : rawOutput
815
+ ? [rawOutput]
816
+ : [];
817
+ const firstRawOutput = rawOutputs[0] && typeof rawOutputs[0] === "object"
818
+ ? rawOutputs[0]
819
+ : undefined;
820
+ const output = rawOutputs.map((entry) => {
821
+ const result = entry && typeof entry === "object" ? entry : {};
822
+ const outcome = result.outcome && typeof result.outcome === "object"
823
+ ? result.outcome
824
+ : { type: "exit", exit_code: 0 };
825
+ return {
826
+ stdout: typeof result.stdout === "string" ? result.stdout : "",
827
+ stderr: typeof result.stderr === "string" ? result.stderr : "",
828
+ outcome: {
829
+ type: outcome.type ?? "exit",
830
+ exit_code: outcome.exit_code ?? outcome.exitCode
831
+ }
832
+ };
833
+ });
631
834
  return {
632
835
  type: "shell_call_output",
633
836
  call_id: part.toolResult.toolCallId,
837
+ max_output_length: rawOutput && typeof rawOutput === "object"
838
+ ? (rawOutput.max_output_length ??
839
+ rawOutput.maxOutputLength ??
840
+ firstRawOutput?.max_output_length ??
841
+ firstRawOutput?.maxOutputLength)
842
+ : undefined,
634
843
  output: part.toolResult.isError
635
- ? {
636
- stdout: "",
637
- stderr: part.toolResult.error?.message ?? "Shell execution failed.",
638
- outcome: { type: "exit", exitCode: 1 }
639
- }
640
- : part.toolResult.output
844
+ ? [{
845
+ stdout: "",
846
+ stderr: part.toolResult.error?.message ?? "Shell execution failed.",
847
+ outcome: { type: "exit", exit_code: 1 }
848
+ }]
849
+ : output
641
850
  };
642
851
  }
643
- if (part.toolResult.toolName === "apply_patch") {
852
+ if (part.toolResult.toolName === "apply_patch" || responsesToolType === "apply_patch") {
644
853
  const output = part.toolResult.output;
645
854
  const outputRecord = output && typeof output === "object" && !Array.isArray(output)
646
855
  ? output
@@ -652,10 +861,23 @@ const serializeToolOutput = (message) => message.parts
652
861
  output: part.toolResult.isError ? part.toolResult.error?.message : outputRecord?.output
653
862
  };
654
863
  }
864
+ if (part.toolResult.toolName === "computer" || responsesToolType === "computer") {
865
+ if (part.toolResult.isError) {
866
+ throw new Error(`OpenAI computer action execution failed: ${part.toolResult.error?.message ?? "unknown error"}`);
867
+ }
868
+ return {
869
+ type: "computer_call_output",
870
+ call_id: part.toolResult.toolCallId,
871
+ output: part.toolResult.output
872
+ };
873
+ }
655
874
  return {
656
875
  type: "function_call_output",
657
876
  call_id: part.toolResult.toolCallId,
658
- output: JSON.stringify(part.toolResult.isError ? part.toolResult.error : part.toolResult.output ?? null)
877
+ output: JSON.stringify(part.toolResult.isError ? part.toolResult.error : part.toolResult.output ?? null),
878
+ ...(part.toolResult.providerMetadata?.caller !== undefined
879
+ ? { caller: part.toolResult.providerMetadata.caller }
880
+ : {})
659
881
  };
660
882
  });
661
883
  const serializeProviderDataInput = (message) => message.parts
@@ -663,15 +885,27 @@ const serializeProviderDataInput = (message) => message.parts
663
885
  part.provider === "openai" &&
664
886
  part.data !== null &&
665
887
  typeof part.data === "object" &&
666
- part.data.type === "mcp_approval_response")
888
+ typeof part.data.type === "string" &&
889
+ !["prompt_cache_breakpoint", "responses_output"].includes(part.data.type))
667
890
  .map((part) => part.data);
891
+ const serializedResponsesOutput = (message) => message.parts.flatMap((part) => {
892
+ if (part.type !== "provider-data" ||
893
+ part.provider !== "openai" ||
894
+ !part.data ||
895
+ typeof part.data !== "object" ||
896
+ part.data.type !== "responses_output" ||
897
+ !Array.isArray(part.data.items)) {
898
+ return [];
899
+ }
900
+ return part.data.items;
901
+ });
668
902
  const parseResponsesProviderData = (item) => {
669
903
  if (!item || typeof item !== "object") {
670
904
  return undefined;
671
905
  }
672
906
  const typedItem = item;
673
907
  if (typeof typedItem.type !== "string" ||
674
- ["message", "function_call", "shell_call", "apply_patch_call"].includes(typedItem.type)) {
908
+ ["message", "function_call"].includes(typedItem.type)) {
675
909
  return undefined;
676
910
  }
677
911
  return item;
@@ -679,7 +913,13 @@ const parseResponsesProviderData = (item) => {
679
913
  const parseShellCallInput = (item) => {
680
914
  const action = item.action && typeof item.action === "object" ? item.action : undefined;
681
915
  return {
682
- command: typeof action?.command === "string" ? action.command : typeof item.command === "string" ? item.command : undefined,
916
+ command: typeof action?.command === "string"
917
+ ? action.command
918
+ : Array.isArray(action?.commands) && typeof action.commands[0] === "string"
919
+ ? action.commands[0]
920
+ : typeof item.command === "string"
921
+ ? item.command
922
+ : undefined,
683
923
  action: action,
684
924
  maxOutputLength: typeof action?.max_output_length === "number"
685
925
  ? action.max_output_length
@@ -753,26 +993,52 @@ const toResponsesInput = (messages) => {
753
993
  input.push(...serializeToolOutput(message));
754
994
  continue;
755
995
  }
996
+ const rawResponsesOutput = serializedResponsesOutput(message);
997
+ if (message.role === "assistant" && rawResponsesOutput.length) {
998
+ input.push(...rawResponsesOutput);
999
+ continue;
1000
+ }
756
1001
  input.push(...serializeProviderDataInput(message));
757
1002
  const content = [];
1003
+ const assistantOutputItems = [];
758
1004
  for (const part of message.parts) {
759
1005
  switch (part.type) {
760
1006
  case "text":
761
- content.push({ type: "input_text", text: part.text });
1007
+ content.push({ type: "input_text", text: part.text, ...promptCacheBreakpointForContentPart(part) });
762
1008
  break;
763
1009
  case "image":
764
- content.push({ type: "input_image", image_url: part.image });
1010
+ const detail = imageDetailForPart(part);
1011
+ content.push({
1012
+ type: "input_image",
1013
+ image_url: part.image,
1014
+ ...(detail ? { detail } : {}),
1015
+ ...promptCacheBreakpointForContentPart(part)
1016
+ });
1017
+ break;
1018
+ case "file":
1019
+ content.push({
1020
+ type: "input_file",
1021
+ file_id: part.data,
1022
+ ...promptCacheBreakpointForContentPart(part)
1023
+ });
765
1024
  break;
766
1025
  case "tool-call":
767
1026
  if (message.role === "assistant") {
768
- content.push({
1027
+ const caller = part.toolCall.providerMetadata?.caller;
1028
+ assistantOutputItems.push({
769
1029
  type: "function_call",
770
1030
  call_id: part.toolCall.id,
771
1031
  name: part.toolCall.name,
772
- arguments: JSON.stringify(part.toolCall.input)
1032
+ arguments: JSON.stringify(part.toolCall.input),
1033
+ ...(caller !== undefined ? { caller } : {})
773
1034
  });
774
1035
  }
775
1036
  break;
1037
+ case "provider-data":
1038
+ if (isOpenAIPromptCacheBreakpointPart(part)) {
1039
+ markLastContentBlockForPromptCaching(content);
1040
+ }
1041
+ break;
776
1042
  }
777
1043
  }
778
1044
  if (content.length) {
@@ -781,49 +1047,76 @@ const toResponsesInput = (messages) => {
781
1047
  content
782
1048
  });
783
1049
  }
1050
+ input.push(...assistantOutputItems);
784
1051
  }
785
1052
  return input;
786
1053
  };
787
- const parseResponsesAssistantMessage = (json) => {
1054
+ const parseResponsesAssistantMessage = (json, multiAgentEnabled = false, localTools = new Map()) => {
788
1055
  const parts = [];
789
1056
  for (const [index, item] of (json.output ?? []).entries()) {
790
1057
  if (item?.type === "message") {
1058
+ if (multiAgentEnabled && (item.agent?.agent_name !== "/root" || item.phase !== "final_answer")) {
1059
+ continue;
1060
+ }
791
1061
  for (const content of item.content ?? []) {
792
1062
  if (typeof content?.text === "string" && content.text) {
793
1063
  parts.push({ type: "text", text: content.text });
794
1064
  }
1065
+ else if (typeof content?.refusal === "string" && content.refusal) {
1066
+ parts.push({ type: "text", text: content.refusal });
1067
+ }
795
1068
  }
796
1069
  continue;
797
1070
  }
798
1071
  if (item?.type === "function_call") {
1072
+ const callId = item.call_id ?? item.id ?? `${item.name}-${index}`;
799
1073
  parts.push({
800
1074
  type: "tool-call",
801
1075
  toolCall: {
802
- id: item.call_id ?? item.id ?? `${item.name}-${index}`,
1076
+ id: callId,
803
1077
  name: item.name,
804
- input: JSON.parse(item.arguments ?? "{}")
1078
+ input: JSON.parse(item.arguments ?? "{}"),
1079
+ ...(item.caller && typeof item.caller === "object"
1080
+ ? { providerMetadata: { caller: item.caller } }
1081
+ : {})
805
1082
  }
806
1083
  });
807
1084
  continue;
808
1085
  }
809
- if (item?.type === "shell_call") {
1086
+ if (item?.type === "shell_call" && localTools.has("shell")) {
810
1087
  parts.push({
811
1088
  type: "tool-call",
812
1089
  toolCall: {
813
1090
  id: item.call_id ?? item.id ?? `shell-${index}`,
814
- name: "shell",
815
- input: parseShellCallInput(item)
1091
+ name: localTools.get("shell"),
1092
+ input: parseShellCallInput(item),
1093
+ providerMetadata: { responsesToolType: "shell" }
816
1094
  }
817
1095
  });
818
1096
  continue;
819
1097
  }
820
- if (item?.type === "apply_patch_call") {
1098
+ if (item?.type === "apply_patch_call" && localTools.has("apply_patch")) {
821
1099
  parts.push({
822
1100
  type: "tool-call",
823
1101
  toolCall: {
824
1102
  id: item.call_id ?? item.id ?? `apply_patch-${index}`,
825
- name: "apply_patch",
826
- input: parseApplyPatchCallInput(item)
1103
+ name: localTools.get("apply_patch"),
1104
+ input: parseApplyPatchCallInput(item),
1105
+ providerMetadata: { responsesToolType: "apply_patch" }
1106
+ }
1107
+ });
1108
+ continue;
1109
+ }
1110
+ if (item?.type === "computer_call" && localTools.has("computer")) {
1111
+ parts.push({
1112
+ type: "tool-call",
1113
+ toolCall: {
1114
+ id: item.call_id ?? item.id ?? `computer-${index}`,
1115
+ name: localTools.get("computer"),
1116
+ input: {
1117
+ actions: Array.isArray(item.actions) ? item.actions : []
1118
+ },
1119
+ providerMetadata: { responsesToolType: "computer" }
827
1120
  }
828
1121
  });
829
1122
  continue;
@@ -833,7 +1126,13 @@ const parseResponsesAssistantMessage = (json) => {
833
1126
  parts.push(providerDataPart("openai", providerData));
834
1127
  }
835
1128
  }
836
- if (!parts.some((part) => part.type === "text") && typeof json.output_text === "string" && json.output_text) {
1129
+ if (Array.isArray(json.output) && json.output.length) {
1130
+ parts.push(providerDataPart("openai", {
1131
+ type: "responses_output",
1132
+ items: json.output
1133
+ }));
1134
+ }
1135
+ if (!multiAgentEnabled && !parts.some((part) => part.type === "text") && typeof json.output_text === "string" && json.output_text) {
837
1136
  parts.push({ type: "text", text: json.output_text });
838
1137
  }
839
1138
  if (typeof json.id === "string") {
@@ -850,10 +1149,13 @@ const parseResponsesAssistantMessage = (json) => {
850
1149
  parts
851
1150
  };
852
1151
  };
853
- const normalizeResponsesFinishReason = (status, hasToolCalls) => {
1152
+ const normalizeResponsesFinishReason = (status, hasToolCalls, hasRefusal = false) => {
854
1153
  if (hasToolCalls) {
855
1154
  return "tool-calls";
856
1155
  }
1156
+ if (hasRefusal) {
1157
+ return "refusal";
1158
+ }
857
1159
  if (status === "completed") {
858
1160
  return "stop";
859
1161
  }
@@ -862,9 +1164,46 @@ const normalizeResponsesFinishReason = (status, hasToolCalls) => {
862
1164
  }
863
1165
  return normalizeFinishReason(status);
864
1166
  };
865
- const streamResponses = async function* (response) {
1167
+ const mapResponsesUsage = (usage) => usage
1168
+ ? {
1169
+ inputTokens: usage.input_tokens,
1170
+ cachedInputTokens: usage.input_tokens_details?.cached_tokens,
1171
+ cacheWriteTokens: usage.input_tokens_details?.cache_write_tokens,
1172
+ outputTokens: usage.output_tokens,
1173
+ reasoningTokens: usage.output_tokens_details?.reasoning_tokens,
1174
+ totalTokens: usage.total_tokens
1175
+ }
1176
+ : undefined;
1177
+ const mapChatUsage = (usage) => usage
1178
+ ? {
1179
+ inputTokens: usage.prompt_tokens,
1180
+ cachedInputTokens: usage.prompt_tokens_details?.cached_tokens,
1181
+ cacheWriteTokens: usage.prompt_tokens_details?.cache_write_tokens,
1182
+ outputTokens: usage.completion_tokens,
1183
+ reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,
1184
+ totalTokens: usage.total_tokens
1185
+ }
1186
+ : undefined;
1187
+ const addTokenUsage = (left, right) => {
1188
+ if (!left)
1189
+ return right;
1190
+ if (!right)
1191
+ return left;
1192
+ const sum = (a, b) => a === undefined && b === undefined ? undefined : (a ?? 0) + (b ?? 0);
1193
+ return {
1194
+ inputTokens: sum(left.inputTokens, right.inputTokens),
1195
+ cachedInputTokens: sum(left.cachedInputTokens, right.cachedInputTokens),
1196
+ cacheWriteTokens: sum(left.cacheWriteTokens, right.cacheWriteTokens),
1197
+ outputTokens: sum(left.outputTokens, right.outputTokens),
1198
+ reasoningTokens: sum(left.reasoningTokens, right.reasoningTokens),
1199
+ totalTokens: sum(left.totalTokens, right.totalTokens)
1200
+ };
1201
+ };
1202
+ const streamResponses = async function* (response, multiAgentEnabled = false, localTools = new Map()) {
866
1203
  const toolBuffers = new Map();
1204
+ const outputAgents = new Map();
867
1205
  let sawToolCalls = false;
1206
+ let sawRefusal = false;
868
1207
  const emitToolCall = (key) => {
869
1208
  const toolCall = toolBuffers.get(key);
870
1209
  if (!toolCall || toolCall.emitted || !toolCall.name) {
@@ -877,7 +1216,8 @@ const streamResponses = async function* (response) {
877
1216
  toolCall: {
878
1217
  id: toolCall.callId,
879
1218
  name: toolCall.name,
880
- input: JSON.parse(toolCall.args || "{}")
1219
+ input: JSON.parse(toolCall.args || "{}"),
1220
+ ...(toolCall.caller !== undefined ? { providerMetadata: { caller: toolCall.caller } } : {})
881
1221
  }
882
1222
  };
883
1223
  };
@@ -887,12 +1227,32 @@ const streamResponses = async function* (response) {
887
1227
  }
888
1228
  const json = JSON.parse(event.data);
889
1229
  const type = json.type;
890
- if (type === "response.output_text.delta" && typeof json.delta === "string") {
1230
+ if (type === "error") {
1231
+ throw new ProviderHTTPError(`OpenAI Responses stream failed: ${json.error?.message ?? json.message ?? "unknown error"}`, 500, { responseBody: JSON.stringify(json) });
1232
+ }
1233
+ if (type === "response.output_text.delta" &&
1234
+ typeof json.delta === "string" &&
1235
+ (!multiAgentEnabled ||
1236
+ (outputAgents.get(json.output_index)?.agentName ?? "/root") === "/root")) {
1237
+ yield { type: "text-delta", textDelta: json.delta };
1238
+ continue;
1239
+ }
1240
+ if (type === "response.refusal.delta" &&
1241
+ typeof json.delta === "string" &&
1242
+ (!multiAgentEnabled ||
1243
+ (outputAgents.get(json.output_index)?.agentName ?? "/root") === "/root")) {
1244
+ sawRefusal = true;
891
1245
  yield { type: "text-delta", textDelta: json.delta };
892
1246
  continue;
893
1247
  }
894
1248
  if (type === "response.output_item.added" || type === "response.output_item.done") {
895
1249
  const item = json.item;
1250
+ let handledLocalToolCall = false;
1251
+ if (typeof json.output_index === "number" && item?.type === "message") {
1252
+ outputAgents.set(json.output_index, {
1253
+ agentName: item.agent?.agent_name
1254
+ });
1255
+ }
896
1256
  if (item?.type === "function_call") {
897
1257
  const key = item.id ?? json.item_id ?? `${json.output_index ?? toolBuffers.size}`;
898
1258
  const existing = toolBuffers.get(key) ?? {
@@ -903,6 +1263,9 @@ const streamResponses = async function* (response) {
903
1263
  };
904
1264
  existing.callId = item.call_id ?? existing.callId;
905
1265
  existing.name ||= item.name ?? "";
1266
+ if (item.caller && typeof item.caller === "object") {
1267
+ existing.caller = item.caller;
1268
+ }
906
1269
  if (typeof item.arguments === "string") {
907
1270
  existing.args = item.arguments;
908
1271
  }
@@ -914,36 +1277,65 @@ const streamResponses = async function* (response) {
914
1277
  }
915
1278
  }
916
1279
  }
917
- if (item?.type === "shell_call" && type === "response.output_item.done") {
1280
+ if (item?.type === "shell_call" && type === "response.output_item.done" && localTools.has("shell")) {
918
1281
  yield {
919
1282
  type: "tool-call",
920
1283
  toolCall: {
921
1284
  id: item.call_id ?? item.id ?? `${json.output_index ?? "shell"}`,
922
- name: "shell",
923
- input: parseShellCallInput(item)
1285
+ name: localTools.get("shell"),
1286
+ input: parseShellCallInput(item),
1287
+ providerMetadata: { responsesToolType: "shell" }
924
1288
  }
925
1289
  };
926
1290
  sawToolCalls = true;
1291
+ handledLocalToolCall = true;
927
1292
  }
928
- if (item?.type === "apply_patch_call" && type === "response.output_item.done") {
1293
+ if (item?.type === "apply_patch_call" && type === "response.output_item.done" && localTools.has("apply_patch")) {
929
1294
  yield {
930
1295
  type: "tool-call",
931
1296
  toolCall: {
932
1297
  id: item.call_id ?? item.id ?? `${json.output_index ?? "apply_patch"}`,
933
- name: "apply_patch",
934
- input: parseApplyPatchCallInput(item)
1298
+ name: localTools.get("apply_patch"),
1299
+ input: parseApplyPatchCallInput(item),
1300
+ providerMetadata: { responsesToolType: "apply_patch" }
935
1301
  }
936
1302
  };
937
1303
  sawToolCalls = true;
1304
+ handledLocalToolCall = true;
1305
+ }
1306
+ if (item?.type === "computer_call" && type === "response.output_item.done" && localTools.has("computer")) {
1307
+ yield {
1308
+ type: "tool-call",
1309
+ toolCall: {
1310
+ id: item.call_id ?? item.id ?? `${json.output_index ?? "computer"}`,
1311
+ name: localTools.get("computer"),
1312
+ input: {
1313
+ actions: Array.isArray(item.actions) ? item.actions : []
1314
+ },
1315
+ providerMetadata: { responsesToolType: "computer" }
1316
+ }
1317
+ };
1318
+ sawToolCalls = true;
1319
+ handledLocalToolCall = true;
938
1320
  }
939
1321
  const providerData = parseResponsesProviderData(item);
940
- if (providerData && type === "response.output_item.done") {
1322
+ if (providerData && type === "response.output_item.done" && !handledLocalToolCall) {
941
1323
  yield {
942
1324
  type: "provider-data",
943
1325
  provider: "openai",
944
1326
  data: providerData
945
1327
  };
946
1328
  }
1329
+ if (item && type === "response.output_item.done") {
1330
+ yield {
1331
+ type: "provider-data",
1332
+ provider: "openai",
1333
+ data: {
1334
+ type: "responses_output",
1335
+ items: [item]
1336
+ }
1337
+ };
1338
+ }
947
1339
  continue;
948
1340
  }
949
1341
  if (type === "response.function_call_arguments.delta") {
@@ -978,21 +1370,47 @@ const streamResponses = async function* (response) {
978
1370
  }
979
1371
  if (type === "response.completed" || type === "response.failed" || type === "response.incomplete") {
980
1372
  const responseData = json.response ?? {};
1373
+ if (typeof responseData.id === "string") {
1374
+ yield {
1375
+ type: "provider-data",
1376
+ provider: "openai",
1377
+ data: { responseId: responseData.id }
1378
+ };
1379
+ }
981
1380
  yield {
982
1381
  type: "finish",
983
- finishReason: normalizeResponsesFinishReason(responseData.status, sawToolCalls),
1382
+ finishReason: normalizeResponsesFinishReason(responseData.status, sawToolCalls, sawRefusal),
984
1383
  providerFinishReason: responseData.status,
985
- usage: responseData.usage
986
- ? {
987
- inputTokens: responseData.usage.input_tokens,
988
- outputTokens: responseData.usage.output_tokens,
989
- totalTokens: responseData.usage.total_tokens
990
- }
991
- : undefined
1384
+ usage: mapResponsesUsage(responseData.usage)
992
1385
  };
993
1386
  }
994
1387
  }
995
1388
  };
1389
+ const streamGenerateResult = async function* (result) {
1390
+ for (const message of result.messages ?? (result.message ? [result.message] : [])) {
1391
+ for (const part of message.parts) {
1392
+ if (part.type === "text" && part.text) {
1393
+ yield { type: "text-delta", textDelta: part.text };
1394
+ }
1395
+ else if (part.type === "tool-call") {
1396
+ yield { type: "tool-call", toolCall: part.toolCall };
1397
+ }
1398
+ else if (part.type === "provider-data") {
1399
+ yield {
1400
+ type: "provider-data",
1401
+ provider: part.provider,
1402
+ data: part.data
1403
+ };
1404
+ }
1405
+ }
1406
+ }
1407
+ yield {
1408
+ type: "finish",
1409
+ finishReason: result.finishReason,
1410
+ providerFinishReason: result.providerFinishReason,
1411
+ usage: result.usage
1412
+ };
1413
+ };
996
1414
  const extractSources = (value) => {
997
1415
  const sources = [];
998
1416
  const visit = (node) => {
@@ -1033,69 +1451,117 @@ class OpenAILanguageModel {
1033
1451
  this.fetcher = fetcher;
1034
1452
  this.capabilities = modelCapabilities(modelId);
1035
1453
  }
1036
- usesResponsesAPI(input) {
1037
- return hasResponsesOnlyTools(input.tools);
1454
+ usesResponsesAPI(input, options) {
1455
+ const requiresResponses = hasResponsesOnlyTools(input.tools) || options.multiAgentEnabled;
1456
+ if (options.apiMode === "chat") {
1457
+ if (requiresResponses) {
1458
+ throw new UnsupportedFeatureError('Provider "openai" cannot use apiMode "chat" with Responses-only tools or Multi-agent.');
1459
+ }
1460
+ return false;
1461
+ }
1462
+ return options.apiMode === "responses" || requiresResponses || isOpenAIGpt56Model(this.modelId);
1038
1463
  }
1039
- async generateViaResponses(input, signal) {
1464
+ async generateViaResponses(input, signal, options) {
1465
+ const responseBodyOptions = openAIResponsesBodyOptions(options.bodyOptions, this.modelId);
1040
1466
  assertResponsesToolsSupported(this.modelId, input.tools);
1041
- const previousResponse = getProviderResponseId(input.messages);
1467
+ assertOpenAIResponsesOptionsSupported(this.modelId, responseBodyOptions);
1468
+ const previousResponse = responseBodyOptions.store === false ? undefined : getProviderResponseId(input.messages);
1042
1469
  const messages = previousResponse && previousResponse.index < input.messages.length - 1
1043
1470
  ? input.messages.slice(previousResponse.index + 1)
1044
1471
  : input.messages;
1045
- const response = await withRetry(() => this.fetcher(`${this.baseURL}/responses`, {
1046
- method: "POST",
1047
- headers: jsonHeaders(this.apiKey),
1048
- signal,
1049
- body: JSON.stringify({
1050
- ...input.providerOptions,
1051
- model: this.modelId,
1052
- ...(previousResponse ? { previous_response_id: previousResponse.responseId } : {}),
1053
- ...(messages.length ? { input: toResponsesInput(messages) } : {}),
1054
- tools: mapResponsesTools(input.tools),
1055
- tool_choice: mapToolChoice(input.toolChoice),
1056
- text: mapResponsesStructuredOutput(input),
1057
- temperature: input.temperature,
1058
- max_output_tokens: input.maxTokens,
1059
- ...mapReasoning(input)
1060
- })
1061
- }), input);
1062
- const json = await parseJson(response);
1063
- const assistantMessage = parseResponsesAssistantMessage(json);
1064
- const hasToolCalls = assistantMessage.parts.some((part) => part.type === "tool-call");
1065
- return {
1066
- messages: [assistantMessage],
1067
- text: extractMessageText(assistantMessage),
1068
- audio: extractAudioOutputs(assistantMessage),
1069
- finishReason: normalizeResponsesFinishReason(json.status, hasToolCalls),
1070
- providerFinishReason: json.status,
1071
- usage: {
1072
- inputTokens: json.usage?.input_tokens,
1073
- outputTokens: json.usage?.output_tokens,
1074
- totalTokens: json.usage?.total_tokens
1075
- },
1076
- rawResponse: json
1077
- };
1472
+ let nextPreviousResponseId = previousResponse?.responseId;
1473
+ let nextInput = messages.length ? toResponsesInput(messages) : [];
1474
+ let accumulatedUsage;
1475
+ let statelessInternalOutputs = [];
1476
+ for (let continuation = 0; continuation < 8; continuation += 1) {
1477
+ const response = await withRetry(() => this.fetcher(`${this.baseURL}/responses`, {
1478
+ method: "POST",
1479
+ headers: options.headers,
1480
+ signal,
1481
+ body: JSON.stringify({
1482
+ ...responseBodyOptions,
1483
+ model: this.modelId,
1484
+ ...(nextPreviousResponseId ? { previous_response_id: nextPreviousResponseId } : {}),
1485
+ ...(nextInput.length ? { input: nextInput } : {}),
1486
+ tools: mapResponsesTools(input.tools),
1487
+ ...(input.toolChoice ? { tool_choice: mapResponsesToolChoice(input.toolChoice) } : {}),
1488
+ text: mapResponsesStructuredOutput(input),
1489
+ temperature: input.temperature,
1490
+ max_output_tokens: input.maxTokens,
1491
+ ...mapResponsesReasoning(input, responseBodyOptions.reasoning)
1492
+ })
1493
+ }), input);
1494
+ const json = await parseJson(response);
1495
+ accumulatedUsage = addTokenUsage(accumulatedUsage, mapResponsesUsage(json.usage));
1496
+ const assistantMessage = parseResponsesAssistantMessage(json, options.multiAgentEnabled, localResponsesTools(input.tools));
1497
+ const currentOutput = Array.isArray(json.output)
1498
+ ? json.output
1499
+ : [];
1500
+ const completeStatelessOutput = [...statelessInternalOutputs, ...currentOutput];
1501
+ if (responseBodyOptions.store === false && completeStatelessOutput.length) {
1502
+ assistantMessage.parts = assistantMessage.parts.filter((part) => part.type !== "provider-data" ||
1503
+ part.provider !== "openai" ||
1504
+ !part.data ||
1505
+ typeof part.data !== "object" ||
1506
+ part.data.type !== "responses_output");
1507
+ assistantMessage.parts.push(providerDataPart("openai", {
1508
+ type: "responses_output",
1509
+ items: completeStatelessOutput
1510
+ }));
1511
+ }
1512
+ const hasToolCalls = assistantMessage.parts.some((part) => part.type === "tool-call");
1513
+ const hasRefusal = (json.output ?? []).some((item) => item?.type === "message" &&
1514
+ (item.content ?? []).some((content) => content?.type === "refusal"));
1515
+ const hasFinalMessage = (json.output ?? []).some((item) => item?.type === "message" &&
1516
+ (!options.multiAgentEnabled || (item.agent?.agent_name === "/root" && item.phase === "final_answer")));
1517
+ const shouldContinueProgram = hasProgrammaticToolCalling(input.tools) &&
1518
+ json.status === "completed" &&
1519
+ !hasToolCalls &&
1520
+ !hasFinalMessage &&
1521
+ (json.output ?? []).some((item) => item?.type === "program" || item?.type === "program_output");
1522
+ if (!shouldContinueProgram) {
1523
+ return {
1524
+ messages: [assistantMessage],
1525
+ text: extractMessageText(assistantMessage),
1526
+ audio: extractAudioOutputs(assistantMessage),
1527
+ finishReason: normalizeResponsesFinishReason(json.status, hasToolCalls, hasRefusal),
1528
+ providerFinishReason: json.status,
1529
+ usage: accumulatedUsage,
1530
+ rawResponse: json
1531
+ };
1532
+ }
1533
+ if (responseBodyOptions.store === false) {
1534
+ statelessInternalOutputs = completeStatelessOutput;
1535
+ nextInput = [...nextInput, ...currentOutput];
1536
+ }
1537
+ else {
1538
+ nextPreviousResponseId = json.id;
1539
+ nextInput = [];
1540
+ }
1541
+ }
1542
+ throw new ProviderHTTPError("OpenAI Programmatic Tool Calling exceeded 8 internal continuations.", 500);
1078
1543
  }
1079
1544
  async generate(input) {
1080
1545
  const { signal, cleanup } = getRequestOptions(input);
1546
+ const options = resolveOpenAILanguageRequestOptions(input.providerOptions, this.apiKey);
1081
1547
  try {
1082
- if (this.usesResponsesAPI(input)) {
1083
- return await this.generateViaResponses(input, signal);
1548
+ if (this.usesResponsesAPI(input, options)) {
1549
+ return await this.generateViaResponses(input, signal, options);
1084
1550
  }
1085
1551
  const response = await withRetry(() => this.fetcher(`${this.baseURL}/chat/completions`, {
1086
1552
  method: "POST",
1087
- headers: jsonHeaders(this.apiKey),
1553
+ headers: options.headers,
1088
1554
  signal,
1089
1555
  body: JSON.stringify({
1090
- ...input.providerOptions,
1556
+ ...options.bodyOptions,
1091
1557
  model: this.modelId,
1092
1558
  messages: mapMessages(input.messages),
1093
1559
  tools: mapTools(input.tools),
1094
- tool_choice: mapToolChoice(input.toolChoice),
1560
+ ...(input.toolChoice ? { tool_choice: mapToolChoice(input.toolChoice) } : {}),
1095
1561
  response_format: mapStructuredOutput(input),
1096
1562
  temperature: input.temperature,
1097
1563
  ...(input.reasoning ? {} : { max_tokens: input.maxTokens }),
1098
- ...mapReasoning(input),
1564
+ ...mapChatReasoning(input),
1099
1565
  stream: false
1100
1566
  })
1101
1567
  }), input);
@@ -1109,11 +1575,7 @@ class OpenAILanguageModel {
1109
1575
  audio: extractAudioOutputs(assistantMessage),
1110
1576
  finishReason: normalizeFinishReason(choice?.finish_reason),
1111
1577
  providerFinishReason: choice?.finish_reason,
1112
- usage: {
1113
- inputTokens: json.usage?.prompt_tokens,
1114
- outputTokens: json.usage?.completion_tokens,
1115
- totalTokens: json.usage?.total_tokens
1116
- },
1578
+ usage: mapChatUsage(json.usage),
1117
1579
  rawResponse: json
1118
1580
  };
1119
1581
  }
@@ -1122,29 +1584,48 @@ class OpenAILanguageModel {
1122
1584
  }
1123
1585
  }
1124
1586
  async stream(input) {
1125
- if (this.usesResponsesAPI(input)) {
1587
+ const options = resolveOpenAILanguageRequestOptions(input.providerOptions, this.apiKey);
1588
+ if (this.usesResponsesAPI(input, options)) {
1589
+ const responseBodyOptions = openAIResponsesBodyOptions(options.bodyOptions, this.modelId);
1126
1590
  assertResponsesToolsSupported(this.modelId, input.tools);
1591
+ assertOpenAIResponsesOptionsSupported(this.modelId, responseBodyOptions);
1127
1592
  const { signal, cleanup } = getRequestOptions(input);
1593
+ if (hasProgrammaticToolCalling(input.tools)) {
1594
+ const result = await this.generateViaResponses(input, signal, options);
1595
+ return (async function* () {
1596
+ try {
1597
+ yield* streamGenerateResult(result);
1598
+ }
1599
+ finally {
1600
+ cleanup();
1601
+ }
1602
+ })();
1603
+ }
1604
+ const previousResponse = responseBodyOptions.store === false ? undefined : getProviderResponseId(input.messages);
1605
+ const messages = previousResponse && previousResponse.index < input.messages.length - 1
1606
+ ? input.messages.slice(previousResponse.index + 1)
1607
+ : input.messages;
1128
1608
  const response = await withRetry(() => this.fetcher(`${this.baseURL}/responses`, {
1129
1609
  method: "POST",
1130
- headers: jsonHeaders(this.apiKey),
1610
+ headers: options.headers,
1131
1611
  signal,
1132
1612
  body: JSON.stringify({
1133
- ...input.providerOptions,
1613
+ ...responseBodyOptions,
1134
1614
  model: this.modelId,
1135
- input: toResponsesInput(input.messages),
1615
+ ...(previousResponse ? { previous_response_id: previousResponse.responseId } : {}),
1616
+ ...(messages.length ? { input: toResponsesInput(messages) } : {}),
1136
1617
  tools: mapResponsesTools(input.tools),
1137
- tool_choice: mapToolChoice(input.toolChoice),
1618
+ ...(input.toolChoice ? { tool_choice: mapResponsesToolChoice(input.toolChoice) } : {}),
1138
1619
  text: mapResponsesStructuredOutput(input),
1139
1620
  temperature: input.temperature,
1140
1621
  max_output_tokens: input.maxTokens,
1141
- ...mapReasoning(input),
1622
+ ...mapResponsesReasoning(input, responseBodyOptions.reasoning),
1142
1623
  stream: true
1143
1624
  })
1144
1625
  }), input);
1145
1626
  return (async function* () {
1146
1627
  try {
1147
- yield* streamResponses(response);
1628
+ yield* streamResponses(response, options.multiAgentEnabled, localResponsesTools(input.tools));
1148
1629
  }
1149
1630
  finally {
1150
1631
  cleanup();
@@ -1154,18 +1635,18 @@ class OpenAILanguageModel {
1154
1635
  const { signal, cleanup } = getRequestOptions(input);
1155
1636
  const response = await withRetry(() => this.fetcher(`${this.baseURL}/chat/completions`, {
1156
1637
  method: "POST",
1157
- headers: jsonHeaders(this.apiKey),
1638
+ headers: options.headers,
1158
1639
  signal,
1159
1640
  body: JSON.stringify({
1160
- ...input.providerOptions,
1641
+ ...options.bodyOptions,
1161
1642
  model: this.modelId,
1162
1643
  messages: mapMessages(input.messages),
1163
1644
  tools: mapTools(input.tools),
1164
- tool_choice: mapToolChoice(input.toolChoice),
1645
+ ...(input.toolChoice ? { tool_choice: mapToolChoice(input.toolChoice) } : {}),
1165
1646
  response_format: mapStructuredOutput(input),
1166
1647
  temperature: input.temperature,
1167
1648
  ...(input.reasoning ? {} : { max_tokens: input.maxTokens }),
1168
- ...mapReasoning(input),
1649
+ ...mapChatReasoning(input),
1169
1650
  stream: true,
1170
1651
  stream_options: { include_usage: true }
1171
1652
  })
@@ -1208,13 +1689,7 @@ class OpenAILanguageModel {
1208
1689
  type: "finish",
1209
1690
  finishReason: normalizeFinishReason(choice.finish_reason),
1210
1691
  providerFinishReason: choice.finish_reason,
1211
- usage: json.usage
1212
- ? {
1213
- inputTokens: json.usage.prompt_tokens,
1214
- outputTokens: json.usage.completion_tokens,
1215
- totalTokens: json.usage.total_tokens
1216
- }
1217
- : undefined
1692
+ usage: mapChatUsage(json.usage)
1218
1693
  };
1219
1694
  }
1220
1695
  }
@@ -1375,29 +1850,28 @@ class OpenAIGroundedLanguageModel {
1375
1850
  }
1376
1851
  async generate(input) {
1377
1852
  const { signal, cleanup } = withTimeoutSignal(input);
1853
+ const options = resolveOpenAILanguageRequestOptions(input.providerOptions, this.apiKey);
1854
+ const responseBodyOptions = openAIResponsesBodyOptions(options.bodyOptions, this.modelId);
1378
1855
  try {
1379
1856
  const response = await withRetry(() => this.fetcher(`${this.baseURL}/responses`, {
1380
1857
  method: "POST",
1381
- headers: jsonHeaders(this.apiKey),
1858
+ headers: options.headers,
1382
1859
  signal,
1383
1860
  body: JSON.stringify({
1384
- ...input.providerOptions,
1861
+ ...responseBodyOptions,
1385
1862
  model: this.modelId,
1386
1863
  input: toResponsesInput(input.messages),
1387
1864
  tools: [{ type: "web_search_preview" }],
1388
1865
  temperature: input.temperature,
1389
- max_output_tokens: input.maxTokens
1866
+ max_output_tokens: input.maxTokens,
1867
+ ...mapResponsesReasoning(input, responseBodyOptions.reasoning)
1390
1868
  })
1391
1869
  }), input);
1392
1870
  const json = await parseJson(response);
1393
1871
  return {
1394
1872
  text: json.output_text ?? "",
1395
1873
  sources: extractSources(json),
1396
- usage: {
1397
- inputTokens: json.usage?.input_tokens,
1398
- outputTokens: json.usage?.output_tokens,
1399
- totalTokens: json.usage?.total_tokens
1400
- },
1874
+ usage: mapResponsesUsage(json.usage),
1401
1875
  finishReason: normalizeFinishReason(json.status),
1402
1876
  providerFinishReason: json.status,
1403
1877
  rawResponse: json
@@ -1644,6 +2118,22 @@ export const openAIToolSearchTool = (config = {}) => hostedTool({
1644
2118
  toolClass: "tool-search",
1645
2119
  config: config
1646
2120
  });
2121
+ export const openAIProgrammaticToolCallingTool = () => hostedTool({
2122
+ name: "programmatic_tool_calling",
2123
+ provider: "openai",
2124
+ type: "programmatic_tool_calling",
2125
+ toolClass: "code-execution"
2126
+ });
2127
+ export const openAIProgrammaticTool = (definition, options = {}) => ({
2128
+ ...definition,
2129
+ metadata: {
2130
+ ...definition.metadata,
2131
+ [openAIResponsesFunctionConfigMetadataKey]: {
2132
+ allowed_callers: options.allowedCallers ?? ["programmatic"],
2133
+ ...(options.outputSchema ? { output_schema: toJSONSchema(options.outputSchema) } : {})
2134
+ }
2135
+ }
2136
+ });
1647
2137
  export const openAIRemoteMcpTool = (config) => hostedTool({
1648
2138
  name: config.server_label ?? "mcp",
1649
2139
  provider: "openai",
@@ -1656,6 +2146,10 @@ export const openAIMcpApprovalResponse = (response) => providerDataPart("openai"
1656
2146
  type: "mcp_approval_response",
1657
2147
  ...response
1658
2148
  });
2149
+ export const openAIPromptCacheBreakpoint = () => providerDataPart("openai", {
2150
+ type: "prompt_cache_breakpoint",
2151
+ mode: "explicit"
2152
+ });
1659
2153
  export const openAIComputerUseTool = (config) => hostedTool({
1660
2154
  name: "computer",
1661
2155
  provider: "openai",
@@ -1663,63 +2157,106 @@ export const openAIComputerUseTool = (config) => hostedTool({
1663
2157
  toolClass: "computer-use",
1664
2158
  config: config
1665
2159
  });
2160
+ export const openAIComputerTool = (config) => ({
2161
+ name: config.name ?? "computer",
2162
+ description: "Execute batched actions requested by the OpenAI Responses computer tool and return a screenshot.",
2163
+ requiresApproval: config.requiresApproval ?? true,
2164
+ metadata: {
2165
+ [openAIResponsesToolMetadataKey]: "computer",
2166
+ "openai.responses_tool_config": {}
2167
+ },
2168
+ schema: z.object({
2169
+ actions: z.array(z.record(z.string(), z.unknown()))
2170
+ }),
2171
+ execute: async (input) => {
2172
+ const output = await config.execute(input);
2173
+ return {
2174
+ ...output,
2175
+ detail: "original"
2176
+ };
2177
+ }
2178
+ });
1666
2179
  const runOpenAIShellCommand = async (input, config) => {
1667
2180
  if (config.execute) {
1668
- return config.execute(input);
2181
+ const result = await config.execute(input);
2182
+ return Array.isArray(result) ? result : [result];
1669
2183
  }
1670
- const command = input.command ?? input.action?.command;
1671
- if (!command) {
2184
+ const commands = input.action?.commands?.length
2185
+ ? input.action.commands
2186
+ : [input.command ?? input.action?.command].filter((command) => Boolean(command));
2187
+ if (!commands.length) {
1672
2188
  throw new Error("OpenAI shell tool did not provide a command.");
1673
2189
  }
1674
2190
  const { exec } = await import("node:child_process");
1675
2191
  const { promisify } = await import("node:util");
1676
2192
  const execAsync = promisify(exec);
1677
- const maxBuffer = Math.max(1024, config.maxOutputLength ?? input.maxOutputLength ?? input.max_output_length ?? input.action?.maxOutputLength ?? input.action?.max_output_length ?? 20000);
2193
+ const maxBuffer = Math.max(1024, config.maxOutputLength ??
2194
+ input.maxOutputLength ??
2195
+ input.max_output_length ??
2196
+ input.action?.maxOutputLength ??
2197
+ input.action?.max_output_length ??
2198
+ 20000);
1678
2199
  const cwd = config.rootDir
1679
2200
  ? await assertOpenAIToolPathInsideRoot(config.rootDir, config.cwd ?? ".", "shell cwd")
1680
2201
  : config.cwd;
1681
- try {
1682
- const result = await execAsync(command, {
1683
- cwd,
1684
- timeout: config.timeoutMs,
1685
- maxBuffer
1686
- });
1687
- return {
1688
- stdout: result.stdout,
1689
- stderr: result.stderr,
1690
- outcome: {
1691
- type: "exit",
1692
- exitCode: 0
1693
- },
1694
- maxOutputLength: maxBuffer
1695
- };
1696
- }
1697
- catch (error) {
1698
- const err = error;
1699
- return {
1700
- stdout: err.stdout ?? "",
1701
- stderr: err.stderr ?? err.message ?? "",
1702
- outcome: {
1703
- type: err.killed || err.signal === "SIGTERM" ? "timeout" : "exit",
1704
- exitCode: typeof err.code === "number" ? err.code : 1
1705
- },
1706
- maxOutputLength: maxBuffer
1707
- };
2202
+ const output = [];
2203
+ for (const command of commands) {
2204
+ try {
2205
+ const result = await execAsync(command, {
2206
+ cwd,
2207
+ timeout: config.timeoutMs ?? input.action?.timeout_ms,
2208
+ maxBuffer
2209
+ });
2210
+ output.push({
2211
+ stdout: result.stdout,
2212
+ stderr: result.stderr,
2213
+ outcome: { type: "exit", exit_code: 0 },
2214
+ maxOutputLength: maxBuffer
2215
+ });
2216
+ }
2217
+ catch (error) {
2218
+ const err = error;
2219
+ output.push({
2220
+ stdout: err.stdout ?? "",
2221
+ stderr: err.stderr ?? err.message ?? "",
2222
+ outcome: {
2223
+ type: err.killed || err.signal === "SIGTERM" ? "timeout" : "exit",
2224
+ exit_code: typeof err.code === "number" ? err.code : 1
2225
+ },
2226
+ maxOutputLength: maxBuffer
2227
+ });
2228
+ }
1708
2229
  }
2230
+ return output;
1709
2231
  };
2232
+ export const openAIHostedShellTool = (config) => hostedTool({
2233
+ name: "shell",
2234
+ provider: "openai",
2235
+ type: "shell",
2236
+ toolClass: "shell",
2237
+ config: {
2238
+ environment: config.environment,
2239
+ ...(config.allowedCallers ? { allowed_callers: config.allowedCallers } : {})
2240
+ }
2241
+ });
1710
2242
  export const openAIShellTool = (config = {}) => ({
1711
2243
  name: config.name ?? "shell",
1712
2244
  description: "Run a shell command requested by the OpenAI Responses shell tool.",
1713
2245
  requiresApproval: true,
1714
2246
  metadata: {
1715
2247
  [openAIResponsesToolMetadataKey]: "shell",
1716
- "openai.responses_tool_config": {}
2248
+ "openai.responses_tool_config": {
2249
+ environment: config.environment ?? { type: "local" },
2250
+ ...(config.allowedCallers ? { allowed_callers: config.allowedCallers } : {})
2251
+ }
1717
2252
  },
1718
2253
  schema: z.object({
1719
2254
  command: z.string().optional(),
1720
2255
  action: z
1721
2256
  .object({
1722
2257
  command: z.string().optional(),
2258
+ commands: z.array(z.string()).optional(),
2259
+ timeout_ms: z.number().optional(),
1723
2260
  max_output_length: z.number().optional(),
1724
2261
  maxOutputLength: z.number().optional()
1725
2262
  })
@@ -1742,7 +2279,9 @@ export const openAIApplyPatchTool = (config) => ({
1742
2279
  requiresApproval: true,
1743
2280
  metadata: {
1744
2281
  [openAIResponsesToolMetadataKey]: "apply_patch",
1745
- "openai.responses_tool_config": {}
2282
+ "openai.responses_tool_config": {
2283
+ ...(config.allowedCallers ? { allowed_callers: config.allowedCallers } : {})
2284
+ }
1746
2285
  },
1747
2286
  schema: z.object({
1748
2287
  operation: z