@codeproxy/core 0.1.9 → 0.1.11

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.d.cts CHANGED
@@ -1,51 +1,3 @@
1
- type UpstreamFormat = 'anthropic' | 'openai-chat';
2
- interface CacheStats {
3
- cachedTokens: number;
4
- cacheCreationTokens: number;
5
- inputTokens: number;
6
- outputTokens: number;
7
- totalTokens: number;
8
- }
9
- interface CreateResponsesFetchOptions {
10
- /** Upstream API format. If omitted, inferred from `baseUrl`. */
11
- upstreamFormat?: UpstreamFormat;
12
- /** Upstream endpoint URL. Required. */
13
- baseUrl: string;
14
- /** Override upstream API version header (Anthropic only). */
15
- apiVersion?: string;
16
- /** Replace the caller-provided `model` field before translation. */
17
- model?: string;
18
- /** Extra headers merged into every upstream call. */
19
- defaultHeaders?: Record<string, string>;
20
- /** Underlying fetch. Defaults to `globalThis.fetch`. */
21
- fetch?: typeof fetch;
22
- /** Non-/responses traffic forward target. Defaults to `options.fetch`. */
23
- passthroughFetch?: typeof fetch;
24
- /** Drop image/file parts from user messages (e.g. DeepSeek text-only models). */
25
- dropImages?: boolean;
26
- /** Fallback thought signature for Gemini OpenAI-compatible tool histories. */
27
- fallbackThoughtSignature?: string;
28
- /** Optional callback to receive cache statistics. */
29
- onCacheStats?: (stats: CacheStats) => void;
30
- /** Override reasoning_effort sent to the upstream model (OpenAI Chat / Anthropic). */
31
- reasoning_effort?: string;
32
- /** Override thinking configuration sent to the upstream model. */
33
- thinking?: unknown;
34
- /** Timeout in milliseconds for upstream requests. Defaults to no timeout. */
35
- timeoutMs?: number;
36
- /** Fallback upstream for requests where the last user message contains images. */
37
- fallbackUpstream?: {
38
- baseUrl: string;
39
- upstreamFormat?: UpstreamFormat;
40
- model?: string;
41
- defaultHeaders?: Record<string, string>;
42
- apiVersion?: string;
43
- reasoning_effort?: string;
44
- thinking?: unknown;
45
- };
46
- }
47
- declare function createResponsesFetch(options: CreateResponsesFetchOptions): typeof fetch;
48
-
49
1
  /**
50
2
  * OpenAI Responses API type definitions (subset used by this library).
51
3
  * Only the fields relevant to request translation and event emission are typed.
@@ -240,6 +192,7 @@ interface ResponsesOutputFunctionCall {
240
192
  type: 'function_call' | 'local_shell_call' | string;
241
193
  status: 'completed' | 'in_progress';
242
194
  name?: string;
195
+ namespace?: string;
243
196
  arguments?: string;
244
197
  call_id?: string;
245
198
  thought_signature?: string;
@@ -295,6 +248,58 @@ interface ResponsesStreamEvent {
295
248
  [key: string]: unknown;
296
249
  }
297
250
 
251
+ type UpstreamFormat = 'anthropic' | 'openai-chat';
252
+ interface CacheStats {
253
+ cachedTokens: number;
254
+ cacheCreationTokens: number;
255
+ inputTokens: number;
256
+ outputTokens: number;
257
+ totalTokens: number;
258
+ }
259
+ interface CreateResponsesFetchOptions {
260
+ /** Upstream API format. If omitted, inferred from `baseUrl`. */
261
+ upstreamFormat?: UpstreamFormat;
262
+ /** Upstream endpoint URL. Required. */
263
+ baseUrl: string;
264
+ /** Override upstream API version header (Anthropic only). */
265
+ apiVersion?: string;
266
+ /** Replace the caller-provided `model` field before translation. */
267
+ model?: string;
268
+ /** Extra headers merged into every upstream call. */
269
+ defaultHeaders?: Record<string, string>;
270
+ /** Underlying fetch. Defaults to `globalThis.fetch`. */
271
+ fetch?: typeof fetch;
272
+ /** Non-/responses traffic forward target. Defaults to `options.fetch`. */
273
+ passthroughFetch?: typeof fetch;
274
+ /** Drop image/file parts from user messages (e.g. DeepSeek text-only models). */
275
+ dropImages?: boolean;
276
+ /** Drop tools from the request before translation. Return true to drop a tool.
277
+ * Use to reduce payload size when the upstream has a limit (e.g. DeepSeek ECONNRESET
278
+ * on 252 KB requests with 143 tools). Applied before namespace flattening. */
279
+ dropTools?: (tool: ResponsesTool) => boolean;
280
+ /** Fallback thought signature for Gemini OpenAI-compatible tool histories. */
281
+ fallbackThoughtSignature?: string;
282
+ /** Optional callback to receive cache statistics. */
283
+ onCacheStats?: (stats: CacheStats) => void;
284
+ /** Override reasoning_effort sent to the upstream model (OpenAI Chat / Anthropic). */
285
+ reasoning_effort?: string;
286
+ /** Override thinking configuration sent to the upstream model. */
287
+ thinking?: unknown;
288
+ /** Timeout in milliseconds for upstream requests. Defaults to no timeout. */
289
+ timeoutMs?: number;
290
+ /** Fallback upstream for requests where the last user message contains images. */
291
+ fallbackUpstream?: {
292
+ baseUrl: string;
293
+ upstreamFormat?: UpstreamFormat;
294
+ model?: string;
295
+ defaultHeaders?: Record<string, string>;
296
+ apiVersion?: string;
297
+ reasoning_effort?: string;
298
+ thinking?: unknown;
299
+ };
300
+ }
301
+ declare function createResponsesFetch(options: CreateResponsesFetchOptions): typeof fetch;
302
+
298
303
  /** Anthropic Messages API type definitions (subset). */
