@jeffusion/bungee-llms 5.20.0 → 5.21.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
@@ -8509,14 +8509,25 @@ var DEFAULT_RESPONSES_CODEC_LIMITS = Object.freeze({
8509
8509
 
8510
8510
  class ResponsesCodecError extends Error {
8511
8511
  code;
8512
- constructor(code, message) {
8512
+ param;
8513
+ constructor(code, message, param) {
8513
8514
  super(message);
8514
8515
  this.code = code;
8516
+ this.param = param;
8515
8517
  this.name = "ResponsesCodecError";
8516
8518
  }
8517
8519
  }
8518
- function fail(code, message) {
8519
- throw new ResponsesCodecError(code, message);
8520
+ function fail(code, message, param) {
8521
+ throw new ResponsesCodecError(code, message, param);
8522
+ }
8523
+ function atParam(param, run) {
8524
+ try {
8525
+ return run();
8526
+ } catch (error) {
8527
+ if (error instanceof ResponsesCodecError && error.param === undefined)
8528
+ throw new ResponsesCodecError(error.code, error.message, param);
8529
+ throw error;
8530
+ }
8520
8531
  }
8521
8532
  function record(value, where) {
8522
8533
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -8679,8 +8690,94 @@ function responsePayload(id, model, output, end, usage, error, createdAt = Math.
8679
8690
  return result;
8680
8691
  }
8681
8692
 
8693
+ // src/responses-codec/request-preferences.ts
8694
+ function fields(value, allowed, path) {
8695
+ for (const key of Object.keys(value))
8696
+ if (!allowed.includes(key)) {
8697
+ fail("unsupported_request", "Unrecognized request option", `${path}.${key}`);
8698
+ }
8699
+ }
8700
+ function applyResponsesPreferences(body, result, protocol, capabilities, maxTokens, diagnostics) {
8701
+ const note = (param, action, reason) => diagnostics.push({ param, action, reason });
8702
+ if (body.stream_options !== undefined)
8703
+ atParam("stream_options", () => {
8704
+ const options = record(body.stream_options, "stream_options");
8705
+ fields(options, ["include_usage", "reasoning_summary_delivery"], "stream_options");
8706
+ if (options.include_usage !== undefined) {
8707
+ if (typeof options.include_usage !== "boolean")
8708
+ fail("invalid_payload", "Usage preference must be boolean", "stream_options.include_usage");
8709
+ note("stream_options.include_usage", protocol === "chat_completions" && body.stream === true ? "mapped" : "omitted", protocol === "chat_completions" && body.stream === true ? "stream_usage_required" : "target_usage_events");
8710
+ }
8711
+ if (options.reasoning_summary_delivery !== undefined) {
8712
+ if (options.reasoning_summary_delivery !== "sequential_cutoff")
8713
+ fail("unsupported_request", "Unsupported summary delivery preference", "stream_options.reasoning_summary_delivery");
8714
+ note("stream_options.reasoning_summary_delivery", "omitted", "source_summary_delivery_not_applicable");
8715
+ }
8716
+ });
8717
+ if (body.access_programs !== undefined)
8718
+ atParam("access_programs", () => {
8719
+ const programs = record(body.access_programs, "access_programs");
8720
+ fields(programs, ["cyber"], "access_programs");
8721
+ if (programs.cyber !== undefined && programs.cyber !== "standard") {
8722
+ if (programs.cyber === "daybreak_blue" || programs.cyber === "daybreak_red") {
8723
+ fail("unsupported_access_program", "Selected access program cannot be represented by the target protocol", "access_programs.cyber");
8724
+ }
8725
+ fail("invalid_payload", "Invalid access program", "access_programs.cyber");
8726
+ }
8727
+ note("access_programs", "omitted", "target_uses_own_access_policy");
8728
+ });
8729
+ if (body.reasoning !== undefined)
8730
+ atParam("reasoning", () => {
8731
+ const reasoning = record(body.reasoning, "reasoning");
8732
+ fields(reasoning, ["effort", "summary", "context"], "reasoning");
8733
+ if (reasoning.summary !== undefined) {
8734
+ if (typeof reasoning.summary !== "string" || !["none", "auto", "concise", "detailed"].includes(reasoning.summary))
8735
+ fail("invalid_payload", "Invalid reasoning summary preference", "reasoning.summary");
8736
+ note("reasoning.summary", "omitted", "target_summary_not_guaranteed");
8737
+ }
8738
+ if (reasoning.context !== undefined) {
8739
+ if (reasoning.context !== "all_turns")
8740
+ fail("unsupported_reasoning", "Unsupported reasoning context preference", "reasoning.context");
8741
+ note("reasoning.context", "omitted", "source_reasoning_context_not_applicable");
8742
+ }
8743
+ if (reasoning.effort !== undefined)
8744
+ atParam("reasoning.effort", () => {
8745
+ const effort = string(reasoning.effort, "reasoning effort", true);
8746
+ if (protocol === "chat_completions" && capabilities.reasoningEffort) {
8747
+ result.reasoning_effort = effort;
8748
+ note("reasoning.effort", "mapped", "target_reasoning_effort");
8749
+ } else if (protocol === "anthropic_messages" && capabilities.anthropicThinkingBudget !== undefined && effort !== "none") {
8750
+ const budget = capabilities.anthropicThinkingBudget;
8751
+ if (!Number.isSafeInteger(budget) || budget < 1024 || typeof maxTokens !== "number" || budget >= maxTokens) {
8752
+ fail("unsupported_reasoning", "Anthropic thinking requires an explicit budget below max_output_tokens");
8753
+ }
8754
+ result.thinking = { type: "enabled", budget_tokens: budget };
8755
+ note("reasoning.effort", "mapped", "explicit_target_thinking_budget");
8756
+ } else
8757
+ note("reasoning.effort", "omitted", "target_reasoning_effort_unavailable");
8758
+ });
8759
+ });
8760
+ if (body.text !== undefined)
8761
+ atParam("text", () => {
8762
+ const text = record(body.text, "text");
8763
+ fields(text, ["verbosity", "format"], "text");
8764
+ if (text.verbosity !== undefined) {
8765
+ if (typeof text.verbosity !== "string" || !["low", "medium", "high"].includes(text.verbosity))
8766
+ fail("invalid_payload", "Invalid text verbosity preference", "text.verbosity");
8767
+ note("text.verbosity", "omitted", "target_verbosity_not_available");
8768
+ }
8769
+ });
8770
+ }
8771
+
8682
8772
  // src/responses-codec/request.ts
8683
8773
  var contentNormalizer = new OpenAIProtocolConversion({ trimWhitespace: false });
8774
+ function definitionKey(value) {
8775
+ if (Array.isArray(value))
8776
+ return `[${value.map(definitionKey).join(",")}]`;
8777
+ if (value && typeof value === "object")
8778
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${definitionKey(value[key])}`).join(",")}}`;
8779
+ return JSON.stringify(value);
8780
+ }
8684
8781
  function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8685
8782
  const body = record(raw, "Responses request");
8686
8783
  const budget = limits(capabilities.limits);
@@ -8706,11 +8803,15 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8706
8803
  "n",
8707
8804
  "truncation",
8708
8805
  "include",
8709
- "store"
8806
+ "store",
8807
+ "prompt_cache_key",
8808
+ "client_metadata",
8809
+ "stream_options",
8810
+ "access_programs"
8710
8811
  ]);
8711
8812
  for (const key of Object.keys(body))
8712
8813
  if (!supported.has(key))
8713
- fail("unsupported_request", `Unsupported Responses request field ${key}`);
8814
+ fail("unsupported_request", "Unrecognized Responses request field", key);
8714
8815
  if (body.store === true)
8715
8816
  fail("unsupported_request", "Stored Responses require caller-owned persistence");
8716
8817
  for (const key of ["previous_response_id", "conversation", "response_id"]) {
@@ -8723,17 +8824,28 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8723
8824
  fail("unsupported_request", "Only one response candidate is supported");
8724
8825
  if (body.truncation !== undefined && body.truncation !== "disabled")
8725
8826
  fail("unsupported_request", "Automatic input truncation cannot be represented");
8726
- if (body.include !== undefined && list(body.include, "include").length)
8827
+ if (body.include !== undefined && list(body.include, "include").some((field) => field !== "reasoning.encrypted_content"))
8727
8828
  fail("unsupported_request", "Requested extra Responses output fields cannot be represented");
8829
+ if (body.prompt_cache_key !== undefined)
8830
+ string(body.prompt_cache_key, "prompt_cache_key");
8831
+ if (body.client_metadata !== undefined)
8832
+ record(body.client_metadata, "client_metadata");
8728
8833
  const model = string(body.model, "model", true);
8834
+ const diagnostics = [];
8835
+ const input = atParam("input", () => typeof body.input === "string" ? [{ role: "user", content: body.input }] : list(body.input, "input"));
8836
+ if (input.length > budget.maxItems)
8837
+ fail("resource_limit", "Too many input items", "input");
8838
+ const canonicalInput = [];
8729
8839
  const toolNames = new Map;
8730
8840
  const byOriginal = new Map;
8841
+ const definitions = new Map;
8842
+ let examinedTools = 0;
8731
8843
  const tools = [];
8732
8844
  const identity = (name, namespace) => JSON.stringify([namespace ?? null, name]);
8733
8845
  const register = (name, namespace, custom) => {
8734
8846
  const key = identity(name, namespace);
8735
8847
  if (byOriginal.has(key))
8736
- fail("tool_name_collision", `Duplicate tool ${namespace ? namespace + "." : ""}${name}`);
8848
+ fail("tool_name_collision", "Duplicate tool identity");
8737
8849
  if (toolNames.size >= budget.maxItems)
8738
8850
  fail("resource_limit", "Too many tools");
8739
8851
  const wire = `bungee_tool_${toolNames.size}`;
@@ -8741,18 +8853,29 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8741
8853
  byOriginal.set(key, wire);
8742
8854
  return wire;
8743
8855
  };
8744
- const expandTool = (rawTool, namespace) => {
8856
+ const expandTool = (rawTool, namespace, path = "tools") => {
8857
+ if (++examinedTools > budget.maxItems)
8858
+ fail("resource_limit", "Too many tool declarations");
8745
8859
  const tool = record(rawTool, "tool");
8860
+ if (namespace === undefined && capabilities.omitOptionalWebSearch && ["web_search", "web_search_preview"].includes(String(tool.type)) && (body.tool_choice === undefined || body.tool_choice === "auto" || body.tool_choice === "none"))
8861
+ return;
8862
+ const allowed = tool.type === "namespace" ? ["type", "name", "description", "tools"] : tool.type === "custom" ? ["type", "name", "description", "format"] : ["type", "name", "description", "parameters", "strict"];
8863
+ for (const key of Object.keys(tool))
8864
+ if (!allowed.includes(key))
8865
+ fail("unsupported_tool", "Unsupported tool definition field", `${path}.${key}`);
8746
8866
  if (tool.type === "namespace") {
8747
8867
  if (namespace !== undefined)
8748
8868
  fail("unsupported_tool", "Nested tool namespaces are not supported");
8749
8869
  const name = string(tool.name, "namespace name", true);
8750
- for (const child of list(tool.tools, "namespace tools"))
8751
- expandTool(child, name);
8870
+ for (const [index, child] of list(tool.tools, "namespace tools").entries()) {
8871
+ const childPath = `${path}.tools[${index}]`;
8872
+ atParam(childPath, () => expandTool(child, name, childPath));
8873
+ }
8752
8874
  return;
8753
8875
  }
8754
8876
  if (tool.type !== "function" && tool.type !== "custom")
8755
- fail("unsupported_tool", `Hosted tool ${String(tool.type)} cannot be executed by Codex through this protocol`);
8877
+ fail("unsupported_tool", "This tool type cannot be executed by Codex through the target protocol");
8878
+ serialized(tool, budget.maxArgumentBytes, "tool definition");
8756
8879
  const name = string(tool.name, "tool name", true);
8757
8880
  const custom = tool.type === "custom";
8758
8881
  if (tool.strict !== undefined && typeof tool.strict !== "boolean")
@@ -8764,7 +8887,15 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8764
8887
  if (format.type !== "text" && format.type !== "grammar")
8765
8888
  fail("unsupported_tool", "Unknown custom tool format");
8766
8889
  }
8890
+ const key = identity(name, namespace);
8891
+ const signature = definitionKey(tool);
8892
+ if (definitions.has(key)) {
8893
+ if (definitions.get(key) !== signature)
8894
+ fail("tool_name_collision", "Conflicting definitions for the same tool identity");
8895
+ return;
8896
+ }
8767
8897
  const wire = register(name, namespace, custom);
8898
+ definitions.set(key, signature);
8768
8899
  const parameters = custom ? { type: "object", properties: { input: { type: "string" } }, required: ["input"], additionalProperties: false } : record(tool.parameters ?? { type: "object", properties: {} }, "tool parameters");
8769
8900
  const description = tool.description === undefined ? undefined : string(tool.description, "tool description");
8770
8901
  const customHint = custom ? `Pass the complete original custom tool input verbatim in the input string.${tool.format ? ` Original input format: ${JSON.stringify(tool.format)}` : ""}` : undefined;
@@ -8777,8 +8908,24 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8777
8908
  }
8778
8909
  };
8779
8910
  if (body.tools !== undefined)
8780
- for (const tool of list(body.tools, "tools"))
8781
- expandTool(tool);
8911
+ atParam("tools", () => {
8912
+ for (const [index, tool] of list(body.tools, "tools").entries())
8913
+ atParam(`tools[${index}]`, () => expandTool(tool, undefined, `tools[${index}]`));
8914
+ });
8915
+ for (const [index, rawItem] of input.entries())
8916
+ atParam(`input[${index}]`, () => {
8917
+ const item = record(rawItem, "input item");
8918
+ if (item.type !== "additional_tools") {
8919
+ canonicalInput.push(item);
8920
+ return;
8921
+ }
8922
+ if (item.content !== undefined)
8923
+ fail("invalid_payload", "A tool declaration carrier cannot also contain message content", `input[${index}].content`);
8924
+ for (const [toolIndex, tool] of list(item.tools, "additional tools").entries()) {
8925
+ const toolPath = `input[${index}].tools[${toolIndex}]`;
8926
+ atParam(toolPath, () => expandTool(tool, undefined, toolPath));
8927
+ }
8928
+ });
8782
8929
  const resolve = (item, custom) => {
8783
8930
  const name = string(item.name, "call name", true);
8784
8931
  const namespace = item.namespace === undefined ? undefined : string(item.namespace, "call namespace", true);
@@ -8786,7 +8933,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8786
8933
  if (wire === undefined)
8787
8934
  wire = register(name, namespace, custom);
8788
8935
  if (toolNames.get(wire).custom !== custom)
8789
- fail("tool_name_collision", `Tool kind changed for ${name}`);
8936
+ fail("tool_name_collision", "Historical tool kind conflicts with its current declaration");
8790
8937
  return wire;
8791
8938
  };
8792
8939
  const normalizeContent = (rawContent, role) => {
@@ -8797,6 +8944,8 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8797
8944
  const part = record(rawPart, "content part");
8798
8945
  if (part.type === "input_text" || part.type === "output_text" || part.type === "text")
8799
8946
  string(part.text, "content text");
8947
+ else if (part.type === "refusal" && role === "assistant")
8948
+ string(part.refusal, "refusal");
8800
8949
  else if (part.type === "input_image" && role === "user") {
8801
8950
  if (protocol !== "chat_completions")
8802
8951
  fail("unsupported_content", "Anthropic image conversion requires provider-specific URL/data support");
@@ -8804,11 +8953,14 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8804
8953
  if (part.detail !== undefined && !["auto", "low", "high"].includes(String(part.detail)))
8805
8954
  fail("unsupported_content", "Unknown image detail");
8806
8955
  } else
8807
- fail("unsupported_content", `Unsupported message content: ${String(part.type)}`);
8956
+ fail("unsupported_content", "Unsupported message content type");
8808
8957
  if (part.annotations !== undefined && list(part.annotations, "annotations").length)
8809
8958
  fail("unsupported_content", "Output annotations cannot be represented in input history");
8810
8959
  }
8811
- const normalized = contentNormalizer.normalizeResponsesMessageContent(parts);
8960
+ const normalized = contentNormalizer.normalizeResponsesMessageContent(parts.map((raw) => {
8961
+ const part = raw;
8962
+ return part.type === "refusal" ? { type: "output_text", text: part.refusal } : part;
8963
+ }));
8812
8964
  for (let i = 0;i < parts.length; i++) {
8813
8965
  const part = parts[i];
8814
8966
  if (part.type === "input_image" && part.detail !== undefined)
@@ -8817,6 +8969,8 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8817
8969
  return normalized;
8818
8970
  };
8819
8971
  const chat = [];
8972
+ const messageParams = new WeakMap;
8973
+ let messageParam = "instructions";
8820
8974
  const callIds = new Set;
8821
8975
  const answered = new Set;
8822
8976
  let pendingReasoning = "";
@@ -8824,73 +8978,77 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8824
8978
  if (chat.length >= budget.maxItems)
8825
8979
  fail("resource_limit", "Too many input messages");
8826
8980
  chat.push(message);
8981
+ messageParams.set(message, messageParam);
8827
8982
  };
8828
8983
  if (body.instructions !== undefined)
8829
8984
  addMessage({ role: "system", content: string(body.instructions, "instructions") });
8830
- const input = typeof body.input === "string" ? [{ role: "user", content: body.input }] : list(body.input, "input");
8831
- if (input.length > budget.maxItems)
8832
- fail("resource_limit", "Too many input items");
8833
- for (const rawItem of input) {
8834
- const item = record(rawItem, "input item");
8835
- if (item.encrypted_content !== undefined || item.type === "compaction")
8836
- fail("unsupported_content", "Encrypted reasoning and compaction cannot be restored");
8837
- if (item.type === "item_reference")
8838
- fail("unresolved_reference", "Input item references must be resolved before conversion");
8839
- if (item.type === "reasoning") {
8840
- if (protocol !== "chat_completions" || !capabilities.reasoningEffort)
8841
- fail("unsupported_reasoning", "Reasoning history requires an explicit compatible reasoning capability");
8842
- const summary = list(item.summary, "reasoning summary");
8843
- pendingReasoning += summary.map((part) => {
8844
- const p = record(part, "reasoning summary");
8845
- if (p.type !== "summary_text")
8846
- fail("unsupported_reasoning", "Unknown reasoning summary");
8847
- return string(p.text, "summary text");
8848
- }).join("");
8849
- continue;
8850
- }
8851
- if (item.type === "function_call" || item.type === "custom_tool_call") {
8852
- const custom = item.type === "custom_tool_call";
8853
- const id = string(item.call_id, "call_id", true);
8854
- if (callIds.has(id))
8855
- fail("invalid_tool_history", `Duplicate call_id ${id}`);
8856
- callIds.add(id);
8857
- const args = custom ? JSON.stringify({ input: string(item.input, "custom input") }) : string(item.arguments, "arguments");
8858
- bounded(args, budget.maxArgumentBytes, "arguments");
8859
- argumentObject(args);
8860
- const call = { id, type: "function", function: { name: resolve(item, custom), arguments: args } };
8861
- const previous = chat.at(-1);
8862
- if (previous?.role === "assistant") {
8863
- previous.tool_calls = [...previous.tool_calls ?? [], call];
8864
- if (pendingReasoning)
8865
- previous.reasoning_content = pendingReasoning;
8866
- } else
8867
- addMessage({ role: "assistant", content: null, tool_calls: [call], ...pendingReasoning ? { reasoning_content: pendingReasoning } : {} });
8868
- pendingReasoning = "";
8869
- continue;
8870
- }
8871
- if (item.type === "function_call_output" || item.type === "custom_tool_call_output") {
8872
- const id = string(item.call_id, "call_id", true);
8873
- if (!callIds.has(id) || answered.has(id))
8874
- fail("invalid_tool_history", `Unmatched or duplicate output ${id}`);
8875
- answered.add(id);
8876
- const output = typeof item.output === "string" ? item.output : normalizeContent(item.output, "tool");
8877
- addMessage({ role: "tool", tool_call_id: id, content: output, ...item.is_error === true ? { is_error: true } : {} });
8878
- continue;
8879
- }
8880
- if (item.type !== undefined && item.type !== "message")
8881
- fail("unsupported_content", `Unsupported input item ${String(item.type)}`);
8882
- const role = string(item.role, "message role", true);
8883
- if (!["user", "assistant", "system", "developer"].includes(role))
8884
- fail("unsupported_content", `Unknown message role ${role}`);
8885
- const message = { role, content: normalizeContent(item.content, role) };
8886
- if (pendingReasoning) {
8887
- if (role !== "assistant")
8888
- fail("unsupported_reasoning", "Reasoning must precede an assistant item");
8889
- message.reasoning_content = pendingReasoning;
8890
- pendingReasoning = "";
8891
- }
8892
- addMessage(message);
8893
- }
8985
+ for (const [inputIndex, rawItem] of input.entries())
8986
+ atParam(`input[${inputIndex}]`, () => {
8987
+ messageParam = `input[${inputIndex}]`;
8988
+ const item = record(rawItem, "input item");
8989
+ if (item.type === "additional_tools")
8990
+ return;
8991
+ if (item.encrypted_content != null || item.type === "compaction")
8992
+ fail("unsupported_content", "Encrypted reasoning and compaction cannot be restored");
8993
+ if (item.type === "item_reference")
8994
+ fail("unresolved_reference", "Input item references must be resolved before conversion");
8995
+ if (item.type === "reasoning") {
8996
+ if (protocol !== "chat_completions" || !(capabilities.reasoningHistory || capabilities.reasoningEffort))
8997
+ fail("unsupported_reasoning", "Reasoning history requires an explicit compatible reasoning capability");
8998
+ if (item.content != null)
8999
+ fail("unsupported_reasoning", "Reasoning content requires an explicit history mapping", `${messageParam}.content`);
9000
+ const summary = list(item.summary, "reasoning summary");
9001
+ pendingReasoning += summary.map((part) => {
9002
+ const p = record(part, "reasoning summary");
9003
+ if (p.type !== "summary_text")
9004
+ fail("unsupported_reasoning", "Unknown reasoning summary");
9005
+ return string(p.text, "summary text");
9006
+ }).join("");
9007
+ return;
9008
+ }
9009
+ if (item.type === "function_call" || item.type === "custom_tool_call") {
9010
+ const custom = item.type === "custom_tool_call";
9011
+ const id = string(item.call_id, "call_id", true);
9012
+ if (callIds.has(id))
9013
+ fail("invalid_tool_history", "Duplicate tool call identifier");
9014
+ callIds.add(id);
9015
+ const args = custom ? JSON.stringify({ input: string(item.input, "custom input") }) : string(item.arguments, "arguments");
9016
+ bounded(args, budget.maxArgumentBytes, "arguments");
9017
+ argumentObject(args);
9018
+ const call = { id, type: "function", function: { name: resolve(item, custom), arguments: args } };
9019
+ const previous = chat.at(-1);
9020
+ if (previous?.role === "assistant") {
9021
+ previous.tool_calls = [...previous.tool_calls ?? [], call];
9022
+ if (pendingReasoning)
9023
+ previous.reasoning_content = pendingReasoning;
9024
+ } else
9025
+ addMessage({ role: "assistant", content: null, tool_calls: [call], ...pendingReasoning ? { reasoning_content: pendingReasoning } : {} });
9026
+ pendingReasoning = "";
9027
+ return;
9028
+ }
9029
+ if (item.type === "function_call_output" || item.type === "custom_tool_call_output") {
9030
+ const id = string(item.call_id, "call_id", true);
9031
+ if (!callIds.has(id) || answered.has(id))
9032
+ fail("invalid_tool_history", "Unmatched or duplicate tool result");
9033
+ answered.add(id);
9034
+ const output = typeof item.output === "string" ? item.output : normalizeContent(item.output, "tool");
9035
+ addMessage({ role: "tool", tool_call_id: id, content: output, ...item.is_error === true ? { is_error: true } : {} });
9036
+ return;
9037
+ }
9038
+ if (item.type !== undefined && item.type !== "message")
9039
+ fail("unsupported_content", "Unsupported input item type");
9040
+ const role = string(item.role, "message role", true);
9041
+ if (!["user", "assistant", "system", "developer"].includes(role))
9042
+ fail("unsupported_content", "Unknown message role");
9043
+ const message = { role, content: normalizeContent(item.content, role) };
9044
+ if (pendingReasoning) {
9045
+ if (role !== "assistant")
9046
+ fail("unsupported_reasoning", "Reasoning must precede an assistant item");
9047
+ message.reasoning_content = pendingReasoning;
9048
+ pendingReasoning = "";
9049
+ }
9050
+ addMessage(message);
9051
+ });
8894
9052
  if (pendingReasoning)
8895
9053
  fail("unsupported_reasoning", "Orphan reasoning history cannot be represented");
8896
9054
  const result = { model, messages: chat };
@@ -8923,41 +9081,35 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8923
9081
  result.tool_choice = protocol === "chat_completions" ? { type: "function", function: { name: wire } } : { type: "tool", name: wire };
8924
9082
  }
8925
9083
  }
