@openai/agents-extensions 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.
@@ -1,5 +1,5 @@
1
1
  import type { LanguageModelV2 as LanguageModelV2Base, LanguageModelV2CallOptions, LanguageModelV2FunctionTool, LanguageModelV2ToolChoice } from '@ai-sdk/provider';
2
- import { Model, ModelRequest, ResponseStreamEvent, Usage, ModelSettingsToolChoice } from '@openai/agents';
2
+ import { Model, ModelRetryAdvice, ModelRetryAdviceRequest, ModelRequest, ResponseStreamEvent, Usage, ModelSettingsToolChoice } from '@openai/agents';
3
3
  type LanguageModelV3Compatible = {
4
4
  specificationVersion: string;
5
5
  provider: string;
@@ -63,7 +63,7 @@ export type AiSdkModelOptions = {
63
63
  * If tracing is enabled, the model will send generation spans to your traces processor.
64
64
  *
65
65
  * ```ts
66
- * import { aisdk } from '@openai/agents-extensions';
66
+ * import { aisdk } from '@openai/agents-extensions/ai-sdk';
67
67
  * import { openai } from '@ai-sdk/openai';
68
68
  *
69
69
  * const model = aisdk(openai('gpt-4o'));
@@ -80,6 +80,7 @@ export type AiSdkModelOptions = {
80
80
  export declare class AiSdkModel implements Model {
81
81
  #private;
82
82
  constructor(model: LanguageModelCompatible, options?: AiSdkModelOptions);
83
+ getRetryAdvice(args: ModelRetryAdviceRequest): ModelRetryAdvice | undefined;
83
84
  getResponse(request: ModelRequest): Promise<{
84
85
  readonly responseId: any;
85
86
  readonly usage: Usage;
@@ -98,7 +99,7 @@ export declare class AiSdkModel implements Model {
98
99
  * If tracing is enabled, the model will send generation spans to your traces processor.
99
100
  *
100
101
  * ```ts
101
- * import { aisdk } from '@openai/agents-extensions';
102
+ * import { aisdk } from '@openai/agents-extensions/ai-sdk';
102
103
  * import { openai } from '@ai-sdk/openai';
103
104
  *
104
105
  * const model = aisdk(openai('gpt-4o'));
@@ -8,8 +8,14 @@ exports.aisdk = aisdk;
8
8
  exports.parseArguments = parseArguments;
9
9
  exports.toolChoiceToLanguageV2Format = toolChoiceToLanguageV2Format;
10
10
  const agents_1 = require("@openai/agents");
11
- const utils_1 = require("@openai/agents/utils");
11
+ const utils_1 = require("@openai/agents-core/utils");
12
12
  const utils_2 = require("@openai/agents/utils");
13
+ function hasComputerDisplayMetadata(tool) {
14
+ return (typeof tool.environment === 'string' &&
15
+ Array.isArray(tool.dimensions) &&
16
+ tool.dimensions.length === 2 &&
17
+ tool.dimensions.every((value) => typeof value === 'number'));
18
+ }
13
19
  function getSpecVersion(model) {
14
20
  const spec = model?.specificationVersion;
15
21
  if (!spec) {
@@ -62,8 +68,13 @@ function parseBase64ImageDataUrl(imageSource) {
62
68
  */
63
69
  function itemsToLanguageV2Messages(model, items, modelSettings) {
64
70
  const messages = [];
71
+ const toolCallNamesById = new Map();
72
+ const pendingToolSearchCallIds = [];
73
+ const pendingServerToolSearchCallIds = [];
74
+ let generatedToolSearchCallId = 0;
65
75
  let currentAssistantMessage;
66
76
  let pendingReasonerReasoning;
77
+ const collapsedItems = collapseReplacedToolSearchOutputs(items);
67
78
  const flushPendingReasonerReasoningToMessages = () => {
68
79
  if (!(shouldIncludeReasoningContent(model, modelSettings) &&
69
80
  pendingReasonerReasoning)) {
@@ -92,7 +103,7 @@ function itemsToLanguageV2Messages(model, items, modelSettings) {
92
103
  }
93
104
  pendingReasonerReasoning = undefined;
94
105
  };
95
- for (const item of items) {
106
+ for (const item of collapsedItems) {
96
107
  if (item.type === 'message' || typeof item.type === 'undefined') {
97
108
  const { role, content, providerData } = item;
98
109
  if (role === 'system') {
@@ -222,10 +233,12 @@ function itemsToLanguageV2Messages(model, items, modelSettings) {
222
233
  };
223
234
  pendingReasonerReasoning = undefined;
224
235
  }
236
+ const toolName = getAiSdkToolName(item);
237
+ toolCallNamesById.set(item.callId, toolName);
225
238
  const content = {
226
239
  type: 'tool-call',
227
240
  toolCallId: item.callId,
228
- toolName: item.name,
241
+ toolName,
229
242
  input: parseArguments(item.arguments),
230
243
  providerOptions: toProviderOptions(item.providerData, model),
231
244
  };
@@ -239,10 +252,11 @@ function itemsToLanguageV2Messages(model, items, modelSettings) {
239
252
  messages.push(currentAssistantMessage);
240
253
  currentAssistantMessage = undefined;
241
254
  }
255
+ const toolName = toolCallNamesById.get(item.callId) ?? getAiSdkToolName(item);
242
256
  const toolResult = {
243
257
  type: 'tool-result',
244
258
  toolCallId: item.callId,
245
- toolName: item.name,
259
+ toolName,
246
260
  output: convertToAiSdkOutput(item.output),
247
261
  providerOptions: toProviderOptions(item.providerData, model),
248
262
  };
@@ -253,6 +267,93 @@ function itemsToLanguageV2Messages(model, items, modelSettings) {
253
267
  });
254
268
  continue;
255
269
  }
270
+ else if (item.type === 'tool_search_call') {
271
+ if (!currentAssistantMessage) {
272
+ currentAssistantMessage = {
273
+ role: 'assistant',
274
+ content: [],
275
+ providerOptions: toProviderOptions(item.providerData, model),
276
+ };
277
+ }
278
+ if (Array.isArray(currentAssistantMessage.content) &&
279
+ currentAssistantMessage.role === 'assistant') {
280
+ if (shouldIncludeReasoningContent(model, modelSettings) &&
281
+ pendingReasonerReasoning) {
282
+ currentAssistantMessage.content.push({
283
+ type: 'reasoning',
284
+ text: pendingReasonerReasoning.text,
285
+ providerOptions: pendingReasonerReasoning.providerOptions,
286
+ });
287
+ currentAssistantMessage.providerOptions = {
288
+ ...pendingReasonerReasoning.providerOptions,
289
+ ...currentAssistantMessage.providerOptions,
290
+ };
291
+ pendingReasonerReasoning = undefined;
292
+ }
293
+ const toolCallId = (0, utils_1.resolveToolSearchCallId)(item, () => {
294
+ generatedToolSearchCallId += 1;
295
+ return `tool_search_${generatedToolSearchCallId}`;
296
+ });
297
+ if ((0, utils_1.shouldQueuePendingToolSearchCall)(item)) {
298
+ pendingToolSearchCallIds.push(toolCallId);
299
+ }
300
+ else {
301
+ pendingServerToolSearchCallIds.push(toolCallId);
302
+ }
303
+ toolCallNamesById.set(toolCallId, 'tool_search');
304
+ const content = {
305
+ type: 'tool-call',
306
+ toolCallId,
307
+ toolName: 'tool_search',
308
+ input: item.arguments,
309
+ providerOptions: toProviderOptions(item.providerData, model),
310
+ };
311
+ currentAssistantMessage.content.push(content);
312
+ }
313
+ continue;
314
+ }
315
+ else if (item.type === 'tool_search_output') {
316
+ flushPendingReasonerReasoningToMessages();
317
+ if (currentAssistantMessage) {
318
+ messages.push(currentAssistantMessage);
319
+ currentAssistantMessage = undefined;
320
+ }
321
+ const rawToolSearchExecution = item.execution ??
322
+ item.providerData?.execution;
323
+ const toolSearchExecution = rawToolSearchExecution === 'client' ||
324
+ rawToolSearchExecution === 'server'
325
+ ? rawToolSearchExecution
326
+ : undefined;
327
+ const toolCallId = toolSearchExecution === 'server'
328
+ ? takeQueuedToolSearchResultCallId(item, pendingServerToolSearchCallIds, () => {
329
+ generatedToolSearchCallId += 1;
330
+ return `tool_search_${generatedToolSearchCallId}`;
331
+ })
332
+ : (0, utils_1.takePendingToolSearchCallId)(item, pendingToolSearchCallIds, () => {
333
+ generatedToolSearchCallId += 1;
334
+ return `tool_search_${generatedToolSearchCallId}`;
335
+ });
336
+ const toolName = toolCallNamesById.get(toolCallId) ?? 'tool_search';
337
+ const toolResult = {
338
+ type: 'tool-result',
339
+ toolCallId,
340
+ toolName,
341
+ output: {
342
+ type: 'json',
343
+ value: {
344
+ ...(typeof item.status === 'string' ? { status: item.status } : {}),
345
+ tools: item.tools,
346
+ },
347
+ },
348
+ providerOptions: toProviderOptions(item.providerData, model),
349
+ };
350
+ messages.push({
351
+ role: 'tool',
352
+ content: [toolResult],
353
+ providerOptions: toProviderOptions(item.providerData, model),
354
+ });
355
+ continue;
356
+ }
256
357
  if (item.type === 'hosted_tool_call') {
257
358
  throw new agents_1.UserError('Hosted tool calls are not supported');
258
359
  }
@@ -434,6 +535,121 @@ function expectsObjectArguments(tool) {
434
535
  }
435
536
  return false;
436
537
  }
538
+ function buildRequestedToolsByName(request) {
539
+ const toolsByName = new Map();
540
+ const addRequestedTool = (name, tool) => {
541
+ const existing = toolsByName.get(name);
542
+ if (name === 'tool_search' &&
543
+ existing &&
544
+ isHostedToolSearchTool(existing) !== isHostedToolSearchTool(tool)) {
545
+ throw new agents_1.UserError('AiSdkModel cannot disambiguate a hosted tool_search helper from a custom tool or handoff that is also named "tool_search". Rename the custom tool or use a different adapter.');
546
+ }
547
+ toolsByName.set(name, tool);
548
+ };
549
+ for (const tool of request.tools) {
550
+ addRequestedTool(tool.type === 'function'
551
+ ? getSerializedFunctionToolName(tool)
552
+ : tool.name, tool);
553
+ }
554
+ for (const handoff of request.handoffs) {
555
+ addRequestedTool(handoff.toolName, handoff);
556
+ }
557
+ return toolsByName;
558
+ }
559
+ function isHostedToolSearchTool(tool) {
560
+ return (!!tool &&
561
+ !('toolName' in tool) &&
562
+ tool.type === 'hosted_tool' &&
563
+ tool.providerData?.type === 'tool_search');
564
+ }
565
+ function normalizeToolSearchArguments(value) {
566
+ if (typeof value !== 'string') {
567
+ return value ?? {};
568
+ }
569
+ const trimmed = value.trim();
570
+ if (trimmed.length === 0) {
571
+ return {};
572
+ }
573
+ try {
574
+ return JSON.parse(value);
575
+ }
576
+ catch {
577
+ return value;
578
+ }
579
+ }
580
+ function takeQueuedToolSearchResultCallId(value, pendingCallIds, generateFallbackId) {
581
+ const explicitCallId = (0, utils_1.getToolSearchProviderCallId)(value);
582
+ if (explicitCallId) {
583
+ const pendingIndex = pendingCallIds.indexOf(explicitCallId);
584
+ if (pendingIndex >= 0) {
585
+ pendingCallIds.splice(pendingIndex, 1);
586
+ }
587
+ return explicitCallId;
588
+ }
589
+ return (pendingCallIds.shift() ?? (0, utils_1.resolveToolSearchCallId)(value, generateFallbackId));
590
+ }
591
+ function getToolSearchOutputReplacementKey(item) {
592
+ const providerCallId = (0, utils_1.getToolSearchProviderCallId)(item);
593
+ if (providerCallId) {
594
+ return `call:${providerCallId}`;
595
+ }
596
+ if (typeof item.id === 'string' && item.id.length > 0) {
597
+ return `item:${item.id}`;
598
+ }
599
+ return undefined;
600
+ }
601
+ function collapseReplacedToolSearchOutputs(items) {
602
+ const latestIndexByReplacementKey = new Map();
603
+ items.forEach((item, index) => {
604
+ if (item.type !== 'tool_search_output') {
605
+ return;
606
+ }
607
+ const replacementKey = getToolSearchOutputReplacementKey(item);
608
+ if (replacementKey) {
609
+ latestIndexByReplacementKey.set(replacementKey, index);
610
+ }
611
+ });
612
+ return items.filter((item, index) => {
613
+ if (item.type !== 'tool_search_output') {
614
+ return true;
615
+ }
616
+ const replacementKey = getToolSearchOutputReplacementKey(item);
617
+ if (!replacementKey) {
618
+ return true;
619
+ }
620
+ return latestIndexByReplacementKey.get(replacementKey) === index;
621
+ });
622
+ }
623
+ function createProtocolToolCallItem(args) {
624
+ const { requestedTool, toolCallId, toolName, input, providerData } = args;
625
+ if (isHostedToolSearchTool(requestedTool)) {
626
+ return {
627
+ type: 'tool_search_call',
628
+ id: toolCallId,
629
+ arguments: normalizeToolSearchArguments(input),
630
+ status: 'completed',
631
+ providerData,
632
+ };
633
+ }
634
+ let toolCallArguments;
635
+ if (typeof input === 'string') {
636
+ toolCallArguments =
637
+ input === '' && expectsObjectArguments(requestedTool)
638
+ ? JSON.stringify({})
639
+ : input;
640
+ }
641
+ else {
642
+ toolCallArguments = JSON.stringify(input ?? {});
643
+ }
644
+ return {
645
+ type: 'function_call',
646
+ callId: toolCallId,
647
+ name: toolName,
648
+ arguments: toolCallArguments,
649
+ status: 'completed',
650
+ providerData,
651
+ };
652
+ }
437
653
  /**
438
654
  * Maps the protocol-level structured outputs into the Language Model V2 result primitives.
439
655
  * The AI SDK expects either plain text or content parts (text + media), so we merge multiple
@@ -504,6 +720,12 @@ function convertStructuredOutputsToAiSdkOutput(outputs) {
504
720
  function isRecord(value) {
505
721
  return typeof value === 'object' && value !== null;
506
722
  }
723
+ function getAiSdkToolName(tool) {
724
+ return (0, utils_1.toolQualifiedName)(tool.name, tool.namespace) ?? tool.name;
725
+ }
726
+ function getSerializedFunctionToolName(tool) {
727
+ return getAiSdkToolName(tool);
728
+ }
507
729
  function getModelIdentifier(model) {
508
730
  return `${model.provider}:${model.modelId}`;
509
731
  }
@@ -630,6 +852,16 @@ function formatInlineData(data, mediaType) {
630
852
  const base64 = typeof data === 'string' ? data : (0, utils_2.encodeUint8ArrayToBase64)(data);
631
853
  return mediaType ? `data:${mediaType};base64,${base64}` : base64;
632
854
  }
855
+ function getHostedToolArgs(providerData) {
856
+ if (!isRecord(providerData)) {
857
+ return {};
858
+ }
859
+ if (isRecord(providerData.args)) {
860
+ return providerData.args;
861
+ }
862
+ const { type: _type, name: _name, args: _args, ...rest } = providerData;
863
+ return rest;
864
+ }
633
865
  /**
634
866
  * @internal
635
867
  * Converts a tool to a language model V2 tool.
@@ -639,9 +871,12 @@ function formatInlineData(data, mediaType) {
639
871
  */
640
872
  function toolToLanguageV2Tool(model, tool) {
641
873
  if (tool.type === 'function') {
874
+ if (tool.deferLoading) {
875
+ throw new agents_1.UserError('The AI SDK adapter does not support deferred Responses function tools (`toolNamespace()` or `deferLoading: true`). Use a Responses API model directly.');
876
+ }
642
877
  return {
643
878
  type: 'function',
644
- name: tool.name,
879
+ name: getSerializedFunctionToolName(tool),
645
880
  description: tool.description,
646
881
  inputSchema: tool.parameters,
647
882
  };
@@ -653,10 +888,13 @@ function toolToLanguageV2Tool(model, tool) {
653
888
  type: providerToolType,
654
889
  id: `${providerToolPrefix}.${tool.name}`,
655
890
  name: tool.name,
656
- args: tool.providerData?.args ?? {},
891
+ args: getHostedToolArgs(tool.providerData),
657
892
  };
658
893
  }
659
894
  if (tool.type === 'computer') {
895
+ if (!hasComputerDisplayMetadata(tool)) {
896
+ throw new agents_1.UserError('The AI SDK adapter requires computer tools to include environment and dimensions metadata.');
897
+ }
660
898
  return {
661
899
  type: providerToolType,
662
900
  id: `${providerToolPrefix}.${tool.name}`,
@@ -709,7 +947,7 @@ function getResponseFormat(outputType) {
709
947
  * If tracing is enabled, the model will send generation spans to your traces processor.
710
948
  *
711
949
  * ```ts
712
- * import { aisdk } from '@openai/agents-extensions';
950
+ * import { aisdk } from '@openai/agents-extensions/ai-sdk';
713
951
  * import { openai } from '@ai-sdk/openai';
714
952
  *
715
953
  * const model = aisdk(openai('gpt-4o'));
@@ -732,6 +970,25 @@ class AiSdkModel {
732
970
  this.#model = model;
733
971
  this.#options = options;
734
972
  }
973
+ getRetryAdvice(args) {
974
+ const error = args.error;
975
+ const isRetryable = typeof error?.isRetryable === 'boolean'
976
+ ? error.isRetryable
977
+ : undefined;
978
+ if (isRetryable === false) {
979
+ return {
980
+ suggested: false,
981
+ reason: error instanceof Error ? error.message : undefined,
982
+ };
983
+ }
984
+ if (isRetryable === true) {
985
+ return {
986
+ suggested: true,
987
+ reason: error instanceof Error ? error.message : undefined,
988
+ };
989
+ }
990
+ return undefined;
991
+ }
735
992
  async #transformOutputText(text, request, stream) {
736
993
  const transform = this.#options.transformOutputText;
737
994
  if (!transform) {
@@ -781,9 +1038,10 @@ class AiSdkModel {
781
1038
  if (span && request.tracing === true) {
782
1039
  span.spanData.input = input;
783
1040
  }
784
- if ((0, utils_1.isZodObject)(request.outputType)) {
1041
+ if ((0, utils_2.isZodObject)(request.outputType)) {
785
1042
  throw new agents_1.UserError('Zod output type is not yet supported');
786
1043
  }
1044
+ const requestedToolsByName = buildRequestedToolsByName(request);
787
1045
  const responseFormat = getResponseFormat(request.outputType);
788
1046
  const aiSdkRequest = {
789
1047
  ...(tools.length ? { tools } : {}),
@@ -823,36 +1081,21 @@ class AiSdkModel {
823
1081
  }
824
1082
  const toolCalls = resultContent.filter((c) => c && c.type === 'tool-call');
825
1083
  const hasToolCalls = toolCalls.length > 0;
826
- const toolsNameToToolMap = new Map(request.tools.map((tool) => [tool.name, tool]));
827
- for (const handoff of request.handoffs) {
828
- toolsNameToToolMap.set(handoff.toolName, handoff);
829
- }
830
1084
  for (const toolCall of toolCalls) {
831
1085
  const requestedTool = typeof toolCall.toolName === 'string'
832
- ? toolsNameToToolMap.get(toolCall.toolName)
1086
+ ? requestedToolsByName.get(toolCall.toolName)
833
1087
  : undefined;
834
1088
  if (!requestedTool && toolCall.toolName) {
835
1089
  this.#logger.warn(`Received tool call for unknown tool '${toolCall.toolName}'.`);
836
1090
  }
837
- let toolCallArguments;
838
- if (typeof toolCall.input === 'string') {
839
- toolCallArguments =
840
- toolCall.input === '' && expectsObjectArguments(requestedTool)
841
- ? JSON.stringify({})
842
- : toolCall.input;
843
- }
844
- else {
845
- toolCallArguments = JSON.stringify(toolCall.input ?? {});
846
- }
847
- output.push({
848
- type: 'function_call',
849
- callId: toolCall.toolCallId,
850
- name: toolCall.toolName,
851
- arguments: toolCallArguments,
852
- status: 'completed',
1091
+ output.push(createProtocolToolCallItem({
1092
+ requestedTool,
1093
+ toolCallId: toolCall.toolCallId,
1094
+ toolName: toolCall.toolName,
1095
+ input: toolCall.input,
853
1096
  providerData: mergeProviderData(baseProviderData, toolCall.providerMetadata ??
854
1097
  (hasToolCalls ? result.providerMetadata : undefined)),
855
- });
1098
+ }));
856
1099
  }
857
1100
  // Some of other platforms may return both tool calls and text.
858
1101
  // Putting a text message here will let the agent loop to complete,
@@ -990,6 +1233,7 @@ class AiSdkModel {
990
1233
  abortSignal: request.signal,
991
1234
  ...(request.modelSettings.providerData ?? {}),
992
1235
  };
1236
+ const requestedToolsByName = buildRequestedToolsByName(request);
993
1237
  if (this.#logger.dontLogModelData) {
994
1238
  this.#logger.debug('Request received (streamed)');
995
1239
  }
@@ -1056,14 +1300,16 @@ class AiSdkModel {
1056
1300
  case 'tool-call': {
1057
1301
  const toolCallId = part.toolCallId;
1058
1302
  if (toolCallId) {
1059
- functionCalls[toolCallId] = {
1060
- type: 'function_call',
1061
- callId: toolCallId,
1062
- name: part.toolName,
1063
- arguments: part.input ?? '',
1064
- status: 'completed',
1303
+ const requestedTool = typeof part.toolName === 'string'
1304
+ ? requestedToolsByName.get(part.toolName)
1305
+ : undefined;
1306
+ functionCalls[toolCallId] = createProtocolToolCallItem({
1307
+ requestedTool,
1308
+ toolCallId,
1309
+ toolName: part.toolName,
1310
+ input: part.input,
1065
1311
  providerData: mergeProviderData(baseProviderData, part.providerMetadata),
1066
- };
1312
+ });
1067
1313
  }
1068
1314
  break;
1069
1315
  }
@@ -1214,7 +1460,7 @@ exports.AiSdkModel = AiSdkModel;
1214
1460
  * If tracing is enabled, the model will send generation spans to your traces processor.
1215
1461
  *
1216
1462
  * ```ts
1217
- * import { aisdk } from '@openai/agents-extensions';
1463
+ * import { aisdk } from '@openai/agents-extensions/ai-sdk';
1218
1464
  * import { openai } from '@ai-sdk/openai';
1219
1465
  *
1220
1466
  * const model = aisdk(openai('gpt-4o'));