299
304
  interface AnthropicTextBlock {
300
305
  type: 'text';
@@ -640,6 +645,9 @@ interface TranslateResponseOptions {
640
645
  responseId?: string;
641
646
  createdAt?: number;
642
647
  model?: string;
648
+ /** Chat Completions tool list from the translated request — used to recover
649
+ * namespace when the upstream omits the "namespace." prefix in a tool call. */
650
+ requestTools?: unknown[];
643
651
  }
644
652
  /** Convert an OpenAI Chat response into a Responses-API response. */
645
653
  declare function translateResponse(body: OpenAiChatResponse, options?: TranslateResponseOptions): ResponsesResponse;
package/dist/index.d.ts CHANGED
@@ -1,51 +1,3 @@
1
- type UpstreamFormat = 'anthropic' | 'openai-chat';
2
- interface CacheStats {
3
- cachedTokens: number;
4
- cacheCreationTokens: number;
5
- inputTokens: number;
6
- outputTokens: number;
7
- totalTokens: number;
8
- }
9
- interface CreateResponsesFetchOptions {
10
- /** Upstream API format. If omitted, inferred from `baseUrl`. */
11
- upstreamFormat?: UpstreamFormat;
12
- /** Upstream endpoint URL. Required. */
13
- baseUrl: string;
14
- /** Override upstream API version header (Anthropic only). */
15
- apiVersion?: string;
16
- /** Replace the caller-provided `model` field before translation. */
17
- model?: string;
18
- /** Extra headers merged into every upstream call. */
19
- defaultHeaders?: Record<string, string>;
20
- /** Underlying fetch. Defaults to `globalThis.fetch`. */
21
- fetch?: typeof fetch;
22
- /** Non-/responses traffic forward target. Defaults to `options.fetch`. */
23
- passthroughFetch?: typeof fetch;
24
- /** Drop image/file parts from user messages (e.g. DeepSeek text-only models). */
25
- dropImages?: boolean;
26
- /** Fallback thought signature for Gemini OpenAI-compatible tool histories. */
27
- fallbackThoughtSignature?: string;
28
- /** Optional callback to receive cache statistics. */
29
- onCacheStats?: (stats: CacheStats) => void;
30
- /** Override reasoning_effort sent to the upstream model (OpenAI Chat / Anthropic). */
31
- reasoning_effort?: string;
32
- /** Override thinking configuration sent to the upstream model. */
33
- thinking?: unknown;
34
- /** Timeout in milliseconds for upstream requests. Defaults to no timeout. */
35
- timeoutMs?: number;
36
- /** Fallback upstream for requests where the last user message contains images. */
37
- fallbackUpstream?: {
38
- baseUrl: string;
39
- upstreamFormat?: UpstreamFormat;
40
- model?: string;
41
- defaultHeaders?: Record<string, string>;
42
- apiVersion?: string;
43
- reasoning_effort?: string;
44
- thinking?: unknown;
45
- };
46
- }
47
- declare function createResponsesFetch(options: CreateResponsesFetchOptions): typeof fetch;
48
-
49
1
  /**
50
2
  * OpenAI Responses API type definitions (subset used by this library).
51
3
  * Only the fields relevant to request translation and event emission are typed.
@@ -240,6 +192,7 @@ interface ResponsesOutputFunctionCall {
240
192
  type: 'function_call' | 'local_shell_call' | string;
241
193
  status: 'completed' | 'in_progress';
242
194
  name?: string;
195
+ namespace?: string;
243
196
  arguments?: string;
244
197
  call_id?: string;
245
198
  thought_signature?: string;
@@ -295,6 +248,58 @@ interface ResponsesStreamEvent {
295
248
  [key: string]: unknown;
296
249
  }
297
250
 
251
+ type UpstreamFormat = 'anthropic' | 'openai-chat';
252
+ interface CacheStats {
253
+ cachedTokens: number;
254
+ cacheCreationTokens: number;
255
+ inputTokens: number;
256
+ outputTokens: number;
257
+ totalTokens: number;
258
+ }
259
+ interface CreateResponsesFetchOptions {
260
+ /** Upstream API format. If omitted, inferred from `baseUrl`. */
261
+ upstreamFormat?: UpstreamFormat;
262
+ /** Upstream endpoint URL. Required. */
263
+ baseUrl: string;
264
+ /** Override upstream API version header (Anthropic only). */
265
+ apiVersion?: string;
266
+ /** Replace the caller-provided `model` field before translation. */
267
+ model?: string;
268
+ /** Extra headers merged into every upstream call. */
269
+ defaultHeaders?: Record<string, string>;
270
+ /** Underlying fetch. Defaults to `globalThis.fetch`. */
271
+ fetch?: typeof fetch;
272
+ /** Non-/responses traffic forward target. Defaults to `options.fetch`. */
273
+ passthroughFetch?: typeof fetch;
274
+ /** Drop image/file parts from user messages (e.g. DeepSeek text-only models). */
275
+ dropImages?: boolean;
276
+ /** Drop tools from the request before translation. Return true to drop a tool.
277
+ * Use to reduce payload size when the upstream has a limit (e.g. DeepSeek ECONNRESET
278
+ * on 252 KB requests with 143 tools). Applied before namespace flattening. */
279
+ dropTools?: (tool: ResponsesTool) => boolean;
280
+ /** Fallback thought signature for Gemini OpenAI-compatible tool histories. */
281
+ fallbackThoughtSignature?: string;
282
+ /** Optional callback to receive cache statistics. */
283
+ onCacheStats?: (stats: CacheStats) => void;
284
+ /** Override reasoning_effort sent to the upstream model (OpenAI Chat / Anthropic). */
285
+ reasoning_effort?: string;
286
+ /** Override thinking configuration sent to the upstream model. */
287
+ thinking?: unknown;
288
+ /** Timeout in milliseconds for upstream requests. Defaults to no timeout. */
289
+ timeoutMs?: number;
290
+ /** Fallback upstream for requests where the last user message contains images. */
291
+ fallbackUpstream?: {
292
+ baseUrl: string;
293
+ upstreamFormat?: UpstreamFormat;
294
+ model?: string;
295
+ defaultHeaders?: Record<string, string>;
296
+ apiVersion?: string;
297
+ reasoning_effort?: string;
298
+ thinking?: unknown;
299
+ };
300
+ }
301
+ declare function createResponsesFetch(options: CreateResponsesFetchOptions): typeof fetch;
302
+
298
303
  /** Anthropic Messages API type definitions (subset). */
