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.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 (
|
|
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 +=
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1495
|
-
const trimmed = sanitized.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 (
|
|
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
|
|
1585
|
-
|
|
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
|
|
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 (
|
|
4093
|
+
if (RE_SIMPLE_IDENTIFIER2.test(segment)) {
|
|
3409
4094
|
out += `.${segment}`;
|
|
3410
4095
|
continue;
|
|
3411
4096
|
}
|
|
3412
|
-
out += `["${segment.replace(
|
|
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(
|
|
4140
|
+
return value.replace(RE_WHITESPACE2, " ").trim();
|
|
3456
4141
|
}
|
|
3457
4142
|
function normalizeStreamConfig(option) {
|
|
3458
4143
|
if (typeof option === "boolean") {
|