extrait 0.1.2 → 0.3.0

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
@@ -298,6 +298,9 @@ function isOnlyWhitespace(value) {
298
298
  }
299
299
 
300
300
  // src/extract.ts
301
+ var RE_EMPTY_OBJECT = /^\{\s*\}$/;
302
+ var RE_EMPTY_ARRAY = /^\[\s*\]$/;
303
+ var RE_BOUNDARY_CHAR = /[\s,.;:!?`"'()\[\]{}<>]/;
301
304
  var DEFAULT_EXTRACTION_HEURISTICS = {
302
305
  firstPassMin: 12,
303
306
  firstPassCap: 30,
@@ -522,7 +525,7 @@ function jsonShapeScore(content, acceptArrays) {
522
525
  const commaCount = countChar(trimmed, ",");
523
526
  const quoteCount = countChar(trimmed, '"');
524
527
  if (root === "{") {
525
- if (/^\{\s*\}$/.test(trimmed)) {
528
+ if (RE_EMPTY_OBJECT.test(trimmed)) {
526
529
  score += 12;
527
530
  } else if (colonCount > 0) {
528
531
  score += 22;
@@ -533,7 +536,7 @@ function jsonShapeScore(content, acceptArrays) {
533
536
  score += quoteCount % 2 === 0 ? 8 : -8;
534
537
  }
535
538
  } else {
536
- score += /^\[\s*\]$/.test(trimmed) ? 8 : 4;
539
+ score += RE_EMPTY_ARRAY.test(trimmed) ? 8 : 4;
537
540
  if (colonCount > 0) {
538
541
  score += 4;
539
542
  }
@@ -566,7 +569,7 @@ function isBoundary(char) {
566
569
  if (!char) {
567
570
  return true;
568
571
  }
569
- return /[\s,.;:!?`"'()[\]{}<>]/.test(char);
572
+ return RE_BOUNDARY_CHAR.test(char);
570
573
  }
