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.js CHANGED
@@ -219,6 +219,9 @@ function isOnlyWhitespace(value) {
219
219
  }
220
220
 
221
221
  // src/extract.ts
222
+ var RE_EMPTY_OBJECT = /^\{\s*\}$/;
223
+ var RE_EMPTY_ARRAY = /^\[\s*\]$/;
224
+ var RE_BOUNDARY_CHAR = /[\s,.;:!?`"'()\[\]{}<>]/;
222
225
  var DEFAULT_EXTRACTION_HEURISTICS = {
223
226
  firstPassMin: 12,
224
227
  firstPassCap: 30,
@@ -443,7 +446,7 @@ function jsonShapeScore(content, acceptArrays) {
443
446
  const commaCount = countChar(trimmed, ",");
444
447
  const quoteCount = countChar(trimmed, '"');
445
448
  if (root === "{") {
446
- if (/^\{\s*\}$/.test(trimmed)) {
449
+ if (RE_EMPTY_OBJECT.test(trimmed)) {
447
450
  score += 12;
448
451
  } else if (colonCount > 0) {
449
452
  score += 22;
@@ -454,7 +457,7 @@ function jsonShapeScore(content, acceptArrays) {
454
457
  score += quoteCount % 2 === 0 ? 8 : -8;
455
458
  }
456
459
  } else {
457
- score += /^\[\s*\]$/.test(trimmed) ? 8 : 4;
460
+ score += RE_EMPTY_ARRAY.test(trimmed) ? 8 : 4;
458
461
  if (colonCount > 0) {
459
462
  score += 4;
460
463
  }
@@ -487,7 +490,7 @@ function isBoundary(char) {
487
490
  if (!char) {
488
491
  return true;
489
492
  }
490
- return /[\s,.;:!?`"'()[\]{}<>]/.test(char);
493
+ return RE_BOUNDARY_CHAR.test(char);
491
494
  }
