@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 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, { type: "tool_use", outputIndex, item, buffer: "" });
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.dropImages);
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, dropImages) {
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
- amsg.tool_calls.push({
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 params = fn?.parameters ?? tool.parameters ?? { type: "object" };
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, { dropImages });
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 };