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/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
  }
@@ -1619,7 +1723,8 @@ function createOpenAICompatibleAdapter(options) {
1619
1723
  async function streamWithChatCompletionsPassThrough(options, fetcher, path, request, callbacks) {
1620
1724
  const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1621
1725
  messages: buildMessages(request),
1622
- stream: true
1726
+ stream: true,
1727
+ stream_options: streamUsageOptions(request)
1623
1728
  }));
1624
1729
  if (!response.ok) {
1625
1730
  const message = await response.text();
@@ -1630,6 +1735,8 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
1630
1735
  let reasoning = "";
1631
1736
  let usage;
1632
1737
  let finishReason;
1738
+ const streamedToolCalls = new Map;
1739
+ const nativeToolCalls = new NativeToolCallStreamState(requestDeclaresTools(options, request));
1633
1740
  await consumeSSE(response, (data) => {
1634
1741
  if (data === "[DONE]") {
1635
1742
  return;
@@ -1638,10 +1745,14 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
1638
1745
  if (!isRecord2(json)) {
1639
1746
  return;
1640
1747
  }
1641
- const delta = pickAssistantDelta(json);
1748
+ const rawDelta = pickAssistantDelta(json);
1642
1749
  const reasoningDelta = pickAssistantReasoningDelta(json);
1643
1750
  const chunkUsage = pickUsage(json);
1644
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);
1645
1756
  usage = preferLatestUsage(usage, chunkUsage);
1646
1757
  if (chunkFinishReason) {
1647
1758
  finishReason = chunkFinishReason;
@@ -1653,13 +1764,21 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
1653
1764
  if (reasoningDelta) {
1654
1765
  reasoning += reasoningDelta;
1655
1766
  }
1656
- emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
1767
+ emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
1657
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);
1658
1776
  const out = {
1659
1777
  text,
1660
1778
  reasoning: reasoning.length > 0 ? reasoning : undefined,
1661
1779
  usage,
1662
- finishReason
1780
+ finishReason: finishReason ?? (toolCalls.length > 0 ? "tool_calls" : undefined),
1781
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
1663
1782
  };
1664
1783
  callbacks.onComplete?.(out);
1665
1784
  return out;
@@ -1711,6 +1830,10 @@ async function completeOpenAIRequest(options, fetcher, chatPath, responsesPath,
1711
1830
  function buildChatCompletionsBody(options, request, overrides) {
1712
1831
  return buildOpenAIRequestBody(options, request, "max_tokens", overrides);
1713
1832
  }
1833
+ function streamUsageOptions(request) {
1834
+ const userOptions = isRecord2(request.body?.stream_options) ? request.body.stream_options : undefined;
1835
+ return { include_usage: true, ...userOptions };
1836
+ }
1714
1837
  function buildResponsesBody(options, request, overrides) {
1715
1838
  return buildOpenAIRequestBody(options, request, "max_output_tokens", overrides);
1716
1839
  }
@@ -1765,15 +1888,16 @@ function buildResponsesMCPResult(state, text, raw) {
1765
1888
  toolExecutions: state.toolExecutions.length > 0 ? state.toolExecutions : undefined
1766
1889
  };
1767
1890
  }
1768
- function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason) {
1769
- if (delta || reasoningDelta || usage || finishReason) {
1891
+ function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason, toolCalls) {
1892
+ if (delta || reasoningDelta || usage || finishReason || toolCalls) {
1770
1893
  callbacks.onChunk?.({
1771
1894
  textDelta: delta,
1772
1895
  reasoningDelta: reasoningDelta || undefined,
1773
1896
  ...round !== undefined ? { turnIndex: round } : {},
1774
1897
  raw,
1775
1898
  usage,
1776
- finishReason
1899
+ finishReason,
1900
+ toolCalls
1777
1901
  });
1778
1902
  }
1779
1903
  }
@@ -1840,7 +1964,7 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1840
1964
  parallel_tool_calls: request.parallelToolCalls
1841
1965
  }));