8926
- if (body.reasoning !== undefined) {
8927
- const reasoning = record(body.reasoning, "reasoning");
8928
- if (reasoning.summary !== undefined && reasoning.summary !== "none")
8929
- fail("unsupported_reasoning", "Requested reasoning summaries are not guaranteed by this upstream protocol");
8930
- if (reasoning.effort !== undefined && reasoning.effort !== "none") {
8931
- const effort = string(reasoning.effort, "reasoning effort", true);
8932
- if (protocol === "chat_completions") {
8933
- if (!capabilities.reasoningEffort)
8934
- fail("unsupported_reasoning", "Upstream does not advertise reasoning effort");
8935
- result.reasoning_effort = effort;
8936
- } else {
8937
- const thinking = capabilities.anthropicThinkingBudget;
8938
- if (!Number.isSafeInteger(thinking) || thinking < 1024 || typeof maxTokens !== "number" || thinking >= maxTokens)
8939
- fail("unsupported_reasoning", "Anthropic thinking requires an explicit budget below max_output_tokens");
8940
- result.thinking = { type: "enabled", budget_tokens: thinking };
8941
- }
8942
- }
8943
- }
8944
- if (body.text !== undefined) {
8945
- const text = record(body.text, "text");
8946
- if (text.verbosity !== undefined)
8947
- fail("unsupported_request", "Text verbosity cannot be represented");
8948
- if (text.format !== undefined) {
8949
- const format = record(text.format, "text format");
8950
- if (protocol !== "chat_completions" && format.type !== "text")
8951
- fail("unsupported_request", "Anthropic structured output requires a separate provider capability");
8952
- if (format.type === "json_schema")
8953
- result.response_format = { type: "json_schema", json_schema: { name: format.name, schema: format.schema, ...format.strict !== undefined ? { strict: format.strict } : {}, ...format.description !== undefined ? { description: format.description } : {} } };
8954
- else if (format.type === "json_object" || format.type === "text") {
8955
- if (protocol === "chat_completions")
8956
- result.response_format = format;
8957
- } else
8958
- fail("unsupported_request", "Unknown text format");
8959
- }
8960
- }
9084
+ applyResponsesPreferences(body, result, protocol, capabilities, maxTokens, diagnostics);
9085
+ if (body.text !== undefined)
9086
+ atParam("text.format", () => {
9087
+ const text = record(body.text, "text");
9088
+ if (text.format !== undefined) {
9089
+ const format = record(text.format, "text format");
9090
+ const allowed = format.type === "json_schema" ? ["type", "name", "schema", "strict", "description"] : ["type"];
9091
+ for (const key of Object.keys(format))
9092
+ if (!allowed.includes(key))
9093
+ fail("unsupported_request", "Unsupported output format field", `text.format.${key}`);
9094
+ if (format.type === "json_schema") {
9095
+ atParam("text.format.name", () => string(format.name, "output schema name", true));
9096
+ atParam("text.format.schema", () => record(format.schema, "output schema"));
9097
+ if (format.strict !== undefined && typeof format.strict !== "boolean")
9098
+ fail("invalid_payload", "Output schema strict must be boolean", "text.format.strict");
9099
+ if (format.description !== undefined)
9100
+ atParam("text.format.description", () => string(format.description, "output schema description"));
9101
+ }
9102
+ if (protocol !== "chat_completions" && format.type !== "text")
9103
+ fail("unsupported_request", "Anthropic structured output requires a separate provider capability");
9104
+ if (format.type === "json_schema")
9105
+ result.response_format = { type: "json_schema", json_schema: { name: format.name, schema: format.schema, ...format.strict !== undefined ? { strict: format.strict } : {}, ...format.description !== undefined ? { description: format.description } : {} } };
9106
+ else if (format.type === "json_object" || format.type === "text") {
9107
+ if (protocol === "chat_completions")
9108
+ result.response_format = format;
9109
+ } else
9110
+ fail("unsupported_request", "Unknown text format");
9111
+ }
9112
+ });
8961
9113
  if (protocol === "anthropic_messages") {
8962
9114
  const system = [];
8963
9115
  const messages = [];
@@ -8971,7 +9123,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8971
9123
  for (const msg of chat) {
8972
9124
  if (msg.role === "system" || msg.role === "developer") {
8973
9125
  if (messages.length)
8974
- fail("unsupported_content", "Mid-conversation system instructions cannot be relocated safely");
9126
+ fail("unsupported_content", "Mid-conversation system instructions cannot be relocated safely", `${messageParams.get(msg)}.role`);
8975
9127
  system.push(typeof msg.content === "string" ? msg.content : msg.content.map((p) => string(p.text, "system text")).join(""));
8976
9128
  continue;
8977
9129
  }
@@ -8997,7 +9149,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8997
9149
  delete result.parallel_tool_calls;
8998
9150
  }
8999
9151
  }
