@tanstack/openai-base 0.10.16 → 0.11.1

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.
@@ -3,7 +3,8 @@ import { buildResponsesUsage } from "../usage.js";
3
3
  import { extractRequestOptions } from "../utils/request-options.js";
4
4
  import { createToolInputNormalizer } from "../utils/tool-input-normalizer.js";
5
5
  import { convertToolsToResponsesFormat } from "./responses-tool-converter.js";
6
- import { EventType, normalizeSystemPrompts } from "@tanstack/ai";
6
+ import { hostedShellCallIds, readUserExecutedCall, userToolRequestItem, userToolResultItem } from "./responses-user-tools.js";
7
+ import { EventType, fileReferenceFor, isFileSource, normalizeSystemPrompts, unsupportedFileSourceError } from "@tanstack/ai";
7
8
  import { toRunErrorPayload, toRunErrorRawEvent } from "@tanstack/ai/adapter-internals";
8
9
  import { BaseTextAdapter } from "@tanstack/ai/adapters";
9
10
  import { generateId } from "@tanstack/ai-utils";
@@ -639,6 +640,46 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
639
640
  hasClosedReasoning = false;
640
641
  accumulatedReasoning = "";
641
642
  };
643
+ const userToolChunks = (item, outputIndex, bareShell) => {
644
+ const call = readUserExecutedCall(item, { bareShell });
645
+ if (!call || toolCallMetadata.get(call.callId)?.ended) return [];
646
+ const tracked = toolCallMetadata.get(call.callId) ?? {
647
+ callId: call.callId,
648
+ index: outputIndex,
649
+ name: call.name,
650
+ started: false
651
+ };
652
+ toolCallMetadata.set(call.callId, tracked);
653
+ tracked.started = true;
654
+ tracked.ended = true;
655
+ tracked.name = call.name;
656
+ const timestamp = Date.now();
657
+ const modelName = model || options.model;
658
+ const metadata = {
659
+ openaiUserTool: call.name,
660
+ ...call.itemId ? { itemId: call.itemId } : {},
661
+ ...call.maxOutputLength !== void 0 ? { maxOutputLength: call.maxOutputLength } : {}
662
+ };
663
+ return [{
664
+ type: EventType.TOOL_CALL_START,
665
+ toolCallId: call.callId,
666
+ toolCallName: call.name,
667
+ toolName: call.name,
668
+ parentMessageId: aguiState.messageId,
669
+ model: modelName,
670
+ timestamp,
671
+ index: outputIndex,
672
+ metadata
673
+ }, {
674
+ type: EventType.TOOL_CALL_END,
675
+ toolCallId: call.callId,
676
+ toolCallName: call.name,
677
+ toolName: call.name,
678
+ model: modelName,
679
+ timestamp,
680
+ input: call.input
681
+ }];
682
+ };
642
683
  const emitReasoningDelta = function* (text) {
643
684
  if (!text) return;
644
685
  yield* openReasoning();
@@ -850,6 +891,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
850
891
  metadata.started = true;
851
892
  }
852
893
  }
894
+ yield* userToolChunks(item, chunk.output_index, false);
853
895
  }
854
896
  if (chunk.type === "response.function_call_arguments.delta" && chunk.delta) {
855
897
  const metadata = toolCallMetadata.get(chunk.item_id);
@@ -973,6 +1015,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
973
1015
  metadata.pendingArguments = void 0;
974
1016
  }
975
1017
  }
1018
+ yield* userToolChunks(item, chunk.output_index, false);
976
1019
  }
977
1020
  if (chunk.type === "response.completed") {
978
1021
  const completedText = chunk.response.output.flatMap((item) => item.type === "message" ? item.content : []).filter((part) => part.type === "output_text").map((part) => part.text).join("");
@@ -1012,11 +1055,11 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
1012
1055
  };
1013
1056
  else if (!closedReasoningStepId) yield* openReasoning();
1014
1057
  }
1015
- for (const item of chunk.response.output) {
1058
+ for (const [outputIndex, item] of chunk.response.output.entries()) {
1016
1059
  if (item.type !== "function_call" || !item.id) continue;
1017
1060
  const metadata = toolCallMetadata.get(item.id) ?? {
1018
1061
  callId: item.call_id || item.id,
1019
- index: 0,
1062
+ index: outputIndex,
1020
1063
  name: item.name || "",
1021
1064
  started: false
1022
1065
  };
@@ -1070,6 +1113,11 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
1070
1113
  metadata.pendingArguments = void 0;
1071
1114
  }
1072
1115
  }
1116
+ const shellOutputs = hostedShellCallIds(chunk.response.output);
1117
+ for (const [outputIndex, item] of chunk.response.output.entries()) {
1118
+ if (isRecord(item) && item.type === "shell_call" && typeof item.call_id === "string" && shellOutputs.has(item.call_id)) continue;
1119
+ yield* userToolChunks(item, outputIndex, true);
1120
+ }
1073
1121
  yield* closeReasoning();
1074
1122
  if (hasEmittedTextMessageStart) {
1075
1123
  yield {
@@ -1080,7 +1128,12 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
1080
1128
  };
1081
1129
  hasEmittedTextMessageStart = false;
1082
1130
  }
1083
- const hasFunctionCalls = chunk.response.output.some((item) => item.type === "function_call");
1131
+ const hasFunctionCalls = chunk.response.output.some((item) => {
1132
+ if (!isRecord(item)) return false;
1133
+ if (item.type === "function_call") return true;
1134
+ if (item.type === "shell_call" && typeof item.call_id === "string" && shellOutputs.has(item.call_id)) return false;
1135
+ return readUserExecutedCall(item, { bareShell: true }) !== null;
1136
+ });
1084
1137
  const incompleteReason = chunk.response.incomplete_details?.reason;
