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.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
  }
@@ -1710,7 +1816,8 @@ function createOpenAICompatibleAdapter(options) {
1710
1816
  async function streamWithChatCompletionsPassThrough(options, fetcher, path, request, callbacks) {
1711
1817
  const response = await sendOpenAIRequest(options, fetcher, path, request, buildChatCompletionsBody(options, request, {
1712
1818
  messages: buildMessages(request),
1713
- stream: true
1819
+ stream: true,
1820
+ stream_options: streamUsageOptions(request)
1714
1821
  }));
1715
1822
  if (!response.ok) {
1716
1823
  const message = await response.text();
@@ -1721,6 +1828,8 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
1721
1828
  let reasoning = "";
1722
1829
  let usage;
1723
1830
  let finishReason;
1831
+ const streamedToolCalls = new Map;
1832
+ const nativeToolCalls = new NativeToolCallStreamState(requestDeclaresTools(options, request));
1724
1833
  await consumeSSE(response, (data) => {
1725
1834
  if (data === "[DONE]") {
1726
1835
  return;
@@ -1729,10 +1838,14 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
1729
1838
  if (!isRecord2(json)) {
1730
1839
  return;
1731
1840
  }
1732
- const delta = pickAssistantDelta(json);
1841
+ const rawDelta = pickAssistantDelta(json);
1733
1842
  const reasoningDelta = pickAssistantReasoningDelta(json);
1734
1843
  const chunkUsage = pickUsage(json);
1735
1844
  const chunkFinishReason = pickFinishReason(json);
1845
+ collectOpenAIStreamToolCalls(json, streamedToolCalls);
1846
+ const nativeDelta = nativeToolCalls.push(rawDelta);
1847
+ const delta = nativeDelta.textDelta;
1848
+ const chunkToolCalls = mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeDelta.toolCalls);
1736
1849
  usage = preferLatestUsage(usage, chunkUsage);
1737
1850
  if (chunkFinishReason) {
1738
1851
  finishReason = chunkFinishReason;
@@ -1744,13 +1857,21 @@ async function streamWithChatCompletionsPassThrough(options, fetcher, path, requ
1744
1857
  if (reasoningDelta) {
1745
1858
  reasoning += reasoningDelta;
1746
1859
  }
1747
- emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
1860
+ emitOpenAIStreamChunk(callbacks, undefined, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
1748
1861
  });
1862
+ const tail = nativeToolCalls.flush();
1863
+ if (tail.textDelta) {
1864
+ text += tail.textDelta;
1865
+ callbacks.onToken?.(tail.textDelta);
1866
+ emitOpenAIStreamChunk(callbacks, undefined, {}, tail.textDelta, "", undefined, undefined);
1867
+ }
1868
+ const toolCalls = mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeToolCalls.calls);
1749
1869
  const out = {
1750
1870
  text,
1751
1871
  reasoning: reasoning.length > 0 ? reasoning : undefined,
1752
1872
  usage,
1753
- finishReason
1873
+ finishReason: finishReason ?? (toolCalls.length > 0 ? "tool_calls" : undefined),
1874
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
1754
1875
  };
1755
1876
  callbacks.onComplete?.(out);
1756
1877
  return out;
@@ -1802,6 +1923,10 @@ async function completeOpenAIRequest(options, fetcher, chatPath, responsesPath,
1802
1923
  function buildChatCompletionsBody(options, request, overrides) {
1803
1924
  return buildOpenAIRequestBody(options, request, "max_tokens", overrides);
1804
1925
  }
1926
+ function streamUsageOptions(request) {
1927
+ const userOptions = isRecord2(request.body?.stream_options) ? request.body.stream_options : undefined;
1928
+ return { include_usage: true, ...userOptions };
1929
+ }
1805
1930
  function buildResponsesBody(options, request, overrides) {
1806
1931
  return buildOpenAIRequestBody(options, request, "max_output_tokens", overrides);
1807
1932
  }
@@ -1856,15 +1981,16 @@ function buildResponsesMCPResult(state, text, raw) {
1856
1981
  toolExecutions: state.toolExecutions.length > 0 ? state.toolExecutions : undefined
1857
1982
  };
1858
1983
  }
1859
- function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason) {
1860
- if (delta || reasoningDelta || usage || finishReason) {
1984
+ function emitOpenAIStreamChunk(callbacks, round, raw, delta, reasoningDelta, usage, finishReason, toolCalls) {
1985
+ if (delta || reasoningDelta || usage || finishReason || toolCalls) {
1861
1986
  callbacks.onChunk?.({
1862
1987
  textDelta: delta,
1863
1988
  reasoningDelta: reasoningDelta || undefined,
1864
1989
  ...round !== undefined ? { turnIndex: round } : {},
1865
1990
  raw,
1866
1991
  usage,
1867
- finishReason
1992
+ finishReason,
1993
+ toolCalls
1868
1994
  });
1869
1995
  }
1870
1996
  }
@@ -1931,7 +2057,7 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1931
2057
  parallel_tool_calls: request.parallelToolCalls
1932
2058
  }));
1933
2059
  lastPayload = payload;
1934
- aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage(payload));
2060
+ aggregatedUsage = mergeUsage2(aggregatedUsage, pickUsage(payload));
1935
2061
  finishReason = pickFinishReason(payload);
1936
2062
  const assistantMessage = pickAssistantMessage(payload);
1937
2063
  const calledTools = pickChatToolCalls(payload);
@@ -2011,7 +2137,7 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
2011
2137
  parallel_tool_calls: request.parallelToolCalls
2012
2138
  }));
