@jeffusion/bungee-llms 5.22.0 → 5.23.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.
package/dist/index.js CHANGED
@@ -3465,7 +3465,13 @@ function normalizeUsage(raw, protocol, prior = {}) {
3465
3465
  output_tokens_details: { reasoning_tokens: tokens(reasoning ?? prior.output_tokens_details?.reasoning_tokens, "reasoning_tokens") }
3466
3466
  };
3467
3467
  }
3468
- function toolItem(id, name, args, names, status = "completed") {
3468
+ function isCollaborationFunction(name, namespace, custom = false) {
3469
+ return !custom && namespace === "collaboration" && typeof name === "string" && ["spawn_agent", "send_message", "followup_task"].includes(name);
3470
+ }
3471
+ function plaintextCollaborationCall(item, delivery) {
3472
+ return delivery === "plaintext" && item.type === "function_call" && isCollaborationFunction(item.name, item.namespace) ? { ...item, encrypted_function_args: [] } : item;
3473
+ }
3474
+ function toolItem(id, name, args, names, status = "completed", agentMessageDelivery) {
3469
3475
  const original = restoreTool(name, names);
3470
3476
  const item = { id: `fc_${id}`, type: original.custom ? "custom_tool_call" : "function_call", call_id: id, name: original.name, status };
3471
3477
  if (original.namespace)
@@ -3476,7 +3482,7 @@ function toolItem(id, name, args, names, status = "completed") {
3476
3482
  argumentObject(args);
3477
3483
  item.arguments = args;
3478
3484
  }
3479
- return item;
3485
+ return plaintextCollaborationCall(item, agentMessageDelivery);
3480
3486
  }
3481
3487
  function responsePayload(id, model, output, end, usage, error, createdAt = Math.floor(Date.now() / 1000)) {
3482
3488
  const result = { id, object: "response", created_at: createdAt, model, status: end.status, output, usage: Object.keys(usage).length ? usage : null };
@@ -3487,6 +3493,114 @@ function responsePayload(id, model, output, end, usage, error, createdAt = Math.
3487
3493
  return result;
3488
3494
  }
3489
3495
 
3496
+ // src/responses-codec/agent-messages.ts
3497
+ function exact(value, keys, path) {
3498
+ for (const key of Object.keys(value))
3499
+ if (!keys.includes(key))
3500
+ fail("unsupported_content", "Unknown agent message field", `${path}.${key}`);
3501
+ }
3502
+ function validateAgentMessage(raw, path, configuredLimits, allowEncrypted = false) {
3503
+ const budget = limits(configuredLimits);
3504
+ const item = atParam(path, () => record(raw, "agent message"));
3505
+ exact(item, ["type", "id", "author", "recipient", "content", "internal_chat_message_metadata_passthrough"], path);
3506
+ if (item.type !== "agent_message")
3507
+ fail("unsupported_content", "Expected agent message", `${path}.type`);
3508
+ if (item.id !== undefined)
3509
+ atParam(`${path}.id`, () => string(item.id, "agent message id", true));
3510
+ if (item.internal_chat_message_metadata_passthrough !== undefined)
3511
+ atParam(`${path}.internal_chat_message_metadata_passthrough`, () => record(item.internal_chat_message_metadata_passthrough, "message metadata"));
3512
+ for (const key of ["author", "recipient"])
3513
+ atParam(`${path}.${key}`, () => string(item[key], "agent identity", true));
3514
+ const content = atParam(`${path}.content`, () => list(item.content, "agent content"));
3515
+ if (content.length > budget.maxItems)
3516
+ fail("resource_limit", "Too many agent content parts", `${path}.content`);
3517
+ for (const [index, rawPart] of content.entries()) {
3518
+ const partPath = `${path}.content[${index}]`;
3519
+ const part = atParam(partPath, () => record(rawPart, "agent content part"));
3520
+ if (part.type === "encrypted_content") {
3521
+ exact(part, ["type", "encrypted_content"], partPath);
3522
+ atParam(`${partPath}.encrypted_content`, () => string(part.encrypted_content, "encrypted content", true));
3523
+ if (!allowEncrypted)
3524
+ fail("unsupported_content", "Encrypted agent content requires explicit plaintext replacement", `${partPath}.encrypted_content`);
3525
+ } else {
3526
+ exact(part, ["type", "text"], partPath);
3527
+ if (part.type !== "input_text")
3528
+ fail("unsupported_content", "Agent messages require input text", `${partPath}.type`);
3529
+ atParam(`${partPath}.text`, () => string(part.text, "agent input text"));
3530
+ }
3531
+ }
3532
+ atParam(path, () => serialized(item, budget.maxRequestBytes, "agent message"));
3533
+ return item;
3534
+ }
3535
+ function inspectAgentMessages(raw, options = {}) {
3536
+ const budget = limits(options.limits);
3537
+ const body = atParam("request", () => record(raw, "request"));
3538
+ atParam("request", () => serialized(body, budget.maxRequestBytes, "request"));
3539
+ const input = typeof body.input === "string" ? [] : atParam("input", () => list(body.input, "input"));
3540
+ if (input.length > budget.maxItems)
3541
+ fail("resource_limit", "Too many input items", "input");
3542
+ const messages = [], diagnostics = [];
3543
+ let parts = 0;
3544
+ for (const [inputIndex, rawItem] of input.entries()) {
3545
+ const item = atParam(`input[${inputIndex}]`, () => record(rawItem, "input item"));
3546
+ if (item.type !== "agent_message")
3547
+ continue;
3548
+ validateAgentMessage(item, `input[${inputIndex}]`, options.limits, true);
3549
+ const encryptedContents = [];
3550
+ for (const [contentIndex, part] of item.content.entries()) {
3551
+ if (++parts > budget.maxItems)
3552
+ fail("resource_limit", "Too many agent content parts", "input");
3553
+ if (part.type === "encrypted_content") {
3554
+ const param = `input[${inputIndex}].content[${contentIndex}].encrypted_content`;
3555
+ encryptedContents.push({ contentIndex, param });
3556
+ }
3557
+ }
3558
+ messages.push({ inputIndex, author: item.author, recipient: item.recipient, encryptedContents });
3559
+ }
3560
+ return { messages, diagnostics };
3561
+ }
3562
+ function replaceAgentMessageEncryptedContent(raw, replacements, options = {}) {
3563
+ const inspection = inspectAgentMessages(raw, options);
3564
+ const budget = limits(options.limits);
3565
+ if (!Array.isArray(replacements))
3566
+ fail("invalid_payload", "Replacements must be an array", "replacements");
3567
+ if (replacements.length > budget.maxItems)
3568
+ fail("resource_limit", "Too many replacements", "replacements");
3569
+ const locations = new Map(inspection.messages.flatMap((m) => m.encryptedContents.map((c) => [c.param, { inputIndex: m.inputIndex, contentIndex: c.contentIndex }])));
3570
+ const selected = new Set;
3571
+ let body;
3572
+ try {
3573
+ body = structuredClone(record(raw, "request"));
3574
+ } catch {
3575
+ fail("invalid_payload", "Request must be cloneable JSON", "request");
3576
+ }
3577
+ const diagnostics = [];
3578
+ for (const [i, rawReplacement] of replacements.entries()) {
3579
+ const replacement = atParam(`replacements[${i}]`, () => record(rawReplacement, "replacement"));
3580
+ exact(replacement, ["param", "text"], `replacements[${i}]`);
3581
+ const param = atParam(`replacements[${i}].param`, () => string(replacement.param, "replacement param", true));
3582
+ const text = atParam(`replacements[${i}].text`, () => string(replacement.text, "replacement text"));
3583
+ const location = locations.get(param);
3584
+ if (!location || selected.has(param))
3585
+ fail("invalid_payload", "Replacement must identify one unique encrypted agent content block", `replacements[${i}].param`);
3586
+ selected.add(param);
3587
+ record(body.input[location.inputIndex], "agent message").content[location.contentIndex] = { type: "input_text", text };
3588
+ diagnostics.push({ param, action: "mapped", reason: "agent_message_plaintext_replacement" });
3589
+ }
3590
+ atParam("request", () => serialized(body, budget.maxRequestBytes, "request"));
3591
+ return { body, diagnostics };
3592
+ }
3593
+ function validateEncryptedFunctionArgs(item, delivery, path) {
3594
+ if (item.encrypted_function_args === undefined)
3595
+ return;
3596
+ const param = `${path}.encrypted_function_args`;
3597
+ if (delivery !== "plaintext" || !isCollaborationFunction(item.name, item.namespace, item.type !== "function_call"))
3598
+ fail("unsupported_content", "Encrypted function argument marker is unavailable for this tool", param);
3599
+ const values = atParam(param, () => list(item.encrypted_function_args, "encrypted function arguments"));
3600
+ if (values.length)
3601
+ fail("unsupported_content", "Encrypted function arguments cannot cross protocols", param);
3602
+ }
3603
+
3490
3604
  // src/responses-codec/request-preferences.ts
3491
3605
  function fields(value, allowed, path) {
3492
3606
  for (const key of Object.keys(value))
@@ -3620,6 +3734,8 @@ function definitionKey(value) {
3620
3734
  }
3621
3735
  function decodeResponsesRequest(raw, protocol, capabilities = {}) {
3622
3736
  const body = record(raw, "Responses request");
3737
+ if (capabilities.agentMessageDelivery !== undefined && capabilities.agentMessageDelivery !== "plaintext")
3738
+ fail("invalid_payload", "Unknown agent message delivery policy", "agentMessageDelivery");
3623
3739
  const budget = limits(capabilities.limits);
3624
3740
  serialized(body, budget.maxRequestBytes, "request");
3625
3741
  const supported = new Set([
@@ -3744,7 +3860,18 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
3744
3860
  }
3745
3861
  const wire = register(name, namespace, custom);
3746
3862
  definitions.set(key, signature);
3747
- const parameters = custom ? { type: "object", properties: { input: { type: "string" } }, required: ["input"], additionalProperties: false } : record(tool.parameters ?? { type: "object", properties: {} }, "tool parameters");
3863
+ let parameters = custom ? { type: "object", properties: { input: { type: "string" } }, required: ["input"], additionalProperties: false } : record(tool.parameters ?? { type: "object", properties: {} }, "tool parameters");
3864
+ if (capabilities.agentMessageDelivery === "plaintext" && isCollaborationFunction(name, namespace, custom)) {
3865
+ const properties = parameters.properties;
3866
+ if (properties && typeof properties === "object" && !Array.isArray(properties)) {
3867
+ const message = properties.message;
3868
+ if (message && typeof message === "object" && !Array.isArray(message) && message.encrypted === true) {
3869
+ const { encrypted: _encrypted, ...plaintext } = message;
3870
+ parameters = { ...parameters, properties: { ...properties, message: plaintext } };
3871
+ diagnostics.push({ param: `${path}.parameters.properties.message.encrypted`, action: "omitted", reason: "collaboration_message_plaintext_delivery" });
3872
+ }
3873
+ }
3874
+ }
3748
3875
  const declaredDescription = tool.description === undefined ? undefined : string(tool.description, "tool description");
3749
3876
  const identityHint = capabilities.preserveToolIdentityDescription ? `Original tool identity: ${JSON.stringify({ name, ...namespace ? { namespace } : {} })}` : undefined;
3750
3877
  const description = [identityHint, namespaceDescription, declaredDescription].filter((value) => value !== undefined).join(`
@@ -3849,7 +3976,20 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
3849
3976
  const item = record(rawItem, "input item");
3850
3977
  if (item.type === "additional_tools")
3851
3978
  return;
3852
- if (item.encrypted_content != null || item.type === "compaction")
3979
+ if (item.type === "agent_message") {
3980
+ validateAgentMessage(item, messageParam, capabilities.limits);
3981
+ if (capabilities.agentMessageDelivery !== "plaintext")
3982
+ fail("unsupported_content", "Agent messages require explicit plaintext delivery", messageParam);
3983
+ if (pendingReasoning)
3984
+ fail("unsupported_reasoning", "Reasoning must precede an assistant item", messageParam);
3985
+ addMessage({ role: "user", content: [{ type: "text", text: `[Agent message metadata: ${JSON.stringify({ author: item.author, recipient: item.recipient })}]
3986
+ ` }, ...item.content.map((part) => ({ type: "text", text: part.text }))] });
3987
+ diagnostics.push({ param: messageParam, action: "mapped", reason: "agent_message_plaintext_delivery" });
3988
+ return;
3989
+ }
3990
+ if (item.encrypted_content != null)
3991
+ fail("unsupported_content", "Encrypted content cannot cross protocols", `${messageParam}.encrypted_content`);
3992
+ if (item.type === "compaction")
3853
3993
  fail("unsupported_content", "Encrypted reasoning and compaction cannot be restored");
3854
3994
  if (item.type === "item_reference")
3855
3995
  fail("unresolved_reference", "Input item references must be resolved before conversion");
@@ -3869,6 +4009,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
3869
4009
  }
3870
4010
  if (item.type === "function_call" || item.type === "custom_tool_call") {
3871
4011
  const custom = item.type === "custom_tool_call";
4012
+ validateEncryptedFunctionArgs(item, capabilities.agentMessageDelivery, messageParam);
3872
4013
  const id = string(item.call_id, "call_id", true);
3873
4014
  if (callIds.has(id))
3874
4015
  fail("invalid_tool_history", "Duplicate tool call identifier");
@@ -4087,9 +4228,9 @@ class ResponsesEventEncoder {
4087
4228
  item(state, status, strict = false) {
4088
4229
  if (state.kind === "tool") {
4089
4230
  if (strict)
4090
- return { ...toolItem(state.callId, state.name, state.args || "{}", this.toolNames, status), id: state.id };
4231
+ return { ...toolItem(state.callId, state.name, state.args || "{}", this.toolNames, status, this.options.agentMessageDelivery), id: state.id };
4091
4232
  const original = state.original ?? restoreTool(state.name, this.toolNames);
4092
- return {
4233
+ return plaintextCollaborationCall({
4093
4234
  id: state.id,
4094
4235
  type: original.custom ? "custom_tool_call" : "function_call",
4095
4236
  call_id: state.callId,
@@ -4097,7 +4238,7 @@ class ResponsesEventEncoder {
4097
4238
  ...original.namespace ? { namespace: original.namespace } : {},
4098
4239
  status,
4099
4240
  ...original.custom ? { input: state.emittedInput } : { arguments: state.args }
4100
- };
4241
+ }, this.options.agentMessageDelivery);
4101
4242
  }
4102
4243
  if (state.kind === "reasoning")
4103
4244
  return { id: state.id, type: "reasoning", summary: status === "in_progress" ? [] : [{ type: "summary_text", text: state.text }] };
@@ -4410,13 +4551,13 @@ class ResponsesEventEncoder {
4410
4551
  }
4411
4552
 
4412
4553
  // src/responses-codec/result.ts
4413
- function encodeResponsesResult(raw, protocol, model, toolNames = new Map, configuredLimits) {
4554
+ function encodeResponsesResult(raw, protocol, model, toolNames = new Map, configuredLimits, options = {}) {
4414
4555
  const body = record(raw, "upstream response");
4415
4556
  const budget = limits(configuredLimits);
4416
4557
  serialized(body, budget.maxOutputBytes, "upstream response");
4417
4558
  if (body.error !== undefined || body.type === "error")
4418
4559
  return responsePayload(`resp_${crypto.randomUUID().replaceAll("-", "")}`, model, [], { status: "failed", reason: "upstream_error" }, {}, record(body.error, "upstream error"));
4419
- const encoder = new ResponsesEventEncoder(protocol, model, toolNames, configuredLimits);
4560
+ const encoder = new ResponsesEventEncoder(protocol, model, toolNames, configuredLimits, options);
4420
4561
  if (protocol === "chat_completions") {
4421
4562
  const choices = list(body.choices, "choices");
4422
4563
  if (choices.length !== 1)
@@ -4834,7 +4975,7 @@ function canonicalRequest(raw, protocol, model) {
4834
4975
  }
4835
4976
  return out;
4836
4977
  }
4837
- function validateCanonical(b) {
4978
+ function validateCanonical(b, context) {
4838
4979
  for (const key of ["stream", "parallel_tool_calls", "store", "background"])
4839
4980
  if (b[key] !== undefined)
4840
4981
  bool(b[key], key);
@@ -4864,7 +5005,11 @@ function validateCanonical(b) {
4864
5005
  const input = typeof b.input === "string" ? [] : list(b.input, "input");
4865
5006
  for (const [i, raw] of input.entries()) {
4866
5007
  const t = record(raw, "input item"), p = `input[${i}]`;
4867
- const allowed = t.type === "function_call" ? ["type", "id", "status", "call_id", "name", "namespace", "arguments"] : t.type === "custom_tool_call" ? ["type", "id", "status", "call_id", "name", "namespace", "input"] : t.type === "function_call_output" || t.type === "custom_tool_call_output" ? ["type", "id", "call_id", "output", "is_error"] : t.type === "reasoning" ? ["type", "id", "status", "summary", "content", "encrypted_content", "internal_chat_message_metadata_passthrough"] : t.type === "additional_tools" ? ["type", "id", "role", "tools"] : ["type", "id", "status", "role", "content", "internal_chat_message_metadata_passthrough"];
5008
+ if (t.type === "agent_message") {
5009
+ validateAgentMessage(t, p, context.capabilities?.limits);
5010
+ continue;
5011
+ }
5012
+ const allowed = t.type === "function_call" ? ["type", "id", "status", "call_id", "name", "namespace", "arguments", "encrypted_function_args"] : t.type === "custom_tool_call" ? ["type", "id", "status", "call_id", "name", "namespace", "input"] : t.type === "function_call_output" || t.type === "custom_tool_call_output" ? ["type", "id", "call_id", "output", "is_error"] : t.type === "reasoning" ? ["type", "id", "status", "summary", "content", "encrypted_content", "internal_chat_message_metadata_passthrough"] : t.type === "additional_tools" ? ["type", "id", "role", "tools"] : ["type", "id", "status", "role", "content", "internal_chat_message_metadata_passthrough"];
4868
5013
  fields2(t, [...allowed, "internal_chat_message_metadata_passthrough"], p, "unsupported_content");
4869
5014
  if (t.is_error !== undefined)
4870
5015
  bool(t.is_error, `${p}.is_error`);
@@ -4914,8 +5059,8 @@ function convertProtocolRequest(raw, context) {
4914
5059
  fail("invalid_payload", "Request stream mode disagrees with transport context", "stream");
4915
5060
  b.stream = context.streaming;
4916
5061
  }
4917
- validateCanonical(b);
4918
- const cap = { ...context.capabilities, omitOptionalWebSearch: true, preserveToolIdentityDescription: true, preserveRefusalContent: true }, policy = context.reasoningPolicy;
5062
+ validateCanonical(b, context);
5063
+ const cap = { ...context.capabilities, agentMessageDelivery: context.agentMessageDelivery ?? context.capabilities?.agentMessageDelivery, omitOptionalWebSearch: true, preserveToolIdentityDescription: true, preserveRefusalContent: true }, policy = context.reasoningPolicy;
4919
5064
  const reasoning = b.reasoning === undefined ? undefined : record(b.reasoning, "reasoning");
4920
5065
  const sourceThinking = b.sourceThinking;
4921
5066
  delete b.sourceThinking;
@@ -5202,7 +5347,7 @@ function validateCanonicalResponse(raw, context, itemOnly = false) {
5202
5347
  fail("unsupported_response", "Unknown item status", `${path}.status`);
5203
5348
  if (b.status === "completed" && t.status !== undefined && t.status !== "completed")
5204
5349
  fail("invalid_stream", "Completed response contains an unfinished item", `${path}.status`);
5205
- fields2(t, t.type === "message" ? ["type", "id", "role", "status", "content"] : t.type === "reasoning" ? ["type", "id", "summary", "status", "encrypted_content", "content"] : t.type === "function_call" ? ["type", "id", "status", "name", "namespace", "call_id", "arguments"] : ["type", "id", "status", "name", "namespace", "call_id", "input"], path, "unsupported_content");
5350
+ fields2(t, t.type === "message" ? ["type", "id", "role", "status", "content"] : t.type === "reasoning" ? ["type", "id", "summary", "status", "encrypted_content", "content"] : t.type === "function_call" ? ["type", "id", "status", "name", "namespace", "call_id", "arguments", "encrypted_function_args"] : ["type", "id", "status", "name", "namespace", "call_id", "input"], path, "unsupported_content");
5206
5351
  if (t.type === "message") {
5207
5352
  if (t.role !== "assistant")
5208
5353
  fail("unsupported_content", "Output message must be assistant", `${path}.role`);
@@ -5229,6 +5374,7 @@ function validateCanonicalResponse(raw, context, itemOnly = false) {
5229
5374
  string(p.text, "summary");
5230
5375
  }
5231
5376
  } else if (t.type === "function_call" || t.type === "custom_tool_call") {
5377
+ validateEncryptedFunctionArgs(t, context.capabilities?.agentMessageDelivery, path);
5232
5378
  const id = string(t.call_id, "call_id", true);
5233
5379
  if (ids.has(id))
5234
5380
  fail("invalid_tool_arguments", "Duplicate output tool call id", path);
@@ -5439,7 +5585,7 @@ function canonicalResponse(raw, context, names) {
5439
5585
  return record(events.at(-1)?.response, "response");
5440
5586
  }
5441
5587
  validateProviderResponse(raw, context.targetProtocol);
5442
- const result = encodeResponsesResult(raw, context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits);
5588
+ const result = encodeResponsesResult(raw, context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits, { agentMessageDelivery: context.capabilities?.agentMessageDelivery });
5443
5589
  if (result.status === "failed")
5444
5590
  result.error = safeProtocolError();
5445
5591
  const usage = record(raw, "response").usage;
@@ -5834,6 +5980,10 @@ function createProtocolSession(context) {
5834
5980
  capabilities: context.capabilities ? { ...context.capabilities, limits: context.capabilities.limits ? { ...context.capabilities.limits } : undefined } : undefined,
5835
5981
  reasoningPolicy: context.reasoningPolicy ? { ...context.reasoningPolicy, effortMap: context.reasoningPolicy.effortMap ? { ...context.reasoningPolicy.effortMap } : undefined } : undefined
5836
5982
  };
5983
+ const delivery = context.agentMessageDelivery ?? context.capabilities?.agentMessageDelivery;
5984
+ if (delivery !== undefined && delivery !== "plaintext")
5985
+ fail("invalid_payload", "Unknown agent message delivery policy", "agentMessageDelivery");
5986
+ context.capabilities = { ...context.capabilities, agentMessageDelivery: delivery };
5837
5987
  if (!LLM_PROTOCOLS.includes(context.sourceProtocol) || !LLM_PROTOCOLS.includes(context.targetProtocol))
5838
5988
  fail("unsupported_protocol", "Unknown protocol");
5839
5989
  if (typeof context.model !== "string" || !context.model)
@@ -5872,7 +6022,7 @@ function createProtocolSession(context) {
5872
6022
  else if (context.targetProtocol === "gemini_generate_content")
5873
6023
  encoder = new GeminiCanonicalDecoder(context, names);
5874
6024
  else
5875
- encoder = new ResponsesEventEncoder(context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits, { preserveContentOrder: true });
6025
+ encoder = new ResponsesEventEncoder(context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits, { preserveContentOrder: true, agentMessageDelivery: context.capabilities?.agentMessageDelivery });
5876
6026
  validator = context.targetProtocol === "responses" ? undefined : new CanonicalStreamValidator(context);
5877
6027
  renderer = new CanonicalStreamRenderer(context.sourceProtocol, context);
5878
6028
  };
@@ -6086,7 +6236,9 @@ export {
6086
6236
  encodeResponsesResult,
6087
6237
  finalizeTokenAccountingEvent,
6088
6238
  getProviderTokenAccountingCapabilities,
6239
+ inspectAgentMessages,
6089
6240
  isRecord5 as isRecord,
6090
6241
  readNumber,
6091
- readRecord
6242
+ readRecord,
6243
+ replaceAgentMessageEncryptedContent
6092
6244
  };
@@ -1757,6 +1757,311 @@ function createTokenAccountingSession(input, options = {}) {
1757
1757
  }
1758
1758
  };
1759
1759
  }
1760
+ // src/responses-codec/common.ts
1761
+ var DEFAULT_RESPONSES_CODEC_LIMITS = Object.freeze({
1762
+ maxRequestBytes: 8 * 1024 * 1024,
1763
+ maxOutputBytes: 8 * 1024 * 1024,
1764
+ maxArgumentBytes: 1024 * 1024,
1765
+ maxItems: 1024
1766
+ });
1767
+
1768
+ class ResponsesCodecError extends Error {
1769
+ code;
1770
+ param;
1771
+ constructor(code, message, param) {
1772
+ super(message);
1773
+ this.code = code;
1774
+ this.param = param;
1775
+ this.name = "ResponsesCodecError";
1776
+ }
1777
+ }
1778
+ function fail(code, message, param) {
1779
+ throw new ResponsesCodecError(code, message, param);
1780
+ }
1781
+ function atParam(param, run) {
1782
+ try {
1783
+ return run();
1784
+ } catch (error) {
1785
+ if (error instanceof ResponsesCodecError && error.param === undefined)
1786
+ throw new ResponsesCodecError(error.code, error.message, param);
1787
+ throw error;
1788
+ }
1789
+ }
1790
+ function record(value, where) {
1791
+ if (!value || typeof value !== "object" || Array.isArray(value))
1792
+ fail("invalid_payload", `${where} must be an object`);
1793
+ return value;
1794
+ }
1795
+ function string(value, where, nonempty = false) {
1796
+ if (typeof value !== "string" || nonempty && !value)
1797
+ fail("invalid_payload", `${where} must be a${nonempty ? " non-empty" : ""} string`);
1798
+ return value;
1799
+ }
1800
+ function list(value, where) {
1801
+ if (!Array.isArray(value))
1802
+ fail("invalid_payload", `${where} must be an array`);
1803
+ return value;
1804
+ }
1805
+ function limits(overrides) {
1806
+ const result = { ...DEFAULT_RESPONSES_CODEC_LIMITS, ...overrides };
1807
+ for (const [key, value] of Object.entries(result)) {
1808
+ if (!Number.isSafeInteger(value) || value <= 0)
1809
+ fail("invalid_limit", `${key} must be a positive integer`);
1810
+ }
1811
+ return result;
1812
+ }
1813
+ function bytes(value) {
1814
+ return new TextEncoder().encode(value).byteLength;
1815
+ }
1816
+ function bounded(value, max, where) {
1817
+ if (bytes(value) > max)
1818
+ fail("resource_limit", `${where} exceeds ${max} bytes`);
1819
+ return value;
1820
+ }
1821
+ function serialized(value, max, where) {
1822
+ let result;
1823
+ try {
1824
+ result = JSON.stringify(value);
1825
+ } catch {
1826
+ fail("invalid_payload", `${where} must be serializable JSON`);
1827
+ }
1828
+ if (result === undefined)
1829
+ fail("invalid_payload", `${where} must be serializable JSON`);
1830
+ return bounded(result, max, where);
1831
+ }
1832
+ function argumentObject(text) {
1833
+ let value;
1834
+ try {
1835
+ value = JSON.parse(text);
1836
+ } catch {
1837
+ fail("invalid_tool_arguments", "Tool arguments must be complete JSON");
1838
+ }
1839
+ return record(value, "tool arguments");
1840
+ }
1841
+ function restoreTool(name, names) {
1842
+ const result = names.get(name);
1843
+ if (result)
1844
+ return result;
1845
+ if (names.size)
1846
+ fail("unknown_tool", `Provider returned undeclared tool ${name}`);
1847
+ return { name, custom: false };
1848
+ }
1849
+ function customInput(text) {
1850
+ const value = argumentObject(text);
1851
+ if (Object.keys(value).length !== 1 || typeof value.input !== "string") {
1852
+ fail("invalid_tool_arguments", "Custom tools require exactly one string input property");
1853
+ }
1854
+ return value.input;
1855
+ }
1856
+ function customInputPrefix(text) {
1857
+ const prefix = /^\s*\{\s*"input"\s*:\s*"/.exec(text);
1858
+ if (!prefix)
1859
+ return "";
1860
+ let result = "";
1861
+ for (let i = prefix[0].length;i < text.length; ) {
1862
+ const char = text[i];
1863
+ if (char === '"')
1864
+ break;
1865
+ if (char !== "\\") {
1866
+ if (char.charCodeAt(0) < 32)
1867
+ fail("invalid_tool_arguments", "Invalid JSON string character");
1868
+ result += char;
1869
+ i++;
1870
+ continue;
1871
+ }
1872
+ const escape = text[i + 1];
1873
+ if (escape === undefined)
1874
+ break;
1875
+ const size = escape === "u" ? 6 : 2;
1876
+ if (i + size > text.length)
1877
+ break;
1878
+ try {
1879
+ result += JSON.parse(`"${text.slice(i, i + size)}"`);
1880
+ } catch {
1881
+ fail("invalid_tool_arguments", "Invalid JSON string escape");
1882
+ }
1883
+ i += size;
1884
+ }
1885
+ return result;
1886
+ }
1887
+ function terminal(reason, protocol) {
1888
+ if (typeof reason !== "string" || !reason)
1889
+ fail("missing_terminal", "Upstream has no explicit completion reason");
1890
+ const completed = protocol === "chat_completions" ? ["stop", "tool_calls", "function_call"] : ["end_turn", "tool_use", "stop_sequence"];
1891
+ if (completed.includes(reason))
1892
+ return { status: "completed", reason };
1893
+ if (reason === "length" || reason === "max_tokens" || reason === "model_context_window_exceeded")
1894
+ return { status: "incomplete", reason: "max_output_tokens" };
1895
+ if (reason === "content_filter" || reason === "refusal")
1896
+ return { status: "incomplete", reason: "content_filter" };
1897
+ if (reason === "error")
1898
+ return { status: "failed", reason: "upstream_error" };
1899
+ fail("unknown_terminal", `Unsupported upstream completion reason: ${reason}`);
1900
+ }
1901
+ function tokens(value, where) {
1902
+ if (value === undefined)
1903
+ return 0;
1904
+ if (!Number.isSafeInteger(value) || value < 0)
1905
+ fail("invalid_usage", `${where} must be a non-negative integer`);
1906
+ return value;
1907
+ }
1908
+ function normalizeUsage(raw, protocol, prior = {}) {
1909
+ if (raw === undefined || raw === null)
1910
+ return prior;
1911
+ const usage = record(raw, "usage");
1912
+ const chat = protocol === "chat_completions";
1913
+ const input = tokens(usage[chat ? "prompt_tokens" : "input_tokens"] ?? prior.input_tokens, "input_tokens");
1914
+ const output = tokens(usage[chat ? "completion_tokens" : "output_tokens"] ?? prior.output_tokens, "output_tokens");
1915
+ const details = chat ? usage.prompt_tokens_details : undefined;
1916
+ const cached = chat ? details ? record(details, "prompt_tokens_details").cached_tokens : undefined : usage.cache_read_input_tokens;
1917
+ const cacheRead = tokens(cached ?? prior.input_tokens_details?.cached_tokens, "cached_tokens");
1918
+ const cacheWrite = chat ? 0 : tokens(usage.cache_creation_input_tokens ?? prior.input_tokens_details?.cache_creation_tokens, "cache_creation_input_tokens");
1919
+ const totalInput = chat ? input : usage.input_tokens === undefined ? input : input + cacheRead + cacheWrite;
1920
+ const reasoning = chat && usage.completion_tokens_details ? record(usage.completion_tokens_details, "completion_tokens_details").reasoning_tokens : undefined;
1921
+ return {
1922
+ input_tokens: totalInput,
1923
+ output_tokens: output,
1924
+ total_tokens: totalInput + output,
1925
+ input_tokens_details: { cached_tokens: cacheRead, ...cacheWrite ? { cache_creation_tokens: cacheWrite } : {} },
1926
+ output_tokens_details: { reasoning_tokens: tokens(reasoning ?? prior.output_tokens_details?.reasoning_tokens, "reasoning_tokens") }
1927
+ };
1928
+ }
1929
+ function isCollaborationFunction(name, namespace, custom = false) {
1930
+ return !custom && namespace === "collaboration" && typeof name === "string" && ["spawn_agent", "send_message", "followup_task"].includes(name);
1931
+ }
1932
+ function plaintextCollaborationCall(item, delivery) {
1933
+ return delivery === "plaintext" && item.type === "function_call" && isCollaborationFunction(item.name, item.namespace) ? { ...item, encrypted_function_args: [] } : item;
1934
+ }
1935
+ function toolItem(id, name, args, names, status = "completed", agentMessageDelivery) {
1936
+ const original = restoreTool(name, names);
1937
+ const item = { id: `fc_${id}`, type: original.custom ? "custom_tool_call" : "function_call", call_id: id, name: original.name, status };
1938
+ if (original.namespace)
1939
+ item.namespace = original.namespace;
1940
+ if (original.custom)
1941
+ item.input = customInput(args);
1942
+ else {
1943
+ argumentObject(args);
1944
+ item.arguments = args;
1945
+ }
1946
+ return plaintextCollaborationCall(item, agentMessageDelivery);
1947
+ }
1948
+ function responsePayload(id, model, output, end, usage, error, createdAt = Math.floor(Date.now() / 1000)) {
1949
+ const result = { id, object: "response", created_at: createdAt, model, status: end.status, output, usage: Object.keys(usage).length ? usage : null };
1950
+ if (end.status === "incomplete")
1951
+ result.incomplete_details = { reason: end.reason };
1952
+ if (end.status === "failed")
1953
+ result.error = error ?? { code: end.reason, message: end.reason };
1954
+ return result;
1955
+ }
1956
+
1957
+ // src/responses-codec/agent-messages.ts
1958
+ function exact(value, keys, path) {
1959
+ for (const key of Object.keys(value))
1960
+ if (!keys.includes(key))
1961
+ fail("unsupported_content", "Unknown agent message field", `${path}.${key}`);
1962
+ }
1963
+ function validateAgentMessage(raw, path, configuredLimits, allowEncrypted = false) {
1964
+ const budget = limits(configuredLimits);
1965
+ const item = atParam(path, () => record(raw, "agent message"));
1966
+ exact(item, ["type", "id", "author", "recipient", "content", "internal_chat_message_metadata_passthrough"], path);
1967
+ if (item.type !== "agent_message")
1968
+ fail("unsupported_content", "Expected agent message", `${path}.type`);
1969
+ if (item.id !== undefined)
1970
+ atParam(`${path}.id`, () => string(item.id, "agent message id", true));
1971
+ if (item.internal_chat_message_metadata_passthrough !== undefined)
1972
+ atParam(`${path}.internal_chat_message_metadata_passthrough`, () => record(item.internal_chat_message_metadata_passthrough, "message metadata"));
1973
+ for (const key of ["author", "recipient"])
1974
+ atParam(`${path}.${key}`, () => string(item[key], "agent identity", true));
1975
+ const content = atParam(`${path}.content`, () => list(item.content, "agent content"));
1976
+ if (content.length > budget.maxItems)
1977
+ fail("resource_limit", "Too many agent content parts", `${path}.content`);
1978
+ for (const [index, rawPart] of content.entries()) {
1979
+ const partPath = `${path}.content[${index}]`;
1980
+ const part = atParam(partPath, () => record(rawPart, "agent content part"));
1981
+ if (part.type === "encrypted_content") {
1982
+ exact(part, ["type", "encrypted_content"], partPath);
1983
+ atParam(`${partPath}.encrypted_content`, () => string(part.encrypted_content, "encrypted content", true));
1984
+ if (!allowEncrypted)
1985
+ fail("unsupported_content", "Encrypted agent content requires explicit plaintext replacement", `${partPath}.encrypted_content`);
1986
+ } else {
1987
+ exact(part, ["type", "text"], partPath);
1988
+ if (part.type !== "input_text")
1989
+ fail("unsupported_content", "Agent messages require input text", `${partPath}.type`);
1990
+ atParam(`${partPath}.text`, () => string(part.text, "agent input text"));
1991
+ }
1992
+ }
1993
+ atParam(path, () => serialized(item, budget.maxRequestBytes, "agent message"));
1994
+ return item;
1995
+ }
1996
+ function inspectAgentMessages(raw, options = {}) {
1997
+ const budget = limits(options.limits);
1998
+ const body = atParam("request", () => record(raw, "request"));
1999
+ atParam("request", () => serialized(body, budget.maxRequestBytes, "request"));
2000
+ const input = typeof body.input === "string" ? [] : atParam("input", () => list(body.input, "input"));
2001
+ if (input.length > budget.maxItems)
2002
+ fail("resource_limit", "Too many input items", "input");
2003
+ const messages = [], diagnostics = [];
2004
+ let parts = 0;
2005
+ for (const [inputIndex, rawItem] of input.entries()) {
2006
+ const item = atParam(`input[${inputIndex}]`, () => record(rawItem, "input item"));
2007
+ if (item.type !== "agent_message")
2008
+ continue;
2009
+ validateAgentMessage(item, `input[${inputIndex}]`, options.limits, true);
2010
+ const encryptedContents = [];
2011
+ for (const [contentIndex, part] of item.content.entries()) {
2012
+ if (++parts > budget.maxItems)
2013
+ fail("resource_limit", "Too many agent content parts", "input");
2014
+ if (part.type === "encrypted_content") {
2015
+ const param = `input[${inputIndex}].content[${contentIndex}].encrypted_content`;
2016
+ encryptedContents.push({ contentIndex, param });
2017
+ }
2018
+ }
2019
+ messages.push({ inputIndex, author: item.author, recipient: item.recipient, encryptedContents });
2020
+ }
2021
+ return { messages, diagnostics };
2022
+ }
2023
+ function replaceAgentMessageEncryptedContent(raw, replacements, options = {}) {
2024
+ const inspection = inspectAgentMessages(raw, options);
2025
+ const budget = limits(options.limits);
2026
+ if (!Array.isArray(replacements))
2027
+ fail("invalid_payload", "Replacements must be an array", "replacements");
2028
+ if (replacements.length > budget.maxItems)
2029
+ fail("resource_limit", "Too many replacements", "replacements");
2030
+ const locations = new Map(inspection.messages.flatMap((m) => m.encryptedContents.map((c) => [c.param, { inputIndex: m.inputIndex, contentIndex: c.contentIndex }])));
2031
+ const selected = new Set;
2032
+ let body;
2033
+ try {
2034
+ body = structuredClone(record(raw, "request"));
2035
+ } catch {
2036
+ fail("invalid_payload", "Request must be cloneable JSON", "request");
2037
+ }
2038
+ const diagnostics = [];
2039
+ for (const [i, rawReplacement] of replacements.entries()) {
2040
+ const replacement = atParam(`replacements[${i}]`, () => record(rawReplacement, "replacement"));
2041
+ exact(replacement, ["param", "text"], `replacements[${i}]`);
2042
+ const param = atParam(`replacements[${i}].param`, () => string(replacement.param, "replacement param", true));
2043
+ const text = atParam(`replacements[${i}].text`, () => string(replacement.text, "replacement text"));
2044
+ const location = locations.get(param);
2045
+ if (!location || selected.has(param))
2046
+ fail("invalid_payload", "Replacement must identify one unique encrypted agent content block", `replacements[${i}].param`);
2047
+ selected.add(param);
2048
+ record(body.input[location.inputIndex], "agent message").content[location.contentIndex] = { type: "input_text", text };
2049
+ diagnostics.push({ param, action: "mapped", reason: "agent_message_plaintext_replacement" });
2050
+ }
2051
+ atParam("request", () => serialized(body, budget.maxRequestBytes, "request"));
2052
+ return { body, diagnostics };
2053
+ }
2054
+ function validateEncryptedFunctionArgs(item, delivery, path) {
2055
+ if (item.encrypted_function_args === undefined)
2056
+ return;
2057
+ const param = `${path}.encrypted_function_args`;
2058
+ if (delivery !== "plaintext" || !isCollaborationFunction(item.name, item.namespace, item.type !== "function_call"))
2059
+ fail("unsupported_content", "Encrypted function argument marker is unavailable for this tool", param);
2060
+ const values = atParam(param, () => list(item.encrypted_function_args, "encrypted function arguments"));
2061
+ if (values.length)
2062
+ fail("unsupported_content", "Encrypted function arguments cannot cross protocols", param);
2063
+ }
2064
+
1760
2065
  // src/providers/openai/protocol-conversion.ts
1761
2066
  var THINKING_TAG_PATTERN = /<thinking>([\s\S]*?)<\/thinking>/gi;
1762
2067
  var TOOL_CALL_CONTENT_TYPES = new Set([
@@ -2336,197 +2641,6 @@ class OpenAIProtocolConversion {
2336
2641
  }
2337
2642
  }
2338
2643
 
2339
- // src/responses-codec/common.ts
2340
- var DEFAULT_RESPONSES_CODEC_LIMITS = Object.freeze({
2341
- maxRequestBytes: 8 * 1024 * 1024,
2342
- maxOutputBytes: 8 * 1024 * 1024,
2343
- maxArgumentBytes: 1024 * 1024,
2344
- maxItems: 1024
2345
- });
2346
-
2347
- class ResponsesCodecError extends Error {
2348
- code;
2349
- param;
2350
- constructor(code, message, param) {
2351
- super(message);
2352
- this.code = code;
2353
- this.param = param;
2354
- this.name = "ResponsesCodecError";
2355
- }
2356
- }
2357
- function fail(code, message, param) {
2358
- throw new ResponsesCodecError(code, message, param);
2359
- }
2360
- function atParam(param, run) {
2361
- try {
2362
- return run();
2363
- } catch (error) {
2364
- if (error instanceof ResponsesCodecError && error.param === undefined)
2365
- throw new ResponsesCodecError(error.code, error.message, param);
2366
- throw error;
2367
- }
2368
- }
2369
- function record(value, where) {
2370
- if (!value || typeof value !== "object" || Array.isArray(value))
2371
- fail("invalid_payload", `${where} must be an object`);
2372
- return value;
2373
- }
2374
- function string(value, where, nonempty = false) {
2375
- if (typeof value !== "string" || nonempty && !value)
2376
- fail("invalid_payload", `${where} must be a${nonempty ? " non-empty" : ""} string`);
2377
- return value;
2378
- }
2379
- function list(value, where) {
2380
- if (!Array.isArray(value))
2381
- fail("invalid_payload", `${where} must be an array`);
2382
- return value;
2383
- }
2384
- function limits(overrides) {
2385
- const result = { ...DEFAULT_RESPONSES_CODEC_LIMITS, ...overrides };
2386
- for (const [key, value] of Object.entries(result)) {
2387
- if (!Number.isSafeInteger(value) || value <= 0)
2388
- fail("invalid_limit", `${key} must be a positive integer`);
2389
- }
2390
- return result;
2391
- }
2392
- function bytes(value) {
2393
- return new TextEncoder().encode(value).byteLength;
2394
- }
2395
- function bounded(value, max, where) {
2396
- if (bytes(value) > max)
2397
- fail("resource_limit", `${where} exceeds ${max} bytes`);
2398
- return value;
2399
- }
2400
- function serialized(value, max, where) {
2401
- let result;
2402
- try {
2403
- result = JSON.stringify(value);
2404
- } catch {
2405
- fail("invalid_payload", `${where} must be serializable JSON`);
2406
- }
2407
- if (result === undefined)
2408
- fail("invalid_payload", `${where} must be serializable JSON`);
2409
- return bounded(result, max, where);
2410
- }
2411
- function argumentObject(text) {
2412
- let value;
2413
- try {
2414
- value = JSON.parse(text);
2415
- } catch {
2416
- fail("invalid_tool_arguments", "Tool arguments must be complete JSON");
2417
- }
2418
- return record(value, "tool arguments");
2419
- }
2420
- function restoreTool(name, names) {
2421
- const result = names.get(name);
2422
- if (result)
2423
- return result;
2424
- if (names.size)
2425
- fail("unknown_tool", `Provider returned undeclared tool ${name}`);
2426
- return { name, custom: false };
2427
- }
2428
- function customInput(text) {
2429
- const value = argumentObject(text);
2430
- if (Object.keys(value).length !== 1 || typeof value.input !== "string") {
2431
- fail("invalid_tool_arguments", "Custom tools require exactly one string input property");
2432
- }
2433
- return value.input;
2434
- }
2435
- function customInputPrefix(text) {
2436
- const prefix = /^\s*\{\s*"input"\s*:\s*"/.exec(text);
2437
- if (!prefix)
2438
- return "";
2439
- let result = "";
2440
- for (let i = prefix[0].length;i < text.length; ) {
2441
- const char = text[i];
2442
- if (char === '"')
2443
- break;
2444
- if (char !== "\\") {
2445
- if (char.charCodeAt(0) < 32)
2446
- fail("invalid_tool_arguments", "Invalid JSON string character");
2447
- result += char;
2448
- i++;
2449
- continue;
2450
- }
2451
- const escape = text[i + 1];
2452
- if (escape === undefined)
2453
- break;
2454
- const size = escape === "u" ? 6 : 2;
2455
- if (i + size > text.length)
2456
- break;
2457
- try {
2458
- result += JSON.parse(`"${text.slice(i, i + size)}"`);
2459
- } catch {
2460
- fail("invalid_tool_arguments", "Invalid JSON string escape");
2461
- }
2462
- i += size;
2463
- }
2464
- return result;
2465
- }
2466
- function terminal(reason, protocol) {
2467
- if (typeof reason !== "string" || !reason)
2468
- fail("missing_terminal", "Upstream has no explicit completion reason");
2469
- const completed = protocol === "chat_completions" ? ["stop", "tool_calls", "function_call"] : ["end_turn", "tool_use", "stop_sequence"];
2470
- if (completed.includes(reason))
2471
- return { status: "completed", reason };
2472
- if (reason === "length" || reason === "max_tokens" || reason === "model_context_window_exceeded")
2473
- return { status: "incomplete", reason: "max_output_tokens" };
2474
- if (reason === "content_filter" || reason === "refusal")
2475
- return { status: "incomplete", reason: "content_filter" };
2476
- if (reason === "error")
2477
- return { status: "failed", reason: "upstream_error" };
2478
- fail("unknown_terminal", `Unsupported upstream completion reason: ${reason}`);
2479
- }
2480
- function tokens(value, where) {
2481
- if (value === undefined)
2482
- return 0;
2483
- if (!Number.isSafeInteger(value) || value < 0)
2484
- fail("invalid_usage", `${where} must be a non-negative integer`);
2485
- return value;
2486
- }
2487
- function normalizeUsage(raw, protocol, prior = {}) {
2488
- if (raw === undefined || raw === null)
2489
- return prior;
2490
- const usage = record(raw, "usage");
2491
- const chat = protocol === "chat_completions";
2492
- const input = tokens(usage[chat ? "prompt_tokens" : "input_tokens"] ?? prior.input_tokens, "input_tokens");
2493
- const output = tokens(usage[chat ? "completion_tokens" : "output_tokens"] ?? prior.output_tokens, "output_tokens");
2494
- const details = chat ? usage.prompt_tokens_details : undefined;
2495
- const cached = chat ? details ? record(details, "prompt_tokens_details").cached_tokens : undefined : usage.cache_read_input_tokens;
2496
- const cacheRead = tokens(cached ?? prior.input_tokens_details?.cached_tokens, "cached_tokens");
2497
- const cacheWrite = chat ? 0 : tokens(usage.cache_creation_input_tokens ?? prior.input_tokens_details?.cache_creation_tokens, "cache_creation_input_tokens");
2498
- const totalInput = chat ? input : usage.input_tokens === undefined ? input : input + cacheRead + cacheWrite;
2499
- const reasoning = chat && usage.completion_tokens_details ? record(usage.completion_tokens_details, "completion_tokens_details").reasoning_tokens : undefined;
2500
- return {
2501
- input_tokens: totalInput,
2502
- output_tokens: output,
2503
- total_tokens: totalInput + output,
2504
- input_tokens_details: { cached_tokens: cacheRead, ...cacheWrite ? { cache_creation_tokens: cacheWrite } : {} },
2505
- output_tokens_details: { reasoning_tokens: tokens(reasoning ?? prior.output_tokens_details?.reasoning_tokens, "reasoning_tokens") }
2506
- };
2507
- }
2508
- function toolItem(id, name, args, names, status = "completed") {
2509
- const original = restoreTool(name, names);
2510
- const item = { id: `fc_${id}`, type: original.custom ? "custom_tool_call" : "function_call", call_id: id, name: original.name, status };
2511
- if (original.namespace)
2512
- item.namespace = original.namespace;
2513
- if (original.custom)
2514
- item.input = customInput(args);
2515
- else {
2516
- argumentObject(args);
2517
- item.arguments = args;
2518
- }
2519
- return item;
2520
- }
2521
- function responsePayload(id, model, output, end, usage, error, createdAt = Math.floor(Date.now() / 1000)) {
2522
- const result = { id, object: "response", created_at: createdAt, model, status: end.status, output, usage: Object.keys(usage).length ? usage : null };
2523
- if (end.status === "incomplete")
2524
- result.incomplete_details = { reason: end.reason };
2525
- if (end.status === "failed")
2526
- result.error = error ?? { code: end.reason, message: end.reason };
2527
- return result;
2528
- }
2529
-
2530
2644
  // src/responses-codec/request-preferences.ts
2531
2645
  function fields(value, allowed, path) {
2532
2646
  for (const key of Object.keys(value))
@@ -2660,6 +2774,8 @@ function definitionKey(value) {
2660
2774
  }
2661
2775
  function decodeResponsesRequest(raw, protocol, capabilities = {}) {
2662
2776
  const body = record(raw, "Responses request");
2777
+ if (capabilities.agentMessageDelivery !== undefined && capabilities.agentMessageDelivery !== "plaintext")
2778
+ fail("invalid_payload", "Unknown agent message delivery policy", "agentMessageDelivery");
2663
2779
  const budget = limits(capabilities.limits);
2664
2780
  serialized(body, budget.maxRequestBytes, "request");
2665
2781
  const supported = new Set([
@@ -2784,7 +2900,18 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
2784
2900
  }
2785
2901
  const wire = register(name, namespace, custom);
2786
2902
  definitions.set(key, signature);
2787
- const parameters = custom ? { type: "object", properties: { input: { type: "string" } }, required: ["input"], additionalProperties: false } : record(tool.parameters ?? { type: "object", properties: {} }, "tool parameters");
2903
+ let parameters = custom ? { type: "object", properties: { input: { type: "string" } }, required: ["input"], additionalProperties: false } : record(tool.parameters ?? { type: "object", properties: {} }, "tool parameters");
2904
+ if (capabilities.agentMessageDelivery === "plaintext" && isCollaborationFunction(name, namespace, custom)) {
2905
+ const properties = parameters.properties;
2906
+ if (properties && typeof properties === "object" && !Array.isArray(properties)) {
2907
+ const message = properties.message;
2908
+ if (message && typeof message === "object" && !Array.isArray(message) && message.encrypted === true) {
2909
+ const { encrypted: _encrypted, ...plaintext } = message;
2910
+ parameters = { ...parameters, properties: { ...properties, message: plaintext } };
2911
+ diagnostics.push({ param: `${path}.parameters.properties.message.encrypted`, action: "omitted", reason: "collaboration_message_plaintext_delivery" });
2912
+ }
2913
+ }
2914
+ }
2788
2915
  const declaredDescription = tool.description === undefined ? undefined : string(tool.description, "tool description");
2789
2916
  const identityHint = capabilities.preserveToolIdentityDescription ? `Original tool identity: ${JSON.stringify({ name, ...namespace ? { namespace } : {} })}` : undefined;
2790
2917
  const description = [identityHint, namespaceDescription, declaredDescription].filter((value) => value !== undefined).join(`
@@ -2889,7 +3016,20 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
2889
3016
  const item = record(rawItem, "input item");
2890
3017
  if (item.type === "additional_tools")
2891
3018
  return;
2892
- if (item.encrypted_content != null || item.type === "compaction")
3019
+ if (item.type === "agent_message") {
3020
+ validateAgentMessage(item, messageParam, capabilities.limits);
3021
+ if (capabilities.agentMessageDelivery !== "plaintext")
3022
+ fail("unsupported_content", "Agent messages require explicit plaintext delivery", messageParam);
3023
+ if (pendingReasoning)
3024
+ fail("unsupported_reasoning", "Reasoning must precede an assistant item", messageParam);
3025
+ addMessage({ role: "user", content: [{ type: "text", text: `[Agent message metadata: ${JSON.stringify({ author: item.author, recipient: item.recipient })}]
3026
+ ` }, ...item.content.map((part) => ({ type: "text", text: part.text }))] });
3027
+ diagnostics.push({ param: messageParam, action: "mapped", reason: "agent_message_plaintext_delivery" });
3028
+ return;
3029
+ }
3030
+ if (item.encrypted_content != null)
3031
+ fail("unsupported_content", "Encrypted content cannot cross protocols", `${messageParam}.encrypted_content`);
3032
+ if (item.type === "compaction")
2893
3033
  fail("unsupported_content", "Encrypted reasoning and compaction cannot be restored");
2894
3034
  if (item.type === "item_reference")
2895
3035
  fail("unresolved_reference", "Input item references must be resolved before conversion");
@@ -2909,6 +3049,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
2909
3049
  }
2910
3050
  if (item.type === "function_call" || item.type === "custom_tool_call") {
2911
3051
  const custom = item.type === "custom_tool_call";
3052
+ validateEncryptedFunctionArgs(item, capabilities.agentMessageDelivery, messageParam);
2912
3053
  const id = string(item.call_id, "call_id", true);
2913
3054
  if (callIds.has(id))
2914
3055
  fail("invalid_tool_history", "Duplicate tool call identifier");
@@ -3127,9 +3268,9 @@ class ResponsesEventEncoder {
3127
3268
  item(state, status, strict = false) {
3128
3269
  if (state.kind === "tool") {
3129
3270
  if (strict)
3130
- return { ...toolItem(state.callId, state.name, state.args || "{}", this.toolNames, status), id: state.id };
3271
+ return { ...toolItem(state.callId, state.name, state.args || "{}", this.toolNames, status, this.options.agentMessageDelivery), id: state.id };
3131
3272
  const original = state.original ?? restoreTool(state.name, this.toolNames);
3132
- return {
3273
+ return plaintextCollaborationCall({
3133
3274
  id: state.id,
3134
3275
  type: original.custom ? "custom_tool_call" : "function_call",
3135
3276
  call_id: state.callId,
@@ -3137,7 +3278,7 @@ class ResponsesEventEncoder {
3137
3278
  ...original.namespace ? { namespace: original.namespace } : {},
3138
3279
  status,
3139
3280
  ...original.custom ? { input: state.emittedInput } : { arguments: state.args }
3140
- };
3281
+ }, this.options.agentMessageDelivery);
3141
3282
  }
3142
3283
  if (state.kind === "reasoning")
3143
3284
  return { id: state.id, type: "reasoning", summary: status === "in_progress" ? [] : [{ type: "summary_text", text: state.text }] };
@@ -3450,13 +3591,13 @@ class ResponsesEventEncoder {
3450
3591
  }
3451
3592
 
3452
3593
  // src/responses-codec/result.ts
3453
- function encodeResponsesResult(raw, protocol, model, toolNames = new Map, configuredLimits) {
3594
+ function encodeResponsesResult(raw, protocol, model, toolNames = new Map, configuredLimits, options = {}) {
3454
3595
  const body = record(raw, "upstream response");
3455
3596
  const budget = limits(configuredLimits);
3456
3597
  serialized(body, budget.maxOutputBytes, "upstream response");
3457
3598
  if (body.error !== undefined || body.type === "error")
3458
3599
  return responsePayload(`resp_${crypto.randomUUID().replaceAll("-", "")}`, model, [], { status: "failed", reason: "upstream_error" }, {}, record(body.error, "upstream error"));
3459
- const encoder = new ResponsesEventEncoder(protocol, model, toolNames, configuredLimits);
3600
+ const encoder = new ResponsesEventEncoder(protocol, model, toolNames, configuredLimits, options);
3460
3601
  if (protocol === "chat_completions") {
3461
3602
  const choices = list(body.choices, "choices");
3462
3603
  if (choices.length !== 1)
@@ -3874,7 +4015,7 @@ function canonicalRequest(raw, protocol, model) {
3874
4015
  }
3875
4016
  return out;
3876
4017
  }
3877
- function validateCanonical(b) {
4018
+ function validateCanonical(b, context) {
3878
4019
  for (const key of ["stream", "parallel_tool_calls", "store", "background"])
3879
4020
  if (b[key] !== undefined)
3880
4021
  bool(b[key], key);
@@ -3904,7 +4045,11 @@ function validateCanonical(b) {
3904
4045
  const input = typeof b.input === "string" ? [] : list(b.input, "input");
3905
4046
  for (const [i, raw] of input.entries()) {
3906
4047
  const t = record(raw, "input item"), p = `input[${i}]`;
3907
- const allowed = t.type === "function_call" ? ["type", "id", "status", "call_id", "name", "namespace", "arguments"] : t.type === "custom_tool_call" ? ["type", "id", "status", "call_id", "name", "namespace", "input"] : t.type === "function_call_output" || t.type === "custom_tool_call_output" ? ["type", "id", "call_id", "output", "is_error"] : t.type === "reasoning" ? ["type", "id", "status", "summary", "content", "encrypted_content", "internal_chat_message_metadata_passthrough"] : t.type === "additional_tools" ? ["type", "id", "role", "tools"] : ["type", "id", "status", "role", "content", "internal_chat_message_metadata_passthrough"];
4048
+ if (t.type === "agent_message") {
4049
+ validateAgentMessage(t, p, context.capabilities?.limits);
4050
+ continue;
4051
+ }
4052
+ const allowed = t.type === "function_call" ? ["type", "id", "status", "call_id", "name", "namespace", "arguments", "encrypted_function_args"] : t.type === "custom_tool_call" ? ["type", "id", "status", "call_id", "name", "namespace", "input"] : t.type === "function_call_output" || t.type === "custom_tool_call_output" ? ["type", "id", "call_id", "output", "is_error"] : t.type === "reasoning" ? ["type", "id", "status", "summary", "content", "encrypted_content", "internal_chat_message_metadata_passthrough"] : t.type === "additional_tools" ? ["type", "id", "role", "tools"] : ["type", "id", "status", "role", "content", "internal_chat_message_metadata_passthrough"];
3908
4053
  fields2(t, [...allowed, "internal_chat_message_metadata_passthrough"], p, "unsupported_content");
3909
4054
  if (t.is_error !== undefined)
3910
4055
  bool(t.is_error, `${p}.is_error`);
@@ -3954,8 +4099,8 @@ function convertProtocolRequest(raw, context) {
3954
4099
  fail("invalid_payload", "Request stream mode disagrees with transport context", "stream");
3955
4100
  b.stream = context.streaming;
3956
4101
  }
3957
- validateCanonical(b);
3958
- const cap = { ...context.capabilities, omitOptionalWebSearch: true, preserveToolIdentityDescription: true, preserveRefusalContent: true }, policy = context.reasoningPolicy;
4102
+ validateCanonical(b, context);
4103
+ const cap = { ...context.capabilities, agentMessageDelivery: context.agentMessageDelivery ?? context.capabilities?.agentMessageDelivery, omitOptionalWebSearch: true, preserveToolIdentityDescription: true, preserveRefusalContent: true }, policy = context.reasoningPolicy;
3959
4104
  const reasoning = b.reasoning === undefined ? undefined : record(b.reasoning, "reasoning");
3960
4105
  const sourceThinking = b.sourceThinking;
3961
4106
  delete b.sourceThinking;
@@ -4242,7 +4387,7 @@ function validateCanonicalResponse(raw, context, itemOnly = false) {
4242
4387
  fail("unsupported_response", "Unknown item status", `${path}.status`);
4243
4388
  if (b.status === "completed" && t.status !== undefined && t.status !== "completed")
4244
4389
  fail("invalid_stream", "Completed response contains an unfinished item", `${path}.status`);
4245
- fields2(t, t.type === "message" ? ["type", "id", "role", "status", "content"] : t.type === "reasoning" ? ["type", "id", "summary", "status", "encrypted_content", "content"] : t.type === "function_call" ? ["type", "id", "status", "name", "namespace", "call_id", "arguments"] : ["type", "id", "status", "name", "namespace", "call_id", "input"], path, "unsupported_content");
4390
+ fields2(t, t.type === "message" ? ["type", "id", "role", "status", "content"] : t.type === "reasoning" ? ["type", "id", "summary", "status", "encrypted_content", "content"] : t.type === "function_call" ? ["type", "id", "status", "name", "namespace", "call_id", "arguments", "encrypted_function_args"] : ["type", "id", "status", "name", "namespace", "call_id", "input"], path, "unsupported_content");
4246
4391
  if (t.type === "message") {
4247
4392
  if (t.role !== "assistant")
4248
4393
  fail("unsupported_content", "Output message must be assistant", `${path}.role`);
@@ -4269,6 +4414,7 @@ function validateCanonicalResponse(raw, context, itemOnly = false) {
4269
4414
  string(p.text, "summary");
4270
4415
  }
4271
4416
  } else if (t.type === "function_call" || t.type === "custom_tool_call") {
4417
+ validateEncryptedFunctionArgs(t, context.capabilities?.agentMessageDelivery, path);
4272
4418
  const id = string(t.call_id, "call_id", true);
4273
4419
  if (ids.has(id))
4274
4420
  fail("invalid_tool_arguments", "Duplicate output tool call id", path);
@@ -4479,7 +4625,7 @@ function canonicalResponse(raw, context, names) {
4479
4625
  return record(events.at(-1)?.response, "response");
4480
4626
  }
4481
4627
  validateProviderResponse(raw, context.targetProtocol);
4482
- const result = encodeResponsesResult(raw, context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits);
4628
+ const result = encodeResponsesResult(raw, context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits, { agentMessageDelivery: context.capabilities?.agentMessageDelivery });
4483
4629
  if (result.status === "failed")
4484
4630
  result.error = safeProtocolError();
4485
4631
  const usage = record(raw, "response").usage;
@@ -4874,6 +5020,10 @@ function createProtocolSession(context) {
4874
5020
  capabilities: context.capabilities ? { ...context.capabilities, limits: context.capabilities.limits ? { ...context.capabilities.limits } : undefined } : undefined,
4875
5021
  reasoningPolicy: context.reasoningPolicy ? { ...context.reasoningPolicy, effortMap: context.reasoningPolicy.effortMap ? { ...context.reasoningPolicy.effortMap } : undefined } : undefined
4876
5022
  };
5023
+ const delivery = context.agentMessageDelivery ?? context.capabilities?.agentMessageDelivery;
5024
+ if (delivery !== undefined && delivery !== "plaintext")
5025
+ fail("invalid_payload", "Unknown agent message delivery policy", "agentMessageDelivery");
5026
+ context.capabilities = { ...context.capabilities, agentMessageDelivery: delivery };
4877
5027
  if (!LLM_PROTOCOLS.includes(context.sourceProtocol) || !LLM_PROTOCOLS.includes(context.targetProtocol))
4878
5028
  fail("unsupported_protocol", "Unknown protocol");
4879
5029
  if (typeof context.model !== "string" || !context.model)
@@ -4912,7 +5062,7 @@ function createProtocolSession(context) {
4912
5062
  else if (context.targetProtocol === "gemini_generate_content")
4913
5063
  encoder = new GeminiCanonicalDecoder(context, names);
4914
5064
  else
4915
- encoder = new ResponsesEventEncoder(context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits, { preserveContentOrder: true });
5065
+ encoder = new ResponsesEventEncoder(context.targetProtocol, context.responseModel ?? context.model, names, context.capabilities?.limits, { preserveContentOrder: true, agentMessageDelivery: context.capabilities?.agentMessageDelivery });
4916
5066
  validator = context.targetProtocol === "responses" ? undefined : new CanonicalStreamValidator(context);
4917
5067
  renderer = new CanonicalStreamRenderer(context.sourceProtocol, context);
4918
5068
  };
@@ -5105,5 +5255,7 @@ export {
5105
5255
  decodeResponsesRequest,
5106
5256
  describeProtocolConversion,
5107
5257
  encodeResponsesResult,
5108
- getProviderTokenAccountingCapabilities
5258
+ getProviderTokenAccountingCapabilities,
5259
+ inspectAgentMessages,
5260
+ replaceAgentMessageEncryptedContent
5109
5261
  };
@@ -19,6 +19,8 @@ export interface ProtocolSessionCapabilities extends ResponsesCodecCapabilities
19
19
  geminiJsonSchema?: boolean;
20
20
  }
21
21
  export interface ProtocolSessionContext {
22
+ /** Request-scoped opt-in; absence preserves strict collaboration handling. */
23
+ agentMessageDelivery?: ResponsesCodecCapabilities['agentMessageDelivery'];
22
24
  sourceProtocol: LLMProtocol;
23
25
  targetProtocol: LLMProtocol;
24
26
  /** Target model; caller owns routing and source URL validation. */
@@ -0,0 +1,33 @@
1
+ import { type JsonRecord, type ResponsesCodecLimits, type ResponsesConversionDiagnostic } from './common';
2
+ export type AgentMessageDelivery = 'plaintext';
3
+ export interface AgentMessageInspection {
4
+ inputIndex: number;
5
+ author: string;
6
+ recipient: string;
7
+ encryptedContents: Array<{
8
+ contentIndex: number;
9
+ param: string;
10
+ }>;
11
+ }
12
+ export interface AgentMessagePlaintextReplacement {
13
+ param: string;
14
+ text: string;
15
+ }
16
+ export interface AgentMessageInspectionResult {
17
+ messages: AgentMessageInspection[];
18
+ diagnostics: ResponsesConversionDiagnostic[];
19
+ }
20
+ /** Validate identities as metadata, never as instruction roles. Inspection permits opaque blocks only to locate them. */
21
+ export declare function validateAgentMessage(raw: unknown, path: string, configuredLimits?: Partial<ResponsesCodecLimits>, allowEncrypted?: boolean): JsonRecord;
22
+ /** Pure bounded inspection. Neither plaintext nor opaque content is returned in diagnostics. */
23
+ export declare function inspectAgentMessages(raw: unknown, options?: {
24
+ limits?: Partial<ResponsesCodecLimits>;
25
+ }): AgentMessageInspectionResult;
26
+ /** Replace only explicitly selected agent opaque blocks, at their original positions. Other history is untouched. */
27
+ export declare function replaceAgentMessageEncryptedContent(raw: unknown, replacements: readonly AgentMessagePlaintextReplacement[], options?: {
28
+ limits?: Partial<ResponsesCodecLimits>;
29
+ }): {
30
+ body: JsonRecord;
31
+ diagnostics: ResponsesConversionDiagnostic[];
32
+ };
33
+ export declare function validateEncryptedFunctionArgs(item: JsonRecord, delivery: AgentMessageDelivery | undefined, path: string): void;
@@ -1,3 +1,4 @@
1
+ import type { AgentMessageDelivery } from './agent-messages';
1
2
  export type ResponsesProtocol = 'chat_completions' | 'anthropic_messages';
2
3
  export type JsonRecord = Record<string, unknown>;
3
4
  export interface ResponsesToolName {
@@ -15,6 +16,8 @@ export interface ResponsesCodecLimits {
15
16
  }
16
17
  export declare const DEFAULT_RESPONSES_CODEC_LIMITS: Readonly<ResponsesCodecLimits>;
17
18
  export interface ResponsesCodecCapabilities {
19
+ /** Explicit caller policy for plaintext collaboration across protocols. */
20
+ agentMessageDelivery?: AgentMessageDelivery;
18
21
  reasoningEffort?: boolean;
19
22
  /** Plain reasoning history support is independent of effort parameter mapping. */
20
23
  reasoningHistory?: boolean;
@@ -59,5 +62,7 @@ export interface Terminal {
59
62
  }
60
63
  export declare function terminal(reason: unknown, protocol: ResponsesProtocol): Terminal;
61
64
  export declare function normalizeUsage(raw: unknown, protocol: ResponsesProtocol, prior?: JsonRecord): JsonRecord;
62
- export declare function toolItem(id: string, name: string, args: string, names: ResponsesToolNames, status?: string): JsonRecord;
65
+ export declare function isCollaborationFunction(name: unknown, namespace: unknown, custom?: boolean): boolean;
66
+ export declare function plaintextCollaborationCall(item: JsonRecord, delivery?: AgentMessageDelivery): JsonRecord;
67
+ export declare function toolItem(id: string, name: string, args: string, names: ResponsesToolNames, status?: string, agentMessageDelivery?: AgentMessageDelivery): JsonRecord;
63
68
  export declare function responsePayload(id: string, model: string, output: JsonRecord[], end: Terminal, usage: JsonRecord, error?: unknown, createdAt?: number): JsonRecord;
@@ -1,3 +1,4 @@
1
+ import type { AgentMessageDelivery } from './agent-messages';
1
2
  import { type JsonRecord, type ResponsesCodecLimits, type ResponsesProtocol, type ResponsesToolNames } from './common';
2
3
  /** Incremental payload codec. SSE framing and transport cancellation remain caller-owned.
3
4
  * Chat terminal is deferred until finish() so trailing usage-only chunks are retained.
@@ -28,6 +29,7 @@ export declare class ResponsesEventEncoder {
28
29
  private contentSerial;
29
30
  constructor(protocol: ResponsesProtocol, model: string, toolNames?: ResponsesToolNames, configuredLimits?: Partial<ResponsesCodecLimits>, options?: {
30
31
  preserveContentOrder?: boolean;
32
+ agentMessageDelivery?: AgentMessageDelivery;
31
33
  });
32
34
  private contentState;
33
35
  private event;
@@ -2,3 +2,4 @@ export { decodeResponsesRequest, type DecodedResponsesRequest } from './request'
2
2
  export { encodeResponsesResult } from './result';
3
3
  export { ResponsesEventEncoder } from './events';
4
4
  export { ResponsesCodecError, DEFAULT_RESPONSES_CODEC_LIMITS, type ResponsesProtocol, type ResponsesToolName, type ResponsesToolNames, type ResponsesCodecCapabilities, type ResponsesCodecLimits, type ResponsesConversionDiagnostic } from './common';
5
+ export { inspectAgentMessages, replaceAgentMessageEncryptedContent, type AgentMessageDelivery, type AgentMessageInspection, type AgentMessageInspectionResult, type AgentMessagePlaintextReplacement } from './agent-messages';
@@ -1,3 +1,6 @@
1
+ import type { AgentMessageDelivery } from './agent-messages';
1
2
  import { type JsonRecord, type ResponsesCodecLimits, type ResponsesProtocol, type ResponsesToolNames } from './common';
2
3
  /** JSON and SSE share the same validation and item/terminal semantics. */
3
- export declare function encodeResponsesResult(raw: unknown, protocol: ResponsesProtocol, model: string, toolNames?: ResponsesToolNames, configuredLimits?: Partial<ResponsesCodecLimits>): JsonRecord;
4
+ export declare function encodeResponsesResult(raw: unknown, protocol: ResponsesProtocol, model: string, toolNames?: ResponsesToolNames, configuredLimits?: Partial<ResponsesCodecLimits>, options?: {
5
+ agentMessageDelivery?: AgentMessageDelivery;
6
+ }): JsonRecord;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeffusion/bungee-llms",
3
- "version": "5.22.0",
3
+ "version": "5.23.0",
4
4
  "description": "Extensible LLM adapters and protocol conversion package for Bungee",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,7 @@
11
11
  "prepublishOnly": "bun run build"
12
12
  },
13
13
  "dependencies": {
14
- "@jeffusion/bungee-types": "^5.22.0"
14
+ "@jeffusion/bungee-types": "^5.23.0"
15
15
  },
16
16
  "devDependencies": {
17
17
  "bun-types": "^1.3.2",