299
304
  interface AnthropicTextBlock {
300
305
  type: 'text';
@@ -640,6 +645,9 @@ interface TranslateResponseOptions {
640
645
  responseId?: string;
641
646
  createdAt?: number;
642
647
  model?: string;
648
+ /** Chat Completions tool list from the translated request — used to recover
649
+ * namespace when the upstream omits the "namespace." prefix in a tool call. */
650
+ requestTools?: unknown[];
643
651
  }
644
652
  /** Convert an OpenAI Chat response into a Responses-API response. */
645
653
  declare function translateResponse(body: OpenAiChatResponse, options?: TranslateResponseOptions): ResponsesResponse;
package/dist/index.js CHANGED
@@ -1412,6 +1412,32 @@ function mapTools2(tools) {
1412
1412
  });
1413
1413
  continue;
1414
1414
  }
1415
+ if (tt === "namespace") {
1416
+ const ns = tool.name;
1417
+ const nested = tool.tools;
1418
+ if (ns && Array.isArray(nested)) {
1419
+ for (const sub of nested) {
1420
+ if (!sub || typeof sub !== "object" || sub.type !== "function") {
1421
+ continue;
1422
+ }
1423
+ const subName = sub.name;
1424
+ if (!subName) {
1425
+ continue;
1426
+ }
1427
+ const params = sub.parameters ?? { type: "object" };
1428
+ out.push({
1429
+ type: "function",
1430
+ function: {
1431
+ name: `${ns}.${subName}`,
1432
+ description: sub.description ?? "",
1433
+ // eslint-disable-next-line no-restricted-syntax -- schema is Record<string,unknown> by OpenAPI convention
1434
+ parameters: params
1435
+ }
1436
+ });
1437
+ }
1438
+ }
1439
+ continue;
1440
+ }
1415
1441
  }