1842
1966
  lastPayload = payload;
1843
- aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage(payload));
1967
+ aggregatedUsage = mergeUsage2(aggregatedUsage, pickUsage(payload));
1844
1968
  finishReason = pickFinishReason(payload);
1845
1969
  const assistantMessage = pickAssistantMessage(payload);
1846
1970
  const calledTools = pickChatToolCalls(payload);
@@ -1920,7 +2044,7 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
1920
2044
  parallel_tool_calls: request.parallelToolCalls
1921
2045
  }));
1922
2046
  state.lastPayload = payload;
1923
- state.aggregatedUsage = mergeUsage(state.aggregatedUsage, pickUsage(payload));
2047
+ state.aggregatedUsage = mergeUsage2(state.aggregatedUsage, pickUsage(payload));
1924
2048
  state.finishReason = pickResponsesFinishReason(payload) ?? state.finishReason;
1925
2049
  pushReasoningBlock(state.reasoningBlocks, round, pickResponsesReasoning(payload));
1926
2050
  const providerToolCalls = pickResponsesToolCalls(payload);
@@ -1968,7 +2092,8 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
1968
2092
  tools: transportTools,
1969
2093
  tool_choice: request.toolChoice,
1970
2094
  parallel_tool_calls: request.parallelToolCalls,
1971
- stream: true
2095
+ stream: true,
2096
+ stream_options: streamUsageOptions(request)
1972
2097
  }));
1973
2098
  if (!response.ok) {
1974
2099
  const message = await response.text();
@@ -1979,6 +2104,7 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
1979
2104
  let roundUsage;
1980
2105
  let roundFinishReason;
1981
2106
  const streamedToolCalls = new Map;
2107
+ const nativeToolCalls = new NativeToolCallStreamState;
1982
2108
  let reasoningFieldName;
1983
2109
  await consumeSSE(response, (data) => {
1984
2110
  if (data === "[DONE]") {
@@ -1989,10 +2115,12 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
1989
2115
  return;
1990
2116
  }
1991
2117
  lastPayload = json;
1992
- const delta = pickAssistantDelta(json);
2118
+ const rawDelta = pickAssistantDelta(json);
1993
2119
  const reasoningDelta = pickAssistantReasoningDelta(json);
1994
2120
  const chunkUsage = pickUsage(json);
1995
2121
  const chunkFinishReason = pickFinishReason(json);
2122
+ const nativeDelta = nativeToolCalls.push(rawDelta);
2123
+ const delta = nativeDelta.textDelta;
1996
2124
  collectOpenAIStreamToolCalls(json, streamedToolCalls);
1997
2125
  roundUsage = preferLatestUsage(roundUsage, chunkUsage);
1998
2126
  if (chunkFinishReason) {
@@ -2006,13 +2134,20 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2006
2134
  roundReasoning += reasoningDelta;
2007
2135
  reasoningFieldName ??= pickAssistantReasoningDeltaFieldName(json);
2008
2136
  }
2009
- emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2137
+ const chunkToolCalls = nativeDelta.toolCalls.length > 0 ? mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeDelta.toolCalls) : undefined;
2138
+ emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls);
2010
2139
  });
2011
- aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
2140
+ const tail = nativeToolCalls.flush();
2141
+ if (tail.textDelta) {
2142
+ roundText += tail.textDelta;
2143
+ callbacks.onToken?.(tail.textDelta);
2144
+ emitOpenAIStreamChunk(callbacks, round, {}, tail.textDelta, "", undefined, undefined);
2145
+ }
2146
+ aggregatedUsage = mergeUsage2(aggregatedUsage, roundUsage);
2012
2147
  if (roundFinishReason) {
2013
2148
  finishReason = roundFinishReason;
2014
2149
  }
