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/README.md +3 -1
- package/dist/index.cjs +741 -56
- package/dist/index.js +741 -56
- package/dist/providers/mcp-runtime.d.ts +1 -0
- package/package.json +1 -1
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 (
|
|
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 +=
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1416
|
-
const trimmed = sanitized.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 (
|
|
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
|
|
1506
|
-
|
|
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
|
|
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 (
|
|
4014
|
+
if (RE_SIMPLE_IDENTIFIER2.test(segment)) {
|
|
3330
4015
|
out += `.${segment}`;
|
|
3331
4016
|
continue;
|
|
3332
4017
|
}
|
|
3333
|
-
out += `["${segment.replace(
|
|
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(
|
|
4061
|
+
return value.replace(RE_WHITESPACE2, " ").trim();
|
|
3377
4062
|
}
|
|
3378
4063
|
function normalizeStreamConfig(option) {
|
|
3379
4064
|
if (typeof option === "boolean") {
|