@jeffusion/bungee-core 6.7.3 → 6.8.1

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.
@@ -3379,7 +3379,7 @@ function definitionKey(value) {
3379
3379
  return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${definitionKey(value[key])}`).join(",")}}`;
3380
3380
  return JSON.stringify(value);
3381
3381
  }
3382
- function decodeResponsesRequest(raw, protocol, capabilities = {}) {
3382
+ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {}) {
3383
3383
  const body = record(raw, "Responses request");
3384
3384
  if (capabilities.agentMessageDelivery !== undefined && capabilities.agentMessageDelivery !== "plaintext")
3385
3385
  fail("invalid_payload", "Unknown agent message delivery policy", "agentMessageDelivery");
@@ -3646,12 +3646,16 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
3646
3646
  if (item.content != null)
3647
3647
  fail("unsupported_reasoning", "Reasoning content requires an explicit history mapping", `${messageParam}.content`);
3648
3648
  const summary = list(item.summary, "reasoning summary");
3649
- pendingReasoning += summary.map((part) => {
3649
+ const text = summary.map((part) => {
3650
3650
  const p = record(part, "reasoning summary");
3651
3651
  if (p.type !== "summary_text")
3652
3652
  fail("unsupported_reasoning", "Unknown reasoning summary");
3653
3653
  return string(p.text, "summary text");
3654
3654
  }).join("");
3655
+ if (options.preserveContentOrder)
3656
+ addMessage({ role: "assistant", content: null, reasoning_content: text, ...item.id === undefined ? {} : { id: string(item.id, "item.id", true) } });
3657
+ else
3658
+ pendingReasoning += text;
3655
3659
  return;
3656
3660
  }
3657
3661
  if (item.type === "function_call" || item.type === "custom_tool_call") {
@@ -3666,7 +3670,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
3666
3670
  argumentObject(args);
3667
3671
  const call = { id, type: "function", function: { name: resolve(item, custom), arguments: args } };
3668
3672
  const previous = chat.at(-1);
3669
- if (previous?.role === "assistant") {
3673
+ if (previous?.role === "assistant" && !options.preserveContentOrder) {
3670
3674
  previous.tool_calls = [...previous.tool_calls ?? [], call];
3671
3675
  if (pendingReasoning)
3672
3676
  previous.reasoning_content = pendingReasoning;
@@ -3690,6 +3694,8 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
3690
3694
  if (!["user", "assistant", "system", "developer"].includes(role))
3691
3695
  fail("unsupported_content", "Unknown message role");
3692
3696
  const message = { role, content: normalizeContent(item.content, role) };
3697
+ if (options.preserveContentOrder && item.id !== undefined)
3698
+ message.id = string(item.id, "item.id", true);
3693
3699
  if (pendingReasoning) {
3694
3700
  if (role !== "assistant")
3695
3701
  fail("unsupported_reasoning", "Reasoning must precede an assistant item");
@@ -4230,6 +4236,9 @@ function encodeResponsesResult(raw, protocol, model, toolNames = new Map, config
4230
4236
  fail("missing_terminal", "JSON response has no terminal result");
4231
4237
  return record(end.response, "Responses result");
4232
4238
  }
4239
+ // packages/llms/src/protocol-session/gemini.ts
4240
+ import { createHash } from "crypto";
4241
+
4233
4242
  // packages/llms/src/protocol-session/validation.ts
4234
4243
  function fields2(value, allowed, path, code = "unsupported_request") {
4235
4244
  for (const key of Object.keys(value))
@@ -4274,6 +4283,145 @@ function textParts(raw, path, style, role) {
4274
4283
  });
4275
4284
  }
4276
4285
 
4286
+ // packages/llms/src/protocol-session/gemini.ts
4287
+ var signatureSeparator = "__thought__";
4288
+ function checkedSignature(value, maxBytes) {
4289
+ const signature = bounded(string(value, "thoughtSignature", true), maxBytes, "thoughtSignature");
4290
+ 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(/=+$/, ""))
4291
+ fail("invalid_tool_arguments", "Invalid Gemini thought signature encoding", "thoughtSignature");
4292
+ return signature;
4293
+ }
4294
+ var envelopePrefix = "bungee_gemini:";
4295
+ var geminiEnvelopeBytes = (payloadBytes) => envelopePrefix.length + Math.ceil(payloadBytes * 4 / 3);
4296
+ var geminiTextHash = (text) => createHash("sha256").update(text).digest("base64url");
4297
+ function geminiSignatureItem(items, budget) {
4298
+ const payload = serialized({ version: 1, items }, budget.maxArgumentBytes, "Gemini signatures");
4299
+ return {
4300
+ type: "reasoning",
4301
+ id: `rs_gemini_${crypto.randomUUID().replaceAll("-", "")}`,
4302
+ summary: [],
4303
+ encrypted_content: bounded(envelopePrefix + Buffer.from(payload).toString("base64url"), budget.maxArgumentBytes, "Gemini signatures")
4304
+ };
4305
+ }
4306
+ function readGeminiSignatures(item, budget) {
4307
+ fields2(item, ["type", "id", "summary", "encrypted_content", "status"], "Gemini signatures", "unsupported_reasoning");
4308
+ if (item.type !== "reasoning" || list(item.summary, "summary").length)
4309
+ fail("unsupported_reasoning", "Invalid Gemini signature carrier");
4310
+ string(item.id, "signature item id", true);
4311
+ const opaque = bounded(string(item.encrypted_content, "encrypted_content", true), budget.maxArgumentBytes, "Gemini signatures");
4312
+ if (!opaque.startsWith(envelopePrefix))
4313
+ fail("unsupported_reasoning", "Native encrypted reasoning cannot be restored to Gemini");
4314
+ const encoded = opaque.slice(envelopePrefix.length), bytes = Buffer.from(encoded, "base64url");
4315
+ if (!encoded || bytes.toString("base64url") !== encoded)
4316
+ fail("unsupported_reasoning", "Invalid Gemini signature carrier encoding");
4317
+ let raw;
4318
+ try {
4319
+ raw = JSON.parse(bytes.toString("utf8"));
4320
+ } catch {
4321
+ fail("unsupported_reasoning", "Invalid Gemini signature carrier JSON");
4322
+ }
4323
+ const body = record(raw, "Gemini signatures");
4324
+ fields2(body, ["version", "items"], "Gemini signatures", "unsupported_reasoning");
4325
+ if (body.version !== 1)
4326
+ fail("unsupported_reasoning", "Unknown Gemini signature format");
4327
+ const ids = new Set;
4328
+ let partCount = 0;
4329
+ const result = list(body.items, "signature items").map((raw) => {
4330
+ const item = record(raw, "signature item");
4331
+ fields2(item, ["id", "thought", "parts"], "signature item", "unsupported_reasoning");
4332
+ const id = string(item.id, "signed item id", true);
4333
+ if (ids.has(id) || typeof item.thought !== "boolean")
4334
+ fail("unsupported_reasoning", "Invalid signed item identity");
4335
+ ids.add(id);
4336
+ const parts = list(item.parts, "signed parts").map((raw) => {
4337
+ if (++partCount > budget.maxItems)
4338
+ fail("resource_limit", "Too many signed Gemini parts");
4339
+ const part = record(raw, "signed part");
4340
+ fields2(part, ["length", "hash", "signature", "textAbsent"], "signed part", "unsupported_reasoning");
4341
+ const length = integer(part.length, "signed part length"), hash = string(part.hash, "signed part hash", true);
4342
+ if (!/^[A-Za-z0-9_-]{43}$/.test(hash))
4343
+ fail("unsupported_reasoning", "Invalid signed part hash");
4344
+ const signature = part.signature === undefined ? undefined : checkedSignature(part.signature, budget.maxArgumentBytes);
4345
+ if (part.textAbsent !== undefined && (part.textAbsent !== true || length !== 0 || signature === undefined))
4346
+ fail("unsupported_reasoning", "Invalid empty signed part");
4347
+ return { length, hash, ...signature === undefined ? {} : { signature }, ...part.textAbsent ? { textAbsent: true } : {} };
4348
+ });
4349
+ if (!parts.some((p) => p.signature !== undefined))
4350
+ fail("unsupported_reasoning", "Signature carrier has no signature");
4351
+ return { id, thought: item.thought, parts };
4352
+ });
4353
+ if (!result.length || result.length > budget.maxItems)
4354
+ fail("unsupported_reasoning", "Invalid signature item count");
4355
+ return result;
4356
+ }
4357
+ function restoreGeminiHistory(input, budget) {
4358
+ const original = typeof input === "string" ? [] : list(input, "input").map((raw) => record(raw, "input item"));
4359
+ const carriers = new Set, parts = new Map, ids = new Map;
4360
+ original.forEach((item, i) => {
4361
+ if (typeof item.id === "string") {
4362
+ if (ids.has(item.id))
4363
+ fail("invalid_tool_history", "Duplicate history item id", "input.id");
4364
+ ids.set(item.id, i);
4365
+ }
4366
+ });
4367
+ original.forEach((carrier, index) => {
4368
+ if (carrier.encrypted_content == null)
4369
+ return;
4370
+ const signed = readGeminiSignatures(carrier, budget);
4371
+ carriers.add(carrier);
4372
+ let previous = -1;
4373
+ for (const item of signed) {
4374
+ const at = ids.get(item.id);
4375
+ if (at === undefined || at >= index || at <= previous || parts.has(item.id))
4376
+ fail("unsupported_reasoning", "Signed history reference is missing or ambiguous");
4377
+ previous = at;
4378
+ const source = original[at];
4379
+ if (item.thought ? source.type !== "reasoning" || source.encrypted_content != null : source.role !== "assistant" || source.type !== undefined && source.type !== "message")
4380
+ fail("unsupported_reasoning", "Signed history item kind changed");
4381
+ const content = item.thought ? list(source.summary, "summary") : typeof source.content === "string" ? [{ type: "output_text", text: source.content }] : list(source.content, "content");
4382
+ const text = content.map((raw) => {
4383
+ const p = record(raw, "signed text");
4384
+ if (p.type !== (item.thought ? "summary_text" : "output_text") && !(p.type === "input_text" && !item.thought))
4385
+ fail("unsupported_reasoning", "Signed history content kind changed");
4386
+ return string(p.text, "signed text");
4387
+ }).join("");
4388
+ let offset = 0;
4389
+ const restored = item.parts.map((part) => {
4390
+ const value = text.slice(offset, offset + part.length);
4391
+ offset += part.length;
4392
+ if (value.length !== part.length || geminiTextHash(value) !== part.hash)
4393
+ fail("unsupported_reasoning", "Signed history text changed");
4394
+ return { ...part.textAbsent ? {} : { text: value }, ...item.thought ? { thought: true } : {}, ...part.signature === undefined ? {} : { thoughtSignature: part.signature } };
4395
+ });
4396
+ if (offset !== text.length)
4397
+ fail("unsupported_reasoning", "Signed history text was not fully consumed");
4398
+ parts.set(item.id, restored);
4399
+ }
4400
+ });
4401
+ return {
4402
+ input: typeof input === "string" ? input : original.filter((item) => !carriers.has(item)),
4403
+ parts,
4404
+ canonicalHistory(decoded) {
4405
+ if (!carriers.size)
4406
+ return decoded;
4407
+ let next = 0;
4408
+ return original.flatMap((item) => carriers.has(item) ? [item] : item.type === "additional_tools" ? [] : [decoded[next++]]);
4409
+ }
4410
+ };
4411
+ }
4412
+ function encodeGeminiCallId(id, signature, maxBytes) {
4413
+ if (id.includes(signatureSeparator))
4414
+ fail("invalid_tool_arguments", "Gemini call id contains the signature separator", "functionCall.id");
4415
+ return signature === undefined ? id : `${id}${signatureSeparator}${checkedSignature(signature, maxBytes)}`;
4416
+ }
4417
+ function decodeGeminiCallId(value, maxBytes) {
4418
+ const separator = value.indexOf(signatureSeparator);
4419
+ if (separator < 0)
4420
+ return { id: value };
4421
+ const id = string(value.slice(0, separator), "functionCall.id", true);
4422
+ return { id, thoughtSignature: checkedSignature(value.slice(separator + signatureSeparator.length), maxBytes) };
4423
+ }
4424
+
4277
4425
  // packages/llms/src/protocol-session/schema.ts
4278
4426
  function geminiSchemaToJson(raw, path, code = "unsupported_request", depth = 0) {
4279
4427
  if (depth > 64)
@@ -4707,6 +4855,9 @@ function convertProtocolRequest(raw, context) {
4707
4855
  b.stream = context.streaming;
4708
4856
  }
4709
4857
  validateCanonical(b, context);
4858
+ const geminiHistory = context.sourceProtocol === "responses" && context.targetProtocol === "gemini_generate_content" ? restoreGeminiHistory(b.input, limits(context.capabilities?.limits)) : undefined;
4859
+ if (geminiHistory)
4860
+ b.input = geminiHistory.input;
4710
4861
  const cap = { ...context.capabilities, agentMessageDelivery: context.agentMessageDelivery ?? context.capabilities?.agentMessageDelivery, omitOptionalWebSearch: true, preserveToolIdentityDescription: true, preserveRefusalContent: true }, policy = context.reasoningPolicy;
4711
4862
  const reasoning = b.reasoning === undefined ? undefined : record(b.reasoning, "reasoning");
4712
4863
  const sourceThinking = b.sourceThinking;
@@ -4732,10 +4883,10 @@ function convertProtocolRequest(raw, context) {
4732
4883
  if (sourceThinking !== undefined && reasoning?.effort === undefined && policy?.targetEffort !== undefined)
4733
4884
  mappedEffort = string(policy.targetEffort, "reasoningPolicy.targetEffort", true);
4734
4885
  const target = context.targetProtocol;
4735
- if (target === "gemini_generate_content" && reasoning?.effort !== undefined && policy?.geminiThinkingBudget === undefined && policy?.geminiThinkingLevel === undefined)
4886
+ if (target === "gemini_generate_content" && reasoning?.effort !== undefined && mappedEffort === undefined && policy?.geminiThinkingBudget === undefined && policy?.geminiThinkingLevel === undefined)
4736
4887
  fail("unsupported_reasoning", "Gemini requires an explicit thinking budget or level", "reasoning.effort");
4737
4888
  if (sourceThinking !== undefined) {
4738
- 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;
4889
+ 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;
4739
4890
  if (!represented)
4740
4891
  fail("unsupported_reasoning", "Source thinking mode needs an explicit target wire policy", "thinking");
4741
4892
  }
@@ -4794,9 +4945,13 @@ function convertProtocolRequest(raw, context) {
4794
4945
  const decodingTarget = target === "gemini_generate_content" ? "chat_completions" : target;
4795
4946
  if (target === "gemini_generate_content" && reasoning)
4796
4947
  b.reasoning = { ...reasoning, effort: undefined };
4797
- const decoded = decodeResponsesRequest(b, decodingTarget, { ...cap, reasoningHistory: target !== "anthropic_messages" && cap.reasoningHistory === true, reasoningEffort: target === "chat_completions" && cap.reasoningEffort === true, anthropicThinkingBudget: 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" });
4798
4949
  const diagnostics = decoded.diagnostics;
4799
- const body = target === "gemini_generate_content" ? renderGemini(decoded.body, cap.geminiJsonSchema === true) : decoded.body;
4950
+ if (target === "gemini_generate_content" && decoded.body.parallel_tool_calls === true) {
4951
+ delete decoded.body.parallel_tool_calls;
4952
+ diagnostics.push({ param: "parallel_tool_calls", action: "omitted", reason: "gemini_parallel_tool_calls_allowed" });
4953
+ }
4954
+ const body = target === "gemini_generate_content" ? renderGemini(decoded.body, context, geminiHistory?.parts, diagnostics) : decoded.body;
4800
4955
  if (stop !== undefined)
4801
4956
  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;
4802
4957
  if (target === "anthropic_messages") {
@@ -4818,18 +4973,24 @@ function convertProtocolRequest(raw, context) {
4818
4973
  body.thinking = { type: "enabled", budget_tokens: budget };
4819
4974
  }
4820
4975
  }
4821
- if (target === "gemini_generate_content" && (policy?.geminiThinkingBudget !== undefined || policy?.geminiThinkingLevel !== undefined)) {
4822
- if (policy.geminiThinkingBudget !== undefined && policy.geminiThinkingLevel !== undefined)
4976
+ if (target === "gemini_generate_content" && (mappedEffort !== undefined || policy?.geminiThinkingBudget !== undefined || policy?.geminiThinkingLevel !== undefined)) {
4977
+ const level = policy?.geminiThinkingLevel ?? mappedEffort;
4978
+ if (policy?.geminiThinkingBudget !== undefined && level !== undefined)
4823
4979
  fail("unsupported_reasoning", "Choose one Gemini thinking policy", "reasoningPolicy");
4824
- const thinkingConfig = policy.geminiThinkingBudget !== undefined ? { thinkingBudget: integer(policy.geminiThinkingBudget, "reasoningPolicy.geminiThinkingBudget", -1) } : { thinkingLevel: string(policy.geminiThinkingLevel, "reasoningPolicy.geminiThinkingLevel", true) };
4980
+ if (level !== undefined && !["minimal", "low", "medium", "high"].includes(level))
4981
+ fail("unsupported_reasoning", "Unknown Gemini thinking level", "reasoning.effort");
4982
+ if (mappedEffort !== undefined && policy?.geminiThinkingLevel !== undefined && mappedEffort !== policy.geminiThinkingLevel)
4983
+ fail("unsupported_reasoning", "Conflicting Gemini thinking levels", "reasoning.effort");
4984
+ const thinkingConfig = policy?.geminiThinkingBudget !== undefined ? { thinkingBudget: integer(policy.geminiThinkingBudget, "reasoningPolicy.geminiThinkingBudget", -1) } : { thinkingLevel: string(level, "reasoningPolicy.geminiThinkingLevel", true) };
4825
4985
  body.generationConfig = { ...record(body.generationConfig ?? {}, "generationConfig"), thinkingConfig };
4826
4986
  }
4827
4987
  if (mappedEffort !== undefined || sourceThinking !== undefined || policy?.anthropicThinkingBudget !== undefined)
4828
4988
  diagnostics.push({ param: "reasoning", action: "mapped", reason: "explicit_verified_wire_policy" });
4829
- return { body, canonicalHistory: decoded.canonicalInput, diagnostics, toolNames: decoded.toolNames, streaming: b.stream === true };
4989
+ return { body, canonicalHistory: geminiHistory?.canonicalHistory(decoded.canonicalInput) ?? decoded.canonicalInput, diagnostics, toolNames: decoded.toolNames, streaming: b.stream === true };
4830
4990
  }
4831
- function renderGemini(chat, jsonSchema) {
4991
+ function renderGemini(chat, context, signedParts, diagnostics) {
4832
4992
  const result = { contents: [] }, contents = result.contents, system = [], calls = new Map;
4993
+ const nativeIds = new Set;
4833
4994
  const append = (role, parts) => {
4834
4995
  const prev = contents.at(-1);
4835
4996
  if (prev?.role === role)
@@ -4838,14 +4999,22 @@ function renderGemini(chat, jsonSchema) {
4838
4999
  contents.push({ role, parts });
4839
5000
  };
4840
5001
  for (const m of chat.messages) {
5002
+ const signed = signedParts?.get(m.id);
5003
+ if (signed) {
5004
+ append("model", signed);
5005
+ continue;
5006
+ }
4841
5007
  const parts = m.content == null ? [] : typeof m.content === "string" ? [{ text: m.content }] : m.content.map((p) => {
4842
5008
  if (p.type !== "text")
4843
5009
  fail("unsupported_content", "This content cannot cross to Gemini", "input");
4844
5010
  return { text: p.text };
4845
5011
  });
4846
5012
  if (m.role === "system" || m.role === "developer") {
4847
- if (contents.length)
4848
- fail("unsupported_content", "Mid-conversation instructions cannot be relocated", "input");
5013
+ if (contents.length) {
5014
+ if (context.sourceProtocol !== "responses")
5015
+ fail("unsupported_content", "Mid-conversation instructions cannot be relocated", "input");
5016
+ diagnostics.push({ param: "input", action: "mapped", reason: "gemini_mid_conversation_instructions_consolidated" });
5017
+ }
4849
5018
  system.push(...parts);
4850
5019
  continue;
4851
5020
  }
@@ -4855,7 +5024,8 @@ function renderGemini(chat, jsonSchema) {
4855
5024
  fail("invalid_tool_history", "Unknown tool result", "input");
4856
5025
  if (m.is_error === true)
4857
5026
  fail("unsupported_content", "Tool error authority cannot be represented by Gemini", "input");
4858
- append("user", [{ functionResponse: { id: m.tool_call_id, name, response: { output: m.content } } }]);
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 } } }]);
4859
5029
  continue;
4860
5030
  }
4861
5031
  if (m.reasoning_content !== undefined)
@@ -4863,10 +5033,20 @@ function renderGemini(chat, jsonSchema) {
4863
5033
  for (const c of m.tool_calls ?? []) {
4864
5034
  const fn = record(c.function, "function");
4865
5035
  calls.set(c.id, fn.name);
4866
- parts.push({ functionCall: { id: c.id, name: fn.name, args: argumentObject(fn.arguments) } });
5036
+ const { id, thoughtSignature } = context.sourceProtocol === "responses" ? decodeGeminiCallId(c.id, limits(context.capabilities?.limits).maxArgumentBytes) : { id: c.id, thoughtSignature: undefined };
5037
+ if (nativeIds.has(id))
5038
+ fail("invalid_tool_history", "Duplicate decoded Gemini call id", "call_id");
5039
+ nativeIds.add(id);
5040
+ parts.push({ functionCall: { id, name: fn.name, args: argumentObject(fn.arguments) }, ...thoughtSignature === undefined ? {} : { thoughtSignature } });
4867
5041
  }
4868
5042
  append(m.role === "assistant" ? "model" : "user", parts);
4869
5043
  }
5044
+ if (chat.parallel_tool_calls !== undefined) {
5045
+ if (context.sourceProtocol !== "responses" || chat.parallel_tool_calls !== false)
5046
+ fail("unsupported_request", "Gemini cannot enforce parallel tool preference", "parallel_tool_calls");
5047
+ system.push({ text: "Call at most one tool per response." });
5048
+ diagnostics.push({ param: "parallel_tool_calls", action: "mapped", reason: "gemini_serial_tool_calls_prompt_only" });
5049
+ }
4870
5050
  if (system.length)
4871
5051
  result.systemInstruction = { parts: system };
4872
5052
  const generation = {};
@@ -4875,12 +5055,10 @@ function renderGemini(chat, jsonSchema) {
4875
5055
  generation[to] = chat[from];
4876
5056
  if (chat.metadata !== undefined)
4877
5057
  fail("unsupported_request", "Gemini has no compatible request metadata", "metadata");
4878
- if (chat.parallel_tool_calls !== undefined)
4879
- fail("unsupported_request", "Gemini cannot enforce parallel tool preference", "parallel_tool_calls");
4880
5058
  if (chat.response_format !== undefined) {
4881
5059
  const f = record(chat.response_format, "format");
4882
5060
  if (f.type === "json_schema") {
4883
- if (!jsonSchema)
5061
+ if (!context.capabilities?.geminiJsonSchema)
4884
5062
  fail("unsupported_request", "Target JSON schema capability is required", "text.format");
4885
5063
  const s = record(f.json_schema, "schema");
4886
5064
  if (s.strict === false)
@@ -5025,7 +5203,11 @@ function validateCanonicalResponse(raw, context, itemOnly = false) {
5025
5203
  fail("unsupported_content", "Output annotations cannot cross protocols", path);
5026
5204
  }
5027
5205
  } else if (t.type === "reasoning") {
5028
- if (t.encrypted_content != null || t.content != null)
5206
+ if (t.encrypted_content != null && context.sourceProtocol === "responses" && context.targetProtocol === "gemini_generate_content")
5207
+ readGeminiSignatures(t, limits(context.capabilities?.limits));
5208
+ else if (t.encrypted_content != null)
5209
+ fail("unsupported_reasoning", "Opaque reasoning cannot cross protocols", path);
5210
+ if (t.content != null)
5029
5211
  fail("unsupported_reasoning", "Opaque or raw reasoning output cannot cross protocols", path);
5030
5212
  for (const rawPart of list(t.summary, "summary")) {
5031
5213
  const p = record(rawPart, "summary");
@@ -5148,20 +5330,28 @@ function responseFromCanonical(raw, protocol, context) {
5148
5330
 
5149
5331
  class GeminiCanonicalDecoder {
5150
5332
  context;
5333
+ streaming;
5151
5334
  encoder;
5152
5335
  serial = 0;
5153
5336
  ended = false;
5154
5337
  tools = false;
5155
5338
  ids = new Set;
5156
- constructor(context, names) {
5339
+ sequence = 0;
5340
+ contentId;
5341
+ partCount = 0;
5342
+ replayBytes = Buffer.byteLength(JSON.stringify({ version: 1, items: [] }));
5343
+ signedItems = 0;
5344
+ signed = new Map;
5345
+ constructor(context, names, streaming = true) {
5157
5346
  this.context = context;
5158
- this.encoder = new ResponsesEventEncoder("chat_completions", context.responseModel ?? context.model, names, context.capabilities?.limits, { preserveContentOrder: true });
5347
+ this.streaming = streaming;
5348
+ this.encoder = new ResponsesEventEncoder("chat_completions", context.responseModel ?? context.model, names, context.capabilities?.limits, { preserveContentOrder: true, agentMessageDelivery: context.capabilities?.agentMessageDelivery });
5159
5349
  }
5160
5350
  push(raw) {
5161
5351
  const b = record(raw, "Gemini response");
5162
5352
  fields2(b, ["candidates", "usageMetadata", "modelVersion", "responseId", "promptFeedback", "error"], "", "unsupported_response");
5163
5353
  if (b.error !== undefined)
5164
- return this.encoder.push({ error: safeProtocolError() });
5354
+ return this.decorate(this.encoder.push({ error: safeProtocolError() }));
5165
5355
  const events = [];
5166
5356
  if (b.promptFeedback !== undefined) {
5167
5357
  const p = record(b.promptFeedback, "promptFeedback");
@@ -5179,34 +5369,69 @@ class GeminiCanonicalDecoder {
5179
5369
  fields2(c, ["index", "content", "finishReason", "safetyRatings", "finishMessage"], "candidates", "unsupported_response");
5180
5370
  if (c.index !== undefined && c.index !== 0)
5181
5371
  fail("unsupported_response", "Only candidate index zero is supported", "candidates.index");
5182
- if (c.content !== undefined) {
5372
+ if (c.content !== undefined && c.content !== null) {
5183
5373
  const content = record(c.content, "content");
5184
5374
  fields2(content, ["role", "parts"], "candidates.content", "unsupported_content");
5185
5375
  if (content.role !== undefined && content.role !== "model")
5186
5376
  fail("unsupported_response", "Response role must be model", "candidates.content.role");
5187
- const parts = list(content.parts, "parts");
5377
+ const parts = list(content.parts ?? [], "parts");
5188
5378
  if (this.ended && parts.length)
5189
5379
  fail("invalid_stream", "Content arrived after Gemini terminal");
5190
5380
  for (const [i, rawPart] of parts.entries()) {
5191
5381
  const p = record(rawPart, "part");
5192
5382
  fields2(p, ["text", "thought", "functionCall", "thoughtSignature"], "candidates.content.parts", "unsupported_content");
5193
- if (p.thoughtSignature !== undefined)
5383
+ if (p.thoughtSignature !== undefined && this.context.sourceProtocol !== "responses")
5194
5384
  fail("unsupported_reasoning", "Thought signatures cannot cross protocols", "candidates.content.parts.thoughtSignature");
5195
5385
  if (p.functionCall !== undefined) {
5196
5386
  if (p.text !== undefined || p.thought !== undefined)
5197
5387
  fail("unsupported_content", "Mixed Gemini part kinds", "candidates.content.parts");
5198
5388
  const f = record(p.functionCall, "functionCall");
5199
5389
  fields2(f, ["id", "name", "args"], "functionCall", "unsupported_tool");
5200
- const index = this.serial++, id = f.id === undefined ? `call_gemini_${index}` : string(f.id, "functionCall.id", true);
5201
- if (this.ids.has(id))
5390
+ const index = this.serial++, originalId = f.id === undefined ? `call_gemini_${crypto.randomUUID().replaceAll("-", "")}` : string(f.id, "functionCall.id", true);
5391
+ if (this.ids.has(originalId))
5202
5392
  fail("invalid_tool_arguments", "Duplicate Gemini call id", "functionCall.id");
5203
- this.ids.add(id);
5393
+ this.ids.add(originalId);
5394
+ const id = this.context.sourceProtocol === "responses" ? encodeGeminiCallId(originalId, p.thoughtSignature, limits(this.context.capabilities?.limits).maxArgumentBytes) : originalId;
5204
5395
  this.tools = true;
5205
5396
  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")) } }] } }] }));
5206
5397
  } else {
5207
5398
  if (p.thought !== undefined && typeof p.thought !== "boolean")
5208
5399
  fail("invalid_payload", "Thought flag must be boolean", "thought");
5209
- events.push(...this.encoder.push({ choices: [{ delta: { [p.thought === true ? "reasoning_content" : "content"]: string(p.text, `parts[${i}].text`) } }] }));
5400
+ const text = string(p.text ?? (p.thoughtSignature === undefined ? undefined : ""), `parts[${i}].text`);
5401
+ const output = this.encoder.push({ choices: [{ delta: { [p.thought === true ? "reasoning_content" : "content"]: text } }] });
5402
+ const added = output.find((e) => e.type === "response.output_item.added");
5403
+ if (added)
5404
+ this.contentId = record(added.item, "item").id;
5405
+ const id = this.contentId;
5406
+ let item = this.signed.get(id);
5407
+ if (!item) {
5408
+ item = { id, thought: p.thought === true, parts: [], hasSignature: false, metadataBytes: Buffer.byteLength(JSON.stringify({ id, thought: p.thought === true, parts: [] })) };
5409
+ this.signed.set(id, item);
5410
+ }
5411
+ const signature = p.thoughtSignature === undefined ? undefined : checkedSignature(p.thoughtSignature, limits(this.context.capabilities?.limits).maxArgumentBytes);
5412
+ const previous = item.parts.at(-1);
5413
+ const merge = this.streaming && signature === undefined && previous && previous.signature === undefined && !previous.textAbsent;
5414
+ 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 } : {} }));
5415
+ const metadataBytes = item.metadataBytes + partBytes - (merge ? previous.metadataBytes : 0) + (!merge && item.parts.length ? 1 : 0);
5416
+ const hasSignature = item.hasSignature || signature !== undefined;
5417
+ const replayBytes = this.replayBytes + (item.hasSignature ? metadataBytes - item.metadataBytes : hasSignature ? metadataBytes + (this.signedItems ? 1 : 0) : 0);
5418
+ const budget = limits(this.context.capabilities?.limits);
5419
+ if (hasSignature && geminiEnvelopeBytes(replayBytes) > Math.min(budget.maxArgumentBytes, budget.maxOutputBytes))
5420
+ fail("resource_limit", "Accumulated Gemini signatures exceed replay budget");
5421
+ if (hasSignature && !item.hasSignature)
5422
+ this.signedItems++;
5423
+ this.replayBytes = replayBytes;
5424
+ item.metadataBytes = metadataBytes;
5425
+ item.hasSignature = hasSignature;
5426
+ if (merge) {
5427
+ previous.text += text;
5428
+ previous.metadataBytes = partBytes;
5429
+ } else {
5430
+ if (++this.partCount > limits(this.context.capabilities?.limits).maxItems)
5431
+ fail("resource_limit", "Too many Gemini parts");
5432
+ item.parts.push({ text, metadataBytes: partBytes, ...signature === undefined ? {} : { signature }, ...p.text === undefined ? { textAbsent: true } : {} });
5433
+ }
5434
+ events.push(...output);
5210
5435
  }
5211
5436
  }
5212
5437
  }
@@ -5223,25 +5448,47 @@ class GeminiCanonicalDecoder {
5223
5448
  }
5224
5449
  if (b.usageMetadata !== undefined) {
5225
5450
  const u = record(b.usageMetadata, "usageMetadata");
5226
- fields2(u, ["promptTokenCount", "candidatesTokenCount", "totalTokenCount", "cachedContentTokenCount", "thoughtsTokenCount"], "usageMetadata", "unsupported_response");
5227
- for (const [k, v] of Object.entries(u))
5228
- integer(v, `usageMetadata.${k}`);
5229
- const input = u.promptTokenCount ?? 0, thoughts = u.thoughtsTokenCount ?? 0, output = (u.candidatesTokenCount ?? 0) + thoughts;
5451
+ for (const k of ["promptTokenCount", "candidatesTokenCount", "totalTokenCount", "cachedContentTokenCount", "thoughtsTokenCount", "toolUsePromptTokenCount"])
5452
+ if (u[k] !== undefined && u[k] !== null)
5453
+ integer(u[k], `usageMetadata.${k}`);
5454
+ const input = (u.promptTokenCount ?? 0) + (u.toolUsePromptTokenCount ?? 0), thoughts = u.thoughtsTokenCount ?? 0, output = (u.candidatesTokenCount ?? 0) + thoughts;
5230
5455
  if (u.totalTokenCount !== undefined && u.totalTokenCount !== input + output)
5231
5456
  fail("invalid_usage", "Gemini total does not match reported components", "usageMetadata.totalTokenCount");
5232
5457
  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 } } }));
5233
5458
  }
5234
- return events;
5459
+ return this.decorate(events);
5460
+ }
5461
+ decorate(events) {
5462
+ const output = [];
5463
+ for (const event of events) {
5464
+ if (["response.completed", "response.incomplete", "response.failed"].includes(event.type)) {
5465
+ const signed = [...this.signed.values()].filter((item) => item.parts.some((p) => p.signature !== undefined));
5466
+ if (signed.length) {
5467
+ const budget = limits(this.context.capabilities?.limits), response = record(event.response, "response");
5468
+ const items = list(response.output, "output");
5469
+ if (items.length >= budget.maxItems)
5470
+ fail("resource_limit", "Too many Gemini output items");
5471
+ 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);
5472
+ const body = { ...response, output: [...items, item] };
5473
+ serialized(body, budget.maxOutputBytes, "Gemini response");
5474
+ 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 });
5475
+ output.push({ ...event, response: body });
5476
+ continue;
5477
+ }
5478
+ }
5479
+ output.push(event);
5480
+ }
5481
+ return output.map((event) => ({ ...event, sequence_number: this.sequence++ }));
5235
5482
  }
5236
5483
  finish() {
5237
- return this.encoder.finish();
5484
+ return this.decorate(this.encoder.finish());
5238
5485
  }
5239
5486
  }
5240
5487
  function canonicalResponse(raw, context, names) {
5241
5488
  if (context.targetProtocol === "responses")
5242
5489
  return validateCanonicalResponse(raw, context);
5243
5490
  if (context.targetProtocol === "gemini_generate_content") {
5244
- const decoder = new GeminiCanonicalDecoder(context, names);
5491
+ const decoder = new GeminiCanonicalDecoder(context, names, false);
5245
5492
  const events = [...decoder.push(raw), ...decoder.finish()];
5246
5493
  return record(events.at(-1)?.response, "response");
5247
5494
  }
@@ -5623,7 +5870,7 @@ class CanonicalStreamRenderer {
5623
5870
  var LLM_PROTOCOLS = ["responses", "chat_completions", "anthropic_messages", "gemini_generate_content"];
5624
5871
 
5625
5872
  // packages/llms/src/protocol-session/index.ts
5626
- var PROTOCOL_CONVERSION_RULES_VERSION = "1.0.1";
5873
+ var PROTOCOL_CONVERSION_RULES_VERSION = "1.0.4";
5627
5874
  function describeProtocolConversion() {
5628
5875
  return {
5629
5876
  rulesVersion: PROTOCOL_CONVERSION_RULES_VERSION,
@@ -5631,7 +5878,7 @@ function describeProtocolConversion() {
5631
5878
  matrix: LLM_PROTOCOLS.flatMap((sourceProtocol) => LLM_PROTOCOLS.map((targetProtocol) => ({
5632
5879
  sourceProtocol,
5633
5880
  targetProtocol,
5634
- mode: sourceProtocol === targetProtocol ? "passthrough" : sourceProtocol === "responses" && targetProtocol === "gemini_generate_content" || sourceProtocol === "gemini_generate_content" && targetProtocol === "responses" ? "unsupported" : "convert"
5881
+ mode: sourceProtocol === targetProtocol ? "passthrough" : sourceProtocol === "gemini_generate_content" && targetProtocol === "responses" ? "unsupported" : "convert"
5635
5882
  })))
5636
5883
  };
5637
5884
  }
@@ -5649,9 +5896,9 @@ function createProtocolSession(context) {
5649
5896
  fail("unsupported_protocol", "Unknown protocol");
5650
5897
  if (typeof context.model !== "string" || !context.model)
5651
5898
  fail("invalid_payload", "Target model is required", "model");
5652
- const unsupported = context.sourceProtocol === "responses" && context.targetProtocol === "gemini_generate_content" || context.sourceProtocol === "gemini_generate_content" && context.targetProtocol === "responses";
5899
+ const unsupported = context.sourceProtocol === "gemini_generate_content" && context.targetProtocol === "responses";
5653
5900
  if (unsupported)
5654
- fail("unsupported_protocol_pair", "Responses and Gemini direct conversion is not supported");
5901
+ fail("unsupported_protocol_pair", "Gemini to Responses conversion is not supported");
5655
5902
  const same = context.sourceProtocol === context.targetProtocol;
5656
5903
  let disposed = false, poisoned = false, requested = false, mode, finished = false, passTerminal = false, passAnthropicStopped = false;
5657
5904
  let names = new Map;
@@ -5890,7 +6137,7 @@ function protocolRequestModel(protocol, body, url) {
5890
6137
  return model;
5891
6138
  }
5892
6139
  // plugins/llm-protocol-adapter/server/relay.ts
5893
- import { createHash } from "crypto";
6140
+ import { createHash as createHash2 } from "crypto";
5894
6141
 
5895
6142
  // plugins/llm-protocol-adapter/server/relay-response.ts
5896
6143
  var RELAY_TOOL = "relay_external_agent_payload";
@@ -6054,7 +6301,7 @@ async function relayPayload(result, param, signal) {
6054
6301
  }
6055
6302
 
6056
6303
  // plugins/llm-protocol-adapter/server/relay.ts
6057
- var hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
6304
+ var hash = (value) => createHash2("sha256").update(JSON.stringify(value)).digest("hex");
6058
6305
  function rejection(param, code, status = 502) {
6059
6306
  return new DataAdmissionError(status, code, undefined, { param, message: "Agent payload relay is unavailable" });
6060
6307
  }
@@ -6260,9 +6507,10 @@ class AgentRelay {
6260
6507
  }
6261
6508
 
6262
6509
  // plugins/llm-protocol-adapter/server/service.ts
6263
- var REASONING_RULES_VERSION = "2026-10-24.1";
6510
+ var REASONING_RULES_VERSION = "2026-10-27.1";
6264
6511
  var concrete = new Set(["none", "minimal", "low", "medium", "high", "xhigh", "max"]);
6265
- var effortProtocols = new Set(["responses", "chat_completions", "anthropic_messages"]);
6512
+ var effortProtocols = new Set(["responses", "chat_completions", "anthropic_messages", "gemini_generate_content"]);
6513
+ var geminiEfforts = new Set(["minimal", "low", "medium", "high"]);
6266
6514
  var anthropicEfforts = new Set(["low", "medium", "high", "xhigh", "max"]);
6267
6515
  function identityError(code, message) {
6268
6516
  throw new ResponsesCodecError(code, message, "modelIdentity");
@@ -6287,7 +6535,7 @@ function resolveCapabilities(catalog, context) {
6287
6535
  if (!info)
6288
6536
  return null;
6289
6537
  const restrictions = context.restrictions, reasoningAllowed = info.reasoning && restrictions?.reasoning !== false;
6290
- const allowed = context.targetProtocol === "anthropic_messages" ? anthropicEfforts : concrete;
6538
+ const allowed = context.targetProtocol === "anthropic_messages" ? anthropicEfforts : context.targetProtocol === "gemini_generate_content" ? geminiEfforts : concrete;
6291
6539
  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)) : []) ?? [] : [];
6292
6540
  const supportedEfforts = [...new Set(candidates)];
6293
6541
  const wirePolicy = supportedEfforts.length ? { effortMap: Object.fromEntries(supportedEfforts.map((value) => [value, value])) } : null;
@@ -6484,4 +6732,4 @@ export {
6484
6732
  control_default as default
6485
6733
  };
6486
6734
 
6487
- //# debugId=62FD9644C2CB7DAB64756E2164756E21
6735
+ //# debugId=165F7F1DADB9687864756E2164756E21