2015
- const calledTools = buildOpenAIStreamToolCalls(streamedToolCalls);
2150
+ const calledTools = mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeToolCalls.calls);
2016
2151
  pushReasoningBlock(reasoningBlocks, round, roundReasoning);
2017
2152
  request.onTurnTransition?.({
2018
2153
  turnIndex: round,
@@ -2099,6 +2234,7 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2099
2234
  let usage;
2100
2235
  let finishReason;
2101
2236
  let lastPayload;
2237
+ const streamedToolCalls = new Map;
2102
2238
  await consumeSSE(response, (data) => {
2103
2239
  if (data === "[DONE]") {
2104
2240
  return;
@@ -2114,6 +2250,8 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2114
2250
  const delta = pickResponsesStreamTextDelta(json);
2115
2251
  const chunkUsage = pickResponsesStreamUsage(json);
2116
2252
  const chunkFinishReason = pickResponsesStreamFinishReason(json);
2253
+ collectResponsesStreamToolCalls(json, streamedToolCalls);
2254
+ const chunkToolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
2117
2255
  usage = preferLatestUsage(usage, chunkUsage);
2118
2256
  if (chunkFinishReason) {
2119
2257
  finishReason = chunkFinishReason;
@@ -2122,14 +2260,16 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2122
2260
  text += delta;
2123
2261
  callbacks.onToken?.(delta);
2124
2262
  }
2125
- emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason);
2263
+ emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
2126
2264
  });
2127
2265
  const finalPayload = lastPayload ?? {};
2266
+ const toolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
2128
2267
  const out = {
2129
2268
  text: text.length > 0 ? text : pickResponsesText(finalPayload) || pickAssistantText(finalPayload),
2130
2269
  raw: finalPayload,
2131
2270
  usage: preferLatestUsage(usage, pickUsage(finalPayload)),
2132
- finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload)
2271
+ finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload),
2272
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
2133
2273
  };
2134
2274
  callbacks.onComplete?.(out);
2135
2275
  return out;
@@ -2191,7 +2331,7 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2191
2331
  emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2192
2332
  });
2193
2333
  const resolvedRoundUsage = preferLatestUsage(roundUsage, roundPayload ? pickUsage(roundPayload) : undefined);
2194
- state.aggregatedUsage = mergeUsage(state.aggregatedUsage, resolvedRoundUsage);
2334
+ state.aggregatedUsage = mergeUsage2(state.aggregatedUsage, resolvedRoundUsage);
2195
2335
  if (roundFinishReason) {
2196
2336
  state.finishReason = roundFinishReason;
2197
2337
  } else if (roundPayload) {
@@ -2445,43 +2585,204 @@ function pickAssistantReasoningDeltaFieldName(payload) {
2445
2585
  }
2446
2586
  return;
2447
2587
  }