492
495
  function lengthScore(length) {
493
496
  return Math.min(120, Math.floor(Math.sqrt(length) * 6));
@@ -676,6 +679,8 @@ function clamp(value, min, max) {
676
679
  return Math.max(min, Math.min(max, Math.floor(value)));
677
680
  }
678
681
  // src/schema.ts
682
+ var RE_SIMPLE_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
683
+ var RE_WHITESPACE = /\s+/g;
679
684
  function formatZodSchemaLikeTypeScript(schema) {
680
685
  return formatType(schema, 0, new WeakSet);
681
686
  }
@@ -847,7 +852,7 @@ ${lines.join(`
847
852
  ${indent}}`;
848
853
  }
849
854
  function formatKey(key) {
850
- return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
855
+ return RE_SIMPLE_IDENTIFIER.test(key) ? key : JSON.stringify(key);
851
856
  }
852
857
  function requiresParentheses(typeText) {
853
858
  return typeText.includes(" | ") || typeText.includes(" & ");
@@ -893,7 +898,7 @@ function readSchemaDescription(schema) {
893
898
  return;
894
899
  }
895
900
  function sanitizeDescription(value) {
896
- return value.replace(/\s+/g, " ").trim();
901
+ return value.replace(RE_WHITESPACE, " ").trim();
897
902
  }
898
903
 
899
904
  // src/format.ts
@@ -918,6 +923,8 @@ function resolveSchemaInstruction(instruction) {
918
923
  }
919
924
  // src/think.ts
920
925
  var THINK_TAG_NAME = "think";
926
+ var RE_IDENTIFIER_CHAR = /[a-zA-Z0-9:_-]/;
927
+ var RE_NON_LINE_BREAK = /[^\r\n]/g;
921
928
  function sanitizeThink(input) {
922
929
  const thinkBlocks = [];
923
930
  const diagnostics = {
@@ -1016,6 +1023,9 @@ function parseThinkTagAt(input, index) {
1016
1023
  if (input[cursor] === "/") {
1017
1024
  closing = true;
1018
1025
  cursor += 1;
1026
+ while (cursor < input.length && isWhitespace(input[cursor])) {
1027
+ cursor += 1;
1028
+ }
1019
1029
  }
1020
1030
  if (!matchesIgnoreCase(input, cursor, THINK_TAG_NAME)) {
1021
1031
  return null;
@@ -1072,7 +1082,7 @@ function matchesIgnoreCase(input, index, expected) {
1072
1082
  return input.slice(index, index + expected.length).toLowerCase() === expected;
1073
1083
  }
1074
1084
  function isIdentifierChar(char) {
1075
- return /[a-zA-Z0-9:_-]/.test(char);
1085
+ return RE_IDENTIFIER_CHAR.test(char);
1076
1086
  }
1077
1087
  function countHiddenChars(value) {
1078
1088
  let count = 0;
@@ -1086,9 +1096,10 @@ function countHiddenChars(value) {
1086
1096
  return count;
1087
1097
  }
1088
1098
  function maskKeepingLineBreaks(value) {
1089
- return value.replace(/[^\r\n]/g, " ");
1099
+ return value.replace(RE_NON_LINE_BREAK, " ");
1090
1100
  }
1091
1101
  // src/providers/stream-utils.ts
1102
+ var RE_LINE_ENDING = /\r?\n/;
1092
1103
  async function consumeSSE(response, onEvent) {
1093
1104
  if (!response.body) {
1094
1105
  return;
@@ -1111,7 +1122,7 @@ async function consumeSSE(response, onEvent) {
1111
1122
  buffer = buffer.slice(boundary + (buffer.startsWith(`\r
1112
1123
  \r
1113
1124
  `, boundary) ? 4 : 2));
1114
- const dataLines = rawEvent.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
1125
+ const dataLines = rawEvent.split(RE_LINE_ENDING).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
1115
1126
  if (dataLines.length === 0) {
1116
1127
  continue;
1117
1128
  }
@@ -1121,7 +1132,7 @@ async function consumeSSE(response, onEvent) {
1121
1132
  }
1122
1133
  const remainder = buffer.trim();
1123
1134
  if (remainder.length > 0) {
1124
- const dataLines = remainder.split(/\r?\n/).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
1135
+ const dataLines = remainder.split(RE_LINE_ENDING).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim());
1125
1136
  if (dataLines.length > 0) {
1126
1137
  onEvent(dataLines.join(`
1127
1138
  `));
@@ -1411,13 +1422,18 @@ function describeTool(clientId, tool, hasCollision) {
1411
1422
  }
1412
1423
  return;
1413
1424
  }
1425
+ var RE_NON_ALPHANUMERIC = /[^A-Za-z0-9_]/g;
1426
+ var RE_MULTIPLE_UNDERSCORES = /_+/g;
1427
+ var RE_LEADING_UNDERSCORES = /^_+/;
1428
+ var RE_TRAILING_UNDERSCORES = /_+$/;
1429
+ var RE_STARTS_WITH_DIGIT = /^[0-9]/;
1414
1430
  function sanitizeToolName(input) {
1415
- const sanitized = input.replace(/[^A-Za-z0-9_]/g, "_").replace(/_+/g, "_");
1416
- const trimmed = sanitized.replace(/^_+/, "").replace(/_+$/, "");
1431
+ const sanitized = input.replace(RE_NON_ALPHANUMERIC, "_").replace(RE_MULTIPLE_UNDERSCORES, "_");
1432
+ const trimmed = sanitized.replace(RE_LEADING_UNDERSCORES, "").replace(RE_TRAILING_UNDERSCORES, "");
1417
1433
  if (!trimmed) {
1418
1434
  return "tool";
1419
1435
  }
1420
- if (/^[0-9]/.test(trimmed)) {
1436
+ if (RE_STARTS_WITH_DIGIT.test(trimmed)) {
1421
1437
  return `tool_${trimmed}`;
1422
1438
  }
1423
1439
  return trimmed;
@@ -1502,8 +1518,14 @@ function createOpenAICompatibleAdapter(options) {
1502
1518
  async stream(request, callbacks = {}) {
1503
1519
  const usesResponses = shouldUseResponsesAPI(options, request);
1504
1520
  const usesMCP = hasMCPClients(request.mcpClients);
1505
- if (usesResponses || usesMCP) {
1506
- return streamViaComplete(callbacks, () => completeOpenAIRequest(options, fetcher, path, responsesPath, request));
1521
+ if (usesResponses) {
1522
+ if (usesMCP) {
1523
+ return streamWithResponsesAPIWithMCP(options, fetcher, responsesPath, request, callbacks);
1524
+ }
1525
+ return streamWithResponsesAPIPassThrough(options, fetcher, responsesPath, request, callbacks);
1526
+ }
1527
+ if (usesMCP) {
1528
+ return streamWithChatCompletionsWithMCP(options, fetcher, path, request, callbacks);
1507
1529
  }
1508
1530
  const response = await fetcher(buildURL(options.baseURL, path), {
1509
1531
  method: "POST",
@@ -1608,8 +1630,6 @@ async function completeWithChatCompletionsPassThrough(options, fetcher, path, re
1608
1630
  };
1609
1631
  }
1610
1632
  async function completeWithChatCompletionsWithMCP(options, fetcher, path, request) {
1611
- const mcpToolset = await resolveMCPToolset(request.mcpClients);
1612
- const transportTools = toProviderFunctionTools(mcpToolset);
1613
1633
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1614
1634
  let messages = buildMessages(request);
1615
1635
  let aggregatedUsage;
@@ -1618,6 +1638,8 @@ async function completeWithChatCompletionsWithMCP(options, fetcher, path, reques
1618
1638
  const toolCalls = [];
1619
1639
  const toolExecutions = [];
1620
1640
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1641
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
1642
+ const transportTools = toProviderFunctionTools(mcpToolset);
1621
1643
  const response = await fetcher(buildURL(options.baseURL, path), {
1622
1644
  method: "POST",
1623
1645
  headers: buildHeaders(options),
@@ -1713,8 +1735,6 @@ async function completeWithResponsesAPIPassThrough(options, fetcher, path, reque
1713
1735
  };
1714
1736
  }
1715
1737
  async function completeWithResponsesAPIWithMCP(options, fetcher, path, request) {
1716
- const mcpToolset = await resolveMCPToolset(request.mcpClients);
1717
- const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
1718
1738
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1719
1739
  let input = buildResponsesInput(request);
1720
1740
  let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
@@ -1724,6 +1744,8 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
1724
1744
  const executedToolCalls = [];
1725
1745
  const toolExecutions = [];
1726
1746
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1747
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
1748
+ const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
1727
1749
  const response = await fetcher(buildURL(options.baseURL, path), {
1728
1750
  method: "POST",
1729
1751
  headers: buildHeaders(options),
@@ -1788,6 +1810,316 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
1788
1810
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1789
1811
  };
1790
1812
  }
1813
+ async function streamWithChatCompletionsWithMCP(options, fetcher, path, request, callbacks) {
1814
+ const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1815
+ let messages = buildMessages(request);
1816
+ let aggregatedUsage;
1817
+ let finishReason;
1818
+ let lastPayload;
1819
+ const executedToolCalls = [];
1820
+ const toolExecutions = [];
1821
+ callbacks.onStart?.();
1822
+ for (let round = 1;round <= maxToolRounds + 1; round += 1) {
1823
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
1824
+ const transportTools = toProviderFunctionTools(mcpToolset);
1825
+ const response = await fetcher(buildURL(options.baseURL, path), {
1826
+ method: "POST",
1827
+ headers: buildHeaders(options),
1828
+ body: JSON.stringify(cleanUndefined({
1829
+ ...options.defaultBody,
1830
+ ...request.body,
1831
+ model: options.model,
1832
+ messages,
1833
+ temperature: request.temperature,
1834
+ max_tokens: request.maxTokens,
1835
+ tools: transportTools,
1836
+ tool_choice: request.toolChoice,
1837
+ parallel_tool_calls: request.parallelToolCalls,
1838
+ stream: true
1839
+ }))
1840
+ });
1841
+ if (!response.ok) {
1842
+ const message = await response.text();
1843
+ throw new Error(`HTTP ${response.status}: ${message}`);
1844
+ }
1845
+ let roundText = "";
1846
+ let roundUsage;
1847
+ let roundFinishReason;
1848
+ const streamedToolCalls = new Map;
1849
+ await consumeSSE(response, (data) => {
1850
+ if (data === "[DONE]") {
1851
+ return;
1852
+ }
1853
+ const json = safeJSONParse(data);
1854
+ if (!isRecord2(json)) {
1855
+ return;
1856
+ }
1857
+ lastPayload = json;
1858
+ const delta = pickAssistantDelta(json);
1859
+ const chunkUsage = pickUsage(json);
1860
+ const chunkFinishReason = pickFinishReason(json);
1861
+ collectOpenAIStreamToolCalls(json, streamedToolCalls);
1862
+ roundUsage = mergeUsage(roundUsage, chunkUsage);
1863
+ if (chunkFinishReason) {
1864
+ roundFinishReason = chunkFinishReason;
1865
+ }
1866
+ if (delta) {
1867
+ roundText += delta;
1868
+ callbacks.onToken?.(delta);
1869
+ }
1870
+ if (delta || chunkUsage || chunkFinishReason) {
1871
+ const chunk = {
1872
+ textDelta: delta,
1873
+ raw: json,
1874
+ usage: chunkUsage,
1875
+ finishReason: chunkFinishReason
1876
+ };
1877
+ callbacks.onChunk?.(chunk);
1878
+ }
1879
+ });
1880
+ aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
1881
+ if (roundFinishReason) {
1882
+ finishReason = roundFinishReason;
1883
+ }
1884
+ const calledTools = buildOpenAIStreamToolCalls(streamedToolCalls);
1885
+ if (calledTools.length === 0) {
1886
+ const out2 = {
1887
+ text: roundText,
1888
+ raw: lastPayload,
1889
+ usage: aggregatedUsage,
1890
+ finishReason,
1891
+ toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
1892
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1893
+ };
1894
+ callbacks.onComplete?.(out2);
1895
+ return out2;
1896
+ }
1897
+ if (round > maxToolRounds) {
1898
+ throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
1899
+ }
1900
+ const outputs = await executeMCPToolCalls(calledTools, mcpToolset, {
1901
+ round,
1902
+ request,
1903
+ provider: "openai-compatible",
1904
+ model: options.model
1905
+ });
1906
+ executedToolCalls.push(...outputs.map((entry) => entry.call));
1907
+ toolExecutions.push(...outputs.map((entry) => entry.execution));
1908
+ const assistantMessage = buildOpenAIAssistantToolMessage(roundText, calledTools);
1909
+ const toolMessages = outputs.map((entry) => ({
1910
+ role: "tool",
1911
+ tool_call_id: entry.call.id,
1912
+ content: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
1913
+ }));
1914
+ messages = [...messages, assistantMessage, ...toolMessages];
1915
+ }
1916
+ const out = {
1917
+ text: "",
1918
+ raw: lastPayload,
1919
+ usage: aggregatedUsage,
1920
+ finishReason,
1921
+ toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
1922
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1923
+ };
1924
+ callbacks.onComplete?.(out);
1925
+ return out;
1926
+ }
1927
+ async function streamWithResponsesAPIPassThrough(options, fetcher, path, request, callbacks) {
1928
+ const body = isRecord2(request.body) ? request.body : undefined;
1929
+ const response = await fetcher(buildURL(options.baseURL, path), {
1930
+ method: "POST",
1931
+ headers: buildHeaders(options),
1932
+ body: JSON.stringify(cleanUndefined({
1933
+ ...options.defaultBody,
1934
+ ...request.body,
1935
+ model: options.model,
1936
+ input: buildResponsesInput(request),
1937
+ previous_response_id: pickString(body?.previous_response_id),
1938
+ temperature: request.temperature,
1939
+ max_output_tokens: request.maxTokens,
1940
+ stream: true
1941
+ }))
1942
+ });
1943
+ if (!response.ok) {
1944
+ const message = await response.text();
1945
+ throw new Error(`HTTP ${response.status}: ${message}`);
1946
+ }
1947
+ callbacks.onStart?.();
1948
+ let text = "";
1949
+ let usage;
1950
+ let finishReason;
1951
+ let lastPayload;
1952
+ await consumeSSE(response, (data) => {
1953
+ if (data === "[DONE]") {
1954
+ return;
1955
+ }
1956
+ const json = safeJSONParse(data);
1957
+ if (!isRecord2(json)) {
1958
+ return;
1959
+ }
1960
+ const roundPayload = pickResponsesStreamPayload(json);
1961
+ if (roundPayload) {
1962
+ lastPayload = roundPayload;
1963
+ }
1964
+ const delta = pickResponsesStreamTextDelta(json);
1965
+ const chunkUsage = pickResponsesStreamUsage(json);
1966
+ const chunkFinishReason = pickResponsesStreamFinishReason(json);
1967
+ usage = mergeUsage(usage, chunkUsage);
1968
+ if (chunkFinishReason) {
1969
+ finishReason = chunkFinishReason;
1970
+ }
1971
+ if (delta) {
1972
+ text += delta;
1973
+ callbacks.onToken?.(delta);
1974
+ }
1975
+ if (delta || chunkUsage || chunkFinishReason) {
1976
+ const chunk = {
1977
+ textDelta: delta,
1978
+ raw: json,
1979
+ usage: chunkUsage,
1980
+ finishReason: chunkFinishReason
1981
+ };
1982
+ callbacks.onChunk?.(chunk);
1983
+ }
1984
+ });
1985
+ const finalPayload = lastPayload ?? {};
1986
+ const out = {
1987
+ text: text.length > 0 ? text : pickResponsesText(finalPayload) || pickAssistantText(finalPayload),
1988
+ raw: finalPayload,
1989
+ usage: mergeUsage(usage, pickUsage(finalPayload)),
1990
+ finishReason: finishReason ?? pickResponsesFinishReason(finalPayload) ?? pickFinishReason(finalPayload)
1991
+ };
1992
+ callbacks.onComplete?.(out);
1993
+ return out;
1994
+ }
1995
+ async function streamWithResponsesAPIWithMCP(options, fetcher, path, request, callbacks) {
1996
+ const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
1997
+ let input = buildResponsesInput(request);
1998
+ let previousResponseId = pickString(isRecord2(request.body) ? request.body.previous_response_id : undefined);
1999
+ let aggregatedUsage;
2000
+ let finishReason;
2001
+ let lastPayload;
2002
+ const executedToolCalls = [];
2003
+ const toolExecutions = [];
2004
+ callbacks.onStart?.();
2005
+ for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2006
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
2007
+ const transportTools = toResponsesTools(toProviderFunctionTools(mcpToolset));
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,
2016
+ previous_response_id: previousResponseId,
2017
+ temperature: request.temperature,
2018
+ max_output_tokens: request.maxTokens,
2019
+ tools: transportTools,
2020
+ tool_choice: request.toolChoice,
2021
+ parallel_tool_calls: request.parallelToolCalls,
2022
+ stream: true
2023
+ }))
2024
+ });
2025
+ if (!response.ok) {
2026
+ const message = await response.text();
2027
+ throw new Error(`HTTP ${response.status}: ${message}`);
2028
+ }
2029
+ let roundText = "";
2030
+ let roundUsage;
2031
+ let roundFinishReason;
2032
+ let roundPayload;
2033
+ const streamedToolCalls = new Map;
2034
+ await consumeSSE(response, (data) => {
2035
+ if (data === "[DONE]") {
2036
+ return;
2037
+ }
2038
+ const json = safeJSONParse(data);
2039
+ if (!isRecord2(json)) {
2040
+ return;
2041
+ }
2042
+ const payload = pickResponsesStreamPayload(json);
2043
+ if (payload) {
2044
+ roundPayload = payload;
2045
+ lastPayload = payload;
2046
+ }
2047
+ const delta = pickResponsesStreamTextDelta(json);
2048
+ const chunkUsage = pickResponsesStreamUsage(json);
2049
+ const chunkFinishReason = pickResponsesStreamFinishReason(json);
2050
+ collectResponsesStreamToolCalls(json, streamedToolCalls);
2051
+ roundUsage = mergeUsage(roundUsage, chunkUsage);
2052
+ if (chunkFinishReason) {
2053
+ roundFinishReason = chunkFinishReason;
2054
+ }
2055
+ if (delta) {
2056
+ roundText += delta;
2057
+ callbacks.onToken?.(delta);
2058
+ }
2059
+ if (delta || chunkUsage || chunkFinishReason) {
2060
+ const chunk = {
2061
+ textDelta: delta,
2062
+ raw: json,
2063
+ usage: chunkUsage,
2064
+ finishReason: chunkFinishReason
2065
+ };
2066
+ callbacks.onChunk?.(chunk);
2067
+ }
2068
+ });
2069
+ aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
2070
+ const payloadUsage = roundPayload ? pickUsage(roundPayload) : undefined;
2071
+ aggregatedUsage = mergeUsage(aggregatedUsage, payloadUsage);
2072
+ if (roundFinishReason) {
2073
+ finishReason = roundFinishReason;
2074
+ } else if (roundPayload) {
2075
+ finishReason = pickResponsesFinishReason(roundPayload) ?? finishReason;
2076
+ }
2077
+ const payloadToolCalls = roundPayload ? pickResponsesToolCalls(roundPayload) : [];
2078
+ const streamedCalls = buildResponsesStreamToolCalls(streamedToolCalls);
2079
+ const providerToolCalls = payloadToolCalls.length > 0 ? payloadToolCalls : streamedCalls;
2080
+ const functionCalls = providerToolCalls.filter((toolCall) => toolCall.type === "function" && typeof toolCall.id === "string" && typeof toolCall.name === "string");
2081
+ if (functionCalls.length === 0) {
2082
+ const finalText = roundText.length > 0 ? roundText : roundPayload ? pickResponsesText(roundPayload) || pickAssistantText(roundPayload) : "";
2083
+ const out2 = {
2084
+ text: finalText,
2085
+ raw: roundPayload ?? lastPayload,
2086
+ usage: aggregatedUsage,
2087
+ finishReason,
2088
+ toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2089
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2090
+ };
2091
+ callbacks.onComplete?.(out2);
2092
+ return out2;
2093
+ }
2094
+ if (round > maxToolRounds) {
2095
+ throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
2096
+ }
2097
+ const outputs = await executeMCPToolCalls(functionCalls, mcpToolset, {
2098
+ round,
2099
+ request,
2100
+ provider: "openai-compatible",
2101
+ model: options.model
2102
+ });
2103
+ executedToolCalls.push(...outputs.map((entry) => entry.call));
2104
+ toolExecutions.push(...outputs.map((entry) => entry.execution));
2105
+ input = outputs.map((entry) => ({
2106
+ type: "function_call_output",
2107
+ call_id: entry.call.id,
2108
+ output: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
2109
+ }));
2110
+ previousResponseId = pickString(roundPayload?.id);
2111
+ }
2112
+ const out = {
2113
+ text: pickResponsesText(lastPayload ?? {}) || pickAssistantText(lastPayload ?? {}),
2114
+ raw: lastPayload,
2115
+ usage: aggregatedUsage,
2116
+ finishReason,
2117
+ toolCalls: executedToolCalls.length > 0 ? executedToolCalls : undefined,
2118
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2119
+ };
2120
+ callbacks.onComplete?.(out);
2121
+ return out;
2122
+ }
1791
2123
  function shouldUseResponsesAPI(options, request) {
1792
2124
  if (options.path?.includes("/responses")) {
1793
2125
  return true;
@@ -1944,6 +2276,190 @@ function pickAssistantDelta(payload) {
1944
2276
  }
1945
2277
  return "";
1946
2278
  }
2279
+ function collectOpenAIStreamToolCalls(payload, state) {
2280
+ const choices = payload.choices;
2281
+ if (!Array.isArray(choices) || choices.length === 0 || !isRecord2(choices[0])) {
2282
+ return;
2283
+ }
2284
+ const delta = choices[0].delta;
2285
+ if (!isRecord2(delta) || !Array.isArray(delta.tool_calls)) {
2286
+ return;
2287
+ }
2288
+ for (const rawToolCall of delta.tool_calls) {
2289
+ if (!isRecord2(rawToolCall)) {
2290
+ continue;
2291
+ }
2292
+ const index = toFiniteNumber(rawToolCall.index);
2293
+ const toolIndex = index !== undefined ? Math.floor(index) : 0;
2294
+ const existing = state.get(toolIndex) ?? {
2295
+ index: toolIndex,
2296
+ argumentsText: ""
2297
+ };
2298
+ const id = pickString(rawToolCall.id);
2299
+ if (id) {
2300
+ existing.id = id;
2301
+ }
2302
+ const type = pickString(rawToolCall.type);
2303
+ if (type) {
2304
+ existing.type = type;
2305
+ }
2306
+ const functionCall = isRecord2(rawToolCall.function) ? rawToolCall.function : undefined;
2307
+ const name = pickString(functionCall?.name);
2308
+ if (name) {
2309
+ existing.name = `${existing.name ?? ""}${name}`;
2310
+ }
2311
+ const argumentsDelta = pickString(functionCall?.arguments);
2312
+ if (argumentsDelta) {
2313
+ existing.argumentsText += argumentsDelta;
2314
+ }
2315
+ state.set(toolIndex, existing);
2316
+ }
2317
+ }
2318
+ function buildOpenAIStreamToolCalls(state) {
2319
+ return [...state.values()].sort((a, b) => a.index - b.index).map((entry) => ({
2320
+ id: entry.id ?? "",
2321
+ type: entry.type ?? "function",
2322
+ name: entry.name,
2323
+ arguments: entry.argumentsText.length > 0 ? entry.argumentsText : {}
2324
+ }));
2325
+ }
2326
+ function buildOpenAIAssistantToolMessage(text, toolCalls) {
2327
+ return {
2328
+ role: "assistant",
2329
+ content: text,
2330
+ tool_calls: toolCalls.map((call) => ({
2331
+ id: call.id,
2332
+ type: "function",
2333
+ function: {
2334
+ name: call.name,
2335
+ arguments: typeof call.arguments === "string" ? call.arguments : JSON.stringify(call.arguments ?? {})
2336
+ }
2337
+ }))
2338
+ };
2339
+ }
2340
+ function pickResponsesStreamPayload(payload) {
2341
+ if (isRecord2(payload.response)) {
2342
+ return payload.response;
2343
+ }
2344
+ if ("output" in payload || "output_text" in payload || "status" in payload || "id" in payload) {
2345
+ return payload;
2346
+ }
2347
+ return;
2348
+ }
2349
+ function pickResponsesStreamTextDelta(payload) {
2350
+ const eventType = pickString(payload.type) ?? "";
2351
+ if (!eventType.includes("output_text.delta")) {
2352
+ return "";
2353
+ }
2354
+ const direct = pickString(payload.delta);
2355
+ if (direct) {
2356
+ return direct;
2357
+ }
2358
+ if (isRecord2(payload.delta)) {
2359
+ return pickString(payload.delta.text) ?? pickString(payload.delta.output_text) ?? "";
2360
+ }
2361
+ return "";
2362
+ }
2363
+ function pickResponsesStreamUsage(payload) {
2364
+ const direct = pickUsage(payload);
2365
+ if (direct) {
2366
+ return direct;
2367
+ }
2368
+ if (isRecord2(payload.response)) {
2369
+ return pickUsage(payload.response);
2370
+ }
2371
+ return;
2372
+ }
2373
+ function pickResponsesStreamFinishReason(payload) {
2374
+ const eventType = pickString(payload.type);
2375
+ if (eventType === "response.completed") {
2376
+ return "completed";
2377
+ }
2378
+ if (eventType === "response.failed") {
2379
+ return "failed";
2380
+ }
2381
+ const directStatus = pickString(payload.status);
2382
+ if (directStatus) {
2383
+ return directStatus;
2384
+ }
2385
+ if (isRecord2(payload.response)) {
2386
+ return pickString(payload.response.status);
2387
+ }
2388
+ return;
2389
+ }
2390
+ function collectResponsesStreamToolCalls(payload, state) {
2391
+ if (isRecord2(payload.response)) {
2392
+ collectResponsesStreamToolCallsFromOutput(payload.response.output, state);
2393
+ }
2394
+ collectResponsesStreamToolCallsFromOutput(payload.output, state);
2395
+ if (isRecord2(payload.item)) {
2396
+ const itemKey = pickString(payload.item_id) ?? pickString(payload.call_id);
2397
+ collectResponsesStreamToolCallsFromItem(payload.item, state, itemKey);
2398
+ }
2399
+ if (isRecord2(payload.output_item)) {
2400
+ const itemKey = pickString(payload.item_id) ?? pickString(payload.call_id);
2401
+ collectResponsesStreamToolCallsFromItem(payload.output_item, state, itemKey);
2402
+ }
2403
+ const eventType = pickString(payload.type) ?? "";
2404
+ if (eventType.includes("function_call_arguments.delta")) {
2405
+ const key = pickString(payload.item_id) ?? pickString(payload.call_id) ?? "function_call";
2406
+ const existing = state.get(key) ?? {
2407
+ key,
2408
+ argumentsText: ""
2409
+ };
2410
+ const delta = pickString(payload.delta) ?? (isRecord2(payload.delta) ? pickString(payload.delta.text) ?? pickString(payload.delta.arguments) : undefined) ?? pickString(payload.arguments_delta);
2411
+ if (delta) {
2412
+ existing.argumentsText += delta;
2413
+ }
2414
+ state.set(key, existing);
2415
+ }
2416
+ }
2417
+ function collectResponsesStreamToolCallsFromOutput(output, state) {
2418
+ if (!Array.isArray(output)) {
2419
+ return;
2420
+ }
2421
+ for (const item of output) {
2422
+ if (!isRecord2(item)) {
2423
+ continue;
2424
+ }
2425
+ collectResponsesStreamToolCallsFromItem(item, state);
2426
+ }
2427
+ }
2428
+ function collectResponsesStreamToolCallsFromItem(item, state, forcedKey) {
2429
+ const type = pickString(item.type);
2430
+ if (type !== "function_call" && !type?.includes("tool") && !type?.includes("mcp")) {
2431
+ return;
2432
+ }
2433
+ const key = forcedKey ?? pickString(item.call_id) ?? pickString(item.id) ?? `call_${state.size}`;
2434
+ const existing = state.get(key) ?? {
2435
+ key,
2436
+ argumentsText: ""
2437
+ };
2438
+ const callId = pickString(item.call_id) ?? pickString(item.id);
2439
+ if (callId) {
2440
+ existing.id = callId;
2441
+ }
2442
+ if (type) {
2443
+ existing.type = type;
2444
+ }
2445
+ const name = pickString(item.name);
2446
+ if (name) {
2447
+ existing.name = name;
2448
+ }
2449
+ const argumentsText = pickString(item.arguments);
2450
+ if (argumentsText && argumentsText.length >= existing.argumentsText.length) {
2451
+ existing.argumentsText = argumentsText;
2452
+ }
2453
+ state.set(key, existing);
2454
+ }
2455
+ function buildResponsesStreamToolCalls(state) {
2456
+ return [...state.values()].map((entry) => ({
2457
+ id: entry.id ?? entry.key,
2458
+ type: entry.type === "function_call" ? "function" : entry.type ?? "function",
2459
+ name: entry.name,
2460
+ arguments: entry.argumentsText.length > 0 ? entry.argumentsText : {}
2461
+ }));
2462
+ }
1947
2463
  function pickResponsesText(payload) {
1948
2464
  const outputText = payload.output_text;
1949
2465
  if (typeof outputText === "string") {
@@ -2036,22 +2552,6 @@ function pickResponsesFinishReason(payload) {
2036
2552
  }
2037
2553
  return;
2038
2554
  }
2039
- async function streamViaComplete(callbacks, complete) {
2040
- callbacks.onStart?.();
2041
- const response = await complete();
2042
- if (response.text.length > 0) {
2043
- callbacks.onToken?.(response.text);
2044
- }
2045
- callbacks.onChunk?.({
2046
- textDelta: response.text,
2047
- raw: response.raw,
2048
- done: true,
2049
- usage: response.usage,
2050
- finishReason: response.finishReason
2051
- });
2052
- callbacks.onComplete?.(response);
2053
- return response;
2054
- }
2055
2555
 
2056
2556
  // src/providers/anthropic-compatible.ts
2057
2557
  var DEFAULT_ANTHROPIC_MAX_TOKENS = 1024;
@@ -2070,7 +2570,7 @@ function createAnthropicCompatibleAdapter(options) {
2070
2570
  },
2071
2571
  async stream(request, callbacks = {}) {
2072
2572
  if (hasMCPClients(request.mcpClients)) {
2073
- return streamViaComplete2(callbacks, () => this.complete(request));
2573
+ return streamWithMCPToolLoop(options, fetcher, path, request, callbacks);
2074
2574
  }
2075
2575
  const response = await fetcher(buildURL(options.baseURL, path), {
2076
2576
  method: "POST",
@@ -2163,8 +2663,6 @@ async function completePassThrough(options, fetcher, path, request) {
2163
2663
  };
2164
2664
  }
2165
2665
  async function completeWithMCPToolLoop(options, fetcher, path, request) {
2166
- const mcpToolset = await resolveMCPToolset(request.mcpClients);
2167
- const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
2168
2666
  const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
2169
2667
  let messages = [{ role: "user", content: request.prompt }];
2170
2668
  let aggregatedUsage;
@@ -2173,6 +2671,8 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2173
2671
  const toolCalls = [];
2174
2672
  const toolExecutions = [];
2175
2673
  for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2674
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
2675
+ const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
2176
2676
  const response = await fetcher(buildURL(options.baseURL, path), {
2177
2677
  method: "POST",
2178
2678
  headers: buildHeaders2(options),
@@ -2244,6 +2744,127 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2244
2744
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2245
2745
  };
2246
2746
  }
2747
+ async function streamWithMCPToolLoop(options, fetcher, path, request, callbacks) {
2748
+ const maxToolRounds = normalizeMaxToolRounds(request.maxToolRounds ?? options.defaultMaxToolRounds);
2749
+ let messages = [{ role: "user", content: request.prompt }];
2750
+ let aggregatedUsage;
2751
+ let finishReason;
2752
+ let lastPayload;
2753
+ const toolCalls = [];
2754
+ const toolExecutions = [];
2755
+ callbacks.onStart?.();
2756
+ for (let round = 1;round <= maxToolRounds + 1; round += 1) {
2757
+ const mcpToolset = await resolveMCPToolset(request.mcpClients);
2758
+ const tools = toAnthropicTools(toProviderFunctionTools(mcpToolset));
2759
+ const response = await fetcher(buildURL(options.baseURL, path), {
2760
+ method: "POST",
2761
+ headers: buildHeaders2(options),
2762
+ body: JSON.stringify(cleanUndefined({
2763
+ ...options.defaultBody,
2764
+ ...request.body,
2765
+ model: options.model,
2766
+ system: request.systemPrompt,
2767
+ messages,
2768
+ temperature: request.temperature,
2769
+ max_tokens: resolveMaxTokens(request.maxTokens, options.defaultMaxTokens),
2770
+ tools,
2771
+ tool_choice: toAnthropicToolChoice(request.toolChoice),
2772
+ stream: true
2773
+ }))
2774
+ });
2775
+ if (!response.ok) {
2776
+ const message = await response.text();
2777
+ throw new Error(`HTTP ${response.status}: ${message}`);
2778
+ }
2779
+ let roundText = "";
2780
+ let roundUsage;
2781
+ let roundFinishReason;
2782
+ const streamedToolCalls = new Map;
2783
+ await consumeSSE(response, (data) => {
2784
+ if (data === "[DONE]") {
2785
+ return;
2786
+ }
2787
+ const json = safeJSONParse(data);
2788
+ if (!isRecord2(json)) {
2789
+ return;
2790
+ }
2791
+ lastPayload = json;
2792
+ const delta = pickAnthropicDelta(json);
2793
+ const chunkUsage = pickUsage2(json);
2794
+ const chunkFinishReason = pickFinishReason2(json);
2795
+ collectAnthropicStreamToolCalls(json, streamedToolCalls);
2796
+ roundUsage = mergeUsage(roundUsage, chunkUsage);
2797
+ if (chunkFinishReason) {
2798
+ roundFinishReason = chunkFinishReason;
2799
+ }
2800
+ if (delta) {
2801
+ roundText += delta;
2802
+ callbacks.onToken?.(delta);
2803
+ }
2804
+ if (delta || chunkUsage || chunkFinishReason) {
2805
+ const chunk = {
2806
+ textDelta: delta,
2807
+ raw: json,
2808
+ usage: chunkUsage,
2809
+ finishReason: chunkFinishReason
2810
+ };
2811
+ callbacks.onChunk?.(chunk);
2812
+ }
2813
+ });
2814
+ aggregatedUsage = mergeUsage(aggregatedUsage, roundUsage);
2815
+ if (roundFinishReason) {
2816
+ finishReason = roundFinishReason;
2817
+ }
2818
+ const calledTools = buildAnthropicStreamToolCalls(streamedToolCalls);
2819
+ if (calledTools.length === 0) {
2820
+ const out2 = {
2821
+ text: roundText,
2822
+ raw: lastPayload,
2823
+ usage: aggregatedUsage,
2824
+ finishReason,
2825
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
2826
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2827
+ };
2828
+ callbacks.onComplete?.(out2);
2829
+ return out2;
2830
+ }
2831
+ if (round > maxToolRounds) {
2832
+ throw new Error(`Tool call loop exceeded maxToolRounds (${maxToolRounds}).`);
2833
+ }
2834
+ const toolResultContent = [];
2835
+ const outputs = await executeMCPToolCalls(calledTools, mcpToolset, {
2836
+ round,
2837
+ request,
2838
+ provider: "anthropic-compatible",
2839
+ model: options.model
2840
+ });
2841
+ toolCalls.push(...outputs.map((entry) => entry.call));
2842
+ toolExecutions.push(...outputs.map((entry) => entry.execution));
2843
+ for (const entry of outputs) {
2844
+ toolResultContent.push({
2845
+ type: "tool_result",
2846
+ tool_use_id: entry.call.id,
2847
+ ...entry.call.error ? { is_error: true } : {},
2848
+ content: stringifyToolOutput(entry.call.error ? { error: entry.call.error } : entry.call.output)
2849
+ });
2850
+ }
2851
+ messages = [
2852
+ ...messages,
2853
+ { role: "assistant", content: buildAnthropicAssistantToolContent(roundText, calledTools) },
2854
+ { role: "user", content: toolResultContent }
2855
+ ];
2856
+ }
2857
+ const out = {
2858
+ text: "",
2859
+ raw: lastPayload,
2860
+ usage: aggregatedUsage,
2861
+ finishReason,
2862
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
2863
+ toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2864
+ };
2865
+ callbacks.onComplete?.(out);
2866
+ return out;
2867
+ }
2247
2868
  function buildHeaders2(options) {
2248
2869
  return {
2249
2870
  "content-type": "application/json",
@@ -2306,6 +2927,83 @@ function pickAnthropicDelta(payload) {
2306
2927
  }
2307
2928
  return "";
2308
2929
  }
2930
+ function collectAnthropicStreamToolCalls(payload, state) {
2931
+ const eventType = pickString(payload.type);
2932
+ if (!eventType) {
2933
+ return;
2934
+ }
2935
+ if (eventType === "content_block_start" && isRecord2(payload.content_block)) {
2936
+ const block = payload.content_block;
2937
+ if (pickString(block.type) !== "tool_use") {
2938
+ return;
2939
+ }
2940
+ const index = pickContentBlockIndex(payload.index);
2941
+ const existing = state.get(index) ?? {
2942
+ index,
2943
+ argumentsText: ""
2944
+ };
2945
+ const id = pickString(block.id);
2946
+ if (id) {
2947
+ existing.id = id;
2948
+ }
2949
+ const name = pickString(block.name);
2950
+ if (name) {
2951
+ existing.name = name;
2952
+ }
2953
+ if ("input" in block) {
2954
+ existing.input = block.input;
2955
+ }
2956
+ state.set(index, existing);
2957
+ return;
2958
+ }
2959
+ if (eventType === "content_block_delta" && isRecord2(payload.delta)) {
2960
+ const delta = payload.delta;
2961
+ if (pickString(delta.type) !== "input_json_delta") {
2962
+ return;
2963
+ }
2964
+ const index = pickContentBlockIndex(payload.index);
2965
+ const existing = state.get(index) ?? {
2966
+ index,
2967
+ argumentsText: ""
2968
+ };
2969
+ const partial = pickString(delta.partial_json);
2970
+ if (partial) {
2971
+ existing.argumentsText += partial;
2972
+ }
2973
+ state.set(index, existing);
2974
+ }
2975
+ }
2976
+ function pickContentBlockIndex(value) {
2977
+ const numeric = toFiniteNumber(value);
2978
+ if (numeric !== undefined) {
2979
+ return Math.floor(numeric);
2980
+ }
2981
+ return 0;
2982
+ }
2983
+ function buildAnthropicStreamToolCalls(state) {
2984
+ return [...state.values()].sort((a, b) => a.index - b.index).map((entry) => ({
2985
+ id: entry.id ?? "",
2986
+ type: "function",
2987
+ name: entry.name,
2988
+ arguments: entry.argumentsText.length > 0 ? entry.argumentsText : entry.input ?? {}
2989
+ }));
2990
+ }
2991
+ function buildAnthropicAssistantToolContent(text, toolCalls) {
2992
+ const content = [];
2993
+ if (text.length > 0) {
2994
+ content.push({ type: "text", text });
2995
+ }
2996
+ for (const call of toolCalls) {
2997
+ const parsedArguments = typeof call.arguments === "string" ? parseToolArguments(call.arguments) : call.arguments;
2998
+ content.push({
2999
+ type: "tool_use",
3000
+ id: call.id,
3001
+ name: call.name,
3002
+ input: isRecord2(parsedArguments) ? parsedArguments : {}
3003
+ });
3004
+ }
3005
+ return content;
3006
+ }
2309
3007
  function pickUsage2(payload) {
2310
3008
  const fromUsage = extractUsageObject(payload.usage);
2311
3009
  if (fromUsage) {
@@ -2385,22 +3083,6 @@ function toAnthropicToolChoice(value) {
2385
3083
  }
2386
3084
  return value;
2387
3085
  }
2388
- async function streamViaComplete2(callbacks, complete) {
2389
- callbacks.onStart?.();
2390
- const response = await complete();
2391
- if (response.text.length > 0) {
2392
- callbacks.onToken?.(response.text);
2393
- }
2394
- callbacks.onChunk?.({
2395
- textDelta: response.text,
2396
- raw: response.raw,
2397
- done: true,
2398
- usage: response.usage,
2399
- finishReason: response.finishReason
2400
- });
2401
- callbacks.onComplete?.(response);
2402
- return response;
2403
- }
2404
3086
 
2405
3087
  // src/providers/registry.ts
2406
3088
  class InMemoryProviderRegistry {
@@ -2982,6 +3664,9 @@ var DEFAULT_SELF_HEAL_PROTOCOL = "extrait.self-heal.v2";
2982
3664
  var DEFAULT_SELF_HEAL_MAX_CONTEXT_CHARS = 12000;
2983
3665
  var DEFAULT_SELF_HEAL_STOP_ON_NO_PROGRESS = true;
2984
3666
  var DEFAULT_SELF_HEAL_MAX_ERRORS = 8;
3667
+ var RE_SIMPLE_IDENTIFIER2 = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
3668
+ var RE_ESCAPE_QUOTE = /"/g;
3669
+ var RE_WHITESPACE2 = /\s+/g;
2985
3670
  var DEFAULT_SELF_HEAL_MAX_DIAGNOSTICS = 8;
2986
3671
  var structuredOutdent = createOutdent({
2987
3672
  trimLeadingNewline: true,
@@ -3326,11 +4011,11 @@ function formatIssuePath(path) {
3326
4011
  out += `[${segment}]`;
3327
4012
  continue;
3328
4013
  }
3329
- if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(segment)) {
4014
+ if (RE_SIMPLE_IDENTIFIER2.test(segment)) {
3330
4015
  out += `.${segment}`;
3331
4016
  continue;
3332
4017
  }
3333
- out += `["${segment.replace(/"/g, "\\\"")}"]`;
4018
+ out += `["${segment.replace(RE_ESCAPE_QUOTE, "\\\"")}"]`;
3334
4019
  }
3335
4020
  return out;
3336
4021
  }
@@ -3373,7 +4058,7 @@ function buildSelfHealFailureFingerprint(attempt) {
3373
4058
  return [issues, errors, source].join("::");
3374
4059
  }
3375
4060
  function normalizeWhitespace(value) {
3376
- return value.replace(/\s+/g, " ").trim();
4061
+ return value.replace(RE_WHITESPACE2, " ").trim();
3377
4062
  }
3378
4063
  function normalizeStreamConfig(option) {
3379
4064
  if (typeof option === "boolean") {