@codeproxy/core 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/README.zh-CN.md +3 -0
- package/dist/index.cjs +128 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +128 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,8 +62,11 @@ Creates a `fetch` wrapper that translates Responses API traffic to the configure
|
|
|
62
62
|
| `dropImages` | `boolean` | Strip image parts from messages (text-only models) |
|
|
63
63
|
| `timeoutMs` | `number` | Upstream request timeout |
|
|
64
64
|
| `onCacheStats` | `(stats) => void` | Receive cache usage stats |
|
|
65
|
+
| `fallbackThoughtSignature` | `string` | Fallback Gemini thought signature for OpenAI-compatible tool call histories |
|
|
65
66
|
| `fallbackUpstream` | `object` | When `dropImages: true` and the request contains images, automatically route to this upstream instead (e.g. a vision-capable model) |
|
|
66
67
|
|
|
68
|
+
Gemini OpenAI-compatible tool calls preserve `thought_signature` through Responses function-call items. When migrating old histories that do not include a returned signature, pass `fallbackThoughtSignature` so the next upstream tool-call request can still include `extra_content.google.thought_signature`.
|
|
69
|
+
|
|
67
70
|
### Translators
|
|
68
71
|
|
|
69
72
|
Low-level translators are also available by namespace:
|
package/README.zh-CN.md
CHANGED
|
@@ -62,8 +62,11 @@ npm install @codeproxy/core
|
|
|
62
62
|
| `dropImages` | `boolean` | 从消息中移除图片部分(纯文本模型) |
|
|
63
63
|
| `timeoutMs` | `number` | 上游请求超时 |
|
|
64
64
|
| `onCacheStats` | `(stats) => void` | 接收缓存使用统计 |
|
|
65
|
+
| `fallbackThoughtSignature` | `string` | Gemini OpenAI 兼容工具调用历史的兜底 thought signature |
|
|
65
66
|
| `fallbackUpstream` | `object` | 当 `dropImages: true` 且请求包含图片时,自动切换到该上游(如支持视觉的模型) |
|
|
66
67
|
|
|
68
|
+
Gemini OpenAI 兼容工具调用会在 Responses function-call item 中保留 `thought_signature`。迁移旧历史时,如果历史里没有上游返回的 signature,可以传入 `fallbackThoughtSignature`,让下一次上游工具调用请求继续带上 `extra_content.google.thought_signature`。
|
|
69
|
+
|
|
67
70
|
### 转换器
|
|
68
71
|
|
|
69
72
|
底层转换器也按命名空间导出:
|
package/dist/index.cjs
CHANGED
|
@@ -527,9 +527,14 @@ function ensureEndsWithUser(messages) {
|
|
|
527
527
|
return [...messages, { role: "user", content: [{ type: "text", text: "Continue." }] }];
|
|
528
528
|
}
|
|
529
529
|
function markBlocksForCache(blocks) {
|
|
530
|
+
let count = 0;
|
|
530
531
|
for (const block of blocks) {
|
|
531
532
|
if (!block.cache_control) {
|
|
532
533
|
block.cache_control = { type: "ephemeral" };
|
|
534
|
+
count++;
|
|
535
|
+
if (count >= 3) {
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
533
538
|
}
|
|
534
539
|
}
|
|
535
540
|
return blocks;
|
|
@@ -838,6 +843,8 @@ var StreamTranslator = class {
|
|
|
838
843
|
*finalize() {
|
|
839
844
|
const items = [];
|
|
840
845
|
if (this.textItem) {
|
|
846
|
+
this.textItem.status = "completed";
|
|
847
|
+
this.textItem.content[0].text = this.textBuffer;
|
|
841
848
|
items.push({ index: this.textItemIndex, item: this.textItem });
|
|
842
849
|
}
|
|
843
850
|
for (const block of this.blocks.values()) {
|
|
@@ -949,15 +956,22 @@ var StreamTranslator = class {
|
|
|
949
956
|
if (btype === "tool_use") {
|
|
950
957
|
const outputIndex = this.outputCounter++;
|
|
951
958
|
const callId = block.id ?? makeId("call");
|
|
959
|
+
const initialInput = typeof block.input === "object" && block.input !== null ? jsonStringifySafe(block.input) : "";
|
|
960
|
+
const hasInitialInput = initialInput !== "" && initialInput !== "{}";
|
|
952
961
|
const item = {
|
|
953
962
|
id: callId,
|
|
954
963
|
type: "function_call",
|
|
955
964
|
status: "in_progress",
|
|
956
965
|
name: block.name ?? "",
|
|
957
|
-
arguments: "",
|
|
966
|
+
arguments: hasInitialInput ? initialInput : "",
|
|
958
967
|
call_id: callId
|
|
959
968
|
};
|
|
960
|
-
this.blocks.set(index, {
|
|
969
|
+
this.blocks.set(index, {
|
|
970
|
+
type: "tool_use",
|
|
971
|
+
outputIndex,
|
|
972
|
+
item,
|
|
973
|
+
buffer: hasInitialInput ? initialInput : ""
|
|
974
|
+
});
|
|
961
975
|
yield this.makeEvent("response.output_item.added", {
|
|
962
976
|
response_id: this.responseId,
|
|
963
977
|
output_index: outputIndex,
|
|
@@ -1059,9 +1073,66 @@ __export(openai_exports, {
|
|
|
1059
1073
|
translateStream: () => translateStream2
|
|
1060
1074
|
});
|
|
1061
1075
|
|
|
1076
|
+
// src/translate/openai/toolSchema.ts
|
|
1077
|
+
function sanitizeJsonSchema(schema, depth = 0) {
|
|
1078
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema) || depth > 20) {
|
|
1079
|
+
return schema;
|
|
1080
|
+
}
|
|
1081
|
+
const src = schema;
|
|
1082
|
+
if ("$ref" in src) {
|
|
1083
|
+
return src.description ? { description: src.description } : {};
|
|
1084
|
+
}
|
|
1085
|
+
const out = {};
|
|
1086
|
+
for (const [key, val] of Object.entries(src)) {
|
|
1087
|
+
if (isDroppedSchemaKeyword(key)) {
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
if (key === "properties" && val && typeof val === "object" && !Array.isArray(val)) {
|
|
1091
|
+
const props = {};
|
|
1092
|
+
for (const [propName, propSchema] of Object.entries(val)) {
|
|
1093
|
+
props[propName] = sanitizeJsonSchema(propSchema, depth + 1);
|
|
1094
|
+
}
|
|
1095
|
+
out[key] = props;
|
|
1096
|
+
} else if (key === "additionalProperties") {
|
|
1097
|
+
if (typeof val !== "boolean") {
|
|
1098
|
+
out[key] = sanitizeJsonSchema(val, depth + 1);
|
|
1099
|
+
}
|
|
1100
|
+
} else if (key === "items") {
|
|
1101
|
+
out[key] = sanitizeJsonSchema(val, depth + 1);
|
|
1102
|
+
} else if (isSchemaCompositionKeyword(key) && Array.isArray(val)) {
|
|
1103
|
+
out[key] = val.map((schemaItem) => sanitizeJsonSchema(schemaItem, depth + 1));
|
|
1104
|
+
} else {
|
|
1105
|
+
out[key] = val;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return out;
|
|
1109
|
+
}
|
|
1110
|
+
function getValidFunctionNames(tools) {
|
|
1111
|
+
const names = /* @__PURE__ */ new Set();
|
|
1112
|
+
for (const tool of tools) {
|
|
1113
|
+
const maybeTool = tool;
|
|
1114
|
+
if (maybeTool.type === "function" && typeof maybeTool.function?.name === "string") {
|
|
1115
|
+
names.add(maybeTool.function.name);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return names.size ? names : void 0;
|
|
1119
|
+
}
|
|
1120
|
+
function isDroppedSchemaKeyword(key) {
|
|
1121
|
+
return key === "$schema" || key === "$defs" || key === "definitions" || key === "$id" || key === "$anchor" || key === "$comment";
|
|
1122
|
+
}
|
|
1123
|
+
function isSchemaCompositionKeyword(key) {
|
|
1124
|
+
return key === "anyOf" || key === "oneOf" || key === "allOf" || key === "not";
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1062
1127
|
// src/translate/openai/translateRequest.ts
|
|
1063
1128
|
function translateRequest2(data, options = {}) {
|
|
1064
1129
|
const messages = [];
|
|
1130
|
+
const tools = mapTools2(data.tools ?? []);
|
|
1131
|
+
const validFunctionNames = getValidFunctionNames(tools);
|
|
1132
|
+
const context = {
|
|
1133
|
+
validFunctionNames,
|
|
1134
|
+
ignoredToolCallIds: /* @__PURE__ */ new Set()
|
|
1135
|
+
};
|
|
1065
1136
|
const systemContent = buildSystemContent(data.instructions);
|
|
1066
1137
|
if (systemContent) {
|
|
1067
1138
|
messages.push({ role: "system", content: systemContent });
|
|
@@ -1076,7 +1147,7 @@ function translateRequest2(data, options = {}) {
|
|
|
1076
1147
|
continue;
|
|
1077
1148
|
}
|
|
1078
1149
|
const rawItem = raw;
|
|
1079
|
-
processInputItem(rawItem, messages, options
|
|
1150
|
+
processInputItem(rawItem, messages, options, context);
|
|
1080
1151
|
}
|
|
1081
1152
|
const request = {
|
|
1082
1153
|
model: data.model,
|
|
@@ -1097,7 +1168,6 @@ function translateRequest2(data, options = {}) {
|
|
|
1097
1168
|
if (typeof maxTokens === "number") {
|
|
1098
1169
|
request.max_tokens = maxTokens;
|
|
1099
1170
|
}
|
|
1100
|
-
const tools = mapTools2(data.tools ?? []);
|
|
1101
1171
|
if (tools.length) {
|
|
1102
1172
|
request.tools = tools;
|
|
1103
1173
|
const toolChoice = mapToolChoice2(data.tool_choice);
|
|
@@ -1136,7 +1206,7 @@ function buildSystemContent(instructions) {
|
|
|
1136
1206
|
}
|
|
1137
1207
|
return out;
|
|
1138
1208
|
}
|
|
1139
|
-
function processInputItem(item, messages,
|
|
1209
|
+
function processInputItem(item, messages, options, context) {
|
|
1140
1210
|
const itemType = String(item.type) || "message";
|
|
1141
1211
|
const getLastAssistant = () => {
|
|
1142
1212
|
const last = messages[messages.length - 1];
|
|
@@ -1198,7 +1268,7 @@ function processInputItem(item, messages, dropImages) {
|
|
|
1198
1268
|
} else if (contentPart.type === "reasoning_text") {
|
|
1199
1269
|
reasoningContent += String(contentPart.text ?? "");
|
|
1200
1270
|
} else if (contentPart.type === "input_image" || contentPart.type === "image" || contentPart.type === "image_url") {
|
|
1201
|
-
if (dropImages) {
|
|
1271
|
+
if (options.dropImages) {
|
|
1202
1272
|
continue;
|
|
1203
1273
|
}
|
|
1204
1274
|
let url = "";
|
|
@@ -1273,15 +1343,15 @@ function processInputItem(item, messages, dropImages) {
|
|
|
1273
1343
|
return;
|
|
1274
1344
|
}
|
|
1275
1345
|
if (itemType === "function_call" || itemType === "commandExecution" || itemType === "local_shell_call" || itemType === "fileChange" || itemType === "custom_tool_call" || itemType === "web_search_call") {
|
|
1276
|
-
processToolCall(item, messages, getLastAssistant);
|
|
1346
|
+
processToolCall(item, messages, getLastAssistant, options.fallbackThoughtSignature, context);
|
|
1277
1347
|
return;
|
|
1278
1348
|
}
|
|
1279
1349
|
if (itemType === "function_call_output" || itemType === "commandExecutionOutput" || itemType === "fileChangeOutput" || itemType === "custom_tool_call_output") {
|
|
1280
|
-
processToolOutput(item, messages);
|
|
1350
|
+
processToolOutput(item, messages, context);
|
|
1281
1351
|
return;
|
|
1282
1352
|
}
|
|
1283
1353
|
}
|
|
1284
|
-
function processToolCall(item, messages, getLastAssistant) {
|
|
1354
|
+
function processToolCall(item, messages, getLastAssistant, fallbackThoughtSignature, context) {
|
|
1285
1355
|
const callId = String(item.call_id ?? "") || String(item.id ?? "") || makeId("call");
|
|
1286
1356
|
let name = item.name === void 0 ? void 0 : String(item.name);
|
|
1287
1357
|
const itemType = item.type === void 0 ? void 0 : String(item.type);
|
|
@@ -1323,25 +1393,35 @@ function processToolCall(item, messages, getLastAssistant) {
|
|
|
1323
1393
|
if (!name) {
|
|
1324
1394
|
return;
|
|
1325
1395
|
}
|
|
1396
|
+
if (context?.validFunctionNames?.size && !context.validFunctionNames.has(name)) {
|
|
1397
|
+
context.ignoredToolCallIds.add(callId);
|
|
1398
|
+
const amsg2 = getLastAssistant();
|
|
1399
|
+
const note = `Unsupported tool call omitted: ${name}`;
|
|
1400
|
+
amsg2.content = typeof amsg2.content === "string" && amsg2.content ? `${amsg2.content}
|
|
1401
|
+
${note}` : note;
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1326
1404
|
const amsg = getLastAssistant();
|
|
1327
1405
|
if (!amsg.tool_calls) {
|
|
1328
1406
|
amsg.tool_calls = [];
|
|
1329
1407
|
}
|
|
1330
|
-
|
|
1408
|
+
const toolCall = {
|
|
1331
1409
|
id: callId,
|
|
1332
1410
|
type: "function",
|
|
1333
1411
|
function: { name, arguments: argsStr }
|
|
1334
|
-
}
|
|
1335
|
-
const sig = item.thought_signature;
|
|
1412
|
+
};
|
|
1413
|
+
const sig = item.thought_signature ?? fallbackThoughtSignature;
|
|
1336
1414
|
const thought = item.thought;
|
|
1337
1415
|
if (typeof sig === "string" && sig) {
|
|
1416
|
+
toolCall.extra_content = { google: { thought_signature: sig } };
|
|
1338
1417
|
amsg.thought_signature = sig;
|
|
1339
1418
|
}
|
|
1419
|
+
amsg.tool_calls.push(toolCall);
|
|
1340
1420
|
if (typeof thought === "string" && thought) {
|
|
1341
1421
|
amsg.reasoning_content = (amsg.reasoning_content ?? "") + thought;
|
|
1342
1422
|
}
|
|
1343
1423
|
}
|
|
1344
|
-
function processToolOutput(item, messages) {
|
|
1424
|
+
function processToolOutput(item, messages, context) {
|
|
1345
1425
|
const callId = item.call_id === void 0 ? void 0 : String(item.call_id);
|
|
1346
1426
|
const outputRaw = item.output ?? item.content ?? item.stdout ?? "";
|
|
1347
1427
|
let content = "";
|
|
@@ -1368,6 +1448,15 @@ function processToolOutput(item, messages) {
|
|
|
1368
1448
|
if (!content && typeof item.stderr === "string" && item.stderr) {
|
|
1369
1449
|
content = `Error: ${item.stderr}`;
|
|
1370
1450
|
}
|
|
1451
|
+
if (callId && context?.ignoredToolCallIds.has(callId)) {
|
|
1452
|
+
if (content) {
|
|
1453
|
+
messages.push({
|
|
1454
|
+
role: "user",
|
|
1455
|
+
content: `Output for omitted unsupported tool call: ${content}`
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1371
1460
|
messages.push({
|
|
1372
1461
|
role: "tool",
|
|
1373
1462
|
tool_call_id: callId,
|
|
@@ -1387,7 +1476,8 @@ function mapTools2(tools) {
|
|
|
1387
1476
|
if (!name) {
|
|
1388
1477
|
continue;
|
|
1389
1478
|
}
|
|
1390
|
-
const
|
|
1479
|
+
const rawParams = fn?.parameters ?? tool.parameters ?? { type: "object" };
|
|
1480
|
+
const params = sanitizeJsonSchema(rawParams);
|
|
1391
1481
|
out.push({
|
|
1392
1482
|
type: "function",
|
|
1393
1483
|
function: {
|
|
@@ -1540,6 +1630,10 @@ function mapToolCallToOutput(tc) {
|
|
|
1540
1630
|
arguments: args,
|
|
1541
1631
|
call_id: callId
|
|
1542
1632
|
};
|
|
1633
|
+
const thoughtSignature = getThoughtSignature(tc);
|
|
1634
|
+
if (thoughtSignature) {
|
|
1635
|
+
item.thought_signature = thoughtSignature;
|
|
1636
|
+
}
|
|
1543
1637
|
if (SHELL_TOOL_NAMES3.has(name)) {
|
|
1544
1638
|
item.type = "local_shell_call";
|
|
1545
1639
|
const parsed = safeJsonParse(args);
|
|
@@ -1547,6 +1641,10 @@ function mapToolCallToOutput(tc) {
|
|
|
1547
1641
|
}
|
|
1548
1642
|
return item;
|
|
1549
1643
|
}
|
|
1644
|
+
function getThoughtSignature(tc) {
|
|
1645
|
+
const sig = tc.extra_content?.google?.thought_signature ?? tc.thought_signature;
|
|
1646
|
+
return typeof sig === "string" && sig ? sig : void 0;
|
|
1647
|
+
}
|
|
1550
1648
|
|
|
1551
1649
|
// src/translate/openai/translateStream.ts
|
|
1552
1650
|
var SHELL_TOOL_NAMES4 = /* @__PURE__ */ new Set(["shell", "container.exec", "shell_command"]);
|
|
@@ -1649,6 +1747,10 @@ var StreamTranslator2 = class {
|
|
|
1649
1747
|
});
|
|
1650
1748
|
}
|
|
1651
1749
|
const fn = tc.function;
|
|
1750
|
+
const thoughtSignature = getThoughtSignature2(tc);
|
|
1751
|
+
if (thoughtSignature) {
|
|
1752
|
+
state.item.thought_signature = thoughtSignature;
|
|
1753
|
+
}
|
|
1652
1754
|
if (fn?.name) {
|
|
1653
1755
|
state.item.name = (state.item.name ?? "") + fn.name;
|
|
1654
1756
|
}
|
|
@@ -1760,6 +1862,10 @@ var StreamTranslator2 = class {
|
|
|
1760
1862
|
};
|
|
1761
1863
|
}
|
|
1762
1864
|
};
|
|
1865
|
+
function getThoughtSignature2(tc) {
|
|
1866
|
+
const sig = tc.extra_content?.google?.thought_signature ?? tc.thought_signature;
|
|
1867
|
+
return typeof sig === "string" && sig ? sig : void 0;
|
|
1868
|
+
}
|
|
1763
1869
|
|
|
1764
1870
|
// src/fetch.ts
|
|
1765
1871
|
function createResponsesFetch(options) {
|
|
@@ -1767,7 +1873,7 @@ function createResponsesFetch(options) {
|
|
|
1767
1873
|
throw new Error("baseUrl is required");
|
|
1768
1874
|
}
|
|
1769
1875
|
const rawFormat = options.upstreamFormat;
|
|
1770
|
-
const format = rawFormat ? normalizeFormat(rawFormat) : inferFormatFromUrl(options.baseUrl) ?? "openai-chat";
|
|
1876
|
+
const format = rawFormat ? normalizeFormat(rawFormat) : inferFormatFromUrl(options.baseUrl) ?? inferFormatFromModel(options.model) ?? "openai-chat";
|
|
1771
1877
|
if (!format) {
|
|
1772
1878
|
throw new Error(
|
|
1773
1879
|
`Unsupported upstream format: ${options.upstreamFormat}. Use 'anthropic' or 'openai-chat'`
|
|
@@ -1938,7 +2044,8 @@ async function handleResponses(request, format, options, baseFetch, incomingHead
|
|
|
1938
2044
|
options.baseUrl,
|
|
1939
2045
|
dropImages,
|
|
1940
2046
|
options.reasoning_effort,
|
|
1941
|
-
options.thinking
|
|
2047
|
+
options.thinking,
|
|
2048
|
+
options.fallbackThoughtSignature
|
|
1942
2049
|
);
|
|
1943
2050
|
const upstreamHeaders = buildUpstreamHeaders(format, options, incomingHeaders);
|
|
1944
2051
|
const upstream = await baseFetch(resolvedUrl, {
|
|
@@ -1994,7 +2101,7 @@ function buildRequestMetadata(request, temperature, top_p) {
|
|
|
1994
2101
|
metadata: request.metadata ?? {}
|
|
1995
2102
|
};
|
|
1996
2103
|
}
|
|
1997
|
-
function buildUpstreamBody(request, format, streaming, baseUrl, dropImages, reasoning_effort, thinking) {
|
|
2104
|
+
function buildUpstreamBody(request, format, streaming, baseUrl, dropImages, reasoning_effort, thinking, fallbackThoughtSignature) {
|
|
1998
2105
|
if (format === "anthropic") {
|
|
1999
2106
|
const { request: ar } = translateRequest(request);
|
|
2000
2107
|
ar.stream = streaming;
|
|
@@ -2025,7 +2132,10 @@ function buildUpstreamBody(request, format, streaming, baseUrl, dropImages, reas
|
|
|
2025
2132
|
requestMetadata: buildRequestMetadata(request, ar.temperature, ar.top_p)
|
|
2026
2133
|
};
|
|
2027
2134
|
}
|
|
2028
|
-
const { request: cr } = translateRequest2(request, {
|
|
2135
|
+
const { request: cr } = translateRequest2(request, {
|
|
2136
|
+
dropImages,
|
|
2137
|
+
fallbackThoughtSignature
|
|
2138
|
+
});
|
|
2029
2139
|
cr.stream = streaming;
|
|
2030
2140
|
if (streaming) {
|
|
2031
2141
|
cr.stream_options = { include_usage: true };
|