571
574
  function lengthScore(length) {
572
575
  return Math.min(120, Math.floor(Math.sqrt(length) * 6));
@@ -755,6 +758,8 @@ function clamp(value, min, max) {
755
758
  return Math.max(min, Math.min(max, Math.floor(value)));
756
759
  }
757
760
  // src/schema.ts
761
+ var RE_SIMPLE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
762
+ var RE_WHITESPACE = /\s+/g;
758
763
  function formatZodSchemaLikeTypeScript(schema) {
759
764
  return formatType(schema, 0, new WeakSet);
760
765
  }
@@ -926,7 +931,7 @@ ${lines.join(`
926
931
  ${indent}}`;
927
932
  }
928
933
  function formatKey(key) {
929
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
934
+ return RE_SIMPLE_IDENTIFIER.test(key) ? key : JSON.stringify(key);
930
935
  }
931
936
  function requiresParentheses(typeText) {
932
937
  return typeText.includes(" | ") || typeText.includes(" & ");
@@ -972,7 +977,7 @@ function readSchemaDescription(schema) {
972
977
  return;
973
978
  }
974
979
  function sanitizeDescription(value) {
975
- return value.replace(/\s+/g, " ").trim();
980
+ return value.replace(RE_WHITESPACE, " ").trim();
976
981
  }
977
982
 
978
983
  // src/format.ts
@@ -997,6 +1002,8 @@ function resolveSchemaInstruction(instruction) {
997
1002
  }
998
1003
  // src/think.ts
999
1004
  var THINK_TAG_NAME = "think";
1005
+ var RE_IDENTIFIER_CHAR = /[a-zA-Z0-9:_-]/;
1006
+ var RE_NON_LINE_BREAK = /[^\r\n]/g;
1000
1007
  function sanitizeThink(input) {
1001
1008
  const thinkBlocks = [];
1002
1009
  const diagnostics = {
@@ -1095,6 +1102,9 @@ function parseThinkTagAt(input, index) {
1095
1102
  if (input[cursor] === "/") {
1096
1103
  closing = true;
1097
1104
  cursor += 1;
1105
+ while (cursor < input.length && isWhitespace(input[cursor])) {
1106
+ cursor += 1;
1107
+ }
1098
1108
  }
1099
1109
  if (!matchesIgnoreCase(input, cursor, THINK_TAG_NAME)) {
1100
1110
  return null;
@@ -1151,7 +1161,7 @@ function matchesIgnoreCase(input, index, expected) {
1151
1161
  return input.slice(index, index + expected.length).toLowerCase() === expected;
1152
1162
  }
1153
1163
  function isIdentifierChar(char) {
1154
- return /[a-zA-Z0-9:_-]/.test(char);
1164
+ return RE_IDENTIFIER_CHAR.test(char);
1155
1165
  }
1156
1166
  function countHiddenChars(value) {
1157
1167
  let count = 0;
@@ -1165,9 +1175,10 @@ function countHiddenChars(value) {
1165
1175
  return count;
1166
1176
  }
1167
1177
  function maskKeepingLineBreaks(value) {
1168
- return value.replace(/[^\r\n]/g, " ");
1178
+ return value.replace(RE_NON_LINE_BREAK, " ");
1169
1179
  }
1170
1180
  // src/providers/stream-utils.ts
1181
+ var RE_LINE_ENDING = /\r?\n/;
1171
1182
  async function consumeSSE(response, onEvent) {
1172
1183
  if (!response.body) {
1173
1184
  return;
@@ -1190,7 +1201,7 @@ async function consumeSSE(response, onEvent) {
1190
1201
  buffer = buffer.slice(boundary + (buffer.startsWith(`\r
1191
1202
  \r
1192
1203
  `, boundary) ? 4 : 2));
1193
- const dataLines = rawEvent.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
1204
+ const dataLines = rawEvent.split(RE_LINE_ENDING).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
1194
1205
  if (dataLines.length === 0) {
1195
1206
  continue;
1196
1207
  }
@@ -1200,7 +1211,7 @@ async function consumeSSE(response, onEvent) {
1200
1211
  }
1201
1212
  const remainder = buffer.trim();
1202
1213
  if (remainder.length > 0) {
1203
- const dataLines = remainder.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
1214
+ const dataLines = remainder.split(RE_LINE_ENDING).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
1204
1215
  if (dataLines.length > 0) {
1205
1216
  onEvent(dataLines.join(`
1206
1217
  `));
@@ -1490,13 +1501,18 @@ function describeTool(clientId, tool, hasCollision) {
1490
1501
  }
1491
1502
  return;
1492
1503
  }
1504
+ var RE_NON_ALPHANUMERIC = /[^A-Za-z0-9_]/g;
1505
+ var RE_MULTIPLE_UNDERSCORES = /_+/g;
1506
+ var RE_LEADING_UNDERSCORES = /^_+/;
1507
+ var RE_TRAILING_UNDERSCORES = /_+$/;
1508
+ var RE_STARTS_WITH_DIGIT = /^[0-9]/;
1493
1509
  function sanitizeToolName(input) {
1494
- const sanitized = input.replace(/[^A-Za-z0-9_]/g, "_").replace(/_+/g, "_");
1495
- const trimmed = sanitized.replace(/^_+/, "").replace(/_+$/, "");
1510
+ const sanitized = input.replace(RE_NON_ALPHANUMERIC, "_").replace(RE_MULTIPLE_UNDERSCORES, "_");
1511
+ const trimmed = sanitized.replace(RE_LEADING_UNDERSCORES, "").replace(RE_TRAILING_UNDERSCORES, "");
1496
1512
  if (!trimmed) {
1497
1513
  return "tool";
1498
1514
  }
1499
- if (/^[0-9]/.test(trimmed)) {
1515
+ if (RE_STARTS_WITH_DIGIT.test(trimmed)) {
1500
1516
  return `tool_${trimmed}`;
1501
1517
  }
1502
1518
  return trimmed;
@@ -1581,8 +1597,14 @@ function createOpenAICompatibleAdapter(options) {
1581
1597
  async stream(request, callbacks = {}) {
1582
1598
  const usesResponses = shouldUseResponsesAPI(options, request);
1583
1599
  const usesMCP = hasMCPClients(request.mcpClients);
1584
- if (usesResponses || usesMCP) {
1585
- return streamViaComplete(callbacks, () => completeOpenAIRequest(options, fetcher, path, responsesPath, request));
1600
+ if (usesResponses) {
1601
+ if (usesMCP) {
1602
+ return streamWithResponsesAPIWithMCP(options, fetcher, responsesPath, request, callbacks);
1603
+ }
1604
+ return streamWithResponsesAPIPassThrough(options, fetcher, responsesPath, request, callbacks);
1605
+ }
1606
+ if (usesMCP) {
1607
+ return streamWithChatCompletionsWithMCP(options, fetcher, path, request, callbacks);
1586
1608
  }
1587
1609
  const response = await fetcher(buildURL(options.baseURL, path), {
1588
1610
  method: "POST",
@@ -1687,8 +1709,6 @@ async function completeWithChatCompletionsPassThrough(options, fetcher, path, re
1687
1709
  };
1688
1710
  }
1689
1711
  async function completeWithChatCompletionsWithMCP(options, fetcher, path, request) {
1690
- const mcpToolset = await resolveMCPToolset(request.mcpClients);
1691
- const transportTools = toProviderFunctionTools(mcpToolset);
1692
1712
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1693
1713
  let messages = buildMessages(request);
1694
1714
  let aggregatedUsage;
@@ -1697,6 +1717,8 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1697
1717
  const toolCalls = [];
1698
1718
  const toolExecutions = [];
1699
1719
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1720
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
1721
+ const transportTools = toProviderFunctionTools(mcpToolset);
1700
1722
  const response = await fetcher(buildURL(options.baseURL, path), {
1701
1723
  method: "POST",
1702
1724
  headers: buildHeaders(options),
@@ -1792,8 +1814,6 @@ async function completeWithResponsesAPIPassThrough(options, fetcher, path, reque
1792
1814
  };
1793
1815
  }
1794
1816
  async function completeWithResponsesAPIWithMCP(options, fetcher, path, request) {
1795
- const mcpToolset = await resolveMCPToolset(request.mcpClients);
1796
- const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
1797
1817
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1798
1818
  let input = buildResponsesInput(request);
1799
1819
  let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
@@ -1803,6 +1823,8 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
1803
1823
  const executedToolCalls = [];
1804
1824
  const toolExecutions = [];
1805
1825
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1826
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
1827
+ const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
1806
1828
  const response = await fetcher(buildURL(options.baseURL, path), {
1807
1829
  method: "POST",
1808
1830
  headers: buildHeaders(options),
@@ -1867,6 +1889,316 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
1867
1889
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1868
1890
  };
1869
1891
  }
1892
+ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request, callbacks) {
1893
+ const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1894
+ let messages = buildMessages(request);
1895
+ let aggregatedUsage;
1896
+ let finishReason;
1897
+ let lastPayload;
1898
+ const executedToolCalls = [];
1899
+ const toolExecutions = [];
1900
+ callbacks.onStart?.();
1901
+ for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1902
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
1903
+ const transportTools = toProviderFunctionTools(mcpToolset);
1904
+ const response = await fetcher(buildURL(options.baseURL, path), {
1905
+ method: "POST",
1906
+ headers: buildHeaders(options),
1907
+ body: JSON.stringify(cleanUndefined({
1908
+ ...options.defaultBody,
1909
+ ...request.body,
1910
+ model: options.model,
1911
+ messages,
1912
+ temperature: request.temperature,
1913
+ max_tokens: request.maxTokens,
1914
+ tools: transportTools,
1915
+ tool_choice: request.toolChoice,
1916
+ parallel_tool_calls: request.parallelToolCalls,
1917
+ stream: true
1918
+ }))
1919
+ });
1920
+ if (!response.ok) {
1921
+ const message = await response.text();
1922
+ throw new Error(`HTTP ${response.status}: ${message}`);
1923
+ }
1924
+ let roundText = "";
1925
+ let roundUsage;
1926
+ let roundFinishReason;
1927
+ const streamedToolCalls = new Map;
1928
+ await consumeSSE(response, (data) => {
1929
+ if (data === "[DONE]") {
1930
+ return;
1931
+ }
1932
+ const json = safeJSONParse(data);
1933
+ if (!isRecord2(json)) {
1934
+ return;
1935
+ }
1936
+ lastPayload = json;
1937
+ const delta = pickAssistantDelta(json);
1938
+ const chunkUsage = pickUsage(json);
1939
+ const chunkFinishReason = pickFinishReason(json);
1940
+ collectOpenAIStreamToolCalls(json, streamedToolCalls);
1941
+ roundUsage = mergeUsage(roundUsage, chunkUsage);
1942
+ if (chunkFinishReason) {
1943
+ roundFinishReason = chunkFinishReason;
1944
+ }
1945
+ if (delta) {
1946
+ roundText += delta;
1947
+ callbacks.onToken?.(delta);
1948
+ }
1949
+ if (delta || chunkUsage || chunkFinishReason) {
1950
+ const chunk = {
1951
+ textDelta: delta,
1952
+ raw: json,
1953
+ usage: chunkUsage,
1954
+ finishReason: chunkFinishReason
1955
+ };
1956
+ callbacks.onChunk?.(chunk);
1957
+ }
1958
+ });
1959
+ aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
1960
+ if (roundFinishReason) {
1961
+ finishReason = roundFinishReason;
1962
+ }
1963
+ const calledTools = buildOpenAIStreamToolCalls(streamedToolCalls);
1964
+ if (calledTools.length === 0) {
1965
+ const out2 = {
1966
+ text: roundText,
1967
+ raw: lastPayload,
1968
+ usage: aggregatedUsage,
1969
+ finishReason,
1970
+ toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
1971
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1972
+ };
1973
+ callbacks.onComplete?.(out2);
1974
+ return out2;
1975
+ }
1976
+ if (round > maxToolRounds) {
1977
+ throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
1978
+ }
1979
+ const outputs = await executeMCPToolCalls(calledTools, mcpToolset, {
1980
+ round,
1981
+ request,
1982
+ provider: "openai-compatible",
1983
+ model: options.model
1984
+ });
1985
+ executedToolCalls.push(...outputs.map((entry) => entry.call));
1986
+ toolExecutions.push(...outputs.map((entry) => entry.execution));
1987
+ const assistantMessage = buildOpenAIAssistantToolMessage(roundText, calledTools);
1988
+ const toolMessages = outputs.map((entry) => ({
1989
+ role: "tool",
1990
+ tool_call_id: entry.call.id,
1991
+ content: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
1992
+ }));
1993
+ messages = [...messages, assistantMessage, ...toolMessages];
1994
+ }
1995
+ const out = {
1996
+ text: "",
1997
+ raw: lastPayload,
1998
+ usage: aggregatedUsage,
1999
+ finishReason,
2000
+ toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2001
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2002
+ };
2003
+ callbacks.onComplete?.(out);
2004
+ return out;
2005
+ }
2006
+ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request, callbacks) {
2007
+ const body = isRecord2(request.body) ? request.body : undefined;
2008
+ const response = await fetcher(buildURL(options.baseURL, path), {
2009
+ method: "POST",
2010
+ headers: buildHeaders(options),
2011
+ body: JSON.stringify(cleanUndefined({
2012
+ ...options.defaultBody,
2013
+ ...request.body,
2014
+ model: options.model,
2015
+ input: buildResponsesInput(request),
2016
+ previous_response_id: pickString(body?.previous_response_id),
2017
+ temperature: request.temperature,
2018
+ max_output_tokens: request.maxTokens,
2019
+ stream: true
2020
+ }))
2021
+ });
2022
+ if (!response.ok) {
2023
+ const message = await response.text();
2024
+ throw new Error(`HTTP ${response.status}: ${message}`);
2025
+ }
2026
+ callbacks.onStart?.();
2027
+ let text = "";
2028
+ let usage;
2029
+ let finishReason;
2030
+ let lastPayload;
2031
+ await consumeSSE(response, (data) => {
2032
+ if (data === "[DONE]") {
2033
+ return;
2034
+ }
2035
+ const json = safeJSONParse(data);
2036
+ if (!isRecord2(json)) {
2037
+ return;
2038
+ }
2039
+ const roundPayload = pickResponsesStreamPayload(json);
2040
+ if (roundPayload) {
2041
+ lastPayload = roundPayload;
2042
+ }
2043
+ const delta = pickResponsesStreamTextDelta(json);
2044
+ const chunkUsage = pickResponsesStreamUsage(json);
2045
+ const chunkFinishReason = pickResponsesStreamFinishReason(json);
2046
+ usage = mergeUsage(usage, chunkUsage);
2047
+ if (chunkFinishReason) {
2048
+ finishReason = chunkFinishReason;
2049
+ }
2050
+ if (delta) {
2051
+ text += delta;
2052
+ callbacks.onToken?.(delta);
2053
+ }
2054
+ if (delta || chunkUsage || chunkFinishReason) {
2055
+ const chunk = {
2056
+ textDelta: delta,
2057
+ raw: json,
2058
+ usage: chunkUsage,
2059
+ finishReason: chunkFinishReason
2060
+ };
2061
+ callbacks.onChunk?.(chunk);
2062
+ }
2063
+ });
2064
+ const finalPayload = lastPayload ?? {};
2065
+ const out = {
2066
+ text: text.length > 0 ? text : pickResponsesText(finalPayload) || pickAssistantText(finalPayload),
2067
+ raw: finalPayload,
2068
+ usage: mergeUsage(usage, pickUsage(finalPayload)),
2069
+ finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload)
2070
+ };
2071
+ callbacks.onComplete?.(out);
2072
+ return out;
2073
+ }
2074
+ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, callbacks) {
2075
+ const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
2076
+ let input = buildResponsesInput(request);
2077
+ let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
2078
+ let aggregatedUsage;
2079
+ let finishReason;
2080
+ let lastPayload;
2081
+ const executedToolCalls = [];
2082
+ const toolExecutions = [];
2083
+ callbacks.onStart?.();
2084
+ for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2085
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
2086
+ const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
2087
+ const response = await fetcher(buildURL(options.baseURL, path), {
2088
+ method: "POST",
2089
+ headers: buildHeaders(options),
2090
+ body: JSON.stringify(cleanUndefined({
2091
+ ...options.defaultBody,
2092
+ ...request.body,
2093
+ model: options.model,
2094
+ input,
2095
+ previous_response_id: previousResponseId,
2096
+ temperature: request.temperature,
2097
+ max_output_tokens: request.maxTokens,
2098
+ tools: transportTools,
2099
+ tool_choice: request.toolChoice,
2100
+ parallel_tool_calls: request.parallelToolCalls,
2101
+ stream: true
2102
+ }))
2103
+ });
2104
+ if (!response.ok) {
2105
+ const message = await response.text();
2106
+ throw new Error(`HTTP ${response.status}: ${message}`);
2107
+ }
2108
+ let roundText = "";
2109
+ let roundUsage;
2110
+ let roundFinishReason;
2111
+ let roundPayload;
2112
+ const streamedToolCalls = new Map;
2113
+ await consumeSSE(response, (data) => {
2114
+ if (data === "[DONE]") {
2115
+ return;
2116
+ }
2117
+ const json = safeJSONParse(data);
2118
+ if (!isRecord2(json)) {
2119
+ return;
2120
+ }
2121
+ const payload = pickResponsesStreamPayload(json);
2122
+ if (payload) {
2123
+ roundPayload = payload;
2124
+ lastPayload = payload;
2125
+ }
2126
+ const delta = pickResponsesStreamTextDelta(json);
2127
+ const chunkUsage = pickResponsesStreamUsage(json);
2128
+ const chunkFinishReason = pickResponsesStreamFinishReason(json);
2129
+ collectResponsesStreamToolCalls(json, streamedToolCalls);
2130
+ roundUsage = mergeUsage(roundUsage, chunkUsage);
2131
+ if (chunkFinishReason) {
2132
+ roundFinishReason = chunkFinishReason;
2133
+ }
2134
+ if (delta) {
2135
+ roundText += delta;
2136
+ callbacks.onToken?.(delta);
2137
+ }
2138
+ if (delta || chunkUsage || chunkFinishReason) {
2139
+ const chunk = {
2140
+ textDelta: delta,
2141
+ raw: json,
2142
+ usage: chunkUsage,
2143
+ finishReason: chunkFinishReason
2144
+ };
2145
+ callbacks.onChunk?.(chunk);
2146
+ }
2147
+ });
2148
+ aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
2149
+ const payloadUsage = roundPayload ? pickUsage(roundPayload) : undefined;
2150
+ aggregatedUsage = mergeUsage(aggregatedUsage, payloadUsage);
2151
+ if (roundFinishReason) {
2152
+ finishReason = roundFinishReason;
2153
+ } else if (roundPayload) {
2154
+ finishReason = pickResponsesFinishReason(roundPayload) ?? finishReason;
2155
+ }
2156
+ const payloadToolCalls = roundPayload ? pickResponsesToolCalls(roundPayload) : [];
2157
+ const streamedCalls = buildResponsesStreamToolCalls(streamedToolCalls);
2158
+ const providerToolCalls = payloadToolCalls.length > 0 ? payloadToolCalls : streamedCalls;
2159
+ const functionCalls = providerToolCalls.filter((toolCall) => toolCall.type === "function" && typeof toolCall.id === "string" && typeof toolCall.name === "string");
2160
+ if (functionCalls.length === 0) {
2161
+ const finalText = roundText.length > 0 ? roundText : roundPayload ? pickResponsesText(roundPayload) || pickAssistantText(roundPayload) : "";
2162
+ const out2 = {
2163
+ text: finalText,
2164
+ raw: roundPayload ?? lastPayload,
2165
+ usage: aggregatedUsage,
2166
+ finishReason,
2167
+ toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2168
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2169
+ };
2170
+ callbacks.onComplete?.(out2);
2171
+ return out2;
2172
+ }
2173
+ if (round > maxToolRounds) {
2174
+ throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
2175
+ }
2176
+ const outputs = await executeMCPToolCalls(functionCalls, mcpToolset, {
2177
+ round,
2178
+ request,
2179
+ provider: "openai-compatible",
2180
+ model: options.model
2181
+ });
2182
+ executedToolCalls.push(...outputs.map((entry) => entry.call));
2183
+ toolExecutions.push(...outputs.map((entry) => entry.execution));
2184
+ input = outputs.map((entry) => ({
2185
+ type: "function_call_output",
2186
+ call_id: entry.call.id,
2187
+ output: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
2188
+ }));
2189
+ previousResponseId = pickString(roundPayload?.id);
2190
+ }
2191
+ const out = {
2192
+ text: pickResponsesText(lastPayload ?? {}) || pickAssistantText(lastPayload ?? {}),
2193
+ raw: lastPayload,
2194
+ usage: aggregatedUsage,
2195
+ finishReason,
2196
+ toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2197
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2198
+ };
2199
+ callbacks.onComplete?.(out);
2200
+ return out;
2201
+ }
1870
2202
  function shouldUseResponsesAPI(options, request) {
1871
2203
  if (options.path?.includes("/responses")) {
1872
2204
  return true;
@@ -2023,6 +2355,190 @@ function pickAssistantDelta(payload) {
2023
2355
  }
2024
2356
  return "";
2025
2357
  }
2358
+ function collectOpenAIStreamToolCalls(payload, state) {
2359
+ const choices = payload.choices;
2360
+ if (!Array.isArray(choices) || choices.length === 0 || !isRecord2(choices[0])) {
2361
+ return;
2362
+ }
2363
+ const delta = choices[0].delta;
2364
+ if (!isRecord2(delta) || !Array.isArray(delta.tool_calls)) {
2365
+ return;
2366
+ }
2367
+ for (const rawToolCall of delta.tool_calls) {
2368
+ if (!isRecord2(rawToolCall)) {
2369
+ continue;
2370
+ }
2371
+ const index = toFiniteNumber(rawToolCall.index);
2372
+ const toolIndex = index !== undefined ? Math.floor(index) : 0;
2373
+ const existing = state.get(toolIndex) ?? {
2374
+ index: toolIndex,
2375
+ argumentsText: ""
2376
+ };
2377
+ const id = pickString(rawToolCall.id);
2378
+ if (id) {
2379
+ existing.id = id;
2380
+ }
2381
+ const type = pickString(rawToolCall.type);
2382
+ if (type) {
2383
+ existing.type = type;
2384
+ }
2385
+ const functionCall = isRecord2(rawToolCall.function) ? rawToolCall.function : undefined;
2386
+ const name = pickString(functionCall?.name);
2387
+ if (name) {
2388
+ existing.name = `${existing.name ?? ""}${name}`;
2389
+ }
2390
+ const argumentsDelta = pickString(functionCall?.arguments);
2391
+ if (argumentsDelta) {
2392
+ existing.argumentsText += argumentsDelta;
2393
+ }
2394
+ state.set(toolIndex, existing);
2395
+ }
2396
+ }
2397
+ function buildOpenAIStreamToolCalls(state) {
2398
+ return [...state.values()].sort((a, b) => a.index - b.index).map((entry) => ({
2399
+ id: entry.id ?? "",
2400
+ type: entry.type ?? "function",
2401
+ name: entry.name,
2402
+ arguments: entry.argumentsText.length > 0 ? entry.argumentsText : {}
2403
+ }));
2404
+ }
2405
+ function buildOpenAIAssistantToolMessage(text, toolCalls) {
2406
+ return {
2407
+ role: "assistant",
2408
+ content: text,
2409
+ tool_calls: toolCalls.map((call) => ({
2410
+ id: call.id,
2411
+ type: "function",
2412
+ function: {
2413
+ name: call.name,
2414
+ arguments: typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments ?? {})
2415
+ }
2416
+ }))
2417
+ };
2418
+ }
2419
+ function pickResponsesStreamPayload(payload) {
2420
+ if (isRecord2(payload.response)) {
2421
+ return payload.response;
2422
+ }
2423
+ if ("output" in payload || "output_text" in payload || "status" in payload || "id" in payload) {
2424
+ return payload;
2425
+ }
2426
+ return;
2427
+ }
2428
+ function pickResponsesStreamTextDelta(payload) {
2429
+ const eventType = pickString(payload.type) ?? "";
2430
+ if (!eventType.includes("output_text.delta")) {
2431
+ return "";
2432
+ }
2433
+ const direct = pickString(payload.delta);
2434
+ if (direct) {
2435
+ return direct;
2436
+ }
2437
+ if (isRecord2(payload.delta)) {
2438
+ return pickString(payload.delta.text) ?? pickString(payload.delta.output_text) ?? "";
2439
+ }
2440
+ return "";
2441
+ }
2442
+ function pickResponsesStreamUsage(payload) {
2443
+ const direct = pickUsage(payload);
2444
+ if (direct) {
2445
+ return direct;
2446
+ }
2447
+ if (isRecord2(payload.response)) {
2448
+ return pickUsage(payload.response);
2449
+ }
2450
+ return;
2451
+ }
2452
+ function pickResponsesStreamFinishReason(payload) {
2453
+ const eventType = pickString(payload.type);
2454
+ if (eventType === "response.completed") {
2455
+ return "completed";
2456
+ }
2457
+ if (eventType === "response.failed") {
2458
+ return "failed";
2459
+ }
2460
+ const directStatus = pickString(payload.status);
2461
+ if (directStatus) {
2462
+ return directStatus;
2463
+ }
2464
+ if (isRecord2(payload.response)) {
2465
+ return pickString(payload.response.status);
2466
+ }
2467
+ return;
2468
+ }
2469
+ function collectResponsesStreamToolCalls(payload, state) {
2470
+ if (isRecord2(payload.response)) {
2471
+ collectResponsesStreamToolCallsFromOutput(payload.response.output, state);
2472
+ }
2473
+ collectResponsesStreamToolCallsFromOutput(payload.output, state);
2474
+ if (isRecord2(payload.item)) {
2475
+ const itemKey = pickString(payload.item_id) ?? pickString(payload.call_id);
2476
+ collectResponsesStreamToolCallsFromItem(payload.item, state, itemKey);
2477
+ }
2478
+ if (isRecord2(payload.output_item)) {
2479
+ const itemKey = pickString(payload.item_id) ?? pickString(payload.call_id);
2480
+ collectResponsesStreamToolCallsFromItem(payload.output_item, state, itemKey);
2481
+ }
2482
+ const eventType = pickString(payload.type) ?? "";
2483
+ if (eventType.includes("function_call_arguments.delta")) {
2484
+ const key = pickString(payload.item_id) ?? pickString(payload.call_id) ?? "function_call";
2485
+ const existing = state.get(key) ?? {
2486
+ key,
2487
+ argumentsText: ""
2488
+ };
2489
+ const delta = pickString(payload.delta) ?? (isRecord2(payload.delta) ? pickString(payload.delta.text) ?? pickString(payload.delta.arguments) : undefined) ?? pickString(payload.arguments_delta);
2490
+ if (delta) {
2491
+ existing.argumentsText += delta;
2492
+ }
2493
+ state.set(key, existing);
2494
+ }
2495
+ }
2496
+ function collectResponsesStreamToolCallsFromOutput(output, state) {
2497
+ if (!Array.isArray(output)) {
2498
+ return;
2499
+ }
2500
+ for (const item of output) {
2501
+ if (!isRecord2(item)) {
2502
+ continue;
2503
+ }
2504
+ collectResponsesStreamToolCallsFromItem(item, state);
2505
+ }
2506
+ }
2507
+ function collectResponsesStreamToolCallsFromItem(item, state, forcedKey) {
2508
+ const type = pickString(item.type);
2509
+ if (type !== "function_call" && !type?.includes("tool") && !type?.includes("mcp")) {
2510
+ return;
2511
+ }
2512
+ const key = forcedKey ?? pickString(item.call_id) ?? pickString(item.id) ?? `call_${state.size}`;
2513
+ const existing = state.get(key) ?? {
2514
+ key,
2515
+ argumentsText: ""
2516
+ };
2517
+ const callId = pickString(item.call_id) ?? pickString(item.id);
2518
+ if (callId) {
2519
+ existing.id = callId;
2520
+ }
2521
+ if (type) {
2522
+ existing.type = type;
2523
+ }
2524
+ const name = pickString(item.name);
2525
+ if (name) {
2526
+ existing.name = name;
2527
+ }
2528
+ const argumentsText = pickString(item.arguments);
2529
+ if (argumentsText && argumentsText.length >= existing.argumentsText.length) {
2530
+ existing.argumentsText = argumentsText;
2531
+ }
2532
+ state.set(key, existing);
2533
+ }
2534
+ function buildResponsesStreamToolCalls(state) {
2535
+ return [...state.values()].map((entry) => ({
2536
+ id: entry.id ?? entry.key,
2537
+ type: entry.type === "function_call" ? "function" : entry.type ?? "function",
2538
+ name: entry.name,
2539
+ arguments: entry.argumentsText.length > 0 ? entry.argumentsText : {}
2540
+ }));
2541
+ }
2026
2542
  function pickResponsesText(payload) {
2027
2543
  const outputText = payload.output_text;
2028
2544
  if (typeof outputText === "string") {
@@ -2115,22 +2631,6 @@ function pickResponsesFinishReason(payload) {
2115
2631
  }
2116
2632
  return;
2117
2633
  }
2118
- async function streamViaComplete(callbacks, complete) {
2119
- callbacks.onStart?.();
2120
- const response = await complete();
2121
- if (response.text.length > 0) {
2122
- callbacks.onToken?.(response.text);
2123
- }
2124
- callbacks.onChunk?.({
2125
- textDelta: response.text,
2126
- raw: response.raw,
2127
- done: true,
2128
- usage: response.usage,
2129
- finishReason: response.finishReason
2130
- });
2131
- callbacks.onComplete?.(response);
2132
- return response;
2133
- }
2134
2634
 
2135
2635
  // src/providers/anthropic-compatible.ts
2136
2636
  var DEFAULT_ANTHROPIC_MAX_TOKENS = 1024;
@@ -2149,7 +2649,7 @@ function createAnthropicCompatibleAdapter(options) {
2149
2649
  },
2150
2650
  async stream(request, callbacks = {}) {
2151
2651
  if (hasMCPClients(request.mcpClients)) {
2152
- return streamViaComplete2(callbacks, () => this.complete(request));
2652
+ return streamWithMCPToolLoop(options, fetcher, path, request, callbacks);
2153
2653
  }
2154
2654
  const response = await fetcher(buildURL(options.baseURL, path), {
2155
2655
  method: "POST",
@@ -2242,8 +2742,6 @@ async function completePassThrough(options, fetcher, path, request) {
2242
2742
  };
2243
2743
  }
2244
2744
  async function completeWithMCPToolLoop(options, fetcher, path, request) {
2245
- const mcpToolset = await resolveMCPToolset(request.mcpClients);
2246
- const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
2247
2745
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
2248
2746
  let messages = [{ role: "user", content: request.prompt }];
2249
2747
  let aggregatedUsage;
@@ -2252,6 +2750,8 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2252
2750
  const toolCalls = [];
2253
2751
  const toolExecutions = [];
2254
2752
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2753
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
2754
+ const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
2255
2755
  const response = await fetcher(buildURL(options.baseURL, path), {
2256
2756
  method: "POST",
2257
2757
  headers: buildHeaders2(options),
@@ -2323,6 +2823,127 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2323
2823
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2324
2824
  };
2325
2825
  }
2826
+ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks) {
2827
+ const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
2828
+ let messages = [{ role: "user", content: request.prompt }];
2829
+ let aggregatedUsage;
2830
+ let finishReason;
2831
+ let lastPayload;
2832
+ const toolCalls = [];
2833
+ const toolExecutions = [];
2834
+ callbacks.onStart?.();
2835
+ for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2836
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
2837
+ const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
2838
+ const response = await fetcher(buildURL(options.baseURL, path), {
2839
+ method: "POST",
2840
+ headers: buildHeaders2(options),
2841
+ body: JSON.stringify(cleanUndefined({
2842
+ ...options.defaultBody,
2843
+ ...request.body,
2844
+ model: options.model,
2845
+ system: request.systemPrompt,
2846
+ messages,
2847
+ temperature: request.temperature,
2848
+ max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
2849
+ tools,
2850
+ tool_choice: toAnthropicToolChoice(request.toolChoice),
2851
+ stream: true
2852
+ }))
2853
+ });
2854
+ if (!response.ok) {
2855
+ const message = await response.text();
2856
+ throw new Error(`HTTP ${response.status}: ${message}`);
2857
+ }
2858
+ let roundText = "";
2859
+ let roundUsage;
2860
+ let roundFinishReason;
2861
+ const streamedToolCalls = new Map;
2862
+ await consumeSSE(response, (data) => {
2863
+ if (data === "[DONE]") {
2864
+ return;
2865
+ }
2866
+ const json = safeJSONParse(data);
2867
+ if (!isRecord2(json)) {
2868
+ return;
2869
+ }
2870
+ lastPayload = json;
2871
+ const delta = pickAnthropicDelta(json);
2872
+ const chunkUsage = pickUsage2(json);
2873
+ const chunkFinishReason = pickFinishReason2(json);
2874
+ collectAnthropicStreamToolCalls(json, streamedToolCalls);
2875
+ roundUsage = mergeUsage(roundUsage, chunkUsage);
2876
+ if (chunkFinishReason) {
2877
+ roundFinishReason = chunkFinishReason;
2878
+ }
2879
+ if (delta) {
2880
+ roundText += delta;
2881
+ callbacks.onToken?.(delta);
2882
+ }
2883
+ if (delta || chunkUsage || chunkFinishReason) {
2884
+ const chunk = {
2885
+ textDelta: delta,
2886
+ raw: json,
2887
+ usage: chunkUsage,
2888
+ finishReason: chunkFinishReason
2889
+ };
2890
+ callbacks.onChunk?.(chunk);
2891
+ }
2892
+ });
2893
+ aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
2894
+ if (roundFinishReason) {
2895
+ finishReason = roundFinishReason;
2896
+ }
2897
+ const calledTools = buildAnthropicStreamToolCalls(streamedToolCalls);
2898
+ if (calledTools.length === 0) {
2899
+ const out2 = {
2900
+ text: roundText,
2901
+ raw: lastPayload,
2902
+ usage: aggregatedUsage,
2903
+ finishReason,
2904
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
2905
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2906
+ };
2907
+ callbacks.onComplete?.(out2);
2908
+ return out2;
2909
+ }
2910
+ if (round > maxToolRounds) {
2911
+ throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
2912
+ }
2913
+ const toolResultContent = [];
2914
+ const outputs = await executeMCPToolCalls(calledTools, mcpToolset, {
2915
+ round,
2916
+ request,
2917
+ provider: "anthropic-compatible",
2918
+ model: options.model
2919
+ });
2920
+ toolCalls.push(...outputs.map((entry) => entry.call));
2921
+ toolExecutions.push(...outputs.map((entry) => entry.execution));
2922
+ for (const entry of outputs) {
2923
+ toolResultContent.push({
2924
+ type: "tool_result",
2925
+ tool_use_id: entry.call.id,
2926
+ ...entry.call.error ? { is_error: true } : {},
2927
+ content: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
2928
+ });
2929
+ }
2930
+ messages = [
2931
+ ...messages,
2932
+ { role: "assistant", content: buildAnthropicAssistantToolContent(roundText, calledTools) },
2933
+ { role: "user", content: toolResultContent }
2934
+ ];
2935
+ }
2936
+ const out = {
2937
+ text: "",
2938
+ raw: lastPayload,
2939
+ usage: aggregatedUsage,
2940
+ finishReason,
2941
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
2942
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2943
+ };
2944
+ callbacks.onComplete?.(out);
2945
+ return out;
2946
+ }
2326
2947
  function buildHeaders2(options) {
2327
2948
  return {
2328
2949
  "content-type": "application/json",
@@ -2385,6 +3006,83 @@ function pickAnthropicDelta(payload) {
2385
3006
  }
2386
3007
  return "";
2387
3008
  }
3009
+ function collectAnthropicStreamToolCalls(payload, state) {
3010
+ const eventType = pickString(payload.type);
3011
+ if (!eventType) {
3012
+ return;
3013
+ }
3014
+ if (eventType === "content_block_start" && isRecord2(payload.content_block)) {
3015
+ const block = payload.content_block;
3016
+ if (pickString(block.type) !== "tool_use") {
3017
+ return;
3018
+ }
3019
+ const index = pickContentBlockIndex(payload.index);
3020
+ const existing = state.get(index) ?? {
3021
+ index,
3022
+ argumentsText: ""
3023
+ };
3024
+ const id = pickString(block.id);
3025
+ if (id) {
3026
+ existing.id = id;
3027
+ }
3028
+ const name = pickString(block.name);
3029
+ if (name) {
3030
+ existing.name = name;
3031
+ }
3032
+ if ("input" in block) {
3033
+ existing.input = block.input;
3034
+ }
3035
+ state.set(index, existing);
3036
+ return;
3037
+ }
3038
+ if (eventType === "content_block_delta" && isRecord2(payload.delta)) {
3039
+ const delta = payload.delta;
3040
+ if (pickString(delta.type) !== "input_json_delta") {
3041
+ return;
3042
+ }
3043
+ const index = pickContentBlockIndex(payload.index);
3044
+ const existing = state.get(index) ?? {
3045
+ index,
3046
+ argumentsText: ""
3047
+ };
3048
+ const partial = pickString(delta.partial_json);
3049
+ if (partial) {
3050
+ existing.argumentsText += partial;
3051
+ }
3052
+ state.set(index, existing);
3053
+ }
3054
+ }
3055
+ function pickContentBlockIndex(value) {
3056
+ const numeric = toFiniteNumber(value);
3057
+ if (numeric !== undefined) {
3058
+ return Math.floor(numeric);
3059
+ }
3060
+ return 0;
3061
+ }
3062
+ function buildAnthropicStreamToolCalls(state) {
3063
+ return [...state.values()].sort((a, b) => a.index - b.index).map((entry) => ({
3064
+ id: entry.id ?? "",
3065
+ type: "function",
3066
+ name: entry.name,
3067
+ arguments: entry.argumentsText.length > 0 ? entry.argumentsText : entry.input ?? {}
3068
+ }));
3069
+ }
3070
+ function buildAnthropicAssistantToolContent(text, toolCalls) {
3071
+ const content = [];
3072
+ if (text.length > 0) {
3073
+ content.push({ type: "text", text });
3074
+ }
3075
+ for (const call of toolCalls) {
3076
+ const parsedArguments = typeof call.arguments === "string" ? parseToolArguments(call.arguments) : call.arguments;
3077
+ content.push({
3078
+ type: "tool_use",
3079
+ id: call.id,
3080
+ name: call.name,
3081
+ input: isRecord2(parsedArguments) ? parsedArguments : {}
3082
+ });
3083
+ }
3084
+ return content;
3085
+ }
2388
3086
  function pickUsage2(payload) {
2389
3087
  const fromUsage = extractUsageObject(payload.usage);
2390
3088
  if (fromUsage) {
@@ -2464,22 +3162,6 @@ function toAnthropicToolChoice(value) {
2464
3162
  }
2465
3163
  return value;
2466
3164
  }
2467
- async function streamViaComplete2(callbacks, complete) {
2468
- callbacks.onStart?.();
2469
- const response = await complete();
2470
- if (response.text.length > 0) {
2471
- callbacks.onToken?.(response.text);
2472
- }
2473
- callbacks.onChunk?.({
2474
- textDelta: response.text,
2475
- raw: response.raw,
2476
- done: true,
2477
- usage: response.usage,
2478
- finishReason: response.finishReason
2479
- });
2480
- callbacks.onComplete?.(response);
2481
- return response;
2482
- }
2483
3165
 
2484
3166
  // src/providers/registry.ts
2485
3167
  class InMemoryProviderRegistry {
@@ -3061,6 +3743,9 @@ var DEFAULT_SELF_HEAL_PROTOCOL = "extrait.self-heal.v2";
3061
3743
  var DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS = 12000;
3062
3744
  var DEFAULT_SELF_HEAL_STOP_ON_NO_PROGRESS = true;
3063
3745
  var DEFAULT_SELF_HEAL_MAX_ERRORS = 8;
3746
+ var RE_SIMPLE_IDENTIFIER2 = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
3747
+ var RE_ESCAPE_QUOTE = /"/g;
3748
+ var RE_WHITESPACE2 = /\s+/g;
3064
3749
  var DEFAULT_SELF_HEAL_MAX_DIAGNOSTICS = 8;
3065
3750
  var structuredOutdent = createOutdent({
3066
3751
  trimLeadingNewline: true,
@@ -3405,11 +4090,11 @@ function formatIssuePath(path) {
3405
4090
  out += `[${segment}]`;
3406
4091
  continue;
3407
4092
  }
3408
- if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segment)) {
4093
+ if (RE_SIMPLE_IDENTIFIER2.test(segment)) {
3409
4094
  out += `.${segment}`;
3410
4095
  continue;
3411
4096
  }
3412
- out += `["${segment.replace(/"/g, "\\\"")}"]`;
4097
+ out += `["${segment.replace(RE_ESCAPE_QUOTE, "\\\"")}"]`;
3413
4098
  }
3414
4099
  return out;
3415
4100
  }
@@ -3452,7 +4137,7 @@ function buildSelfHealFailureFingerprint(attempt) {
3452
4137
  return [issues, errors, source].join("::");
3453
4138
  }
3454
4139
  function normalizeWhitespace(value) {
3455
- return value.replace(/\s+/g, " ").trim();
4140
+ return value.replace(RE_WHITESPACE2, " ").trim();
3456
4141
  }
3457
4142
  function normalizeStreamConfig(option) {
3458
4143
  if (typeof option === "boolean") {