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/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 mergeUsage(base, next) {
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 delta = pickAssistantDelta(json);
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 = mergeUsage(aggregatedUsage, pickUsage(payload));
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 = mergeUsage(state.aggregatedUsage, pickUsage(payload));
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 delta = pickAssistantDelta(json);
2211
+ const rawDelta = pickAssistantDelta(json);
2090
2212
  const reasoningDelta = pickAssistantReasoningDelta(json);
2091
2213
  const chunkUsage = pickUsage(json);
2092
2214
  const chunkFinishReason = pickFinishReason(json);
2215
+ const nativeDelta = nativeToolCalls.push(rawDelta);
2216
+ const delta = nativeDelta.textDelta;
2093
2217
  collectOpenAIStreamToolCalls(json, streamedToolCalls);
2094
2218
  roundUsage = preferLatestUsage(roundUsage, chunkUsage);
2095
2219
  if (chunkFinishReason) {
@@ -2103,13 +2227,21 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2103
2227
  roundReasoning += reasoningDelta;
2104
2228
  reasoningFieldName ??= pickAssistantReasoningDeltaFieldName(json);
2105
2229
  }
2106
- emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2230
+ const streamedSnapshot = buildOpenAIStreamToolCalls(streamedToolCalls);
2231
+ const chunkToolCalls = nativeDelta.toolCalls.length > 0 ? mergeToolCalls(streamedSnapshot, nativeDelta.toolCalls) : streamedSnapshot.length > 0 ? streamedSnapshot : undefined;
2232
+ emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls);
2107
2233
  });
2108
- aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
2234
+ const tail = nativeToolCalls.flush();
2235
+ if (tail.textDelta) {
2236
+ roundText += tail.textDelta;
2237
+ callbacks.onToken?.(tail.textDelta);
2238
+ emitOpenAIStreamChunk(callbacks, round, {}, tail.textDelta, "", undefined, undefined);
2239
+ }
2240
+ aggregatedUsage = mergeUsage2(aggregatedUsage, roundUsage);
2109
2241
  if (roundFinishReason) {
2110
2242
  finishReason = roundFinishReason;
2111
2243
  }
2112
- const calledTools = buildOpenAIStreamToolCalls(streamedToolCalls);
2244
+ const calledTools = mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeToolCalls.calls);
2113
2245
  pushReasoningBlock(reasoningBlocks, round, roundReasoning);
2114
2246
  request.onTurnTransition?.({
2115
2247
  turnIndex: round,
@@ -2196,6 +2328,7 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2196
2328
  let usage;
2197
2329
  let finishReason;
2198
2330
  let lastPayload;
2331
+ const streamedToolCalls = new Map;
2199
2332
  await consumeSSE(response, (data) => {
2200
2333
  if (data === "[DONE]") {
2201
2334
  return;
@@ -2211,6 +2344,8 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2211
2344
  const delta = pickResponsesStreamTextDelta(json);
2212
2345
  const chunkUsage = pickResponsesStreamUsage(json);
2213
2346
  const chunkFinishReason = pickResponsesStreamFinishReason(json);
2347
+ collectResponsesStreamToolCalls(json, streamedToolCalls);
2348
+ const chunkToolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
2214
2349
  usage = preferLatestUsage(usage, chunkUsage);
2215
2350
  if (chunkFinishReason) {
2216
2351
  finishReason = chunkFinishReason;
@@ -2219,14 +2354,16 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2219
2354
  text += delta;
2220
2355
  callbacks.onToken?.(delta);
2221
2356
  }
2222
- emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason);
2357
+ emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
2223
2358
  });
2224
2359
  const finalPayload = lastPayload ?? {};
2360
+ const toolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
2225
2361
  const out = {
2226
2362
  text: text.length > 0 ? text : pickResponsesText(finalPayload) || pickAssistantText(finalPayload),
2227
2363
  raw: finalPayload,
2228
2364
  usage: preferLatestUsage(usage, pickUsage(finalPayload)),
2229
- finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload)
2365
+ finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload),
2366
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
2230
2367
  };
2231
2368
  callbacks.onComplete?.(out);
2232
2369
  return out;
