@codeproxy/core 0.1.5 → 0.1.7
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 +106 -17
- 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 +106 -17
- 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
|
@@ -45,13 +45,21 @@ var DEFAULT_REASONING_BUDGETS = {
|
|
|
45
45
|
function translateRequest(data, options = {}) {
|
|
46
46
|
const model = data.model;
|
|
47
47
|
const maxTokens = typeof data.max_output_tokens === "number" && data.max_output_tokens || typeof data.max_tokens === "number" && data.max_tokens || options.defaultMaxTokens || 8192;
|
|
48
|
-
|
|
48
|
+
let systemBlocks = extractSystemBlocks(data.instructions);
|
|
49
49
|
const built = buildMessages(data, systemBlocks);
|
|
50
50
|
let messages = built.messages;
|
|
51
|
-
|
|
51
|
+
let hasPromptCache = built.hasPromptCache;
|
|
52
|
+
if (data.prompt_cache_key) {
|
|
53
|
+
hasPromptCache = true;
|
|
54
|
+
systemBlocks = markBlocksForCache(systemBlocks);
|
|
55
|
+
messages = markCacheBreakpoint(messages);
|
|
56
|
+
}
|
|
52
57
|
messages = repairToolAdjacency(messages);
|
|
53
58
|
messages = sanitizeMessages(messages);
|
|
54
59
|
messages = ensureEndsWithUser(messages);
|
|
60
|
+
if (data.prompt_cache_key) {
|
|
61
|
+
messages = markCacheBreakpoint(messages);
|
|
62
|
+
}
|
|
55
63
|
const request = {
|
|
56
64
|
model,
|
|
57
65
|
messages,
|
|
@@ -192,7 +200,15 @@ function buildMessages(data, systemBlocks) {
|
|
|
192
200
|
} else if (part && typeof part === "object") {
|
|
193
201
|
const contentPart = part;
|
|
194
202
|
if (contentPart.type === "input_text" || contentPart.type === "text" || contentPart.type === "output_text") {
|
|
195
|
-
|
|
203
|
+
const textBlock = {
|
|
204
|
+
type: "text",
|
|
205
|
+
text: String(contentPart.text ?? "")
|
|
206
|
+
};
|
|
207
|
+
const cc = contentPart.cache_control;
|
|
208
|
+
if (cc) {
|
|
209
|
+
textBlock.cache_control = cc;
|
|
210
|
+
}
|
|
211
|
+
contentBlocks.push(textBlock);
|
|
196
212
|
} else if (contentPart.type === "input_image" || contentPart.type === "image" || contentPart.type === "image_url") {
|
|
197
213
|
const imgUrlPart = contentPart;
|
|
198
214
|
const imgUrl = imgUrlPart.image_url;
|
|
@@ -510,6 +526,48 @@ function ensureEndsWithUser(messages) {
|
|
|
510
526
|
}
|
|
511
527
|
return [...messages, { role: "user", content: [{ type: "text", text: "Continue." }] }];
|
|
512
528
|
}
|
|
529
|
+
function markBlocksForCache(blocks) {
|
|
530
|
+
let count = 0;
|
|
531
|
+
for (const block of blocks) {
|
|
532
|
+
if (!block.cache_control) {
|
|
533
|
+
block.cache_control = { type: "ephemeral" };
|
|
534
|
+
count++;
|
|
535
|
+
if (count >= 3) {
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return blocks;
|
|
541
|
+
}
|
|
542
|
+
function markCacheBreakpoint(messages) {
|
|
543
|
+
for (const msg of messages) {
|
|
544
|
+
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
545
|
+
for (let j = msg.content.length - 1; j >= 0; j--) {
|
|
546
|
+
const block = msg.content[j];
|
|
547
|
+
if (block.type === "text") {
|
|
548
|
+
if (!block.cache_control) {
|
|
549
|
+
block.cache_control = { type: "ephemeral" };
|
|
550
|
+
}
|
|
551
|
+
return messages;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
for (const msg of messages) {
|
|
557
|
+
if (msg.role === "user" && Array.isArray(msg.content)) {
|
|
558
|
+
for (let j = msg.content.length - 1; j >= 0; j--) {
|
|
559
|
+
const block = msg.content[j];
|
|
560
|
+
if (block.type === "text") {
|
|
561
|
+
if (!block.cache_control) {
|
|
562
|
+
block.cache_control = { type: "ephemeral" };
|
|
563
|
+
}
|
|
564
|
+
return messages;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return messages;
|
|
570
|
+
}
|
|
513
571
|
|
|
514
572
|
// src/utils/json.ts
|
|
515
573
|
function safeJsonParse(text) {
|
|
@@ -785,6 +843,8 @@ var StreamTranslator = class {
|
|
|
785
843
|
*finalize() {
|
|
786
844
|
const items = [];
|
|
787
845
|
if (this.textItem) {
|
|
846
|
+
this.textItem.status = "completed";
|
|
847
|
+
this.textItem.content[0].text = this.textBuffer;
|
|
788
848
|
items.push({ index: this.textItemIndex, item: this.textItem });
|
|
789
849
|
}
|
|
790
850
|
for (const block of this.blocks.values()) {
|
|
@@ -896,15 +956,22 @@ var StreamTranslator = class {
|
|
|
896
956
|
if (btype === "tool_use") {
|
|
897
957
|
const outputIndex = this.outputCounter++;
|
|
898
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 !== "{}";
|
|
899
961
|
const item = {
|
|
900
962
|
id: callId,
|
|
901
963
|
type: "function_call",
|
|
902
964
|
status: "in_progress",
|
|
903
965
|
name: block.name ?? "",
|
|
904
|
-
arguments: "",
|
|
966
|
+
arguments: hasInitialInput ? initialInput : "",
|
|
905
967
|
call_id: callId
|
|
906
968
|
};
|
|
907
|
-
this.blocks.set(index, {
|
|
969
|
+
this.blocks.set(index, {
|
|
970
|
+
type: "tool_use",
|
|
971
|
+
outputIndex,
|
|
972
|
+
item,
|
|
973
|
+
buffer: hasInitialInput ? initialInput : ""
|
|
974
|
+
});
|
|
908
975
|
yield this.makeEvent("response.output_item.added", {
|
|
909
976
|
response_id: this.responseId,
|
|
910
977
|
output_index: outputIndex,
|
|
@@ -1023,7 +1090,7 @@ function translateRequest2(data, options = {}) {
|
|
|
1023
1090
|
continue;
|
|
1024
1091
|
}
|
|
1025
1092
|
const rawItem = raw;
|
|
1026
|
-
processInputItem(rawItem, messages, options
|
|
1093
|
+
processInputItem(rawItem, messages, options);
|
|
1027
1094
|
}
|
|
1028
1095
|
const request = {
|
|
1029
1096
|
model: data.model,
|
|
@@ -1083,7 +1150,7 @@ function buildSystemContent(instructions) {
|
|
|
1083
1150
|
}
|
|
1084
1151
|
return out;
|
|
1085
1152
|
}
|
|
1086
|
-
function processInputItem(item, messages,
|
|
1153
|
+
function processInputItem(item, messages, options) {
|
|
1087
1154
|
const itemType = String(item.type) || "message";
|
|
1088
1155
|
const getLastAssistant = () => {
|
|
1089
1156
|
const last = messages[messages.length - 1];
|
|
@@ -1145,7 +1212,7 @@ function processInputItem(item, messages, dropImages) {
|
|
|
1145
1212
|
} else if (contentPart.type === "reasoning_text") {
|
|
1146
1213
|
reasoningContent += String(contentPart.text ?? "");
|
|
1147
1214
|
} else if (contentPart.type === "input_image" || contentPart.type === "image" || contentPart.type === "image_url") {
|
|
1148
|
-
if (dropImages) {
|
|
1215
|
+
if (options.dropImages) {
|
|
1149
1216
|
continue;
|
|
1150
1217
|
}
|
|
1151
1218
|
let url = "";
|
|
@@ -1220,7 +1287,7 @@ function processInputItem(item, messages, dropImages) {
|
|
|
1220
1287
|
return;
|
|
1221
1288
|
}
|
|
1222
1289
|
if (itemType === "function_call" || itemType === "commandExecution" || itemType === "local_shell_call" || itemType === "fileChange" || itemType === "custom_tool_call" || itemType === "web_search_call") {
|
|
1223
|
-
processToolCall(item, messages, getLastAssistant);
|
|
1290
|
+
processToolCall(item, messages, getLastAssistant, options.fallbackThoughtSignature);
|
|
1224
1291
|
return;
|
|
1225
1292
|
}
|
|
1226
1293
|
if (itemType === "function_call_output" || itemType === "commandExecutionOutput" || itemType === "fileChangeOutput" || itemType === "custom_tool_call_output") {
|
|
@@ -1228,7 +1295,7 @@ function processInputItem(item, messages, dropImages) {
|
|
|
1228
1295
|
return;
|
|
1229
1296
|
}
|
|
1230
1297
|
}
|
|
1231
|
-
function processToolCall(item, messages, getLastAssistant) {
|
|
1298
|
+
function processToolCall(item, messages, getLastAssistant, fallbackThoughtSignature) {
|
|
1232
1299
|
const callId = String(item.call_id ?? "") || String(item.id ?? "") || makeId("call");
|
|
1233
1300
|
let name = item.name === void 0 ? void 0 : String(item.name);
|
|
1234
1301
|
const itemType = item.type === void 0 ? void 0 : String(item.type);
|
|
@@ -1274,16 +1341,18 @@ function processToolCall(item, messages, getLastAssistant) {
|
|
|
1274
1341
|
if (!amsg.tool_calls) {
|
|
1275
1342
|
amsg.tool_calls = [];
|
|
1276
1343
|
}
|
|
1277
|
-
|
|
1344
|
+
const toolCall = {
|
|
1278
1345
|
id: callId,
|
|
1279
1346
|
type: "function",
|
|
1280
1347
|
function: { name, arguments: argsStr }
|
|
1281
|
-
}
|
|
1282
|
-
const sig = item.thought_signature;
|
|
1348
|
+
};
|
|
1349
|
+
const sig = item.thought_signature ?? fallbackThoughtSignature;
|
|
1283
1350
|
const thought = item.thought;
|
|
1284
1351
|
if (typeof sig === "string" && sig) {
|
|
1352
|
+
toolCall.extra_content = { google: { thought_signature: sig } };
|
|
1285
1353
|
amsg.thought_signature = sig;
|
|
1286
1354
|
}
|
|
1355
|
+
amsg.tool_calls.push(toolCall);
|
|
1287
1356
|
if (typeof thought === "string" && thought) {
|
|
1288
1357
|
amsg.reasoning_content = (amsg.reasoning_content ?? "") + thought;
|
|
1289
1358
|
}
|
|
@@ -1487,6 +1556,10 @@ function mapToolCallToOutput(tc) {
|
|
|
1487
1556
|
arguments: args,
|
|
1488
1557
|
call_id: callId
|
|
1489
1558
|
};
|
|
1559
|
+
const thoughtSignature = getThoughtSignature(tc);
|
|
1560
|
+
if (thoughtSignature) {
|
|
1561
|
+
item.thought_signature = thoughtSignature;
|
|
1562
|
+
}
|
|
1490
1563
|
if (SHELL_TOOL_NAMES3.has(name)) {
|
|
1491
1564
|
item.type = "local_shell_call";
|
|
1492
1565
|
const parsed = safeJsonParse(args);
|
|
@@ -1494,6 +1567,10 @@ function mapToolCallToOutput(tc) {
|
|
|
1494
1567
|
}
|
|
1495
1568
|
return item;
|
|
1496
1569
|
}
|
|
1570
|
+
function getThoughtSignature(tc) {
|
|
1571
|
+
const sig = tc.extra_content?.google?.thought_signature ?? tc.thought_signature;
|
|
1572
|
+
return typeof sig === "string" && sig ? sig : void 0;
|
|
1573
|
+
}
|
|
1497
1574
|
|
|
1498
1575
|
// src/translate/openai/translateStream.ts
|
|
1499
1576
|
var SHELL_TOOL_NAMES4 = /* @__PURE__ */ new Set(["shell", "container.exec", "shell_command"]);
|
|
@@ -1596,6 +1673,10 @@ var StreamTranslator2 = class {
|
|
|
1596
1673
|
});
|
|
1597
1674
|
}
|
|
1598
1675
|
const fn = tc.function;
|
|
1676
|
+
const thoughtSignature = getThoughtSignature2(tc);
|
|
1677
|
+
if (thoughtSignature) {
|
|
1678
|
+
state.item.thought_signature = thoughtSignature;
|
|
1679
|
+
}
|
|
1599
1680
|
if (fn?.name) {
|
|
1600
1681
|
state.item.name = (state.item.name ?? "") + fn.name;
|
|
1601
1682
|
}
|
|
@@ -1707,6 +1788,10 @@ var StreamTranslator2 = class {
|
|
|
1707
1788
|
};
|
|
1708
1789
|
}
|
|
1709
1790
|
};
|
|
1791
|
+
function getThoughtSignature2(tc) {
|
|
1792
|
+
const sig = tc.extra_content?.google?.thought_signature ?? tc.thought_signature;
|
|
1793
|
+
return typeof sig === "string" && sig ? sig : void 0;
|
|
1794
|
+
}
|
|
1710
1795
|
|
|
1711
1796
|
// src/fetch.ts
|
|
1712
1797
|
function createResponsesFetch(options) {
|
|
@@ -1714,7 +1799,7 @@ function createResponsesFetch(options) {
|
|
|
1714
1799
|
throw new Error("baseUrl is required");
|
|
1715
1800
|
}
|
|
1716
1801
|
const rawFormat = options.upstreamFormat;
|
|
1717
|
-
const format = rawFormat ? normalizeFormat(rawFormat) : inferFormatFromUrl(options.baseUrl) ?? "openai-chat";
|
|
1802
|
+
const format = rawFormat ? normalizeFormat(rawFormat) : inferFormatFromUrl(options.baseUrl) ?? inferFormatFromModel(options.model) ?? "openai-chat";
|
|
1718
1803
|
if (!format) {
|
|
1719
1804
|
throw new Error(
|
|
1720
1805
|
`Unsupported upstream format: ${options.upstreamFormat}. Use 'anthropic' or 'openai-chat'`
|
|
@@ -1885,7 +1970,8 @@ async function handleResponses(request, format, options, baseFetch, incomingHead
|
|
|
1885
1970
|
options.baseUrl,
|
|
1886
1971
|
dropImages,
|
|
1887
1972
|
options.reasoning_effort,
|
|
1888
|
-
options.thinking
|
|
1973
|
+
options.thinking,
|
|
1974
|
+
options.fallbackThoughtSignature
|
|
1889
1975
|
);
|
|
1890
1976
|
const upstreamHeaders = buildUpstreamHeaders(format, options, incomingHeaders);
|
|
1891
1977
|
const upstream = await baseFetch(resolvedUrl, {
|
|
@@ -1941,7 +2027,7 @@ function buildRequestMetadata(request, temperature, top_p) {
|
|
|
1941
2027
|
metadata: request.metadata ?? {}
|
|
1942
2028
|
};
|
|
1943
2029
|
}
|
|
1944
|
-
function buildUpstreamBody(request, format, streaming, baseUrl, dropImages, reasoning_effort, thinking) {
|
|
2030
|
+
function buildUpstreamBody(request, format, streaming, baseUrl, dropImages, reasoning_effort, thinking, fallbackThoughtSignature) {
|
|
1945
2031
|
if (format === "anthropic") {
|
|
1946
2032
|
const { request: ar } = translateRequest(request);
|
|
1947
2033
|
ar.stream = streaming;
|
|
@@ -1972,7 +2058,10 @@ function buildUpstreamBody(request, format, streaming, baseUrl, dropImages, reas
|
|
|
1972
2058
|
requestMetadata: buildRequestMetadata(request, ar.temperature, ar.top_p)
|
|
1973
2059
|
};
|
|
1974
2060
|
}
|
|
1975
|
-
const { request: cr } = translateRequest2(request, {
|
|
2061
|
+
const { request: cr } = translateRequest2(request, {
|
|
2062
|
+
dropImages,
|
|
2063
|
+
fallbackThoughtSignature
|
|
2064
|
+
});
|
|
1976
2065
|
cr.stream = streaming;
|
|
1977
2066
|
if (streaming) {
|
|
1978
2067
|
cr.stream_options = { include_usage: true };
|