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.cjs CHANGED
@@ -1597,8 +1597,14 @@ function createOpenAICompatibleAdapter(options) {
1597
1597
  async stream(request, callbacks = {}) {
1598
1598
  const usesResponses = shouldUseResponsesAPI(options, request);
1599
1599
  const usesMCP = hasMCPClients(request.mcpClients);
1600
- if (usesResponses || usesMCP) {
1601
- return streamViaComplete(callbacks, () => completeOpenAIRequest(options, fetcher, path, responsesPath, request));
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);
1602
1608
  }
1603
1609
  const response = await fetcher(buildURL(options.baseURL, path), {
1604
1610
  method: "POST",
@@ -1883,6 +1889,316 @@ async function completeWithResponsesAPIWithMCP(options, fetcher, path, request)
1883
1889
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
1884
1890
  };
1885
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
+ }
1886
2202
  function shouldUseResponsesAPI(options, request) {
1887
2203
  if (options.path?.includes("/responses")) {
1888
2204
  return true;
@@ -2039,6 +2355,190 @@ function pickAssistantDelta(payload) {
2039
2355
  }
2040
2356
  return "";
2041
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
+ }
2042
2542
  function pickResponsesText(payload) {
2043
2543
  const outputText = payload.output_text;
2044
2544
  if (typeof outputText === "string") {
@@ -2131,22 +2631,6 @@ function pickResponsesFinishReason(payload) {
2131
2631
  }
2132
2632
  return;
2133
2633
  }
2134
- async function streamViaComplete(callbacks, complete) {
2135
- callbacks.onStart?.();
2136
- const response = await complete();
2137
- if (response.text.length > 0) {
2138
- callbacks.onToken?.(response.text);
2139
- }
2140
- callbacks.onChunk?.({
2141
- textDelta: response.text,
2142
- raw: response.raw,
2143
- done: true,
2144
- usage: response.usage,
2145
- finishReason: response.finishReason
2146
- });
2147
- callbacks.onComplete?.(response);
2148
- return response;
2149
- }
2150
2634
 
2151
2635
  // src/providers/anthropic-compatible.ts
2152
2636
  var DEFAULT_ANTHROPIC_MAX_TOKENS = 1024;
@@ -2165,7 +2649,7 @@ function createAnthropicCompatibleAdapter(options) {
2165
2649
  },
2166
2650
  async stream(request, callbacks = {}) {
2167
2651
  if (hasMCPClients(request.mcpClients)) {
2168
- return streamViaComplete2(callbacks, () => this.complete(request));
2652
+ return streamWithMCPToolLoop(options, fetcher, path, request, callbacks);
2169
2653
  }
2170
2654
  const response = await fetcher(buildURL(options.baseURL, path), {
2171
2655
  method: "POST",
@@ -2339,6 +2823,127 @@ async function completeWithMCPToolLoop(options, fetcher, path, request) {
2339
2823
  toolExecutions: toolExecutions.length > 0 ? toolExecutions : undefined
2340
2824
  };
2341
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
+ }
2342
2947
  function buildHeaders2(options) {
2343
2948
  return {
2344
2949
  "content-type": "application/json",
@@ -2401,6 +3006,83 @@ function pickAnthropicDelta(payload) {
2401
3006
  }
2402
3007
  return "";
2403
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
+ }
2404
3086
  function pickUsage2(payload) {
2405
3087
  const fromUsage = extractUsageObject(payload.usage);
2406
3088
  if (fromUsage) {
@@ -2480,22 +3162,6 @@ function toAnthropicToolChoice(value) {
2480
3162
  }
2481
3163
  return value;
2482
3164
  }
2483
- async function streamViaComplete2(callbacks, complete) {
2484
- callbacks.onStart?.();
2485
- const response = await complete();
2486
- if (response.text.length > 0) {
2487
- callbacks.onToken?.(response.text);
2488
- }
2489
- callbacks.onChunk?.({
2490
- textDelta: response.text,
2491
- raw: response.raw,
2492
- done: true,
2493
- usage: response.usage,
2494
- finishReason: response.finishReason
2495
- });
2496
- callbacks.onComplete?.(response);
2497
- return response;
2498
- }
2499
3165
 
2500
3166
  // src/providers/registry.ts
2501
3167
  class InMemoryProviderRegistry {