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.js
CHANGED
|
@@ -1077,6 +1077,110 @@ function countHiddenChars(value) {
|
|
|
1077
1077
|
function maskKeepingLineBreaks(value) {
|
|
1078
1078
|
return value.replace(RE_NON_LINE_BREAK, " ");
|
|
1079
1079
|
}
|
|
1080
|
+
// src/generate-output.ts
|
|
1081
|
+
var RE_THINK_TAGS = /<\/?think\s*>/gi;
|
|
1082
|
+
function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
|
|
1083
|
+
const sanitized = sanitizeThink(text);
|
|
1084
|
+
const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
|
|
1085
|
+
const reasoning = joinReasoningSegments([
|
|
1086
|
+
sanitizeReasoningText(dedicatedReasoning),
|
|
1087
|
+
...sanitized.thinkBlocks.map((block) => block.content)
|
|
1088
|
+
]);
|
|
1089
|
+
return {
|
|
1090
|
+
text: visibleText,
|
|
1091
|
+
reasoning,
|
|
1092
|
+
reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
|
|
1093
|
+
thinkBlocks: sanitized.thinkBlocks,
|
|
1094
|
+
parseSource: composeParseSource(visibleText, reasoning)
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
function normalizeReasoningBlocks(blocks) {
|
|
1098
|
+
if (!Array.isArray(blocks)) {
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
const normalized = blocks.map((block) => ({
|
|
1102
|
+
turnIndex: block.turnIndex,
|
|
1103
|
+
text: block.text.replace(RE_THINK_TAGS, "").trim()
|
|
1104
|
+
})).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
|
|
1105
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
1106
|
+
}
|
|
1107
|
+
function appendReasoningBlock(blocks, transition) {
|
|
1108
|
+
const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
|
|
1109
|
+
if (!text) {
|
|
1110
|
+
return blocks;
|
|
1111
|
+
}
|
|
1112
|
+
const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
|
|
1113
|
+
return normalizeReasoningBlocks(next);
|
|
1114
|
+
}
|
|
1115
|
+
function composeParseSource(text, reasoning) {
|
|
1116
|
+
if (typeof reasoning !== "string" || reasoning.length === 0) {
|
|
1117
|
+
return text;
|
|
1118
|
+
}
|
|
1119
|
+
const sanitized = reasoning.replace(RE_THINK_TAGS, "");
|
|
1120
|
+
if (sanitized.length === 0) {
|
|
1121
|
+
return text;
|
|
1122
|
+
}
|
|
1123
|
+
return `<think>${sanitized}</think>${text}`;
|
|
1124
|
+
}
|
|
1125
|
+
function aggregateUsage(attempts) {
|
|
1126
|
+
let usage;
|
|
1127
|
+
for (const attempt of attempts) {
|
|
1128
|
+
usage = mergeUsage(usage, attempt.usage);
|
|
1129
|
+
}
|
|
1130
|
+
return usage;
|
|
1131
|
+
}
|
|
1132
|
+
function mergeUsage(base, next) {
|
|
1133
|
+
if (!base && !next) {
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
return {
|
|
1137
|
+
inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
|
|
1138
|
+
outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
|
|
1139
|
+
totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
|
|
1140
|
+
cost: (base?.cost ?? 0) + (next?.cost ?? 0)
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
function joinReasoningSegments(parts) {
|
|
1144
|
+
return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
|
|
1145
|
+
|
|
1146
|
+
`);
|
|
1147
|
+
}
|
|
1148
|
+
function sanitizeReasoningText(value) {
|
|
1149
|
+
const sanitized = value?.replace(RE_THINK_TAGS, "").trim();
|
|
1150
|
+
return sanitized ? sanitized : undefined;
|
|
1151
|
+
}
|
|
1152
|
+
var THINK_TAG_VARIANTS = ["<think>", "</think>"];
|
|
1153
|
+
var MAX_THINK_TAG_PREFIX = Math.max(...THINK_TAG_VARIANTS.map((tag) => tag.length)) - 1;
|
|
1154
|
+
function withoutTrailingThinkTagPrefix(value) {
|
|
1155
|
+
const max = Math.min(value.length, MAX_THINK_TAG_PREFIX);
|
|
1156
|
+
for (let length = max;length > 0; length -= 1) {
|
|
1157
|
+
const suffix = value.slice(value.length - length);
|
|
1158
|
+
if (THINK_TAG_VARIANTS.some((tag) => tag.length > suffix.length && tag.startsWith(suffix))) {
|
|
1159
|
+
return value.slice(0, value.length - length);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
return value;
|
|
1163
|
+
}
|
|
1164
|
+
function stripThinkBlocks(text, thinkBlocks) {
|
|
1165
|
+
if (thinkBlocks.length === 0) {
|
|
1166
|
+
return text;
|
|
1167
|
+
}
|
|
1168
|
+
let output = "";
|
|
1169
|
+
let cursor = 0;
|
|
1170
|
+
for (const block of thinkBlocks) {
|
|
1171
|
+
output += text.slice(cursor, block.start);
|
|
1172
|
+
cursor = block.end;
|
|
1173
|
+
}
|
|
1174
|
+
output += text.slice(cursor);
|
|
1175
|
+
return output;
|
|
1176
|
+
}
|
|
1177
|
+
function toStreamDataFingerprint(value) {
|
|
1178
|
+
try {
|
|
1179
|
+
return JSON.stringify(value);
|
|
1180
|
+
} catch {
|
|
1181
|
+
return "__unserializable__";
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1080
1184
|
// src/providers/stream-utils.ts
|
|
1081
1185
|
var RE_LINE_ENDING = /\r?\n/;
|
|
1082
1186
|
async function consumeSSE(response, onEvent) {
|
|
@@ -1506,7 +1610,7 @@ function pickString(value) {
|
|
|
1506
1610
|
function toFiniteNumber(value) {
|
|
1507
1611
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
1508
1612
|
}
|
|
1509
|
-
function
|
|
1613
|
+
function mergeUsage2(base, next) {
|
|
1510
1614
|
if (!base && !next) {
|
|
1511
1615
|
return;
|
|
1512
1616
|
}
|
|
@@ -1631,6 +1735,8 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
|
|
|
1631
1735
|
let reasoning = "";
|
|
1632
1736
|
let usage;
|
|
1633
1737
|
let finishReason;
|
|
1738
|
+
const streamedToolCalls = new Map;
|
|
1739
|
+
const nativeToolCalls = new NativeToolCallStreamState(requestDeclaresTools(options, request));
|
|
1634
1740
|
await consumeSSE(response, (data) => {
|
|
1635
1741
|
if (data === "[DONE]") {
|
|
1636
1742
|
return;
|
|
@@ -1639,10 +1745,14 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
|
|
|
1639
1745
|
if (!isRecord2(json)) {
|
|
1640
1746
|
return;
|
|
1641
1747
|
}
|
|
1642
|
-
const
|
|
1748
|
+
const rawDelta = pickAssistantDelta(json);
|
|
1643
1749
|
const reasoningDelta = pickAssistantReasoningDelta(json);
|
|
1644
1750
|
const chunkUsage = pickUsage(json);
|
|
1645
1751
|
const chunkFinishReason = pickFinishReason(json);
|
|
1752
|
+
collectOpenAIStreamToolCalls(json, streamedToolCalls);
|
|
1753
|
+
const nativeDelta = nativeToolCalls.push(rawDelta);
|
|
1754
|
+
const delta = nativeDelta.textDelta;
|
|
1755
|
+
const chunkToolCalls = mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeDelta.toolCalls);
|
|
1646
1756
|
usage = preferLatestUsage(usage, chunkUsage);
|
|
1647
1757
|
if (chunkFinishReason) {
|
|
1648
1758
|
finishReason = chunkFinishReason;
|
|
@@ -1654,13 +1764,21 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
|
|
|
1654
1764
|
if (reasoningDelta) {
|
|
1655
1765
|
reasoning += reasoningDelta;
|
|
1656
1766
|
}
|
|
1657
|
-
emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
|
|
1767
|
+
emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
|
|
1658
1768
|
});
|
|
1769
|
+
const tail = nativeToolCalls.flush();
|
|
1770
|
+
if (tail.textDelta) {
|
|
1771
|
+
text += tail.textDelta;
|
|
1772
|
+
callbacks.onToken?.(tail.textDelta);
|
|
1773
|
+
emitOpenAIStreamChunk(callbacks, undefined, {}, tail.textDelta, "", undefined, undefined);
|
|
1774
|
+
}
|
|
1775
|
+
const toolCalls = mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeToolCalls.calls);
|
|
1659
1776
|
const out = {
|
|
1660
1777
|
text,
|
|
1661
1778
|
reasoning: reasoning.length > 0 ? reasoning : undefined,
|
|
1662
1779
|
usage,
|
|
1663
|
-
finishReason
|
|
1780
|
+
finishReason: finishReason ?? (toolCalls.length > 0 ? "tool_calls" : undefined),
|
|
1781
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
1664
1782
|
};
|
|
1665
1783
|
callbacks.onComplete?.(out);
|
|
1666
1784
|
return out;
|
|
@@ -1770,15 +1888,16 @@ function buildResponsesMCPResult(state, text, raw) {
|
|
|
1770
1888
|
toolExecutions: state.toolExecutions.length > 0 ? state.toolExecutions : undefined
|
|
1771
1889
|
};
|
|
1772
1890
|
}
|
|
1773
|
-
function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason) {
|
|
1774
|
-
if (delta || reasoningDelta || usage || finishReason) {
|
|
1891
|
+
function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason, toolCalls) {
|
|
1892
|
+
if (delta || reasoningDelta || usage || finishReason || toolCalls) {
|
|
1775
1893
|
callbacks.onChunk?.({
|
|
1776
1894
|
textDelta: delta,
|
|
1777
1895
|
reasoningDelta: reasoningDelta || undefined,
|
|
1778
1896
|
...round !== undefined ? { turnIndex: round } : {},
|
|
1779
1897
|
raw,
|
|
1780
1898
|
usage,
|
|
1781
|
-
finishReason
|
|
1899
|
+
finishReason,
|
|
1900
|
+
toolCalls
|
|
1782
1901
|
});
|
|
1783
1902
|
}
|
|
1784
1903
|
}
|
|
@@ -1845,7 +1964,7 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
|
|
|
1845
1964
|
parallel_tool_calls: request.parallelToolCalls
|
|
1846
1965
|
}));
|
|
1847
1966
|
lastPayload = payload;
|
|
1848
|
-
aggregatedUsage =
|
|
1967
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, pickUsage(payload));
|
|
1849
1968
|
finishReason = pickFinishReason(payload);
|
|
1850
1969
|
const assistantMessage = pickAssistantMessage(payload);
|
|
1851
1970
|
const calledTools = pickChatToolCalls(payload);
|
|
@@ -1925,7 +2044,7 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
|
|
|
1925
2044
|
parallel_tool_calls: request.parallelToolCalls
|
|
1926
2045
|
}));
|
|
1927
2046
|
state.lastPayload = payload;
|
|
1928
|
-
state.aggregatedUsage =
|
|
2047
|
+
state.aggregatedUsage = mergeUsage2(state.aggregatedUsage, pickUsage(payload));
|
|
1929
2048
|
state.finishReason = pickResponsesFinishReason(payload) ?? state.finishReason;
|
|
1930
2049
|
pushReasoningBlock(state.reasoningBlocks, round, pickResponsesReasoning(payload));
|
|
1931
2050
|
const providerToolCalls = pickResponsesToolCalls(payload);
|
|
@@ -1985,6 +2104,7 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
1985
2104
|
let roundUsage;
|
|
1986
2105
|
let roundFinishReason;
|
|
1987
2106
|
const streamedToolCalls = new Map;
|
|
2107
|
+
const nativeToolCalls = new NativeToolCallStreamState;
|
|
1988
2108
|
let reasoningFieldName;
|
|
1989
2109
|
await consumeSSE(response, (data) => {
|
|
1990
2110
|
if (data === "[DONE]") {
|
|
@@ -1995,10 +2115,12 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
1995
2115
|
return;
|
|
1996
2116
|
}
|
|
1997
2117
|
lastPayload = json;
|
|
1998
|
-
const
|
|
2118
|
+
const rawDelta = pickAssistantDelta(json);
|
|
1999
2119
|
const reasoningDelta = pickAssistantReasoningDelta(json);
|
|
2000
2120
|
const chunkUsage = pickUsage(json);
|
|
2001
2121
|
const chunkFinishReason = pickFinishReason(json);
|
|
2122
|
+
const nativeDelta = nativeToolCalls.push(rawDelta);
|
|
2123
|
+
const delta = nativeDelta.textDelta;
|
|
2002
2124
|
collectOpenAIStreamToolCalls(json, streamedToolCalls);
|
|
2003
2125
|
roundUsage = preferLatestUsage(roundUsage, chunkUsage);
|
|
2004
2126
|
if (chunkFinishReason) {
|
|
@@ -2012,13 +2134,21 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
2012
2134
|
roundReasoning += reasoningDelta;
|
|
2013
2135
|
reasoningFieldName ??= pickAssistantReasoningDeltaFieldName(json);
|
|
2014
2136
|
}
|
|
2015
|
-
|
|
2137
|
+
const streamedSnapshot = buildOpenAIStreamToolCalls(streamedToolCalls);
|
|
2138
|
+
const chunkToolCalls = nativeDelta.toolCalls.length > 0 ? mergeToolCalls(streamedSnapshot, nativeDelta.toolCalls) : streamedSnapshot.length > 0 ? streamedSnapshot : undefined;
|
|
2139
|
+
emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls);
|
|
2016
2140
|
});
|
|
2017
|
-
|
|
2141
|
+
const tail = nativeToolCalls.flush();
|
|
2142
|
+
if (tail.textDelta) {
|
|
2143
|
+
roundText += tail.textDelta;
|
|
2144
|
+
callbacks.onToken?.(tail.textDelta);
|
|
2145
|
+
emitOpenAIStreamChunk(callbacks, round, {}, tail.textDelta, "", undefined, undefined);
|
|
2146
|
+
}
|
|
2147
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, roundUsage);
|
|
2018
2148
|
if (roundFinishReason) {
|
|
2019
2149
|
finishReason = roundFinishReason;
|
|
2020
2150
|
}
|
|
2021
|
-
const calledTools = buildOpenAIStreamToolCalls(streamedToolCalls);
|
|
2151
|
+
const calledTools = mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeToolCalls.calls);
|
|
2022
2152
|
pushReasoningBlock(reasoningBlocks, round, roundReasoning);
|
|
2023
2153
|
request.onTurnTransition?.({
|
|
2024
2154
|
turnIndex: round,
|
|
@@ -2105,6 +2235,7 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
|
|
|
2105
2235
|
let usage;
|
|
2106
2236
|
let finishReason;
|
|
2107
2237
|
let lastPayload;
|
|
2238
|
+
const streamedToolCalls = new Map;
|
|
2108
2239
|
await consumeSSE(response, (data) => {
|
|
2109
2240
|
if (data === "[DONE]") {
|
|
2110
2241
|
return;
|
|
@@ -2120,6 +2251,8 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
|
|
|
2120
2251
|
const delta = pickResponsesStreamTextDelta(json);
|
|
2121
2252
|
const chunkUsage = pickResponsesStreamUsage(json);
|
|
2122
2253
|
const chunkFinishReason = pickResponsesStreamFinishReason(json);
|
|
2254
|
+
collectResponsesStreamToolCalls(json, streamedToolCalls);
|
|
2255
|
+
const chunkToolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
|
|
2123
2256
|
usage = preferLatestUsage(usage, chunkUsage);
|
|
2124
2257
|
if (chunkFinishReason) {
|
|
2125
2258
|
finishReason = chunkFinishReason;
|
|
@@ -2128,14 +2261,16 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
|
|
|
2128
2261
|
text += delta;
|
|
2129
2262
|
callbacks.onToken?.(delta);
|
|
2130
2263
|
}
|
|
2131
|
-
emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason);
|
|
2264
|
+
emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
|
|
2132
2265
|
});
|
|
2133
2266
|
const finalPayload = lastPayload ?? {};
|
|
2267
|
+
const toolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
|
|
2134
2268
|
const out = {
|
|
2135
2269
|
text: text.length > 0 ? text : pickResponsesText(finalPayload) || pickAssistantText(finalPayload),
|
|
2136
2270
|
raw: finalPayload,
|
|
2137
2271
|
usage: preferLatestUsage(usage, pickUsage(finalPayload)),
|
|
2138
|
-
finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload)
|
|
2272
|
+
finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload),
|
|
2273
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
2139
2274
|
};
|
|
2140
2275
|
callbacks.onComplete?.(out);
|
|
2141
2276
|
return out;
|
|
@@ -2194,10 +2329,11 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
|
|
|
2194
2329
|
if (reasoningDelta) {
|
|
2195
2330
|
roundReasoning += reasoningDelta;
|
|
2196
2331
|
}
|
|
2197
|
-
|
|
2332
|
+
const chunkToolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
|
|
2333
|
+
emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
|
|
2198
2334
|
});
|
|
2199
2335
|
const resolvedRoundUsage = preferLatestUsage(roundUsage, roundPayload ? pickUsage(roundPayload) : undefined);
|
|
2200
|
-
state.aggregatedUsage =
|
|
2336
|
+
state.aggregatedUsage = mergeUsage2(state.aggregatedUsage, resolvedRoundUsage);
|
|
2201
2337
|
if (roundFinishReason) {
|
|
2202
2338
|
state.finishReason = roundFinishReason;
|
|
2203
2339
|
} else if (roundPayload) {
|
|
@@ -2451,43 +2587,204 @@ function pickAssistantReasoningDeltaFieldName(payload) {
|
|
|
2451
2587
|
}
|
|
2452
2588
|
return;
|
|
2453
2589
|
}
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2590
|
+
var NATIVE_TOOL_CALL_OPEN = "<tool_call";
|
|
2591
|
+
var NATIVE_TOOL_CALL_CLOSE = "</tool_call>";
|
|
2592
|
+
|
|
2593
|
+
class NativeToolCallStreamState {
|
|
2594
|
+
enabled;
|
|
2595
|
+
calls = [];
|
|
2596
|
+
pending = "";
|
|
2597
|
+
constructor(enabled = true) {
|
|
2598
|
+
this.enabled = enabled;
|
|
2599
|
+
}
|
|
2600
|
+
push(delta) {
|
|
2601
|
+
if (!delta || !this.enabled) {
|
|
2602
|
+
return { textDelta: delta, toolCalls: [] };
|
|
2603
|
+
}
|
|
2604
|
+
this.pending += delta;
|
|
2605
|
+
return this.drain(false);
|
|
2606
|
+
}
|
|
2607
|
+
flush() {
|
|
2608
|
+
return this.enabled ? this.drain(true) : { textDelta: "", toolCalls: [] };
|
|
2609
|
+
}
|
|
2610
|
+
drain(flush) {
|
|
2611
|
+
let textDelta = "";
|
|
2612
|
+
const toolCalls = [];
|
|
2613
|
+
while (this.pending.length > 0) {
|
|
2614
|
+
const openIndex = this.pending.indexOf(NATIVE_TOOL_CALL_OPEN);
|
|
2615
|
+
if (openIndex < 0) {
|
|
2616
|
+
const keep = flush ? 0 : nativeToolCallPrefixSuffixLength(this.pending);
|
|
2617
|
+
const emitLength = this.pending.length - keep;
|
|
2618
|
+
if (emitLength > 0) {
|
|
2619
|
+
textDelta += this.pending.slice(0, emitLength);
|
|
2620
|
+
this.pending = this.pending.slice(emitLength);
|
|
2621
|
+
}
|
|
2622
|
+
break;
|
|
2623
|
+
}
|
|
2624
|
+
if (openIndex > 0) {
|
|
2625
|
+
textDelta += this.pending.slice(0, openIndex);
|
|
2626
|
+
this.pending = this.pending.slice(openIndex);
|
|
2627
|
+
continue;
|
|
2628
|
+
}
|
|
2629
|
+
const closeIndex = this.pending.indexOf(NATIVE_TOOL_CALL_CLOSE);
|
|
2630
|
+
if (closeIndex < 0) {
|
|
2631
|
+
if (flush) {
|
|
2632
|
+
this.pending = "";
|
|
2633
|
+
}
|
|
2634
|
+
break;
|
|
2635
|
+
}
|
|
2636
|
+
const blockEnd = closeIndex + NATIVE_TOOL_CALL_CLOSE.length;
|
|
2637
|
+
const call = parseNativeToolCallBlock(this.pending.slice(0, blockEnd), this.calls.length);
|
|
2638
|
+
if (call) {
|
|
2639
|
+
this.calls.push(call);
|
|
2640
|
+
toolCalls.push(call);
|
|
2641
|
+
}
|
|
2642
|
+
this.pending = this.pending.slice(blockEnd);
|
|
2643
|
+
}
|
|
2644
|
+
return { textDelta, toolCalls };
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
function requestDeclaresTools(options, request) {
|
|
2648
|
+
const hasTools = (body) => Array.isArray(body?.tools) && body.tools.length > 0;
|
|
2649
|
+
return hasTools(request.body) || hasTools(options.defaultBody);
|
|
2650
|
+
}
|
|
2651
|
+
var NATIVE_FUNCTION_PATTERN = /<function=([^>\s]+)\s*>([\s\S]*?)<\/function>/;
|
|
2652
|
+
var NATIVE_PARAMETER_PATTERN = /<parameter=([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
|
|
2653
|
+
function parseNativeToolCallBlock(block, index) {
|
|
2654
|
+
const inner = extractNativeToolCallInner(block);
|
|
2655
|
+
if (inner === undefined) {
|
|
2457
2656
|
return;
|
|
2458
2657
|
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2658
|
+
return parseNativeJsonToolCall(inner, index) ?? parseNativeXmlToolCall(inner, index);
|
|
2659
|
+
}
|
|
2660
|
+
function extractNativeToolCallInner(block) {
|
|
2661
|
+
const openEnd = block.indexOf(">");
|
|
2662
|
+
const closeStart = block.lastIndexOf(NATIVE_TOOL_CALL_CLOSE);
|
|
2663
|
+
if (openEnd < 0 || closeStart < 0 || closeStart <= openEnd) {
|
|
2461
2664
|
return;
|
|
2462
2665
|
}
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2666
|
+
return block.slice(openEnd + 1, closeStart).trim();
|
|
2667
|
+
}
|
|
2668
|
+
function parseNativeXmlToolCall(inner, index) {
|
|
2669
|
+
const functionMatch = NATIVE_FUNCTION_PATTERN.exec(inner);
|
|
2670
|
+
const functionName = functionMatch?.[1];
|
|
2671
|
+
const functionBody = functionMatch?.[2];
|
|
2672
|
+
if (!functionName || functionBody === undefined) {
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
const args = {};
|
|
2676
|
+
for (const [, key, rawValue] of functionBody.matchAll(NATIVE_PARAMETER_PATTERN)) {
|
|
2677
|
+
if (key && rawValue !== undefined) {
|
|
2678
|
+
args[key] = coerceNativeParameterValue(rawValue.trim());
|
|
2466
2679
|
}
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2680
|
+
}
|
|
2681
|
+
return {
|
|
2682
|
+
id: `call_native_${index}`,
|
|
2683
|
+
type: "function",
|
|
2684
|
+
name: functionName,
|
|
2685
|
+
arguments: JSON.stringify(args)
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
function coerceNativeParameterValue(value) {
|
|
2689
|
+
if (value.length === 0) {
|
|
2690
|
+
return "";
|
|
2691
|
+
}
|
|
2692
|
+
const parsed = safeJSONParse(value);
|
|
2693
|
+
if (parsed === null) {
|
|
2694
|
+
return value === "null" ? null : value;
|
|
2695
|
+
}
|
|
2696
|
+
return parsed;
|
|
2697
|
+
}
|
|
2698
|
+
function nativeToolCallPrefixSuffixLength(value) {
|
|
2699
|
+
const max = Math.min(value.length, NATIVE_TOOL_CALL_OPEN.length - 1);
|
|
2700
|
+
for (let length = max;length > 0; length -= 1) {
|
|
2701
|
+
if (NATIVE_TOOL_CALL_OPEN.startsWith(value.slice(-length))) {
|
|
2702
|
+
return length;
|
|
2476
2703
|
}
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2704
|
+
}
|
|
2705
|
+
return 0;
|
|
2706
|
+
}
|
|
2707
|
+
function parseNativeJsonToolCall(inner, index) {
|
|
2708
|
+
const parsed = safeJSONParse(inner);
|
|
2709
|
+
if (!isRecord2(parsed)) {
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
const name = pickString(parsed.name) ?? pickString(parsed.function);
|
|
2713
|
+
if (!name) {
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
const rawArguments = parsed.arguments ?? parsed.parameters ?? {};
|
|
2717
|
+
const args = typeof rawArguments === "string" ? rawArguments : JSON.stringify(rawArguments);
|
|
2718
|
+
return {
|
|
2719
|
+
id: pickString(parsed.id) ?? `call_native_${index}`,
|
|
2720
|
+
type: "function",
|
|
2721
|
+
name,
|
|
2722
|
+
arguments: args
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
function mergeToolCalls(...groups) {
|
|
2726
|
+
const merged = [];
|
|
2727
|
+
const seen = new Set;
|
|
2728
|
+
for (const group of groups) {
|
|
2729
|
+
for (const call of group) {
|
|
2730
|
+
const key = call.id || `${call.name ?? ""}:${String(call.arguments ?? "")}`;
|
|
2731
|
+
if (seen.has(key)) {
|
|
2732
|
+
continue;
|
|
2733
|
+
}
|
|
2734
|
+
seen.add(key);
|
|
2735
|
+
merged.push(call);
|
|
2480
2736
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2737
|
+
}
|
|
2738
|
+
return merged;
|
|
2739
|
+
}
|
|
2740
|
+
function collectOpenAIStreamToolCalls(payload, state) {
|
|
2741
|
+
const choices = payload.choices;
|
|
2742
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
2743
|
+
return;
|
|
2744
|
+
}
|
|
2745
|
+
for (const choice of choices) {
|
|
2746
|
+
if (!isRecord2(choice)) {
|
|
2747
|
+
continue;
|
|
2485
2748
|
}
|
|
2486
|
-
const
|
|
2487
|
-
|
|
2488
|
-
|
|
2749
|
+
const delta = isRecord2(choice.delta) ? choice.delta : undefined;
|
|
2750
|
+
const message = isRecord2(choice.message) ? choice.message : undefined;
|
|
2751
|
+
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;
|
|
2752
|
+
if (!toolCalls) {
|
|
2753
|
+
continue;
|
|
2754
|
+
}
|
|
2755
|
+
for (const rawToolCall of toolCalls) {
|
|
2756
|
+
if (!isRecord2(rawToolCall)) {
|
|
2757
|
+
continue;
|
|
2758
|
+
}
|
|
2759
|
+
const index = toFiniteNumber(rawToolCall.index);
|
|
2760
|
+
const toolIndex = index !== undefined ? Math.floor(index) : state.size;
|
|
2761
|
+
const existing = state.get(toolIndex) ?? {
|
|
2762
|
+
index: toolIndex,
|
|
2763
|
+
argumentsText: ""
|
|
2764
|
+
};
|
|
2765
|
+
const id = pickString(rawToolCall.id);
|
|
2766
|
+
if (id) {
|
|
2767
|
+
existing.id = id;
|
|
2768
|
+
}
|
|
2769
|
+
const type = pickString(rawToolCall.type);
|
|
2770
|
+
if (type) {
|
|
2771
|
+
existing.type = type;
|
|
2772
|
+
}
|
|
2773
|
+
const functionCall = isRecord2(rawToolCall.function) ? rawToolCall.function : undefined;
|
|
2774
|
+
const name = pickString(functionCall?.name);
|
|
2775
|
+
if (name) {
|
|
2776
|
+
existing.name = `${existing.name ?? ""}${name}`;
|
|
2777
|
+
}
|
|
2778
|
+
const argumentsDelta = pickString(functionCall?.arguments);
|
|
2779
|
+
if (argumentsDelta) {
|
|
2780
|
+
if (message?.tool_calls === toolCalls || choice.tool_calls === toolCalls) {
|
|
2781
|
+
existing.argumentsText = argumentsDelta;
|
|
2782
|
+
} else {
|
|
2783
|
+
existing.argumentsText += argumentsDelta;
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
state.set(toolIndex, existing);
|
|
2489
2787
|
}
|
|
2490
|
-
state.set(toolIndex, existing);
|
|
2491
2788
|
}
|
|
2492
2789
|
}
|
|
2493
2790
|
function buildOpenAIStreamToolCalls(state) {
|
|
@@ -2838,6 +3135,7 @@ async function streamPassThrough(options, fetcher, path, request, callbacks) {
|
|
|
2838
3135
|
let text = "";
|
|
2839
3136
|
let usage;
|
|
2840
3137
|
let finishReason;
|
|
3138
|
+
const streamedToolCalls = new Map;
|
|
2841
3139
|
await consumeSSE(response, (data) => {
|
|
2842
3140
|
if (data === "[DONE]") {
|
|
2843
3141
|
return;
|
|
@@ -2849,6 +3147,7 @@ async function streamPassThrough(options, fetcher, path, request, callbacks) {
|
|
|
2849
3147
|
const delta = pickAnthropicDelta(json);
|
|
2850
3148
|
const chunkUsage = pickUsage2(json);
|
|
2851
3149
|
const chunkFinishReason = pickFinishReason2(json);
|
|
3150
|
+
collectAnthropicStreamToolCalls(json, streamedToolCalls);
|
|
2852
3151
|
usage = preferLatestUsage(usage, chunkUsage);
|
|
2853
3152
|
if (chunkFinishReason) {
|
|
2854
3153
|
finishReason = chunkFinishReason;
|
|
@@ -2857,16 +3156,25 @@ async function streamPassThrough(options, fetcher, path, request, callbacks) {
|
|
|
2857
3156
|
text += delta;
|
|
2858
3157
|
callbacks.onToken?.(delta);
|
|
2859
3158
|
}
|
|
2860
|
-
|
|
3159
|
+
const streamedSnapshot = buildAnthropicStreamToolCalls(streamedToolCalls);
|
|
3160
|
+
const chunkToolCalls = streamedSnapshot.length > 0 ? streamedSnapshot : undefined;
|
|
3161
|
+
if (delta || chunkUsage || chunkFinishReason || chunkToolCalls) {
|
|
2861
3162
|
callbacks.onChunk?.({
|
|
2862
3163
|
textDelta: delta,
|
|
2863
3164
|
raw: json,
|
|
2864
3165
|
usage: chunkUsage,
|
|
2865
|
-
finishReason: chunkFinishReason
|
|
3166
|
+
finishReason: chunkFinishReason,
|
|
3167
|
+
toolCalls: chunkToolCalls
|
|
2866
3168
|
});
|
|
2867
3169
|
}
|
|
2868
3170
|
});
|
|
2869
|
-
const
|
|
3171
|
+
const toolCalls = buildAnthropicStreamToolCalls(streamedToolCalls);
|
|
3172
|
+
const out = {
|
|
3173
|
+
text,
|
|
3174
|
+
usage,
|
|
3175
|
+
finishReason: finishReason ?? (toolCalls.length > 0 ? "tool_use" : undefined),
|
|
3176
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
3177
|
+
};
|
|
2870
3178
|
callbacks.onComplete?.(out);
|
|
2871
3179
|
return out;
|
|
2872
3180
|
}
|
|
@@ -2935,7 +3243,7 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
|
|
|
2935
3243
|
}
|
|
2936
3244
|
const payload = await response.json();
|
|
2937
3245
|
lastPayload = payload;
|
|
2938
|
-
aggregatedUsage =
|
|
3246
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, pickUsage2(payload));
|
|
2939
3247
|
finishReason = pickFinishReason2(payload);
|
|
2940
3248
|
const content = Array.isArray(payload.content) ? payload.content : [];
|
|
2941
3249
|
const calledTools = pickAnthropicToolCalls(payload).filter((call) => call.type === "function");
|
|
@@ -3044,19 +3352,22 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
|
|
|
3044
3352
|
if (reasoningDelta) {
|
|
3045
3353
|
roundReasoning += reasoningDelta;
|
|
3046
3354
|
}
|
|
3047
|
-
|
|
3355
|
+
const streamedSnapshot = buildAnthropicStreamToolCalls(streamedToolCalls);
|
|
3356
|
+
const chunkToolCalls = streamedSnapshot.length > 0 ? streamedSnapshot : undefined;
|
|
3357
|
+
if (delta || reasoningDelta || chunkUsage || chunkFinishReason || chunkToolCalls) {
|
|
3048
3358
|
const chunk = {
|
|
3049
3359
|
textDelta: delta,
|
|
3050
3360
|
reasoningDelta: reasoningDelta || undefined,
|
|
3051
3361
|
turnIndex: round,
|
|
3052
3362
|
raw: json,
|
|
3053
3363
|
usage: chunkUsage,
|
|
3054
|
-
finishReason: chunkFinishReason
|
|
3364
|
+
finishReason: chunkFinishReason,
|
|
3365
|
+
toolCalls: chunkToolCalls
|
|
3055
3366
|
};
|
|
3056
3367
|
callbacks.onChunk?.(chunk);
|
|
3057
3368
|
}
|
|
3058
3369
|
});
|
|
3059
|
-
aggregatedUsage =
|
|
3370
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, roundUsage);
|
|
3060
3371
|
if (roundFinishReason) {
|
|
3061
3372
|
finishReason = roundFinishReason;
|
|
3062
3373
|
}
|
|
@@ -3715,95 +4026,6 @@ function withToolTimeout(client, toolTimeoutMs) {
|
|
|
3715
4026
|
function applyToolTimeout(clients, toolTimeoutMs) {
|
|
3716
4027
|
return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
|
|
3717
4028
|
}
|
|
3718
|
-
// src/generate-output.ts
|
|
3719
|
-
var RE_THINK_TAGS = /<\/?think\s*>/gi;
|
|
3720
|
-
function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
|
|
3721
|
-
const sanitized = sanitizeThink(text);
|
|
3722
|
-
const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
|
|
3723
|
-
const reasoning = joinReasoningSegments([
|
|
3724
|
-
dedicatedReasoning,
|
|
3725
|
-
...sanitized.thinkBlocks.map((block) => block.content)
|
|
3726
|
-
]);
|
|
3727
|
-
return {
|
|
3728
|
-
text: visibleText,
|
|
3729
|
-
reasoning,
|
|
3730
|
-
reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
|
|
3731
|
-
thinkBlocks: sanitized.thinkBlocks,
|
|
3732
|
-
parseSource: composeParseSource(visibleText, reasoning)
|
|
3733
|
-
};
|
|
3734
|
-
}
|
|
3735
|
-
function normalizeReasoningBlocks(blocks) {
|
|
3736
|
-
if (!Array.isArray(blocks)) {
|
|
3737
|
-
return;
|
|
3738
|
-
}
|
|
3739
|
-
const normalized = blocks.map((block) => ({
|
|
3740
|
-
turnIndex: block.turnIndex,
|
|
3741
|
-
text: block.text.replace(RE_THINK_TAGS, "").trim()
|
|
3742
|
-
})).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
|
|
3743
|
-
return normalized.length > 0 ? normalized : undefined;
|
|
3744
|
-
}
|
|
3745
|
-
function appendReasoningBlock(blocks, transition) {
|
|
3746
|
-
const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
|
|
3747
|
-
if (!text) {
|
|
3748
|
-
return blocks;
|
|
3749
|
-
}
|
|
3750
|
-
const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
|
|
3751
|
-
return normalizeReasoningBlocks(next);
|
|
3752
|
-
}
|
|
3753
|
-
function composeParseSource(text, reasoning) {
|
|
3754
|
-
if (typeof reasoning !== "string" || reasoning.length === 0) {
|
|
3755
|
-
return text;
|
|
3756
|
-
}
|
|
3757
|
-
const sanitized = reasoning.replace(RE_THINK_TAGS, "");
|
|
3758
|
-
if (sanitized.length === 0) {
|
|
3759
|
-
return text;
|
|
3760
|
-
}
|
|
3761
|
-
return `<think>${sanitized}</think>${text}`;
|
|
3762
|
-
}
|
|
3763
|
-
function aggregateUsage(attempts) {
|
|
3764
|
-
let usage;
|
|
3765
|
-
for (const attempt of attempts) {
|
|
3766
|
-
usage = mergeUsage2(usage, attempt.usage);
|
|
3767
|
-
}
|
|
3768
|
-
return usage;
|
|
3769
|
-
}
|
|
3770
|
-
function mergeUsage2(base, next) {
|
|
3771
|
-
if (!base && !next) {
|
|
3772
|
-
return;
|
|
3773
|
-
}
|
|
3774
|
-
return {
|
|
3775
|
-
inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
|
|
3776
|
-
outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
|
|
3777
|
-
totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
|
|
3778
|
-
cost: (base?.cost ?? 0) + (next?.cost ?? 0)
|
|
3779
|
-
};
|
|
3780
|
-
}
|
|
3781
|
-
function joinReasoningSegments(parts) {
|
|
3782
|
-
return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
|
|
3783
|
-
|
|
3784
|
-
`);
|
|
3785
|
-
}
|
|
3786
|
-
function stripThinkBlocks(text, thinkBlocks) {
|
|
3787
|
-
if (thinkBlocks.length === 0) {
|
|
3788
|
-
return text;
|
|
3789
|
-
}
|
|
3790
|
-
let output = "";
|
|
3791
|
-
let cursor = 0;
|
|
3792
|
-
for (const block of thinkBlocks) {
|
|
3793
|
-
output += text.slice(cursor, block.start);
|
|
3794
|
-
cursor = block.end;
|
|
3795
|
-
}
|
|
3796
|
-
output += text.slice(cursor);
|
|
3797
|
-
return output;
|
|
3798
|
-
}
|
|
3799
|
-
function toStreamDataFingerprint(value) {
|
|
3800
|
-
try {
|
|
3801
|
-
return JSON.stringify(value);
|
|
3802
|
-
} catch {
|
|
3803
|
-
return "__unserializable__";
|
|
3804
|
-
}
|
|
3805
|
-
}
|
|
3806
|
-
|
|
3807
4029
|
// src/utils/debug-colors.ts
|
|
3808
4030
|
var ANSI = {
|
|
3809
4031
|
reset: "\x1B[0m",
|
|
@@ -3965,13 +4187,15 @@ async function callModel(adapter, options) {
|
|
|
3965
4187
|
if (!done && fingerprint === lastSnapshotFingerprint) {
|
|
3966
4188
|
return;
|
|
3967
4189
|
}
|
|
4190
|
+
const stableText = done ? normalized2.text : withoutTrailingThinkTagPrefix(normalized2.text);
|
|
4191
|
+
const stableReasoning = done ? normalized2.reasoning : withoutTrailingThinkTagPrefix(normalized2.reasoning);
|
|
3968
4192
|
const delta = {
|
|
3969
|
-
text:
|
|
3970
|
-
reasoning:
|
|
4193
|
+
text: stableText.startsWith(previousSnapshotText) ? stableText.slice(previousSnapshotText.length) : "",
|
|
4194
|
+
reasoning: stableReasoning.startsWith(previousSnapshotReasoning) ? stableReasoning.slice(previousSnapshotReasoning.length) : ""
|
|
3971
4195
|
};
|
|
3972
4196
|
lastSnapshotFingerprint = fingerprint;
|
|
3973
|
-
previousSnapshotText =
|
|
3974
|
-
previousSnapshotReasoning =
|
|
4197
|
+
previousSnapshotText = stableText;
|
|
4198
|
+
previousSnapshotReasoning = stableReasoning;
|
|
3975
4199
|
options.stream.onData?.({
|
|
3976
4200
|
delta,
|
|
3977
4201
|
snapshot,
|
|
@@ -5816,6 +6040,7 @@ function unwrap2(schema) {
|
|
|
5816
6040
|
}
|
|
5817
6041
|
export {
|
|
5818
6042
|
wrapMCPClient,
|
|
6043
|
+
withoutTrailingThinkTagPrefix,
|
|
5819
6044
|
withFormat,
|
|
5820
6045
|
structured,
|
|
5821
6046
|
sanitizeThink,
|
|
@@ -5825,6 +6050,7 @@ export {
|
|
|
5825
6050
|
registerBuiltinProviders,
|
|
5826
6051
|
prompt,
|
|
5827
6052
|
parseLLMOutput,
|
|
6053
|
+
normalizeModelOutput,
|
|
5828
6054
|
inspectSchemaMetadata,
|
|
5829
6055
|
inferSchemaExample,
|
|
5830
6056
|
images,
|