@@ -2285,10 +2422,11 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2285
2422
  if (reasoningDelta) {
2286
2423
  roundReasoning += reasoningDelta;
2287
2424
  }
2288
- emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2425
+ const chunkToolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
2426
+ emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
2289
2427
  });
2290
2428
  const resolvedRoundUsage = preferLatestUsage(roundUsage, roundPayload ? pickUsage(roundPayload) : undefined);
2291
- state.aggregatedUsage = mergeUsage(state.aggregatedUsage, resolvedRoundUsage);
2429
+ state.aggregatedUsage = mergeUsage2(state.aggregatedUsage, resolvedRoundUsage);
2292
2430
  if (roundFinishReason) {
2293
2431
  state.finishReason = roundFinishReason;
2294
2432
  } else if (roundPayload) {
@@ -2542,43 +2680,204 @@ function pickAssistantReasoningDeltaFieldName(payload) {
2542
2680
  }
2543
2681
  return;
2544
2682
  }
2545
- function collectOpenAIStreamToolCalls(payload, state) {
2546
- const choices = payload.choices;
2547
- if (!Array.isArray(choices) || choices.length === 0 || !isRecord2(choices[0])) {
2683
+ var NATIVE_TOOL_CALL_OPEN = "<tool_call";
2684
+ var NATIVE_TOOL_CALL_CLOSE = "</tool_call>";
2685
+
2686
+ class NativeToolCallStreamState {
2687
+ enabled;
2688
+ calls = [];
2689
+ pending = "";
2690
+ constructor(enabled = true) {
2691
+ this.enabled = enabled;
2692
+ }
2693
+ push(delta) {
2694
+ if (!delta || !this.enabled) {
2695
+ return { textDelta: delta, toolCalls: [] };
2696
+ }
2697
+ this.pending += delta;
2698
+ return this.drain(false);
2699
+ }
2700
+ flush() {
2701
+ return this.enabled ? this.drain(true) : { textDelta: "", toolCalls: [] };
2702
+ }
2703
+ drain(flush) {
2704
+ let textDelta = "";
2705
+ const toolCalls = [];
2706
+ while (this.pending.length > 0) {
2707
+ const openIndex = this.pending.indexOf(NATIVE_TOOL_CALL_OPEN);
2708
+ if (openIndex < 0) {
2709
+ const keep = flush ? 0 : nativeToolCallPrefixSuffixLength(this.pending);
2710
+ const emitLength = this.pending.length - keep;
2711
+ if (emitLength > 0) {
2712
+ textDelta += this.pending.slice(0, emitLength);
2713
+ this.pending = this.pending.slice(emitLength);
2714
+ }
2715
+ break;
2716
+ }
2717
+ if (openIndex > 0) {
2718
+ textDelta += this.pending.slice(0, openIndex);
2719
+ this.pending = this.pending.slice(openIndex);
2720
+ continue;
2721
+ }
2722
+ const closeIndex = this.pending.indexOf(NATIVE_TOOL_CALL_CLOSE);
2723
+ if (closeIndex < 0) {
2724
+ if (flush) {
2725
+ this.pending = "";
2726
+ }
2727
+ break;
2728
+ }
2729
+ const blockEnd = closeIndex + NATIVE_TOOL_CALL_CLOSE.length;
2730
+ const call = parseNativeToolCallBlock(this.pending.slice(0, blockEnd), this.calls.length);
2731
+ if (call) {
2732
+ this.calls.push(call);
2733
+ toolCalls.push(call);
2734
+ }
2735
+ this.pending = this.pending.slice(blockEnd);
2736
+ }
2737
+ return { textDelta, toolCalls };
2738
+ }
2739
+ }
2740
+ function requestDeclaresTools(options, request) {
2741
+ const hasTools = (body) => Array.isArray(body?.tools) && body.tools.length > 0;
2742
+ return hasTools(request.body) || hasTools(options.defaultBody);
2743
+ }
2744
+ var NATIVE_FUNCTION_PATTERN = /<function=([^>\s]+)\s*>([\s\S]*?)<\/function>/;
2745
+ var NATIVE_PARAMETER_PATTERN = /<parameter=([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
2746
+ function parseNativeToolCallBlock(block, index) {
2747
+ const inner = extractNativeToolCallInner(block);
2748
+ if (inner === undefined) {
2548
2749
  return;
2549
2750
  }
2550
- const delta = choices[0].delta;
2551
- if (!isRecord2(delta) || !Array.isArray(delta.tool_calls)) {
2751
+ return parseNativeJsonToolCall(inner, index) ?? parseNativeXmlToolCall(inner, index);
2752
+ }
2753
+ function extractNativeToolCallInner(block) {
2754
+ const openEnd = block.indexOf(">");
2755
+ const closeStart = block.lastIndexOf(NATIVE_TOOL_CALL_CLOSE);
2756
+ if (openEnd < 0 || closeStart < 0 || closeStart <= openEnd) {
2552
2757
  return;
2553
2758
  }
2554
- for (const rawToolCall of delta.tool_calls) {
2555
- if (!isRecord2(rawToolCall)) {
2556
- continue;
2759
+ return block.slice(openEnd + 1, closeStart).trim();
2760
+ }
2761
+ function parseNativeXmlToolCall(inner, index) {
2762
+ const functionMatch = NATIVE_FUNCTION_PATTERN.exec(inner);
2763
+ const functionName = functionMatch?.[1];
2764
+ const functionBody = functionMatch?.[2];
2765
+ if (!functionName || functionBody === undefined) {
2766
+ return;
2767
+ }
2768
+ const args = {};
2769
+ for (const [, key, rawValue] of functionBody.matchAll(NATIVE_PARAMETER_PATTERN)) {
2770
+ if (key && rawValue !== undefined) {
2771
+ args[key] = coerceNativeParameterValue(rawValue.trim());
2557
2772
  }
2558
- const index = toFiniteNumber(rawToolCall.index);
2559
- const toolIndex = index !== undefined ? Math.floor(index) : 0;
2560
- const existing = state.get(toolIndex) ?? {
2561
- index: toolIndex,
2562
- argumentsText: ""
2563
- };
2564
- const id = pickString(rawToolCall.id);
2565
- if (id) {
2566
- existing.id = id;
2773
+ }
2774
+ return {
2775
+ id: `call_native_${index}`,
2776
+ type: "function",
2777
+ name: functionName,
2778
+ arguments: JSON.stringify(args)
2779
+ };
2780
+ }
2781
+ function coerceNativeParameterValue(value) {
2782
+ if (value.length === 0) {
2783
+ return "";
2784
+ }
2785
+ const parsed = safeJSONParse(value);
2786
+ if (parsed === null) {
2787
+ return value === "null" ? null : value;
2788
+ }
2789
+ return parsed;
2790
+ }
2791
+ function nativeToolCallPrefixSuffixLength(value) {
2792
+ const max = Math.min(value.length, NATIVE_TOOL_CALL_OPEN.length - 1);
2793
+ for (let length = max;length > 0; length -= 1) {
2794
+ if (NATIVE_TOOL_CALL_OPEN.startsWith(value.slice(-length))) {
2795
+ return length;
2567
2796
  }
2568
- const type = pickString(rawToolCall.type);
2569
- if (type) {
2570
- existing.type = type;
2797
+ }
2798
+ return 0;
2799
+ }
2800
+ function parseNativeJsonToolCall(inner, index) {
2801
+ const parsed = safeJSONParse(inner);
2802
+ if (!isRecord2(parsed)) {
2803
+ return;
2804
+ }
2805
+ const name = pickString(parsed.name) ?? pickString(parsed.function);
2806
+ if (!name) {
2807
+ return;
2808
+ }
2809
+ const rawArguments = parsed.arguments ?? parsed.parameters ?? {};
2810
+ const args = typeof rawArguments === "string" ? rawArguments : JSON.stringify(rawArguments);
2811
+ return {
2812
+ id: pickString(parsed.id) ?? `call_native_${index}`,
2813
+ type: "function",
2814
+ name,
2815
+ arguments: args
2816
+ };
2817
+ }
2818
+ function mergeToolCalls(...groups) {
2819
+ const merged = [];
2820
+ const seen = new Set;
2821
+ for (const group of groups) {
2822
+ for (const call of group) {
2823
+ const key = call.id || `${call.name ?? ""}:${String(call.arguments ?? "")}`;
2824
+ if (seen.has(key)) {
2825
+ continue;
2826
+ }
2827
+ seen.add(key);
2828
+ merged.push(call);
2571
2829
  }
2572
- const functionCall = isRecord2(rawToolCall.function) ? rawToolCall.function : undefined;
2573
- const name = pickString(functionCall?.name);
2574
- if (name) {
2575
- existing.name = `${existing.name ?? ""}${name}`;
2830
+ }
2831
+ return merged;
2832
+ }
2833
+ function collectOpenAIStreamToolCalls(payload, state) {
2834
+ const choices = payload.choices;
2835
+ if (!Array.isArray(choices) || choices.length === 0) {
2836
+ return;
2837
+ }
2838
+ for (const choice of choices) {
2839
+ if (!isRecord2(choice)) {
2840
+ continue;
2576
2841
  }
2577
- const argumentsDelta = pickString(functionCall?.arguments);
2578
- if (argumentsDelta) {
2579
- existing.argumentsText += argumentsDelta;
2842
+ const delta = isRecord2(choice.delta) ? choice.delta : undefined;
2843
+ const message = isRecord2(choice.message) ? choice.message : undefined;
2844
+ const toolCalls = Array.isArray(delta?.tool_calls) ? delta.tool_calls : Array.isArray(message?.tool_calls) ? message.tool_calls : Array.isArray(choice.tool_calls) ? choice.tool_calls : undefined;
2845
+ if (!toolCalls) {
2846
+ continue;
2847
+ }
2848
+ for (const rawToolCall of toolCalls) {
2849
+ if (!isRecord2(rawToolCall)) {
2850
+ continue;
2851
+ }
2852
+ const index = toFiniteNumber(rawToolCall.index);
2853
+ const toolIndex = index !== undefined ? Math.floor(index) : state.size;
2854
+ const existing = state.get(toolIndex) ?? {
2855
+ index: toolIndex,
2856
+ argumentsText: ""
2857
+ };
2858
+ const id = pickString(rawToolCall.id);
2859
+ if (id) {
2860
+ existing.id = id;
2861
+ }
2862
+ const type = pickString(rawToolCall.type);
2863
+ if (type) {
2864
+ existing.type = type;
2865
+ }
2866
+ const functionCall = isRecord2(rawToolCall.function) ? rawToolCall.function : undefined;
2867
+ const name = pickString(functionCall?.name);
2868
+ if (name) {
2869
+ existing.name = `${existing.name ?? ""}${name}`;
2870
+ }
2871
+ const argumentsDelta = pickString(functionCall?.arguments);
2872
+ if (argumentsDelta) {
2873
+ if (message?.tool_calls === toolCalls || choice.tool_calls === toolCalls) {
2874
+ existing.argumentsText = argumentsDelta;
2875
+ } else {
2876
+ existing.argumentsText += argumentsDelta;
2877
+ }
2878
+ }
2879
+ state.set(toolIndex, existing);
2580
2880
  }
2581
- state.set(toolIndex, existing);
2582
2881
  }
2583
2882
  }
2584
2883
  function buildOpenAIStreamToolCalls(state) {
@@ -2929,6 +3228,7 @@ async function streamPassThrough(options, fetcher, path, request, callbacks) {
2929
3228
  let text = "";
2930
3229
  let usage;
2931
3230
  let finishReason;
3231
+ const streamedToolCalls = new Map;
2932
3232
  await consumeSSE(response, (data) => {
2933
3233
  if (data === "[DONE]") {
2934
3234
  return;
@@ -2940,6 +3240,7 @@ async function streamPassThrough(options, fetcher, path, request, callbacks) {
2940
3240
  const delta = pickAnthropicDelta(json);
2941
3241
  const chunkUsage = pickUsage2(json);
2942
3242
  const chunkFinishReason = pickFinishReason2(json);
3243
+ collectAnthropicStreamToolCalls(json, streamedToolCalls);
2943
3244
  usage = preferLatestUsage(usage, chunkUsage);
2944
3245
  if (chunkFinishReason) {
2945
3246
  finishReason = chunkFinishReason;
@@ -2948,16 +3249,25 @@ async function streamPassThrough(options, fetcher, path, request, callbacks) {
2948
3249
  text += delta;
2949
3250
  callbacks.onToken?.(delta);
2950
3251
  }
2951
- if (delta || chunkUsage || chunkFinishReason) {
3252
+ const streamedSnapshot = buildAnthropicStreamToolCalls(streamedToolCalls);
3253
+ const chunkToolCalls = streamedSnapshot.length > 0 ? streamedSnapshot : undefined;
3254
+ if (delta || chunkUsage || chunkFinishReason || chunkToolCalls) {
2952
3255
  callbacks.onChunk?.({
2953
3256
  textDelta: delta,
2954
3257
  raw: json,
2955
3258
  usage: chunkUsage,
2956
- finishReason: chunkFinishReason
3259
+ finishReason: chunkFinishReason,
3260
+ toolCalls: chunkToolCalls
2957
3261
  });
2958
3262
  }
2959
3263
  });
2960
- const out = { text, usage, finishReason };
3264
+ const toolCalls = buildAnthropicStreamToolCalls(streamedToolCalls);
3265
+ const out = {
3266
+ text,
3267
+ usage,
3268
+ finishReason: finishReason ?? (toolCalls.length > 0 ? "tool_use" : undefined),
3269
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
3270
+ };
2961
3271
  callbacks.onComplete?.(out);
2962
3272
  return out;
2963
3273
  }
@@ -3026,7 +3336,7 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
3026
3336
  }
3027
3337
  const payload = await response.json();
3028
3338
  lastPayload = payload;
3029
- aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage2(payload));
3339
+ aggregatedUsage = mergeUsage2(aggregatedUsage, pickUsage2(payload));
3030
3340
  finishReason = pickFinishReason2(payload);
3031
3341
  const content = Array.isArray(payload.content) ? payload.content : [];
3032
3342
  const calledTools = pickAnthropicToolCalls(payload).filter((call) => call.type === "function");
@@ -3135,19 +3445,22 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3135
3445
  if (reasoningDelta) {
3136
3446
  roundReasoning += reasoningDelta;
3137
3447
  }
3138
- if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
3448
+ const streamedSnapshot = buildAnthropicStreamToolCalls(streamedToolCalls);
3449
+ const chunkToolCalls = streamedSnapshot.length > 0 ? streamedSnapshot : undefined;
3450
+ if (delta || reasoningDelta || chunkUsage || chunkFinishReason || chunkToolCalls) {
3139
3451
  const chunk = {
3140
3452
  textDelta: delta,
3141
3453
  reasoningDelta: reasoningDelta || undefined,
3142
3454
  turnIndex: round,
3143
3455
  raw: json,
3144
3456
  usage: chunkUsage,
3145
- finishReason: chunkFinishReason
3457
+ finishReason: chunkFinishReason,
3458
+ toolCalls: chunkToolCalls
3146
3459
  };
3147
3460
  callbacks.onChunk?.(chunk);
3148
3461
  }
3149
3462
  });
3150
- aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
3463
+ aggregatedUsage = mergeUsage2(aggregatedUsage, roundUsage);
3151
3464
  if (roundFinishReason) {
3152
3465
  finishReason = roundFinishReason;
3153
3466
  }
@@ -3806,95 +4119,6 @@ function withToolTimeout(client, toolTimeoutMs) {
3806
4119
  function applyToolTimeout(clients, toolTimeoutMs) {
3807
4120
  return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
3808
4121
  }
3809
- // src/generate-output.ts
3810
- var RE_THINK_TAGS = /<\/?think\s*>/gi;
3811
- function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
3812
- const sanitized = sanitizeThink(text);
3813
- const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
3814
- const reasoning = joinReasoningSegments([
3815
- dedicatedReasoning,
3816
- ...sanitized.thinkBlocks.map((block) => block.content)
3817
- ]);
3818
- return {
3819
- text: visibleText,
3820
- reasoning,
3821
- reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
3822
- thinkBlocks: sanitized.thinkBlocks,
3823
- parseSource: composeParseSource(visibleText, reasoning)
3824
- };
3825
- }
3826
- function normalizeReasoningBlocks(blocks) {
3827
- if (!Array.isArray(blocks)) {
3828
- return;
3829
- }
3830
- const normalized = blocks.map((block) => ({
3831
- turnIndex: block.turnIndex,
3832
- text: block.text.replace(RE_THINK_TAGS, "").trim()
3833
- })).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
3834
- return normalized.length > 0 ? normalized : undefined;
3835
- }
3836
- function appendReasoningBlock(blocks, transition) {
3837
- const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
3838
- if (!text) {
3839
- return blocks;
3840
- }
3841
- const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
3842
- return normalizeReasoningBlocks(next);
3843
- }
3844
- function composeParseSource(text, reasoning) {
3845
- if (typeof reasoning !== "string" || reasoning.length === 0) {
3846
- return text;
3847
- }
3848
- const sanitized = reasoning.replace(RE_THINK_TAGS, "");
3849
- if (sanitized.length === 0) {
3850
- return text;
3851
- }
3852
- return `<think>${sanitized}</think>${text}`;
3853
- }
3854
- function aggregateUsage(attempts) {
3855
- let usage;
3856
- for (const attempt of attempts) {
3857
- usage = mergeUsage2(usage, attempt.usage);
3858
- }
3859
- return usage;
3860
- }
3861
- function mergeUsage2(base, next) {
3862
- if (!base && !next) {
3863
- return;
3864
- }
3865
- return {
3866
- inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
3867
- outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
3868
- totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
3869
- cost: (base?.cost ?? 0) + (next?.cost ?? 0)
3870
- };
3871
- }
3872
- function joinReasoningSegments(parts) {
3873
- return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
3874
-
3875
- `);
3876
- }
3877
- function stripThinkBlocks(text, thinkBlocks) {
3878
- if (thinkBlocks.length === 0) {
3879
- return text;
3880
- }
3881
- let output = "";
3882
- let cursor = 0;
3883
- for (const block of thinkBlocks) {
3884
- output += text.slice(cursor, block.start);
3885
- cursor = block.end;
3886
- }
3887
- output += text.slice(cursor);
3888
- return output;
3889
- }
3890
- function toStreamDataFingerprint(value) {
3891
- try {
3892
- return JSON.stringify(value);
3893
- } catch {
3894
- return "__unserializable__";
3895
- }
3896
- }
3897
-
3898
4122
  // src/utils/debug-colors.ts
3899
4123
  var ANSI = {
3900
4124
  reset: "\x1B[0m",
@@ -4056,13 +4280,15 @@ async function callModel(adapter, options) {
4056
4280
  if (!done && fingerprint === lastSnapshotFingerprint) {
4057
4281
  return;
4058
4282
  }
4283
+ const stableText = done ? normalized2.text : withoutTrailingThinkTagPrefix(normalized2.text);
4284
+ const stableReasoning = done ? normalized2.reasoning : withoutTrailingThinkTagPrefix(normalized2.reasoning);
4059
4285
  const delta = {
4060
- text: normalized2.text.startsWith(previousSnapshotText) ? normalized2.text.slice(previousSnapshotText.length) : "",
4061
- reasoning: normalized2.reasoning.startsWith(previousSnapshotReasoning) ? normalized2.reasoning.slice(previousSnapshotReasoning.length) : ""
4286
+ text: stableText.startsWith(previousSnapshotText) ? stableText.slice(previousSnapshotText.length) : "",
4287
+ reasoning: stableReasoning.startsWith(previousSnapshotReasoning) ? stableReasoning.slice(previousSnapshotReasoning.length) : ""
4062
4288
  };
4063
4289
  lastSnapshotFingerprint = fingerprint;
4064
- previousSnapshotText = normalized2.text;
4065
- previousSnapshotReasoning = normalized2.reasoning;
4290
+ previousSnapshotText = stableText;
4291
+ previousSnapshotReasoning = stableReasoning;
4066
4292
  options.stream.onData?.({
4067
4293
  delta,
4068
4294
  snapshot,