@openai/agents-openai 0.5.4 → 0.7.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 (39) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +2 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +1 -1
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/metadata.js +3 -3
  7. package/dist/metadata.mjs +3 -3
  8. package/dist/openaiChatCompletionsConverter.d.ts +1 -0
  9. package/dist/openaiChatCompletionsConverter.js +122 -16
  10. package/dist/openaiChatCompletionsConverter.js.map +1 -1
  11. package/dist/openaiChatCompletionsConverter.mjs +121 -16
  12. package/dist/openaiChatCompletionsConverter.mjs.map +1 -1
  13. package/dist/openaiChatCompletionsModel.d.ts +2 -1
  14. package/dist/openaiChatCompletionsModel.js +20 -3
  15. package/dist/openaiChatCompletionsModel.js.map +1 -1
  16. package/dist/openaiChatCompletionsModel.mjs +22 -5
  17. package/dist/openaiChatCompletionsModel.mjs.map +1 -1
  18. package/dist/openaiResponsesModel.d.ts +19 -5
  19. package/dist/openaiResponsesModel.js +724 -74
  20. package/dist/openaiResponsesModel.js.map +1 -1
  21. package/dist/openaiResponsesModel.mjs +726 -76
  22. package/dist/openaiResponsesModel.mjs.map +1 -1
  23. package/dist/retryAdvice.d.ts +2 -0
  24. package/dist/retryAdvice.js +98 -0
  25. package/dist/retryAdvice.js.map +1 -0
  26. package/dist/retryAdvice.mjs +95 -0
  27. package/dist/retryAdvice.mjs.map +1 -0
  28. package/dist/tools.d.ts +22 -1
  29. package/dist/tools.js +38 -0
  30. package/dist/tools.js.map +1 -1
  31. package/dist/tools.mjs +37 -0
  32. package/dist/tools.mjs.map +1 -1
  33. package/dist/types/providerData.d.ts +4 -0
  34. package/dist/utils/providerData.d.ts +26 -2
  35. package/dist/utils/providerData.js +60 -8
  36. package/dist/utils/providerData.js.map +1 -1
  37. package/dist/utils/providerData.mjs +57 -8
  38. package/dist/utils/providerData.mjs.map +1 -1
  39. package/package.json +3 -3
@@ -11,6 +11,7 @@ exports.convertToOutputItem = convertToOutputItem;
11
11
  const agents_core_1 = require("@openai/agents-core");
12
12
  const openai_1 = __importDefault(require("openai"));
13
13
  const logger_1 = __importDefault(require("./logger.js"));
14
+ const retryAdvice_1 = require("./retryAdvice.js");
14
15
  const zod_1 = require("zod");
15
16
  const defaults_1 = require("./defaults.js");
16
17
  const responsesWebSocketConnection_1 = require("./responsesWebSocketConnection.js");
@@ -18,6 +19,106 @@ const responsesTransportUtils_1 = require("./responsesTransportUtils.js");
18
19
  const tools_1 = require("./tools.js");
19
20
  const providerData_1 = require("./utils/providerData.js");
20
21
  const utils_1 = require("@openai/agents-core/utils");
