@jeffusion/bungee-core 6.8.1 → 6.9.0

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.
@@ -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
- if (body.include !== undefined && list(body.include, "include").some((field) => field !== "reasoning.encrypted_content"))
3617
- fail("unsupported_request", "Requested extra Responses output fields cannot be represented");
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
- const part = record(rawPart, "content part");
3762
- if (part.type === "input_text" || part.type === "output_text" || part.type === "text")
3763
- string(part.text, "content text");
3764
- else if (part.type === "refusal" && role === "assistant") {
3765
- string(part.refusal, "refusal");
3766
- if (capabilities.preserveRefusalContent && protocol !== "chat_completions")
3767
- fail("unsupported_content", "Target protocol cannot preserve refusal history authority");
3768
- } else if (part.type === "input_image" && role === "user") {
3769
- if (protocol !== "chat_completions")
3770
- fail("unsupported_content", "Anthropic image conversion requires provider-specific URL/data support");
3771
- string(part.image_url, "image_url", true);
3772
- if (part.detail !== undefined && !["auto", "low", "high"].includes(String(part.detail)))
3773
- fail("unsupported_content", "Unknown image detail");
3774
- } else
3775
- fail("unsupported_content", "Unsupported message content type");
3776
- if (part.annotations !== undefined && list(part.annotations, "annotations").length)
3777
- fail("unsupported_content", "Output annotations cannot be represented in input history");
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
- const parts = m.content == null ? [] : typeof m.content === "string" ? [{ text: m.content }] : m.content.map((p) => {
5194
- if (p.type !== "text")
5195
- fail("unsupported_content", "This content cannot cross to Gemini", "input");
5196
- return { text: p.text };
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=25B3D2AD452DB0F464756E2164756E21
7515
+ //# debugId=F228E39D1C0449DF64756E2164756E21