2013
2139
  state.lastPayload = payload;
2014
- state.aggregatedUsage = mergeUsage(state.aggregatedUsage, pickUsage(payload));
2140
+ state.aggregatedUsage = mergeUsage2(state.aggregatedUsage, pickUsage(payload));
2015
2141
  state.finishReason = pickResponsesFinishReason(payload) ?? state.finishReason;
2016
2142
  pushReasoningBlock(state.reasoningBlocks, round, pickResponsesReasoning(payload));
2017
2143
  const providerToolCalls = pickResponsesToolCalls(payload);
@@ -2059,7 +2185,8 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2059
2185
  tools: transportTools,
2060
2186
  tool_choice: request.toolChoice,
2061
2187
  parallel_tool_calls: request.parallelToolCalls,
2062
- stream: true
2188
+ stream: true,
2189
+ stream_options: streamUsageOptions(request)
2063
2190
  }));
2064
2191
  if (!response.ok) {
2065
2192
  const message = await response.text();
@@ -2070,6 +2197,7 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2070
2197
  let roundUsage;
2071
2198
  let roundFinishReason;
2072
2199
  const streamedToolCalls = new Map;
2200
+ const nativeToolCalls = new NativeToolCallStreamState;
2073
2201
  let reasoningFieldName;
2074
2202
  await consumeSSE(response, (data) => {
2075
2203
  if (data === "[DONE]") {
@@ -2080,10 +2208,12 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2080
2208
  return;
2081
2209
  }
2082
2210
  lastPayload = json;
2083
- const delta = pickAssistantDelta(json);
2211
+ const rawDelta = pickAssistantDelta(json);
2084
2212
  const reasoningDelta = pickAssistantReasoningDelta(json);
2085
2213
  const chunkUsage = pickUsage(json);
2086
2214
  const chunkFinishReason = pickFinishReason(json);
2215
+ const nativeDelta = nativeToolCalls.push(rawDelta);
2216
+ const delta = nativeDelta.textDelta;
2087
2217
  collectOpenAIStreamToolCalls(json, streamedToolCalls);
2088
2218
  roundUsage = preferLatestUsage(roundUsage, chunkUsage);
2089
2219
  if (chunkFinishReason) {
@@ -2097,13 +2227,20 @@ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request,
2097
2227
  roundReasoning += reasoningDelta;
2098
2228
  reasoningFieldName ??= pickAssistantReasoningDeltaFieldName(json);
2099
2229
  }
2100
- emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2230
+ const chunkToolCalls = nativeDelta.toolCalls.length > 0 ? mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeDelta.toolCalls) : undefined;
2231
+ emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason, chunkToolCalls);
2101
2232
  });
