@jeffusion/bungee-core 6.7.3 → 6.8.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.
@@ -3565,7 +3565,7 @@ function definitionKey(value) {
3565
3565
  return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${definitionKey(value[key])}`).join(",")}}`;
3566
3566
  return JSON.stringify(value);
3567
3567
  }
3568
- function decodeResponsesRequest(raw, protocol, capabilities = {}) {
3568
+ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {}) {
3569
3569
  const body = record(raw, "Responses request");
3570
3570
  if (capabilities.agentMessageDelivery !== undefined && capabilities.agentMessageDelivery !== "plaintext")
3571
3571
  fail("invalid_payload", "Unknown agent message delivery policy", "agentMessageDelivery");
@@ -3832,12 +3832,16 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
3832
3832
  if (item.content != null)
3833
3833
  fail("unsupported_reasoning", "Reasoning content requires an explicit history mapping", `${messageParam}.content`);
3834
3834
  const summary = list(item.summary, "reasoning summary");
3835
- pendingReasoning += summary.map((part) => {
3835
+ const text = summary.map((part) => {
3836
3836
  const p = record(part, "reasoning summary");
3837
3837
  if (p.type !== "summary_text")
3838
3838
  fail("unsupported_reasoning", "Unknown reasoning summary");
3839
3839
  return string(p.text, "summary text");
3840
3840
  }).join("");
3841
+ if (options.preserveContentOrder)
3842
+ addMessage({ role: "assistant", content: null, reasoning_content: text, ...item.id === undefined ? {} : { id: string(item.id, "item.id", true) } });
3843
+ else
3844
+ pendingReasoning += text;
3841
3845
  return;
3842
3846
  }
3843
3847
  if (item.type === "function_call" || item.type === "custom_tool_call") {
@@ -3852,7 +3856,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
3852
3856
  argumentObject(args);
3853
3857
  const call = { id, type: "function", function: { name: resolve(item, custom), arguments: args } };
3854
3858
  const previous = chat.at(-1);
3855
- if (previous?.role === "assistant") {
3859
+ if (previous?.role === "assistant" && !options.preserveContentOrder) {
3856
3860
  previous.tool_calls = [...previous.tool_calls ?? [], call];
3857
3861
  if (pendingReasoning)
3858
3862
  previous.reasoning_content = pendingReasoning;
@@ -3876,6 +3880,8 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
3876
3880
  if (!["user", "assistant", "system", "developer"].includes(role))
3877
3881
  fail("unsupported_content", "Unknown message role");
3878
3882
  const message = { role, content: normalizeContent(item.content, role) };
3883
+ if (options.preserveContentOrder && item.id !== undefined)
3884
+ message.id = string(item.id, "item.id", true);
3879
3885
  if (pendingReasoning) {
3880
3886
  if (role !== "assistant")
3881
3887
  fail("unsupported_reasoning", "Reasoning must precede an assistant item");
@@ -4416,6 +4422,9 @@ function encodeResponsesResult(raw, protocol, model, toolNames = new Map, config
4416
4422
  fail("missing_terminal", "JSON response has no terminal result");
4417
4423
  return record(end.response, "Responses result");
4418
4424
  }
4425
+ // packages/llms/src/protocol-session/gemini.ts
4426
+ import { createHash } from "crypto";
4427
+
4419
4428
  // packages/llms/src/protocol-session/validation.ts
4420
4429
  function fields2(value, allowed, path, code = "unsupported_request") {
4421
4430
  for (const key of Object.keys(value))
@@ -4460,6 +4469,145 @@ function textParts(raw, path, style, role) {
4460
4469
  });
4461
4470
  }
4462
4471
 
4472
+ // packages/llms/src/protocol-session/gemini.ts
4473
+ var signatureSeparator = "__thought__";
4474
+ function checkedSignature(value, maxBytes) {
4475
+ const signature = bounded(string(value, "thoughtSignature", true), maxBytes, "thoughtSignature");
4476
+ 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(/=+$/, ""))
4477
+ fail("invalid_tool_arguments", "Invalid Gemini thought signature encoding", "thoughtSignature");
4478
+ return signature;
4479
+ }
4480
+ var envelopePrefix = "bungee_gemini:";
4481
+ var geminiEnvelopeBytes = (payloadBytes) => envelopePrefix.length + Math.ceil(payloadBytes * 4 / 3);
4482
+ var geminiTextHash = (text) => createHash("sha256").update(text).digest("base64url");
4483
+ function geminiSignatureItem(items, budget) {
4484
+ const payload = serialized({ version: 1, items }, budget.maxArgumentBytes, "Gemini signatures");
4485
+ return {
4486
+ type: "reasoning",
4487
+ id: `rs_gemini_${crypto.randomUUID().replaceAll("-", "")}`,
4488
+ summary: [],
4489
+ encrypted_content: bounded(envelopePrefix + Buffer.from(payload).toString("base64url"), budget.maxArgumentBytes, "Gemini signatures")
4490
+ };
4491
+ }
4492
+ function readGeminiSignatures(item, budget) {
4493
+ fields2(item, ["type", "id", "summary", "encrypted_content", "status"], "Gemini signatures", "unsupported_reasoning");
4494
+ if (item.type !== "reasoning" || list(item.summary, "summary").length)
4495
+ fail("unsupported_reasoning", "Invalid Gemini signature carrier");
4496
+ string(item.id, "signature item id", true);
4497
+ const opaque = bounded(string(item.encrypted_content, "encrypted_content", true), budget.maxArgumentBytes, "Gemini signatures");
4498
+ if (!opaque.startsWith(envelopePrefix))
4499
+ fail("unsupported_reasoning", "Native encrypted reasoning cannot be restored to Gemini");
4500
+ const encoded = opaque.slice(envelopePrefix.length), bytes = Buffer.from(encoded, "base64url");
4501
+ if (!encoded || bytes.toString("base64url") !== encoded)
4502
+ fail("unsupported_reasoning", "Invalid Gemini signature carrier encoding");
4503
+ let raw;
4504
+ try {
4505
+ raw = JSON.parse(bytes.toString("utf8"));
4506
+ } catch {
4507
+ fail("unsupported_reasoning", "Invalid Gemini signature carrier JSON");
4508
+ }
4509
+ const body = record(raw, "Gemini signatures");
4510
+ fields2(body, ["version", "items"], "Gemini signatures", "unsupported_reasoning");
4511
+ if (body.version !== 1)
4512
+ fail("unsupported_reasoning", "Unknown Gemini signature format");
4513
+ const ids = new Set;
4514
+ let partCount = 0;
4515
+ const result = list(body.items, "signature items").map((raw) => {
4516
+ const item = record(raw, "signature item");
4517
+ fields2(item, ["id", "thought", "parts"], "signature item", "unsupported_reasoning");
4518
+ const id = string(item.id, "signed item id", true);
4519
+ if (ids.has(id) || typeof item.thought !== "boolean")
4520
+ fail("unsupported_reasoning", "Invalid signed item identity");
4521
+ ids.add(id);
4522
+ const parts = list(item.parts, "signed parts").map((raw) => {
4523
+ if (++partCount > budget.maxItems)
4524
+ fail("resource_limit", "Too many signed Gemini parts");
4525
+ const part = record(raw, "signed part");
4526
+ fields2(part, ["length", "hash", "signature", "textAbsent"], "signed part", "unsupported_reasoning");
4527
+ const length = integer(part.length, "signed part length"), hash = string(part.hash, "signed part hash", true);
4528
+ if (!/^[A-Za-z0-9_-]{43}$/.test(hash))
4529
+ fail("unsupported_reasoning", "Invalid signed part hash");
4530
+ const signature = part.signature === undefined ? undefined : checkedSignature(part.signature, budget.maxArgumentBytes);
4531
+ if (part.textAbsent !== undefined && (part.textAbsent !== true || length !== 0 || signature === undefined))
4532
+ fail("unsupported_reasoning", "Invalid empty signed part");
4533
+ return { length, hash, ...signature === undefined ? {} : { signature }, ...part.textAbsent ? { textAbsent: true } : {} };
4534
+ });
4535
+ if (!parts.some((p) => p.signature !== undefined))
4536
+ fail("unsupported_reasoning", "Signature carrier has no signature");
4537
+ return { id, thought: item.thought, parts };
4538
+ });
4539
+ if (!result.length || result.length > budget.maxItems)
4540
+ fail("unsupported_reasoning", "Invalid signature item count");
4541
+ return result;
4542
+ }
4543
+ function restoreGeminiHistory(input, budget) {
4544
+ const original = typeof input === "string" ? [] : list(input, "input").map((raw) => record(raw, "input item"));
4545
+ const carriers = new Set, parts = new Map, ids = new Map;
4546
+ original.forEach((item, i) => {
4547
+ if (typeof item.id === "string") {
4548
+ if (ids.has(item.id))
4549
+ fail("invalid_tool_history", "Duplicate history item id", "input.id");
4550
+ ids.set(item.id, i);
4551
+ }
4552
+ });
4553
+ original.forEach((carrier, index) => {
4554
+ if (carrier.encrypted_content == null)
4555
+ return;
4556
+ const signed = readGeminiSignatures(carrier, budget);
4557
+ carriers.add(carrier);
4558
+ let previous = -1;
4559
+ for (const item of signed) {
4560
+ const at = ids.get(item.id);
4561
+ if (at === undefined || at >= index || at <= previous || parts.has(item.id))
4562
+ fail("unsupported_reasoning", "Signed history reference is missing or ambiguous");
4563
+ previous = at;
4564
+ const source = original[at];
4565
+ if (item.thought ? source.type !== "reasoning" || source.encrypted_content != null : source.role !== "assistant" || source.type !== undefined && source.type !== "message")
4566
+ fail("unsupported_reasoning", "Signed history item kind changed");
4567
+ const content = item.thought ? list(source.summary, "summary") : typeof source.content === "string" ? [{ type: "output_text", text: source.content }] : list(source.content, "content");
4568
+ const text = content.map((raw) => {
4569
+ const p = record(raw, "signed text");
4570
+ if (p.type !== (item.thought ? "summary_text" : "output_text") && !(p.type === "input_text" && !item.thought))
4571
+ fail("unsupported_reasoning", "Signed history content kind changed");
4572
+ return string(p.text, "signed text");
4573
+ }).join("");
4574
+ let offset = 0;
4575
+ const restored = item.parts.map((part) => {
4576
+ const value = text.slice(offset, offset + part.length);
4577
+ offset += part.length;
4578
+ if (value.length !== part.length || geminiTextHash(value) !== part.hash)
4579
+ fail("unsupported_reasoning", "Signed history text changed");
4580
+ return { ...part.textAbsent ? {} : { text: value }, ...item.thought ? { thought: true } : {}, ...part.signature === undefined ? {} : { thoughtSignature: part.signature } };
4581
+ });
4582
+ if (offset !== text.length)
4583
+ fail("unsupported_reasoning", "Signed history text was not fully consumed");
4584
+ parts.set(item.id, restored);
4585
+ }
4586
+ });
4587
+ return {
4588
+ input: typeof input === "string" ? input : original.filter((item) => !carriers.has(item)),
4589
+ parts,
4590
+ canonicalHistory(decoded) {
4591
+ if (!carriers.size)
4592
+ return decoded;
4593
+ let next = 0;
4594
+ return original.flatMap((item) => carriers.has(item) ? [item] : item.type === "additional_tools" ? [] : [decoded[next++]]);
4595
+ }
4596
+ };
4597
+ }
4598
+ function encodeGeminiCallId(id, signature, maxBytes) {
4599
+ if (id.includes(signatureSeparator))
4600
+ fail("invalid_tool_arguments", "Gemini call id contains the signature separator", "functionCall.id");
4601
+ return signature === undefined ? id : `${id}${signatureSeparator}${checkedSignature(signature, maxBytes)}`;
4602
+ }
4603
+ function decodeGeminiCallId(value, maxBytes) {
4604
+ const separator = value.indexOf(signatureSeparator);
4605
+ if (separator < 0)
4606
+ return { id: value };
4607
+ const id = string(value.slice(0, separator), "functionCall.id", true);
4608
+ return { id, thoughtSignature: checkedSignature(value.slice(separator + signatureSeparator.length), maxBytes) };
4609
+ }
4610
+
4463
4611
  // packages/llms/src/protocol-session/schema.ts
4464
4612
  function geminiSchemaToJson(raw, path, code = "unsupported_request", depth = 0) {
4465
4613
  if (depth > 64)
@@ -4893,6 +5041,9 @@ function convertProtocolRequest(raw, context) {
4893
5041
  b.stream = context.streaming;
4894
5042
  }
4895
5043
  validateCanonical(b, context);
5044
+ const geminiHistory = context.sourceProtocol === "responses" && context.targetProtocol === "gemini_generate_content" ? restoreGeminiHistory(b.input, limits(context.capabilities?.limits)) : undefined;
5045
+ if (geminiHistory)
5046
+ b.input = geminiHistory.input;
4896
5047
  const cap = { ...context.capabilities, agentMessageDelivery: context.agentMessageDelivery ?? context.capabilities?.agentMessageDelivery, omitOptionalWebSearch: true, preserveToolIdentityDescription: true, preserveRefusalContent: true }, policy = context.reasoningPolicy;
4897
5048
  const reasoning = b.reasoning === undefined ? undefined : record(b.reasoning, "reasoning");
4898
5049
  const sourceThinking = b.sourceThinking;
@@ -4918,10 +5069,10 @@ function convertProtocolRequest(raw, context) {
4918
5069
  if (sourceThinking !== undefined && reasoning?.effort === undefined && policy?.targetEffort !== undefined)
4919
5070
  mappedEffort = string(policy.targetEffort, "reasoningPolicy.targetEffort", true);
4920
5071
  const target = context.targetProtocol;
4921
- if (target === "gemini_generate_content" && reasoning?.effort !== undefined && policy?.geminiThinkingBudget === undefined && policy?.geminiThinkingLevel === undefined)
5072
+ if (target === "gemini_generate_content" && reasoning?.effort !== undefined && mappedEffort === undefined && policy?.geminiThinkingBudget === undefined && policy?.geminiThinkingLevel === undefined)
4922
5073
  fail("unsupported_reasoning", "Gemini requires an explicit thinking budget or level", "reasoning.effort");
4923
5074
  if (sourceThinking !== undefined) {
4924
- const represented = target === "responses" || target === "chat_completions" ? mappedEffort !== undefined : target === "anthropic_messages" ? mappedEffort !== undefined || policy?.anthropicThinkingMode !== undefined || policy?.anthropicThinkingBudget !== undefined : policy?.geminiThinkingBudget !== undefined || policy?.geminiThinkingLevel !== undefined;
5075
+ const represented = target === "responses" || target === "chat_completions" ? mappedEffort !== undefined : target === "anthropic_messages" ? mappedEffort !== undefined || policy?.anthropicThinkingMode !== undefined || policy?.anthropicThinkingBudget !== undefined : mappedEffort !== undefined || policy?.geminiThinkingBudget !== undefined || policy?.geminiThinkingLevel !== undefined;
4925
5076
  if (!represented)
4926
5077
  fail("unsupported_reasoning", "Source thinking mode needs an explicit target wire policy", "thinking");
4927
5078
  }
@@ -4980,9 +5131,13 @@ function convertProtocolRequest(raw, context) {
4980
5131
  const decodingTarget = target === "gemini_generate_content" ? "chat_completions" : target;
4981
5132
  if (target === "gemini_generate_content" && reasoning)
4982
5133
  b.reasoning = { ...reasoning, effort: undefined };
4983
- const decoded = decodeResponsesRequest(b, decodingTarget, { ...cap, reasoningHistory: target !== "anthropic_messages" && cap.reasoningHistory === true, reasoningEffort: target === "chat_completions" && cap.reasoningEffort === true, anthropicThinkingBudget: 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" });
4984
5135
  const diagnostics = decoded.diagnostics;
4985
- const body = target === "gemini_generate_content" ? renderGemini(decoded.body, cap.geminiJsonSchema === true) : decoded.body;
5136
+ if (target === "gemini_generate_content" && decoded.body.parallel_tool_calls === true) {
5137
+ delete decoded.body.parallel_tool_calls;
5138
+ diagnostics.push({ param: "parallel_tool_calls", action: "omitted", reason: "gemini_parallel_tool_calls_allowed" });
5139
+ }
5140
+ const body = target === "gemini_generate_content" ? renderGemini(decoded.body, context, geminiHistory?.parts, diagnostics) : decoded.body;
4986
5141
  if (stop !== undefined)
4987
5142
  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;
4988
5143
  if (target === "anthropic_messages") {
@@ -5004,18 +5159,24 @@ function convertProtocolRequest(raw, context) {
5004
5159
  body.thinking = { type: "enabled", budget_tokens: budget };
5005
5160
  }
5006
5161
  }
5007
- if (target === "gemini_generate_content" && (policy?.geminiThinkingBudget !== undefined || policy?.geminiThinkingLevel !== undefined)) {
5008
- if (policy.geminiThinkingBudget !== undefined && policy.geminiThinkingLevel !== undefined)
5162
+ if (target === "gemini_generate_content" && (mappedEffort !== undefined || policy?.geminiThinkingBudget !== undefined || policy?.geminiThinkingLevel !== undefined)) {
5163
+ const level = policy?.geminiThinkingLevel ?? mappedEffort;
5164
+ if (policy?.geminiThinkingBudget !== undefined && level !== undefined)
5009
5165
  fail("unsupported_reasoning", "Choose one Gemini thinking policy", "reasoningPolicy");
5010
- const thinkingConfig = policy.geminiThinkingBudget !== undefined ? { thinkingBudget: integer(policy.geminiThinkingBudget, "reasoningPolicy.geminiThinkingBudget", -1) } : { thinkingLevel: string(policy.geminiThinkingLevel, "reasoningPolicy.geminiThinkingLevel", true) };
5166
+ if (level !== undefined && !["minimal", "low", "medium", "high"].includes(level))
5167
+ fail("unsupported_reasoning", "Unknown Gemini thinking level", "reasoning.effort");
5168
+ if (mappedEffort !== undefined && policy?.geminiThinkingLevel !== undefined && mappedEffort !== policy.geminiThinkingLevel)
5169
+ fail("unsupported_reasoning", "Conflicting Gemini thinking levels", "reasoning.effort");
5170
+ const thinkingConfig = policy?.geminiThinkingBudget !== undefined ? { thinkingBudget: integer(policy.geminiThinkingBudget, "reasoningPolicy.geminiThinkingBudget", -1) } : { thinkingLevel: string(level, "reasoningPolicy.geminiThinkingLevel", true) };
5011
5171
  body.generationConfig = { ...record(body.generationConfig ?? {}, "generationConfig"), thinkingConfig };
5012
5172
  }
5013
5173
  if (mappedEffort !== undefined || sourceThinking !== undefined || policy?.anthropicThinkingBudget !== undefined)
5014
5174
  diagnostics.push({ param: "reasoning", action: "mapped", reason: "explicit_verified_wire_policy" });
5015
- return { body, canonicalHistory: decoded.canonicalInput, diagnostics, toolNames: decoded.toolNames, streaming: b.stream === true };
5175
+ return { body, canonicalHistory: geminiHistory?.canonicalHistory(decoded.canonicalInput) ?? decoded.canonicalInput, diagnostics, toolNames: decoded.toolNames, streaming: b.stream === true };
5016
5176
  }
5017
- function renderGemini(chat, jsonSchema) {
5177
+ function renderGemini(chat, context, signedParts, diagnostics) {
5018
5178
  const result = { contents: [] }, contents = result.contents, system = [], calls = new Map;
5179
+ const nativeIds = new Set;
5019
5180
  const append = (role, parts) => {
5020
5181
  const prev = contents.at(-1);
5021
5182
  if (prev?.role === role)
@@ -5024,14 +5185,22 @@ function renderGemini(chat, jsonSchema) {
5024
5185
  contents.push({ role, parts });
5025
5186
  };
5026
5187
  for (const m of chat.messages) {
5188
+ const signed = signedParts?.get(m.id);
5189
+ if (signed) {
5190
+ append("model", signed);
5191
+ continue;
5192
+ }
5027
5193
  const parts = m.content == null ? [] : typeof m.content === "string" ? [{ text: m.content }] : m.content.map((p) => {
5028
5194
  if (p.type !== "text")
5029
5195
  fail("unsupported_content", "This content cannot cross to Gemini", "input");
5030
5196
  return { text: p.text };
5031
5197
  });
5032
5198
  if (m.role === "system" || m.role === "developer") {
5033
- if (contents.length)
5034
- fail("unsupported_content", "Mid-conversation instructions cannot be relocated", "input");
5199
+ if (contents.length) {
5200
+ if (context.sourceProtocol !== "responses")
5201
+ fail("unsupported_content", "Mid-conversation instructions cannot be relocated", "input");
5202
+ diagnostics.push({ param: "input", action: "mapped", reason: "gemini_mid_conversation_instructions_consolidated" });
5203
+ }
5035
5204
  system.push(...parts);
5036
5205
  continue;
5037
5206
  }
@@ -5041,7 +5210,8 @@ function renderGemini(chat, jsonSchema) {
5041
5210
  fail("invalid_tool_history", "Unknown tool result", "input");
5042
5211
  if (m.is_error === true)
5043
5212
  fail("unsupported_content", "Tool error authority cannot be represented by Gemini", "input");
5044
- append("user", [{ functionResponse: { id: m.tool_call_id, name, response: { output: m.content } } }]);
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 } } }]);
5045
5215
  continue;
5046
5216
  }
5047
5217
  if (m.reasoning_content !== undefined)
@@ -5049,10 +5219,20 @@ function renderGemini(chat, jsonSchema) {
5049
5219
  for (const c of m.tool_calls ?? []) {
5050
5220
  const fn = record(c.function, "function");
5051
5221
  calls.set(c.id, fn.name);
5052
- parts.push({ functionCall: { id: c.id, name: fn.name, args: argumentObject(fn.arguments) } });
5222
+ const { id, thoughtSignature } = context.sourceProtocol === "responses" ? decodeGeminiCallId(c.id, limits(context.capabilities?.limits).maxArgumentBytes) : { id: c.id, thoughtSignature: undefined };
5223
+ if (nativeIds.has(id))
5224
+ fail("invalid_tool_history", "Duplicate decoded Gemini call id", "call_id");
5225
+ nativeIds.add(id);
5226
+ parts.push({ functionCall: { id, name: fn.name, args: argumentObject(fn.arguments) }, ...thoughtSignature === undefined ? {} : { thoughtSignature } });
5053
5227
  }
5054
5228
  append(m.role === "assistant" ? "model" : "user", parts);
5055
5229
  }
5230
+ if (chat.parallel_tool_calls !== undefined) {
5231
+ if (context.sourceProtocol !== "responses" || chat.parallel_tool_calls !== false)
5232
+ fail("unsupported_request", "Gemini cannot enforce parallel tool preference", "parallel_tool_calls");
5233
+ system.push({ text: "Call at most one tool per response." });
5234
+ diagnostics.push({ param: "parallel_tool_calls", action: "mapped", reason: "gemini_serial_tool_calls_prompt_only" });
5235
+ }
5056
5236
  if (system.length)
5057
5237
  result.systemInstruction = { parts: system };
5058
5238
  const generation = {};
@@ -5061,12 +5241,10 @@ function renderGemini(chat, jsonSchema) {
5061
5241
  generation[to] = chat[from];
5062
5242
  if (chat.metadata !== undefined)
5063
5243
  fail("unsupported_request", "Gemini has no compatible request metadata", "metadata");
5064
- if (chat.parallel_tool_calls !== undefined)
5065
- fail("unsupported_request", "Gemini cannot enforce parallel tool preference", "parallel_tool_calls");
5066
5244
  if (chat.response_format !== undefined) {
5067
5245
  const f = record(chat.response_format, "format");
5068
5246
  if (f.type === "json_schema") {
5069
- if (!jsonSchema)
5247
+ if (!context.capabilities?.geminiJsonSchema)
5070
5248
  fail("unsupported_request", "Target JSON schema capability is required", "text.format");
5071
5249
  const s = record(f.json_schema, "schema");
5072
5250
  if (s.strict === false)
@@ -5211,7 +5389,11 @@ function validateCanonicalResponse(raw, context, itemOnly = false) {
5211
5389
  fail("unsupported_content", "Output annotations cannot cross protocols", path);
5212
5390
  }
5213
5391
  } else if (t.type === "reasoning") {
5214
- if (t.encrypted_content != null || t.content != null)
5392
+ if (t.encrypted_content != null && context.sourceProtocol === "responses" && context.targetProtocol === "gemini_generate_content")
5393
+ readGeminiSignatures(t, limits(context.capabilities?.limits));
5394
+ else if (t.encrypted_content != null)
5395
+ fail("unsupported_reasoning", "Opaque reasoning cannot cross protocols", path);
5396
+ if (t.content != null)
5215
5397
  fail("unsupported_reasoning", "Opaque or raw reasoning output cannot cross protocols", path);
5216
5398
  for (const rawPart of list(t.summary, "summary")) {
5217
5399
  const p = record(rawPart, "summary");
@@ -5334,20 +5516,28 @@ function responseFromCanonical(raw, protocol, context) {
5334
5516
 
5335
5517
  class GeminiCanonicalDecoder {
5336
5518
  context;
5519
+ streaming;
5337
5520
  encoder;
5338
5521
  serial = 0;
5339
5522
  ended = false;
5340
5523
  tools = false;
5341
5524
  ids = new Set;
5342
- constructor(context, names) {
5525
+ sequence = 0;
5526
+ contentId;
5527
+ partCount = 0;
5528
+ replayBytes = Buffer.byteLength(JSON.stringify({ version: 1, items: [] }));
5529
+ signedItems = 0;
5530
+ signed = new Map;
5531
+ constructor(context, names, streaming = true) {
5343
5532
  this.context = context;
5344
- this.encoder = new ResponsesEventEncoder("chat_completions", context.responseModel ?? context.model, names, context.capabilities?.limits, { preserveContentOrder: true });
5533
+ this.streaming = streaming;
5534
+ this.encoder = new ResponsesEventEncoder("chat_completions", context.responseModel ?? context.model, names, context.capabilities?.limits, { preserveContentOrder: true, agentMessageDelivery: context.capabilities?.agentMessageDelivery });
5345
5535
  }
5346
5536
  push(raw) {
5347
5537
  const b = record(raw, "Gemini response");
5348
5538
  fields2(b, ["candidates", "usageMetadata", "modelVersion", "responseId", "promptFeedback", "error"], "", "unsupported_response");
5349
5539
  if (b.error !== undefined)
5350
- return this.encoder.push({ error: safeProtocolError() });
5540
+ return this.decorate(this.encoder.push({ error: safeProtocolError() }));
5351
5541
  const events = [];
5352
5542
  if (b.promptFeedback !== undefined) {
5353
5543
  const p = record(b.promptFeedback, "promptFeedback");
@@ -5365,34 +5555,69 @@ class GeminiCanonicalDecoder {
5365
5555
  fields2(c, ["index", "content", "finishReason", "safetyRatings", "finishMessage"], "candidates", "unsupported_response");
5366
5556
  if (c.index !== undefined && c.index !== 0)
5367
5557
  fail("unsupported_response", "Only candidate index zero is supported", "candidates.index");
5368
- if (c.content !== undefined) {
5558
+ if (c.content !== undefined && c.content !== null) {
5369
5559
  const content = record(c.content, "content");
5370
5560
  fields2(content, ["role", "parts"], "candidates.content", "unsupported_content");
5371
5561
  if (content.role !== undefined && content.role !== "model")
5372
5562
  fail("unsupported_response", "Response role must be model", "candidates.content.role");
5373
- const parts = list(content.parts, "parts");
5563
+ const parts = list(content.parts ?? [], "parts");
5374
5564
  if (this.ended && parts.length)
5375
5565
  fail("invalid_stream", "Content arrived after Gemini terminal");
5376
5566
  for (const [i, rawPart] of parts.entries()) {
5377
5567
  const p = record(rawPart, "part");
5378
5568
  fields2(p, ["text", "thought", "functionCall", "thoughtSignature"], "candidates.content.parts", "unsupported_content");
5379
- if (p.thoughtSignature !== undefined)
5569
+ if (p.thoughtSignature !== undefined && this.context.sourceProtocol !== "responses")
5380
5570
  fail("unsupported_reasoning", "Thought signatures cannot cross protocols", "candidates.content.parts.thoughtSignature");
5381
5571
  if (p.functionCall !== undefined) {
5382
5572
  if (p.text !== undefined || p.thought !== undefined)
5383
5573
  fail("unsupported_content", "Mixed Gemini part kinds", "candidates.content.parts");
5384
5574
  const f = record(p.functionCall, "functionCall");
5385
5575
  fields2(f, ["id", "name", "args"], "functionCall", "unsupported_tool");
5386
- const index = this.serial++, id = f.id === undefined ? `call_gemini_${index}` : string(f.id, "functionCall.id", true);
5387
- if (this.ids.has(id))
5576
+ const index = this.serial++, originalId = f.id === undefined ? `call_gemini_${crypto.randomUUID().replaceAll("-", "")}` : string(f.id, "functionCall.id", true);
5577
+ if (this.ids.has(originalId))
5388
5578
  fail("invalid_tool_arguments", "Duplicate Gemini call id", "functionCall.id");
5389
- this.ids.add(id);
5579
+ this.ids.add(originalId);
5580
+ const id = this.context.sourceProtocol === "responses" ? encodeGeminiCallId(originalId, p.thoughtSignature, limits(this.context.capabilities?.limits).maxArgumentBytes) : originalId;
5390
5581
  this.tools = true;
5391
5582
  events.push(...this.encoder.push({ choices: [{ delta: { tool_calls: [{ index, id, type: "function", function: { name: string(f.name, "functionCall.name", true), arguments: JSON.stringify(record(f.args ?? {}, "functionCall.args")) } }] } }] }));
5392
5583
  } else {
5393
5584
  if (p.thought !== undefined && typeof p.thought !== "boolean")
5394
5585
  fail("invalid_payload", "Thought flag must be boolean", "thought");
5395
- events.push(...this.encoder.push({ choices: [{ delta: { [p.thought === true ? "reasoning_content" : "content"]: string(p.text, `parts[${i}].text`) } }] }));
5586
+ const text = string(p.text ?? (p.thoughtSignature === undefined ? undefined : ""), `parts[${i}].text`);
5587
+ const output = this.encoder.push({ choices: [{ delta: { [p.thought === true ? "reasoning_content" : "content"]: text } }] });
5588
+ const added = output.find((e) => e.type === "response.output_item.added");
5589
+ if (added)
5590
+ this.contentId = record(added.item, "item").id;
5591
+ const id = this.contentId;
5592
+ let item = this.signed.get(id);
5593
+ if (!item) {
5594
+ item = { id, thought: p.thought === true, parts: [], hasSignature: false, metadataBytes: Buffer.byteLength(JSON.stringify({ id, thought: p.thought === true, parts: [] })) };
5595
+ this.signed.set(id, item);
5596
+ }
5597
+ const signature = p.thoughtSignature === undefined ? undefined : checkedSignature(p.thoughtSignature, limits(this.context.capabilities?.limits).maxArgumentBytes);
5598
+ const previous = item.parts.at(-1);
5599
+ const merge = this.streaming && signature === undefined && previous && previous.signature === undefined && !previous.textAbsent;
5600
+ const partBytes = Buffer.byteLength(JSON.stringify({ length: text.length + (merge ? previous.text.length : 0), hash: "x".repeat(43), ...signature === undefined ? {} : { signature }, ...p.text === undefined ? { textAbsent: true } : {} }));
5601
+ const metadataBytes = item.metadataBytes + partBytes - (merge ? previous.metadataBytes : 0) + (!merge && item.parts.length ? 1 : 0);
5602
+ const hasSignature = item.hasSignature || signature !== undefined;
5603
+ const replayBytes = this.replayBytes + (item.hasSignature ? metadataBytes - item.metadataBytes : hasSignature ? metadataBytes + (this.signedItems ? 1 : 0) : 0);
5604
+ const budget = limits(this.context.capabilities?.limits);
5605
+ if (hasSignature && geminiEnvelopeBytes(replayBytes) > Math.min(budget.maxArgumentBytes, budget.maxOutputBytes))
5606
+ fail("resource_limit", "Accumulated Gemini signatures exceed replay budget");
5607
+ if (hasSignature && !item.hasSignature)
5608
+ this.signedItems++;
5609
+ this.replayBytes = replayBytes;
5610
+ item.metadataBytes = metadataBytes;
5611
+ item.hasSignature = hasSignature;
5612
+ if (merge) {
5613
+ previous.text += text;
5614
+ previous.metadataBytes = partBytes;
5615
+ } else {
5616
+ if (++this.partCount > limits(this.context.capabilities?.limits).maxItems)
5617
+ fail("resource_limit", "Too many Gemini parts");
5618
+ item.parts.push({ text, metadataBytes: partBytes, ...signature === undefined ? {} : { signature }, ...p.text === undefined ? { textAbsent: true } : {} });
5619
+ }
5620
+ events.push(...output);
5396
5621
  }
5397
5622
  }
5398
5623
  }
@@ -5409,25 +5634,47 @@ class GeminiCanonicalDecoder {
5409
5634
  }
5410
5635
  if (b.usageMetadata !== undefined) {
5411
5636
  const u = record(b.usageMetadata, "usageMetadata");
5412
- fields2(u, ["promptTokenCount", "candidatesTokenCount", "totalTokenCount", "cachedContentTokenCount", "thoughtsTokenCount"], "usageMetadata", "unsupported_response");
5413
- for (const [k, v] of Object.entries(u))
5414
- integer(v, `usageMetadata.${k}`);
5415
- const input = u.promptTokenCount ?? 0, thoughts = u.thoughtsTokenCount ?? 0, output = (u.candidatesTokenCount ?? 0) + thoughts;
5637
+ for (const k of ["promptTokenCount", "candidatesTokenCount", "totalTokenCount", "cachedContentTokenCount", "thoughtsTokenCount", "toolUsePromptTokenCount"])
5638
+ if (u[k] !== undefined && u[k] !== null)
5639
+ integer(u[k], `usageMetadata.${k}`);
5640
+ const input = (u.promptTokenCount ?? 0) + (u.toolUsePromptTokenCount ?? 0), thoughts = u.thoughtsTokenCount ?? 0, output = (u.candidatesTokenCount ?? 0) + thoughts;
5416
5641
  if (u.totalTokenCount !== undefined && u.totalTokenCount !== input + output)
5417
5642
  fail("invalid_usage", "Gemini total does not match reported components", "usageMetadata.totalTokenCount");
5418
5643
  events.push(...this.encoder.push({ choices: [], usage: { prompt_tokens: input, completion_tokens: output, prompt_tokens_details: { cached_tokens: u.cachedContentTokenCount ?? 0 }, completion_tokens_details: { reasoning_tokens: thoughts } } }));
5419
5644
  }
5420
- return events;
5645
+ return this.decorate(events);
5646
+ }
5647
+ decorate(events) {
5648
+ const output = [];
5649
+ for (const event of events) {
5650
+ if (["response.completed", "response.incomplete", "response.failed"].includes(event.type)) {
5651
+ const signed = [...this.signed.values()].filter((item) => item.parts.some((p) => p.signature !== undefined));
5652
+ if (signed.length) {
5653
+ const budget = limits(this.context.capabilities?.limits), response = record(event.response, "response");
5654
+ const items = list(response.output, "output");
5655
+ if (items.length >= budget.maxItems)
5656
+ fail("resource_limit", "Too many Gemini output items");
5657
+ const item = geminiSignatureItem(signed.map((item) => ({ id: item.id, thought: item.thought, parts: item.parts.map((p) => ({ length: p.text.length, hash: geminiTextHash(p.text), ...p.signature === undefined ? {} : { signature: p.signature }, ...p.textAbsent ? { textAbsent: true } : {} })) })), budget);
5658
+ const body = { ...response, output: [...items, item] };
5659
+ serialized(body, budget.maxOutputBytes, "Gemini response");
5660
+ output.push({ type: "response.output_item.added", response_id: response.id, output_index: items.length, item }, { type: "response.output_item.done", response_id: response.id, output_index: items.length, item });
5661
+ output.push({ ...event, response: body });
5662
+ continue;
5663
+ }
5664
+ }
5665
+ output.push(event);
5666
+ }
5667
+ return output.map((event) => ({ ...event, sequence_number: this.sequence++ }));
5421
5668
  }
5422
5669
  finish() {
5423
- return this.encoder.finish();
5670
+ return this.decorate(this.encoder.finish());
5424
5671
  }
5425
5672
  }
5426
5673
  function canonicalResponse(raw, context, names) {
5427
5674
  if (context.targetProtocol === "responses")
5428
5675
  return validateCanonicalResponse(raw, context);
5429
5676
  if (context.targetProtocol === "gemini_generate_content") {
5430
- const decoder = new GeminiCanonicalDecoder(context, names);
5677
+ const decoder = new GeminiCanonicalDecoder(context, names, false);
5431
5678
  const events = [...decoder.push(raw), ...decoder.finish()];
5432
5679
  return record(events.at(-1)?.response, "response");
5433
5680
  }
@@ -5809,7 +6056,7 @@ class CanonicalStreamRenderer {
5809
6056
  var LLM_PROTOCOLS = ["responses", "chat_completions", "anthropic_messages", "gemini_generate_content"];
5810
6057
 
5811
6058
  // packages/llms/src/protocol-session/index.ts
5812
- var PROTOCOL_CONVERSION_RULES_VERSION = "1.0.1";
6059
+ var PROTOCOL_CONVERSION_RULES_VERSION = "1.0.4";
5813
6060
  function describeProtocolConversion() {
5814
6061
  return {
5815
6062
  rulesVersion: PROTOCOL_CONVERSION_RULES_VERSION,
@@ -5817,7 +6064,7 @@ function describeProtocolConversion() {
5817
6064
  matrix: LLM_PROTOCOLS.flatMap((sourceProtocol) => LLM_PROTOCOLS.map((targetProtocol) => ({
5818
6065
  sourceProtocol,
5819
6066
  targetProtocol,
5820
- mode: sourceProtocol === targetProtocol ? "passthrough" : sourceProtocol === "responses" && targetProtocol === "gemini_generate_content" || sourceProtocol === "gemini_generate_content" && targetProtocol === "responses" ? "unsupported" : "convert"
6067
+ mode: sourceProtocol === targetProtocol ? "passthrough" : sourceProtocol === "gemini_generate_content" && targetProtocol === "responses" ? "unsupported" : "convert"
5821
6068
  })))
5822
6069
  };
5823
6070
  }
@@ -5835,9 +6082,9 @@ function createProtocolSession(context) {
5835
6082
  fail("unsupported_protocol", "Unknown protocol");
5836
6083
  if (typeof context.model !== "string" || !context.model)
5837
6084
  fail("invalid_payload", "Target model is required", "model");
5838
- const unsupported = context.sourceProtocol === "responses" && context.targetProtocol === "gemini_generate_content" || context.sourceProtocol === "gemini_generate_content" && context.targetProtocol === "responses";
6085
+ const unsupported = context.sourceProtocol === "gemini_generate_content" && context.targetProtocol === "responses";
5839
6086
  if (unsupported)
5840
- fail("unsupported_protocol_pair", "Responses and Gemini direct conversion is not supported");
6087
+ fail("unsupported_protocol_pair", "Gemini to Responses conversion is not supported");
5841
6088
  const same = context.sourceProtocol === context.targetProtocol;
5842
6089
  let disposed = false, poisoned = false, requested = false, mode, finished = false, passTerminal = false, passAnthropicStopped = false;
5843
6090
  let names = new Map;
@@ -6076,7 +6323,7 @@ function protocolRequestModel(protocol, body, url) {
6076
6323
  return model;
6077
6324
  }
6078
6325
  // plugins/llm-protocol-adapter/server/relay.ts
6079
- import { createHash } from "crypto";
6326
+ import { createHash as createHash2 } from "crypto";
6080
6327
 
6081
6328
  // plugins/llm-protocol-adapter/server/history.ts
6082
6329
  function serializeHistory2(value) {
@@ -6323,7 +6570,7 @@ async function relayPayload(result, param, signal) {
6323
6570
  }
6324
6571
 
6325
6572
  // plugins/llm-protocol-adapter/server/relay.ts
6326
- var hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
6573
+ var hash = (value) => createHash2("sha256").update(JSON.stringify(value)).digest("hex");
6327
6574
  function rejection(param, code, status = 502) {
6328
6575
  return new DataAdmissionError(status, code, undefined, { param, message: "Agent payload relay is unavailable" });
6329
6576
  }
@@ -6529,9 +6776,10 @@ class AgentRelay {
6529
6776
  }
6530
6777
 
6531
6778
  // plugins/llm-protocol-adapter/server/service.ts
6532
- var REASONING_RULES_VERSION = "2026-10-24.1";
6779
+ var REASONING_RULES_VERSION = "2026-10-27.1";
6533
6780
  var concrete = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max"]);
6534
- var effortProtocols = new Set(["responses", "chat_completions", "anthropic_messages"]);
6781
+ var effortProtocols = new Set(["responses", "chat_completions", "anthropic_messages", "gemini_generate_content"]);
6782
+ var geminiEfforts = new Set(["minimal", "low", "medium", "high"]);
6535
6783
  var anthropicEfforts = new Set(["low", "medium", "high", "xhigh", "max"]);
6536
6784
  function identityError(code, message) {
6537
6785
  throw new ResponsesCodecError(code, message, "modelIdentity");
@@ -6556,7 +6804,7 @@ function resolveCapabilities(catalog, context) {
6556
6804
  if (!info)
6557
6805
  return null;
6558
6806
  const restrictions = context.restrictions, reasoningAllowed = info.reasoning && restrictions?.reasoning !== false;
6559
- const allowed = context.targetProtocol === "anthropic_messages" ? anthropicEfforts : concrete;
6807
+ const allowed = context.targetProtocol === "anthropic_messages" ? anthropicEfforts : context.targetProtocol === "gemini_generate_content" ? geminiEfforts : concrete;
6560
6808
  const candidates = reasoningAllowed && effortProtocols.has(context.targetProtocol) && info.reasoningOptionsStatus === "known" ? info.reasoningOptions?.flatMap((option) => option.type === "effort" ? option.values.filter((value) => typeof value === "string" && allowed.has(value)) : []) ?? [] : [];
6561
6809
  const supportedEfforts = [...new Set(candidates)];
6562
6810
  const wirePolicy = supportedEfforts.length ? { effortMap: Object.fromEntries(supportedEfforts.map((value) => [value, value])) } : null;
@@ -6974,6 +7222,10 @@ var manifest_default = {
6974
7222
  "responses",
6975
7223
  "anthropic_messages"
6976
7224
  ],
7225
+ [
7226
+ "responses",
7227
+ "gemini_generate_content"
7228
+ ],
6977
7229
  [
6978
7230
  "chat_completions",
6979
7231
  "responses"
@@ -7062,7 +7314,10 @@ function protocolForPath(path) {
7062
7314
  }
7063
7315
  function targetPath(path, protocol, model, streaming) {
7064
7316
  const suffix = protocol === "responses" ? "/responses" : protocol === "chat_completions" ? "/chat/completions" : protocol === "anthropic_messages" ? "/messages" : `/models/${encodeURIComponent(model)}:${streaming ? "streamGenerateContent" : "generateContent"}`;
7065
- return path.replace(/\/(?:responses|chat\/completions|messages|models\/[^/]+:(?:streamGenerateContent|generateContent))$/, suffix);
7317
+ const prefix = path.replace(/\/(?:responses|chat\/completions|messages|models\/[^/]+:(?:streamGenerateContent|generateContent))$/, "");
7318
+ if (prefix === path)
7319
+ return path;
7320
+ return (protocol === "gemini_generate_content" ? prefix.replace(/\/v1(?:beta|alpha)?$/, "") + "/v1beta" : prefix) + suffix;
7066
7321
  }
7067
7322
  var server_default = definePlugin(class {
7068
7323
  options;
@@ -7121,6 +7376,7 @@ var server_default = definePlugin(class {
7121
7376
  targetProtocol: this.options.targetProtocol,
7122
7377
  model,
7123
7378
  modelIdentity: readModelIdentity(ctx.annotations),
7379
+ ...source === "responses" && this.options.targetProtocol === "gemini_generate_content" ? { agentMessageDelivery: "plaintext" } : {},
7124
7380
  ...source === "gemini_generate_content" ? { streaming: ctx.url.pathname.endsWith(":streamGenerateContent") } : {}
7125
7381
  });
7126
7382
  const result = session.convertRequest(ctx.body);
@@ -7169,4 +7425,4 @@ export {
7169
7425
  targetPath
7170
7426
  };
7171
7427
 
7172
- //# debugId=0868FA94A110E88F64756E2164756E21
7428
+ //# debugId=25B3D2AD452DB0F464756E2164756E21