@codeproxy/core 0.1.9 → 0.1.12
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 +246 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -48
- package/dist/index.d.ts +56 -48
- package/dist/index.js +246 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
{
|