1085
1138
  const finishReason = hasFunctionCalls ? "tool_calls" : incompleteReason === "max_output_tokens" ? "length" : incompleteReason === "content_filter" ? "content_filter" : "stop";
1086
1139
  yield {
@@ -1198,8 +1251,16 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
1198
1251
  */
1199
1252
  convertMessagesToInput(messages) {
1200
1253
  const result = [];
1254
+ const seenReasoningIds = /* @__PURE__ */ new Set();
1255
+ const userToolCalls = /* @__PURE__ */ new Map();
1201
1256
  for (const message of messages) {
1202
1257
  if (message.role === "tool") {
1258
+ const owner = message.toolCallId ? userToolCalls.get(message.toolCallId) : void 0;
1259
+ const userOutput = owner ? userToolResultItem(owner, message.content) : null;
1260
+ if (userOutput) {
1261
+ result.push(userOutput);
1262
+ continue;
1263
+ }
1203
1264
  const toolContent = message.content;
1204
1265
  const output = Array.isArray(toolContent) ? toolContent.map((part) => this.convertContentPartToInput(part)) : typeof toolContent === "string" ? toolContent : JSON.stringify(toolContent);
1205
1266
  result.push({
@@ -1210,10 +1271,16 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
1210
1271
  continue;
1211
1272
  }
1212
1273
  if (message.role === "assistant") {
1274
+ let reasoningCandidates = 0;
1275
+ let emittedReasoning = 0;
1213
1276
  if (message.thinking) for (const thinking of message.thinking) {
1214
1277
  if (!thinking.signature) continue;
1215
1278
  const packed = unpackResponsesReasoningSignature(thinking.signature);
1216
1279
  if (!packed?.id) continue;
1280
+ reasoningCandidates++;
1281
+ if (seenReasoningIds.has(packed.id)) continue;
1282
+ seenReasoningIds.add(packed.id);
1283
+ emittedReasoning++;
1217
1284
  result.push({
1218
1285
  type: "reasoning",
1219
1286
  id: packed.id,
@@ -1224,13 +1291,28 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
1224
1291
  }] : []
1225
1292
  });
1226
1293
  }
1294
+ const canPairReasoning = reasoningCandidates === 0 || reasoningCandidates === 1 && emittedReasoning === 1;
1227
1295
  if (message.toolCalls && message.toolCalls.length > 0) for (const toolCall of message.toolCalls) {
1228
1296
  const argumentsString = typeof toolCall.function.arguments === "string" ? toolCall.function.arguments : JSON.stringify(toolCall.function.arguments);
1297
+ const replayCall = {
1298
+ id: toolCall.id,
1299
+ function: {
1300
+ name: toolCall.function.name,
1301
+ arguments: argumentsString
1302
+ },
1303
+ ...toolCall.metadata !== void 0 ? { metadata: toolCall.metadata } : {}
1304
+ };
1305
+ const userItem = userToolRequestItem(replayCall);
1306
+ if (userItem) {
1307
+ userToolCalls.set(toolCall.id, replayCall);
1308
+ result.push(userItem);
1309
+ continue;
1310
+ }
1229
1311
  const itemId = toolCall.metadata?.itemId;
1230
1312
  result.push({
1231
1313
  type: "function_call",
1232
1314
  call_id: toolCall.id,
1233
- ...itemId && { id: itemId },
1315
+ ...itemId && canPairReasoning && { id: itemId },
1234
1316
  name: toolCall.function.name,
1235
1317
  arguments: argumentsString
1236
1318
  });
@@ -1270,6 +1352,14 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
1270
1352
  };
1271
1353
  case "image": {
1272
1354
  const imageMetadata = part.metadata;
1355
+ if (isFileSource(part.source)) {
1356
+ if (this.supportsFileSources !== true) throw unsupportedFileSourceError(this.name);
1357
+ return {
1358
+ type: "input_image",
1359
+ file_id: fileReferenceFor(part.source, this.name),
1360
+ detail: imageMetadata?.detail || "auto"
1361
+ };
1362
+ }
1273
1363
  if (part.source.type === "url") return {
1274
1364
  type: "input_image",
1275
1365
  image_url: part.source.value,
@@ -1284,6 +1374,13 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
1284
1374
  };
1285
1375
  }
1286
1376
  case "audio": {
1377
+ if (isFileSource(part.source)) {
1378
+ if (this.supportsFileSources !== true) throw unsupportedFileSourceError(this.name);
1379
+ return {
1380
+ type: "input_file",
1381
+ file_id: fileReferenceFor(part.source, this.name)
1382
+ };
1383
+ }
1287
1384
  if (part.source.type === "url") return {
1288
1385
  type: "input_file",
1289
1386
  file_url: part.source.value
@@ -1298,6 +1395,14 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
1298
1395
  case "document": {
1299
1396
  const documentMetadata = part.metadata;
1300
1397
  const documentDetail = documentMetadata?.detail !== void 0 ? { detail: documentMetadata.detail } : {};
1398
+ if (isFileSource(part.source)) {
1399
+ if (this.supportsFileSources !== true) throw unsupportedFileSourceError(this.name);
1400
+ return {
1401
+ type: "input_file",
1402
+ file_id: fileReferenceFor(part.source, this.name),
1403
+ ...documentDetail
1404
+ };
1405
+ }
1301
1406
  if (part.source.type === "url") return {
1302
1407
  type: "input_file",
1303
1408
  file_url: part.source.value,