@jeffusion/bungee-core 6.8.0 → 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 +27 -27
- 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
|
@@ -3613,14 +3613,20 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
|
|
|
3613
3613
|
fail("unsupported_request", "Only one response candidate is supported");
|
|
3614
3614
|
if (body.truncation !== undefined && body.truncation !== "disabled")
|
|
3615
3615
|
fail("unsupported_request", "Automatic input truncation cannot be represented");
|
|
3616
|
-
|
|
3617
|
-
|
|
3616
|
+
const diagnostics = [];
|
|
3617
|
+
if (body.include !== undefined)
|
|
3618
|
+
atParam("include", () => {
|
|
3619
|
+
for (const [index, field] of list(body.include, "include").entries()) {
|
|
3620
|
+
if (field !== "reasoning.encrypted_content" && field !== "output.encrypted_content")
|
|
3621
|
+
fail("unsupported_request", "Requested extra Responses output fields cannot be represented", `include[${index}]`);
|
|
3622
|
+
diagnostics.push({ param: `include[${index}]`, action: "omitted", reason: "target_encrypted_output_unavailable" });
|
|
3623
|
+
}
|
|
3624
|
+
});
|
|
3618
3625
|
if (body.prompt_cache_key !== undefined)
|
|
3619
3626
|
string(body.prompt_cache_key, "prompt_cache_key");
|
|
3620
3627
|
if (body.client_metadata !== undefined)
|
|
3621
3628
|
record(body.client_metadata, "client_metadata");
|
|
3622
3629
|
const model = string(body.model, "model", true);
|
|
3623
|
-
const diagnostics = [];
|
|
3624
3630
|
const input = atParam("input", () => typeof body.input === "string" ? [{ role: "user", content: body.input }] : list(body.input, "input"));
|
|
3625
3631
|
if (input.length > budget.maxItems)
|
|
3626
3632
|
fail("resource_limit", "Too many input items", "input");
|
|
@@ -3753,29 +3759,30 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
|
|
|
3753
3759
|
fail("tool_name_collision", "Historical tool kind conflicts with its current declaration");
|
|
3754
3760
|
return wire;
|
|
3755
3761
|
};
|
|
3756
|
-
const normalizeContent = (rawContent, role) => {
|
|
3762
|
+
const normalizeContent = (rawContent, role, path) => {
|
|
3757
3763
|
if (typeof rawContent === "string")
|
|
3758
3764
|
return rawContent;
|
|
3759
3765
|
const parts = list(rawContent, "message content");
|
|
3760
|
-
for (const rawPart of parts)
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
if (
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3766
|
+
for (const [index, rawPart] of parts.entries())
|
|
3767
|
+
atParam(path ? `${path}[${index}]` : messageParam, () => {
|
|
3768
|
+
const part = record(rawPart, "content part");
|
|
3769
|
+
if (part.type === "input_text" || part.type === "output_text" || part.type === "text")
|
|
3770
|
+
string(part.text, "content text");
|
|
3771
|
+
else if (part.type === "refusal" && role === "assistant") {
|
|
3772
|
+
string(part.refusal, "refusal");
|
|
3773
|
+
if (capabilities.preserveRefusalContent && protocol !== "chat_completions")
|
|
3774
|
+
fail("unsupported_content", "Target protocol cannot preserve refusal history authority");
|
|
3775
|
+
} else if (part.type === "input_image" && (role === "user" || role === "tool" && options.toolOutputImages)) {
|
|
3776
|
+
if (protocol !== "chat_completions")
|
|
3777
|
+
fail("unsupported_content", "Anthropic image conversion requires provider-specific URL/data support");
|
|
3778
|
+
string(part.image_url, "image_url", true);
|
|
3779
|
+
if (part.detail !== undefined && !["auto", "low", "high"].includes(String(part.detail)))
|
|
3780
|
+
fail("unsupported_content", "Unknown image detail", path ? `${path}[${index}].detail` : undefined);
|
|
3781
|
+
} else
|
|
3782
|
+
fail("unsupported_content", "Unsupported message content type");
|
|
3783
|
+
if (part.annotations !== undefined && list(part.annotations, "annotations").length)
|
|
3784
|
+
fail("unsupported_content", "Output annotations cannot be represented in input history");
|
|
3785
|
+
});
|
|
3779
3786
|
const normalized = contentNormalizer.normalizeResponsesMessageContent(parts.map((raw) => {
|
|
3780
3787
|
const part = raw;
|
|
3781
3788
|
return part.type === "refusal" ? { type: "output_text", text: part.refusal } : part;
|
|
@@ -3870,7 +3877,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
|
|
|
3870
3877
|
if (!callIds.has(id) || answered.has(id))
|
|
3871
3878
|
fail("invalid_tool_history", "Unmatched or duplicate tool result");
|
|
3872
3879
|
answered.add(id);
|
|
3873
|
-
const output = typeof item.output === "string" ? item.output : normalizeContent(item.output, "tool");
|
|
3880
|
+
const output = typeof item.output === "string" ? item.output : normalizeContent(item.output, "tool", `${messageParam}.output`);
|
|
3874
3881
|
addMessage({ role: "tool", tool_call_id: id, content: output, ...item.is_error === true ? { is_error: true } : {} });
|
|
3875
3882
|
return;
|
|
3876
3883
|
}
|
|
@@ -4471,6 +4478,38 @@ function textParts(raw, path, style, role) {
|
|
|
4471
4478
|
|
|
4472
4479
|
// packages/llms/src/protocol-session/gemini.ts
|
|
4473
4480
|
var signatureSeparator = "__thought__";
|
|
4481
|
+
function geminiInlineImage(image, path, budget) {
|
|
4482
|
+
const param = `${path}.image_url`;
|
|
4483
|
+
const url = bounded(string(image.url, "image URL", true), budget.maxRequestBytes, param);
|
|
4484
|
+
const match = /^data:([^;,]+);base64,([\s\S]*)$/.exec(url);
|
|
4485
|
+
if (!match || !["image/png", "image/jpeg", "image/webp"].includes(match[1]))
|
|
4486
|
+
fail("unsupported_content", "Gemini images require inline PNG, JPEG or WebP data", param);
|
|
4487
|
+
const data = match[2];
|
|
4488
|
+
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(/=+$/, ""))
|
|
4489
|
+
fail("invalid_payload", "Invalid image Base64 encoding", param);
|
|
4490
|
+
return { mimeType: match[1], data };
|
|
4491
|
+
}
|
|
4492
|
+
function geminiToolResponse(content, path, budget, diagnostics) {
|
|
4493
|
+
if (!Array.isArray(content) || !content.some((part) => part.type === "image_url"))
|
|
4494
|
+
return { response: { output: content } };
|
|
4495
|
+
if (content.length > budget.maxItems)
|
|
4496
|
+
fail("resource_limit", "Too many tool output parts", path);
|
|
4497
|
+
const parts = [];
|
|
4498
|
+
const output = content.map((part, index) => atParam(`${path}[${index}]`, () => {
|
|
4499
|
+
if (part.type === "text")
|
|
4500
|
+
return part;
|
|
4501
|
+
if (part.type !== "image_url")
|
|
4502
|
+
fail("unsupported_content", "Unsupported Gemini tool result content");
|
|
4503
|
+
const image = record(part.image_url, "tool image");
|
|
4504
|
+
const inlineData = geminiInlineImage(image, `${path}[${index}]`, budget);
|
|
4505
|
+
const displayName = `tool-image-${index}.${inlineData.mimeType.slice("image/".length)}`;
|
|
4506
|
+
parts.push({ inlineData: { ...inlineData, displayName } });
|
|
4507
|
+
if (image.detail !== undefined)
|
|
4508
|
+
diagnostics.push({ param: `${path}[${index}].detail`, action: "omitted", reason: "gemini_tool_image_detail_unavailable" });
|
|
4509
|
+
return { $ref: displayName };
|
|
4510
|
+
}));
|
|
4511
|
+
return { response: { output }, parts };
|
|
4512
|
+
}
|
|
4474
4513
|
function checkedSignature(value, maxBytes) {
|
|
4475
4514
|
const signature = bounded(string(value, "thoughtSignature", true), maxBytes, "thoughtSignature");
|
|
4476
4515
|
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(/=+$/, ""))
|
|
@@ -5001,7 +5040,31 @@ function validateCanonical(b, context) {
|
|
|
5001
5040
|
for (const [j, part] of t.content.entries()) {
|
|
5002
5041
|
const c = record(part, "part");
|
|
5003
5042
|
fields2(c, c.type === "input_image" ? ["type", "image_url", "detail"] : c.type === "refusal" ? ["type", "refusal"] : ["type", "text", "annotations"], `${p}.content[${j}]`, "unsupported_content");
|
|
5043
|
+
if (context.sourceProtocol === "responses" && context.targetProtocol === "gemini_generate_content" && t.role === "user" && c.type === "input_image") {
|
|
5044
|
+
atParam(`${p}.content[${j}].image_url`, () => string(c.image_url, "image URL", true));
|
|
5045
|
+
if (c.detail !== undefined && (typeof c.detail !== "string" || !["auto", "low", "high"].includes(c.detail)))
|
|
5046
|
+
fail("unsupported_content", "Unknown image detail", `${p}.content[${j}].detail`);
|
|
5047
|
+
}
|
|
5004
5048
|
}
|
|
5049
|
+
if (context.sourceProtocol === "responses" && context.targetProtocol === "gemini_generate_content" && Array.isArray(t.output))
|
|
5050
|
+
for (const [j, part] of t.output.entries())
|
|
5051
|
+
atParam(`${p}.output[${j}]`, () => {
|
|
5052
|
+
const c = record(part, "tool output part");
|
|
5053
|
+
fields2(c, c.type === "input_image" ? ["type", "image_url", "detail"] : ["type", "text", "annotations"], `${p}.output[${j}]`, "unsupported_content");
|
|
5054
|
+
if (c.type === "input_image") {
|
|
5055
|
+
atParam(`${p}.output[${j}].image_url`, () => string(c.image_url, "image URL", true));
|
|
5056
|
+
if (c.detail !== undefined && (typeof c.detail !== "string" || !["auto", "low", "high"].includes(c.detail)))
|
|
5057
|
+
fail("unsupported_content", "Unknown image detail", `${p}.output[${j}].detail`);
|
|
5058
|
+
} else if (c.type === "input_text" || c.type === "output_text" || c.type === "text")
|
|
5059
|
+
atParam(`${p}.output[${j}].text`, () => string(c.text, "tool output text"));
|
|
5060
|
+
else
|
|
5061
|
+
fail("unsupported_content", "Unsupported tool output content type", `${p}.output[${j}].type`);
|
|
5062
|
+
if (c.annotations !== undefined)
|
|
5063
|
+
atParam(`${p}.output[${j}].annotations`, () => {
|
|
5064
|
+
if (list(c.annotations, "annotations").length)
|
|
5065
|
+
fail("unsupported_content", "Output annotations cannot be represented in tool history");
|
|
5066
|
+
});
|
|
5067
|
+
});
|
|
5005
5068
|
}
|
|
5006
5069
|
const inspectTool = (raw, path, nested = false) => {
|
|
5007
5070
|
const t = record(raw, "tool");
|
|
@@ -5034,6 +5097,7 @@ function validateCanonical(b, context) {
|
|
|
5034
5097
|
}
|
|
5035
5098
|
function convertProtocolRequest(raw, context) {
|
|
5036
5099
|
const b = canonicalRequest(raw, context.sourceProtocol, context.model);
|
|
5100
|
+
const originalInput = b.input;
|
|
5037
5101
|
if (context.streaming !== undefined) {
|
|
5038
5102
|
bool(context.streaming, "context.streaming");
|
|
5039
5103
|
if (b.stream !== undefined && b.stream !== context.streaming)
|
|
@@ -5131,13 +5195,13 @@ function convertProtocolRequest(raw, context) {
|
|
|
5131
5195
|
const decodingTarget = target === "gemini_generate_content" ? "chat_completions" : target;
|
|
5132
5196
|
if (target === "gemini_generate_content" && reasoning)
|
|
5133
5197
|
b.reasoning = { ...reasoning, effort: undefined };
|
|
5134
|
-
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" });
|
|
5198
|
+
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" });
|
|
5135
5199
|
const diagnostics = decoded.diagnostics;
|
|
5136
5200
|
if (target === "gemini_generate_content" && decoded.body.parallel_tool_calls === true) {
|
|
5137
5201
|
delete decoded.body.parallel_tool_calls;
|
|
5138
5202
|
diagnostics.push({ param: "parallel_tool_calls", action: "omitted", reason: "gemini_parallel_tool_calls_allowed" });
|
|
5139
5203
|
}
|
|
5140
|
-
const body = target === "gemini_generate_content" ? renderGemini(decoded.body, context, geminiHistory?.parts, diagnostics) : decoded.body;
|
|
5204
|
+
const body = target === "gemini_generate_content" ? renderGemini(decoded.body, context, originalInput, geminiHistory?.parts, diagnostics) : decoded.body;
|
|
5141
5205
|
if (stop !== undefined)
|
|
5142
5206
|
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;
|
|
5143
5207
|
if (target === "anthropic_messages") {
|
|
@@ -5174,9 +5238,19 @@ function convertProtocolRequest(raw, context) {
|
|
|
5174
5238
|
diagnostics.push({ param: "reasoning", action: "mapped", reason: "explicit_verified_wire_policy" });
|
|
5175
5239
|
return { body, canonicalHistory: geminiHistory?.canonicalHistory(decoded.canonicalInput) ?? decoded.canonicalInput, diagnostics, toolNames: decoded.toolNames, streaming: b.stream === true };
|
|
5176
5240
|
}
|
|
5177
|
-
function renderGemini(chat, context, signedParts, diagnostics) {
|
|
5241
|
+
function renderGemini(chat, context, input, signedParts, diagnostics) {
|
|
5178
5242
|
const result = { contents: [] }, contents = result.contents, system = [], calls = new Map;
|
|
5179
5243
|
const nativeIds = new Set;
|
|
5244
|
+
const budget = limits(context.capabilities?.limits);
|
|
5245
|
+
const toolOutputParams = new Map;
|
|
5246
|
+
const userMessageParams = [];
|
|
5247
|
+
let userMessageIndex = 0;
|
|
5248
|
+
for (const [index, item] of (Array.isArray(input) ? input : []).entries()) {
|
|
5249
|
+
if (item.type === "function_call_output" || item.type === "custom_tool_call_output")
|
|
5250
|
+
toolOutputParams.set(item.call_id, `input[${index}].output`);
|
|
5251
|
+
if ((item.type === undefined || item.type === "message") && item.role === "user" || item.type === "agent_message")
|
|
5252
|
+
userMessageParams.push(`input[${index}].content`);
|
|
5253
|
+
}
|
|
5180
5254
|
const append = (role, parts) => {
|
|
5181
5255
|
const prev = contents.at(-1);
|
|
5182
5256
|
if (prev?.role === role)
|
|
@@ -5190,10 +5264,33 @@ function renderGemini(chat, context, signedParts, diagnostics) {
|
|
|
5190
5264
|
append("model", signed);
|
|
5191
5265
|
continue;
|
|
5192
5266
|
}
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5267
|
+
if (m.role === "tool") {
|
|
5268
|
+
const name = calls.get(string(m.tool_call_id, "tool_call_id"));
|
|
5269
|
+
if (!name)
|
|
5270
|
+
fail("invalid_tool_history", "Unknown tool result", "input");
|
|
5271
|
+
if (m.is_error === true)
|
|
5272
|
+
fail("unsupported_content", "Tool error authority cannot be represented by Gemini", "input");
|
|
5273
|
+
const { id } = context.sourceProtocol === "responses" ? decodeGeminiCallId(m.tool_call_id, budget.maxArgumentBytes) : { id: m.tool_call_id };
|
|
5274
|
+
const response = geminiToolResponse(m.content, toolOutputParams.get(m.tool_call_id) ?? "input", budget, diagnostics);
|
|
5275
|
+
append("user", [{ functionResponse: { id, name, ...response } }]);
|
|
5276
|
+
continue;
|
|
5277
|
+
}
|
|
5278
|
+
const contentParam = m.role === "user" ? userMessageParams[userMessageIndex++] ?? "input" : "input";
|
|
5279
|
+
if (Array.isArray(m.content) && m.content.some((p) => p.type === "image_url") && m.content.length > budget.maxItems)
|
|
5280
|
+
fail("resource_limit", "Too many message content parts", contentParam);
|
|
5281
|
+
const parts = m.content == null ? [] : typeof m.content === "string" ? [{ text: m.content }] : m.content.map((p, index) => {
|
|
5282
|
+
if (p.type === "text")
|
|
5283
|
+
return { text: p.text };
|
|
5284
|
+
const path = `${contentParam}[${index}]`;
|
|
5285
|
+
if (context.sourceProtocol !== "responses" || m.role !== "user" || p.type !== "image_url")
|
|
5286
|
+
fail("unsupported_content", "This content cannot cross to Gemini", contentParam);
|
|
5287
|
+
const image = record(p.image_url, "image"), part = { inlineData: geminiInlineImage(image, path, budget) };
|
|
5288
|
+
if (image.detail === "low" || image.detail === "high") {
|
|
5289
|
+
part.mediaResolution = { level: image.detail === "high" ? "MEDIA_RESOLUTION_HIGH" : "MEDIA_RESOLUTION_LOW" };
|
|
5290
|
+
diagnostics.push({ param: `${path}.detail`, action: "mapped", reason: "gemini_image_media_resolution" });
|
|
5291
|
+
} else if (image.detail === "auto")
|
|
5292
|
+
diagnostics.push({ param: `${path}.detail`, action: "omitted", reason: "gemini_image_detail_auto" });
|
|
5293
|
+
return part;
|
|
5197
5294
|
});
|
|
5198
5295
|
if (m.role === "system" || m.role === "developer") {
|
|
5199
5296
|
if (contents.length) {
|
|
@@ -5204,16 +5301,6 @@ function renderGemini(chat, context, signedParts, diagnostics) {
|
|
|
5204
5301
|
system.push(...parts);
|
|
5205
5302
|
continue;
|
|
5206
5303
|
}
|
|
5207
|
-
if (m.role === "tool") {
|
|
5208
|
-
const name = calls.get(string(m.tool_call_id, "tool_call_id"));
|
|
5209
|
-
if (!name)
|
|
5210
|
-
fail("invalid_tool_history", "Unknown tool result", "input");
|
|
5211
|
-
if (m.is_error === true)
|
|
5212
|
-
fail("unsupported_content", "Tool error authority cannot be represented by Gemini", "input");
|
|
5213
|
-
const { id } = context.sourceProtocol === "responses" ? decodeGeminiCallId(m.tool_call_id, limits(context.capabilities?.limits).maxArgumentBytes) : { id: m.tool_call_id };
|
|
5214
|
-
append("user", [{ functionResponse: { id, name, response: { output: m.content } } }]);
|
|
5215
|
-
continue;
|
|
5216
|
-
}
|
|
5217
5304
|
if (m.reasoning_content !== undefined)
|
|
5218
5305
|
parts.unshift({ text: m.reasoning_content, thought: true });
|
|
5219
5306
|
for (const c of m.tool_calls ?? []) {
|
|
@@ -7425,4 +7512,4 @@ export {
|
|
|
7425
7512
|
targetPath
|
|
7426
7513
|
};
|
|
7427
7514
|
|
|
7428
|
-
//# debugId=
|
|
7515
|
+
//# debugId=F228E39D1C0449DF64756E2164756E21
|