@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,6 +1,12 @@
1
1
  import { createGenerationSpan, resetCurrentSpan, setCurrentSpan, Usage, UserError, withGenerationSpan, getLogger, } from '@openai/agents';
2
- import { isZodObject } from '@openai/agents/utils';
3
- import { encodeUint8ArrayToBase64 } from '@openai/agents/utils';
2
+ import { getToolSearchProviderCallId, resolveToolSearchCallId, shouldQueuePendingToolSearchCall, takePendingToolSearchCallId, toolQualifiedName, } from '@openai/agents-core/utils';
3
+ import { isZodObject, encodeUint8ArrayToBase64 } from '@openai/agents/utils';
4
+ function hasComputerDisplayMetadata(tool) {
5
+ return (typeof tool.environment === 'string' &&
6
+ Array.isArray(tool.dimensions) &&
7
+ tool.dimensions.length === 2 &&
8
+ tool.dimensions.every((value) => typeof value === 'number'));
9
+ }
4
10
  function getSpecVersion(model) {
5
11
  const spec = model?.specificationVersion;
6
12
  if (!spec) {
@@ -53,8 +59,13 @@ function parseBase64ImageDataUrl(imageSource) {
53
59
  */
54
60
  export function itemsToLanguageV2Messages(model, items, modelSettings) {
55
61
  const messages = [];
62
+ const toolCallNamesById = new Map();
63
+ const pendingToolSearchCallIds = [];
64
+ const pendingServerToolSearchCallIds = [];
65
+ let generatedToolSearchCallId = 0;
56
66
  let currentAssistantMessage;
57
67
  let pendingReasonerReasoning;
68
+ const collapsedItems = collapseReplacedToolSearchOutputs(items);
58
69
  const flushPendingReasonerReasoningToMessages = () => {
59
70
  if (!(shouldIncludeReasoningContent(model, modelSettings) &&
60
71
  pendingReasonerReasoning)) {
@@ -83,7 +94,7 @@ export function itemsToLanguageV2Messages(model, items, modelSettings) {
83
94
  }
84
95
  pendingReasonerReasoning = undefined;
85
96
  };
86
- for (const item of items) {
97
+ for (const item of collapsedItems) {
87
98
  if (item.type === 'message' || typeof item.type === 'undefined') {
88
99
  const { role, content, providerData } = item;
89
100
  if (role === 'system') {
@@ -213,10 +224,12 @@ export function itemsToLanguageV2Messages(model, items, modelSettings) {
213
224
  };
214
225
  pendingReasonerReasoning = undefined;
215
226
  }
227
+ const toolName = getAiSdkToolName(item);
228
+ toolCallNamesById.set(item.callId, toolName);
216
229
  const content = {
217
230
  type: 'tool-call',
218
231
  toolCallId: item.callId,
219
- toolName: item.name,
232
+ toolName,
220
233
  input: parseArguments(item.arguments),
221
234
  providerOptions: toProviderOptions(item.providerData, model),
222
235
  };
@@ -230,10 +243,11 @@ export function itemsToLanguageV2Messages(model, items, modelSettings) {
230
243
  messages.push(currentAssistantMessage);
231
244
  currentAssistantMessage = undefined;
232
245
  }
246
+ const toolName = toolCallNamesById.get(item.callId) ?? getAiSdkToolName(item);
233
247
  const toolResult = {
234
248
  type: 'tool-result',
235
249
  toolCallId: item.callId,
236
- toolName: item.name,
250
+ toolName,
237
251
  output: convertToAiSdkOutput(item.output),
238
252
  providerOptions: toProviderOptions(item.providerData, model),
239
253
  };
@@ -244,6 +258,93 @@ export function itemsToLanguageV2Messages(model, items, modelSettings) {
244
258
  });
245
259
  continue;
246
260
  }
261
+ else if (item.type === 'tool_search_call') {
262
+ if (!currentAssistantMessage) {
263
+ currentAssistantMessage = {
264
+ role: 'assistant',
265
+ content: [],
266
+ providerOptions: toProviderOptions(item.providerData, model),
267
+ };
268
+ }
269
+ if (Array.isArray(currentAssistantMessage.content) &&
270
+ currentAssistantMessage.role === 'assistant') {
271
+ if (shouldIncludeReasoningContent(model, modelSettings) &&
272
+ pendingReasonerReasoning) {
273
+ currentAssistantMessage.content.push({
274
+ type: 'reasoning',
275
+ text: pendingReasonerReasoning.text,
276
+ providerOptions: pendingReasonerReasoning.providerOptions,
277
+ });
278
+ currentAssistantMessage.providerOptions = {
279
+ ...pendingReasonerReasoning.providerOptions,
280
+ ...currentAssistantMessage.providerOptions,
281
+ };
282
+ pendingReasonerReasoning = undefined;
283
+ }
284
+ const toolCallId = resolveToolSearchCallId(item, () => {
285
+ generatedToolSearchCallId += 1;
286
+ return `tool_search_${generatedToolSearchCallId}`;
287
+ });
288
+ if (shouldQueuePendingToolSearchCall(item)) {
289
+ pendingToolSearchCallIds.push(toolCallId);
290
+ }
291
+ else {
292
+ pendingServerToolSearchCallIds.push(toolCallId);
293
+ }
294
+ toolCallNamesById.set(toolCallId, 'tool_search');
295
+ const content = {
296
+ type: 'tool-call',
297
+ toolCallId,
298
+ toolName: 'tool_search',
299
+ input: item.arguments,
300
+ providerOptions: toProviderOptions(item.providerData, model),
301
+ };
302
+ currentAssistantMessage.content.push(content);
303
+ }
304
+ continue;
305
+ }
306
+ else if (item.type === 'tool_search_output') {
307
+ flushPendingReasonerReasoningToMessages();
308
+ if (currentAssistantMessage) {
309
+ messages.push(currentAssistantMessage);
310
+ currentAssistantMessage = undefined;
311
+ }
312
+ const rawToolSearchExecution = item.execution ??
313
+ item.providerData?.execution;
314
+ const toolSearchExecution = rawToolSearchExecution === 'client' ||
315
+ rawToolSearchExecution === 'server'
316
+ ? rawToolSearchExecution
317
+ : undefined;
318
+ const toolCallId = toolSearchExecution === 'server'
319
+ ? takeQueuedToolSearchResultCallId(item, pendingServerToolSearchCallIds, () => {
320
+ generatedToolSearchCallId += 1;
321
+ return `tool_search_${generatedToolSearchCallId}`;
322
+ })
323
+ : takePendingToolSearchCallId(item, pendingToolSearchCallIds, () => {
324
+ generatedToolSearchCallId += 1;
325
+ return `tool_search_${generatedToolSearchCallId}`;
326
+ });
327
+ const toolName = toolCallNamesById.get(toolCallId) ?? 'tool_search';
328
+ const toolResult = {
329
+ type: 'tool-result',
330
+ toolCallId,
331
+ toolName,
332
+ output: {
333
+ type: 'json',
334
+ value: {
335
+ ...(typeof item.status === 'string' ? { status: item.status } : {}),
336
+ tools: item.tools,
337
+ },
338
+ },
339
+ providerOptions: toProviderOptions(item.providerData, model),
340
+ };
341
+ messages.push({
342
+ role: 'tool',
343
+ content: [toolResult],
344
+ providerOptions: toProviderOptions(item.providerData, model),
345
+ });
346
+ continue;
347
+ }
247
348
  if (item.type === 'hosted_tool_call') {
248
349
  throw new UserError('Hosted tool calls are not supported');
249
350
  }
@@ -425,6 +526,121 @@ function expectsObjectArguments(tool) {
425
526
  }
426
527
  return false;
427
528
  }
529
+ function buildRequestedToolsByName(request) {
530
+ const toolsByName = new Map();
531
+ const addRequestedTool = (name, tool) => {
532
+ const existing = toolsByName.get(name);
533
+ if (name === 'tool_search' &&
534
+ existing &&
535
+ isHostedToolSearchTool(existing) !== isHostedToolSearchTool(tool)) {
536
+ throw new 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.');
537
+ }
538
+ toolsByName.set(name, tool);
539
+ };
540
+ for (const tool of request.tools) {
541
+ addRequestedTool(tool.type === 'function'
542
+ ? getSerializedFunctionToolName(tool)
543
+ : tool.name, tool);
544
+ }
545
+ for (const handoff of request.handoffs) {
546
+ addRequestedTool(handoff.toolName, handoff);
547
+ }
548
+ return toolsByName;
549
+ }
550
+ function isHostedToolSearchTool(tool) {
551
+ return (!!tool &&
552
+ !('toolName' in tool) &&
553
+ tool.type === 'hosted_tool' &&
554
+ tool.providerData?.type === 'tool_search');
555
+ }
556
+ function normalizeToolSearchArguments(value) {
557
+ if (typeof value !== 'string') {
558
+ return value ?? {};
559
+ }
560
+ const trimmed = value.trim();
561
+ if (trimmed.length === 0) {
562
+ return {};
563
+ }
564
+ try {
565
+ return JSON.parse(value);
566
+ }
567
+ catch {
568
+ return value;
569
+ }
570
+ }
571
+ function takeQueuedToolSearchResultCallId(value, pendingCallIds, generateFallbackId) {
572
+ const explicitCallId = getToolSearchProviderCallId(value);
573
+ if (explicitCallId) {
574
+ const pendingIndex = pendingCallIds.indexOf(explicitCallId);
575
+ if (pendingIndex >= 0) {
576
+ pendingCallIds.splice(pendingIndex, 1);
577
+ }
578
+ return explicitCallId;
579
+ }
580
+ return (pendingCallIds.shift() ?? resolveToolSearchCallId(value, generateFallbackId));
581
+ }
582
+ function getToolSearchOutputReplacementKey(item) {
583
+ const providerCallId = getToolSearchProviderCallId(item);
584
+ if (providerCallId) {
585
+ return `call:${providerCallId}`;
586
+ }
587
+ if (typeof item.id === 'string' && item.id.length > 0) {
588
+ return `item:${item.id}`;
589
+ }
590
+ return undefined;
591
+ }
592
+ function collapseReplacedToolSearchOutputs(items) {
593
+ const latestIndexByReplacementKey = new Map();
594
+ items.forEach((item, index) => {
595
+ if (item.type !== 'tool_search_output') {
596
+ return;
597
+ }
598
+ const replacementKey = getToolSearchOutputReplacementKey(item);
599
+ if (replacementKey) {
600
+ latestIndexByReplacementKey.set(replacementKey, index);
601
+ }
602
+ });
603
+ return items.filter((item, index) => {
604
+ if (item.type !== 'tool_search_output') {
605
+ return true;
606
+ }
607
+ const replacementKey = getToolSearchOutputReplacementKey(item);
608
+ if (!replacementKey) {
609
+ return true;
610
+ }
611
+ return latestIndexByReplacementKey.get(replacementKey) === index;
612
+ });
613
+ }
614
+ function createProtocolToolCallItem(args) {
615
+ const { requestedTool, toolCallId, toolName, input, providerData } = args;
616
+ if (isHostedToolSearchTool(requestedTool)) {
617
+ return {
618
+ type: 'tool_search_call',
619
+ id: toolCallId,
620
+ arguments: normalizeToolSearchArguments(input),
621
+ status: 'completed',
622
+ providerData,
623
+ };
624
+ }
625
+ let toolCallArguments;
626
+ if (typeof input === 'string') {
627
+ toolCallArguments =
628
+ input === '' && expectsObjectArguments(requestedTool)
629
+ ? JSON.stringify({})
630
+ : input;
631
+ }
632
+ else {
633
+ toolCallArguments = JSON.stringify(input ?? {});
634
+ }
635
+ return {
636
+ type: 'function_call',
637
+ callId: toolCallId,
638
+ name: toolName,
639
+ arguments: toolCallArguments,
640
+ status: 'completed',
641
+ providerData,
642
+ };
643
+ }
428
644
  /**
429
645
  * Maps the protocol-level structured outputs into the Language Model V2 result primitives.
430
646
  * The AI SDK expects either plain text or content parts (text + media), so we merge multiple
@@ -495,6 +711,12 @@ function convertStructuredOutputsToAiSdkOutput(outputs) {
495
711
  function isRecord(value) {
496
712
  return typeof value === 'object' && value !== null;
497
713
  }
714
+ function getAiSdkToolName(tool) {
715
+ return toolQualifiedName(tool.name, tool.namespace) ?? tool.name;
716
+ }
717
+ function getSerializedFunctionToolName(tool) {
718
+ return getAiSdkToolName(tool);
719
+ }
498
720
  function getModelIdentifier(model) {
499
721
  return `${model.provider}:${model.modelId}`;
500
722
  }
@@ -621,6 +843,16 @@ function formatInlineData(data, mediaType) {
621
843
  const base64 = typeof data === 'string' ? data : encodeUint8ArrayToBase64(data);
622
844
  return mediaType ? `data:${mediaType};base64,${base64}` : base64;
623
845
  }
846
+ function getHostedToolArgs(providerData) {
847
+ if (!isRecord(providerData)) {
848
+ return {};
849
+ }
850
+ if (isRecord(providerData.args)) {
851
+ return providerData.args;
852
+ }
853
+ const { type: _type, name: _name, args: _args, ...rest } = providerData;
854
+ return rest;
855
+ }
624
856
  /**
625
857
  * @internal
626
858
  * Converts a tool to a language model V2 tool.
@@ -630,9 +862,12 @@ function formatInlineData(data, mediaType) {
630
862
  */
631
863
  export function toolToLanguageV2Tool(model, tool) {
632
864
  if (tool.type === 'function') {
865
+ if (tool.deferLoading) {
866
+ throw new UserError('The AI SDK adapter does not support deferred Responses function tools (`toolNamespace()` or `deferLoading: true`). Use a Responses API model directly.');
867
+ }
633
868
  return {
634
869
  type: 'function',
635
- name: tool.name,
870
+ name: getSerializedFunctionToolName(tool),
636
871
  description: tool.description,
637
872
  inputSchema: tool.parameters,
638
873
  };
@@ -644,10 +879,13 @@ export function toolToLanguageV2Tool(model, tool) {
644
879
  type: providerToolType,
645
880
  id: `${providerToolPrefix}.${tool.name}`,
646
881
  name: tool.name,
647
- args: tool.providerData?.args ?? {},
882
+ args: getHostedToolArgs(tool.providerData),
648
883
  };
649
884
  }
650
885
  if (tool.type === 'computer') {
886
+ if (!hasComputerDisplayMetadata(tool)) {
887
+ throw new UserError('The AI SDK adapter requires computer tools to include environment and dimensions metadata.');
888
+ }
651
889
  return {
652
890
  type: providerToolType,
653
891
  id: `${providerToolPrefix}.${tool.name}`,
@@ -700,7 +938,7 @@ export function getResponseFormat(outputType) {
700
938
  * If tracing is enabled, the model will send generation spans to your traces processor.
701
939
  *
702
940
  * ```ts
703
- * import { aisdk } from '@openai/agents-extensions';
941
+ * import { aisdk } from '@openai/agents-extensions/ai-sdk';
704
942
  * import { openai } from '@ai-sdk/openai';
705
943
  *
706
944
  * const model = aisdk(openai('gpt-4o'));
@@ -723,6 +961,25 @@ export class AiSdkModel {
723
961
  this.#model = model;
724
962
  this.#options = options;
725
963
  }
964
+ getRetryAdvice(args) {
965
+ const error = args.error;
966
+ const isRetryable = typeof error?.isRetryable === 'boolean'
967
+ ? error.isRetryable
968
+ : undefined;
969
+ if (isRetryable === false) {
970
+ return {
971
+ suggested: false,
972
+ reason: error instanceof Error ? error.message : undefined,
973
+ };
974
+ }
975
+ if (isRetryable === true) {
976
+ return {
977
+ suggested: true,
978
+ reason: error instanceof Error ? error.message : undefined,
979
+ };
980
+ }
981
+ return undefined;
982
+ }
726
983
  async #transformOutputText(text, request, stream) {
727
984
  const transform = this.#options.transformOutputText;
728
985
  if (!transform) {
@@ -775,6 +1032,7 @@ export class AiSdkModel {
775
1032
  if (isZodObject(request.outputType)) {
776
1033
  throw new UserError('Zod output type is not yet supported');
777
1034
  }
1035
+ const requestedToolsByName = buildRequestedToolsByName(request);
778
1036
  const responseFormat = getResponseFormat(request.outputType);
779
1037
  const aiSdkRequest = {
780
1038
  ...(tools.length ? { tools } : {}),
@@ -814,36 +1072,21 @@ export class AiSdkModel {
814
1072
  }
815
1073
  const toolCalls = resultContent.filter((c) => c && c.type === 'tool-call');
816
1074
  const hasToolCalls = toolCalls.length > 0;
817
- const toolsNameToToolMap = new Map(request.tools.map((tool) => [tool.name, tool]));
818
- for (const handoff of request.handoffs) {
819
- toolsNameToToolMap.set(handoff.toolName, handoff);
820
- }
821
1075
  for (const toolCall of toolCalls) {
822
1076
  const requestedTool = typeof toolCall.toolName === 'string'
823
- ? toolsNameToToolMap.get(toolCall.toolName)
1077
+ ? requestedToolsByName.get(toolCall.toolName)
824
1078
  : undefined;
825
1079
  if (!requestedTool && toolCall.toolName) {
826
1080
  this.#logger.warn(`Received tool call for unknown tool '${toolCall.toolName}'.`);
827
1081
  }
828
- let toolCallArguments;
829
- if (typeof toolCall.input === 'string') {
830
- toolCallArguments =
831
- toolCall.input === '' && expectsObjectArguments(requestedTool)
832
- ? JSON.stringify({})
833
- : toolCall.input;
834
- }
835
- else {
836
- toolCallArguments = JSON.stringify(toolCall.input ?? {});
837
- }
838
- output.push({
839
- type: 'function_call',
840
- callId: toolCall.toolCallId,
841
- name: toolCall.toolName,
842
- arguments: toolCallArguments,
843
- status: 'completed',
1082
+ output.push(createProtocolToolCallItem({
1083
+ requestedTool,
1084
+ toolCallId: toolCall.toolCallId,
1085
+ toolName: toolCall.toolName,
1086
+ input: toolCall.input,
844
1087
  providerData: mergeProviderData(baseProviderData, toolCall.providerMetadata ??
845
1088
  (hasToolCalls ? result.providerMetadata : undefined)),
846
- });
1089
+ }));
847
1090
  }
848
1091
  // Some of other platforms may return both tool calls and text.
849
1092
  // Putting a text message here will let the agent loop to complete,
@@ -981,6 +1224,7 @@ export class AiSdkModel {
981
1224
  abortSignal: request.signal,
982
1225
  ...(request.modelSettings.providerData ?? {}),
983
1226
  };
1227
+ const requestedToolsByName = buildRequestedToolsByName(request);
984
1228
  if (this.#logger.dontLogModelData) {
985
1229
  this.#logger.debug('Request received (streamed)');
986
1230
  }
@@ -1047,14 +1291,16 @@ export class AiSdkModel {
1047
1291
  case 'tool-call': {
1048
1292
  const toolCallId = part.toolCallId;
1049
1293
  if (toolCallId) {
1050
- functionCalls[toolCallId] = {
1051
- type: 'function_call',
1052
- callId: toolCallId,
1053
- name: part.toolName,
1054
- arguments: part.input ?? '',
1055
- status: 'completed',
1294
+ const requestedTool = typeof part.toolName === 'string'
1295
+ ? requestedToolsByName.get(part.toolName)
1296
+ : undefined;
1297
+ functionCalls[toolCallId] = createProtocolToolCallItem({
1298
+ requestedTool,
1299
+ toolCallId,
1300
+ toolName: part.toolName,
1301
+ input: part.input,
1056
1302
  providerData: mergeProviderData(baseProviderData, part.providerMetadata),
1057
- };
1303
+ });
1058
1304
  }
1059
1305
  break;
1060
1306
  }
@@ -1204,7 +1450,7 @@ export class AiSdkModel {
1204
1450
  * If tracing is enabled, the model will send generation spans to your traces processor.
1205
1451
  *
1206
1452
  * ```ts
1207
- * import { aisdk } from '@openai/agents-extensions';
1453
+ * import { aisdk } from '@openai/agents-extensions/ai-sdk';
1208
1454
  * import { openai } from '@ai-sdk/openai';
1209
1455
  *
1210
1456
  * const model = aisdk(openai('gpt-4o'));