@jeffusion/bungee-core 5.11.0 → 5.12.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.
@@ -15788,6 +15788,14 @@ async function* parseCodexSSE(source, options = {}) {
15788
15788
  yield* consumeLine(remainingLine);
15789
15789
  yield* dispatch();
15790
15790
  }
15791
+ function chatToolCall(state, value, includeIdentity, preserveCustomTools) {
15792
+ const custom = preserveCustomTools && state.kind === "custom_tool_call";
15793
+ return {
15794
+ index: state.index,
15795
+ ...includeIdentity ? { id: state.id, type: custom ? "custom" : "function" } : {},
15796
+ [custom ? "custom" : "function"]: { ...includeIdentity ? { name: state.name } : {}, [custom ? "input" : "arguments"]: value }
15797
+ };
15798
+ }
15791
15799
  function streamIncludesUsage(request) {
15792
15800
  return request?.stream_options?.include_usage === true;
15793
15801
  }
@@ -15872,6 +15880,7 @@ class CodexResponseProcessor {
15872
15880
  output = [];
15873
15881
  includeUsage;
15874
15882
  target;
15883
+ preserveCustomTools;
15875
15884
  text = "";
15876
15885
  reasoning = "";
15877
15886
  messageText = new Map;
@@ -15891,6 +15900,7 @@ class CodexResponseProcessor {
15891
15900
  constructor(options = {}) {
15892
15901
  this.includeUsage = options.includeUsage ?? (options.request ? streamIncludesUsage(options.request) : false);
15893
15902
  this.target = options.target ?? "chat";
15903
+ this.preserveCustomTools = options.preserveCustomTools ?? false;
15894
15904
  this.redactDiagnostic = options.redactDiagnostic;
15895
15905
  }
15896
15906
  validateOutputItem(item) {
@@ -16061,7 +16071,7 @@ class CodexResponseProcessor {
16061
16071
  this.tools.set(`output:${String(data.output_index)}`, state);
16062
16072
  const chunk = base();
16063
16073
  chunk.choices[0].delta.role = "assistant";
16064
- chunk.choices[0].delta.tool_calls = [{ index: state.index, id: state.id, type: "function", function: { name: state.name, arguments: "" } }];
16074
+ chunk.choices[0].delta.tool_calls = [chatToolCall(state, "", true, this.preserveCustomTools)];
16065
16075
  output.push(chunk);
16066
16076
  } else if (type === "response.function_call_arguments.delta" || type === "response.custom_tool_call_input.delta") {
16067
16077
  const state = this.findTool(data);
@@ -16070,7 +16080,7 @@ class CodexResponseProcessor {
16070
16080
  state.arguments += delta;
16071
16081
  state.emittedArguments = true;
16072
16082
  const chunk = base();
16073
- chunk.choices[0].delta.tool_calls = [{ index: state.index, function: { arguments: delta } }];
16083
+ chunk.choices[0].delta.tool_calls = [chatToolCall(state, delta, false, this.preserveCustomTools)];
16074
16084
  output.push(chunk);
16075
16085
  }
16076
16086
  } else if (type === "response.function_call_arguments.done" || type === "response.custom_tool_call_input.done") {
@@ -16081,7 +16091,7 @@ class CodexResponseProcessor {
16081
16091
  if (state && !state.emittedArguments && state.arguments) {
16082
16092
  state.emittedArguments = true;
16083
16093
  const chunk = base();
16084
- chunk.choices[0].delta.tool_calls = [{ index: state.index, function: { arguments: state.arguments } }];
16094
+ chunk.choices[0].delta.tool_calls = [chatToolCall(state, state.arguments, false, this.preserveCustomTools)];
16085
16095
  output.push(chunk);
16086
16096
  }
16087
16097
  } else if (type === "response.output_item.done") {
@@ -16095,7 +16105,7 @@ class CodexResponseProcessor {
16095
16105
  if (!state.emittedArguments && state.arguments) {
16096
16106
  state.emittedArguments = true;
16097
16107
  const chunk = base();
16098
- chunk.choices[0].delta.tool_calls = [{ index: state.index, function: { arguments: state.arguments } }];
16108
+ chunk.choices[0].delta.tool_calls = [chatToolCall(state, state.arguments, false, this.preserveCustomTools)];
16099
16109
  output.push(chunk);
16100
16110
  }
16101
16111
  } else {
@@ -16105,7 +16115,7 @@ class CodexResponseProcessor {
16105
16115
  if (data.output_index !== undefined)
16106
16116
  this.tools.set(`output:${String(data.output_index)}`, state2);
16107
16117
  const chunk = base();
16108
- chunk.choices[0].delta.tool_calls = [{ index: state2.index, id: state2.id, type: "function", function: { name: state2.name, arguments: state2.arguments } }];
16118
+ chunk.choices[0].delta.tool_calls = [chatToolCall(state2, state2.arguments, true, this.preserveCustomTools)];
16109
16119
  output.push(chunk);
16110
16120
  }
16111
16121
  this.upsertOutput(item, data.output_index === undefined ? undefined : Number(data.output_index), this.target === "responses", data.content_index === undefined ? undefined : Number(data.content_index), typeof data.item_id === "string" ? data.item_id : undefined);
@@ -16230,7 +16240,7 @@ async function consumeCodexResponse(source, options = {}) {
16230
16240
  processor.process(event);
16231
16241
  return processor.finish();
16232
16242
  }
16233
- function responsesToChatCompletion(response) {
16243
+ function responsesToChatCompletion(response, preserveCustomTools = false) {
16234
16244
  let content = "";
16235
16245
  let reasoning = "";
16236
16246
  const tool_calls = [];
@@ -16243,7 +16253,9 @@ function responsesToChatCompletion(response) {
16243
16253
  content += textFromItem(item);
16244
16254
  if (item.type === "reasoning")
16245
16255
  reasoning += (item.summary ?? []).filter((part) => part.type === "summary_text").map((part) => String(part.text ?? "")).join("") + (item.content ?? []).filter((part) => part.type === "reasoning_text").map((part) => String(part.text ?? "")).join("");
16246
- if (item.type === "function_call" || item.type === "custom_tool_call")
16256
+ if (item.type === "custom_tool_call" && preserveCustomTools)
16257
+ tool_calls.push({ id: item.call_id, type: "custom", custom: { name: item.name, input: String(item.input ?? "") } });
16258
+ else if (item.type === "function_call" || item.type === "custom_tool_call")
16247
16259
  tool_calls.push({ id: item.call_id, type: "function", function: { name: item.name, arguments: String(item.arguments ?? item.input ?? "") } });
16248
16260
  if (item.type === "image_generation_call" && item.result)
16249
16261
  images.push({ type: "image_url", image_url: { url: `data:${imageMime(item.output_format)};base64,${item.result}` } });
@@ -16346,13 +16358,140 @@ var MAX_DISCARD_BYTES = 64 * 1024;
16346
16358
  var MAX_MODELS_BODY_BYTES = 256 * 1024;
16347
16359
  var MAX_PROFILE_HEADER_VALUE_BYTES = 8192;
16348
16360
  var CHATGPT_ORIGIN = "https://chatgpt.com";
16361
+ var SIWC_ORIGIN = "https://api.openai.com";
16349
16362
  var CODEX_ROUTING_HINT_HEADER = "X-Codex-Routing-Hint";
16363
+ var SIWC_FORBIDDEN_FIELDS = [
16364
+ "background",
16365
+ "conversation",
16366
+ "max_output_tokens",
16367
+ "max_completion_tokens",
16368
+ "max_tool_calls",
16369
+ "metadata",
16370
+ "moderation",
16371
+ "multi_agent",
16372
+ "prompt",
16373
+ "prompt_cache_retention",
16374
+ "safety_identifier",
16375
+ "temperature",
16376
+ "top_logprobs",
16377
+ "top_p",
16378
+ "truncation",
16379
+ "user",
16380
+ "previous_response_id",
16381
+ "client_metadata",
16382
+ "context_management",
16383
+ "prompt_cache_options"
16384
+ ];
16350
16385
  function record(value) {
16351
16386
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
16352
16387
  throw new CodexProtocolError("invalid_response", "Request must be a JSON object");
16353
16388
  }
16354
16389
  return value;
16355
16390
  }
16391
+ function siwcTool(tool) {
16392
+ const value = record(tool);
16393
+ if (value.type !== "function" && value.type !== "custom") {
16394
+ throw new CodexProtocolError("invalid_response", "SIWC supports only function and custom tools");
16395
+ }
16396
+ const nested = value.type === "function" ? value.function : value.custom;
16397
+ const definition = nested === undefined ? value : record(nested);
16398
+ if (typeof definition.name !== "string" || !/^[A-Za-z0-9_-]{1,64}$/.test(definition.name)) {
16399
+ throw new CodexProtocolError("invalid_response", "SIWC tool names must be 1-64 ASCII letters, digits, underscores or hyphens");
16400
+ }
16401
+ const normalized = { ...definition, type: value.type };
16402
+ if (value.type === "custom" && normalized.format?.type === "grammar" && normalized.format.grammar !== undefined) {
16403
+ const grammar = record(normalized.format.grammar);
16404
+ if (!["lark", "regex"].includes(grammar.syntax) || typeof grammar.definition !== "string") {
16405
+ throw new CodexProtocolError("invalid_response", "SIWC custom tool grammar is invalid");
16406
+ }
16407
+ normalized.format = { type: "grammar", syntax: grammar.syntax, definition: grammar.definition };
16408
+ }
16409
+ return normalized;
16410
+ }
16411
+ function normalizeSiwcRequest(input, target) {
16412
+ if (input.previous_response_id !== undefined) {
16413
+ throw new CodexProtocolError("invalid_response", "SIWC requires the full conversation in input; previous_response_id is unsupported");
16414
+ }
16415
+ if (input.tools !== undefined && !Array.isArray(input.tools)) {
16416
+ throw new CodexProtocolError("invalid_response", "SIWC tools must be an array");
16417
+ }
16418
+ input.tools?.forEach(siwcTool);
16419
+ if (target === "chat" && !Array.isArray(input.messages)) {
16420
+ throw new CodexProtocolError("invalid_response", "Chat messages must be an array");
16421
+ }
16422
+ const out = target === "chat" ? convertChatCompletionsRequestToCodex(input) : JSON.parse(JSON.stringify(input));
16423
+ out.store = false;
16424
+ out.stream = true;
16425
+ if (out.instructions === undefined || out.instructions === null)
16426
+ out.instructions = "";
16427
+ if (typeof out.instructions !== "string")
16428
+ throw new CodexProtocolError("invalid_response", "SIWC instructions must be text");
16429
+ if (typeof out.input === "string")
16430
+ out.input = [{ type: "message", role: "user", content: [{ type: "input_text", text: out.input }] }];
16431
+ if (out.input === undefined)
16432
+ out.input = [];
16433
+ if (!Array.isArray(out.input))
16434
+ throw new CodexProtocolError("invalid_response", "SIWC input must contain the full conversation as an array");
16435
+ const inputTypes = new Set(["message", "function_call", "function_call_output", "custom_tool_call", "custom_tool_call_output", "reasoning", "compaction"]);
16436
+ for (const item of out.input) {
16437
+ const value = record(item);
16438
+ if (value.type === undefined && typeof value.role === "string")
16439
+ value.type = "message";
16440
+ if (!inputTypes.has(value.type))
16441
+ throw new CodexProtocolError("invalid_response", "SIWC input contains an unsupported item");
16442
+ if (value.type === "message") {
16443
+ if (!["system", "developer", "user", "assistant"].includes(value.role))
16444
+ throw new CodexProtocolError("invalid_response", "SIWC input contains an unsupported role");
16445
+ if (value.role === "system")
16446
+ value.role = "developer";
16447
+ }
16448
+ }
16449
+ if (Array.isArray(out.tools))
16450
+ out.tools = out.tools.map(siwcTool);
16451
+ if (out.tool_choice !== undefined) {
16452
+ if (typeof out.tool_choice === "string") {
16453
+ if (!["auto", "none", "required"].includes(out.tool_choice))
16454
+ throw new CodexProtocolError("invalid_response", "SIWC tool choice is unsupported");
16455
+ } else
16456
+ out.tool_choice = siwcTool(out.tool_choice);
16457
+ }
16458
+ for (const field of SIWC_FORBIDDEN_FIELDS)
16459
+ delete out[field];
16460
+ return out;
16461
+ }
16462
+ function setSiwcHeaders(context, target) {
16463
+ for (const name of Object.keys(context.headers)) {
16464
+ if (/^(chatgpt-account-id|session-id|thread-id|version|x-codex-.*|x-openai-internal-codex-.*)$/i.test(name))
16465
+ delete context.headers[name];
16466
+ }
16467
+ setHeader(context.headers, "User-Agent", "Bungee/5.11.0");
16468
+ setHeader(context.headers, "Originator", "Bungee");
16469
+ setHeader(context.headers, "Accept", target === "models" ? "application/json" : "text/event-stream");
16470
+ if (target !== "models")
16471
+ setHeader(context.headers, "Content-Type", "application/json");
16472
+ }
16473
+ function parseSiwcModelsBody(body) {
16474
+ let payload;
16475
+ try {
16476
+ payload = JSON.parse(body);
16477
+ } catch {
16478
+ throw new CodexModelsError("invalid_json");
16479
+ }
16480
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload))
16481
+ throw new CodexModelsError("invalid_structure");
16482
+ const models = payload.models;
16483
+ if (!Array.isArray(models) || models.length > 512)
16484
+ throw new CodexModelsError("invalid_structure");
16485
+ const text = (value) => typeof value === "string" && value.length > 0 && value.length <= 512 && value === value.trim() && !/[\r\n\0]/.test(value);
16486
+ const listed = [];
16487
+ for (const model of models) {
16488
+ if (!model || typeof model !== "object" || Array.isArray(model) || !text(model.slug) || typeof model.visibility !== "string" || model.display_name !== undefined && !text(model.display_name))
16489
+ throw new CodexModelsError("invalid_structure");
16490
+ if (model.visibility === "list")
16491
+ listed.push({ id: model.slug });
16492
+ }
16493
+ return listed;
16494
+ }
16356
16495
  function validHeaderValue(value) {
16357
16496
  return typeof value === "string" && new TextEncoder().encode(value).byteLength <= MAX_PROFILE_HEADER_VALUE_BYTES && value.trim().length > 0 && !/[\r\n\0]/.test(value);
16358
16497
  }
@@ -16577,7 +16716,7 @@ function serialize(event) {
16577
16716
 
16578
16717
  `;
16579
16718
  }
16580
- function protocolStreamBody(source, target, includeUsage, signal, redact) {
16719
+ function protocolStreamBody(source, target, includeUsage, signal, redact, preserveCustomTools = false) {
16581
16720
  const protocolController = new AbortController;
16582
16721
  const onAbort = () => protocolController.abort(signal.reason ?? "cancelled");
16583
16722
  if (signal.aborted)
@@ -16595,7 +16734,7 @@ function protocolStreamBody(source, target, includeUsage, signal, redact) {
16595
16734
  const completion = new Promise((resolve) => {
16596
16735
  resolveCompletion = resolve;
16597
16736
  });
16598
- const processor = new CodexResponseProcessor({ target, includeUsage, redactDiagnostic: redact });
16737
+ const processor = new CodexResponseProcessor({ target, includeUsage, redactDiagnostic: redact, preserveCustomTools });
16599
16738
  let terminalOutcome;
16600
16739
  const events = parseCodexSSE(source, { signal: protocolController.signal });
16601
16740
  const iterator = async function* () {
@@ -16667,6 +16806,26 @@ class ChatgptOauthAdapter {
16667
16806
  constructor() {}
16668
16807
  beforeRequest(context) {
16669
16808
  const target = context.url.pathname === MODELS_PATH ? "models" : context.url.pathname === CHAT_COMPLETIONS_PATH ? "chat" : context.url.pathname === RESPONSES_PATH ? "responses" : undefined;
16809
+ if (context.url.origin === SIWC_ORIGIN) {
16810
+ if (target === undefined)
16811
+ return context;
16812
+ const input = target === "models" ? undefined : record(context.body);
16813
+ const stream = input?.stream === true;
16814
+ const includeUsage = input ? streamIncludesUsage(input) : false;
16815
+ context.body = input ? normalizeSiwcRequest(input, target) : undefined;
16816
+ if (target !== "models")
16817
+ context.url.pathname = RESPONSES_PATH;
16818
+ setSiwcHeaders(context, target);
16819
+ this.requests.set(context.requestId, Object.freeze({
16820
+ target,
16821
+ stream,
16822
+ includeUsage,
16823
+ siwc: true,
16824
+ allowMissingContentType: target !== "models",
16825
+ adaptedResponses: new WeakSet
16826
+ }));
16827
+ return context;
16828
+ }
16670
16829
  if (context.url.pathname === CODEX_RESPONSES_PATH) {
16671
16830
  setCodexRoutingHint(context.headers, context.body);
16672
16831
  enforceCodexResponsesLite(context);
@@ -16722,6 +16881,14 @@ class ChatgptOauthAdapter {
16722
16881
  return context;
16723
16882
  }
16724
16883
  reconcileOutboundRequest(context) {
16884
+ if (context.url.origin === SIWC_ORIGIN) {
16885
+ if (context.url.pathname === RESPONSES_PATH) {
16886
+ context.body = normalizeSiwcRequest(record(context.body), "responses");
16887
+ setSiwcHeaders(context, "responses");
16888
+ } else if (context.url.pathname === MODELS_PATH)
16889
+ setSiwcHeaders(context, "models");
16890
+ return context;
16891
+ }
16725
16892
  if (context.url.pathname === CODEX_RESPONSES_PATH) {
16726
16893
  setCodexRoutingHint(context.headers, context.body);
16727
16894
  enforceCodexResponsesLite(context);
@@ -16766,7 +16933,8 @@ class ChatgptOauthAdapter {
16766
16933
  return errorResponse(502, joinCompletion(result.completion, Promise.resolve({ status: "failed", code: "invalid_response" }), context.signal));
16767
16934
  }
16768
16935
  try {
16769
- const models = parseCodexModelsBody(await readBoundedBody(result.response, context.signal), MAX_MODELS_BODY_BYTES);
16936
+ const text = await readBoundedBody(result.response, context.signal);
16937
+ const models = state.siwc ? parseSiwcModelsBody(text) : parseCodexModelsBody(text, MAX_MODELS_BODY_BYTES);
16770
16938
  const body = {
16771
16939
  object: "list",
16772
16940
  data: models.map(({ id }) => ({ id, object: "model", owned_by: "openai" }))
@@ -16792,7 +16960,7 @@ class ChatgptOauthAdapter {
16792
16960
  return errorResponse(502, joinCompletion(result.completion, bodyCompletion, context.signal));
16793
16961
  }
16794
16962
  if (state.stream) {
16795
- const converted = protocolStreamBody(result.response.body, state.target, state.includeUsage, context.signal, context.redactDiagnostic);
16963
+ const converted = protocolStreamBody(result.response.body, state.target, state.includeUsage, context.signal, context.redactDiagnostic, state.siwc);
16796
16964
  const streamCompletion = joinCompletion(result.completion, converted.completion, context.signal);
16797
16965
  streamCompletion.then(cleanup, cleanup);
16798
16966
  return {
@@ -16807,7 +16975,7 @@ class ChatgptOauthAdapter {
16807
16975
  target: state.target,
16808
16976
  redactDiagnostic: context.redactDiagnostic
16809
16977
  });
16810
- const body = state.target === "chat" ? responsesToChatCompletion(stateResult.response) : stateResult.response;
16978
+ const body = state.target === "chat" ? responsesToChatCompletion(stateResult.response, state.siwc) : stateResult.response;
16811
16979
  const protocolCompletion = Promise.resolve(stateResult.terminal === "completed" ? { status: "completed" } : { status: "incomplete", code: "incomplete" });
16812
16980
  const nonStreamCompletion = joinCompletion(result.completion, protocolCompletion, context.signal);
16813
16981
  nonStreamCompletion.then(cleanup, cleanup);
@@ -16847,4 +17015,4 @@ export {
16847
17015
  server_default as default
16848
17016
  };
16849
17017
 
16850
- //# debugId=AD304F01A6899F4964756E2164756E21
17018
+ //# debugId=9E89B5DD5BF3FADA64756E2164756E21