9000
- return { body: result, toolNames };
9152
+ return { body: result, toolNames, canonicalInput, diagnostics };
9001
9153
  }
9002
9154
  // src/responses-codec/events.ts
9003
9155
  class ResponsesEventEncoder {
@@ -8515,14 +8515,25 @@ var DEFAULT_RESPONSES_CODEC_LIMITS = Object.freeze({
8515
8515
 
8516
8516
  class ResponsesCodecError extends Error {
8517
8517
  code;
8518
- constructor(code, message) {
8518
+ param;
8519
+ constructor(code, message, param) {
8519
8520
  super(message);
8520
8521
  this.code = code;
8522
+ this.param = param;
8521
8523
  this.name = "ResponsesCodecError";
8522
8524
  }
8523
8525
  }
8524
- function fail(code, message) {
8525
- throw new ResponsesCodecError(code, message);
8526
+ function fail(code, message, param) {
8527
+ throw new ResponsesCodecError(code, message, param);
8528
+ }
8529
+ function atParam(param, run) {
8530
+ try {
8531
+ return run();
8532
+ } catch (error) {
8533
+ if (error instanceof ResponsesCodecError && error.param === undefined)
8534
+ throw new ResponsesCodecError(error.code, error.message, param);
8535
+ throw error;
8536
+ }
8526
8537
  }
8527
8538
  function record(value, where) {
8528
8539
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -8685,8 +8696,94 @@ function responsePayload(id, model, output, end, usage, error, createdAt = Math.
8685
8696
  return result;
8686
8697
  }
8687
8698
 
8699
+ // src/responses-codec/request-preferences.ts
8700
+ function fields(value, allowed, path) {
8701
+ for (const key of Object.keys(value))
8702
+ if (!allowed.includes(key)) {
8703
+ fail("unsupported_request", "Unrecognized request option", `${path}.${key}`);
8704
+ }
8705
+ }
8706
+ function applyResponsesPreferences(body, result, protocol, capabilities, maxTokens, diagnostics) {
8707
+ const note = (param, action, reason) => diagnostics.push({ param, action, reason });
8708
+ if (body.stream_options !== undefined)
8709
+ atParam("stream_options", () => {
8710
+ const options = record(body.stream_options, "stream_options");
8711
+ fields(options, ["include_usage", "reasoning_summary_delivery"], "stream_options");
8712
+ if (options.include_usage !== undefined) {
8713
+ if (typeof options.include_usage !== "boolean")
8714
+ fail("invalid_payload", "Usage preference must be boolean", "stream_options.include_usage");
8715
+ note("stream_options.include_usage", protocol === "chat_completions" && body.stream === true ? "mapped" : "omitted", protocol === "chat_completions" && body.stream === true ? "stream_usage_required" : "target_usage_events");
8716
+ }
8717
+ if (options.reasoning_summary_delivery !== undefined) {
8718
+ if (options.reasoning_summary_delivery !== "sequential_cutoff")
8719
+ fail("unsupported_request", "Unsupported summary delivery preference", "stream_options.reasoning_summary_delivery");
8720
+ note("stream_options.reasoning_summary_delivery", "omitted", "source_summary_delivery_not_applicable");
8721
+ }
8722
+ });
8723
+ if (body.access_programs !== undefined)
8724
+ atParam("access_programs", () => {
8725
+ const programs = record(body.access_programs, "access_programs");
8726
+ fields(programs, ["cyber"], "access_programs");
8727
+ if (programs.cyber !== undefined && programs.cyber !== "standard") {
8728
+ if (programs.cyber === "daybreak_blue" || programs.cyber === "daybreak_red") {
8729
+ fail("unsupported_access_program", "Selected access program cannot be represented by the target protocol", "access_programs.cyber");
8730
+ }
8731
+ fail("invalid_payload", "Invalid access program", "access_programs.cyber");
8732
+ }
8733
+ note("access_programs", "omitted", "target_uses_own_access_policy");
8734
+ });
8735
+ if (body.reasoning !== undefined)
8736
+ atParam("reasoning", () => {
8737
+ const reasoning = record(body.reasoning, "reasoning");
8738
+ fields(reasoning, ["effort", "summary", "context"], "reasoning");
8739
+ if (reasoning.summary !== undefined) {
8740
+ if (typeof reasoning.summary !== "string" || !["none", "auto", "concise", "detailed"].includes(reasoning.summary))
8741
+ fail("invalid_payload", "Invalid reasoning summary preference", "reasoning.summary");
8742
+ note("reasoning.summary", "omitted", "target_summary_not_guaranteed");
8743
+ }
8744
+ if (reasoning.context !== undefined) {
8745
+ if (reasoning.context !== "all_turns")
8746
+ fail("unsupported_reasoning", "Unsupported reasoning context preference", "reasoning.context");
8747
+ note("reasoning.context", "omitted", "source_reasoning_context_not_applicable");
8748
+ }
8749
+ if (reasoning.effort !== undefined)
8750
+ atParam("reasoning.effort", () => {
8751
+ const effort = string(reasoning.effort, "reasoning effort", true);
8752
+ if (protocol === "chat_completions" && capabilities.reasoningEffort) {
8753
+ result.reasoning_effort = effort;
8754
+ note("reasoning.effort", "mapped", "target_reasoning_effort");
8755
+ } else if (protocol === "anthropic_messages" && capabilities.anthropicThinkingBudget !== undefined && effort !== "none") {
8756
+ const budget = capabilities.anthropicThinkingBudget;
8757
+ if (!Number.isSafeInteger(budget) || budget < 1024 || typeof maxTokens !== "number" || budget >= maxTokens) {
8758
+ fail("unsupported_reasoning", "Anthropic thinking requires an explicit budget below max_output_tokens");
8759
+ }
8760
+ result.thinking = { type: "enabled", budget_tokens: budget };
8761
+ note("reasoning.effort", "mapped", "explicit_target_thinking_budget");
8762
+ } else
8763
+ note("reasoning.effort", "omitted", "target_reasoning_effort_unavailable");
8764
+ });
8765
+ });
8766
+ if (body.text !== undefined)
8767
+ atParam("text", () => {
8768
+ const text = record(body.text, "text");
8769
+ fields(text, ["verbosity", "format"], "text");
8770
+ if (text.verbosity !== undefined) {
8771
+ if (typeof text.verbosity !== "string" || !["low", "medium", "high"].includes(text.verbosity))
8772
+ fail("invalid_payload", "Invalid text verbosity preference", "text.verbosity");
8773
+ note("text.verbosity", "omitted", "target_verbosity_not_available");
8774
+ }
8775
+ });
8776
+ }
8777
+
8688
8778
  // src/responses-codec/request.ts
8689
8779
  var contentNormalizer = new OpenAIProtocolConversion({ trimWhitespace: false });
8780
+ function definitionKey(value) {
8781
+ if (Array.isArray(value))
8782
+ return `[${value.map(definitionKey).join(",")}]`;
8783
+ if (value && typeof value === "object")
8784
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${definitionKey(value[key])}`).join(",")}}`;
8785
+ return JSON.stringify(value);
8786
+ }
8690
8787
  function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8691
8788
  const body = record(raw, "Responses request");
8692
8789
  const budget = limits(capabilities.limits);
@@ -8712,11 +8809,15 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8712
8809
  "n",
8713
8810
  "truncation",
8714
8811
  "include",
8715
- "store"
8812
+ "store",
8813
+ "prompt_cache_key",
8814
+ "client_metadata",
8815
+ "stream_options",
8816
+ "access_programs"
8716
8817
  ]);
8717
8818
  for (const key of Object.keys(body))
8718
8819
  if (!supported.has(key))
8719
- fail("unsupported_request", `Unsupported Responses request field ${key}`);
8820
+ fail("unsupported_request", "Unrecognized Responses request field", key);
8720
8821
  if (body.store === true)
8721
8822
  fail("unsupported_request", "Stored Responses require caller-owned persistence");
8722
8823
  for (const key of ["previous_response_id", "conversation", "response_id"]) {
@@ -8729,17 +8830,28 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8729
8830
  fail("unsupported_request", "Only one response candidate is supported");
8730
8831
  if (body.truncation !== undefined && body.truncation !== "disabled")
8731
8832
  fail("unsupported_request", "Automatic input truncation cannot be represented");
8732
- if (body.include !== undefined && list(body.include, "include").length)
8833
+ if (body.include !== undefined && list(body.include, "include").some((field) => field !== "reasoning.encrypted_content"))
8733
8834
  fail("unsupported_request", "Requested extra Responses output fields cannot be represented");
8835
+ if (body.prompt_cache_key !== undefined)
8836
+ string(body.prompt_cache_key, "prompt_cache_key");
8837
+ if (body.client_metadata !== undefined)
8838
+ record(body.client_metadata, "client_metadata");
8734
8839
  const model = string(body.model, "model", true);
8840
+ const diagnostics = [];
8841
+ const input = atParam("input", () => typeof body.input === "string" ? [{ role: "user", content: body.input }] : list(body.input, "input"));
8842
+ if (input.length > budget.maxItems)
8843
+ fail("resource_limit", "Too many input items", "input");
8844
+ const canonicalInput = [];
8735
8845
  const toolNames = new Map;
8736
8846
  const byOriginal = new Map;
8847
+ const definitions = new Map;
8848
+ let examinedTools = 0;
8737
8849
  const tools = [];
8738
8850
  const identity = (name, namespace) => JSON.stringify([namespace ?? null, name]);
8739
8851
  const register = (name, namespace, custom) => {
8740
8852
  const key = identity(name, namespace);
8741
8853
  if (byOriginal.has(key))
8742
- fail("tool_name_collision", `Duplicate tool ${namespace ? namespace + "." : ""}${name}`);
8854
+ fail("tool_name_collision", "Duplicate tool identity");
8743
8855
  if (toolNames.size >= budget.maxItems)
8744
8856
  fail("resource_limit", "Too many tools");
8745
8857
  const wire = `bungee_tool_${toolNames.size}`;
@@ -8747,18 +8859,29 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8747
8859
  byOriginal.set(key, wire);
8748
8860
  return wire;
8749
8861
  };
8750
- const expandTool = (rawTool, namespace) => {
8862
+ const expandTool = (rawTool, namespace, path = "tools") => {
8863
+ if (++examinedTools > budget.maxItems)
8864
+ fail("resource_limit", "Too many tool declarations");
8751
8865
  const tool = record(rawTool, "tool");
8866
+ if (namespace === undefined && capabilities.omitOptionalWebSearch && ["web_search", "web_search_preview"].includes(String(tool.type)) && (body.tool_choice === undefined || body.tool_choice === "auto" || body.tool_choice === "none"))
8867
+ return;
8868
+ const allowed = tool.type === "namespace" ? ["type", "name", "description", "tools"] : tool.type === "custom" ? ["type", "name", "description", "format"] : ["type", "name", "description", "parameters", "strict"];
8869
+ for (const key of Object.keys(tool))
8870
+ if (!allowed.includes(key))
8871
+ fail("unsupported_tool", "Unsupported tool definition field", `${path}.${key}`);
8752
8872
  if (tool.type === "namespace") {
8753
8873
  if (namespace !== undefined)
8754
8874
  fail("unsupported_tool", "Nested tool namespaces are not supported");
8755
8875
  const name = string(tool.name, "namespace name", true);
8756
- for (const child of list(tool.tools, "namespace tools"))
8757
- expandTool(child, name);
8876
+ for (const [index, child] of list(tool.tools, "namespace tools").entries()) {
8877
+ const childPath = `${path}.tools[${index}]`;
8878
+ atParam(childPath, () => expandTool(child, name, childPath));
8879
+ }
8758
8880
  return;
8759
8881
  }
8760
8882
  if (tool.type !== "function" && tool.type !== "custom")
8761
- fail("unsupported_tool", `Hosted tool ${String(tool.type)} cannot be executed by Codex through this protocol`);
8883
+ fail("unsupported_tool", "This tool type cannot be executed by Codex through the target protocol");
8884
+ serialized(tool, budget.maxArgumentBytes, "tool definition");
8762
8885
  const name = string(tool.name, "tool name", true);
8763
8886
  const custom = tool.type === "custom";
8764
8887
  if (tool.strict !== undefined && typeof tool.strict !== "boolean")
@@ -8770,7 +8893,15 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8770
8893
  if (format.type !== "text" && format.type !== "grammar")
8771
8894
  fail("unsupported_tool", "Unknown custom tool format");
8772
8895
  }
8896
+ const key = identity(name, namespace);
8897
+ const signature = definitionKey(tool);
8898
+ if (definitions.has(key)) {
8899
+ if (definitions.get(key) !== signature)
8900
+ fail("tool_name_collision", "Conflicting definitions for the same tool identity");
8901
+ return;
8902
+ }
8773
8903
  const wire = register(name, namespace, custom);
8904
+ definitions.set(key, signature);
8774
8905
  const parameters = custom ? { type: "object", properties: { input: { type: "string" } }, required: ["input"], additionalProperties: false } : record(tool.parameters ?? { type: "object", properties: {} }, "tool parameters");
8775
8906
  const description = tool.description === undefined ? undefined : string(tool.description, "tool description");
8776
8907
  const customHint = custom ? `Pass the complete original custom tool input verbatim in the input string.${tool.format ? ` Original input format: ${JSON.stringify(tool.format)}` : ""}` : undefined;
@@ -8783,8 +8914,24 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8783
8914
  }
8784
8915
  };
8785
8916
  if (body.tools !== undefined)
8786
- for (const tool of list(body.tools, "tools"))
8787
- expandTool(tool);
8917
+ atParam("tools", () => {
8918
+ for (const [index, tool] of list(body.tools, "tools").entries())
8919
+ atParam(`tools[${index}]`, () => expandTool(tool, undefined, `tools[${index}]`));
8920
+ });
8921
+ for (const [index, rawItem] of input.entries())
8922
+ atParam(`input[${index}]`, () => {
8923
+ const item = record(rawItem, "input item");
8924
+ if (item.type !== "additional_tools") {
8925
+ canonicalInput.push(item);
8926
+ return;
8927
+ }
8928
+ if (item.content !== undefined)
8929
+ fail("invalid_payload", "A tool declaration carrier cannot also contain message content", `input[${index}].content`);
8930
+ for (const [toolIndex, tool] of list(item.tools, "additional tools").entries()) {
8931
+ const toolPath = `input[${index}].tools[${toolIndex}]`;
8932
+ atParam(toolPath, () => expandTool(tool, undefined, toolPath));
8933
+ }
8934
+ });
8788
8935
  const resolve = (item, custom) => {
8789
8936
  const name = string(item.name, "call name", true);
8790
8937
  const namespace = item.namespace === undefined ? undefined : string(item.namespace, "call namespace", true);
@@ -8792,7 +8939,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8792
8939
  if (wire === undefined)
8793
8940
  wire = register(name, namespace, custom);
8794
8941
  if (toolNames.get(wire).custom !== custom)
8795
- fail("tool_name_collision", `Tool kind changed for ${name}`);
8942
+ fail("tool_name_collision", "Historical tool kind conflicts with its current declaration");
8796
8943
  return wire;
8797
8944
  };
8798
8945
  const normalizeContent = (rawContent, role) => {
@@ -8803,6 +8950,8 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8803
8950
  const part = record(rawPart, "content part");
8804
8951
  if (part.type === "input_text" || part.type === "output_text" || part.type === "text")
8805
8952
  string(part.text, "content text");
8953
+ else if (part.type === "refusal" && role === "assistant")
8954
+ string(part.refusal, "refusal");
8806
8955
  else if (part.type === "input_image" && role === "user") {
8807
8956
  if (protocol !== "chat_completions")
8808
8957
  fail("unsupported_content", "Anthropic image conversion requires provider-specific URL/data support");
@@ -8810,11 +8959,14 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8810
8959
  if (part.detail !== undefined && !["auto", "low", "high"].includes(String(part.detail)))
8811
8960
  fail("unsupported_content", "Unknown image detail");
8812
8961
  } else
8813
- fail("unsupported_content", `Unsupported message content: ${String(part.type)}`);
8962
+ fail("unsupported_content", "Unsupported message content type");
8814
8963
  if (part.annotations !== undefined && list(part.annotations, "annotations").length)
8815
8964
  fail("unsupported_content", "Output annotations cannot be represented in input history");
8816
8965
  }
8817
- const normalized = contentNormalizer.normalizeResponsesMessageContent(parts);
8966
+ const normalized = contentNormalizer.normalizeResponsesMessageContent(parts.map((raw) => {
8967
+ const part = raw;
8968
+ return part.type === "refusal" ? { type: "output_text", text: part.refusal } : part;
8969
+ }));
8818
8970
  for (let i = 0;i < parts.length; i++) {
8819
8971
  const part = parts[i];
8820
8972
  if (part.type === "input_image" && part.detail !== undefined)
@@ -8823,6 +8975,8 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8823
8975
  return normalized;
8824
8976
  };
8825
8977
  const chat = [];
8978
+ const messageParams = new WeakMap;
8979
+ let messageParam = "instructions";
8826
8980
  const callIds = new Set;
8827
8981
  const answered = new Set;
8828
8982
  let pendingReasoning = "";
@@ -8830,73 +8984,77 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8830
8984
  if (chat.length >= budget.maxItems)
8831
8985
  fail("resource_limit", "Too many input messages");
8832
8986
  chat.push(message);
8987
+ messageParams.set(message, messageParam);
8833
8988
  };
8834
8989
  if (body.instructions !== undefined)
8835
8990
  addMessage({ role: "system", content: string(body.instructions, "instructions") });
8836
- const input = typeof body.input === "string" ? [{ role: "user", content: body.input }] : list(body.input, "input");
8837
- if (input.length > budget.maxItems)
8838
- fail("resource_limit", "Too many input items");
8839
- for (const rawItem of input) {
8840
- const item = record(rawItem, "input item");
8841
- if (item.encrypted_content !== undefined || item.type === "compaction")
8842
- fail("unsupported_content", "Encrypted reasoning and compaction cannot be restored");
8843
- if (item.type === "item_reference")
8844
- fail("unresolved_reference", "Input item references must be resolved before conversion");
8845
- if (item.type === "reasoning") {
8846
- if (protocol !== "chat_completions" || !capabilities.reasoningEffort)
8847
- fail("unsupported_reasoning", "Reasoning history requires an explicit compatible reasoning capability");
8848
- const summary = list(item.summary, "reasoning summary");
8849
- pendingReasoning += summary.map((part) => {
8850
- const p = record(part, "reasoning summary");
8851
- if (p.type !== "summary_text")
8852
- fail("unsupported_reasoning", "Unknown reasoning summary");
8853
- return string(p.text, "summary text");
8854
- }).join("");
8855
- continue;
8856
- }
8857
- if (item.type === "function_call" || item.type === "custom_tool_call") {
8858
- const custom = item.type === "custom_tool_call";
8859
- const id = string(item.call_id, "call_id", true);
8860
- if (callIds.has(id))
8861
- fail("invalid_tool_history", `Duplicate call_id ${id}`);
8862
- callIds.add(id);
8863
- const args = custom ? JSON.stringify({ input: string(item.input, "custom input") }) : string(item.arguments, "arguments");
8864
- bounded(args, budget.maxArgumentBytes, "arguments");
8865
- argumentObject(args);
8866
- const call = { id, type: "function", function: { name: resolve(item, custom), arguments: args } };
8867
- const previous = chat.at(-1);
8868
- if (previous?.role === "assistant") {
8869
- previous.tool_calls = [...previous.tool_calls ?? [], call];
8870
- if (pendingReasoning)
8871
- previous.reasoning_content = pendingReasoning;
8872
- } else
8873
- addMessage({ role: "assistant", content: null, tool_calls: [call], ...pendingReasoning ? { reasoning_content: pendingReasoning } : {} });
8874
- pendingReasoning = "";
8875
- continue;
8876
- }
8877
- if (item.type === "function_call_output" || item.type === "custom_tool_call_output") {
8878
- const id = string(item.call_id, "call_id", true);
8879
- if (!callIds.has(id) || answered.has(id))
8880
- fail("invalid_tool_history", `Unmatched or duplicate output ${id}`);
8881
- answered.add(id);
8882
- const output = typeof item.output === "string" ? item.output : normalizeContent(item.output, "tool");
8883
- addMessage({ role: "tool", tool_call_id: id, content: output, ...item.is_error === true ? { is_error: true } : {} });
8884
- continue;
8885
- }
8886
- if (item.type !== undefined && item.type !== "message")
8887
- fail("unsupported_content", `Unsupported input item ${String(item.type)}`);
8888
- const role = string(item.role, "message role", true);
8889
- if (!["user", "assistant", "system", "developer"].includes(role))
8890
- fail("unsupported_content", `Unknown message role ${role}`);
8891
- const message = { role, content: normalizeContent(item.content, role) };
8892
- if (pendingReasoning) {
8893
- if (role !== "assistant")
8894
- fail("unsupported_reasoning", "Reasoning must precede an assistant item");
8895
- message.reasoning_content = pendingReasoning;
8896
- pendingReasoning = "";
8897
- }
8898
- addMessage(message);
8899
- }
8991
+ for (const [inputIndex, rawItem] of input.entries())
8992
+ atParam(`input[${inputIndex}]`, () => {
8993
+ messageParam = `input[${inputIndex}]`;
8994
+ const item = record(rawItem, "input item");
8995
+ if (item.type === "additional_tools")
8996
+ return;
8997
+ if (item.encrypted_content != null || item.type === "compaction")
8998
+ fail("unsupported_content", "Encrypted reasoning and compaction cannot be restored");
8999
+ if (item.type === "item_reference")
9000
+ fail("unresolved_reference", "Input item references must be resolved before conversion");
9001
+ if (item.type === "reasoning") {
9002
+ if (protocol !== "chat_completions" || !(capabilities.reasoningHistory || capabilities.reasoningEffort))
9003
+ fail("unsupported_reasoning", "Reasoning history requires an explicit compatible reasoning capability");
9004
+ if (item.content != null)
9005
+ fail("unsupported_reasoning", "Reasoning content requires an explicit history mapping", `${messageParam}.content`);
9006
+ const summary = list(item.summary, "reasoning summary");
9007
+ pendingReasoning += summary.map((part) => {
9008
+ const p = record(part, "reasoning summary");
9009
+ if (p.type !== "summary_text")
9010
+ fail("unsupported_reasoning", "Unknown reasoning summary");
9011
+ return string(p.text, "summary text");
9012
+ }).join("");
9013
+ return;
9014
+ }
9015
+ if (item.type === "function_call" || item.type === "custom_tool_call") {
9016
+ const custom = item.type === "custom_tool_call";
9017
+ const id = string(item.call_id, "call_id", true);
9018
+ if (callIds.has(id))
9019
+ fail("invalid_tool_history", "Duplicate tool call identifier");
9020
+ callIds.add(id);
9021
+ const args = custom ? JSON.stringify({ input: string(item.input, "custom input") }) : string(item.arguments, "arguments");
9022
+ bounded(args, budget.maxArgumentBytes, "arguments");
9023
+ argumentObject(args);
9024
+ const call = { id, type: "function", function: { name: resolve(item, custom), arguments: args } };
9025
+ const previous = chat.at(-1);
9026
+ if (previous?.role === "assistant") {
9027
+ previous.tool_calls = [...previous.tool_calls ?? [], call];
9028
+ if (pendingReasoning)
9029
+ previous.reasoning_content = pendingReasoning;
9030
+ } else
9031
+ addMessage({ role: "assistant", content: null, tool_calls: [call], ...pendingReasoning ? { reasoning_content: pendingReasoning } : {} });
9032
+ pendingReasoning = "";
9033
+ return;
9034
+ }
9035
+ if (item.type === "function_call_output" || item.type === "custom_tool_call_output") {
9036
+ const id = string(item.call_id, "call_id", true);
9037
+ if (!callIds.has(id) || answered.has(id))
9038
+ fail("invalid_tool_history", "Unmatched or duplicate tool result");
9039
+ answered.add(id);
9040
+ const output = typeof item.output === "string" ? item.output : normalizeContent(item.output, "tool");
9041
+ addMessage({ role: "tool", tool_call_id: id, content: output, ...item.is_error === true ? { is_error: true } : {} });
9042
+ return;
9043
+ }
9044
+ if (item.type !== undefined && item.type !== "message")
9045
+ fail("unsupported_content", "Unsupported input item type");
9046
+ const role = string(item.role, "message role", true);
9047
+ if (!["user", "assistant", "system", "developer"].includes(role))
9048
+ fail("unsupported_content", "Unknown message role");
9049
+ const message = { role, content: normalizeContent(item.content, role) };
9050
+ if (pendingReasoning) {
9051
+ if (role !== "assistant")
9052
+ fail("unsupported_reasoning", "Reasoning must precede an assistant item");
9053
+ message.reasoning_content = pendingReasoning;
9054
+ pendingReasoning = "";
9055
+ }
9056
+ addMessage(message);
9057
+ });
8900
9058
  if (pendingReasoning)
8901
9059
  fail("unsupported_reasoning", "Orphan reasoning history cannot be represented");
8902
9060
  const result = { model, messages: chat };
@@ -8929,41 +9087,35 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8929
9087
  result.tool_choice = protocol === "chat_completions" ? { type: "function", function: { name: wire } } : { type: "tool", name: wire };
8930
9088
  }
8931
9089
  }
8932
- if (body.reasoning !== undefined) {
8933
- const reasoning = record(body.reasoning, "reasoning");
8934
- if (reasoning.summary !== undefined && reasoning.summary !== "none")
8935
- fail("unsupported_reasoning", "Requested reasoning summaries are not guaranteed by this upstream protocol");
8936
- if (reasoning.effort !== undefined && reasoning.effort !== "none") {
8937
- const effort = string(reasoning.effort, "reasoning effort", true);
8938
- if (protocol === "chat_completions") {
8939
- if (!capabilities.reasoningEffort)
8940
- fail("unsupported_reasoning", "Upstream does not advertise reasoning effort");
8941
- result.reasoning_effort = effort;
8942
- } else {
8943
- const thinking = capabilities.anthropicThinkingBudget;
8944
- if (!Number.isSafeInteger(thinking) || thinking < 1024 || typeof maxTokens !== "number" || thinking >= maxTokens)
8945
- fail("unsupported_reasoning", "Anthropic thinking requires an explicit budget below max_output_tokens");
8946
- result.thinking = { type: "enabled", budget_tokens: thinking };
8947
- }
8948
- }
8949
- }
8950
- if (body.text !== undefined) {
8951
- const text = record(body.text, "text");
8952
- if (text.verbosity !== undefined)
8953
- fail("unsupported_request", "Text verbosity cannot be represented");
8954
- if (text.format !== undefined) {
8955
- const format = record(text.format, "text format");
8956
- if (protocol !== "chat_completions" && format.type !== "text")
8957
- fail("unsupported_request", "Anthropic structured output requires a separate provider capability");
8958
- if (format.type === "json_schema")
8959
- result.response_format = { type: "json_schema", json_schema: { name: format.name, schema: format.schema, ...format.strict !== undefined ? { strict: format.strict } : {}, ...format.description !== undefined ? { description: format.description } : {} } };
8960
- else if (format.type === "json_object" || format.type === "text") {
8961
- if (protocol === "chat_completions")
8962
- result.response_format = format;
8963
- } else
8964
- fail("unsupported_request", "Unknown text format");
8965
- }
8966
- }
9090
+ applyResponsesPreferences(body, result, protocol, capabilities, maxTokens, diagnostics);
9091
+ if (body.text !== undefined)
9092
+ atParam("text.format", () => {
9093
+ const text = record(body.text, "text");
9094
+ if (text.format !== undefined) {
9095
+ const format = record(text.format, "text format");
9096
+ const allowed = format.type === "json_schema" ? ["type", "name", "schema", "strict", "description"] : ["type"];
9097
+ for (const key of Object.keys(format))
9098
+ if (!allowed.includes(key))
9099
+ fail("unsupported_request", "Unsupported output format field", `text.format.${key}`);
9100
+ if (format.type === "json_schema") {
9101
+ atParam("text.format.name", () => string(format.name, "output schema name", true));
9102
+ atParam("text.format.schema", () => record(format.schema, "output schema"));
9103
+ if (format.strict !== undefined && typeof format.strict !== "boolean")
9104
+ fail("invalid_payload", "Output schema strict must be boolean", "text.format.strict");
9105
+ if (format.description !== undefined)
9106
+ atParam("text.format.description", () => string(format.description, "output schema description"));
9107
+ }
9108
+ if (protocol !== "chat_completions" && format.type !== "text")
9109
+ fail("unsupported_request", "Anthropic structured output requires a separate provider capability");
9110
+ if (format.type === "json_schema")
9111
+ result.response_format = { type: "json_schema", json_schema: { name: format.name, schema: format.schema, ...format.strict !== undefined ? { strict: format.strict } : {}, ...format.description !== undefined ? { description: format.description } : {} } };
9112
+ else if (format.type === "json_object" || format.type === "text") {
9113
+ if (protocol === "chat_completions")
9114
+ result.response_format = format;
9115
+ } else
9116
+ fail("unsupported_request", "Unknown text format");
9117
+ }
9118
+ });
8967
9119
  if (protocol === "anthropic_messages") {
8968
9120
  const system = [];
8969
9121
  const messages = [];
@@ -8977,7 +9129,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
8977
9129
  for (const msg of chat) {
8978
9130
  if (msg.role === "system" || msg.role === "developer") {
8979
9131
  if (messages.length)
8980
- fail("unsupported_content", "Mid-conversation system instructions cannot be relocated safely");
9132
+ fail("unsupported_content", "Mid-conversation system instructions cannot be relocated safely", `${messageParams.get(msg)}.role`);
8981
9133
  system.push(typeof msg.content === "string" ? msg.content : msg.content.map((p) => string(p.text, "system text")).join(""));
8982
9134
  continue;
8983
9135
  }
@@ -9003,7 +9155,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}) {
9003
9155
  delete result.parallel_tool_calls;
9004
9156
  }
9005
9157
  }
