extrait 0.7.3 → 0.7.5
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/generate-output.d.ts +11 -0
- package/dist/index.cjs +371 -145
- package/dist/index.d.ts +1 -0
- package/dist/index.js +371 -145
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,7 @@ var __export = (target, all) => {
|
|
|
40
40
|
var exports_src = {};
|
|
41
41
|
__export(exports_src, {
|
|
42
42
|
wrapMCPClient: () => wrapMCPClient,
|
|
43
|
+
withoutTrailingThinkTagPrefix: () => withoutTrailingThinkTagPrefix,
|
|
43
44
|
withFormat: () => withFormat,
|
|
44
45
|
structured: () => structured,
|
|
45
46
|
sanitizeThink: () => sanitizeThink,
|
|
@@ -49,6 +50,7 @@ __export(exports_src, {
|
|
|
49
50
|
registerBuiltinProviders: () => registerBuiltinProviders,
|
|
50
51
|
prompt: () => prompt,
|
|
51
52
|
parseLLMOutput: () => parseLLMOutput,
|
|
53
|
+
normalizeModelOutput: () => normalizeModelOutput,
|
|
52
54
|
inspectSchemaMetadata: () => inspectSchemaMetadata,
|
|
53
55
|
inferSchemaExample: () => inferSchemaExample,
|
|
54
56
|
images: () => images,
|
|
@@ -1168,6 +1170,110 @@ function countHiddenChars(value) {
|
|
|
1168
1170
|
function maskKeepingLineBreaks(value) {
|
|
1169
1171
|
return value.replace(RE_NON_LINE_BREAK, " ");
|
|
1170
1172
|
}
|
|
1173
|
+
// src/generate-output.ts
|
|
1174
|
+
var RE_THINK_TAGS = /<\/?think\s*>/gi;
|
|
1175
|
+
function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
|
|
1176
|
+
const sanitized = sanitizeThink(text);
|
|
1177
|
+
const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
|
|
1178
|
+
const reasoning = joinReasoningSegments([
|
|
1179
|
+
sanitizeReasoningText(dedicatedReasoning),
|
|
1180
|
+
...sanitized.thinkBlocks.map((block) => block.content)
|
|
1181
|
+
]);
|
|
1182
|
+
return {
|
|
1183
|
+
text: visibleText,
|
|
1184
|
+
reasoning,
|
|
1185
|
+
reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
|
|
1186
|
+
thinkBlocks: sanitized.thinkBlocks,
|
|
1187
|
+
parseSource: composeParseSource(visibleText, reasoning)
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
function normalizeReasoningBlocks(blocks) {
|
|
1191
|
+
if (!Array.isArray(blocks)) {
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
const normalized = blocks.map((block) => ({
|
|
1195
|
+
turnIndex: block.turnIndex,
|
|
1196
|
+
text: block.text.replace(RE_THINK_TAGS, "").trim()
|
|
1197
|
+
})).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
|
|
1198
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
1199
|
+
}
|
|
1200
|
+
function appendReasoningBlock(blocks, transition) {
|
|
1201
|
+
const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
|
|
1202
|
+
if (!text) {
|
|
1203
|
+
return blocks;
|
|
1204
|
+
}
|
|
1205
|
+
const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
|
|
1206
|
+
return normalizeReasoningBlocks(next);
|
|
1207
|
+
}
|
|
1208
|
+
function composeParseSource(text, reasoning) {
|
|
1209
|
+
if (typeof reasoning !== "string" || reasoning.length === 0) {
|
|
1210
|
+
return text;
|
|
1211
|
+
}
|
|
1212
|
+
const sanitized = reasoning.replace(RE_THINK_TAGS, "");
|
|
1213
|
+
if (sanitized.length === 0) {
|
|
1214
|
+
return text;
|
|
1215
|
+
}
|
|
1216
|
+
return `<think>${sanitized}</think>${text}`;
|
|
1217
|
+
}
|
|
1218
|
+
function aggregateUsage(attempts) {
|
|
1219
|
+
let usage;
|
|
1220
|
+
for (const attempt of attempts) {
|
|
1221
|
+
usage = mergeUsage(usage, attempt.usage);
|
|
1222
|
+
}
|
|
1223
|
+
return usage;
|
|
1224
|
+
}
|
|
1225
|
+
function mergeUsage(base, next) {
|
|
1226
|
+
if (!base && !next) {
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
return {
|
|
1230
|
+
inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
|
|
1231
|
+
outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
|
|
1232
|
+
totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
|
|
1233
|
+
cost: (base?.cost ?? 0) + (next?.cost ?? 0)
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
function joinReasoningSegments(parts) {
|
|
1237
|
+
return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
|
|
1238
|
+
|
|
1239
|
+
`);
|
|
1240
|
+
}
|
|
1241
|
+
function sanitizeReasoningText(value) {
|
|
1242
|
+
const sanitized = value?.replace(RE_THINK_TAGS, "").trim();
|
|
1243
|
+
return sanitized ? sanitized : undefined;
|
|
1244
|
+
}
|
|
1245
|
+
var THINK_TAG_VARIANTS = ["<think>", "</think>"];
|
|
1246
|
+
var MAX_THINK_TAG_PREFIX = Math.max(...THINK_TAG_VARIANTS.map((tag) => tag.length)) - 1;
|
|
1247
|
+
function withoutTrailingThinkTagPrefix(value) {
|
|
1248
|
+
const max = Math.min(value.length, MAX_THINK_TAG_PREFIX);
|
|
1249
|
+
for (let length = max;length > 0; length -= 1) {
|
|
1250
|
+
const suffix = value.slice(value.length - length);
|
|
1251
|
+
if (THINK_TAG_VARIANTS.some((tag) => tag.length > suffix.length && tag.startsWith(suffix))) {
|
|
1252
|
+
return value.slice(0, value.length - length);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
return value;
|
|
1256
|
+
}
|
|
1257
|
+
function stripThinkBlocks(text, thinkBlocks) {
|
|
1258
|
+
if (thinkBlocks.length === 0) {
|
|
1259
|
+
return text;
|
|
1260
|
+
}
|
|
1261
|
+
let output = "";
|
|
1262
|
+
let cursor = 0;
|
|
1263
|
+
for (const block of thinkBlocks) {
|
|
1264
|
+
output += text.slice(cursor, block.start);
|
|
1265
|
+
cursor = block.end;
|
|
1266
|
+
}
|
|
1267
|
+
output += text.slice(cursor);
|
|
1268
|
+
return output;
|
|
1269
|
+
}
|
|
1270
|
+
function toStreamDataFingerprint(value) {
|
|
1271
|
+
try {
|
|
1272
|
+
return JSON.stringify(value);
|
|
1273
|
+
} catch {
|
|
1274
|
+
return "__unserializable__";
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1171
1277
|
// src/providers/stream-utils.ts
|
|
1172
1278
|
var RE_LINE_ENDING = /\r?\n/;
|
|
1173
1279
|
async function consumeSSE(response, onEvent) {
|
|
@@ -1597,7 +1703,7 @@ function pickString(value) {
|
|
|
1597
1703
|
function toFiniteNumber(value) {
|
|
1598
1704
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
1599
1705
|
}
|
|
1600
|
-
function
|
|
1706
|
+
function mergeUsage2(base, next) {
|
|
1601
1707
|
if (!base && !next) {
|
|
1602
1708
|
return;
|
|
1603
1709
|
}
|
|
@@ -1722,6 +1828,8 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
|
|
|
1722
1828
|
let reasoning = "";
|
|
1723
1829
|
let usage;
|
|
1724
1830
|
let finishReason;
|
|
1831
|
+
const streamedToolCalls = new Map;
|
|
1832
|
+
const nativeToolCalls = new NativeToolCallStreamState(requestDeclaresTools(options, request));
|
|
1725
1833
|
await consumeSSE(response, (data) => {
|
|
1726
1834
|
if (data === "[DONE]") {
|
|
1727
1835
|
return;
|
|
@@ -1730,10 +1838,14 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
|
|
|
1730
1838
|
if (!isRecord2(json)) {
|
|
1731
1839
|
return;
|
|
1732
1840
|
}
|
|
1733
|
-
const
|
|
1841
|
+
const rawDelta = pickAssistantDelta(json);
|
|
1734
1842
|
const reasoningDelta = pickAssistantReasoningDelta(json);
|
|
1735
1843
|
const chunkUsage = pickUsage(json);
|
|
1736
1844
|
const chunkFinishReason = pickFinishReason(json);
|
|
1845
|
+
collectOpenAIStreamToolCalls(json, streamedToolCalls);
|
|
1846
|
+
const nativeDelta = nativeToolCalls.push(rawDelta);
|
|
1847
|
+
const delta = nativeDelta.textDelta;
|
|
1848
|
+
const chunkToolCalls = mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeDelta.toolCalls);
|
|
1737
1849
|
usage = preferLatestUsage(usage, chunkUsage);
|
|
1738
1850
|
if (chunkFinishReason) {
|
|
1739
1851
|
finishReason = chunkFinishReason;
|
|
@@ -1745,13 +1857,21 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
|
|
|
1745
1857
|
if (reasoningDelta) {
|
|
1746
1858
|
reasoning += reasoningDelta;
|
|
1747
1859
|
}
|
|
1748
|
-
emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
|
|
1860
|
+
emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
|
|
1749
1861
|
});
|
|
1862
|
+
const tail = nativeToolCalls.flush();
|
|
1863
|
+
if (tail.textDelta) {
|
|
1864
|
+
text += tail.textDelta;
|
|
1865
|
+
callbacks.onToken?.(tail.textDelta);
|
|
1866
|
+
emitOpenAIStreamChunk(callbacks, undefined, {}, tail.textDelta, "", undefined, undefined);
|
|
1867
|
+
}
|
|
1868
|
+
const toolCalls = mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeToolCalls.calls);
|
|
1750
1869
|
const out = {
|
|
1751
1870
|
text,
|
|
1752
1871
|
reasoning: reasoning.length > 0 ? reasoning : undefined,
|
|
1753
1872
|
usage,
|
|
1754
|
-
finishReason
|
|
1873
|
+
finishReason: finishReason ?? (toolCalls.length > 0 ? "tool_calls" : undefined),
|
|
1874
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
1755
1875
|
};
|
|
1756
1876
|
callbacks.onComplete?.(out);
|
|
1757
1877
|
return out;
|
|
@@ -1861,15 +1981,16 @@ function buildResponsesMCPResult(state, text, raw) {
|
|
|
1861
1981
|
toolExecutions: state.toolExecutions.length > 0 ? state.toolExecutions : undefined
|
|
1862
1982
|
};
|
|
1863
1983
|
}
|
|
1864
|
-
function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason) {
|
|
1865
|
-
if (delta || reasoningDelta || usage || finishReason) {
|
|
1984
|
+
function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason, toolCalls) {
|
|
1985
|
+
if (delta || reasoningDelta || usage || finishReason || toolCalls) {
|
|
1866
1986
|
callbacks.onChunk?.({
|
|
1867
1987
|
textDelta: delta,
|
|
1868
1988
|
reasoningDelta: reasoningDelta || undefined,
|
|
1869
1989
|
...round !== undefined ? { turnIndex: round } : {},
|
|
1870
1990
|
raw,
|
|
1871
1991
|
usage,
|
|
1872
|
-
finishReason
|
|
1992
|
+
finishReason,
|
|
1993
|
+
toolCalls
|
|
1873
1994
|
});
|
|
1874
1995
|
}
|
|
1875
1996
|
}
|
|
@@ -1936,7 +2057,7 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
|
|
|
1936
2057
|
parallel_tool_calls: request.parallelToolCalls
|
|
1937
2058
|
}));
|
|
1938
2059
|
lastPayload = payload;
|
|
1939
|
-
aggregatedUsage =
|
|
2060
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, pickUsage(payload));
|
|
1940
2061
|
finishReason = pickFinishReason(payload);
|
|
1941
2062
|
const assistantMessage = pickAssistantMessage(payload);
|
|
1942
2063
|
const calledTools = pickChatToolCalls(payload);
|
|
@@ -2016,7 +2137,7 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
|
|
|
2016
2137
|
parallel_tool_calls: request.parallelToolCalls
|
|
2017
2138
|
}));
|
|
2018
2139
|
state.lastPayload = payload;
|
|
2019
|
-
state.aggregatedUsage =
|
|
2140
|
+
state.aggregatedUsage = mergeUsage2(state.aggregatedUsage, pickUsage(payload));
|
|
2020
2141
|
state.finishReason = pickResponsesFinishReason(payload) ?? state.finishReason;
|
|
2021
2142
|
pushReasoningBlock(state.reasoningBlocks, round, pickResponsesReasoning(payload));
|
|
2022
2143
|
const providerToolCalls = pickResponsesToolCalls(payload);
|
|
@@ -2076,6 +2197,7 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
2076
2197
|
let roundUsage;
|
|
2077
2198
|
let roundFinishReason;
|
|
2078
2199
|
const streamedToolCalls = new Map;
|
|
2200
|
+
const nativeToolCalls = new NativeToolCallStreamState;
|
|
2079
2201
|
let reasoningFieldName;
|
|
2080
2202
|
await consumeSSE(response, (data) => {
|
|
2081
2203
|
if (data === "[DONE]") {
|
|
@@ -2086,10 +2208,12 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
2086
2208
|
return;
|
|
2087
2209
|
}
|
|
2088
2210
|
lastPayload = json;
|
|
2089
|
-
const
|
|
2211
|
+
const rawDelta = pickAssistantDelta(json);
|
|
2090
2212
|
const reasoningDelta = pickAssistantReasoningDelta(json);
|
|
2091
2213
|
const chunkUsage = pickUsage(json);
|
|
2092
2214
|
const chunkFinishReason = pickFinishReason(json);
|
|
2215
|
+
const nativeDelta = nativeToolCalls.push(rawDelta);
|
|
2216
|
+
const delta = nativeDelta.textDelta;
|
|
2093
2217
|
collectOpenAIStreamToolCalls(json, streamedToolCalls);
|
|
2094
2218
|
roundUsage = preferLatestUsage(roundUsage, chunkUsage);
|
|
2095
2219
|
if (chunkFinishReason) {
|
|
@@ -2103,13 +2227,21 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
2103
2227
|
roundReasoning += reasoningDelta;
|
|
2104
2228
|
reasoningFieldName ??= pickAssistantReasoningDeltaFieldName(json);
|
|
2105
2229
|
}
|
|
2106
|
-
|
|
2230
|
+
const streamedSnapshot = buildOpenAIStreamToolCalls(streamedToolCalls);
|
|
2231
|
+
const chunkToolCalls = nativeDelta.toolCalls.length > 0 ? mergeToolCalls(streamedSnapshot, nativeDelta.toolCalls) : streamedSnapshot.length > 0 ? streamedSnapshot : undefined;
|
|
2232
|
+
emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls);
|
|
2107
2233
|
});
|
|
2108
|
-
|
|
2234
|
+
const tail = nativeToolCalls.flush();
|
|
2235
|
+
if (tail.textDelta) {
|
|
2236
|
+
roundText += tail.textDelta;
|
|
2237
|
+
callbacks.onToken?.(tail.textDelta);
|
|
2238
|
+
emitOpenAIStreamChunk(callbacks, round, {}, tail.textDelta, "", undefined, undefined);
|
|
2239
|
+
}
|
|
2240
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, roundUsage);
|
|
2109
2241
|
if (roundFinishReason) {
|
|
2110
2242
|
finishReason = roundFinishReason;
|
|
2111
2243
|
}
|
|
2112
|
-
const calledTools = buildOpenAIStreamToolCalls(streamedToolCalls);
|
|
2244
|
+
const calledTools = mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeToolCalls.calls);
|
|
2113
2245
|
pushReasoningBlock(reasoningBlocks, round, roundReasoning);
|
|
2114
2246
|
request.onTurnTransition?.({
|
|
2115
2247
|
turnIndex: round,
|
|
@@ -2196,6 +2328,7 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
|
|
|
2196
2328
|
let usage;
|
|
2197
2329
|
let finishReason;
|
|
2198
2330
|
let lastPayload;
|
|
2331
|
+
const streamedToolCalls = new Map;
|
|
2199
2332
|
await consumeSSE(response, (data) => {
|
|
2200
2333
|
if (data === "[DONE]") {
|
|
2201
2334
|
return;
|
|
@@ -2211,6 +2344,8 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
|
|
|
2211
2344
|
const delta = pickResponsesStreamTextDelta(json);
|
|
2212
2345
|
const chunkUsage = pickResponsesStreamUsage(json);
|
|
2213
2346
|
const chunkFinishReason = pickResponsesStreamFinishReason(json);
|
|
2347
|
+
collectResponsesStreamToolCalls(json, streamedToolCalls);
|
|
2348
|
+
const chunkToolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
|
|
2214
2349
|
usage = preferLatestUsage(usage, chunkUsage);
|
|
2215
2350
|
if (chunkFinishReason) {
|
|
2216
2351
|
finishReason = chunkFinishReason;
|
|
@@ -2219,14 +2354,16 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
|
|
|
2219
2354
|
text += delta;
|
|
2220
2355
|
callbacks.onToken?.(delta);
|
|
2221
2356
|
}
|
|
2222
|
-
emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason);
|
|
2357
|
+
emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
|
|
2223
2358
|
});
|
|
2224
2359
|
const finalPayload = lastPayload ?? {};
|
|
2360
|
+
const toolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
|
|
2225
2361
|
const out = {
|
|
2226
2362
|
text: text.length > 0 ? text : pickResponsesText(finalPayload) || pickAssistantText(finalPayload),
|
|
2227
2363
|
raw: finalPayload,
|
|
2228
2364
|
usage: preferLatestUsage(usage, pickUsage(finalPayload)),
|
|
2229
|
-
finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload)
|
|
2365
|
+
finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload),
|
|
2366
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
2230
2367
|
};
|
|
2231
2368
|
callbacks.onComplete?.(out);
|
|
2232
2369
|
return out;
|
|
@@ -2285,10 +2422,11 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
|
|
|
2285
2422
|
if (reasoningDelta) {
|
|
2286
2423
|
roundReasoning += reasoningDelta;
|
|
2287
2424
|
}
|
|
2288
|
-
|
|
2425
|
+
const chunkToolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
|
|
2426
|
+
emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
|
|
2289
2427
|
});
|
|
2290
2428
|
const resolvedRoundUsage = preferLatestUsage(roundUsage, roundPayload ? pickUsage(roundPayload) : undefined);
|
|
2291
|
-
state.aggregatedUsage =
|
|
2429
|
+
state.aggregatedUsage = mergeUsage2(state.aggregatedUsage, resolvedRoundUsage);
|
|
2292
2430
|
if (roundFinishReason) {
|
|
2293
2431
|
state.finishReason = roundFinishReason;
|
|
2294
2432
|
} else if (roundPayload) {
|
|
@@ -2542,43 +2680,204 @@ function pickAssistantReasoningDeltaFieldName(payload) {
|
|
|
2542
2680
|
}
|
|
2543
2681
|
return;
|
|
2544
2682
|
}
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2683
|
+
var NATIVE_TOOL_CALL_OPEN = "<tool_call";
|
|
2684
|
+
var NATIVE_TOOL_CALL_CLOSE = "</tool_call>";
|
|
2685
|
+
|
|
2686
|
+
class NativeToolCallStreamState {
|
|
2687
|
+
enabled;
|
|
2688
|
+
calls = [];
|
|
2689
|
+
pending = "";
|
|
2690
|
+
constructor(enabled = true) {
|
|
2691
|
+
this.enabled = enabled;
|
|
2692
|
+
}
|
|
2693
|
+
push(delta) {
|
|
2694
|
+
if (!delta || !this.enabled) {
|
|
2695
|
+
return { textDelta: delta, toolCalls: [] };
|
|
2696
|
+
}
|
|
2697
|
+
this.pending += delta;
|
|
2698
|
+
return this.drain(false);
|
|
2699
|
+
}
|
|
2700
|
+
flush() {
|
|
2701
|
+
return this.enabled ? this.drain(true) : { textDelta: "", toolCalls: [] };
|
|
2702
|
+
}
|
|
2703
|
+
drain(flush) {
|
|
2704
|
+
let textDelta = "";
|
|
2705
|
+
const toolCalls = [];
|
|
2706
|
+
while (this.pending.length > 0) {
|
|
2707
|
+
const openIndex = this.pending.indexOf(NATIVE_TOOL_CALL_OPEN);
|
|
2708
|
+
if (openIndex < 0) {
|
|
2709
|
+
const keep = flush ? 0 : nativeToolCallPrefixSuffixLength(this.pending);
|
|
2710
|
+
const emitLength = this.pending.length - keep;
|
|
2711
|
+
if (emitLength > 0) {
|
|
2712
|
+
textDelta += this.pending.slice(0, emitLength);
|
|
2713
|
+
this.pending = this.pending.slice(emitLength);
|
|
2714
|
+
}
|
|
2715
|
+
break;
|
|
2716
|
+
}
|
|
2717
|
+
if (openIndex > 0) {
|
|
2718
|
+
textDelta += this.pending.slice(0, openIndex);
|
|
2719
|
+
this.pending = this.pending.slice(openIndex);
|
|
2720
|
+
continue;
|
|
2721
|
+
}
|
|
2722
|
+
const closeIndex = this.pending.indexOf(NATIVE_TOOL_CALL_CLOSE);
|
|
2723
|
+
if (closeIndex < 0) {
|
|
2724
|
+
if (flush) {
|
|
2725
|
+
this.pending = "";
|
|
2726
|
+
}
|
|
2727
|
+
break;
|
|
2728
|
+
}
|
|
2729
|
+
const blockEnd = closeIndex + NATIVE_TOOL_CALL_CLOSE.length;
|
|
2730
|
+
const call = parseNativeToolCallBlock(this.pending.slice(0, blockEnd), this.calls.length);
|
|
2731
|
+
if (call) {
|
|
2732
|
+
this.calls.push(call);
|
|
2733
|
+
toolCalls.push(call);
|
|
2734
|
+
}
|
|
2735
|
+
this.pending = this.pending.slice(blockEnd);
|
|
2736
|
+
}
|
|
2737
|
+
return { textDelta, toolCalls };
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
function requestDeclaresTools(options, request) {
|
|
2741
|
+
const hasTools = (body) => Array.isArray(body?.tools) && body.tools.length > 0;
|
|
2742
|
+
return hasTools(request.body) || hasTools(options.defaultBody);
|
|
2743
|
+
}
|
|
2744
|
+
var NATIVE_FUNCTION_PATTERN = /<function=([^>\s]+)\s*>([\s\S]*?)<\/function>/;
|
|
2745
|
+
var NATIVE_PARAMETER_PATTERN = /<parameter=([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
|
|
2746
|
+
function parseNativeToolCallBlock(block, index) {
|
|
2747
|
+
const inner = extractNativeToolCallInner(block);
|
|
2748
|
+
if (inner === undefined) {
|
|
2548
2749
|
return;
|
|
2549
2750
|
}
|
|
2550
|
-
|
|
2551
|
-
|
|
2751
|
+
return parseNativeJsonToolCall(inner, index) ?? parseNativeXmlToolCall(inner, index);
|
|
2752
|
+
}
|
|
2753
|
+
function extractNativeToolCallInner(block) {
|
|
2754
|
+
const openEnd = block.indexOf(">");
|
|
2755
|
+
const closeStart = block.lastIndexOf(NATIVE_TOOL_CALL_CLOSE);
|
|
2756
|
+
if (openEnd < 0 || closeStart < 0 || closeStart <= openEnd) {
|
|
2552
2757
|
return;
|
|
2553
2758
|
}
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2759
|
+
return block.slice(openEnd + 1, closeStart).trim();
|
|
2760
|
+
}
|
|
2761
|
+
function parseNativeXmlToolCall(inner, index) {
|
|
2762
|
+
const functionMatch = NATIVE_FUNCTION_PATTERN.exec(inner);
|
|
2763
|
+
const functionName = functionMatch?.[1];
|
|
2764
|
+
const functionBody = functionMatch?.[2];
|
|
2765
|
+
if (!functionName || functionBody === undefined) {
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
const args = {};
|
|
2769
|
+
for (const [, key, rawValue] of functionBody.matchAll(NATIVE_PARAMETER_PATTERN)) {
|
|
2770
|
+
if (key && rawValue !== undefined) {
|
|
2771
|
+
args[key] = coerceNativeParameterValue(rawValue.trim());
|
|
2557
2772
|
}
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2773
|
+
}
|
|
2774
|
+
return {
|
|
2775
|
+
id: `call_native_${index}`,
|
|
2776
|
+
type: "function",
|
|
2777
|
+
name: functionName,
|
|
2778
|
+
arguments: JSON.stringify(args)
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
function coerceNativeParameterValue(value) {
|
|
2782
|
+
if (value.length === 0) {
|
|
2783
|
+
return "";
|
|
2784
|
+
}
|
|
2785
|
+
const parsed = safeJSONParse(value);
|
|
2786
|
+
if (parsed === null) {
|
|
2787
|
+
return value === "null" ? null : value;
|
|
2788
|
+
}
|
|
2789
|
+
return parsed;
|
|
2790
|
+
}
|
|
2791
|
+
function nativeToolCallPrefixSuffixLength(value) {
|
|
2792
|
+
const max = Math.min(value.length, NATIVE_TOOL_CALL_OPEN.length - 1);
|
|
2793
|
+
for (let length = max;length > 0; length -= 1) {
|
|
2794
|
+
if (NATIVE_TOOL_CALL_OPEN.startsWith(value.slice(-length))) {
|
|
2795
|
+
return length;
|
|
2567
2796
|
}
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2797
|
+
}
|
|
2798
|
+
return 0;
|
|
2799
|
+
}
|
|
2800
|
+
function parseNativeJsonToolCall(inner, index) {
|
|
2801
|
+
const parsed = safeJSONParse(inner);
|
|
2802
|
+
if (!isRecord2(parsed)) {
|
|
2803
|
+
return;
|
|
2804
|
+
}
|
|
2805
|
+
const name = pickString(parsed.name) ?? pickString(parsed.function);
|
|
2806
|
+
if (!name) {
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
const rawArguments = parsed.arguments ?? parsed.parameters ?? {};
|
|
2810
|
+
const args = typeof rawArguments === "string" ? rawArguments : JSON.stringify(rawArguments);
|
|
2811
|
+
return {
|
|
2812
|
+
id: pickString(parsed.id) ?? `call_native_${index}`,
|
|
2813
|
+
type: "function",
|
|
2814
|
+
name,
|
|
2815
|
+
arguments: args
|
|
2816
|
+
};
|
|
2817
|
+
}
|
|
2818
|
+
function mergeToolCalls(...groups) {
|
|
2819
|
+
const merged = [];
|
|
2820
|
+
const seen = new Set;
|
|
2821
|
+
for (const group of groups) {
|
|
2822
|
+
for (const call of group) {
|
|
2823
|
+
const key = call.id || `${call.name ?? ""}:${String(call.arguments ?? "")}`;
|
|
2824
|
+
if (seen.has(key)) {
|
|
2825
|
+
continue;
|
|
2826
|
+
}
|
|
2827
|
+
seen.add(key);
|
|
2828
|
+
merged.push(call);
|
|
2571
2829
|
}
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2830
|
+
}
|
|
2831
|
+
return merged;
|
|
2832
|
+
}
|
|
2833
|
+
function collectOpenAIStreamToolCalls(payload, state) {
|
|
2834
|
+
const choices = payload.choices;
|
|
2835
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
for (const choice of choices) {
|
|
2839
|
+
if (!isRecord2(choice)) {
|
|
2840
|
+
continue;
|
|
2576
2841
|
}
|
|
2577
|
-
const
|
|
2578
|
-
|
|
2579
|
-
|
|
2842
|
+
const delta = isRecord2(choice.delta) ? choice.delta : undefined;
|
|
2843
|
+
const message = isRecord2(choice.message) ? choice.message : undefined;
|
|
2844
|
+
const toolCalls = Array.isArray(delta?.tool_calls) ? delta.tool_calls : Array.isArray(message?.tool_calls) ? message.tool_calls : Array.isArray(choice.tool_calls) ? choice.tool_calls : undefined;
|
|
2845
|
+
if (!toolCalls) {
|
|
2846
|
+
continue;
|
|
2847
|
+
}
|
|
2848
|
+
for (const rawToolCall of toolCalls) {
|
|
2849
|
+
if (!isRecord2(rawToolCall)) {
|
|
2850
|
+
continue;
|
|
2851
|
+
}
|
|
2852
|
+
const index = toFiniteNumber(rawToolCall.index);
|
|
2853
|
+
const toolIndex = index !== undefined ? Math.floor(index) : state.size;
|
|
2854
|
+
const existing = state.get(toolIndex) ?? {
|
|
2855
|
+
index: toolIndex,
|
|
2856
|
+
argumentsText: ""
|
|
2857
|
+
};
|
|
2858
|
+
const id = pickString(rawToolCall.id);
|
|
2859
|
+
if (id) {
|
|
2860
|
+
existing.id = id;
|
|
2861
|
+
}
|
|
2862
|
+
const type = pickString(rawToolCall.type);
|
|
2863
|
+
if (type) {
|
|
2864
|
+
existing.type = type;
|
|
2865
|
+
}
|
|
2866
|
+
const functionCall = isRecord2(rawToolCall.function) ? rawToolCall.function : undefined;
|
|
2867
|
+
const name = pickString(functionCall?.name);
|
|
2868
|
+
if (name) {
|
|
2869
|
+
existing.name = `${existing.name ?? ""}${name}`;
|
|
2870
|
+
}
|
|
2871
|
+
const argumentsDelta = pickString(functionCall?.arguments);
|
|
2872
|
+
if (argumentsDelta) {
|
|
2873
|
+
if (message?.tool_calls === toolCalls || choice.tool_calls === toolCalls) {
|
|
2874
|
+
existing.argumentsText = argumentsDelta;
|
|
2875
|
+
} else {
|
|
2876
|
+
existing.argumentsText += argumentsDelta;
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
state.set(toolIndex, existing);
|
|
2580
2880
|
}
|
|
2581
|
-
state.set(toolIndex, existing);
|
|
2582
2881
|
}
|
|
2583
2882
|
}
|
|
2584
2883
|
function buildOpenAIStreamToolCalls(state) {
|
|
@@ -2929,6 +3228,7 @@ async function streamPassThrough(options, fetcher, path, request, callbacks) {
|
|
|
2929
3228
|
let text = "";
|
|
2930
3229
|
let usage;
|
|
2931
3230
|
let finishReason;
|
|
3231
|
+
const streamedToolCalls = new Map;
|
|
2932
3232
|
await consumeSSE(response, (data) => {
|
|
2933
3233
|
if (data === "[DONE]") {
|
|
2934
3234
|
return;
|
|
@@ -2940,6 +3240,7 @@ async function streamPassThrough(options, fetcher, path, request, callbacks) {
|
|
|
2940
3240
|
const delta = pickAnthropicDelta(json);
|
|
2941
3241
|
const chunkUsage = pickUsage2(json);
|
|
2942
3242
|
const chunkFinishReason = pickFinishReason2(json);
|
|
3243
|
+
collectAnthropicStreamToolCalls(json, streamedToolCalls);
|
|
2943
3244
|
usage = preferLatestUsage(usage, chunkUsage);
|
|
2944
3245
|
if (chunkFinishReason) {
|
|
2945
3246
|
finishReason = chunkFinishReason;
|
|
@@ -2948,16 +3249,25 @@ async function streamPassThrough(options, fetcher, path, request, callbacks) {
|
|
|
2948
3249
|
text += delta;
|
|
2949
3250
|
callbacks.onToken?.(delta);
|
|
2950
3251
|
}
|
|
2951
|
-
|
|
3252
|
+
const streamedSnapshot = buildAnthropicStreamToolCalls(streamedToolCalls);
|
|
3253
|
+
const chunkToolCalls = streamedSnapshot.length > 0 ? streamedSnapshot : undefined;
|
|
3254
|
+
if (delta || chunkUsage || chunkFinishReason || chunkToolCalls) {
|
|
2952
3255
|
callbacks.onChunk?.({
|
|
2953
3256
|
textDelta: delta,
|
|
2954
3257
|
raw: json,
|
|
2955
3258
|
usage: chunkUsage,
|
|
2956
|
-
finishReason: chunkFinishReason
|
|
3259
|
+
finishReason: chunkFinishReason,
|
|
3260
|
+
toolCalls: chunkToolCalls
|
|
2957
3261
|
});
|
|
2958
3262
|
}
|
|
2959
3263
|
});
|
|
2960
|
-
const
|
|
3264
|
+
const toolCalls = buildAnthropicStreamToolCalls(streamedToolCalls);
|
|
3265
|
+
const out = {
|
|
3266
|
+
text,
|
|
3267
|
+
usage,
|
|
3268
|
+
finishReason: finishReason ?? (toolCalls.length > 0 ? "tool_use" : undefined),
|
|
3269
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
3270
|
+
};
|
|
2961
3271
|
callbacks.onComplete?.(out);
|
|
2962
3272
|
return out;
|
|
2963
3273
|
}
|
|
@@ -3026,7 +3336,7 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
|
|
|
3026
3336
|
}
|
|
3027
3337
|
const payload = await response.json();
|
|
3028
3338
|
lastPayload = payload;
|
|
3029
|
-
aggregatedUsage =
|
|
3339
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, pickUsage2(payload));
|
|
3030
3340
|
finishReason = pickFinishReason2(payload);
|
|
3031
3341
|
const content = Array.isArray(payload.content) ? payload.content : [];
|
|
3032
3342
|
const calledTools = pickAnthropicToolCalls(payload).filter((call) => call.type === "function");
|
|
@@ -3135,19 +3445,22 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
|
|
|
3135
3445
|
if (reasoningDelta) {
|
|
3136
3446
|
roundReasoning += reasoningDelta;
|
|
3137
3447
|
}
|
|
3138
|
-
|
|
3448
|
+
const streamedSnapshot = buildAnthropicStreamToolCalls(streamedToolCalls);
|
|
3449
|
+
const chunkToolCalls = streamedSnapshot.length > 0 ? streamedSnapshot : undefined;
|
|
3450
|
+
if (delta || reasoningDelta || chunkUsage || chunkFinishReason || chunkToolCalls) {
|
|
3139
3451
|
const chunk = {
|
|
3140
3452
|
textDelta: delta,
|
|
3141
3453
|
reasoningDelta: reasoningDelta || undefined,
|
|
3142
3454
|
turnIndex: round,
|
|
3143
3455
|
raw: json,
|
|
3144
3456
|
usage: chunkUsage,
|
|
3145
|
-
finishReason: chunkFinishReason
|
|
3457
|
+
finishReason: chunkFinishReason,
|
|
3458
|
+
toolCalls: chunkToolCalls
|
|
3146
3459
|
};
|
|
3147
3460
|
callbacks.onChunk?.(chunk);
|
|
3148
3461
|
}
|
|
3149
3462
|
});
|
|
3150
|
-
aggregatedUsage =
|
|
3463
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, roundUsage);
|
|
3151
3464
|
if (roundFinishReason) {
|
|
3152
3465
|
finishReason = roundFinishReason;
|
|
3153
3466
|
}
|
|
@@ -3806,95 +4119,6 @@ function withToolTimeout(client, toolTimeoutMs) {
|
|
|
3806
4119
|
function applyToolTimeout(clients, toolTimeoutMs) {
|
|
3807
4120
|
return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
|
|
3808
4121
|
}
|
|
3809
|
-
// src/generate-output.ts
|
|
3810
|
-
var RE_THINK_TAGS = /<\/?think\s*>/gi;
|
|
3811
|
-
function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
|
|
3812
|
-
const sanitized = sanitizeThink(text);
|
|
3813
|
-
const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
|
|
3814
|
-
const reasoning = joinReasoningSegments([
|
|
3815
|
-
dedicatedReasoning,
|
|
3816
|
-
...sanitized.thinkBlocks.map((block) => block.content)
|
|
3817
|
-
]);
|
|
3818
|
-
return {
|
|
3819
|
-
text: visibleText,
|
|
3820
|
-
reasoning,
|
|
3821
|
-
reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
|
|
3822
|
-
thinkBlocks: sanitized.thinkBlocks,
|
|
3823
|
-
parseSource: composeParseSource(visibleText, reasoning)
|
|
3824
|
-
};
|
|
3825
|
-
}
|
|
3826
|
-
function normalizeReasoningBlocks(blocks) {
|
|
3827
|
-
if (!Array.isArray(blocks)) {
|
|
3828
|
-
return;
|
|
3829
|
-
}
|
|
3830
|
-
const normalized = blocks.map((block) => ({
|
|
3831
|
-
turnIndex: block.turnIndex,
|
|
3832
|
-
text: block.text.replace(RE_THINK_TAGS, "").trim()
|
|
3833
|
-
})).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
|
|
3834
|
-
return normalized.length > 0 ? normalized : undefined;
|
|
3835
|
-
}
|
|
3836
|
-
function appendReasoningBlock(blocks, transition) {
|
|
3837
|
-
const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
|
|
3838
|
-
if (!text) {
|
|
3839
|
-
return blocks;
|
|
3840
|
-
}
|
|
3841
|
-
const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
|
|
3842
|
-
return normalizeReasoningBlocks(next);
|
|
3843
|
-
}
|
|
3844
|
-
function composeParseSource(text, reasoning) {
|
|
3845
|
-
if (typeof reasoning !== "string" || reasoning.length === 0) {
|
|
3846
|
-
return text;
|
|
3847
|
-
}
|
|
3848
|
-
const sanitized = reasoning.replace(RE_THINK_TAGS, "");
|
|
3849
|
-
if (sanitized.length === 0) {
|
|
3850
|
-
return text;
|
|
3851
|
-
}
|
|
3852
|
-
return `<think>${sanitized}</think>${text}`;
|
|
3853
|
-
}
|
|
3854
|
-
function aggregateUsage(attempts) {
|
|
3855
|
-
let usage;
|
|
3856
|
-
for (const attempt of attempts) {
|
|
3857
|
-
usage = mergeUsage2(usage, attempt.usage);
|
|
3858
|
-
}
|
|
3859
|
-
return usage;
|
|
3860
|
-
}
|
|
3861
|
-
function mergeUsage2(base, next) {
|
|
3862
|
-
if (!base && !next) {
|
|
3863
|
-
return;
|
|
3864
|
-
}
|
|
3865
|
-
return {
|
|
3866
|
-
inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
|
|
3867
|
-
outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
|
|
3868
|
-
totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
|
|
3869
|
-
cost: (base?.cost ?? 0) + (next?.cost ?? 0)
|
|
3870
|
-
};
|
|
3871
|
-
}
|
|
3872
|
-
function joinReasoningSegments(parts) {
|
|
3873
|
-
return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
|
|
3874
|
-
|
|
3875
|
-
`);
|
|
3876
|
-
}
|
|
3877
|
-
function stripThinkBlocks(text, thinkBlocks) {
|
|
3878
|
-
if (thinkBlocks.length === 0) {
|
|
3879
|
-
return text;
|
|
3880
|
-
}
|
|
3881
|
-
let output = "";
|
|
3882
|
-
let cursor = 0;
|
|
3883
|
-
for (const block of thinkBlocks) {
|
|
3884
|
-
output += text.slice(cursor, block.start);
|
|
3885
|
-
cursor = block.end;
|
|
3886
|
-
}
|
|
3887
|
-
output += text.slice(cursor);
|
|
3888
|
-
return output;
|
|
3889
|
-
}
|
|
3890
|
-
function toStreamDataFingerprint(value) {
|
|
3891
|
-
try {
|
|
3892
|
-
return JSON.stringify(value);
|
|
3893
|
-
} catch {
|
|
3894
|
-
return "__unserializable__";
|
|
3895
|
-
}
|
|
3896
|
-
}
|
|
3897
|
-
|
|
3898
4122
|
// src/utils/debug-colors.ts
|
|
3899
4123
|
var ANSI = {
|
|
3900
4124
|
reset: "\x1B[0m",
|
|
@@ -4056,13 +4280,15 @@ async function callModel(adapter, options) {
|
|
|
4056
4280
|
if (!done && fingerprint === lastSnapshotFingerprint) {
|
|
4057
4281
|
return;
|
|
4058
4282
|
}
|
|
4283
|
+
const stableText = done ? normalized2.text : withoutTrailingThinkTagPrefix(normalized2.text);
|
|
4284
|
+
const stableReasoning = done ? normalized2.reasoning : withoutTrailingThinkTagPrefix(normalized2.reasoning);
|
|
4059
4285
|
const delta = {
|
|
4060
|
-
text:
|
|
4061
|
-
reasoning:
|
|
4286
|
+
text: stableText.startsWith(previousSnapshotText) ? stableText.slice(previousSnapshotText.length) : "",
|
|
4287
|
+
reasoning: stableReasoning.startsWith(previousSnapshotReasoning) ? stableReasoning.slice(previousSnapshotReasoning.length) : ""
|
|
4062
4288
|
};
|
|
4063
4289
|
lastSnapshotFingerprint = fingerprint;
|
|
4064
|
-
previousSnapshotText =
|
|
4065
|
-
previousSnapshotReasoning =
|
|
4290
|
+
previousSnapshotText = stableText;
|
|
4291
|
+
previousSnapshotReasoning = stableReasoning;
|
|
4066
4292
|
options.stream.onData?.({
|
|
4067
4293
|
delta,
|
|
4068
4294
|
snapshot,
|