extrait 0.7.3 → 0.7.4
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 +349 -139
- package/dist/index.d.ts +1 -0
- package/dist/index.js +349 -139
- package/package.json +2 -2
|
@@ -7,4 +7,15 @@ export declare function aggregateUsage<T extends {
|
|
|
7
7
|
usage?: LLMUsage;
|
|
8
8
|
}>(attempts: T[]): LLMUsage | undefined;
|
|
9
9
|
export declare function mergeUsage(base: LLMUsage | undefined, next: LLMUsage | undefined): LLMUsage | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Drops a trailing run of `value` that could be the start of a `<think>` /
|
|
12
|
+
* `</think>` tag whose remaining characters have not streamed in yet (e.g. a
|
|
13
|
+
* chunk ending in `<th`). Streaming consumers diff successive snapshots to emit
|
|
14
|
+
* deltas; without this, a partial tag is emitted as a delta and then can never
|
|
15
|
+
* be retracted once it resolves into a real tag that sanitization removes.
|
|
16
|
+
*
|
|
17
|
+
* Only safe for incremental streaming snapshots — never apply it to a final
|
|
18
|
+
* result, where a legitimate trailing `<` must survive.
|
|
19
|
+
*/
|
|
20
|
+
export declare function withoutTrailingThinkTagPrefix(value: string): string;
|
|
10
21
|
export declare function toStreamDataFingerprint(value: unknown): string;
|
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,20 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
2103
2227
|
roundReasoning += reasoningDelta;
|
|
2104
2228
|
reasoningFieldName ??= pickAssistantReasoningDeltaFieldName(json);
|
|
2105
2229
|
}
|
|
2106
|
-
|
|
2230
|
+
const chunkToolCalls = nativeDelta.toolCalls.length > 0 ? mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeDelta.toolCalls) : undefined;
|
|
2231
|
+
emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls);
|
|
2107
2232
|
});
|
|
2108
|
-
|
|
2233
|
+
const tail = nativeToolCalls.flush();
|
|
2234
|
+
if (tail.textDelta) {
|
|
2235
|
+
roundText += tail.textDelta;
|
|
2236
|
+
callbacks.onToken?.(tail.textDelta);
|
|
2237
|
+
emitOpenAIStreamChunk(callbacks, round, {}, tail.textDelta, "", undefined, undefined);
|
|
2238
|
+
}
|
|
2239
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, roundUsage);
|
|
2109
2240
|
if (roundFinishReason) {
|
|
2110
2241
|
finishReason = roundFinishReason;
|
|
2111
2242
|
}
|
|
2112
|
-
const calledTools = buildOpenAIStreamToolCalls(streamedToolCalls);
|
|
2243
|
+
const calledTools = mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeToolCalls.calls);
|
|
2113
2244
|
pushReasoningBlock(reasoningBlocks, round, roundReasoning);
|
|
2114
2245
|
request.onTurnTransition?.({
|
|
2115
2246
|
turnIndex: round,
|
|
@@ -2196,6 +2327,7 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
|
|
|
2196
2327
|
let usage;
|
|
2197
2328
|
let finishReason;
|
|
2198
2329
|
let lastPayload;
|
|
2330
|
+
const streamedToolCalls = new Map;
|
|
2199
2331
|
await consumeSSE(response, (data) => {
|
|
2200
2332
|
if (data === "[DONE]") {
|
|
2201
2333
|
return;
|
|
@@ -2211,6 +2343,8 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
|
|
|
2211
2343
|
const delta = pickResponsesStreamTextDelta(json);
|
|
2212
2344
|
const chunkUsage = pickResponsesStreamUsage(json);
|
|
2213
2345
|
const chunkFinishReason = pickResponsesStreamFinishReason(json);
|
|
2346
|
+
collectResponsesStreamToolCalls(json, streamedToolCalls);
|
|
2347
|
+
const chunkToolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
|
|
2214
2348
|
usage = preferLatestUsage(usage, chunkUsage);
|
|
2215
2349
|
if (chunkFinishReason) {
|
|
2216
2350
|
finishReason = chunkFinishReason;
|
|
@@ -2219,14 +2353,16 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
|
|
|
2219
2353
|
text += delta;
|
|
2220
2354
|
callbacks.onToken?.(delta);
|
|
2221
2355
|
}
|
|
2222
|
-
emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason);
|
|
2356
|
+
emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
|
|
2223
2357
|
});
|
|
2224
2358
|
const finalPayload = lastPayload ?? {};
|
|
2359
|
+
const toolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
|
|
2225
2360
|
const out = {
|
|
2226
2361
|
text: text.length > 0 ? text : pickResponsesText(finalPayload) || pickAssistantText(finalPayload),
|
|
2227
2362
|
raw: finalPayload,
|
|
2228
2363
|
usage: preferLatestUsage(usage, pickUsage(finalPayload)),
|
|
2229
|
-
finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload)
|
|
2364
|
+
finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload),
|
|
2365
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
2230
2366
|
};
|
|
2231
2367
|
callbacks.onComplete?.(out);
|
|
2232
2368
|
return out;
|
|
@@ -2288,7 +2424,7 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
|
|
|
2288
2424
|
emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
|
|
2289
2425
|
});
|
|
2290
2426
|
const resolvedRoundUsage = preferLatestUsage(roundUsage, roundPayload ? pickUsage(roundPayload) : undefined);
|
|
2291
|
-
state.aggregatedUsage =
|
|
2427
|
+
state.aggregatedUsage = mergeUsage2(state.aggregatedUsage, resolvedRoundUsage);
|
|
2292
2428
|
if (roundFinishReason) {
|
|
2293
2429
|
state.finishReason = roundFinishReason;
|
|
2294
2430
|
} else if (roundPayload) {
|
|
@@ -2542,43 +2678,204 @@ function pickAssistantReasoningDeltaFieldName(payload) {
|
|
|
2542
2678
|
}
|
|
2543
2679
|
return;
|
|
2544
2680
|
}
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2681
|
+
var NATIVE_TOOL_CALL_OPEN = "<tool_call";
|
|
2682
|
+
var NATIVE_TOOL_CALL_CLOSE = "</tool_call>";
|
|
2683
|
+
|
|
2684
|
+
class NativeToolCallStreamState {
|
|
2685
|
+
enabled;
|
|
2686
|
+
calls = [];
|
|
2687
|
+
pending = "";
|
|
2688
|
+
constructor(enabled = true) {
|
|
2689
|
+
this.enabled = enabled;
|
|
2690
|
+
}
|
|
2691
|
+
push(delta) {
|
|
2692
|
+
if (!delta || !this.enabled) {
|
|
2693
|
+
return { textDelta: delta, toolCalls: [] };
|
|
2694
|
+
}
|
|
2695
|
+
this.pending += delta;
|
|
2696
|
+
return this.drain(false);
|
|
2697
|
+
}
|
|
2698
|
+
flush() {
|
|
2699
|
+
return this.enabled ? this.drain(true) : { textDelta: "", toolCalls: [] };
|
|
2700
|
+
}
|
|
2701
|
+
drain(flush) {
|
|
2702
|
+
let textDelta = "";
|
|
2703
|
+
const toolCalls = [];
|
|
2704
|
+
while (this.pending.length > 0) {
|
|
2705
|
+
const openIndex = this.pending.indexOf(NATIVE_TOOL_CALL_OPEN);
|
|
2706
|
+
if (openIndex < 0) {
|
|
2707
|
+
const keep = flush ? 0 : nativeToolCallPrefixSuffixLength(this.pending);
|
|
2708
|
+
const emitLength = this.pending.length - keep;
|
|
2709
|
+
if (emitLength > 0) {
|
|
2710
|
+
textDelta += this.pending.slice(0, emitLength);
|
|
2711
|
+
this.pending = this.pending.slice(emitLength);
|
|
2712
|
+
}
|
|
2713
|
+
break;
|
|
2714
|
+
}
|
|
2715
|
+
if (openIndex > 0) {
|
|
2716
|
+
textDelta += this.pending.slice(0, openIndex);
|
|
2717
|
+
this.pending = this.pending.slice(openIndex);
|
|
2718
|
+
continue;
|
|
2719
|
+
}
|
|
2720
|
+
const closeIndex = this.pending.indexOf(NATIVE_TOOL_CALL_CLOSE);
|
|
2721
|
+
if (closeIndex < 0) {
|
|
2722
|
+
if (flush) {
|
|
2723
|
+
this.pending = "";
|
|
2724
|
+
}
|
|
2725
|
+
break;
|
|
2726
|
+
}
|
|
2727
|
+
const blockEnd = closeIndex + NATIVE_TOOL_CALL_CLOSE.length;
|
|
2728
|
+
const call = parseNativeToolCallBlock(this.pending.slice(0, blockEnd), this.calls.length);
|
|
2729
|
+
if (call) {
|
|
2730
|
+
this.calls.push(call);
|
|
2731
|
+
toolCalls.push(call);
|
|
2732
|
+
}
|
|
2733
|
+
this.pending = this.pending.slice(blockEnd);
|
|
2734
|
+
}
|
|
2735
|
+
return { textDelta, toolCalls };
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
function requestDeclaresTools(options, request) {
|
|
2739
|
+
const hasTools = (body) => Array.isArray(body?.tools) && body.tools.length > 0;
|
|
2740
|
+
return hasTools(request.body) || hasTools(options.defaultBody);
|
|
2741
|
+
}
|
|
2742
|
+
var NATIVE_FUNCTION_PATTERN = /<function=([^>\s]+)\s*>([\s\S]*?)<\/function>/;
|
|
2743
|
+
var NATIVE_PARAMETER_PATTERN = /<parameter=([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
|
|
2744
|
+
function parseNativeToolCallBlock(block, index) {
|
|
2745
|
+
const inner = extractNativeToolCallInner(block);
|
|
2746
|
+
if (inner === undefined) {
|
|
2548
2747
|
return;
|
|
2549
2748
|
}
|
|
2550
|
-
|
|
2551
|
-
|
|
2749
|
+
return parseNativeJsonToolCall(inner, index) ?? parseNativeXmlToolCall(inner, index);
|
|
2750
|
+
}
|
|
2751
|
+
function extractNativeToolCallInner(block) {
|
|
2752
|
+
const openEnd = block.indexOf(">");
|
|
2753
|
+
const closeStart = block.lastIndexOf(NATIVE_TOOL_CALL_CLOSE);
|
|
2754
|
+
if (openEnd < 0 || closeStart < 0 || closeStart <= openEnd) {
|
|
2552
2755
|
return;
|
|
2553
2756
|
}
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2757
|
+
return block.slice(openEnd + 1, closeStart).trim();
|
|
2758
|
+
}
|
|
2759
|
+
function parseNativeXmlToolCall(inner, index) {
|
|
2760
|
+
const functionMatch = NATIVE_FUNCTION_PATTERN.exec(inner);
|
|
2761
|
+
const functionName = functionMatch?.[1];
|
|
2762
|
+
const functionBody = functionMatch?.[2];
|
|
2763
|
+
if (!functionName || functionBody === undefined) {
|
|
2764
|
+
return;
|
|
2765
|
+
}
|
|
2766
|
+
const args = {};
|
|
2767
|
+
for (const [, key, rawValue] of functionBody.matchAll(NATIVE_PARAMETER_PATTERN)) {
|
|
2768
|
+
if (key && rawValue !== undefined) {
|
|
2769
|
+
args[key] = coerceNativeParameterValue(rawValue.trim());
|
|
2557
2770
|
}
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2771
|
+
}
|
|
2772
|
+
return {
|
|
2773
|
+
id: `call_native_${index}`,
|
|
2774
|
+
type: "function",
|
|
2775
|
+
name: functionName,
|
|
2776
|
+
arguments: JSON.stringify(args)
|
|
2777
|
+
};
|
|
2778
|
+
}
|
|
2779
|
+
function coerceNativeParameterValue(value) {
|
|
2780
|
+
if (value.length === 0) {
|
|
2781
|
+
return "";
|
|
2782
|
+
}
|
|
2783
|
+
const parsed = safeJSONParse(value);
|
|
2784
|
+
if (parsed === null) {
|
|
2785
|
+
return value === "null" ? null : value;
|
|
2786
|
+
}
|
|
2787
|
+
return parsed;
|
|
2788
|
+
}
|
|
2789
|
+
function nativeToolCallPrefixSuffixLength(value) {
|
|
2790
|
+
const max = Math.min(value.length, NATIVE_TOOL_CALL_OPEN.length - 1);
|
|
2791
|
+
for (let length = max;length > 0; length -= 1) {
|
|
2792
|
+
if (NATIVE_TOOL_CALL_OPEN.startsWith(value.slice(-length))) {
|
|
2793
|
+
return length;
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
return 0;
|
|
2797
|
+
}
|
|
2798
|
+
function parseNativeJsonToolCall(inner, index) {
|
|
2799
|
+
const parsed = safeJSONParse(inner);
|
|
2800
|
+
if (!isRecord2(parsed)) {
|
|
2801
|
+
return;
|
|
2802
|
+
}
|
|
2803
|
+
const name = pickString(parsed.name) ?? pickString(parsed.function);
|
|
2804
|
+
if (!name) {
|
|
2805
|
+
return;
|
|
2806
|
+
}
|
|
2807
|
+
const rawArguments = parsed.arguments ?? parsed.parameters ?? {};
|
|
2808
|
+
const args = typeof rawArguments === "string" ? rawArguments : JSON.stringify(rawArguments);
|
|
2809
|
+
return {
|
|
2810
|
+
id: pickString(parsed.id) ?? `call_native_${index}`,
|
|
2811
|
+
type: "function",
|
|
2812
|
+
name,
|
|
2813
|
+
arguments: args
|
|
2814
|
+
};
|
|
2815
|
+
}
|
|
2816
|
+
function mergeToolCalls(...groups) {
|
|
2817
|
+
const merged = [];
|
|
2818
|
+
const seen = new Set;
|
|
2819
|
+
for (const group of groups) {
|
|
2820
|
+
for (const call of group) {
|
|
2821
|
+
const key = call.id || `${call.name ?? ""}:${String(call.arguments ?? "")}`;
|
|
2822
|
+
if (seen.has(key)) {
|
|
2823
|
+
continue;
|
|
2824
|
+
}
|
|
2825
|
+
seen.add(key);
|
|
2826
|
+
merged.push(call);
|
|
2567
2827
|
}
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2828
|
+
}
|
|
2829
|
+
return merged;
|
|
2830
|
+
}
|
|
2831
|
+
function collectOpenAIStreamToolCalls(payload, state) {
|
|
2832
|
+
const choices = payload.choices;
|
|
2833
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
for (const choice of choices) {
|
|
2837
|
+
if (!isRecord2(choice)) {
|
|
2838
|
+
continue;
|
|
2571
2839
|
}
|
|
2572
|
-
const
|
|
2573
|
-
const
|
|
2574
|
-
|
|
2575
|
-
|
|
2840
|
+
const delta = isRecord2(choice.delta) ? choice.delta : undefined;
|
|
2841
|
+
const message = isRecord2(choice.message) ? choice.message : undefined;
|
|
2842
|
+
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;
|
|
2843
|
+
if (!toolCalls) {
|
|
2844
|
+
continue;
|
|
2576
2845
|
}
|
|
2577
|
-
const
|
|
2578
|
-
|
|
2579
|
-
|
|
2846
|
+
for (const rawToolCall of toolCalls) {
|
|
2847
|
+
if (!isRecord2(rawToolCall)) {
|
|
2848
|
+
continue;
|
|
2849
|
+
}
|
|
2850
|
+
const index = toFiniteNumber(rawToolCall.index);
|
|
2851
|
+
const toolIndex = index !== undefined ? Math.floor(index) : state.size;
|
|
2852
|
+
const existing = state.get(toolIndex) ?? {
|
|
2853
|
+
index: toolIndex,
|
|
2854
|
+
argumentsText: ""
|
|
2855
|
+
};
|
|
2856
|
+
const id = pickString(rawToolCall.id);
|
|
2857
|
+
if (id) {
|
|
2858
|
+
existing.id = id;
|
|
2859
|
+
}
|
|
2860
|
+
const type = pickString(rawToolCall.type);
|
|
2861
|
+
if (type) {
|
|
2862
|
+
existing.type = type;
|
|
2863
|
+
}
|
|
2864
|
+
const functionCall = isRecord2(rawToolCall.function) ? rawToolCall.function : undefined;
|
|
2865
|
+
const name = pickString(functionCall?.name);
|
|
2866
|
+
if (name) {
|
|
2867
|
+
existing.name = `${existing.name ?? ""}${name}`;
|
|
2868
|
+
}
|
|
2869
|
+
const argumentsDelta = pickString(functionCall?.arguments);
|
|
2870
|
+
if (argumentsDelta) {
|
|
2871
|
+
if (message?.tool_calls === toolCalls || choice.tool_calls === toolCalls) {
|
|
2872
|
+
existing.argumentsText = argumentsDelta;
|
|
2873
|
+
} else {
|
|
2874
|
+
existing.argumentsText += argumentsDelta;
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
state.set(toolIndex, existing);
|
|
2580
2878
|
}
|
|
2581
|
-
state.set(toolIndex, existing);
|
|
2582
2879
|
}
|
|
2583
2880
|
}
|
|
2584
2881
|
function buildOpenAIStreamToolCalls(state) {
|
|
@@ -3026,7 +3323,7 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
|
|
|
3026
3323
|
}
|
|
3027
3324
|
const payload = await response.json();
|
|
3028
3325
|
lastPayload = payload;
|
|
3029
|
-
aggregatedUsage =
|
|
3326
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, pickUsage2(payload));
|
|
3030
3327
|
finishReason = pickFinishReason2(payload);
|
|
3031
3328
|
const content = Array.isArray(payload.content) ? payload.content : [];
|
|
3032
3329
|
const calledTools = pickAnthropicToolCalls(payload).filter((call) => call.type === "function");
|
|
@@ -3147,7 +3444,7 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
|
|
|
3147
3444
|
callbacks.onChunk?.(chunk);
|
|
3148
3445
|
}
|
|
3149
3446
|
});
|
|
3150
|
-
aggregatedUsage =
|
|
3447
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, roundUsage);
|
|
3151
3448
|
if (roundFinishReason) {
|
|
3152
3449
|
finishReason = roundFinishReason;
|
|
3153
3450
|
}
|
|
@@ -3806,95 +4103,6 @@ function withToolTimeout(client, toolTimeoutMs) {
|
|
|
3806
4103
|
function applyToolTimeout(clients, toolTimeoutMs) {
|
|
3807
4104
|
return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
|
|
3808
4105
|
}
|
|
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
4106
|
// src/utils/debug-colors.ts
|
|
3899
4107
|
var ANSI = {
|
|
3900
4108
|
reset: "\x1B[0m",
|
|
@@ -4056,13 +4264,15 @@ async function callModel(adapter, options) {
|
|
|
4056
4264
|
if (!done && fingerprint === lastSnapshotFingerprint) {
|
|
4057
4265
|
return;
|
|
4058
4266
|
}
|
|
4267
|
+
const stableText = done ? normalized2.text : withoutTrailingThinkTagPrefix(normalized2.text);
|
|
4268
|
+
const stableReasoning = done ? normalized2.reasoning : withoutTrailingThinkTagPrefix(normalized2.reasoning);
|
|
4059
4269
|
const delta = {
|
|
4060
|
-
text:
|
|
4061
|
-
reasoning:
|
|
4270
|
+
text: stableText.startsWith(previousSnapshotText) ? stableText.slice(previousSnapshotText.length) : "",
|
|
4271
|
+
reasoning: stableReasoning.startsWith(previousSnapshotReasoning) ? stableReasoning.slice(previousSnapshotReasoning.length) : ""
|
|
4062
4272
|
};
|
|
4063
4273
|
lastSnapshotFingerprint = fingerprint;
|
|
4064
|
-
previousSnapshotText =
|
|
4065
|
-
previousSnapshotReasoning =
|
|
4274
|
+
previousSnapshotText = stableText;
|
|
4275
|
+
previousSnapshotReasoning = stableReasoning;
|
|
4066
4276
|
options.stream.onData?.({
|
|
4067
4277
|
delta,
|
|
4068
4278
|
snapshot,
|