extrait 0.7.2 → 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 +357 -141
- package/dist/index.d.ts +1 -0
- package/dist/index.js +357 -141
- 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
|
}
|
|
@@ -1710,7 +1816,8 @@ function createOpenAICompatibleAdapter(options) {
|
|
|
1710
1816
|
async function streamWithChatCompletionsPassThrough(options, fetcher, path, request, callbacks) {
|
|
1711
1817
|
const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
|
|
1712
1818
|
messages: buildMessages(request),
|
|
1713
|
-
stream: true
|
|
1819
|
+
stream: true,
|
|
1820
|
+
stream_options: streamUsageOptions(request)
|
|
1714
1821
|
}));
|
|
1715
1822
|
if (!response.ok) {
|
|
1716
1823
|
const message = await response.text();
|
|
@@ -1721,6 +1828,8 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
|
|
|
1721
1828
|
let reasoning = "";
|
|
1722
1829
|
let usage;
|
|
1723
1830
|
let finishReason;
|
|
1831
|
+
const streamedToolCalls = new Map;
|
|
1832
|
+
const nativeToolCalls = new NativeToolCallStreamState(requestDeclaresTools(options, request));
|
|
1724
1833
|
await consumeSSE(response, (data) => {
|
|
1725
1834
|
if (data === "[DONE]") {
|
|
1726
1835
|
return;
|
|
@@ -1729,10 +1838,14 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
|
|
|
1729
1838
|
if (!isRecord2(json)) {
|
|
1730
1839
|
return;
|
|
1731
1840
|
}
|
|
1732
|
-
const
|
|
1841
|
+
const rawDelta = pickAssistantDelta(json);
|
|
1733
1842
|
const reasoningDelta = pickAssistantReasoningDelta(json);
|
|
1734
1843
|
const chunkUsage = pickUsage(json);
|
|
1735
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);
|
|
1736
1849
|
usage = preferLatestUsage(usage, chunkUsage);
|
|
1737
1850
|
if (chunkFinishReason) {
|
|
1738
1851
|
finishReason = chunkFinishReason;
|
|
@@ -1744,13 +1857,21 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
|
|
|
1744
1857
|
if (reasoningDelta) {
|
|
1745
1858
|
reasoning += reasoningDelta;
|
|
1746
1859
|
}
|
|
1747
|
-
emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
|
|
1860
|
+
emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
|
|
1748
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);
|
|
1749
1869
|
const out = {
|
|
1750
1870
|
text,
|
|
1751
1871
|
reasoning: reasoning.length > 0 ? reasoning : undefined,
|
|
1752
1872
|
usage,
|
|
1753
|
-
finishReason
|
|
1873
|
+
finishReason: finishReason ?? (toolCalls.length > 0 ? "tool_calls" : undefined),
|
|
1874
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
1754
1875
|
};
|
|
1755
1876
|
callbacks.onComplete?.(out);
|
|
1756
1877
|
return out;
|
|
@@ -1802,6 +1923,10 @@ async function completeOpenAIRequest(options, fetcher, chatPath, responsesPath,
|
|
|
1802
1923
|
function buildChatCompletionsBody(options, request, overrides) {
|
|
1803
1924
|
return buildOpenAIRequestBody(options, request, "max_tokens", overrides);
|
|
1804
1925
|
}
|
|
1926
|
+
function streamUsageOptions(request) {
|
|
1927
|
+
const userOptions = isRecord2(request.body?.stream_options) ? request.body.stream_options : undefined;
|
|
1928
|
+
return { include_usage: true, ...userOptions };
|
|
1929
|
+
}
|
|
1805
1930
|
function buildResponsesBody(options, request, overrides) {
|
|
1806
1931
|
return buildOpenAIRequestBody(options, request, "max_output_tokens", overrides);
|
|
1807
1932
|
}
|
|
@@ -1856,15 +1981,16 @@ function buildResponsesMCPResult(state, text, raw) {
|
|
|
1856
1981
|
toolExecutions: state.toolExecutions.length > 0 ? state.toolExecutions : undefined
|
|
1857
1982
|
};
|
|
1858
1983
|
}
|
|
1859
|
-
function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason) {
|
|
1860
|
-
if (delta || reasoningDelta || usage || finishReason) {
|
|
1984
|
+
function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason, toolCalls) {
|
|
1985
|
+
if (delta || reasoningDelta || usage || finishReason || toolCalls) {
|
|
1861
1986
|
callbacks.onChunk?.({
|
|
1862
1987
|
textDelta: delta,
|
|
1863
1988
|
reasoningDelta: reasoningDelta || undefined,
|
|
1864
1989
|
...round !== undefined ? { turnIndex: round } : {},
|
|
1865
1990
|
raw,
|
|
1866
1991
|
usage,
|
|
1867
|
-
finishReason
|
|
1992
|
+
finishReason,
|
|
1993
|
+
toolCalls
|
|
1868
1994
|
});
|
|
1869
1995
|
}
|
|
1870
1996
|
}
|
|
@@ -1931,7 +2057,7 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
|
|
|
1931
2057
|
parallel_tool_calls: request.parallelToolCalls
|
|
1932
2058
|
}));
|
|
1933
2059
|
lastPayload = payload;
|
|
1934
|
-
aggregatedUsage =
|
|
2060
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, pickUsage(payload));
|
|
1935
2061
|
finishReason = pickFinishReason(payload);
|
|
1936
2062
|
const assistantMessage = pickAssistantMessage(payload);
|
|
1937
2063
|
const calledTools = pickChatToolCalls(payload);
|
|
@@ -2011,7 +2137,7 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
|
|
|
2011
2137
|
parallel_tool_calls: request.parallelToolCalls
|
|
2012
2138
|
}));
|
|
2013
2139
|
state.lastPayload = payload;
|
|
2014
|
-
state.aggregatedUsage =
|
|
2140
|
+
state.aggregatedUsage = mergeUsage2(state.aggregatedUsage, pickUsage(payload));
|
|
2015
2141
|
state.finishReason = pickResponsesFinishReason(payload) ?? state.finishReason;
|
|
2016
2142
|
pushReasoningBlock(state.reasoningBlocks, round, pickResponsesReasoning(payload));
|
|
2017
2143
|
const providerToolCalls = pickResponsesToolCalls(payload);
|
|
@@ -2059,7 +2185,8 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
2059
2185
|
tools: transportTools,
|
|
2060
2186
|
tool_choice: request.toolChoice,
|
|
2061
2187
|
parallel_tool_calls: request.parallelToolCalls,
|
|
2062
|
-
stream: true
|
|
2188
|
+
stream: true,
|
|
2189
|
+
stream_options: streamUsageOptions(request)
|
|
2063
2190
|
}));
|
|
2064
2191
|
if (!response.ok) {
|
|
2065
2192
|
const message = await response.text();
|
|
@@ -2070,6 +2197,7 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
2070
2197
|
let roundUsage;
|
|
2071
2198
|
let roundFinishReason;
|
|
2072
2199
|
const streamedToolCalls = new Map;
|
|
2200
|
+
const nativeToolCalls = new NativeToolCallStreamState;
|
|
2073
2201
|
let reasoningFieldName;
|
|
2074
2202
|
await consumeSSE(response, (data) => {
|
|
2075
2203
|
if (data === "[DONE]") {
|
|
@@ -2080,10 +2208,12 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
2080
2208
|
return;
|
|
2081
2209
|
}
|
|
2082
2210
|
lastPayload = json;
|
|
2083
|
-
const
|
|
2211
|
+
const rawDelta = pickAssistantDelta(json);
|
|
2084
2212
|
const reasoningDelta = pickAssistantReasoningDelta(json);
|
|
2085
2213
|
const chunkUsage = pickUsage(json);
|
|
2086
2214
|
const chunkFinishReason = pickFinishReason(json);
|
|
2215
|
+
const nativeDelta = nativeToolCalls.push(rawDelta);
|
|
2216
|
+
const delta = nativeDelta.textDelta;
|
|
2087
2217
|
collectOpenAIStreamToolCalls(json, streamedToolCalls);
|
|
2088
2218
|
roundUsage = preferLatestUsage(roundUsage, chunkUsage);
|
|
2089
2219
|
if (chunkFinishReason) {
|
|
@@ -2097,13 +2227,20 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
|
|
|
2097
2227
|
roundReasoning += reasoningDelta;
|
|
2098
2228
|
reasoningFieldName ??= pickAssistantReasoningDeltaFieldName(json);
|
|
2099
2229
|
}
|
|
2100
|
-
|
|
2230
|
+
const chunkToolCalls = nativeDelta.toolCalls.length > 0 ? mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeDelta.toolCalls) : undefined;
|
|
2231
|
+
emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls);
|
|
2101
2232
|
});
|
|
2102
|
-
|
|
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);
|
|
2103
2240
|
if (roundFinishReason) {
|
|
2104
2241
|
finishReason = roundFinishReason;
|
|
2105
2242
|
}
|
|
2106
|
-
const calledTools = buildOpenAIStreamToolCalls(streamedToolCalls);
|
|
2243
|
+
const calledTools = mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeToolCalls.calls);
|
|
2107
2244
|
pushReasoningBlock(reasoningBlocks, round, roundReasoning);
|
|
2108
2245
|
request.onTurnTransition?.({
|
|
2109
2246
|
turnIndex: round,
|
|
@@ -2190,6 +2327,7 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
|
|
|
2190
2327
|
let usage;
|
|
2191
2328
|
let finishReason;
|
|
2192
2329
|
let lastPayload;
|
|
2330
|
+
const streamedToolCalls = new Map;
|
|
2193
2331
|
await consumeSSE(response, (data) => {
|
|
2194
2332
|
if (data === "[DONE]") {
|
|
2195
2333
|
return;
|
|
@@ -2205,6 +2343,8 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
|
|
|
2205
2343
|
const delta = pickResponsesStreamTextDelta(json);
|
|
2206
2344
|
const chunkUsage = pickResponsesStreamUsage(json);
|
|
2207
2345
|
const chunkFinishReason = pickResponsesStreamFinishReason(json);
|
|
2346
|
+
collectResponsesStreamToolCalls(json, streamedToolCalls);
|
|
2347
|
+
const chunkToolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
|
|
2208
2348
|
usage = preferLatestUsage(usage, chunkUsage);
|
|
2209
2349
|
if (chunkFinishReason) {
|
|
2210
2350
|
finishReason = chunkFinishReason;
|
|
@@ -2213,14 +2353,16 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
|
|
|
2213
2353
|
text += delta;
|
|
2214
2354
|
callbacks.onToken?.(delta);
|
|
2215
2355
|
}
|
|
2216
|
-
emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason);
|
|
2356
|
+
emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
|
|
2217
2357
|
});
|
|
2218
2358
|
const finalPayload = lastPayload ?? {};
|
|
2359
|
+
const toolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
|
|
2219
2360
|
const out = {
|
|
2220
2361
|
text: text.length > 0 ? text : pickResponsesText(finalPayload) || pickAssistantText(finalPayload),
|
|
2221
2362
|
raw: finalPayload,
|
|
2222
2363
|
usage: preferLatestUsage(usage, pickUsage(finalPayload)),
|
|
2223
|
-
finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload)
|
|
2364
|
+
finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload),
|
|
2365
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined
|
|
2224
2366
|
};
|
|
2225
2367
|
callbacks.onComplete?.(out);
|
|
2226
2368
|
return out;
|
|
@@ -2282,7 +2424,7 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
|
|
|
2282
2424
|
emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
|
|
2283
2425
|
});
|
|
2284
2426
|
const resolvedRoundUsage = preferLatestUsage(roundUsage, roundPayload ? pickUsage(roundPayload) : undefined);
|
|
2285
|
-
state.aggregatedUsage =
|
|
2427
|
+
state.aggregatedUsage = mergeUsage2(state.aggregatedUsage, resolvedRoundUsage);
|
|
2286
2428
|
if (roundFinishReason) {
|
|
2287
2429
|
state.finishReason = roundFinishReason;
|
|
2288
2430
|
} else if (roundPayload) {
|
|
@@ -2536,43 +2678,204 @@ function pickAssistantReasoningDeltaFieldName(payload) {
|
|
|
2536
2678
|
}
|
|
2537
2679
|
return;
|
|
2538
2680
|
}
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
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) {
|
|
2542
2747
|
return;
|
|
2543
2748
|
}
|
|
2544
|
-
|
|
2545
|
-
|
|
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) {
|
|
2546
2755
|
return;
|
|
2547
2756
|
}
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
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());
|
|
2551
2770
|
}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
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;
|
|
2561
2794
|
}
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
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);
|
|
2565
2827
|
}
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
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;
|
|
2570
2839
|
}
|
|
2571
|
-
const
|
|
2572
|
-
|
|
2573
|
-
|
|
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;
|
|
2845
|
+
}
|
|
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);
|
|
2574
2878
|
}
|
|
2575
|
-
state.set(toolIndex, existing);
|
|
2576
2879
|
}
|
|
2577
2880
|
}
|
|
2578
2881
|
function buildOpenAIStreamToolCalls(state) {
|
|
@@ -3020,7 +3323,7 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
|
|
|
3020
3323
|
}
|
|
3021
3324
|
const payload = await response.json();
|
|
3022
3325
|
lastPayload = payload;
|
|
3023
|
-
aggregatedUsage =
|
|
3326
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, pickUsage2(payload));
|
|
3024
3327
|
finishReason = pickFinishReason2(payload);
|
|
3025
3328
|
const content = Array.isArray(payload.content) ? payload.content : [];
|
|
3026
3329
|
const calledTools = pickAnthropicToolCalls(payload).filter((call) => call.type === "function");
|
|
@@ -3141,7 +3444,7 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
|
|
|
3141
3444
|
callbacks.onChunk?.(chunk);
|
|
3142
3445
|
}
|
|
3143
3446
|
});
|
|
3144
|
-
aggregatedUsage =
|
|
3447
|
+
aggregatedUsage = mergeUsage2(aggregatedUsage, roundUsage);
|
|
3145
3448
|
if (roundFinishReason) {
|
|
3146
3449
|
finishReason = roundFinishReason;
|
|
3147
3450
|
}
|
|
@@ -3800,95 +4103,6 @@ function withToolTimeout(client, toolTimeoutMs) {
|
|
|
3800
4103
|
function applyToolTimeout(clients, toolTimeoutMs) {
|
|
3801
4104
|
return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
|
|
3802
4105
|
}
|
|
3803
|
-
// src/generate-output.ts
|
|
3804
|
-
var RE_THINK_TAGS = /<\/?think\s*>/gi;
|
|
3805
|
-
function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
|
|
3806
|
-
const sanitized = sanitizeThink(text);
|
|
3807
|
-
const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
|
|
3808
|
-
const reasoning = joinReasoningSegments([
|
|
3809
|
-
dedicatedReasoning,
|
|
3810
|
-
...sanitized.thinkBlocks.map((block) => block.content)
|
|
3811
|
-
]);
|
|
3812
|
-
return {
|
|
3813
|
-
text: visibleText,
|
|
3814
|
-
reasoning,
|
|
3815
|
-
reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
|
|
3816
|
-
thinkBlocks: sanitized.thinkBlocks,
|
|
3817
|
-
parseSource: composeParseSource(visibleText, reasoning)
|
|
3818
|
-
};
|
|
3819
|
-
}
|
|
3820
|
-
function normalizeReasoningBlocks(blocks) {
|
|
3821
|
-
if (!Array.isArray(blocks)) {
|
|
3822
|
-
return;
|
|
3823
|
-
}
|
|
3824
|
-
const normalized = blocks.map((block) => ({
|
|
3825
|
-
turnIndex: block.turnIndex,
|
|
3826
|
-
text: block.text.replace(RE_THINK_TAGS, "").trim()
|
|
3827
|
-
})).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
|
|
3828
|
-
return normalized.length > 0 ? normalized : undefined;
|
|
3829
|
-
}
|
|
3830
|
-
function appendReasoningBlock(blocks, transition) {
|
|
3831
|
-
const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
|
|
3832
|
-
if (!text) {
|
|
3833
|
-
return blocks;
|
|
3834
|
-
}
|
|
3835
|
-
const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
|
|
3836
|
-
return normalizeReasoningBlocks(next);
|
|
3837
|
-
}
|
|
3838
|
-
function composeParseSource(text, reasoning) {
|
|
3839
|
-
if (typeof reasoning !== "string" || reasoning.length === 0) {
|
|
3840
|
-
return text;
|
|
3841
|
-
}
|
|
3842
|
-
const sanitized = reasoning.replace(RE_THINK_TAGS, "");
|
|
3843
|
-
if (sanitized.length === 0) {
|
|
3844
|
-
return text;
|
|
3845
|
-
}
|
|
3846
|
-
return `<think>${sanitized}</think>${text}`;
|
|
3847
|
-
}
|
|
3848
|
-
function aggregateUsage(attempts) {
|
|
3849
|
-
let usage;
|
|
3850
|
-
for (const attempt of attempts) {
|
|
3851
|
-
usage = mergeUsage2(usage, attempt.usage);
|
|
3852
|
-
}
|
|
3853
|
-
return usage;
|
|
3854
|
-
}
|
|
3855
|
-
function mergeUsage2(base, next) {
|
|
3856
|
-
if (!base && !next) {
|
|
3857
|
-
return;
|
|
3858
|
-
}
|
|
3859
|
-
return {
|
|
3860
|
-
inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
|
|
3861
|
-
outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
|
|
3862
|
-
totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
|
|
3863
|
-
cost: (base?.cost ?? 0) + (next?.cost ?? 0)
|
|
3864
|
-
};
|
|
3865
|
-
}
|
|
3866
|
-
function joinReasoningSegments(parts) {
|
|
3867
|
-
return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
|
|
3868
|
-
|
|
3869
|
-
`);
|
|
3870
|
-
}
|
|
3871
|
-
function stripThinkBlocks(text, thinkBlocks) {
|
|
3872
|
-
if (thinkBlocks.length === 0) {
|
|
3873
|
-
return text;
|
|
3874
|
-
}
|
|
3875
|
-
let output = "";
|
|
3876
|
-
let cursor = 0;
|
|
3877
|
-
for (const block of thinkBlocks) {
|
|
3878
|
-
output += text.slice(cursor, block.start);
|
|
3879
|
-
cursor = block.end;
|
|
3880
|
-
}
|
|
3881
|
-
output += text.slice(cursor);
|
|
3882
|
-
return output;
|
|
3883
|
-
}
|
|
3884
|
-
function toStreamDataFingerprint(value) {
|
|
3885
|
-
try {
|
|
3886
|
-
return JSON.stringify(value);
|
|
3887
|
-
} catch {
|
|
3888
|
-
return "__unserializable__";
|
|
3889
|
-
}
|
|
3890
|
-
}
|
|
3891
|
-
|
|
3892
4106
|
// src/utils/debug-colors.ts
|
|
3893
4107
|
var ANSI = {
|
|
3894
4108
|
reset: "\x1B[0m",
|
|
@@ -4050,13 +4264,15 @@ async function callModel(adapter, options) {
|
|
|
4050
4264
|
if (!done && fingerprint === lastSnapshotFingerprint) {
|
|
4051
4265
|
return;
|
|
4052
4266
|
}
|
|
4267
|
+
const stableText = done ? normalized2.text : withoutTrailingThinkTagPrefix(normalized2.text);
|
|
4268
|
+
const stableReasoning = done ? normalized2.reasoning : withoutTrailingThinkTagPrefix(normalized2.reasoning);
|
|
4053
4269
|
const delta = {
|
|
4054
|
-
text:
|
|
4055
|
-
reasoning:
|
|
4270
|
+
text: stableText.startsWith(previousSnapshotText) ? stableText.slice(previousSnapshotText.length) : "",
|
|
4271
|
+
reasoning: stableReasoning.startsWith(previousSnapshotReasoning) ? stableReasoning.slice(previousSnapshotReasoning.length) : ""
|
|
4056
4272
|
};
|
|
4057
4273
|
lastSnapshotFingerprint = fingerprint;
|
|
4058
|
-
previousSnapshotText =
|
|
4059
|
-
previousSnapshotReasoning =
|
|
4274
|
+
previousSnapshotText = stableText;
|
|
4275
|
+
previousSnapshotReasoning = stableReasoning;
|
|
4060
4276
|
options.stream.onData?.({
|
|
4061
4277
|
delta,
|
|
4062
4278
|
snapshot,
|