@jeffusion/bungee-core 6.10.0 → 6.11.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.
@@ -3763,8 +3763,9 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
3763
3763
  if (typeof rawContent === "string")
3764
3764
  return rawContent;
3765
3765
  const parts = list(rawContent, "message content");
3766
- for (const [index, rawPart] of parts.entries())
3767
- atParam(path ? `${path}[${index}]` : messageParam, () => {
3766
+ for (const [index, rawPart] of parts.entries()) {
3767
+ const partPath = `${path}[${index}]`;
3768
+ atParam(partPath, () => {
3768
3769
  const part = record(rawPart, "content part");
3769
3770
  if (part.type === "input_text" || part.type === "output_text" || part.type === "text")
3770
3771
  string(part.text, "content text");
@@ -3772,17 +3773,21 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
3772
3773
  string(part.refusal, "refusal");
3773
3774
  if (capabilities.preserveRefusalContent && protocol !== "chat_completions")
3774
3775
  fail("unsupported_content", "Target protocol cannot preserve refusal history authority");
3775
- } else if (part.type === "input_image" && (role === "user" || role === "tool" && options.toolOutputImages)) {
3776
+ } else if (part.type === "input_image" && (role === "user" || role === "tool")) {
3776
3777
  if (protocol !== "chat_completions")
3777
3778
  fail("unsupported_content", "Anthropic image conversion requires provider-specific URL/data support");
3779
+ for (const key of Object.keys(part))
3780
+ if (!["type", "image_url", "detail"].includes(key))
3781
+ fail("unsupported_content", "Unsupported image field", `${partPath}.${key}`);
3778
3782
  string(part.image_url, "image_url", true);
3779
- if (part.detail !== undefined && !["auto", "low", "high"].includes(String(part.detail)))
3780
- fail("unsupported_content", "Unknown image detail", path ? `${path}[${index}].detail` : undefined);
3783
+ if (part.detail !== undefined && (typeof part.detail !== "string" || !["auto", "low", "high"].includes(part.detail)))
3784
+ fail("unsupported_content", "Unknown image detail", `${partPath}.detail`);
3781
3785
  } else
3782
3786
  fail("unsupported_content", "Unsupported message content type");
3783
3787
  if (part.annotations !== undefined && list(part.annotations, "annotations").length)
3784
3788
  fail("unsupported_content", "Output annotations cannot be represented in input history");
3785
3789
  });
3790
+ }
3786
3791
  const normalized = contentNormalizer.normalizeResponsesMessageContent(parts.map((raw) => {
3787
3792
  const part = raw;
3788
3793
  return part.type === "refusal" ? { type: "output_text", text: part.refusal } : part;
@@ -3796,11 +3801,23 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
3796
3801
  }
3797
3802
  return normalized;
3798
3803
  };
3804
+ const appendAssistantContent = (previous, next) => {
3805
+ if (previous == null)
3806
+ return next;
3807
+ if (next == null)
3808
+ return previous;
3809
+ if (typeof previous === "string" && typeof next === "string")
3810
+ return previous + next;
3811
+ const parts = (value) => typeof value === "string" ? [{ type: "text", text: value }] : Array.isArray(value) ? value : [value];
3812
+ return [...parts(previous), ...parts(next)];
3813
+ };
3799
3814
  const chat = [];
3800
3815
  const messageParams = new WeakMap;
3801
3816
  let messageParam = "instructions";
3802
3817
  const callIds = new Set;
3818
+ const callToolNames = new Map;
3803
3819
  const answered = new Set;
3820
+ const projectedToolImages = [];
3804
3821
  let pendingReasoning = "";
3805
3822
  const addMessage = (message) => {
3806
3823
  if (chat.length >= budget.maxItems)
@@ -3808,12 +3825,21 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
3808
3825
  chat.push(message);
3809
3826
  messageParams.set(message, messageParam);
3810
3827
  };
3828
+ const flushToolImages = (force = false) => {
3829
+ if (!force && [...callIds].some((id) => !answered.has(id)))
3830
+ return;
3831
+ for (const { callId, name, images } of projectedToolImages)
3832
+ addMessage({ role: "user", content: [{ type: "text", text: `Images returned by tool ${JSON.stringify(name)} (call ${JSON.stringify(callId)}):` }, ...images] });
3833
+ projectedToolImages.length = 0;
3834
+ };
3811
3835
  if (body.instructions !== undefined)
3812
3836
  addMessage({ role: "system", content: string(body.instructions, "instructions") });
3813
3837
  for (const [inputIndex, rawItem] of input.entries())
3814
3838
  atParam(`input[${inputIndex}]`, () => {
3815
3839
  messageParam = `input[${inputIndex}]`;
3816
3840
  const item = record(rawItem, "input item");
3841
+ if (item.type !== "function_call_output" && item.type !== "custom_tool_call_output")
3842
+ flushToolImages();
3817
3843
  if (item.type === "additional_tools")
3818
3844
  return;
3819
3845
  if (item.type === "agent_message") {
@@ -3862,11 +3888,12 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
3862
3888
  bounded(args, budget.maxArgumentBytes, "arguments");
3863
3889
  argumentObject(args);
3864
3890
  const call = { id, type: "function", function: { name: resolve(item, custom), arguments: args } };
3891
+ callToolNames.set(id, String(item.name));
3865
3892
  const previous = chat.at(-1);
3866
3893
  if (previous?.role === "assistant" && !options.preserveContentOrder) {
3867
3894
  previous.tool_calls = [...previous.tool_calls ?? [], call];
3868
3895
  if (pendingReasoning)
3869
- previous.reasoning_content = pendingReasoning;
3896
+ previous.reasoning_content = previous.reasoning_content === undefined ? pendingReasoning : `${previous.reasoning_content}${pendingReasoning}`;
3870
3897
  } else
3871
3898
  addMessage({ role: "assistant", content: null, tool_calls: [call], ...pendingReasoning ? { reasoning_content: pendingReasoning } : {} });
3872
3899
  pendingReasoning = "";
@@ -3877,7 +3904,21 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
3877
3904
  if (!callIds.has(id) || answered.has(id))
3878
3905
  fail("invalid_tool_history", "Unmatched or duplicate tool result");
3879
3906
  answered.add(id);
3880
- const output = typeof item.output === "string" ? item.output : normalizeContent(item.output, "tool", `${messageParam}.output`);
3907
+ if (Array.isArray(item.output) && item.output.length > budget.maxItems && item.output.some((part) => part && typeof part === "object" && part.type === "input_image"))
3908
+ fail("resource_limit", "Too many tool output content parts", `${messageParam}.output`);
3909
+ let output = typeof item.output === "string" ? item.output : normalizeContent(item.output, "tool", `${messageParam}.output`);
3910
+ if (protocol === "chat_completions" && !options.toolOutputImages && Array.isArray(output)) {
3911
+ const images = output.filter((part) => part.type === "image_url");
3912
+ if (images.length) {
3913
+ output.forEach((part, index) => {
3914
+ if (part.type === "image_url")
3915
+ diagnostics.push({ param: `${messageParam}.output[${index}]`, action: "mapped", reason: "chat_tool_image_projected_to_user_message" });
3916
+ });
3917
+ const text = output.filter((part) => part.type !== "image_url");
3918
+ projectedToolImages.push({ callId: id, name: callToolNames.get(id) ?? id, images });
3919
+ output = text.length ? text : "Tool output contained only images; see the following images.";
3920
+ }
3921
+ }
3881
3922
  addMessage({ role: "tool", tool_call_id: id, content: output, ...item.is_error === true ? { is_error: true } : {} });
3882
3923
  return;
3883
3924
  }
@@ -3886,7 +3927,7 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
3886
3927
  const role = string(item.role, "message role", true);
3887
3928
  if (!["user", "assistant", "system", "developer"].includes(role))
3888
3929
  fail("unsupported_content", "Unknown message role");
3889
- const message = { role, content: normalizeContent(item.content, role) };
3930
+ const message = { role, content: normalizeContent(item.content, role, `${messageParam}.content`) };
3890
3931
  if (options.preserveContentOrder && item.id !== undefined)
3891
3932
  message.id = string(item.id, "item.id", true);
3892
3933
  if (pendingReasoning) {
@@ -3895,10 +3936,20 @@ function decodeResponsesRequest(raw, protocol, capabilities = {}, options = {})
3895
3936
  message.reasoning_content = pendingReasoning;
3896
3937
  pendingReasoning = "";
3897
3938
  }
3939
+ if (protocol === "chat_completions" && !options.preserveContentOrder && role === "assistant") {
3940
+ const previous = chat.at(-1);
3941
+ if (previous?.role === "assistant") {
3942
+ previous.content = appendAssistantContent(previous.content, message.content);
3943
+ if (message.reasoning_content !== undefined)
3944
+ previous.reasoning_content = previous.reasoning_content === undefined ? message.reasoning_content : `${previous.reasoning_content}${message.reasoning_content}`;
3945
+ return;
3946
+ }
3947
+ }
3898
3948
  addMessage(message);
3899
3949
  });
3900
3950
  if (pendingReasoning)
3901
3951
  fail("unsupported_reasoning", "Orphan reasoning history cannot be represented");
3952
+ flushToolImages(true);
3902
3953
  const result = { model, messages: chat };
3903
3954
  const omittedAllTools = !tools.length && diagnostics.some((item) => item.reason === "optional_hosted_web_search_unavailable");
3904
3955
  for (const key of ["stream", "temperature", "top_p", "parallel_tool_calls", "metadata"])
@@ -5403,14 +5454,21 @@ function validateProviderResponse(raw, protocol) {
5403
5454
  fields2(b, ["id", "object", "created", "model", "choices", "usage", "system_fingerprint", "service_tier"], "", "unsupported_response");
5404
5455
  for (const rawChoice of list(b.choices, "choices")) {
5405
5456
  const c = record(rawChoice, "choice");
5406
- fields2(c, ["index", "message", "finish_reason", "logprobs"], "choices", "unsupported_response");
5457
+ fields2(c, ["index", "message", "finish_reason", "logprobs", "provider_specific_fields"], "choices", "unsupported_response");
5458
+ if (c.provider_specific_fields !== undefined) {
5459
+ const providerFields = c.provider_specific_fields;
5460
+ if (providerFields === null || typeof providerFields !== "object" || Array.isArray(providerFields) || Object.keys(providerFields).length)
5461
+ fail("unsupported_response", "Only empty provider-specific choice fields are portable", "choices.provider_specific_fields");
5462
+ }
5407
5463
  if (c.logprobs != null)
5408
5464
  fail("unsupported_response", "Log probabilities cannot cross protocols", "choices.logprobs");
5409
5465
  const m = record(c.message, "message");
5410
5466
  fields2(m, ["role", "content", "refusal", "reasoning_content", "reasoning", "tool_calls", "annotations"], "message", "unsupported_content");
5411
- for (const rawCall of m.tool_calls === undefined ? [] : list(m.tool_calls, "tool_calls")) {
5467
+ for (const [callIndex, rawCall] of (m.tool_calls === undefined ? [] : list(m.tool_calls, "tool_calls")).entries()) {
5412
5468
  const call = record(rawCall, "call");
5413
- fields2(call, ["id", "type", "function"], "tool_calls", "unsupported_tool");
5469
+ fields2(call, ["id", "type", "function", "index"], "tool_calls", "unsupported_tool");
5470
+ if (call.index !== undefined && integer(call.index, `tool_calls[${callIndex}].index`) !== callIndex)
5471
+ fail("invalid_payload", "Tool call index must match its array position", `tool_calls[${callIndex}].index`);
5414
5472
  fields2(record(call.function, "function"), ["name", "arguments"], "tool_calls.function", "unsupported_tool");
5415
5473
  }
5416
5474
  }
@@ -7512,4 +7570,4 @@ export {
7512
7570
  targetPath
7513
7571
  };
7514
7572
 
7515
- //# debugId=F228E39D1C0449DF64756E2164756E21
7573
+ //# debugId=EA74ECD3BF3B21AA64756E2164756E21