2448
- function collectOpenAIStreamToolCalls(payload, state) {
2449
- const choices = payload.choices;
2450
- if (!Array.isArray(choices) || choices.length === 0 || !isRecord2(choices[0])) {
2588
+ var NATIVE_TOOL_CALL_OPEN = "<tool_call";
2589
+ var NATIVE_TOOL_CALL_CLOSE = "</tool_call>";
2590
+
2591
+ class NativeToolCallStreamState {
2592
+ enabled;
2593
+ calls = [];
2594
+ pending = "";
2595
+ constructor(enabled = true) {
2596
+ this.enabled = enabled;
2597
+ }
2598
+ push(delta) {
2599
+ if (!delta || !this.enabled) {
2600
+ return { textDelta: delta, toolCalls: [] };
2601
+ }
2602
+ this.pending += delta;
2603
+ return this.drain(false);
2604
+ }
2605
+ flush() {
2606
+ return this.enabled ? this.drain(true) : { textDelta: "", toolCalls: [] };
2607
+ }
2608
+ drain(flush) {
2609
+ let textDelta = "";
2610
+ const toolCalls = [];
2611
+ while (this.pending.length > 0) {
2612
+ const openIndex = this.pending.indexOf(NATIVE_TOOL_CALL_OPEN);
2613
+ if (openIndex < 0) {
2614
+ const keep = flush ? 0 : nativeToolCallPrefixSuffixLength(this.pending);
2615
+ const emitLength = this.pending.length - keep;
2616
+ if (emitLength > 0) {
2617
+ textDelta += this.pending.slice(0, emitLength);
2618
+ this.pending = this.pending.slice(emitLength);
2619
+ }
2620
+ break;
2621
+ }
2622
+ if (openIndex > 0) {
2623
+ textDelta += this.pending.slice(0, openIndex);
2624
+ this.pending = this.pending.slice(openIndex);
2625
+ continue;
2626
+ }
2627
+ const closeIndex = this.pending.indexOf(NATIVE_TOOL_CALL_CLOSE);
2628
+ if (closeIndex < 0) {
2629
+ if (flush) {
2630
+ this.pending = "";
2631
+ }
2632
+ break;
2633
+ }
2634
+ const blockEnd = closeIndex + NATIVE_TOOL_CALL_CLOSE.length;
2635
+ const call = parseNativeToolCallBlock(this.pending.slice(0, blockEnd), this.calls.length);
2636
+ if (call) {
2637
+ this.calls.push(call);
2638
+ toolCalls.push(call);
2639
+ }
2640
+ this.pending = this.pending.slice(blockEnd);
2641
+ }
2642
+ return { textDelta, toolCalls };
2643
+ }
2644
+ }
2645
+ function requestDeclaresTools(options, request) {
2646
+ const hasTools = (body) => Array.isArray(body?.tools) && body.tools.length > 0;
2647
+ return hasTools(request.body) || hasTools(options.defaultBody);
2648
+ }
2649
+ var NATIVE_FUNCTION_PATTERN = /<function=([^>\s]+)\s*>([\s\S]*?)<\/function>/;
2650
+ var NATIVE_PARAMETER_PATTERN = /<parameter=([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
2651
+ function parseNativeToolCallBlock(block, index) {
2652
+ const inner = extractNativeToolCallInner(block);
2653
+ if (inner === undefined) {
2451
2654
  return;
2452
2655
  }
2453
- const delta = choices[0].delta;
2454
- if (!isRecord2(delta) || !Array.isArray(delta.tool_calls)) {
2656
+ return parseNativeJsonToolCall(inner, index) ?? parseNativeXmlToolCall(inner, index);
2657
+ }
2658
+ function extractNativeToolCallInner(block) {
2659
+ const openEnd = block.indexOf(">");
2660
+ const closeStart = block.lastIndexOf(NATIVE_TOOL_CALL_CLOSE);
2661
+ if (openEnd < 0 || closeStart < 0 || closeStart <= openEnd) {
2455
2662
  return;
2456
2663
  }
2457
- for (const rawToolCall of delta.tool_calls) {
2458
- if (!isRecord2(rawToolCall)) {
2459
- continue;
2664
+ return block.slice(openEnd + 1, closeStart).trim();
2665
+ }
2666
+ function parseNativeXmlToolCall(inner, index) {
2667
+ const functionMatch = NATIVE_FUNCTION_PATTERN.exec(inner);
2668
+ const functionName = functionMatch?.[1];
2669
+ const functionBody = functionMatch?.[2];
2670
+ if (!functionName || functionBody === undefined) {
2671
+ return;
2672
+ }
2673
+ const args = {};
2674
+ for (const [, key, rawValue] of functionBody.matchAll(NATIVE_PARAMETER_PATTERN)) {
2675
+ if (key && rawValue !== undefined) {
2676
+ args[key] = coerceNativeParameterValue(rawValue.trim());
2460
2677
  }
2461
- const index = toFiniteNumber(rawToolCall.index);
2462
- const toolIndex = index !== undefined ? Math.floor(index) : 0;
2463
- const existing = state.get(toolIndex) ?? {
2464
- index: toolIndex,
2465
- argumentsText: ""
2466
- };
2467
- const id = pickString(rawToolCall.id);
2468
- if (id) {
2469
- existing.id = id;
2678
+ }
2679
+ return {
2680
+ id: `call_native_${index}`,
2681
+ type: "function",
2682
+ name: functionName,
2683
+ arguments: JSON.stringify(args)
2684
+ };
2685
+ }
2686
+ function coerceNativeParameterValue(value) {
2687
+ if (value.length === 0) {
2688
+ return "";
2689
+ }
2690
+ const parsed = safeJSONParse(value);
2691
+ if (parsed === null) {
2692
+ return value === "null" ? null : value;
2693
+ }
2694
+ return parsed;
2695
+ }
2696
+ function nativeToolCallPrefixSuffixLength(value) {
2697
+ const max = Math.min(value.length, NATIVE_TOOL_CALL_OPEN.length - 1);
2698
+ for (let length = max;length > 0; length -= 1) {
2699
+ if (NATIVE_TOOL_CALL_OPEN.startsWith(value.slice(-length))) {
2700
+ return length;
2470
2701
  }
2471
- const type = pickString(rawToolCall.type);
2472
- if (type) {
2473
- existing.type = type;
2702
+ }
2703
+ return 0;
2704
+ }
2705
+ function parseNativeJsonToolCall(inner, index) {
2706
+ const parsed = safeJSONParse(inner);
2707
+ if (!isRecord2(parsed)) {
2708
+ return;
2709
+ }
2710
+ const name = pickString(parsed.name) ?? pickString(parsed.function);
2711
+ if (!name) {
2712
+ return;
2713
+ }
2714
+ const rawArguments = parsed.arguments ?? parsed.parameters ?? {};
2715
+ const args = typeof rawArguments === "string" ? rawArguments : JSON.stringify(rawArguments);
2716
+ return {
2717
+ id: pickString(parsed.id) ?? `call_native_${index}`,
2718
+ type: "function",
2719
+ name,
2720
+ arguments: args
2721
+ };
2722
+ }
2723
+ function mergeToolCalls(...groups) {
2724
+ const merged = [];
2725
+ const seen = new Set;
2726
+ for (const group of groups) {
2727
+ for (const call of group) {
2728
+ const key = call.id || `${call.name ?? ""}:${String(call.arguments ?? "")}`;
2729
+ if (seen.has(key)) {
2730
+ continue;
2731
+ }
2732
+ seen.add(key);
2733
+ merged.push(call);
2474
2734
  }
2475
- const functionCall = isRecord2(rawToolCall.function) ? rawToolCall.function : undefined;
2476
- const name = pickString(functionCall?.name);
2477
- if (name) {
2478
- existing.name = `${existing.name ?? ""}${name}`;
2735
+ }
2736
+ return merged;
2737
+ }
2738
+ function collectOpenAIStreamToolCalls(payload, state) {
2739
+ const choices = payload.choices;
2740
+ if (!Array.isArray(choices) || choices.length === 0) {
2741
+ return;
2742
+ }
2743
+ for (const choice of choices) {
2744
+ if (!isRecord2(choice)) {
2745
+ continue;
2479
2746
  }
2480
- const argumentsDelta = pickString(functionCall?.arguments);
2481
- if (argumentsDelta) {
2482
- existing.argumentsText += argumentsDelta;
2747
+ const delta = isRecord2(choice.delta) ? choice.delta : undefined;
2748
+ const message = isRecord2(choice.message) ? choice.message : undefined;
2749
+ 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;
2750
+ if (!toolCalls) {
2751
+ continue;
2752
+ }
2753
+ for (const rawToolCall of toolCalls) {
2754
+ if (!isRecord2(rawToolCall)) {
2755
+ continue;
2756
+ }
2757
+ const index = toFiniteNumber(rawToolCall.index);
2758
+ const toolIndex = index !== undefined ? Math.floor(index) : state.size;
2759
+ const existing = state.get(toolIndex) ?? {
2760
+ index: toolIndex,
2761
+ argumentsText: ""
2762
+ };
2763
+ const id = pickString(rawToolCall.id);
2764
+ if (id) {
2765
+ existing.id = id;
2766
+ }
2767
+ const type = pickString(rawToolCall.type);
2768
+ if (type) {
2769
+ existing.type = type;
2770
+ }
2771
+ const functionCall = isRecord2(rawToolCall.function) ? rawToolCall.function : undefined;
2772
+ const name = pickString(functionCall?.name);
2773
+ if (name) {
2774
+ existing.name = `${existing.name ?? ""}${name}`;
2775
+ }
2776
+ const argumentsDelta = pickString(functionCall?.arguments);
2777
+ if (argumentsDelta) {
2778
+ if (message?.tool_calls === toolCalls || choice.tool_calls === toolCalls) {
2779
+ existing.argumentsText = argumentsDelta;
2780
+ } else {
2781
+ existing.argumentsText += argumentsDelta;
2782
+ }
2783
+ }
2784
+ state.set(toolIndex, existing);
2483
2785
  }
2484
- state.set(toolIndex, existing);
2485
2786
  }
2486
2787
  }
2487
2788
  function buildOpenAIStreamToolCalls(state) {
@@ -2929,7 +3230,7 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2929
3230
  }
2930
3231
  const payload = await response.json();
2931
3232
  lastPayload = payload;
2932
- aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage2(payload));
3233
+ aggregatedUsage = mergeUsage2(aggregatedUsage, pickUsage2(payload));
2933
3234
  finishReason = pickFinishReason2(payload);
2934
3235
  const content = Array.isArray(payload.content) ? payload.content : [];
2935
3236
  const calledTools = pickAnthropicToolCalls(payload).filter((call) => call.type === "function");
@@ -3050,7 +3351,7 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3050
3351
  callbacks.onChunk?.(chunk);
3051
3352
  }
3052
3353
  });
3053
- aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
3354
+ aggregatedUsage = mergeUsage2(aggregatedUsage, roundUsage);
3054
3355
  if (roundFinishReason) {
3055
3356
  finishReason = roundFinishReason;
3056
3357
  }
@@ -3709,95 +4010,6 @@ function withToolTimeout(client, toolTimeoutMs) {
3709
4010
  function applyToolTimeout(clients, toolTimeoutMs) {
3710
4011
  return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
3711
4012
  }
3712
- // src/generate-output.ts
3713
- var RE_THINK_TAGS = /<\/?think\s*>/gi;
3714
- function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
3715
- const sanitized = sanitizeThink(text);
3716
- const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
3717
- const reasoning = joinReasoningSegments([
3718
- dedicatedReasoning,
3719
- ...sanitized.thinkBlocks.map((block) => block.content)
3720
- ]);
3721
- return {
3722
- text: visibleText,
3723
- reasoning,
3724
- reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
3725
- thinkBlocks: sanitized.thinkBlocks,
3726
- parseSource: composeParseSource(visibleText, reasoning)
3727
- };
3728
- }
3729
- function normalizeReasoningBlocks(blocks) {
3730
- if (!Array.isArray(blocks)) {
3731
- return;
3732
- }
3733
- const normalized = blocks.map((block) => ({
3734
- turnIndex: block.turnIndex,
3735
- text: block.text.replace(RE_THINK_TAGS, "").trim()
3736
- })).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
3737
- return normalized.length > 0 ? normalized : undefined;
3738
- }
3739
- function appendReasoningBlock(blocks, transition) {
3740
- const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
3741
- if (!text) {
3742
- return blocks;
3743
- }
3744
- const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
3745
- return normalizeReasoningBlocks(next);
3746
- }
3747
- function composeParseSource(text, reasoning) {
3748
- if (typeof reasoning !== "string" || reasoning.length === 0) {
3749
- return text;
3750
- }
3751
- const sanitized = reasoning.replace(RE_THINK_TAGS, "");
3752
- if (sanitized.length === 0) {
3753
- return text;
3754
- }
3755
- return `<think>${sanitized}</think>${text}`;
3756
- }
3757
- function aggregateUsage(attempts) {
3758
- let usage;
3759
- for (const attempt of attempts) {
3760
- usage = mergeUsage2(usage, attempt.usage);
3761
- }
3762
- return usage;
3763
- }
3764
- function mergeUsage2(base, next) {
3765
- if (!base && !next) {
3766
- return;
3767
- }
3768
- return {
3769
- inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
3770
- outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
3771
- totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
3772
- cost: (base?.cost ?? 0) + (next?.cost ?? 0)
3773
- };
3774
- }
3775
- function joinReasoningSegments(parts) {
3776
- return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
3777
-
3778
- `);
3779
- }
3780
- function stripThinkBlocks(text, thinkBlocks) {
3781
- if (thinkBlocks.length === 0) {
3782
- return text;
3783
- }
3784
- let output = "";
3785
- let cursor = 0;
3786
- for (const block of thinkBlocks) {
3787
- output += text.slice(cursor, block.start);
3788
- cursor = block.end;
3789
- }
3790
- output += text.slice(cursor);
3791
- return output;
3792
- }
3793
- function toStreamDataFingerprint(value) {
3794
- try {
3795
- return JSON.stringify(value);
3796
- } catch {
3797
- return "__unserializable__";
3798
- }
3799
- }
3800
-
3801
4013
  // src/utils/debug-colors.ts
3802
4014
  var ANSI = {
3803
4015
  reset: "\x1B[0m",
@@ -3959,13 +4171,15 @@ async function callModel(adapter, options) {
3959
4171
  if (!done && fingerprint === lastSnapshotFingerprint) {
3960
4172
  return;
3961
4173
  }
4174
+ const stableText = done ? normalized2.text : withoutTrailingThinkTagPrefix(normalized2.text);
4175
+ const stableReasoning = done ? normalized2.reasoning : withoutTrailingThinkTagPrefix(normalized2.reasoning);
3962
4176
  const delta = {
3963
- text: normalized2.text.startsWith(previousSnapshotText) ? normalized2.text.slice(previousSnapshotText.length) : "",
3964
- reasoning: normalized2.reasoning.startsWith(previousSnapshotReasoning) ? normalized2.reasoning.slice(previousSnapshotReasoning.length) : ""
4177
+ text: stableText.startsWith(previousSnapshotText) ? stableText.slice(previousSnapshotText.length) : "",
4178
+ reasoning: stableReasoning.startsWith(previousSnapshotReasoning) ? stableReasoning.slice(previousSnapshotReasoning.length) : ""
3965
4179
  };
3966
4180
  lastSnapshotFingerprint = fingerprint;
3967
- previousSnapshotText = normalized2.text;
3968
- previousSnapshotReasoning = normalized2.reasoning;
4181
+ previousSnapshotText = stableText;
4182
+ previousSnapshotReasoning = stableReasoning;
3969
4183
  options.stream.onData?.({
3970
4184
  delta,
3971
4185
  snapshot,
@@ -5810,6 +6024,7 @@ function unwrap2(schema) {
5810
6024
  }
5811
6025
  export {
5812
6026
  wrapMCPClient,
6027
+ withoutTrailingThinkTagPrefix,
5813
6028
  withFormat,
5814
6029
  structured,
5815
6030
  sanitizeThink,
@@ -5819,6 +6034,7 @@ export {
5819
6034
  registerBuiltinProviders,
5820
6035
  prompt,
5821
6036
  parseLLMOutput,
6037
+ normalizeModelOutput,
5822
6038
  inspectSchemaMetadata,
5823
6039
  inferSchemaExample,
5824
6040
  images,