extrait 0.2.0 → 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 +701 -35
- package/dist/index.js +701 -35
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1518,8 +1518,14 @@ function createOpenAICompatibleAdapter(options) {
|
|
|
1518
1518
|
async stream(request, callbacks = {}) {
|
|
1519
1519
|
const usesResponses = shouldUseResponsesAPI(options, request);
|
|
1520
1520
|
const usesMCP = hasMCPClients(request.mcpClients);
|
|
1521
|
-
if (usesResponses
|
|
1522
|
-
|
|
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);
|
|
1523
1529
|
}
|
|
1524
1530
|
const response = await fetcher(buildURL(options.baseURL, path), {
|
|
1525
1531
|
method: "POST",
|
|
@@ -1804,6 +1810,316 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
|
|
|
1804
1810
|
toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
|
|
1805
1811
|
};
|
|
1806
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
|
+
}
|
|
1807
2123
|
function shouldUseResponsesAPI(options, request) {
|
|
1808
2124
|
if (options.path?.includes("/responses")) {
|
|
1809
2125
|
return true;
|
|
@@ -1960,6 +2276,190 @@ function pickAssistantDelta(payload) {
|
|
|
1960
2276
|
}
|
|
1961
2277
|
return "";
|
|
1962
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
|
+
}
|
|
1963
2463
|
function pickResponsesText(payload) {
|
|
1964
2464
|
const outputText = payload.output_text;
|
|
1965
2465
|
if (typeof outputText === "string") {
|
|
@@ -2052,22 +2552,6 @@ function pickResponsesFinishReason(payload) {
|
|
|
2052
2552
|
}
|
|
2053
2553
|
return;
|
|
2054
2554
|
}
|
|
2055
|
-
async function streamViaComplete(callbacks, complete) {
|
|
2056
|
-
callbacks.onStart?.();
|
|
2057
|
-
const response = await complete();
|
|
2058
|
-
if (response.text.length > 0) {
|
|
2059
|
-
callbacks.onToken?.(response.text);
|
|
2060
|
-
}
|
|
2061
|
-
callbacks.onChunk?.({
|
|
2062
|
-
textDelta: response.text,
|
|
2063
|
-
raw: response.raw,
|
|
2064
|
-
done: true,
|
|
2065
|
-
usage: response.usage,
|
|
2066
|
-
finishReason: response.finishReason
|
|
2067
|
-
});
|
|
2068
|
-
callbacks.onComplete?.(response);
|
|
2069
|
-
return response;
|
|
2070
|
-
}
|
|
2071
2555
|
|
|
2072
2556
|
// src/providers/anthropic-compatible.ts
|
|
2073
2557
|
var DEFAULT_ANTHROPIC_MAX_TOKENS = 1024;
|
|
@@ -2086,7 +2570,7 @@ function createAnthropicCompatibleAdapter(options) {
|
|
|
2086
2570
|
},
|
|
2087
2571
|
async stream(request, callbacks = {}) {
|
|
2088
2572
|
if (hasMCPClients(request.mcpClients)) {
|
|
2089
|
-
return
|
|
2573
|
+
return streamWithMCPToolLoop(options, fetcher, path, request, callbacks);
|
|
2090
2574
|
}
|
|
2091
2575
|
const response = await fetcher(buildURL(options.baseURL, path), {
|
|
2092
2576
|
method: "POST",
|
|
@@ -2260,6 +2744,127 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
|
|
|
2260
2744
|
toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
|
|
2261
2745
|
};
|
|
2262
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
|
+
}
|
|
2263
2868
|
function buildHeaders2(options) {
|
|
2264
2869
|
return {
|
|
2265
2870
|
"content-type": "application/json",
|
|
@@ -2322,6 +2927,83 @@ function pickAnthropicDelta(payload) {
|
|
|
2322
2927
|
}
|
|
2323
2928
|
return "";
|
|
2324
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
|
+
}
|
|
2325
3007
|
function pickUsage2(payload) {
|
|
2326
3008
|
const fromUsage = extractUsageObject(payload.usage);
|
|
2327
3009
|
if (fromUsage) {
|
|
@@ -2401,22 +3083,6 @@ function toAnthropicToolChoice(value) {
|
|
|
2401
3083
|
}
|
|
2402
3084
|
return value;
|
|
2403
3085
|
}
|
|
2404
|
-
async function streamViaComplete2(callbacks, complete) {
|
|
2405
|
-
callbacks.onStart?.();
|
|
2406
|
-
const response = await complete();
|
|
2407
|
-
if (response.text.length > 0) {
|
|
2408
|
-
callbacks.onToken?.(response.text);
|
|
2409
|
-
}
|
|
2410
|
-
callbacks.onChunk?.({
|
|
2411
|
-
textDelta: response.text,
|
|
2412
|
-
raw: response.raw,
|
|
2413
|
-
done: true,
|
|
2414
|
-
usage: response.usage,
|
|
2415
|
-
finishReason: response.finishReason
|
|
2416
|
-
});
|
|
2417
|
-
callbacks.onComplete?.(response);
|
|
2418
|
-
return response;
|
|
2419
|
-
}
|
|
2420
3086
|
|
|
2421
3087
|
// src/providers/registry.ts
|
|
2422
3088
|
class InMemoryProviderRegistry {
|