@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.
@@ -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
- if (body.include !== undefined && list(body.include, "include").some((field) => field !== "reasoning.encrypted_content"))
3431
- fail("unsupported_request", "Requested extra Responses output fields cannot be represented");
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
- const part = record(rawPart, "content part");
3576
- if (part.type === "input_text" || part.type === "output_text" || part.type === "text")
3577
- string(part.text, "content text");
3578
- else if (part.type === "refusal" && role === "assistant") {
3579
- string(part.refusal, "refusal");
3580
- if (capabilities.preserveRefusalContent && protocol !== "chat_completions")
3581
- fail("unsupported_content", "Target protocol cannot preserve refusal history authority");
3582
- } else if (part.type === "input_image" && role === "user") {
3583
- if (protocol !== "chat_completions")
3584
- fail("unsupported_content", "Anthropic image conversion requires provider-specific URL/data support");
3585
- string(part.image_url, "image_url", true);
3586
- if (part.detail !== undefined && !["auto", "low", "high"].includes(String(part.detail)))
3587
- fail("unsupported_content", "Unknown image detail");
3588
- } else
3589
- fail("unsupported_content", "Unsupported message content type");
3590
- if (part.annotations !== undefined && list(part.annotations, "annotations").length)
3591
- fail("unsupported_content", "Output annotations cannot be represented in input history");
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
- const parts = m.content == null ? [] : typeof m.content === "string" ? [{ text: m.content }] : m.content.map((p) => {
5008
- if (p.type !== "text")
5009
- fail("unsupported_content", "This content cannot cross to Gemini", "input");
5010
- return { text: p.text };
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=165F7F1DADB9687864756E2164756E21
6822
+ //# debugId=529D4667BD09A6C364756E2164756E21