9006
- return { body: result, toolNames };
9158
+ return { body: result, toolNames, canonicalInput, diagnostics };
9007
9159
  }
9008
9160
  // src/responses-codec/events.ts
9009
9161
  class ResponsesEventEncoder {
@@ -16,16 +16,27 @@ export interface ResponsesCodecLimits {
16
16
  export declare const DEFAULT_RESPONSES_CODEC_LIMITS: Readonly<ResponsesCodecLimits>;
17
17
  export interface ResponsesCodecCapabilities {
18
18
  reasoningEffort?: boolean;
19
+ /** Plain reasoning history support is independent of effort parameter mapping. */
20
+ reasoningHistory?: boolean;
21
+ /** Codex declares optional hosted search even when the selected model has no search backend. */
22
+ omitOptionalWebSearch?: boolean;
19
23
  /** Explicit provider-approved thinking budget; do not guess budgets from effort labels. */
20
24
  anthropicThinkingBudget?: number;
21
25
  maxOutputTokens?: number;
22
26
  limits?: Partial<ResponsesCodecLimits>;
23
27
  }
28
+ export interface ResponsesConversionDiagnostic {
29
+ param: string;
30
+ action: 'mapped' | 'omitted';
31
+ reason: string;
32
+ }
24
33
  export declare class ResponsesCodecError extends Error {
25
34
  readonly code: string;
26
- constructor(code: string, message: string);
35
+ readonly param?: string | undefined;
36
+ constructor(code: string, message: string, param?: string | undefined);
27
37
  }
28
- export declare function fail(code: string, message: string): never;
38
+ export declare function fail(code: string, message: string, param?: string): never;
39
+ export declare function atParam<T>(param: string, run: () => T): T;
29
40
  export declare function record(value: unknown, where: string): JsonRecord;
30
41
  export declare function string(value: unknown, where: string, nonempty?: boolean): string;
31
42
  export declare function list(value: unknown, where: string): unknown[];
@@ -1,4 +1,4 @@
1
1
  export { decodeResponsesRequest, type DecodedResponsesRequest } from './request';
2
2
  export { encodeResponsesResult } from './result';
3
3
  export { ResponsesEventEncoder } from './events';
4
- export { ResponsesCodecError, DEFAULT_RESPONSES_CODEC_LIMITS, type ResponsesProtocol, type ResponsesToolName, type ResponsesToolNames, type ResponsesCodecCapabilities, type ResponsesCodecLimits } from './common';
4
+ export { ResponsesCodecError, DEFAULT_RESPONSES_CODEC_LIMITS, type ResponsesProtocol, type ResponsesToolName, type ResponsesToolNames, type ResponsesCodecCapabilities, type ResponsesCodecLimits, type ResponsesConversionDiagnostic } from './common';
@@ -0,0 +1,3 @@
1
+ import { type JsonRecord, type ResponsesCodecCapabilities, type ResponsesConversionDiagnostic, type ResponsesProtocol } from './common';
2
+ /** Known preferences may be omitted; output constraints are handled by the request renderer. */
3
+ export declare function applyResponsesPreferences(body: JsonRecord, result: JsonRecord, protocol: ResponsesProtocol, capabilities: ResponsesCodecCapabilities, maxTokens: unknown, diagnostics: ResponsesConversionDiagnostic[]): void;
@@ -1,7 +1,9 @@
1
- import { type JsonRecord, type ResponsesCodecCapabilities, type ResponsesProtocol, type ResponsesToolNames } from './common';
1
+ import { type JsonRecord, type ResponsesCodecCapabilities, type ResponsesConversionDiagnostic, type ResponsesProtocol, type ResponsesToolNames } from './common';
2
2
  export interface DecodedResponsesRequest {
3
3
  body: JsonRecord;
4
4
  toolNames: ResponsesToolNames;
5
+ canonicalInput: JsonRecord[];
6
+ diagnostics: ResponsesConversionDiagnostic[];
5
7
  }
6
8
  /** Converts only self-contained history. Reference resolution belongs to the caller. */
7
9
  export declare function decodeResponsesRequest(raw: unknown, protocol: ResponsesProtocol, capabilities?: ResponsesCodecCapabilities): DecodedResponsesRequest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeffusion/bungee-llms",
3
- "version": "5.20.0",
3
+ "version": "5.21.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.20.0"
14
+ "@jeffusion/bungee-types": "^5.21.0"
15
15
  },
16
16
  "devDependencies": {
17
17
  "bun-types": "^1.3.2",