22
+ /**
23
+ * Tool search outputs are replayed through agents-core protocol items, which use camelCase
24
+ * field names, while the Responses API wire shape uses snake_case. Keep this codec even with
25
+ * first-class upstream types because the local protocol still normalizes these payloads.
26
+ */
27
+ function toOpenAIToolSearchOutputToolPayload(tool, _withinNamespace = false) {
28
+ if (!tool || typeof tool !== 'object' || Array.isArray(tool)) {
29
+ return tool;
30
+ }
31
+ if (tool.type === 'tool_reference' && typeof tool.functionName === 'string') {
32
+ return {
33
+ type: 'tool_reference',
34
+ function_name: tool.functionName,
35
+ ...(typeof tool.namespace === 'string'
36
+ ? { namespace: tool.namespace }
37
+ : {}),
38
+ };
39
+ }
40
+ if (tool.type === 'namespace' && Array.isArray(tool.tools)) {
41
+ return {
42
+ ...tool,
43
+ tools: tool.tools.map((entry) => toOpenAIToolSearchOutputToolPayload(entry, true)),
44
+ };
45
+ }
46
+ if (tool.type === 'function') {
47
+ const { deferLoading, ...rest } = tool;
48
+ return {
49
+ ...rest,
50
+ ...(typeof deferLoading === 'boolean'
51
+ ? { defer_loading: deferLoading }
52
+ : {}),
53
+ };
54
+ }
55
+ return tool;
56
+ }
57
+ function fromOpenAIToolSearchOutputToolPayload(tool, _withinNamespace = false) {
58
+ if (!tool || typeof tool !== 'object' || Array.isArray(tool)) {
59
+ return tool;
60
+ }
61
+ if (tool.type === 'tool_reference' &&
62
+ typeof tool.function_name === 'string') {
63
+ return {
64
+ type: 'tool_reference',
65
+ functionName: tool.function_name,
66
+ ...(typeof tool.namespace === 'string'
67
+ ? { namespace: tool.namespace }
68
+ : {}),
69
+ };
70
+ }
71
+ if (tool.type === 'namespace' && Array.isArray(tool.tools)) {
72
+ return {
73
+ ...tool,
74
+ tools: tool.tools.map((entry) => fromOpenAIToolSearchOutputToolPayload(entry, true)),
75
+ };
76
+ }
77
+ if (tool.type === 'function') {
78
+ const { defer_loading, ...rest } = tool;
79
+ return {
80
+ ...rest,
81
+ ...(typeof defer_loading === 'boolean'
82
+ ? { deferLoading: defer_loading }
83
+ : {}),
84
+ };
85
+ }
86
+ return tool;
87
+ }
88
+ function isNeverSentWebSocketError(error) {
89
+ if ((0, responsesWebSocketConnection_1.isWebSocketNotOpenError)(error)) {
90
+ return true;
91
+ }
92
+ const errorCause = error instanceof Error
93
+ ? error.cause
94
+ : undefined;
95
+ if (error instanceof responsesWebSocketConnection_1.ResponsesWebSocketInternalError &&
96
+ error.code === 'connection_closed_before_opening') {
97
+ return true;
98
+ }
99
+ if (errorCause instanceof responsesWebSocketConnection_1.ResponsesWebSocketInternalError &&
100
+ errorCause.code === 'connection_closed_before_opening') {
101
+ return true;
102
+ }
103
+ return false;
104
+ }
105
+ function isAmbiguousWebSocketReplayError(error) {
106
+ if (error instanceof responsesWebSocketConnection_1.ResponsesWebSocketInternalError &&
107
+ error.code === 'connection_closed_before_terminal_response_event') {
108
+ return true;
109
+ }
110
+ const errorCause = error instanceof Error
111
+ ? error.cause
112
+ : undefined;
113
+ return (errorCause instanceof responsesWebSocketConnection_1.ResponsesWebSocketInternalError &&
114
+ errorCause.code === 'connection_closed_before_terminal_response_event');
115
+ }
116
+ function hasSerializedComputerDisplayMetadata(tool) {
117
+ return (typeof tool.environment === 'string' &&
118
+ Array.isArray(tool.dimensions) &&
119
+ tool.dimensions.length === 2 &&
120
+ tool.dimensions.every((value) => typeof value === 'number'));
121
+ }
21
122
  const HostedToolChoice = zod_1.z.enum([
22
123
  'file_search',
23
124
  'web_search',
@@ -26,12 +127,16 @@ const HostedToolChoice = zod_1.z.enum([
26
127
  'image_generation',
27
128
  'mcp',
28
129
  // Specialized local tools
29
- 'computer_use_preview',
30
130
  'shell',
31
131
  'apply_patch',
32
132
  ]);
33
133
  const DefaultToolChoice = zod_1.z.enum(['auto', 'required', 'none']);
34
- function getToolChoice(toolChoice) {
134
+ const BuiltinComputerToolChoice = zod_1.z.enum([
135
+ 'computer',
136
+ 'computer_use',
137
+ 'computer_use_preview',
138
+ ]);
139
+ function getToolChoice(toolChoice, options) {
35
140
  if (typeof toolChoice === 'undefined') {
36
141
  return undefined;
37
142
  }
@@ -39,12 +144,202 @@ function getToolChoice(toolChoice) {
39
144
  if (resultDefaultCheck.success) {
40
145
  return resultDefaultCheck.data;
41
146
  }
147
+ const builtinComputerToolChoice = BuiltinComputerToolChoice.safeParse(toolChoice);
148
+ if (builtinComputerToolChoice.success) {
149
+ if (hasBuiltinComputerTool(options?.tools) ||
150
+ options?.allowPromptSuppliedComputerTool === true) {
151
+ return getBuiltinComputerToolChoice(builtinComputerToolChoice.data, {
152
+ model: options?.model,
153
+ });
154
+ }
155
+ if (builtinComputerToolChoice.data === 'computer_use_preview') {
156
+ return { type: 'computer_use_preview' };
157
+ }
158
+ return { type: 'function', name: builtinComputerToolChoice.data };
159
+ }
42
160
  const result = HostedToolChoice.safeParse(toolChoice);
43
161
  if (result.success) {
44
162
  return { type: result.data };
45
163
  }
46
164
  return { type: 'function', name: toolChoice };
47
165
  }
166
+ function normalizeToolSearchStatus(status) {
167
+ return status === 'in_progress' ||
168
+ status === 'completed' ||
169
+ status === 'incomplete'
170
+ ? status
171
+ : null;
172
+ }
173
+ function hasBuiltinComputerTool(tools) {
174
+ return (tools ?? []).some((tool) => tool.type === 'computer' ||
175
+ tool.type === 'computer_use' ||
176
+ tool.type === 'computer_use_preview');
177
+ }
178
+ function isPreviewComputerModel(model) {
179
+ return typeof model === 'string' && model.startsWith('computer-use-preview');
180
+ }
181
+ function shouldUsePreviewComputerTool(options) {
182
+ if (isPreviewComputerModel(options?.model)) {
183
+ return true;
184
+ }
185
+ if (typeof options?.model === 'string') {
186
+ return false;
187
+ }
188
+ if (options?.toolChoice === 'computer' ||
189
+ options?.toolChoice === 'computer_use') {
190
+ return false;
191
+ }
192
+ return true;
193
+ }
194
+ function getBuiltinComputerToolChoice(toolChoice, options) {
195
+ if (shouldUsePreviewComputerTool({
196
+ model: options?.model,
197
+ toolChoice,
198
+ })) {
199
+ return { type: 'computer_use_preview' };
200
+ }
201
+ if (toolChoice === 'computer_use') {
202
+ return { type: 'computer_use' };
203
+ }
204
+ return { type: 'computer' };
205
+ }
206
+ function isBuiltinComputerToolType(type) {
207
+ return (type === 'computer' ||
208
+ type === 'computer_use' ||
209
+ type === 'computer_use_preview');
210
+ }
211
+ function isCompatibleBuiltinComputerToolChoice(toolChoiceType, toolType) {
212
+ if (!isBuiltinComputerToolType(toolChoiceType)) {
213
+ return false;
214
+ }
215
+ if (toolChoiceType === 'computer_use_preview') {
216
+ return toolType === 'computer_use_preview';
217
+ }
218
+ return toolType === 'computer';
219
+ }
220
+ function isToolChoiceAvailable(toolChoice, tools) {
221
+ if (toolChoice === 'auto' || toolChoice === 'none') {
222
+ return true;
223
+ }
224
+ if (toolChoice === 'required') {
225
+ return tools.length > 0;
226
+ }
227
+ if (toolChoice.type === 'function') {
228
+ return hasFunctionToolChoiceName(toolChoice.name, tools);
229
+ }
230
+ return tools.some((tool) => isCompatibleBuiltinComputerToolChoice(toolChoice.type, tool.type)
231
+ ? true
232
+ : tool.type === toolChoice.type);
233
+ }
234
+ function hasFunctionToolChoiceName(toolChoiceName, tools, namespacePrefix) {
235
+ return (findFunctionToolChoice(toolChoiceName, tools, namespacePrefix) !== undefined);
236
+ }
237
+ function findFunctionToolChoice(toolChoiceName, tools, namespacePrefix) {
238
+ for (const tool of tools) {
239
+ if (isNamedFunctionTool(tool)) {
240
+ const qualifiedName = namespacePrefix
241
+ ? `${namespacePrefix}.${tool.name}`
242
+ : tool.name;
243
+ if (toolChoiceName === qualifiedName) {
244
+ return tool;
245
+ }
246
+ continue;
247
+ }
248
+ if (isNamespaceTool(tool)) {
249
+ const nestedNamespace = namespacePrefix
250
+ ? `${namespacePrefix}.${tool.name}`
251
+ : tool.name;
252
+ const matchedTool = findFunctionToolChoice(toolChoiceName, tool.tools, nestedNamespace);
253
+ if (matchedTool) {
254
+ return matchedTool;
255
+ }
256
+ }
257
+ }
258
+ return undefined;
259
+ }
260
+ function collectAvailableToolChoiceNames(tools, namespacePrefix) {
261
+ const availableToolChoices = [];
262
+ for (const tool of tools) {
263
+ if (isNamedFunctionTool(tool)) {
264
+ availableToolChoices.push(namespacePrefix ? `${namespacePrefix}.${tool.name}` : tool.name);
265
+ continue;
266
+ }
267
+ if (isNamespaceTool(tool)) {
268
+ const nestedNamespace = namespacePrefix
269
+ ? `${namespacePrefix}.${tool.name}`
270
+ : tool.name;
271
+ availableToolChoices.push(...collectAvailableToolChoiceNames(tool.tools, nestedNamespace));
272
+ continue;
273
+ }
274
+ availableToolChoices.push(tool.type);
275
+ }
276
+ return availableToolChoices;
277
+ }
278
+ function isNamedFunctionTool(tool) {
279
+ return (tool.type === 'function' &&
280
+ typeof tool.name === 'string');
281
+ }
282
+ function isNamespaceTool(tool) {
283
+ const candidate = tool;
284
+ return (tool.type === 'namespace' &&
285
+ typeof candidate.name === 'string' &&
286
+ Array.isArray(candidate.tools));
287
+ }
288
+ function getExtraBodyToolsForToolChoiceValidation(extraBody) {
289
+ if (!extraBody || !Array.isArray(extraBody.tools)) {
290
+ return [];
291
+ }
292
+ return extraBody.tools;
293
+ }
294
+ function assertSupportedToolChoice(toolChoice, tools, options) {
295
+ const allowPromptSuppliedTools = options?.allowPromptSuppliedTools === true;
296
+ if (!toolChoice ||
297
+ toolChoice === 'auto' ||
298
+ toolChoice === 'required' ||
299
+ toolChoice === 'none' ||
300
+ toolChoice.type !== 'function') {
301
+ return;
302
+ }
303
+ const matchedFunctionTool = findFunctionToolChoice(toolChoice.name, tools);
304
+ if (!matchedFunctionTool &&
305
+ allowPromptSuppliedTools &&
306
+ toolChoice.name !== 'tool_search') {
307
+ return;
308
+ }
309
+ if (matchedFunctionTool
310
+ ?.defer_loading === true) {
311
+ throw new agents_core_1.UserError(`modelSettings.toolChoice="${toolChoice.name}" cannot force a deferred function tool in Responses. Use "auto" so tool_search can load it.`);
312
+ }
313
+ if (toolChoice.name === 'tool_search' &&
314
+ !hasFunctionToolChoiceName(toolChoice.name, tools)) {
315
+ throw new agents_core_1.UserError('modelSettings.toolChoice="tool_search" is only supported for a custom function named "tool_search". Responses does not support forcing the built-in tool_search tool. Use "auto" instead.');
316
+ }
317
+ }
318
+ function getCompatibleToolChoice(toolChoice, tools, options) {
319
+ const allowPromptSuppliedTools = options?.allowPromptSuppliedTools === true;
320
+ if (typeof toolChoice === 'undefined') {
321
+ return undefined;
322
+ }
323
+ if (isToolChoiceAvailable(toolChoice, tools) || allowPromptSuppliedTools) {
324
+ return toolChoice;
325
+ }
326
+ const availableToolChoices = [
327
+ ...new Set(collectAvailableToolChoiceNames(tools)),
328
+ ];
329
+ const availableToolChoicesMessage = availableToolChoices.length > 0
330
+ ? ` Available tools: ${availableToolChoices.join(', ')}.`
331
+ : ' No tools are available in the outgoing Responses request.';
332
+ if (toolChoice === 'required') {
333
+ throw new agents_core_1.UserError(`modelSettings.toolChoice="required" requires at least one available tool in the outgoing Responses request.${availableToolChoicesMessage}`);
334
+ }
335
+ if (toolChoice === 'auto' || toolChoice === 'none') {
336
+ throw new Error(`Unexpected unavailable tool choice: ${JSON.stringify(toolChoice)}`);
337
+ }
338
+ if (toolChoice.type === 'function') {
339
+ throw new agents_core_1.UserError(`modelSettings.toolChoice="${toolChoice.name}" does not match any available tool in the outgoing Responses request.${availableToolChoicesMessage}`);
340
+ }
341
+ throw new agents_core_1.UserError(`modelSettings.toolChoice="${toolChoice.type}" is unavailable in the outgoing Responses request.${availableToolChoicesMessage}`);
342
+ }
48
343
  function getResponseFormat(outputType, otherProperties) {
49
344
  if (outputType === 'text') {
50
345
  return otherProperties;
@@ -98,12 +393,13 @@ function convertLegacyToolOutputContent(output) {
98
393
  const legacyImageUrl = output.imageUrl;
99
394
  const legacyFileId = output.fileId;
100
395
  const dataValue = output.data;
396
+ const topLevelInlineMediaType = getImageInlineMediaType(output);
101
397
  if (typeof output.image === 'string' && output.image.length > 0) {
102
398
  structured.image = output.image;
103
399
  }
104
400
  else if (isRecord(output.image)) {
105
401
  const imageObj = output.image;
106
- const inlineMediaType = getImageInlineMediaType(imageObj);
402
+ const inlineMediaType = getImageInlineMediaType(imageObj) ?? topLevelInlineMediaType;
107
403
  if (typeof imageObj.url === 'string' && imageObj.url.length > 0) {
108
404
  structured.image = imageObj.url;
109
405
  }
@@ -143,7 +439,7 @@ function convertLegacyToolOutputContent(output) {
143
439
  base64Data = (0, utils_1.encodeUint8ArrayToBase64)(dataValue);
144
440
  }
145
441
  if (base64Data) {
146
- structured.image = base64Data;
442
+ structured.image = formatInlineData(base64Data, topLevelInlineMediaType);
147
443
  }
148
444
  }
149
445
  if (output.providerData) {
@@ -388,9 +684,16 @@ function getImageInlineMediaType(source) {
388
684
  if (typeof source.mediaType === 'string' && source.mediaType.length > 0) {
389
685
  return source.mediaType;
390
686
  }
687
+ if (typeof source.mimeType === 'string' &&
688
+ source.mimeType.length > 0) {
689
+ return source.mimeType;
690
+ }
391
691
  return undefined;
392
692
  }
393
693
  function formatInlineData(data, mediaType) {
694
+ if (typeof data === 'string' && data.startsWith('data:')) {
695
+ return data;
696
+ }
394
697
  const base64 = typeof data === 'string' ? data : (0, utils_1.encodeUint8ArrayToBase64)(data);
395
698
  return mediaType ? `data:${mediaType};base64,${base64}` : base64;
396
699
  }
@@ -507,11 +810,70 @@ function toOpenAIShellEnvironment(environment) {
507
810
  }
508
811
  throw new agents_core_1.UserError(`Unsupported shell environment type: ${String(environment.type)}`);
509
812
  }
510
- function getTools(tools, handoffs) {
813
+ function getTools(tools, handoffs, options) {
511
814
  const openaiTools = [];
512
815
  const include = [];
816
+ const namespaceStateByName = new Map();
817
+ let hasDeferredSearchableTool = false;
818
+ let hasToolSearch = false;
819
+ const usePreviewComputerTool = shouldUsePreviewComputerTool({
820
+ model: options?.model,
821
+ toolChoice: options?.toolChoice,
822
+ });
513
823
  for (const tool of tools) {
514
- const { tool: openaiTool, include: openaiIncludes } = converTool(tool);
824
+ if (tool.type === 'function') {
825
+ const isDeferredFunction = tool.deferLoading === true;
826
+ hasDeferredSearchableTool ||= isDeferredFunction;
827
+ const namespaceName = typeof tool.namespace === 'string' ? tool.namespace.trim() : '';
828
+ if (namespaceName.length > 0) {
829
+ const namespaceDescription = typeof tool.namespaceDescription === 'string'
830
+ ? tool.namespaceDescription.trim()
831
+ : '';
832
+ if (namespaceDescription.length === 0) {
833
+ throw new agents_core_1.UserError(`All tools in namespace "${namespaceName}" must provide a non-empty description.`);
834
+ }
835
+ let namespaceState = namespaceStateByName.get(namespaceName);
836
+ if (!namespaceState) {
837
+ namespaceState = {
838
+ index: openaiTools.length,
839
+ description: namespaceDescription,
840
+ functionNames: new Set(),
841
+ tools: [],
842
+ };
843
+ namespaceStateByName.set(namespaceName, namespaceState);
844
+ openaiTools.push({});
845
+ }
846
+ else if (namespaceState.description !== namespaceDescription) {
847
+ throw new agents_core_1.UserError(`All tools in namespace "${namespaceName}" must share the same description.`);
848
+ }
849
+ const { tool: openaiTool, include: openaiIncludes } = converTool(tool, {
850
+ usePreviewComputerTool,
851
+ });
852
+ if (namespaceState.functionNames.has(tool.name)) {
853
+ throw new agents_core_1.UserError(`Namespace "${namespaceName}" cannot contain duplicate function tool name "${tool.name}".`);
854
+ }
855
+ namespaceState.functionNames.add(tool.name);
856
+ namespaceState.tools.push(openaiTool);
857
+ if (openaiIncludes && openaiIncludes.length > 0) {
858
+ for (const item of openaiIncludes) {
859
+ include.push(item);
860
+ }
861
+ }
862
+ continue;
863
+ }
864
+ }
865
+ if (tool.type === 'hosted_tool' &&
866
+ tool.providerData?.type === 'tool_search') {
867
+ hasToolSearch = true;
868
+ }
869
+ if (tool.type === 'hosted_tool' &&
870
+ tool.providerData?.type === 'mcp' &&
871
+ tool.providerData.defer_loading === true) {
872
+ hasDeferredSearchableTool = true;
873
+ }
874
+ const { tool: openaiTool, include: openaiIncludes } = converTool(tool, {
875
+ usePreviewComputerTool,
876
+ });
515
877
  openaiTools.push(openaiTool);
516
878
  if (openaiIncludes && openaiIncludes.length > 0) {
517
879
  for (const item of openaiIncludes) {
@@ -519,31 +881,57 @@ function getTools(tools, handoffs) {
519
881
  }
520
882
  }
521
883
  }
884
+ if (hasDeferredSearchableTool && !hasToolSearch) {
885
+ throw new agents_core_1.UserError('Deferred function tools and hosted MCP tools with deferLoading: true require toolSearchTool() in the same request.');
886
+ }
887
+ for (const [namespaceName, namespaceState,] of namespaceStateByName.entries()) {
888
+ openaiTools[namespaceState.index] = {
889
+ type: 'namespace',
890
+ name: namespaceName,
891
+ description: namespaceState.description,
892
+ tools: namespaceState.tools,
893
+ };
894
+ }
522
895
  return {
523
896
  tools: [...openaiTools, ...handoffs.map(getHandoffTool)],
524
897
  include,
525
898
  };
526
899
  }
527
- function converTool(tool) {
900
+ function converTool(tool, options) {
528
901
  if (tool.type === 'function') {
902
+ const openaiTool = {
903
+ type: 'function',
904
+ name: tool.name,
905
+ description: tool.description,
906
+ parameters: tool.parameters,
907
+ strict: tool.strict,
908
+ };
909
+ if (tool.deferLoading) {
910
+ openaiTool.defer_loading = true;
911
+ }
529
912
  return {
530
- tool: {
531
- type: 'function',
532
- name: tool.name,
533
- description: tool.description,
534
- parameters: tool.parameters,
535
- strict: tool.strict,
536
- },
913
+ tool: openaiTool,
537
914
  include: undefined,
538
915
  };
539
916
  }
540
917
  else if (tool.type === 'computer') {
918
+ if (options?.usePreviewComputerTool) {
919
+ if (!hasSerializedComputerDisplayMetadata(tool)) {
920
+ throw new agents_core_1.UserError('Preview computer tools require environment and dimensions. Provide them on your Computer implementation or target a GA computer model such as gpt-5.4.');
921
+ }
922
+ return {
923
+ tool: {
924
+ type: 'computer_use_preview',
925
+ environment: tool.environment,
926
+ display_width: tool.dimensions[0],
927
+ display_height: tool.dimensions[1],
928
+ },
929
+ include: undefined,
930
+ };
931
+ }
541
932
  return {
542
933
  tool: {
543
- type: 'computer_use_preview',
544
- environment: tool.environment,
545
- display_width: tool.dimensions[0],
546
- display_height: tool.dimensions[1],
934
+ type: 'computer',
547
935
  },
548
936
  include: undefined,
549
937
  };
@@ -614,6 +1002,17 @@ function converTool(tool) {
614
1002
  include: undefined,
615
1003
  };
616
1004
  }
1005
+ else if (tool.providerData?.type === 'tool_search') {
1006
+ return {
1007
+ tool: {
1008
+ type: 'tool_search',
1009
+ execution: tool.providerData.execution,
1010
+ description: tool.providerData.description,
1011
+ parameters: tool.providerData.parameters,
1012
+ },
1013
+ include: undefined,
1014
+ };
1015
+ }
617
1016
  else if (tool.providerData?.type === 'image_generation') {
618
1017
  return {
619
1018
  tool: {
@@ -633,17 +1032,22 @@ function converTool(tool) {
633
1032
  };
634
1033
  }
635
1034
  else if (tool.providerData?.type === 'mcp') {
1035
+ const openaiTool = {
1036
+ type: 'mcp',
1037
+ server_label: tool.providerData.server_label,
1038
+ server_url: tool.providerData.server_url,
1039
+ connector_id: tool.providerData.connector_id,
1040
+ authorization: tool.providerData.authorization,
1041
+ allowed_tools: tool.providerData.allowed_tools,
1042
+ headers: tool.providerData.headers,
1043
+ require_approval: convertMCPRequireApproval(tool.providerData.require_approval),
1044
+ server_description: tool.providerData.server_description,
1045
+ };
1046
+ if (tool.providerData.defer_loading === true) {
1047
+ openaiTool.defer_loading = true;
1048
+ }
636
1049
  return {
637
- tool: {
638
- type: 'mcp',
639
- server_label: tool.providerData.server_label,
640
- server_url: tool.providerData.server_url,
641
- connector_id: tool.providerData.connector_id,
642
- authorization: tool.providerData.authorization,
643
- allowed_tools: tool.providerData.allowed_tools,
644
- headers: tool.providerData.headers,
645
- require_approval: convertMCPRequireApproval(tool.providerData.require_approval),
646
- },
1050
+ tool: openaiTool,
647
1051
  include: undefined,
648
1052
  };
649
1053
  }
@@ -682,7 +1086,10 @@ function getInputMessageContent(entry) {
682
1086
  return {
683
1087
  type: 'input_text',
684
1088
  text: entry.text,
685
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(entry.providerData),
1089
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(entry.providerData, [
1090
+ 'type',
1091
+ 'text',
1092
+ ]),
686
1093
  };
687
1094
  }
688
1095
  else if (entry.type === 'input_image') {
@@ -704,7 +1111,12 @@ function getInputMessageContent(entry) {
704
1111
  }
705
1112
  return {
706
1113
  ...imageEntry,
707
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(entry.providerData),
1114
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(entry.providerData, [
1115
+ 'type',
1116
+ 'detail',
1117
+ 'image_url',
1118
+ 'file_id',
1119
+ ]),
708
1120
  };
709
1121
  }
710
1122
  else if (entry.type === 'input_file') {
@@ -749,25 +1161,54 @@ function getInputMessageContent(entry) {
749
1161
  }
750
1162
  return {
751
1163
  ...fileEntry,
752
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(entry.providerData),
1164
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(entry.providerData, [
1165
+ 'type',
1166
+ 'file_data',
1167
+ 'file_url',
1168
+ 'file_id',
1169
+ 'filename',
1170
+ ]),
753
1171
  };
754
1172
  }
755
1173
  throw new agents_core_1.UserError(`Unsupported input content type: ${JSON.stringify(entry)}`);
756
1174
  }
1175
+ function getProviderDataField(providerData, keys) {
1176
+ if (!providerData ||
1177
+ typeof providerData !== 'object' ||
1178
+ Array.isArray(providerData)) {
1179
+ return undefined;
1180
+ }
1181
+ const record = providerData;
1182
+ for (const key of keys) {
1183
+ if (typeof record[key] !== 'undefined') {
1184
+ return record[key];
1185
+ }
1186
+ }
1187
+ return undefined;
1188
+ }
757
1189
  function getOutputMessageContent(entry) {
758
1190
  if (entry.type === 'output_text') {
1191
+ const annotations = getProviderDataField(entry.providerData, ['annotations']);
1192
+ const normalizedAnnotations = Array.isArray(annotations) ? annotations : [];
759
1193
  return {
760
1194
  type: 'output_text',
761
1195
  text: entry.text,
762
- annotations: [],
763
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(entry.providerData),
1196
+ annotations: normalizedAnnotations,
1197
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(entry.providerData, [
1198
+ 'type',
1199
+ 'text',
1200
+ 'annotations',
1201
+ ]),
764
1202
  };
765
1203
  }
766
1204
  if (entry.type === 'refusal') {
767
1205
  return {
768
1206
  type: 'refusal',
769
1207
  refusal: entry.refusal,
770
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(entry.providerData),
1208
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(entry.providerData, [
1209
+ 'type',
1210
+ 'refusal',
1211
+ ]),
771
1212
  };
772
1213
  }
773
1214
  throw new agents_core_1.UserError(`Unsupported output content type: ${JSON.stringify(entry)}`);
@@ -778,7 +1219,11 @@ function getMessageItem(item) {
778
1219
  id: item.id,
779
1220
  role: 'system',
780
1221
  content: item.content,
781
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(item.providerData),
1222
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(item.providerData, [
1223
+ 'id',
1224
+ 'role',
1225
+ 'content',
1226
+ ]),
782
1227
  };
783
1228
  }
784
1229
  if (item.role === 'user') {
@@ -787,14 +1232,22 @@ function getMessageItem(item) {
787
1232
  id: item.id,
788
1233
  role: 'user',
789
1234
  content: item.content,
790
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(item.providerData),
1235
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(item.providerData, [
1236
+ 'id',
1237
+ 'role',
1238
+ 'content',
1239
+ ]),
791
1240
  };
792
1241
  }
793
1242
  return {
794
1243
  id: item.id,
795
1244
  role: 'user',
796
1245
  content: item.content.map(getInputMessageContent),
797
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(item.providerData),
1246
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(item.providerData, [
1247
+ 'id',
1248
+ 'role',
1249
+ 'content',
1250
+ ]),
798
1251
  };
799
1252
  }
800
1253
  if (item.role === 'assistant') {
@@ -804,7 +1257,13 @@ function getMessageItem(item) {
804
1257
  role: 'assistant',
805
1258
  content: item.content.map(getOutputMessageContent),
806
1259
  status: item.status,
807
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(item.providerData),
1260
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(item.providerData, [
1261
+ 'type',
1262
+ 'id',
1263
+ 'role',
1264
+ 'content',
1265
+ 'status',
1266
+ ]),
808
1267
  };
809
1268
  return assistantMessage;
810
1269
  }
@@ -851,6 +1310,52 @@ function getInputItems(input) {
851
1310
  if (isMessageItem(item)) {
852
1311
  return getMessageItem(item);
853
1312
  }
1313
+ if (item.type === 'tool_search_call') {
1314
+ const status = normalizeToolSearchStatus(item.status);
1315
+ const callId = (0, utils_1.getToolSearchProviderCallId)(item);
1316
+ const execution = (0, utils_1.getToolSearchExecution)(item);
1317
+ const toolSearchCall = {
1318
+ type: 'tool_search_call',
1319
+ id: item.id,
1320
+ ...(status !== null ? { status } : {}),
1321
+ arguments: item.arguments,
1322
+ ...(callId ? { call_id: callId } : {}),
1323
+ ...(execution ? { execution } : {}),
1324
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(item.providerData, [
1325
+ 'type',
1326
+ 'id',
1327
+ 'status',
1328
+ 'arguments',
1329
+ 'call_id',
1330
+ 'callId',
1331
+ 'execution',
1332
+ ]),
1333
+ };
1334
+ return toolSearchCall;
1335
+ }
1336
+ if (item.type === 'tool_search_output') {
1337
+ const status = normalizeToolSearchStatus(item.status);
1338
+ const callId = (0, utils_1.getToolSearchProviderCallId)(item);
1339
+ const execution = (0, utils_1.getToolSearchExecution)(item);
1340
+ const toolSearchOutput = {
1341
+ type: 'tool_search_output',
1342
+ id: item.id,
1343
+ ...(status !== null ? { status } : {}),
1344
+ tools: item.tools.map((tool) => toOpenAIToolSearchOutputToolPayload(tool)),
1345
+ ...(callId ? { call_id: callId } : {}),
1346
+ ...(execution ? { execution } : {}),
1347
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(item.providerData, [
1348
+ 'type',
1349
+ 'id',
1350
+ 'status',
1351
+ 'tools',
1352
+ 'call_id',
1353
+ 'callId',
1354
+ 'execution',
1355
+ ]),
1356
+ };
1357
+ return toolSearchOutput;
1358
+ }
854
1359
  if (item.type === 'function_call') {
855
1360
  const entry = {
856
1361
  id: item.id,
@@ -859,7 +1364,18 @@ function getInputItems(input) {
859
1364
  call_id: item.callId,
860
1365
  arguments: item.arguments,
861
1366
  status: item.status,
862
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(item.providerData),
1367
+ ...(typeof item.namespace === 'string'
1368
+ ? { namespace: item.namespace }
1369
+ : {}),
1370
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(item.providerData, [
1371
+ 'id',
1372
+ 'type',
1373
+ 'name',
1374
+ 'call_id',
1375
+ 'arguments',
1376
+ 'status',
1377
+ 'namespace',
1378
+ ]),
863
1379
  };
864
1380
  return entry;
865
1381
  }
@@ -871,45 +1387,98 @@ function getInputItems(input) {
871
1387
  call_id: item.callId,
872
1388
  output: normalizedOutput,
873
1389
  status: item.status,
874
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(item.providerData),
1390
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(item.providerData, [
1391
+ 'type',
1392
+ 'id',
1393
+ 'call_id',
1394
+ 'output',
1395
+ 'status',
1396
+ 'namespace',
1397
+ ]),
875
1398
  };
876
1399
  return entry;
877
1400
  }
878
1401
  if (item.type === 'reasoning') {
1402
+ const encryptedContent = getProviderDataField(item.providerData, [
1403
+ 'encryptedContent',
1404
+ 'encrypted_content',
1405
+ ]);
879
1406
  const entry = {
880
1407
  id: item.id,
881
1408
  type: 'reasoning',
882
1409
  summary: item.content.map((content) => ({
883
1410
  type: 'summary_text',
884
1411
  text: content.text,
885
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(content.providerData),
1412
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(content.providerData, ['type', 'text']),
886
1413
  })),
887
- encrypted_content: item.providerData?.encryptedContent,
888
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(item.providerData),
1414
+ ...(typeof encryptedContent === 'string'
1415
+ ? { encrypted_content: encryptedContent }
1416
+ : {}),
1417
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(item.providerData, [
1418
+ 'id',
1419
+ 'type',
1420
+ 'summary',
1421
+ 'encrypted_content',
1422
+ ]),
889
1423
  };
890
1424
  return entry;
891
1425
  }
892
1426
  if (item.type === 'computer_call') {
1427
+ const pendingSafetyChecks = getProviderDataField(item.providerData, ['pendingSafetyChecks', 'pending_safety_checks']);
1428
+ const normalizedPendingSafetyChecks = Array.isArray(pendingSafetyChecks) ? pendingSafetyChecks : [];
1429
+ const batchedActions = Array.isArray(item.actions)
1430
+ ? (item
1431
+ .actions ?? [])
1432
+ : [];
1433
+ const actionPayload = batchedActions.length > 0
1434
+ ? {
1435
+ action: item.action ?? batchedActions[0],
1436
+ actions: batchedActions,
1437
+ }
1438
+ : item.action
1439
+ ? { action: item.action }
1440
+ : {};
893
1441
  const entry = {
894
1442
  type: 'computer_call',
895
1443
  call_id: item.callId,
896
1444
  id: item.id,
897
- action: item.action,
898
1445
  status: item.status,
899
- pending_safety_checks: [],
900
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(item.providerData),
1446
+ pending_safety_checks: normalizedPendingSafetyChecks,
1447
+ ...actionPayload,
1448
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(item.providerData, [
1449
+ 'type',
1450
+ 'call_id',
1451
+ 'id',
1452
+ 'action',
1453
+ 'actions',
1454
+ 'status',
1455
+ 'pending_safety_checks',
1456
+ ]),
901
1457
  };
902
1458
  return entry;
903
1459
  }
904
1460
  if (item.type === 'computer_call_result') {
1461
+ const acknowledgedSafetyChecks = getProviderDataField(item.providerData, [
1462
+ 'acknowledgedSafetyChecks',
1463
+ 'acknowledged_safety_checks',
1464
+ ]);
905
1465
  const entry = {
906
1466
  type: 'computer_call_output',
907
1467
  id: item.id,
908
1468
  call_id: item.callId,
909
1469
  output: buildResponseOutput(item),
910
1470
  status: item.providerData?.status,
911
- acknowledged_safety_checks: item.providerData?.acknowledgedSafetyChecks,
912
- ...(0, providerData_1.camelOrSnakeToSnakeCase)(item.providerData),
1471
+ acknowledged_safety_checks: Array.isArray(acknowledgedSafetyChecks)
1472
+ ? acknowledgedSafetyChecks
1473
+ : [],
1474
+ ...(0, providerData_1.getSnakeCasedProviderDataWithoutReservedKeys)(item.providerData, [
1475
+ 'type',
1476
+ 'id',
1477
+ 'call_id',
1478
+ 'output',
1479
+ 'status',
1480
+ 'acknowledged_safety_checks',
1481
+ ]),
913
1482
  };
914
1483
  return entry;
915
1484
  }
@@ -1162,6 +1731,30 @@ function convertToOutputItem(items) {
1162
1731
  providerData,
1163
1732
  };
1164
1733
  }
1734
+ else if (item.type === 'tool_search_call') {
1735
+ const { id, type: _type, status, arguments: args, ...providerData } = item;
1736
+ const output = {
1737
+ type: 'tool_search_call',
1738
+ id,
1739
+ status,
1740
+ arguments: args,
1741
+ providerData,
1742
+ };
1743
+ return output;
1744
+ }
1745
+ else if (item.type === 'tool_search_output') {
1746
+ const { id, type: _type, status, tools, ...providerData } = item;
1747
+ const output = {
1748
+ type: 'tool_search_output',
1749
+ id,
1750
+ status,
1751
+ tools: Array.isArray(tools)
1752
+ ? tools.map((tool) => fromOpenAIToolSearchOutputToolPayload(tool))
1753
+ : [],
1754
+ providerData,
1755
+ };
1756
+ return output;
1757
+ }
1165
1758
  else if (item.type === 'file_search_call' ||
1166
1759
  item.type === 'web_search_call' ||
1167
1760
  item.type === 'image_generation_call' ||
@@ -1184,12 +1777,14 @@ function convertToOutputItem(items) {
1184
1777
  return output;
1185
1778
  }
1186
1779
  else if (item.type === 'function_call') {
1187
- const { call_id, name, status, arguments: args, ...providerData } = item;
1780
+ const functionCall = item;
1781
+ const { call_id, name, namespace, status, arguments: args, ...providerData } = functionCall;
1188
1782
  const output = {
1189
1783
  type: 'function_call',
1190
- id: item.id,
1784
+ id: functionCall.id,
1191
1785
  callId: call_id,
1192
1786
  name,
1787
+ ...(typeof namespace === 'string' ? { namespace } : {}),
1193
1788
  status,
1194
1789
  arguments: args,
1195
1790
  providerData,
@@ -1197,12 +1792,13 @@ function convertToOutputItem(items) {
1197
1792
  return output;
1198
1793
  }
1199
1794
  else if (item.type === 'function_call_output') {
1200
- const { call_id, status, output: rawOutput, name: toolName, function_name: functionName, ...providerData } = item;
1795
+ const { call_id, status, output: rawOutput, name: toolName, function_name: functionName, namespace, ...providerData } = item;
1201
1796
  const output = {
1202
1797
  type: 'function_call_result',
1203
1798
  id: item.id,
1204
1799
  callId: call_id,
1205
1800
  name: toolName ?? functionName ?? call_id,
1801
+ ...(typeof namespace === 'string' ? { namespace } : {}),
1206
1802
  status: status ?? 'completed',
1207
1803
  output: convertFunctionCallOutputToProtocol(rawOutput),
1208
1804
  providerData,
@@ -1210,13 +1806,18 @@ function convertToOutputItem(items) {
1210
1806
  return output;
1211
1807
  }
1212
1808
  else if (item.type === 'computer_call') {
1213
- const { call_id, status, action, ...providerData } = item;
1809
+ const { call_id, status, action, actions, ...providerData } = item;
1810
+ const normalizedActions = Array.isArray(actions) && actions.length > 0 ? actions : undefined;
1811
+ if (!normalizedActions && !action) {
1812
+ throw new agents_core_1.UserError(`Unsupported computer call item without an action or actions: ${JSON.stringify(item)}`);
1813
+ }
1214
1814
  const output = {
1215
1815
  type: 'computer_call',
1216
1816
  id: item.id,
1217
1817
  callId: call_id,
1218
1818
  status,
1219
- action,
1819
+ action: action ?? normalizedActions?.[0],
1820
+ ...(normalizedActions ? { actions: normalizedActions } : {}),
1220
1821
  providerData,
1221
1822
  };
1222
1823
  return output;
@@ -1460,15 +2061,22 @@ class OpenAIResponsesModel {
1460
2061
  this._client = client;
1461
2062
  this._model = model;
1462
2063
  }
2064
+ getRetryAdvice(args) {
2065
+ return (0, retryAdvice_1.getOpenAIRetryAdvice)(args);
2066
+ }
1463
2067
  async _fetchResponse(request, stream) {
1464
2068
  const builtRequest = this._buildResponsesCreateRequest(request, stream);
1465
- const responsePromise = this._client.responses.create(builtRequest.requestData, {
2069
+ const requestOptions = {
1466
2070
  headers: builtRequest.sdkRequestHeaders,
1467
2071
  signal: builtRequest.signal,
1468
2072
  ...(builtRequest.transportExtraQuery
1469
2073
  ? { query: builtRequest.transportExtraQuery }
1470
2074
  : {}),
1471
- });
2075
+ };
2076
+ if (request._internal?.runnerManagedRetry === true) {
2077
+ requestOptions.maxRetries = 0;
2078
+ }
2079
+ const responsePromise = this._client.responses.create(builtRequest.requestData, requestOptions);
1472
2080
  let response;
1473
2081
  if (stream) {
1474
2082
  const withResponse = responsePromise
@@ -1495,9 +2103,30 @@ class OpenAIResponsesModel {
1495
2103
  }
1496
2104
  _buildResponsesCreateRequest(request, stream) {
1497
2105
  const input = getInputItems(request.input);
1498
- const { tools, include } = getTools(request.tools, request.handoffs);
1499
- const toolChoice = getToolChoice(request.modelSettings.toolChoice);
2106
+ const prompt = getPrompt(request.prompt);
2107
+ // When a prompt template already declares a model, skip sending the agent's default model.
2108
+ // If the caller explicitly requests an override, include the resolved model name in the request.
2109
+ const shouldSendModel = !request.prompt || request.overridePromptModel === true;
2110
+ const effectiveRequestModel = shouldSendModel ? this._model : undefined;
1500
2111
  const { providerData: providerDataWithoutTransport, overrides: transportOverrides, } = (0, responsesTransportUtils_1.splitResponsesTransportOverrides)(request.modelSettings.providerData);
2112
+ const { tools, include } = getTools(request.tools, request.handoffs, {
2113
+ model: effectiveRequestModel,
2114
+ toolChoice: request.modelSettings.toolChoice,
2115
+ });
2116
+ const toolChoiceValidationTools = [
2117
+ ...tools,
2118
+ ...getExtraBodyToolsForToolChoiceValidation(transportOverrides.extraBody),
2119
+ ];
2120
+ const allowPromptSuppliedTools = Boolean(request.prompt) &&
2121
+ !(request.toolsExplicitlyProvided === true && tools.length === 0);
2122
+ const toolChoice = getToolChoice(request.modelSettings.toolChoice, {
2123
+ tools: toolChoiceValidationTools,
2124
+ model: effectiveRequestModel,
2125
+ allowPromptSuppliedComputerTool: allowPromptSuppliedTools,
2126
+ });
2127
+ assertSupportedToolChoice(toolChoice, toolChoiceValidationTools, {
2128
+ allowPromptSuppliedTools,
2129
+ });
1501
2130
  const { text, ...restOfProviderData } = providerDataWithoutTransport;
1502
2131
  if (request.modelSettings.reasoning) {
1503
2132
  // Merge top-level reasoning settings with provider data.
@@ -1512,25 +2141,19 @@ class OpenAIResponsesModel {
1512
2141
  mergedText = { ...request.modelSettings.text, ...text };
1513
2142
  }
1514
2143
  const responseFormat = getResponseFormat(request.outputType, mergedText);
1515
- const prompt = getPrompt(request.prompt);
1516
2144
  let parallelToolCalls = undefined;
1517
2145
  if (typeof request.modelSettings.parallelToolCalls === 'boolean') {
1518
- if (request.modelSettings.parallelToolCalls && tools.length === 0) {
1519
- throw new Error('Parallel tool calls are not supported without tools');
1520
- }
1521
2146
  parallelToolCalls = request.modelSettings.parallelToolCalls;
1522
2147
  }
1523
- // When a prompt template already declares a model, skip sending the agent's default model.
1524
- // If the caller explicitly requests an override, include the resolved model name in the request.
1525
- const shouldSendModel = !request.prompt || request.overridePromptModel === true;
1526
2148
  const shouldSendTools = tools.length > 0 ||
1527
2149
  request.toolsExplicitlyProvided === true ||
1528
2150
  !request.prompt;
1529
- const shouldOmitToolChoice = Boolean(request.prompt) &&
1530
- !shouldSendTools &&
1531
- typeof toolChoice === 'object';
2151
+ const compatibleToolChoice = getCompatibleToolChoice(toolChoice, toolChoiceValidationTools, {
2152
+ allowPromptSuppliedTools,
2153
+ });
2154
+ const shouldOmitToolChoice = typeof compatibleToolChoice === 'undefined';
1532
2155
  let requestData = {
1533
- ...(shouldSendModel ? { model: this._model } : {}),
2156
+ ...(effectiveRequestModel ? { model: effectiveRequestModel } : {}),
1534
2157
  instructions: normalizeInstructions(request.systemInstructions),
1535
2158
  input,
1536
2159
  include,
@@ -1547,7 +2170,7 @@ class OpenAIResponsesModel {
1547
2170
  truncation: request.modelSettings.truncation,
1548
2171
  max_output_tokens: request.modelSettings.maxTokens,
1549
2172
  ...(!shouldOmitToolChoice
1550
- ? { tool_choice: toolChoice }
2173
+ ? { tool_choice: compatibleToolChoice }
1551
2174
  : {}),
1552
2175
  parallel_tool_calls: parallelToolCalls,
1553
2176
  stream,
@@ -1739,6 +2362,23 @@ class OpenAIResponsesWSModel extends OpenAIResponsesModel {
1739
2362
  this.#websocketBaseURL = options.websocketBaseURL;
1740
2363
  this.#reuseConnection = options.reuseConnection ?? true;
1741
2364
  }
2365
+ getRetryAdvice(args) {
2366
+ if (isNeverSentWebSocketError(args.error)) {
2367
+ return {
2368
+ suggested: true,
2369
+ replaySafety: 'safe',
2370
+ reason: args.error instanceof Error ? args.error.message : undefined,
2371
+ };
2372
+ }
2373
+ if (isAmbiguousWebSocketReplayError(args.error)) {
2374
+ return {
2375
+ suggested: false,
2376
+ replaySafety: 'unsafe',
2377
+ reason: args.error instanceof Error ? args.error.message : undefined,
2378
+ };
2379
+ }
2380
+ return super.getRetryAdvice(args);
2381
+ }
1742
2382
  async _fetchResponse(request, stream) {
1743
2383
  // The websocket transport always uses streamed Responses events, then callers either
1744
2384
  // consume the stream directly or collapse it into the final terminal response.
@@ -1747,12 +2387,22 @@ class OpenAIResponsesWSModel extends OpenAIResponsesModel {
1747
2387
  return this.#iterWebSocketResponseEvents(builtRequest);
1748
2388
  }
1749
2389
  let finalResponse;
1750
- for await (const event of this.#iterWebSocketResponseEvents(builtRequest)) {
1751
- const eventType = event.type;
1752
- if (isTerminalResponsesStreamEventType(eventType)) {
1753
- finalResponse = event
1754
- .response;
2390
+ let receivedAnyEvent = false;
2391
+ try {
2392
+ for await (const event of this.#iterWebSocketResponseEvents(builtRequest)) {
2393
+ receivedAnyEvent = true;
2394
+ const eventType = event.type;
2395
+ if (isTerminalResponsesStreamEventType(eventType)) {
2396
+ finalResponse = event
2397
+ .response;
2398
+ }
2399
+ }
2400
+ }
2401
+ catch (error) {
2402
+ if (receivedAnyEvent && error instanceof Error) {
2403
+ error.unsafeToReplay = true;
1755
2404
  }
2405
+ throw error;
1756
2406
  }
1757
2407
  if (!finalResponse) {
1758
2408
  throw new Error('Responses websocket stream ended without a terminal response event.');