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.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 mergeUsage(base, next) {
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 delta = pickAssistantDelta(json);
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 = mergeUsage(aggregatedUsage, pickUsage(payload));
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 = mergeUsage(state.aggregatedUsage, pickUsage(payload));
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 delta = pickAssistantDelta(json);
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
- emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
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
- aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
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
- emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
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 = mergeUsage(state.aggregatedUsage, resolvedRoundUsage);
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
- function collectOpenAIStreamToolCalls(payload, state) {
2455
- const choices = payload.choices;
2456
- if (!Array.isArray(choices) || choices.length === 0 || !isRecord2(choices[0])) {
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
- const delta = choices[0].delta;
2460
- if (!isRecord2(delta) || !Array.isArray(delta.tool_calls)) {
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
- for (const rawToolCall of delta.tool_calls) {
2464
- if (!isRecord2(rawToolCall)) {
2465
- continue;
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
- const index = toFiniteNumber(rawToolCall.index);
2468
- const toolIndex = index !== undefined ? Math.floor(index) : 0;
2469
- const existing = state.get(toolIndex) ?? {
2470
- index: toolIndex,
2471
- argumentsText: ""
2472
- };
2473
- const id = pickString(rawToolCall.id);
2474
- if (id) {
2475
- existing.id = id;
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
- const type = pickString(rawToolCall.type);
2478
- if (type) {
2479
- existing.type = type;
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
- const functionCall = isRecord2(rawToolCall.function) ? rawToolCall.function : undefined;
2482
- const name = pickString(functionCall?.name);
2483
- if (name) {
2484
- existing.name = `${existing.name ?? ""}${name}`;
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 argumentsDelta = pickString(functionCall?.arguments);
2487
- if (argumentsDelta) {
2488
- existing.argumentsText += argumentsDelta;
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
- if (delta || chunkUsage || chunkFinishReason) {
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 out = { text, usage, finishReason };
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 = mergeUsage(aggregatedUsage, pickUsage2(payload));
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
- if (delta || reasoningDelta || chunkUsage || chunkFinishReason) {
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 = mergeUsage(aggregatedUsage, roundUsage);
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: normalized2.text.startsWith(previousSnapshotText) ? normalized2.text.slice(previousSnapshotText.length) : "",
3970
- reasoning: normalized2.reasoning.startsWith(previousSnapshotReasoning) ? normalized2.reasoning.slice(previousSnapshotReasoning.length) : ""
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 = normalized2.text;
3974
- previousSnapshotReasoning = normalized2.reasoning;
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,