1416
1442
  return out;
1417
1443
  }
@@ -1490,6 +1516,36 @@ function repairToolMessageOrder(messages) {
1490
1516
 
1491
1517
  // src/translate/openai/translateResponse.ts
1492
1518
  var SHELL_TOOL_NAMES3 = /* @__PURE__ */ new Set(["shell", "container.exec", "shell_command"]);
1519
+ function buildShortNameToNamespace(tools) {
1520
+ const map = /* @__PURE__ */ new Map();
1521
+ for (const tool of tools) {
1522
+ if (!tool || typeof tool !== "object") {
1523
+ continue;
1524
+ }
1525
+ const entry = tool;
1526
+ const fn = entry.function;
1527
+ const flatName = typeof fn?.name === "string" ? fn.name : typeof entry.name === "string" ? entry.name : "";
1528
+ const dotIdx = flatName.indexOf(".");
1529
+ if (dotIdx !== -1) {
1530
+ map.set(flatName.slice(dotIdx + 1), flatName.slice(0, dotIdx));
1531
+ continue;
1532
+ }
1533
+ if (entry.type === "namespace" && typeof entry.name === "string" && Array.isArray(entry.tools)) {
1534
+ const ns = entry.name;
1535
+ for (const sub of entry.tools) {
1536
+ if (!sub || typeof sub !== "object") {
1537
+ continue;
1538
+ }
1539
+ const subEntry = sub;
1540
+ const subName = typeof subEntry.name === "string" ? subEntry.name : "";
1541
+ if (subName) {
1542
+ map.set(subName, ns);
1543
+ }
1544
+ }
1545
+ }
1546
+ }
1547
+ return map;
1548
+ }
1493
1549
  function translateResponse2(body, options = {}) {
1494
1550
  const createdAt = options.createdAt ?? body.created ?? Math.floor(Date.now() / 1e3);
1495
1551
  const id = options.responseId ?? body.id ?? makeId("resp");
@@ -1497,9 +1553,10 @@ function translateResponse2(body, options = {}) {
1497
1553
  const choice = body.choices?.[0];
1498
1554
  const message = choice?.message;
1499
1555
  const output = [];
1556
+ const shortNameToNs = buildShortNameToNamespace(options.requestTools ?? []);
1500
1557
  if (message?.tool_calls?.length) {
1501
1558
  for (const tc of message.tool_calls) {
1502
- const item = mapToolCallToOutput(tc);
1559
+ const item = mapToolCallToOutput(tc, shortNameToNs);
1503
1560
  if (item) {
1504
1561
  output.push(item);
1505
1562
  }
@@ -1536,7 +1593,7 @@ function translateResponse2(body, options = {}) {
1536
1593
  }
1537
1594
  };
1538
1595
  }
1539
- function mapToolCallToOutput(tc) {
1596
+ function mapToolCallToOutput(tc, shortNameToNs) {
1540
1597
  const name = tc.function?.name;
1541
1598
  if (!name) {
1542
1599
  return void 0;
@@ -1546,11 +1603,23 @@ function mapToolCallToOutput(tc) {
1546
1603
  if (typeof args !== "string") {
1547
1604
  args = jsonStringifySafe(args ?? {});
1548
1605
  }
1606
+ let resolvedName = name;
1607
+ let resolvedNamespace;
1608
+ if (!SHELL_TOOL_NAMES3.has(name)) {
1609
+ const dotIdx = name.indexOf(".");
1610
+ if (dotIdx !== -1) {
1611
+ resolvedNamespace = name.slice(0, dotIdx);
1612
+ resolvedName = name.slice(dotIdx + 1);
1613
+ } else {
1614
+ resolvedNamespace = shortNameToNs?.get(name);
1615
+ }
1616
+ }
1549
1617
  const item = {
1550
1618
  id: callId,
1551
1619
  type: "function_call",
1552
1620
  status: "completed",
1553
- name,
1621
+ name: resolvedName,
1622
+ ...resolvedNamespace ? { namespace: resolvedNamespace } : {},
1554
1623
  arguments: args,
1555
1624
  call_id: callId
1556
1625
  };
@@ -1558,7 +1627,7 @@ function mapToolCallToOutput(tc) {
1558
1627
  if (thoughtSignature) {
1559
1628
  item.thought_signature = thoughtSignature;
1560
1629
  }
1561
- if (SHELL_TOOL_NAMES3.has(name)) {
1630
+ if (SHELL_TOOL_NAMES3.has(resolvedName)) {
1562
1631
  item.type = "local_shell_call";
1563
1632
  const parsed = safeJsonParse(args);
1564
1633
  item.action = { type: "exec", command: parsed?.command ?? [] };
@@ -1572,6 +1641,36 @@ function getThoughtSignature(tc) {
1572
1641
 
1573
1642
  // src/translate/openai/translateStream.ts
1574
1643
  var SHELL_TOOL_NAMES4 = /* @__PURE__ */ new Set(["shell", "container.exec", "shell_command"]);
1644
+ function buildShortNameToNamespace2(tools) {
1645
+ const map = /* @__PURE__ */ new Map();
1646
+ for (const tool of tools) {
1647
+ if (!tool || typeof tool !== "object") {
1648
+ continue;
1649
+ }
1650
+ const entry = tool;
1651
+ const fn = entry.function;
1652
+ const flatName = typeof fn?.name === "string" ? fn.name : typeof entry.name === "string" ? entry.name : "";
1653
+ const dotIdx = flatName.indexOf(".");
1654
+ if (dotIdx !== -1) {
1655
+ map.set(flatName.slice(dotIdx + 1), flatName.slice(0, dotIdx));
1656
+ continue;
1657
+ }
1658
+ if (entry.type === "namespace" && typeof entry.name === "string" && Array.isArray(entry.tools)) {
1659
+ const ns = entry.name;
1660
+ for (const sub of entry.tools) {
1661
+ if (!sub || typeof sub !== "object") {
1662
+ continue;
1663
+ }
1664
+ const subEntry = sub;
1665
+ const subName = typeof subEntry.name === "string" ? subEntry.name : "";
1666
+ if (subName) {
1667
+ map.set(subName, ns);
1668
+ }
1669
+ }
1670
+ }
1671
+ }
1672
+ return map;
1673
+ }
1575
1674
  async function* translateStream2(stream, options = {}) {
1576
1675
  const translator = new StreamTranslator2(options);
1577
1676
  yield translator.createInitialEvent();
@@ -1611,6 +1710,10 @@ var StreamTranslator2 = class {
1611
1710
  __publicField(this, "textItemIndex", -1);
1612
1711
  __publicField(this, "textBuffer", "");
1613
1712
  __publicField(this, "toolCalls", /* @__PURE__ */ new Map());
1713
+ // shortName → namespace reverse map built from flattened namespace tools in
1714
+ // the translated request. Used to restore the namespace when an upstream
1715
+ // (e.g. DeepSeek) omits the "namespace." prefix in its tool-call response.
1716
+ __publicField(this, "shortNameToNamespace");
1614
1717
  __publicField(this, "inputTokens", 0);
1615
1718
  __publicField(this, "outputTokens", 0);
1616
1719
  __publicField(this, "cachedTokens", 0);
@@ -1618,6 +1721,7 @@ var StreamTranslator2 = class {
1618
1721
  this.responseId = options.responseId ?? makeId("resp");
1619
1722
  this.createdAt = options.createdAt ?? Math.floor(Date.now() / 1e3);
1620
1723
  this.metadata = options.requestMetadata ?? {};
1724
+ this.shortNameToNamespace = buildShortNameToNamespace2(this.metadata.tools ?? []);
1621
1725
  }
1622
1726
  createInitialEvent() {
1623
1727
  const toolsArr = this.metadata.tools ?? [];
@@ -1739,6 +1843,18 @@ var StreamTranslator2 = class {
1739
1843
  for (const state of this.toolCalls.values()) {
1740
1844
  const item = state.item;
1741
1845
  item.status = "completed";
1846
+ if (item.name && !SHELL_TOOL_NAMES4.has(item.name)) {
1847
+ const dotIdx = item.name.indexOf(".");
1848
+ if (dotIdx !== -1) {
1849
+ item.namespace = item.name.slice(0, dotIdx);
1850
+ item.name = item.name.slice(dotIdx + 1);
1851
+ } else {
1852
+ const ns = this.shortNameToNamespace.get(item.name);
1853
+ if (ns) {
1854
+ item.namespace = ns;
1855
+ }
1856
+ }
1857
+ }
1742
1858
  if (item.name && SHELL_TOOL_NAMES4.has(item.name)) {
1743
1859
  item.type = "local_shell_call";
1744
1860
  const parsed = safeJsonParse(item.arguments ?? "");
@@ -1800,6 +1916,109 @@ function getThoughtSignature2(tc) {
1800
1916
  return typeof sig === "string" && sig ? sig : void 0;
1801
1917
  }
1802
1918
 
1919
+ // src/tool-name-sanitizer.ts
1920
+ function sanitizeUpstreamToolNames(body) {
1921
+ const map = /* @__PURE__ */ new Map();
1922
+ const tools = body.tools;
1923
+ if (!Array.isArray(tools)) {
1924
+ return map;
1925
+ }
1926
+ for (const tool of tools) {
1927
+ if (!tool || typeof tool !== "object") {
1928
+ continue;
1929
+ }
1930
+ const toolRecord = tool;
1931
+ const fn = toolRecord.function;
1932
+ if (!fn || typeof fn !== "object") {
1933
+ continue;
1934
+ }
1935
+ const fnRecord = fn;
1936
+ const original = fnRecord.name;
1937
+ if (typeof original !== "string") {
1938
+ continue;
1939
+ }
1940
+ const sanitized = original.replace(/[^a-zA-Z0-9_-]/g, "_");
1941
+ if (sanitized !== original) {
1942
+ map.set(sanitized, original);
1943
+ fnRecord.name = sanitized;
1944
+ }
1945
+ }
1946
+ return map;
1947
+ }
1948
+ function restoreToolNamesInChatResponse(body, map) {
1949
+ if (map.size === 0) {
1950
+ return body;
1951
+ }
1952
+ const choices = body.choices;
1953
+ if (!Array.isArray(choices)) {
1954
+ return body;
1955
+ }
1956
+ for (const choice of choices) {
1957
+ if (!choice || typeof choice !== "object") {
1958
+ continue;
1959
+ }
1960
+ const choiceRecord = choice;
1961
+ const message = choiceRecord.message;
1962
+ if (!message || typeof message !== "object") {
1963
+ continue;
1964
+ }
1965
+ const msg = message;
1966
+ const toolCalls = msg.tool_calls;
1967
+ if (!Array.isArray(toolCalls)) {
1968
+ continue;
1969
+ }
1970
+ for (const tc of toolCalls) {
1971
+ if (!tc || typeof tc !== "object") {
1972
+ continue;
1973
+ }
1974
+ const callRecord = tc;
1975
+ const fn = callRecord.function;
1976
+ if (!fn || typeof fn !== "object") {
1977
+ continue;
1978
+ }
1979
+ const fnRecord = fn;
1980
+ const name = fnRecord.name;
1981
+ if (typeof name === "string" && map.has(name)) {
1982
+ fnRecord.name = map.get(name);
1983
+ }
1984
+ }
1985
+ }
1986
+ return body;
1987
+ }
1988
+ function createToolNameRestoreStream(body, map) {
1989
+ if (map.size === 0) {
1990
+ return body;
1991
+ }
1992
+ const entries = Array.from(map.entries());
1993
+ const decoder = new TextDecoder();
1994
+ const encoder = new TextEncoder();
1995
+ const reader = body.getReader();
1996
+ return new ReadableStream({
1997
+ async pull(controller) {
1998
+ try {
1999
+ const { value, done } = await reader.read();
2000
+ if (done) {
2001
+ controller.close();
2002
+ return;
2003
+ }
2004
+ let text = decoder.decode(value, { stream: true });
2005
+ for (const [sanitized, original] of entries) {
2006
+ const escapedSanitized = sanitized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2007
+ const re = new RegExp(`("name"\\s*:\\s*")${escapedSanitized}(")`, "g");
2008
+ text = text.replace(re, `$1${original}$2`);
2009
+ }
2010
+ controller.enqueue(encoder.encode(text));
2011
+ } catch (err) {
2012
+ controller.error(err);
2013
+ }
2014
+ },
2015
+ cancel() {
2016
+ reader.cancel().catch(() => {
2017
+ });
2018
+ }
2019
+ });
2020
+ }
2021
+
1803
2022
  // src/fetch.ts
1804
2023
  function createResponsesFetch(options) {
1805
2024
  if (!options.baseUrl) {
@@ -1968,6 +2187,10 @@ async function handleResponses(request, format, options, baseFetch, incomingHead
1968
2187
  const fbModel = fb.model ? `, model: ${fb.model}` : "";
1969
2188
  console.warn(`[fallback] last user message has image, routing to ${fb.baseUrl}${fbModel}`);
1970
2189
  }
2190
+ if (options.dropTools && request.tools) {
2191
+ const { dropTools } = options;
2192
+ request = { ...request, tools: request.tools.filter((tool) => !dropTools(tool)) };
2193
+ }
1971
2194
  const streaming = request.stream ?? false;
1972
2195
  const resolvedUrl = normalizeBaseUrl(options.baseUrl, format);
1973
2196
  const { upstreamBody, requestMetadata } = buildUpstreamBody(
@@ -1981,6 +2204,10 @@ async function handleResponses(request, format, options, baseFetch, incomingHead
1981
2204
  options.fallbackThoughtSignature
1982
2205
  );
1983
2206
  const upstreamHeaders = buildUpstreamHeaders(format, options, incomingHeaders);
2207
+ const toolNameMap = format === "openai-chat" ? (
2208
+ // eslint-disable-next-line no-restricted-syntax -- upstreamBody is unknown; cast to Record for tool mutation
2209
+ sanitizeUpstreamToolNames(upstreamBody)
2210
+ ) : /* @__PURE__ */ new Map();
1984
2211
  const upstream = await baseFetch(resolvedUrl, {
1985
2212
  method: "POST",
1986
2213
  headers: upstreamHeaders,
@@ -1994,13 +2221,23 @@ async function handleResponses(request, format, options, baseFetch, incomingHead
1994
2221
  });
1995
2222
  }
1996
2223
  if (!streaming) {
1997
- const body = await upstream.json();
2224
+ const rawBody = await upstream.json();
2225
+ const restoredBody = restoreToolNamesInChatResponse(
2226
+ // eslint-disable-next-line no-restricted-syntax -- upstream json() returns unknown; cast to Record for tool name restoration
2227
+ rawBody,
2228
+ toolNameMap
2229
+ );
1998
2230
  const translated = format === "anthropic" ? (
1999
2231
  // eslint-disable-next-line no-restricted-syntax -- union type narrowing requires type assertion
2000
- translateResponse(body, { model: request.model })
2232
+ translateResponse(restoredBody, {
2233
+ model: request.model
2234
+ })
2001
2235
  ) : (
2002
2236
  // eslint-disable-next-line no-restricted-syntax -- union type narrowing requires type assertion
2003
- translateResponse2(body, { model: request.model })
2237
+ translateResponse2(restoredBody, {
2238
+ model: request.model,
2239
+ requestTools: request.tools ?? []
2240
+ })
2004
2241
  );
2005
2242
  options.onCacheStats?.(extractCacheStatsFromResponse(translated));
2006
2243
  return new Response(JSON.stringify(translated), {
@@ -2011,7 +2248,8 @@ async function handleResponses(request, format, options, baseFetch, incomingHead
2011
2248
  if (!upstream.body) {
2012
2249
  return jsonErrorResponse(502, "Upstream streaming response has no body");
2013
2250
  }
2014
- const events = format === "anthropic" ? translateStream(upstream.body, { model: request.model, requestMetadata }) : translateStream2(upstream.body, { model: request.model, requestMetadata });
2251
+ const upstreamBodyStream = createToolNameRestoreStream(upstream.body, toolNameMap);
2252
+ const events = format === "anthropic" ? translateStream(upstreamBodyStream, { model: request.model, requestMetadata }) : translateStream2(upstreamBodyStream, { model: request.model, requestMetadata });
2015
2253
  return new Response(
2016
2254
  responsesEventsToSseStream(collectCacheStatsFromStream(events, options.onCacheStats)),
2017
2255
  {