2102
- aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
2233
+ const tail = nativeToolCalls.flush();
2234
+ if (tail.textDelta) {
2235
+ roundText += tail.textDelta;
2236
+ callbacks.onToken?.(tail.textDelta);
2237
+ emitOpenAIStreamChunk(callbacks, round, {}, tail.textDelta, "", undefined, undefined);
2238
+ }
2239
+ aggregatedUsage = mergeUsage2(aggregatedUsage, roundUsage);
2103
2240
  if (roundFinishReason) {
2104
2241
  finishReason = roundFinishReason;
2105
2242
  }
2106
- const calledTools = buildOpenAIStreamToolCalls(streamedToolCalls);
2243
+ const calledTools = mergeToolCalls(buildOpenAIStreamToolCalls(streamedToolCalls), nativeToolCalls.calls);
2107
2244
  pushReasoningBlock(reasoningBlocks, round, roundReasoning);
2108
2245
  request.onTurnTransition?.({
2109
2246
  turnIndex: round,
@@ -2190,6 +2327,7 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2190
2327
  let usage;
2191
2328
  let finishReason;
2192
2329
  let lastPayload;
2330
+ const streamedToolCalls = new Map;
2193
2331
  await consumeSSE(response, (data) => {
2194
2332
  if (data === "[DONE]") {
2195
2333
  return;
@@ -2205,6 +2343,8 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2205
2343
  const delta = pickResponsesStreamTextDelta(json);
2206
2344
  const chunkUsage = pickResponsesStreamUsage(json);
2207
2345
  const chunkFinishReason = pickResponsesStreamFinishReason(json);
2346
+ collectResponsesStreamToolCalls(json, streamedToolCalls);
2347
+ const chunkToolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
2208
2348
  usage = preferLatestUsage(usage, chunkUsage);
2209
2349
  if (chunkFinishReason) {
2210
2350
  finishReason = chunkFinishReason;
@@ -2213,14 +2353,16 @@ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request
2213
2353
  text += delta;
2214
2354
  callbacks.onToken?.(delta);
2215
2355
  }
2216
- emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason);
2356
+ emitOpenAIStreamChunk(callbacks, undefined, json, delta, "", chunkUsage, chunkFinishReason, chunkToolCalls.length > 0 ? chunkToolCalls : undefined);
2217
2357
  });
2218
2358
  const finalPayload = lastPayload ?? {};
2359
+ const toolCalls = buildResponsesStreamToolCalls(streamedToolCalls);
2219
2360
  const out = {
2220
2361
  text: text.length > 0 ? text : pickResponsesText(finalPayload) || pickAssistantText(finalPayload),
2221
2362
  raw: finalPayload,
2222
2363
  usage: preferLatestUsage(usage, pickUsage(finalPayload)),
2223
- finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload)
2364
+ finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload),
2365
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined
2224
2366
  };
2225
2367
  callbacks.onComplete?.(out);
2226
2368
  return out;
@@ -2282,7 +2424,7 @@ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, ca
2282
2424
  emitOpenAIStreamChunk(callbacks, round, json, delta, reasoningDelta, chunkUsage, chunkFinishReason);
2283
2425
  });
2284
2426
  const resolvedRoundUsage = preferLatestUsage(roundUsage, roundPayload ? pickUsage(roundPayload) : undefined);
2285
- state.aggregatedUsage = mergeUsage(state.aggregatedUsage, resolvedRoundUsage);
2427
+ state.aggregatedUsage = mergeUsage2(state.aggregatedUsage, resolvedRoundUsage);
2286
2428
  if (roundFinishReason) {
2287
2429
  state.finishReason = roundFinishReason;
2288
2430
  } else if (roundPayload) {
@@ -2536,43 +2678,204 @@ function pickAssistantReasoningDeltaFieldName(payload) {
2536
2678
  }
2537
2679
  return;
2538
2680
  }
