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/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 || usesMCP) {
1522
- return streamViaComplete(callbacks, () => completeOpenAIRequest(options, fetcher, path, responsesPath, request));
1521
+ if (usesResponses) {
1522
+ if (usesMCP) {
1523
+ return streamWithResponsesAPIWithMCP(options, fetcher, responsesPath, request, callbacks);
1524
+ }
1525
+ return streamWithResponsesAPIPassThrough(options, fetcher, responsesPath, request, callbacks);
1526
+ }
1527
+ if (usesMCP) {
1528
+ return streamWithChatCompletionsWithMCP(options, fetcher, path, request, callbacks);
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 streamViaComplete2(callbacks, () => this.complete(request));
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 {