@tanstack/openai-base 0.11.1 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/adapters/chat-completions-text.d.ts +4 -2
- package/dist/esm/adapters/chat-completions-text.js +6 -2
- package/dist/esm/adapters/chat-completions-text.js.map +1 -1
- package/dist/esm/adapters/responses-text.d.ts +12 -5
- package/dist/esm/adapters/responses-text.js +155 -15
- package/dist/esm/adapters/responses-text.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/usage.d.ts +2 -2
- package/dist/esm/usage.js +5 -3
- package/dist/esm/usage.js.map +1 -1
- package/dist/esm/utils/schema-converter.d.ts +22 -0
- package/dist/esm/utils/schema-converter.js +41 -8
- package/dist/esm/utils/schema-converter.js.map +1 -1
- package/package.json +4 -3
- package/src/adapters/chat-completions-text.ts +21 -3
- package/src/adapters/responses-text.ts +276 -31
- package/src/index.ts +2 -0
- package/src/usage.ts +16 -5
- package/src/utils/schema-converter.ts +72 -12
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { makeStructuredOutputCompatibleWithMap } from "../utils/schema-converter.js";
|
|
1
|
+
import { makeStructuredOutputCompatibleWithMap, warnStrictFallback } from "../utils/schema-converter.js";
|
|
2
2
|
import { buildResponsesUsage } from "../usage.js";
|
|
3
3
|
import { extractRequestOptions } from "../utils/request-options.js";
|
|
4
4
|
import { createToolInputNormalizer } from "../utils/tool-input-normalizer.js";
|
|
@@ -13,6 +13,40 @@ var PDF_BASE64_MAGIC = "JVBERi";
|
|
|
13
13
|
function isRecord(value) {
|
|
14
14
|
return typeof value === "object" && value !== null;
|
|
15
15
|
}
|
|
16
|
+
function readURLCitation(value) {
|
|
17
|
+
if (!isRecord(value) || value.type !== "url_citation") return void 0;
|
|
18
|
+
if (typeof value.url !== "string" || typeof value.title !== "string" || typeof value.start_index !== "number" || typeof value.end_index !== "number") return;
|
|
19
|
+
return {
|
|
20
|
+
type: "url_citation",
|
|
21
|
+
url: value.url,
|
|
22
|
+
title: value.title,
|
|
23
|
+
start_index: value.start_index,
|
|
24
|
+
end_index: value.end_index
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function readWebSearchCall(value) {
|
|
28
|
+
if (!isRecord(value) || value.type !== "web_search_call") return void 0;
|
|
29
|
+
if (typeof value.id !== "string" || typeof value.status !== "string" || !isRecord(value.action) || typeof value.action.type !== "string") return;
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
function collectWebSearchSources(item, citations) {
|
|
33
|
+
const sources = /* @__PURE__ */ new Map();
|
|
34
|
+
const add = (url, title) => {
|
|
35
|
+
if (typeof url !== "string" || url.length === 0) return;
|
|
36
|
+
const existing = sources.get(url);
|
|
37
|
+
if (existing) {
|
|
38
|
+
if (!existing.title && typeof title === "string" && title.length > 0) existing.title = title;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
sources.set(url, {
|
|
42
|
+
url,
|
|
43
|
+
...typeof title === "string" && title.length > 0 ? { title } : {}
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
if (item.action.type === "search") for (const source of item.action.sources ?? []) add(source.url);
|
|
47
|
+
for (const citation of citations) add(citation.url, citation.title);
|
|
48
|
+
return [...sources.values()];
|
|
49
|
+
}
|
|
16
50
|
function packResponsesReasoningSignature(id, encryptedContent) {
|
|
17
51
|
if (!id && !encryptedContent) return void 0;
|
|
18
52
|
return JSON.stringify({
|
|
@@ -55,10 +89,13 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
55
89
|
kind = "text";
|
|
56
90
|
name;
|
|
57
91
|
client;
|
|
58
|
-
|
|
92
|
+
/** See {@link OpenAIBaseTextAdapterOptions.strictFallbackWarning}. */
|
|
93
|
+
strictFallbackWarning;
|
|
94
|
+
constructor(model, name, client, options = {}) {
|
|
59
95
|
super({}, model);
|
|
60
96
|
this.name = name;
|
|
61
97
|
this.client = client;
|
|
98
|
+
this.strictFallbackWarning = options.strictFallbackWarning ?? true;
|
|
62
99
|
}
|
|
63
100
|
async *chatStream(options) {
|
|
64
101
|
const toolCallMetadata = /* @__PURE__ */ new Map();
|
|
@@ -198,6 +235,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
198
235
|
let hasClosedReasoning = false;
|
|
199
236
|
let model = chatOptions.model;
|
|
200
237
|
let usage;
|
|
238
|
+
let responseCompleted = false;
|
|
201
239
|
const closeReasoning = function* () {
|
|
202
240
|
if (reasoningMessageId && !hasClosedReasoning) {
|
|
203
241
|
hasClosedReasoning = true;
|
|
@@ -348,10 +386,11 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
348
386
|
continue;
|
|
349
387
|
}
|
|
350
388
|
if (chunk.type === "response.completed") {
|
|
389
|
+
responseCompleted = true;
|
|
351
390
|
const response = chunk.response;
|
|
352
391
|
if (response.usage) usage = response.usage;
|
|
353
392
|
if (response.model) model = response.model;
|
|
354
|
-
|
|
393
|
+
break;
|
|
355
394
|
}
|
|
356
395
|
if (chunk.type === "response.failed") {
|
|
357
396
|
const response = chunk.response;
|
|
@@ -379,6 +418,22 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
379
418
|
model,
|
|
380
419
|
timestamp: Date.now()
|
|
381
420
|
};
|
|
421
|
+
if (!responseCompleted) {
|
|
422
|
+
const message = "Response stream ended before response.completed";
|
|
423
|
+
yield {
|
|
424
|
+
type: EventType.RUN_ERROR,
|
|
425
|
+
runId: aguiState.runId,
|
|
426
|
+
model,
|
|
427
|
+
timestamp: Date.now(),
|
|
428
|
+
message,
|
|
429
|
+
code: "incomplete-stream",
|
|
430
|
+
error: {
|
|
431
|
+
message,
|
|
432
|
+
code: "incomplete-stream"
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
382
437
|
if (accumulatedContent.length === 0) {
|
|
383
438
|
yield {
|
|
384
439
|
type: EventType.RUN_ERROR,
|
|
@@ -567,8 +622,61 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
567
622
|
let closedReasoningStepId;
|
|
568
623
|
let hasClosedReasoning = false;
|
|
569
624
|
let runFinishedEmitted = false;
|
|
625
|
+
const providerWebSearchCalls = /* @__PURE__ */ new Map();
|
|
626
|
+
const webSearchCitations = [];
|
|
570
627
|
const adapterName = this.name;
|
|
571
628
|
const emitModel = () => model || options.model;
|
|
629
|
+
const recordProviderWebSearchCall = (value, index) => {
|
|
630
|
+
const item = readWebSearchCall(value);
|
|
631
|
+
if (!item) return;
|
|
632
|
+
const existing = providerWebSearchCalls.get(item.id);
|
|
633
|
+
if (existing) {
|
|
634
|
+
existing.item = item;
|
|
635
|
+
existing.index = index;
|
|
636
|
+
} else providerWebSearchCalls.set(item.id, {
|
|
637
|
+
item,
|
|
638
|
+
index,
|
|
639
|
+
started: false
|
|
640
|
+
});
|
|
641
|
+
};
|
|
642
|
+
const emitProviderWebSearchCalls = function* (assistantMessage, completedOnly = false) {
|
|
643
|
+
for (const entry of providerWebSearchCalls.values()) {
|
|
644
|
+
if (entry.started || completedOnly && entry.item.status !== "completed") continue;
|
|
645
|
+
const callUrls = new Set(entry.item.action.type === "search" ? (entry.item.action.sources ?? []).map((source) => source.url) : []);
|
|
646
|
+
const citations = webSearchCitations.filter((citation) => callUrls.has(citation.url));
|
|
647
|
+
const metadata = {
|
|
648
|
+
itemId: entry.item.id,
|
|
649
|
+
providerExecuted: true,
|
|
650
|
+
sources: collectWebSearchSources(entry.item, citations),
|
|
651
|
+
openai: {
|
|
652
|
+
webSearchCall: entry.item,
|
|
653
|
+
urlCitations: citations,
|
|
654
|
+
...assistantMessage ? { assistantMessage } : {}
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
entry.started = true;
|
|
658
|
+
yield {
|
|
659
|
+
type: EventType.TOOL_CALL_START,
|
|
660
|
+
toolCallId: entry.item.id,
|
|
661
|
+
toolCallName: "web_search",
|
|
662
|
+
toolName: "web_search",
|
|
663
|
+
parentMessageId: aguiState.messageId,
|
|
664
|
+
model: emitModel(),
|
|
665
|
+
timestamp: Date.now(),
|
|
666
|
+
index: entry.index,
|
|
667
|
+
metadata
|
|
668
|
+
};
|
|
669
|
+
yield {
|
|
670
|
+
type: EventType.TOOL_CALL_END,
|
|
671
|
+
toolCallId: entry.item.id,
|
|
672
|
+
toolCallName: "web_search",
|
|
673
|
+
toolName: "web_search",
|
|
674
|
+
model: emitModel(),
|
|
675
|
+
timestamp: Date.now(),
|
|
676
|
+
input: entry.item.action
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
};
|
|
572
680
|
const openReasoning = function* () {
|
|
573
681
|
if (reasoningMessageId) return;
|
|
574
682
|
reasoningMessageId = generateId(adapterName);
|
|
@@ -750,6 +858,10 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
750
858
|
accumulatedReasoning = "";
|
|
751
859
|
}
|
|
752
860
|
if (chunk.type === "response.failed" || chunk.type === "response.incomplete") {
|
|
861
|
+
if (chunk.type === "response.incomplete") {
|
|
862
|
+
for (const [index, item] of (chunk.response.output ?? []).entries()) recordProviderWebSearchCall(item, index);
|
|
863
|
+
yield* emitProviderWebSearchCalls(void 0, true);
|
|
864
|
+
}
|
|
753
865
|
yield* closeReasoning();
|
|
754
866
|
if (hasEmittedTextMessageStart) {
|
|
755
867
|
yield {
|
|
@@ -858,6 +970,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
858
970
|
}
|
|
859
971
|
if (chunk.type === "response.output_item.added") {
|
|
860
972
|
const item = chunk.item;
|
|
973
|
+
if (item.type === "web_search_call") recordProviderWebSearchCall(item, chunk.output_index);
|
|
861
974
|
if (item.type === "reasoning") {
|
|
862
975
|
captureReasoningItem(item);
|
|
863
976
|
yield* openReasoning();
|
|
@@ -954,6 +1067,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
954
1067
|
}
|
|
955
1068
|
if (chunk.type === "response.output_item.done") {
|
|
956
1069
|
const item = chunk.item;
|
|
1070
|
+
if (item.type === "web_search_call") recordProviderWebSearchCall(item, chunk.output_index);
|
|
957
1071
|
if (item.type === "reasoning") {
|
|
958
1072
|
captureReasoningItem(item);
|
|
959
1073
|
yield* openReasoning();
|
|
@@ -1017,8 +1131,15 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
1017
1131
|
}
|
|
1018
1132
|
yield* userToolChunks(item, chunk.output_index, false);
|
|
1019
1133
|
}
|
|
1134
|
+
if (chunk.type === "response.output_text.annotation.added") {
|
|
1135
|
+
const citation = readURLCitation(chunk.annotation);
|
|
1136
|
+
if (citation) webSearchCitations.push(citation);
|
|
1137
|
+
}
|
|
1020
1138
|
if (chunk.type === "response.completed") {
|
|
1021
|
-
const
|
|
1139
|
+
const responseOutput = Array.isArray(chunk.response.output) ? chunk.response.output : [];
|
|
1140
|
+
const assistantMessage = responseOutput.find((item) => item.type === "message");
|
|
1141
|
+
for (const [index, item] of responseOutput.entries()) if (item.type === "web_search_call") recordProviderWebSearchCall(item, index);
|
|
1142
|
+
const completedText = responseOutput.flatMap((item) => item.type === "message" ? item.content : []).filter((part) => part.type === "output_text").map((part) => part.text).join("");
|
|
1022
1143
|
if (accumulatedContent.length === 0 && completedText.length > 0) {
|
|
1023
1144
|
if (!hasEmittedTextMessageStart) {
|
|
1024
1145
|
hasEmittedTextMessageStart = true;
|
|
@@ -1041,7 +1162,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
1041
1162
|
content: accumulatedContent
|
|
1042
1163
|
};
|
|
1043
1164
|
}
|
|
1044
|
-
|
|
1165
|
+
for (const item of responseOutput) captureReasoningItem(item);
|
|
1045
1166
|
if (!reasoningMessageId && (reasoningItemId || reasoningEncryptedContent)) {
|
|
1046
1167
|
const signature = packResponsesReasoningSignature(reasoningItemId, reasoningEncryptedContent);
|
|
1047
1168
|
if (closedReasoningStepId && signature) yield {
|
|
@@ -1055,7 +1176,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
1055
1176
|
};
|
|
1056
1177
|
else if (!closedReasoningStepId) yield* openReasoning();
|
|
1057
1178
|
}
|
|
1058
|
-
for (const [outputIndex, item] of
|
|
1179
|
+
for (const [outputIndex, item] of responseOutput.entries()) {
|
|
1059
1180
|
if (item.type !== "function_call" || !item.id) continue;
|
|
1060
1181
|
const metadata = toolCallMetadata.get(item.id) ?? {
|
|
1061
1182
|
callId: item.call_id || item.id,
|
|
@@ -1113,8 +1234,9 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
1113
1234
|
metadata.pendingArguments = void 0;
|
|
1114
1235
|
}
|
|
1115
1236
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1237
|
+
yield* emitProviderWebSearchCalls(assistantMessage);
|
|
1238
|
+
const shellOutputs = hostedShellCallIds(responseOutput);
|
|
1239
|
+
for (const [outputIndex, item] of responseOutput.entries()) {
|
|
1118
1240
|
if (isRecord(item) && item.type === "shell_call" && typeof item.call_id === "string" && shellOutputs.has(item.call_id)) continue;
|
|
1119
1241
|
yield* userToolChunks(item, outputIndex, true);
|
|
1120
1242
|
}
|
|
@@ -1128,7 +1250,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
1128
1250
|
};
|
|
1129
1251
|
hasEmittedTextMessageStart = false;
|
|
1130
1252
|
}
|
|
1131
|
-
const hasFunctionCalls =
|
|
1253
|
+
const hasFunctionCalls = responseOutput.some((item) => {
|
|
1132
1254
|
if (!isRecord(item)) return false;
|
|
1133
1255
|
if (item.type === "function_call") return true;
|
|
1134
1256
|
if (item.type === "shell_call" && typeof item.call_id === "string" && shellOutputs.has(item.call_id)) return false;
|
|
@@ -1146,6 +1268,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
1146
1268
|
finishReason
|
|
1147
1269
|
};
|
|
1148
1270
|
runFinishedEmitted = true;
|
|
1271
|
+
return;
|
|
1149
1272
|
}
|
|
1150
1273
|
if (chunk.type === "error") {
|
|
1151
1274
|
const code = chunk.code ?? void 0;
|
|
@@ -1165,6 +1288,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
1165
1288
|
}
|
|
1166
1289
|
}
|
|
1167
1290
|
if (!runFinishedEmitted && aguiState.hasEmittedRunStarted) {
|
|
1291
|
+
yield* emitProviderWebSearchCalls(void 0, true);
|
|
1168
1292
|
yield* closeReasoning();
|
|
1169
1293
|
if (hasEmittedTextMessageStart) yield {
|
|
1170
1294
|
type: EventType.TEXT_MESSAGE_END,
|
|
@@ -1172,13 +1296,17 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
1172
1296
|
model: model || options.model,
|
|
1173
1297
|
timestamp: Date.now()
|
|
1174
1298
|
};
|
|
1299
|
+
const message = "Response stream ended before response.completed";
|
|
1175
1300
|
yield {
|
|
1176
|
-
type: EventType.
|
|
1177
|
-
runId: aguiState.runId,
|
|
1178
|
-
threadId: aguiState.threadId,
|
|
1301
|
+
type: EventType.RUN_ERROR,
|
|
1179
1302
|
model: model || options.model,
|
|
1180
1303
|
timestamp: Date.now(),
|
|
1181
|
-
|
|
1304
|
+
message,
|
|
1305
|
+
code: "incomplete-stream",
|
|
1306
|
+
error: {
|
|
1307
|
+
message,
|
|
1308
|
+
code: "incomplete-stream"
|
|
1309
|
+
}
|
|
1182
1310
|
};
|
|
1183
1311
|
}
|
|
1184
1312
|
} catch (error) {
|
|
@@ -1208,6 +1336,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
1208
1336
|
*/
|
|
1209
1337
|
mapOptionsToRequest(options) {
|
|
1210
1338
|
const input = this.convertMessagesToInput(options.messages);
|
|
1339
|
+
if (this.strictFallbackWarning) warnStrictFallback(options.tools, options.logger);
|
|
1211
1340
|
const tools = options.tools ? convertToolsToResponsesFormat(options.tools, this.makeStructuredOutputCompatible.bind(this)) : void 0;
|
|
1212
1341
|
const modelOptions = options.modelOptions;
|
|
1213
1342
|
const combinedSchema = options.outputSchema;
|
|
@@ -1292,7 +1421,17 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
1292
1421
|
});
|
|
1293
1422
|
}
|
|
1294
1423
|
const canPairReasoning = reasoningCandidates === 0 || reasoningCandidates === 1 && emittedReasoning === 1;
|
|
1424
|
+
let rawAssistantMessage;
|
|
1295
1425
|
if (message.toolCalls && message.toolCalls.length > 0) for (const toolCall of message.toolCalls) {
|
|
1426
|
+
const metadata = toolCall.metadata;
|
|
1427
|
+
if (metadata?.providerExecuted) {
|
|
1428
|
+
const webSearchCall = metadata.openai?.webSearchCall;
|
|
1429
|
+
if (webSearchCall && canPairReasoning) {
|
|
1430
|
+
result.push(webSearchCall);
|
|
1431
|
+
rawAssistantMessage ??= metadata.openai?.assistantMessage;
|
|
1432
|
+
}
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1296
1435
|
const argumentsString = typeof toolCall.function.arguments === "string" ? toolCall.function.arguments : JSON.stringify(toolCall.function.arguments);
|
|
1297
1436
|
const replayCall = {
|
|
1298
1437
|
id: toolCall.id,
|
|
@@ -1308,7 +1447,7 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
1308
1447
|
result.push(userItem);
|
|
1309
1448
|
continue;
|
|
1310
1449
|
}
|
|
1311
|
-
const itemId =
|
|
1450
|
+
const itemId = metadata?.itemId;
|
|
1312
1451
|
result.push({
|
|
1313
1452
|
type: "function_call",
|
|
1314
1453
|
call_id: toolCall.id,
|
|
@@ -1317,7 +1456,8 @@ var OpenAIBaseResponsesTextAdapter = class extends BaseTextAdapter {
|
|
|
1317
1456
|
arguments: argumentsString
|
|
1318
1457
|
});
|
|
1319
1458
|
}
|
|
1320
|
-
if (
|
|
1459
|
+
if (rawAssistantMessage) result.push(rawAssistantMessage);
|
|
1460
|
+
else if (message.content) {
|
|
1321
1461
|
const contentStr = this.extractTextContent(message.content);
|
|
1322
1462
|
if (contentStr) result.push({
|
|
1323
1463
|
type: "message",
|