@jeffusion/bungee-core 6.8.1 → 6.8.2
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/main.js +60 -60
- package/dist/plugins/codex-router/index.js.map +1 -1
- package/dist/plugins/llm-protocol-adapter/control.js +129 -42
- package/dist/plugins/llm-protocol-adapter/control.js.map +5 -5
- package/dist/plugins/llm-protocol-adapter/index.js +129 -42
- package/dist/plugins/llm-protocol-adapter/index.js.map +5 -5
- package/dist/plugins/token-metering/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -3427,14 +3427,20 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
|
|
|
3427
3427
|
fail("unsupported_request", "Only one response candidate is supported");
|
|
3428
3428
|
if (body.truncation !== undefined && body.truncation !== "disabled")
|
|
3429
3429
|
fail("unsupported_request", "Automatic input truncation cannot be represented");
|
|
3430
|
-
|
|
3431
|
-
|
|
3430
|
+
const diagnostics = [];
|
|
3431
|
+
if (body.include !== undefined)
|
|
3432
|
+
atParam("include", () => {
|
|
3433
|
+
for (const [index, field] of list(body.include, "include").entries()) {
|
|
3434
|
+
if (field !== "reasoning.encrypted_content" && field !== "output.encrypted_content")
|
|
3435
|
+
fail("unsupported_request", "Requested extra Responses output fields cannot be represented", `include[${index}]`);
|
|
3436
|
+
diagnostics.push({ param: `include[${index}]`, action: "omitted", reason: "target_encrypted_output_unavailable" });
|
|
3437
|
+
}
|
|
3438
|
+
});
|
|
3432
3439
|
if (body.prompt_cache_key !== undefined)
|
|
3433
3440
|
string(body.prompt_cache_key, "prompt_cache_key");
|
|
3434
3441
|
if (body.client_metadata !== undefined)
|
|
3435
3442
|
record(body.client_metadata, "client_metadata");
|
|
3436
3443
|
const model = string(body.model, "model", true);
|
|
3437
|
-
const diagnostics = [];
|
|
3438
3444
|
const input = atParam("input", () => typeof body.input === "string" ? [{ role: "user", content: body.input }] : list(body.input, "input"));
|
|
3439
3445
|
if (input.length > budget.maxItems)
|
|
3440
3446
|
fail("resource_limit", "Too many input items", "input");
|
|
@@ -3567,29 +3573,30 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
|
|
|
3567
3573
|
fail("tool_name_collision", "Historical tool kind conflicts with its current declaration");
|
|
3568
3574
|
return wire;
|
|
3569
3575
|
};
|
|
3570
|
-
const normalizeContent = (rawContent, role) => {
|
|
3576
|
+
const normalizeContent = (rawContent, role, path) => {
|
|
3571
3577
|
if (typeof rawContent === "string")
|
|
3572
3578
|
return rawContent;
|
|
3573
3579
|
const parts = list(rawContent, "message content");
|
|
3574
|
-
for (const rawPart of parts)
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
if (
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3580
|
+
for (const [index, rawPart] of parts.entries())
|
|
3581
|
+
atParam(path ? `${path}[${index}]` : messageParam, () => {
|
|
3582
|
+
const part = record(rawPart, "content part");
|
|
3583
|
+
if (part.type === "input_text" || part.type === "output_text" || part.type === "text")
|
|
3584
|
+
string(part.text, "content text");
|
|
3585
|
+
else if (part.type === "refusal" && role === "assistant") {
|
|
3586
|
+
string(part.refusal, "refusal");
|
|
3587
|
+
if (capabilities.preserveRefusalContent && protocol !== "chat_completions")
|
|
3588
|
+
fail("unsupported_content", "Target protocol cannot preserve refusal history authority");
|
|
3589
|
+
} else if (part.type === "input_image" && (role === "user" || role === "tool" && options.toolOutputImages)) {
|
|
3590
|
+
if (protocol !== "chat_completions")
|
|
3591
|
+
fail("unsupported_content", "Anthropic image conversion requires provider-specific URL/data support");
|
|
3592
|
+
string(part.image_url, "image_url", true);
|
|
3593
|
+
if (part.detail !== undefined && !["auto", "low", "high"].includes(String(part.detail)))
|
|
3594
|
+
fail("unsupported_content", "Unknown image detail", path ? `${path}[${index}].detail` : undefined);
|
|
3595
|
+
} else
|
|
3596
|
+
fail("unsupported_content", "Unsupported message content type");
|
|
3597
|
+
if (part.annotations !== undefined && list(part.annotations, "annotations").length)
|
|
3598
|
+
fail("unsupported_content", "Output annotations cannot be represented in input history");
|
|
3599
|
+
});
|
|
3593
3600
|
const normalized = contentNormalizer.normalizeResponsesMessageContent(parts.map((raw) => {
|
|
3594
3601
|
const part = raw;
|
|
3595
3602
|
return part.type === "refusal" ? { type: "output_text", text: part.refusal } : part;
|
|
@@ -3684,7 +3691,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
|
|
|
3684
3691
|
if (!callIds.has(id) || answered.has(id))
|
|
3685
3692
|
fail("invalid_tool_history", "Unmatched or duplicate tool result");
|
|
3686
3693
|
answered.add(id);
|
|
3687
|
-
const output = typeof item.output === "string" ? item.output : normalizeContent(item.output, "tool");
|
|
3694
|
+
const output = typeof item.output === "string" ? item.output : normalizeContent(item.output, "tool", `${messageParam}.output`);
|
|
3688
3695
|
addMessage({ role: "tool", tool_call_id: id, content: output, ...item.is_error === true ? { is_error: true } : {} });
|
|
3689
3696
|
return;
|
|
3690
3697
|
}
|
|
@@ -4285,6 +4292,38 @@ function textParts(raw, path, style, role) {
|
|
|
4285
4292
|
|
|
4286
4293
|
// packages/llms/src/protocol-session/gemini.ts
|
|
4287
4294
|
var signatureSeparator = "__thought__";
|
|
4295
|
+
function geminiInlineImage(image, path, budget) {
|
|
4296
|
+
const param = `${path}.image_url`;
|
|
4297
|
+
const url = bounded(string(image.url, "image URL", true), budget.maxRequestBytes, param);
|
|
4298
|
+
const match = /^data:([^;,]+);base64,([\s\S]*)$/.exec(url);
|
|
4299
|
+
if (!match || !["image/png", "image/jpeg", "image/webp"].includes(match[1]))
|
|
4300
|
+
fail("unsupported_content", "Gemini images require inline PNG, JPEG or WebP data", param);
|
|
4301
|
+
const data = match[2];
|
|
4302
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(data) || data.length % 4 === 1 || data.includes("=") && data.length % 4 !== 0 || Buffer.from(data, "base64").toString("base64").replace(/=+$/, "") !== data.replace(/=+$/, ""))
|
|
4303
|
+
fail("invalid_payload", "Invalid image Base64 encoding", param);
|
|
4304
|
+
return { mimeType: match[1], data };
|
|
4305
|
+
}
|
|
4306
|
+
function geminiToolResponse(content, path, budget, diagnostics) {
|
|
4307
|
+
if (!Array.isArray(content) || !content.some((part) => part.type === "image_url"))
|
|
4308
|
+
return { response: { output: content } };
|
|
4309
|
+
if (content.length > budget.maxItems)
|
|
4310
|
+
fail("resource_limit", "Too many tool output parts", path);
|
|
4311
|
+
const parts = [];
|
|
4312
|
+
const output = content.map((part, index) => atParam(`${path}[${index}]`, () => {
|
|
4313
|
+
if (part.type === "text")
|
|
4314
|
+
return part;
|
|
4315
|
+
if (part.type !== "image_url")
|
|
4316
|
+
fail("unsupported_content", "Unsupported Gemini tool result content");
|
|
4317
|
+
const image = record(part.image_url, "tool image");
|
|
4318
|
+
const inlineData = geminiInlineImage(image, `${path}[${index}]`, budget);
|
|
4319
|
+
const displayName = `tool-image-${index}.${inlineData.mimeType.slice("image/".length)}`;
|
|
4320
|
+
parts.push({ inlineData: { ...inlineData, displayName } });
|
|
4321
|
+
if (image.detail !== undefined)
|
|
4322
|
+
diagnostics.push({ param: `${path}[${index}].detail`, action: "omitted", reason: "gemini_tool_image_detail_unavailable" });
|
|
4323
|
+
return { $ref: displayName };
|
|
4324
|
+
}));
|
|
4325
|
+
return { response: { output }, parts };
|
|
4326
|
+
}
|
|
4288
4327
|
function checkedSignature(value, maxBytes) {
|
|
4289
4328
|
const signature = bounded(string(value, "thoughtSignature", true), maxBytes, "thoughtSignature");
|
|
4290
4329
|
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(signature) || signature.length % 4 === 1 || signature.includes("=") && signature.length % 4 !== 0 || Buffer.from(signature, "base64").toString("base64").replace(/=+$/, "") !== signature.replace(/=+$/, ""))
|
|
@@ -4815,7 +4854,31 @@ function validateCanonical(b, context) {
|
|
|
4815
4854
|
for (const [j, part] of t.content.entries()) {
|
|
4816
4855
|
const c = record(part, "part");
|
|
4817
4856
|
fields2(c, c.type === "input_image" ? ["type", "image_url", "detail"] : c.type === "refusal" ? ["type", "refusal"] : ["type", "text", "annotations"], `${p}.content[${j}]`, "unsupported_content");
|
|
4857
|
+
if (context.sourceProtocol === "responses" && context.targetProtocol === "gemini_generate_content" && t.role === "user" && c.type === "input_image") {
|
|
4858
|
+
atParam(`${p}.content[${j}].image_url`, () => string(c.image_url, "image URL", true));
|
|
4859
|
+
if (c.detail !== undefined && (typeof c.detail !== "string" || !["auto", "low", "high"].includes(c.detail)))
|
|
4860
|
+
fail("unsupported_content", "Unknown image detail", `${p}.content[${j}].detail`);
|
|
4861
|
+
}
|
|
4818
4862
|
}
|
|
4863
|
+
if (context.sourceProtocol === "responses" && context.targetProtocol === "gemini_generate_content" && Array.isArray(t.output))
|
|
4864
|
+
for (const [j, part] of t.output.entries())
|
|
4865
|
+
atParam(`${p}.output[${j}]`, () => {
|
|
4866
|
+
const c = record(part, "tool output part");
|
|
4867
|
+
fields2(c, c.type === "input_image" ? ["type", "image_url", "detail"] : ["type", "text", "annotations"], `${p}.output[${j}]`, "unsupported_content");
|
|
4868
|
+
if (c.type === "input_image") {
|
|
4869
|
+
atParam(`${p}.output[${j}].image_url`, () => string(c.image_url, "image URL", true));
|
|
4870
|
+
if (c.detail !== undefined && (typeof c.detail !== "string" || !["auto", "low", "high"].includes(c.detail)))
|
|
4871
|
+
fail("unsupported_content", "Unknown image detail", `${p}.output[${j}].detail`);
|
|
4872
|
+
} else if (c.type === "input_text" || c.type === "output_text" || c.type === "text")
|
|
4873
|
+
atParam(`${p}.output[${j}].text`, () => string(c.text, "tool output text"));
|
|
4874
|
+
else
|
|
4875
|
+
fail("unsupported_content", "Unsupported tool output content type", `${p}.output[${j}].type`);
|
|
4876
|
+
if (c.annotations !== undefined)
|
|
4877
|
+
atParam(`${p}.output[${j}].annotations`, () => {
|
|
4878
|
+
if (list(c.annotations, "annotations").length)
|
|
4879
|
+
fail("unsupported_content", "Output annotations cannot be represented in tool history");
|
|
4880
|
+
});
|
|
4881
|
+
});
|
|
4819
4882
|
}
|
|
4820
4883
|
const inspectTool = (raw, path, nested = false) => {
|
|
4821
4884
|
const t = record(raw, "tool");
|
|
@@ -4848,6 +4911,7 @@ function validateCanonical(b, context) {
|
|
|
4848
4911
|
}
|
|
4849
4912
|
function convertProtocolRequest(raw, context) {
|
|
4850
4913
|
const b = canonicalRequest(raw, context.sourceProtocol, context.model);
|
|
4914
|
+
const originalInput = b.input;
|
|
4851
4915
|
if (context.streaming !== undefined) {
|
|
4852
4916
|
bool(context.streaming, "context.streaming");
|
|
4853
4917
|
if (b.stream !== undefined && b.stream !== context.streaming)
|
|
@@ -4945,13 +5009,13 @@ function convertProtocolRequest(raw, context) {
|
|
|
4945
5009
|
const decodingTarget = target === "gemini_generate_content" ? "chat_completions" : target;
|
|
4946
5010
|
if (target === "gemini_generate_content" && reasoning)
|
|
4947
5011
|
b.reasoning = { ...reasoning, effort: undefined };
|
|
4948
|
-
const decoded = decodeResponsesRequest(b, decodingTarget, { ...cap, reasoningHistory: target !== "anthropic_messages" && cap.reasoningHistory === true, reasoningEffort: target === "chat_completions" && cap.reasoningEffort === true, anthropicThinkingBudget: undefined }, { preserveContentOrder: target === "gemini_generate_content" });
|
|
5012
|
+
const decoded = decodeResponsesRequest(b, decodingTarget, { ...cap, reasoningHistory: target !== "anthropic_messages" && cap.reasoningHistory === true, reasoningEffort: target === "chat_completions" && cap.reasoningEffort === true, anthropicThinkingBudget: undefined }, { preserveContentOrder: target === "gemini_generate_content", toolOutputImages: context.sourceProtocol === "responses" && target === "gemini_generate_content" });
|
|
4949
5013
|
const diagnostics = decoded.diagnostics;
|
|
4950
5014
|
if (target === "gemini_generate_content" && decoded.body.parallel_tool_calls === true) {
|
|
4951
5015
|
delete decoded.body.parallel_tool_calls;
|
|
4952
5016
|
diagnostics.push({ param: "parallel_tool_calls", action: "omitted", reason: "gemini_parallel_tool_calls_allowed" });
|
|
4953
5017
|
}
|
|
4954
|
-
const body = target === "gemini_generate_content" ? renderGemini(decoded.body, context, geminiHistory?.parts, diagnostics) : decoded.body;
|
|
5018
|
+
const body = target === "gemini_generate_content" ? renderGemini(decoded.body, context, originalInput, geminiHistory?.parts, diagnostics) : decoded.body;
|
|
4955
5019
|
if (stop !== undefined)
|
|
4956
5020
|
body[target === "chat_completions" ? "stop" : target === "anthropic_messages" ? "stop_sequences" : "generationConfig"] = target === "gemini_generate_content" ? { ...record(body.generationConfig ?? {}, "generationConfig"), stopSequences: typeof stop === "string" ? [stop] : stop } : typeof stop === "string" ? [stop] : stop;
|
|
4957
5021
|
if (target === "anthropic_messages") {
|
|
@@ -4988,9 +5052,19 @@ function convertProtocolRequest(raw, context) {
|
|
|
4988
5052
|
diagnostics.push({ param: "reasoning", action: "mapped", reason: "explicit_verified_wire_policy" });
|
|
4989
5053
|
return { body, canonicalHistory: geminiHistory?.canonicalHistory(decoded.canonicalInput) ?? decoded.canonicalInput, diagnostics, toolNames: decoded.toolNames, streaming: b.stream === true };
|
|
4990
5054
|
}
|
|
4991
|
-
function renderGemini(chat, context, signedParts, diagnostics) {
|
|
5055
|
+
function renderGemini(chat, context, input, signedParts, diagnostics) {
|
|
4992
5056
|
const result = { contents: [] }, contents = result.contents, system = [], calls = new Map;
|
|
4993
5057
|
const nativeIds = new Set;
|
|
5058
|
+
const budget = limits(context.capabilities?.limits);
|
|
5059
|
+
const toolOutputParams = new Map;
|
|
5060
|
+
const userMessageParams = [];
|
|
5061
|
+
let userMessageIndex = 0;
|
|
5062
|
+
for (const [index, item] of (Array.isArray(input) ? input : []).entries()) {
|
|
5063
|
+
if (item.type === "function_call_output" || item.type === "custom_tool_call_output")
|
|
5064
|
+
toolOutputParams.set(item.call_id, `input[${index}].output`);
|
|
5065
|
+
if ((item.type === undefined || item.type === "message") && item.role === "user" || item.type === "agent_message")
|
|
5066
|
+
userMessageParams.push(`input[${index}].content`);
|
|
5067
|
+
}
|
|
4994
5068
|
const append = (role, parts) => {
|
|
4995
5069
|
const prev = contents.at(-1);
|
|
4996
5070
|
if (prev?.role === role)
|
|
@@ -5004,10 +5078,33 @@ function renderGemini(chat, context, signedParts, diagnostics) {
|
|
|
5004
5078
|
append("model", signed);
|
|
5005
5079
|
continue;
|
|
5006
5080
|
}
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5081
|
+
if (m.role === "tool") {
|
|
5082
|
+
const name = calls.get(string(m.tool_call_id, "tool_call_id"));
|
|
5083
|
+
if (!name)
|
|
5084
|
+
fail("invalid_tool_history", "Unknown tool result", "input");
|
|
5085
|
+
if (m.is_error === true)
|
|
5086
|
+
fail("unsupported_content", "Tool error authority cannot be represented by Gemini", "input");
|
|
5087
|
+
const { id } = context.sourceProtocol === "responses" ? decodeGeminiCallId(m.tool_call_id, budget.maxArgumentBytes) : { id: m.tool_call_id };
|
|
5088
|
+
const response = geminiToolResponse(m.content, toolOutputParams.get(m.tool_call_id) ?? "input", budget, diagnostics);
|
|
5089
|
+
append("user", [{ functionResponse: { id, name, ...response } }]);
|
|
5090
|
+
continue;
|
|
5091
|
+
}
|
|
5092
|
+
const contentParam = m.role === "user" ? userMessageParams[userMessageIndex++] ?? "input" : "input";
|
|
5093
|
+
if (Array.isArray(m.content) && m.content.some((p) => p.type === "image_url") && m.content.length > budget.maxItems)
|
|
5094
|
+
fail("resource_limit", "Too many message content parts", contentParam);
|
|
5095
|
+
const parts = m.content == null ? [] : typeof m.content === "string" ? [{ text: m.content }] : m.content.map((p, index) => {
|
|
5096
|
+
if (p.type === "text")
|
|
5097
|
+
return { text: p.text };
|
|
5098
|
+
const path = `${contentParam}[${index}]`;
|
|
5099
|
+
if (context.sourceProtocol !== "responses" || m.role !== "user" || p.type !== "image_url")
|
|
5100
|
+
fail("unsupported_content", "This content cannot cross to Gemini", contentParam);
|
|
5101
|
+
const image = record(p.image_url, "image"), part = { inlineData: geminiInlineImage(image, path, budget) };
|
|
5102
|
+
if (image.detail === "low" || image.detail === "high") {
|
|
5103
|
+
part.mediaResolution = { level: image.detail === "high" ? "MEDIA_RESOLUTION_HIGH" : "MEDIA_RESOLUTION_LOW" };
|
|
5104
|
+
diagnostics.push({ param: `${path}.detail`, action: "mapped", reason: "gemini_image_media_resolution" });
|
|
5105
|
+
} else if (image.detail === "auto")
|
|
5106
|
+
diagnostics.push({ param: `${path}.detail`, action: "omitted", reason: "gemini_image_detail_auto" });
|
|
5107
|
+
return part;
|
|
5011
5108
|
});
|
|
5012
5109
|
if (m.role === "system" || m.role === "developer") {
|
|
5013
5110
|
if (contents.length) {
|
|
@@ -5018,16 +5115,6 @@ function renderGemini(chat, context, signedParts, diagnostics) {
|
|
|
5018
5115
|
system.push(...parts);
|
|
5019
5116
|
continue;
|
|
5020
5117
|
}
|
|
5021
|
-
if (m.role === "tool") {
|
|
5022
|
-
const name = calls.get(string(m.tool_call_id, "tool_call_id"));
|
|
5023
|
-
if (!name)
|
|
5024
|
-
fail("invalid_tool_history", "Unknown tool result", "input");
|
|
5025
|
-
if (m.is_error === true)
|
|
5026
|
-
fail("unsupported_content", "Tool error authority cannot be represented by Gemini", "input");
|
|
5027
|
-
const { id } = context.sourceProtocol === "responses" ? decodeGeminiCallId(m.tool_call_id, limits(context.capabilities?.limits).maxArgumentBytes) : { id: m.tool_call_id };
|
|
5028
|
-
append("user", [{ functionResponse: { id, name, response: { output: m.content } } }]);
|
|
5029
|
-
continue;
|
|
5030
|
-
}
|
|
5031
5118
|
if (m.reasoning_content !== undefined)
|
|
5032
5119
|
parts.unshift({ text: m.reasoning_content, thought: true });
|
|
5033
5120
|
for (const c of m.tool_calls ?? []) {
|
|
@@ -6732,4 +6819,4 @@ export {
|
|
|
6732
6819
|
control_default as default
|
|
6733
6820
|
};
|
|
6734
6821
|
|
|
6735
|
-
//# debugId=
|
|
6822
|
+
//# debugId=529D4667BD09A6C364756E2164756E21
|