2539
- function collectOpenAIStreamToolCalls(payload, state) {
2540
- const choices = payload.choices;
2541
- if (!Array.isArray(choices) || choices.length === 0 || !isRecord2(choices[0])) {
2681
+ var NATIVE_TOOL_CALL_OPEN = "<tool_call";
2682
+ var NATIVE_TOOL_CALL_CLOSE = "</tool_call>";
2683
+
2684
+ class NativeToolCallStreamState {
2685
+ enabled;
2686
+ calls = [];
2687
+ pending = "";
2688
+ constructor(enabled = true) {
2689
+ this.enabled = enabled;
2690
+ }
2691
+ push(delta) {
2692
+ if (!delta || !this.enabled) {
2693
+ return { textDelta: delta, toolCalls: [] };
2694
+ }
2695
+ this.pending += delta;
2696
+ return this.drain(false);
2697
+ }
2698
+ flush() {
2699
+ return this.enabled ? this.drain(true) : { textDelta: "", toolCalls: [] };
2700
+ }
2701
+ drain(flush) {
2702
+ let textDelta = "";
2703
+ const toolCalls = [];
2704
+ while (this.pending.length > 0) {
2705
+ const openIndex = this.pending.indexOf(NATIVE_TOOL_CALL_OPEN);
2706
+ if (openIndex < 0) {
2707
+ const keep = flush ? 0 : nativeToolCallPrefixSuffixLength(this.pending);
2708
+ const emitLength = this.pending.length - keep;
2709
+ if (emitLength > 0) {
2710
+ textDelta += this.pending.slice(0, emitLength);
2711
+ this.pending = this.pending.slice(emitLength);
2712
+ }
2713
+ break;
2714
+ }
2715
+ if (openIndex > 0) {
2716
+ textDelta += this.pending.slice(0, openIndex);
2717
+ this.pending = this.pending.slice(openIndex);
2718
+ continue;
2719
+ }
2720
+ const closeIndex = this.pending.indexOf(NATIVE_TOOL_CALL_CLOSE);
2721
+ if (closeIndex < 0) {
2722
+ if (flush) {
2723
+ this.pending = "";
2724
+ }
2725
+ break;
2726
+ }
2727
+ const blockEnd = closeIndex + NATIVE_TOOL_CALL_CLOSE.length;
2728
+ const call = parseNativeToolCallBlock(this.pending.slice(0, blockEnd), this.calls.length);
2729
+ if (call) {
2730
+ this.calls.push(call);
2731
+ toolCalls.push(call);
2732
+ }
2733
+ this.pending = this.pending.slice(blockEnd);
2734
+ }
2735
+ return { textDelta, toolCalls };
2736
+ }
2737
+ }
2738
+ function requestDeclaresTools(options, request) {
2739
+ const hasTools = (body) => Array.isArray(body?.tools) && body.tools.length > 0;
2740
+ return hasTools(request.body) || hasTools(options.defaultBody);
2741
+ }
2742
+ var NATIVE_FUNCTION_PATTERN = /<function=([^>\s]+)\s*>([\s\S]*?)<\/function>/;
2743
+ var NATIVE_PARAMETER_PATTERN = /<parameter=([^>\s]+)\s*>([\s\S]*?)<\/parameter>/g;
2744
+ function parseNativeToolCallBlock(block, index) {
2745
+ const inner = extractNativeToolCallInner(block);
2746
+ if (inner === undefined) {
2542
2747
  return;
2543
2748
  }
2544
- const delta = choices[0].delta;
2545
- if (!isRecord2(delta) || !Array.isArray(delta.tool_calls)) {
2749
+ return parseNativeJsonToolCall(inner, index) ?? parseNativeXmlToolCall(inner, index);
2750
+ }
2751
+ function extractNativeToolCallInner(block) {
2752
+ const openEnd = block.indexOf(">");
2753
+ const closeStart = block.lastIndexOf(NATIVE_TOOL_CALL_CLOSE);
2754
+ if (openEnd < 0 || closeStart < 0 || closeStart <= openEnd) {
2546
2755
  return;
2547
2756
  }
2548
- for (const rawToolCall of delta.tool_calls) {
2549
- if (!isRecord2(rawToolCall)) {
2550
- continue;
2757
+ return block.slice(openEnd + 1, closeStart).trim();
2758
+ }
2759
+ function parseNativeXmlToolCall(inner, index) {
2760
+ const functionMatch = NATIVE_FUNCTION_PATTERN.exec(inner);
2761
+ const functionName = functionMatch?.[1];
2762
+ const functionBody = functionMatch?.[2];
2763
+ if (!functionName || functionBody === undefined) {
2764
+ return;
2765
+ }
2766
+ const args = {};
2767
+ for (const [, key, rawValue] of functionBody.matchAll(NATIVE_PARAMETER_PATTERN)) {
2768
+ if (key && rawValue !== undefined) {
2769
+ args[key] = coerceNativeParameterValue(rawValue.trim());
2551
2770
  }
2552
- const index = toFiniteNumber(rawToolCall.index);
2553
- const toolIndex = index !== undefined ? Math.floor(index) : 0;
2554
- const existing = state.get(toolIndex) ?? {
2555
- index: toolIndex,
2556
- argumentsText: ""
2557
- };
2558
- const id = pickString(rawToolCall.id);
2559
- if (id) {
2560
- existing.id = id;
2771
+ }
2772
+ return {
2773
+ id: `call_native_${index}`,
2774
+ type: "function",
2775
+ name: functionName,
2776
+ arguments: JSON.stringify(args)
2777
+ };
2778
+ }
2779
+ function coerceNativeParameterValue(value) {
2780
+ if (value.length === 0) {
2781
+ return "";
2782
+ }
2783
+ const parsed = safeJSONParse(value);
2784
+ if (parsed === null) {
2785
+ return value === "null" ? null : value;
2786
+ }
2787
+ return parsed;
2788
+ }
2789
+ function nativeToolCallPrefixSuffixLength(value) {
2790
+ const max = Math.min(value.length, NATIVE_TOOL_CALL_OPEN.length - 1);
2791
+ for (let length = max;length > 0; length -= 1) {
2792
+ if (NATIVE_TOOL_CALL_OPEN.startsWith(value.slice(-length))) {
2793
+ return length;
2561
2794
  }
2562
- const type = pickString(rawToolCall.type);
2563
- if (type) {
2564
- existing.type = type;
2795
+ }
2796
+ return 0;
2797
+ }
2798
+ function parseNativeJsonToolCall(inner, index) {
2799
+ const parsed = safeJSONParse(inner);
2800
+ if (!isRecord2(parsed)) {
2801
+ return;
2802
+ }
2803
+ const name = pickString(parsed.name) ?? pickString(parsed.function);
2804
+ if (!name) {
2805
+ return;
2806
+ }
2807
+ const rawArguments = parsed.arguments ?? parsed.parameters ?? {};
2808
+ const args = typeof rawArguments === "string" ? rawArguments : JSON.stringify(rawArguments);
2809
+ return {
2810
+ id: pickString(parsed.id) ?? `call_native_${index}`,
2811
+ type: "function",
2812
+ name,
2813
+ arguments: args
2814
+ };
2815
+ }
2816
+ function mergeToolCalls(...groups) {
2817
+ const merged = [];
2818
+ const seen = new Set;
2819
+ for (const group of groups) {
2820
+ for (const call of group) {
2821
+ const key = call.id || `${call.name ?? ""}:${String(call.arguments ?? "")}`;
2822
+ if (seen.has(key)) {
2823
+ continue;
2824
+ }
2825
+ seen.add(key);
2826
+ merged.push(call);
2565
2827
  }
2566
- const functionCall = isRecord2(rawToolCall.function) ? rawToolCall.function : undefined;
2567
- const name = pickString(functionCall?.name);
2568
- if (name) {
2569
- existing.name = `${existing.name ?? ""}${name}`;
2828
+ }
2829
+ return merged;
2830
+ }
2831
+ function collectOpenAIStreamToolCalls(payload, state) {
2832
+ const choices = payload.choices;
2833
+ if (!Array.isArray(choices) || choices.length === 0) {
2834
+ return;
2835
+ }
2836
+ for (const choice of choices) {
2837
+ if (!isRecord2(choice)) {
2838
+ continue;
2570
2839
  }
2571
- const argumentsDelta = pickString(functionCall?.arguments);
2572
- if (argumentsDelta) {
2573
- existing.argumentsText += argumentsDelta;
2840
+ const delta = isRecord2(choice.delta) ? choice.delta : undefined;
2841
+ const message = isRecord2(choice.message) ? choice.message : undefined;
2842
+ const toolCalls = Array.isArray(delta?.tool_calls) ? delta.tool_calls : Array.isArray(message?.tool_calls) ? message.tool_calls : Array.isArray(choice.tool_calls) ? choice.tool_calls : undefined;
2843
+ if (!toolCalls) {
2844
+ continue;
2845
+ }
2846
+ for (const rawToolCall of toolCalls) {
2847
+ if (!isRecord2(rawToolCall)) {
2848
+ continue;
2849
+ }
2850
+ const index = toFiniteNumber(rawToolCall.index);
2851
+ const toolIndex = index !== undefined ? Math.floor(index) : state.size;
2852
+ const existing = state.get(toolIndex) ?? {
2853
+ index: toolIndex,
2854
+ argumentsText: ""
2855
+ };
2856
+ const id = pickString(rawToolCall.id);
2857
+ if (id) {
2858
+ existing.id = id;
2859
+ }
2860
+ const type = pickString(rawToolCall.type);
2861
+ if (type) {
2862
+ existing.type = type;
2863
+ }
2864
+ const functionCall = isRecord2(rawToolCall.function) ? rawToolCall.function : undefined;
2865
+ const name = pickString(functionCall?.name);
2866
+ if (name) {
2867
+ existing.name = `${existing.name ?? ""}${name}`;
2868
+ }
2869
+ const argumentsDelta = pickString(functionCall?.arguments);
2870
+ if (argumentsDelta) {
2871
+ if (message?.tool_calls === toolCalls || choice.tool_calls === toolCalls) {
2872
+ existing.argumentsText = argumentsDelta;
2873
+ } else {
2874
+ existing.argumentsText += argumentsDelta;
2875
+ }
2876
+ }
2877
+ state.set(toolIndex, existing);
2574
2878
  }
2575
- state.set(toolIndex, existing);
2576
2879
  }
2577
2880
  }
2578
2881
  function buildOpenAIStreamToolCalls(state) {
@@ -3020,7 +3323,7 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
3020
3323
  }
3021
3324
  const payload = await response.json();
3022
3325
  lastPayload = payload;
3023
- aggregatedUsage = mergeUsage(aggregatedUsage, pickUsage2(payload));
3326
+ aggregatedUsage = mergeUsage2(aggregatedUsage, pickUsage2(payload));
3024
3327
  finishReason = pickFinishReason2(payload);
3025
3328
  const content = Array.isArray(payload.content) ? payload.content : [];
3026
3329
  const calledTools = pickAnthropicToolCalls(payload).filter((call) => call.type === "function");
@@ -3141,7 +3444,7 @@ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks)
3141
3444
  callbacks.onChunk?.(chunk);
3142
3445
  }
3143
3446
  });
3144
- aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
3447
+ aggregatedUsage = mergeUsage2(aggregatedUsage, roundUsage);
3145
3448
  if (roundFinishReason) {
3146
3449
  finishReason = roundFinishReason;
3147
3450
  }
@@ -3800,95 +4103,6 @@ function withToolTimeout(client, toolTimeoutMs) {
3800
4103
  function applyToolTimeout(clients, toolTimeoutMs) {
3801
4104
  return clients.map((client) => withToolTimeout(client, toolTimeoutMs));
3802
4105
  }
3803
- // src/generate-output.ts
3804
- var RE_THINK_TAGS = /<\/?think\s*>/gi;
3805
- function normalizeModelOutput(text, dedicatedReasoning, reasoningBlocks) {
3806
- const sanitized = sanitizeThink(text);
3807
- const visibleText = stripThinkBlocks(text, sanitized.thinkBlocks);
3808
- const reasoning = joinReasoningSegments([
3809
- dedicatedReasoning,
3810
- ...sanitized.thinkBlocks.map((block) => block.content)
3811
- ]);
3812
- return {
3813
- text: visibleText,
3814
- reasoning,
3815
- reasoningBlocks: normalizeReasoningBlocks(reasoningBlocks),
3816
- thinkBlocks: sanitized.thinkBlocks,
3817
- parseSource: composeParseSource(visibleText, reasoning)
3818
- };
3819
- }
3820
- function normalizeReasoningBlocks(blocks) {
3821
- if (!Array.isArray(blocks)) {
3822
- return;
3823
- }
3824
- const normalized = blocks.map((block) => ({
3825
- turnIndex: block.turnIndex,
3826
- text: block.text.replace(RE_THINK_TAGS, "").trim()
3827
- })).filter((block) => Number.isFinite(block.turnIndex) && block.text.length > 0);
3828
- return normalized.length > 0 ? normalized : undefined;
3829
- }
3830
- function appendReasoningBlock(blocks, transition) {
3831
- const text = transition.reasoningText?.replace(RE_THINK_TAGS, "").trim();
3832
- if (!text) {
3833
- return blocks;
3834
- }
3835
- const next = [...blocks ?? [], { turnIndex: transition.turnIndex, text }];
3836
- return normalizeReasoningBlocks(next);
3837
- }
3838
- function composeParseSource(text, reasoning) {
3839
- if (typeof reasoning !== "string" || reasoning.length === 0) {
3840
- return text;
3841
- }
3842
- const sanitized = reasoning.replace(RE_THINK_TAGS, "");
3843
- if (sanitized.length === 0) {
3844
- return text;
3845
- }
3846
- return `<think>${sanitized}</think>${text}`;
3847
- }
3848
- function aggregateUsage(attempts) {
3849
- let usage;
3850
- for (const attempt of attempts) {
3851
- usage = mergeUsage2(usage, attempt.usage);
3852
- }
3853
- return usage;
3854
- }
3855
- function mergeUsage2(base, next) {
3856
- if (!base && !next) {
3857
- return;
3858
- }
3859
- return {
3860
- inputTokens: (base?.inputTokens ?? 0) + (next?.inputTokens ?? 0),
3861
- outputTokens: (base?.outputTokens ?? 0) + (next?.outputTokens ?? 0),
3862
- totalTokens: (base?.totalTokens ?? 0) + (next?.totalTokens ?? 0),
3863
- cost: (base?.cost ?? 0) + (next?.cost ?? 0)
3864
- };
3865
- }
3866
- function joinReasoningSegments(parts) {
3867
- return parts.map((value) => value?.trim()).filter((value) => Boolean(value)).join(`
3868
-
3869
- `);
3870
- }
3871
- function stripThinkBlocks(text, thinkBlocks) {
3872
- if (thinkBlocks.length === 0) {
3873
- return text;
3874
- }
3875
- let output = "";
3876
- let cursor = 0;
3877
- for (const block of thinkBlocks) {
3878
- output += text.slice(cursor, block.start);
3879
- cursor = block.end;
3880
- }
3881
- output += text.slice(cursor);
3882
- return output;
3883
- }
3884
- function toStreamDataFingerprint(value) {
3885
- try {
3886
- return JSON.stringify(value);
3887
- } catch {
3888
- return "__unserializable__";
3889
- }
3890
- }
3891
-
3892
4106
  // src/utils/debug-colors.ts
3893
4107
  var ANSI = {
3894
4108
  reset: "\x1B[0m",
@@ -4050,13 +4264,15 @@ async function callModel(adapter, options) {
4050
4264
  if (!done && fingerprint === lastSnapshotFingerprint) {
4051
4265
  return;
4052
4266
  }
4267
+ const stableText = done ? normalized2.text : withoutTrailingThinkTagPrefix(normalized2.text);
4268
+ const stableReasoning = done ? normalized2.reasoning : withoutTrailingThinkTagPrefix(normalized2.reasoning);
4053
4269
  const delta = {
4054
- text: normalized2.text.startsWith(previousSnapshotText) ? normalized2.text.slice(previousSnapshotText.length) : "",
4055
- reasoning: normalized2.reasoning.startsWith(previousSnapshotReasoning) ? normalized2.reasoning.slice(previousSnapshotReasoning.length) : ""
4270
+ text: stableText.startsWith(previousSnapshotText) ? stableText.slice(previousSnapshotText.length) : "",
4271
+ reasoning: stableReasoning.startsWith(previousSnapshotReasoning) ? stableReasoning.slice(previousSnapshotReasoning.length) : ""
4056
4272
  };
4057
4273
  lastSnapshotFingerprint = fingerprint;
4058
- previousSnapshotText = normalized2.text;
4059
- previousSnapshotReasoning = normalized2.reasoning;
4274
+ previousSnapshotText = stableText;
4275
+ previousSnapshotReasoning = stableReasoning;
4060
4276
  options.stream.onData?.({
4061
4277
  delta,
4062
4278
  snapshot,