@jeffusion/bungee-core 5.18.0 → 5.20.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.
Files changed (41) hide show
  1. package/dist/gateway/contracts.d.ts +31 -2
  2. package/dist/gateway/dispatch-plugin.d.ts +7 -0
  3. package/dist/gateway/dispatch-targets.d.ts +4 -0
  4. package/dist/gateway/plugin.js +2 -0
  5. package/dist/gateway/routing-plugin.d.ts +3 -1
  6. package/dist/hooks/plugin-hooks.d.ts +6 -1
  7. package/dist/main.js +182 -50
  8. package/dist/plugin-extensions.d.ts +2 -0
  9. package/dist/plugins/ai-transformer/index.js +11 -1
  10. package/dist/plugins/ai-transformer/index.js.map +6 -4
  11. package/dist/plugins/anthropic-request-sanitizer/index.js.map +1 -1
  12. package/dist/plugins/anthropic-tool-name-transformer/index.js.map +1 -1
  13. package/dist/plugins/chatgpt-oauth/control.js.map +1 -1
  14. package/dist/plugins/chatgpt-oauth/index.js.map +1 -1
  15. package/dist/plugins/codex-router/control.js +673 -2
  16. package/dist/plugins/codex-router/control.js.map +19 -4
  17. package/dist/plugins/codex-router/index.js +3938 -4
  18. package/dist/plugins/codex-router/index.js.map +24 -7
  19. package/dist/plugins/codex-router/manifest.json +15 -1
  20. package/dist/plugins/deepseek-reasoning-fix/index.js.map +1 -1
  21. package/dist/plugins/key-access/control.js.map +2 -2
  22. package/dist/plugins/key-access/index.js +14 -10
  23. package/dist/plugins/key-access/index.js.map +4 -4
  24. package/dist/plugins/key-access/ingress.js +14 -10
  25. package/dist/plugins/key-access/ingress.js.map +4 -4
  26. package/dist/plugins/key-rate-limit/index.js.map +1 -1
  27. package/dist/plugins/local-accounts/control.js.map +1 -1
  28. package/dist/plugins/local-accounts/index.js.map +1 -1
  29. package/dist/plugins/model-mapping/index.js.map +1 -1
  30. package/dist/plugins/models-dev/index.js.map +1 -1
  31. package/dist/plugins/openai-messages-to-chat/index.js +691 -543
  32. package/dist/plugins/openai-messages-to-chat/index.js.map +9 -5
  33. package/dist/plugins/signature-repair/index.js.map +1 -1
  34. package/dist/plugins/token-budget/control.js.map +1 -1
  35. package/dist/plugins/token-budget/index.js.map +1 -1
  36. package/dist/plugins/token-metering/index.js +652 -71
  37. package/dist/plugins/token-metering/index.js.map +6 -4
  38. package/dist/plugins/token-stats/control.js.map +1 -1
  39. package/dist/plugins/token-stats/index.js.map +1 -1
  40. package/dist/scoped-plugin-registry.d.ts +5 -2
  41. package/package.json +2 -2
@@ -5337,6 +5337,599 @@ var PROVIDER_ADAPTERS = {
5337
5337
  anthropic: createAnthropicAdapter(),
5338
5338
  gemini: createGeminiAdapter()
5339
5339
  };
5340
+ // packages/llms/src/responses-codec/common.ts
5341
+ var DEFAULT_RESPONSES_CODEC_LIMITS = Object.freeze({
5342
+ maxRequestBytes: 8 * 1024 * 1024,
5343
+ maxOutputBytes: 8 * 1024 * 1024,
5344
+ maxArgumentBytes: 1024 * 1024,
5345
+ maxItems: 1024
5346
+ });
5347
+
5348
+ class ResponsesCodecError extends Error {
5349
+ code;
5350
+ constructor(code, message) {
5351
+ super(message);
5352
+ this.code = code;
5353
+ this.name = "ResponsesCodecError";
5354
+ }
5355
+ }
5356
+ function fail(code, message) {
5357
+ throw new ResponsesCodecError(code, message);
5358
+ }
5359
+ function record(value, where) {
5360
+ if (!value || typeof value !== "object" || Array.isArray(value))
5361
+ fail("invalid_payload", `${where} must be an object`);
5362
+ return value;
5363
+ }
5364
+ function string(value, where, nonempty = false) {
5365
+ if (typeof value !== "string" || nonempty && !value)
5366
+ fail("invalid_payload", `${where} must be a${nonempty ? " non-empty" : ""} string`);
5367
+ return value;
5368
+ }
5369
+ function list(value, where) {
5370
+ if (!Array.isArray(value))
5371
+ fail("invalid_payload", `${where} must be an array`);
5372
+ return value;
5373
+ }
5374
+ function limits(overrides) {
5375
+ const result = { ...DEFAULT_RESPONSES_CODEC_LIMITS, ...overrides };
5376
+ for (const [key, value] of Object.entries(result)) {
5377
+ if (!Number.isSafeInteger(value) || value <= 0)
5378
+ fail("invalid_limit", `${key} must be a positive integer`);
5379
+ }
5380
+ return result;
5381
+ }
5382
+ function bytes(value) {
5383
+ return new TextEncoder().encode(value).byteLength;
5384
+ }
5385
+ function bounded(value, max, where) {
5386
+ if (bytes(value) > max)
5387
+ fail("resource_limit", `${where} exceeds ${max} bytes`);
5388
+ return value;
5389
+ }
5390
+ function serialized(value, max, where) {
5391
+ let result;
5392
+ try {
5393
+ result = JSON.stringify(value);
5394
+ } catch {
5395
+ fail("invalid_payload", `${where} must be serializable JSON`);
5396
+ }
5397
+ if (result === undefined)
5398
+ fail("invalid_payload", `${where} must be serializable JSON`);
5399
+ return bounded(result, max, where);
5400
+ }
5401
+ function argumentObject(text) {
5402
+ let value;
5403
+ try {
5404
+ value = JSON.parse(text);
5405
+ } catch {
5406
+ fail("invalid_tool_arguments", "Tool arguments must be complete JSON");
5407
+ }
5408
+ return record(value, "tool arguments");
5409
+ }
5410
+ function restoreTool(name, names) {
5411
+ const result = names.get(name);
5412
+ if (result)
5413
+ return result;
5414
+ if (names.size)
5415
+ fail("unknown_tool", `Provider returned undeclared tool ${name}`);
5416
+ return { name, custom: false };
5417
+ }
5418
+ function customInput(text) {
5419
+ const value = argumentObject(text);
5420
+ if (Object.keys(value).length !== 1 || typeof value.input !== "string") {
5421
+ fail("invalid_tool_arguments", "Custom tools require exactly one string input property");
5422
+ }
5423
+ return value.input;
5424
+ }
5425
+ function customInputPrefix(text) {
5426
+ const prefix = /^\s*\{\s*"input"\s*:\s*"/.exec(text);
5427
+ if (!prefix)
5428
+ return "";
5429
+ let result = "";
5430
+ for (let i = prefix[0].length;i < text.length; ) {
5431
+ const char = text[i];
5432
+ if (char === '"')
5433
+ break;
5434
+ if (char !== "\\") {
5435
+ if (char.charCodeAt(0) < 32)
5436
+ fail("invalid_tool_arguments", "Invalid JSON string character");
5437
+ result += char;
5438
+ i++;
5439
+ continue;
5440
+ }
5441
+ const escape = text[i + 1];
5442
+ if (escape === undefined)
5443
+ break;
5444
+ const size = escape === "u" ? 6 : 2;
5445
+ if (i + size > text.length)
5446
+ break;
5447
+ try {
5448
+ result += JSON.parse(`"${text.slice(i, i + size)}"`);
5449
+ } catch {
5450
+ fail("invalid_tool_arguments", "Invalid JSON string escape");
5451
+ }
5452
+ i += size;
5453
+ }
5454
+ return result;
5455
+ }
5456
+ function terminal(reason, protocol) {
5457
+ if (typeof reason !== "string" || !reason)
5458
+ fail("missing_terminal", "Upstream has no explicit completion reason");
5459
+ const completed = protocol === "chat_completions" ? ["stop", "tool_calls", "function_call"] : ["end_turn", "tool_use", "stop_sequence"];
5460
+ if (completed.includes(reason))
5461
+ return { status: "completed", reason };
5462
+ if (reason === "length" || reason === "max_tokens" || reason === "model_context_window_exceeded")
5463
+ return { status: "incomplete", reason: "max_output_tokens" };
5464
+ if (reason === "content_filter" || reason === "refusal")
5465
+ return { status: "incomplete", reason: "content_filter" };
5466
+ if (reason === "error")
5467
+ return { status: "failed", reason: "upstream_error" };
5468
+ fail("unknown_terminal", `Unsupported upstream completion reason: ${reason}`);
5469
+ }
5470
+ function tokens(value, where) {
5471
+ if (value === undefined)
5472
+ return 0;
5473
+ if (!Number.isSafeInteger(value) || value < 0)
5474
+ fail("invalid_usage", `${where} must be a non-negative integer`);
5475
+ return value;
5476
+ }
5477
+ function normalizeUsage(raw, protocol, prior = {}) {
5478
+ if (raw === undefined || raw === null)
5479
+ return prior;
5480
+ const usage = record(raw, "usage");
5481
+ const chat = protocol === "chat_completions";
5482
+ const input = tokens(usage[chat ? "prompt_tokens" : "input_tokens"] ?? prior.input_tokens, "input_tokens");
5483
+ const output = tokens(usage[chat ? "completion_tokens" : "output_tokens"] ?? prior.output_tokens, "output_tokens");
5484
+ const details = chat ? usage.prompt_tokens_details : undefined;
5485
+ const cached = chat ? details ? record(details, "prompt_tokens_details").cached_tokens : undefined : usage.cache_read_input_tokens;
5486
+ const cacheRead = tokens(cached ?? prior.input_tokens_details?.cached_tokens, "cached_tokens");
5487
+ const cacheWrite = chat ? 0 : tokens(usage.cache_creation_input_tokens, "cache_creation_input_tokens");
5488
+ const totalInput = chat ? input : usage.input_tokens === undefined ? input : input + cacheRead + cacheWrite;
5489
+ const reasoning = chat && usage.completion_tokens_details ? record(usage.completion_tokens_details, "completion_tokens_details").reasoning_tokens : undefined;
5490
+ return {
5491
+ input_tokens: totalInput,
5492
+ output_tokens: output,
5493
+ total_tokens: totalInput + output,
5494
+ input_tokens_details: { cached_tokens: cacheRead },
5495
+ output_tokens_details: { reasoning_tokens: tokens(reasoning ?? prior.output_tokens_details?.reasoning_tokens, "reasoning_tokens") }
5496
+ };
5497
+ }
5498
+ function toolItem(id, name, args, names, status = "completed") {
5499
+ const original = restoreTool(name, names);
5500
+ const item = { id: `fc_${id}`, type: original.custom ? "custom_tool_call" : "function_call", call_id: id, name: original.name, status };
5501
+ if (original.namespace)
5502
+ item.namespace = original.namespace;
5503
+ if (original.custom)
5504
+ item.input = customInput(args);
5505
+ else {
5506
+ argumentObject(args);
5507
+ item.arguments = args;
5508
+ }
5509
+ return item;
5510
+ }
5511
+ function responsePayload(id, model, output, end, usage, error, createdAt = Math.floor(Date.now() / 1000)) {
5512
+ const result = { id, object: "response", created_at: createdAt, model, status: end.status, output, usage: Object.keys(usage).length ? usage : null };
5513
+ if (end.status === "incomplete")
5514
+ result.incomplete_details = { reason: end.reason };
5515
+ if (end.status === "failed")
5516
+ result.error = error ?? { code: end.reason, message: end.reason };
5517
+ return result;
5518
+ }
5519
+
5520
+ // packages/llms/src/responses-codec/request.ts
5521
+ var contentNormalizer = new OpenAIProtocolConversion({ trimWhitespace: false });
5522
+ // packages/llms/src/responses-codec/events.ts
5523
+ class ResponsesEventEncoder {
5524
+ protocol;
5525
+ model;
5526
+ toolNames;
5527
+ budget;
5528
+ id = `resp_${crypto.randomUUID().replaceAll("-", "")}`;
5529
+ createdAt = Math.floor(Date.now() / 1000);
5530
+ output = [];
5531
+ states = new Map;
5532
+ anthropicBlocks = new Map;
5533
+ usage = {};
5534
+ end;
5535
+ error;
5536
+ started = false;
5537
+ anthropicStarted = false;
5538
+ anthropicStopped = false;
5539
+ finalized = false;
5540
+ poisoned = false;
5541
+ sequence = 0;
5542
+ retained = 0;
5543
+ constructor(protocol, model, toolNames = new Map, configuredLimits) {
5544
+ this.protocol = protocol;
5545
+ this.model = model;
5546
+ this.toolNames = toolNames;
5547
+ this.budget = limits(configuredLimits);
5548
+ }
5549
+ event(type, fields = {}) {
5550
+ return { type, sequence_number: this.sequence++, ...fields };
5551
+ }
5552
+ start(events) {
5553
+ if (this.started)
5554
+ return;
5555
+ this.started = true;
5556
+ const payload = { id: this.id, object: "response", created_at: this.createdAt, model: this.model, status: "in_progress", output: [], usage: null };
5557
+ events.push(this.event("response.created", { response: payload }), this.event("response.in_progress", { response: { ...payload } }));
5558
+ }
5559
+ retain(text) {
5560
+ this.retained += bytes(text);
5561
+ if (this.retained > this.budget.maxOutputBytes)
5562
+ fail("resource_limit", "Accumulated response output exceeds maxOutputBytes");
5563
+ }
5564
+ state(key, kind) {
5565
+ const existing = this.states.get(key);
5566
+ if (existing) {
5567
+ if (existing.kind !== kind)
5568
+ fail("invalid_stream", "Output item changed kind");
5569
+ return existing;
5570
+ }
5571
+ if (this.output.length >= this.budget.maxItems)
5572
+ fail("resource_limit", "Too many output items");
5573
+ const state = { key, index: this.output.length, kind, id: `${this.id}_item_${this.output.length}`, text: "", name: "", callId: "", args: "", emittedInput: "", added: false, closed: false };
5574
+ this.states.set(key, state);
5575
+ this.output.push(state);
5576
+ return state;
5577
+ }
5578
+ fields(state) {
5579
+ return { response_id: this.id, item_id: state.id, output_index: state.index };
5580
+ }
5581
+ item(state, status, strict = false) {
5582
+ if (state.kind === "tool") {
5583
+ if (strict)
5584
+ return { ...toolItem(state.callId, state.name, state.args || "{}", this.toolNames, status), id: state.id };
5585
+ const original = state.original ?? restoreTool(state.name, this.toolNames);
5586
+ return {
5587
+ id: state.id,
5588
+ type: original.custom ? "custom_tool_call" : "function_call",
5589
+ call_id: state.callId,
5590
+ name: original.name,
5591
+ ...original.namespace ? { namespace: original.namespace } : {},
5592
+ status,
5593
+ ...original.custom ? { input: state.emittedInput } : { arguments: state.args }
5594
+ };
5595
+ }
5596
+ if (state.kind === "reasoning")
5597
+ return { id: state.id, type: "reasoning", summary: status === "in_progress" ? [] : [{ type: "summary_text", text: state.text }] };
5598
+ return {
5599
+ id: state.id,
5600
+ type: "message",
5601
+ role: "assistant",
5602
+ status,
5603
+ content: status === "in_progress" ? [] : [this.part(state)]
5604
+ };
5605
+ }
5606
+ part(state) {
5607
+ return state.kind === "refusal" ? { type: "refusal", refusal: state.text } : state.kind === "reasoning" ? { type: "summary_text", text: state.text } : { type: "output_text", text: state.text, annotations: [] };
5608
+ }
5609
+ add(state, events, final = false) {
5610
+ if (state.added)
5611
+ return;
5612
+ if (state.kind === "tool") {
5613
+ if (!state.callId || !state.name || this.toolNames.size && !this.toolNames.has(state.name))
5614
+ return;
5615
+ if (!final && !state.args && [...this.toolNames.keys()].some((name) => name !== state.name && name.startsWith(state.name)))
5616
+ return;
5617
+ state.original = restoreTool(state.name, this.toolNames);
5618
+ }
5619
+ state.added = true;
5620
+ const initial = this.item(state, "in_progress");
5621
+ if (state.kind === "tool") {
5622
+ if (state.original?.custom)
5623
+ initial.input = "";
5624
+ else
5625
+ initial.arguments = "";
5626
+ }
5627
+ events.push(this.event("response.output_item.added", { response_id: this.id, output_index: state.index, item: initial }));
5628
+ if (state.kind !== "tool") {
5629
+ const part = state.kind === "reasoning" ? { type: "summary_text", text: "" } : state.kind === "refusal" ? { type: "refusal", refusal: "" } : { type: "output_text", text: "", annotations: [] };
5630
+ events.push(this.event(state.kind === "reasoning" ? "response.reasoning_summary_part.added" : "response.content_part.added", { ...this.fields(state), [state.kind === "reasoning" ? "summary_index" : "content_index"]: 0, part }));
5631
+ }
5632
+ }
5633
+ appendText(state, delta, events) {
5634
+ if (state.closed)
5635
+ fail("invalid_stream", "Delta arrived after content block stop");
5636
+ this.retain(delta);
5637
+ state.text += delta;
5638
+ this.add(state, events);
5639
+ const type = state.kind === "reasoning" ? "response.reasoning_summary_text.delta" : state.kind === "refusal" ? "response.refusal.delta" : "response.output_text.delta";
5640
+ if (delta)
5641
+ events.push(this.event(type, { ...this.fields(state), [state.kind === "reasoning" ? "summary_index" : "content_index"]: 0, delta }));
5642
+ }
5643
+ emitArgs(state, events, fragment = "", first = false) {
5644
+ if (!state.added)
5645
+ return;
5646
+ if (state.original?.custom) {
5647
+ const prefix = customInputPrefix(state.args);
5648
+ if (!prefix.startsWith(state.emittedInput))
5649
+ fail("invalid_tool_arguments", "Custom input prefix changed");
5650
+ const delta = prefix.slice(state.emittedInput.length);
5651
+ state.emittedInput = prefix;
5652
+ if (delta)
5653
+ events.push(this.event("response.custom_tool_call_input.delta", { ...this.fields(state), delta }));
5654
+ } else {
5655
+ const delta = first ? state.args : fragment;
5656
+ if (delta)
5657
+ events.push(this.event("response.function_call_arguments.delta", { ...this.fields(state), delta }));
5658
+ }
5659
+ }
5660
+ toolDelta(state, raw, events) {
5661
+ if (state.closed)
5662
+ fail("invalid_stream", "Tool delta arrived after content block stop");
5663
+ if (raw.id !== undefined) {
5664
+ const id = string(raw.id, "tool call id", true);
5665
+ if (state.callId && state.callId !== id)
5666
+ fail("invalid_stream", "Tool call id changed");
5667
+ if (!state.callId)
5668
+ this.retain(id);
5669
+ state.callId = id;
5670
+ }
5671
+ const fn = raw.function === undefined ? {} : record(raw.function, "tool function");
5672
+ if (fn.name !== undefined) {
5673
+ const name = string(fn.name, "tool name");
5674
+ if (state.added && name)
5675
+ fail("invalid_stream", "Tool name changed after output_item.added");
5676
+ this.retain(name);
5677
+ state.name += name;
5678
+ }
5679
+ const args = fn.arguments === undefined ? "" : string(fn.arguments, "tool arguments delta");
5680
+ this.retain(args);
5681
+ state.args = bounded(state.args + args, this.budget.maxArgumentBytes, "tool arguments");
5682
+ const wasAdded = state.added;
5683
+ this.add(state, events);
5684
+ this.emitArgs(state, events, args, !wasAdded);
5685
+ }
5686
+ push(raw) {
5687
+ if (this.poisoned)
5688
+ fail("invalid_stream", "Encoder cannot continue after a codec error");
5689
+ if (this.finalized)
5690
+ fail("invalid_stream", "Event arrived after response terminal");
5691
+ try {
5692
+ const chunk = record(raw, "stream event");
5693
+ serialized(chunk, this.budget.maxOutputBytes, "stream event");
5694
+ const events = [];
5695
+ this.start(events);
5696
+ if (chunk.error !== undefined || chunk.type === "error") {
5697
+ this.error = record(chunk.error, "upstream error");
5698
+ this.end = { status: "failed", reason: "upstream_error" };
5699
+ this.anthropicStopped = true;
5700
+ return [...events, ...this.complete()];
5701
+ }
5702
+ if (this.protocol === "chat_completions")
5703
+ this.chat(chunk, events);
5704
+ else
5705
+ this.anthropic(chunk, events);
5706
+ return events;
5707
+ } catch (error) {
5708
+ this.poisoned = true;
5709
+ throw error;
5710
+ }
5711
+ }
5712
+ chat(chunk, events) {
5713
+ const choices = list(chunk.choices, "choices");
5714
+ if (choices.length > 1)
5715
+ fail("unsupported_response", "Multiple response candidates are not supported");
5716
+ if (choices.length) {
5717
+ const choice = record(choices[0], "choice");
5718
+ if (choice.index !== undefined && choice.index !== 0)
5719
+ fail("unsupported_response", "Only choice index 0 is supported");
5720
+ const delta = record(choice.delta ?? {}, "choice delta");
5721
+ for (const key of Object.keys(delta))
5722
+ if (!["role", "content", "refusal", "reasoning_content", "reasoning", "tool_calls", "function_call", "annotations"].includes(key))
5723
+ fail("unsupported_content", `Unsupported Chat output field ${key}`);
5724
+ if (delta.annotations !== undefined && list(delta.annotations, "annotations").length)
5725
+ fail("unsupported_content", "Chat output annotations cannot be faithfully converted");
5726
+ if (delta.role !== undefined && delta.role !== "assistant")
5727
+ fail("unsupported_response", "Response role must be assistant");
5728
+ if (this.end && Object.values(delta).some((v) => v !== undefined && v !== null && v !== "" && (!Array.isArray(v) || v.length)))
5729
+ fail("invalid_stream", "Output arrived after finish_reason");
5730
+ const reasoning = delta.reasoning_content ?? delta.reasoning;
5731
+ if (reasoning !== undefined && reasoning !== null)
5732
+ this.appendText(this.state("reasoning", "reasoning"), string(reasoning, "reasoning delta"), events);
5733
+ if (delta.content !== undefined && delta.content !== null)
5734
+ this.appendText(this.state("text", "text"), string(delta.content, "text delta"), events);
5735
+ if (delta.refusal !== undefined && delta.refusal !== null)
5736
+ this.appendText(this.state("refusal", "refusal"), string(delta.refusal, "refusal delta"), events);
5737
+ if (delta.tool_calls !== undefined) {
5738
+ for (const rawTool of list(delta.tool_calls, "tool_calls")) {
5739
+ const tool = record(rawTool, "tool call");
5740
+ if (!Number.isSafeInteger(tool.index) || tool.index < 0)
5741
+ fail("invalid_stream", "Streamed tool requires a non-negative integer index");
5742
+ if (tool.type !== undefined && tool.type !== "function")
5743
+ fail("unsupported_tool", "Unknown upstream tool call type");
5744
+ this.toolDelta(this.state(`tool:${tool.index}`, "tool"), tool, events);
5745
+ }
5746
+ }
5747
+ if (delta.function_call !== undefined)
5748
+ fail("unsupported_response", "Legacy function_call lacks a stable call_id");
5749
+ if (choice.finish_reason !== undefined && choice.finish_reason !== null) {
5750
+ const next = terminal(choice.finish_reason, this.protocol);
5751
+ if (this.end && (this.end.status !== next.status || this.end.reason !== next.reason))
5752
+ fail("invalid_stream", "Conflicting finish reasons");
5753
+ this.end = next;
5754
+ }
5755
+ }
5756
+ this.usage = normalizeUsage(chunk.usage, this.protocol, this.usage);
5757
+ }
5758
+ anthropic(chunk, events) {
5759
+ if (chunk.type === "ping")
5760
+ return;
5761
+ if (this.anthropicStopped)
5762
+ fail("invalid_stream", "Event arrived after message_stop");
5763
+ if (chunk.type === "message_start") {
5764
+ if (this.anthropicStarted)
5765
+ fail("invalid_stream", "Duplicate message_start");
5766
+ const message = record(chunk.message, "message");
5767
+ this.anthropicStarted = true;
5768
+ this.usage = normalizeUsage(message.usage, this.protocol, this.usage);
5769
+ return;
5770
+ }
5771
+ if (!this.anthropicStarted)
5772
+ fail("invalid_stream", "Anthropic event arrived before message_start");
5773
+ if (chunk.type === "message_delta") {
5774
+ const delta = record(chunk.delta, "message delta");
5775
+ if (delta.stop_reason != null) {
5776
+ const next = terminal(delta.stop_reason, this.protocol);
5777
+ if (this.end && (this.end.status !== next.status || this.end.reason !== next.reason))
5778
+ fail("invalid_stream", "Conflicting stop reasons");
5779
+ this.end = next;
5780
+ }
5781
+ this.usage = normalizeUsage(chunk.usage, this.protocol, this.usage);
5782
+ return;
5783
+ }
5784
+ if (chunk.type === "message_stop") {
5785
+ if (!this.end)
5786
+ fail("missing_terminal", "message_stop has no preceding stop_reason");
5787
+ if ([...this.anthropicBlocks.values()].some((s) => !s.closed))
5788
+ fail("invalid_stream", "message_stop arrived before content_block_stop");
5789
+ this.anthropicStopped = true;
5790
+ return;
5791
+ }
5792
+ if (!Number.isSafeInteger(chunk.index) || chunk.index < 0)
5793
+ fail("invalid_stream", "Anthropic content requires a non-negative integer index");
5794
+ const index = chunk.index;
5795
+ if (this.end)
5796
+ fail("invalid_stream", "Content arrived after stop_reason");
5797
+ if (chunk.type === "content_block_start") {
5798
+ if (this.anthropicBlocks.has(index))
5799
+ fail("invalid_stream", "Duplicate content_block_start");
5800
+ const block = record(chunk.content_block, "content block");
5801
+ const kind = block.type === "tool_use" ? "tool" : block.type === "text" ? "text" : block.type === "thinking" ? "reasoning" : undefined;
5802
+ if (!kind)
5803
+ fail("unsupported_content", `Unsupported Anthropic block ${String(block.type)}`);
5804
+ const state = this.state(`block:${index}`, kind);
5805
+ this.anthropicBlocks.set(index, state);
5806
+ if (kind === "tool") {
5807
+ const input = record(block.input ?? {}, "tool input");
5808
+ this.toolDelta(state, { id: block.id, function: { name: block.name, ...Object.keys(input).length ? { arguments: serialized(input, this.budget.maxArgumentBytes, "tool input") } : {} } }, events);
5809
+ } else
5810
+ this.appendText(state, string(block[kind === "text" ? "text" : "thinking"] ?? "", "block text"), events);
5811
+ return;
5812
+ }
5813
+ const state = this.anthropicBlocks.get(index);
5814
+ if (!state || state.closed)
5815
+ fail("invalid_stream", "Content event has no open block");
5816
+ if (chunk.type === "content_block_stop") {
5817
+ state.closed = true;
5818
+ return;
5819
+ }
5820
+ if (chunk.type !== "content_block_delta")
5821
+ fail("unsupported_response", `Unknown Anthropic event ${String(chunk.type)}`);
5822
+ const delta = record(chunk.delta, "content delta");
5823
+ if (delta.type === "input_json_delta" && state.kind === "tool")
5824
+ this.toolDelta(state, { function: { arguments: delta.partial_json } }, events);
5825
+ else if (delta.type === "text_delta" && state.kind === "text")
5826
+ this.appendText(state, string(delta.text, "text delta"), events);
5827
+ else if (delta.type === "thinking_delta" && state.kind === "reasoning")
5828
+ this.appendText(state, string(delta.thinking, "thinking delta"), events);
5829
+ else if (delta.type === "signature_delta" && state.kind === "reasoning") {
5830
+ bounded(string(delta.signature, "signature"), this.budget.maxArgumentBytes, "signature");
5831
+ } else
5832
+ fail("unsupported_content", `Unsupported content delta ${String(delta.type)}`);
5833
+ }
5834
+ complete() {
5835
+ if (!this.end)
5836
+ fail("missing_terminal", "Stream ended without an explicit upstream terminal");
5837
+ const events = [];
5838
+ const items = [];
5839
+ const callIds = new Set;
5840
+ for (const state of this.output) {
5841
+ const strict = this.end.status === "completed";
5842
+ if (state.kind === "tool") {
5843
+ if (!state.callId || !state.name)
5844
+ fail("invalid_tool_arguments", "Tool call is missing id or name");
5845
+ if (callIds.has(state.callId))
5846
+ fail("invalid_tool_arguments", "Duplicate provider call_id");
5847
+ callIds.add(state.callId);
5848
+ state.original ??= restoreTool(state.name, this.toolNames);
5849
+ this.add(state, events, true);
5850
+ if (!state.added)
5851
+ fail("unknown_tool", `Unknown tool ${state.name}`);
5852
+ if (strict) {
5853
+ argumentObject(state.args || "{}");
5854
+ if (state.original.custom) {
5855
+ const text = customInput(state.args);
5856
+ if (!text.startsWith(state.emittedInput))
5857
+ fail("invalid_tool_arguments", "Custom tool input changed");
5858
+ if (text.length > state.emittedInput.length)
5859
+ events.push(this.event("response.custom_tool_call_input.delta", { ...this.fields(state), delta: text.slice(state.emittedInput.length) }));
5860
+ state.emittedInput = text;
5861
+ }
5862
+ }
5863
+ }
5864
+ const item = this.item(state, this.end.status === "completed" ? "completed" : "incomplete", strict);
5865
+ const fields = this.fields(state);
5866
+ if (state.kind === "tool") {
5867
+ const custom = state.original.custom;
5868
+ events.push(this.event(custom ? "response.custom_tool_call_input.done" : "response.function_call_arguments.done", { ...fields, [custom ? "input" : "arguments"]: custom ? item.input : item.arguments }));
5869
+ } else {
5870
+ const reasoning = state.kind === "reasoning";
5871
+ const textField = state.kind === "refusal" ? "refusal" : "text";
5872
+ events.push(this.event(reasoning ? "response.reasoning_summary_text.done" : state.kind === "refusal" ? "response.refusal.done" : "response.output_text.done", { ...fields, [reasoning ? "summary_index" : "content_index"]: 0, [textField]: state.text }));
5873
+ events.push(this.event(reasoning ? "response.reasoning_summary_part.done" : "response.content_part.done", { ...fields, [reasoning ? "summary_index" : "content_index"]: 0, part: this.part(state) }));
5874
+ }
5875
+ events.push(this.event("response.output_item.done", { response_id: this.id, output_index: state.index, item }));
5876
+ items.push(item);
5877
+ }
5878
+ const response = responsePayload(this.id, this.model, items, this.end, this.usage, this.error, this.createdAt);
5879
+ serialized(response, this.budget.maxOutputBytes, "final response");
5880
+ events.push(this.event(`response.${this.end.status}`, { response }));
5881
+ this.finalized = true;
5882
+ return events;
5883
+ }
5884
+ finish() {
5885
+ if (this.poisoned)
5886
+ fail("invalid_stream", "Encoder cannot finish after a codec error");
5887
+ if (this.finalized)
5888
+ return [];
5889
+ try {
5890
+ if (this.protocol === "anthropic_messages" && !this.anthropicStopped)
5891
+ fail("missing_terminal", "Anthropic stream ended before message_stop");
5892
+ return this.complete();
5893
+ } catch (error) {
5894
+ this.poisoned = true;
5895
+ throw error;
5896
+ }
5897
+ }
5898
+ }
5899
+
5900
+ // packages/llms/src/responses-codec/result.ts
5901
+ function encodeResponsesResult(raw, protocol, model, toolNames = new Map, configuredLimits) {
5902
+ const body = record(raw, "upstream response");
5903
+ const budget = limits(configuredLimits);
5904
+ serialized(body, budget.maxOutputBytes, "upstream response");
5905
+ if (body.error !== undefined || body.type === "error")
5906
+ return responsePayload(`resp_${crypto.randomUUID().replaceAll("-", "")}`, model, [], { status: "failed", reason: "upstream_error" }, {}, record(body.error, "upstream error"));
5907
+ const encoder = new ResponsesEventEncoder(protocol, model, toolNames, configuredLimits);
5908
+ if (protocol === "chat_completions") {
5909
+ const choices = list(body.choices, "choices");
5910
+ if (choices.length !== 1)
5911
+ fail("unsupported_response", "Exactly one response choice is required");
5912
+ const choice = record(choices[0], "choice");
5913
+ const message = record(choice.message, "message");
5914
+ const delta = { ...message };
5915
+ if (message.tool_calls !== undefined)
5916
+ delta.tool_calls = list(message.tool_calls, "tool_calls").map((tool, index) => ({ ...record(tool, "tool call"), index }));
5917
+ encoder.push({ choices: [{ index: choice.index ?? 0, delta, finish_reason: choice.finish_reason }], usage: body.usage });
5918
+ } else {
5919
+ encoder.push({ type: "message_start", message: { usage: body.usage } });
5920
+ for (const [index, rawBlock] of list(body.content, "content").entries()) {
5921
+ const block = record(rawBlock, "content block");
5922
+ encoder.push({ type: "content_block_start", index, content_block: block });
5923
+ encoder.push({ type: "content_block_stop", index });
5924
+ }
5925
+ encoder.push({ type: "message_delta", delta: { stop_reason: body.stop_reason } });
5926
+ encoder.push({ type: "message_stop" });
5927
+ }
5928
+ const end = encoder.finish().at(-1);
5929
+ if (!end)
5930
+ fail("missing_terminal", "JSON response has no terminal result");
5931
+ return record(end.response, "Responses result");
5932
+ }
5340
5933
  // plugins/openai-messages-to-chat/server/index.ts
5341
5934
  var RESPONSE_STATE_REFERENCE_FIELDS = [
5342
5935
  "previous_response_id",
@@ -5354,7 +5947,7 @@ var OpenAIMessagesToChatPlugin = definePlugin(class {
5354
5947
  adaptedRequestIds = new Set;
5355
5948
  responsesAdaptedRequestIds = new Set;
5356
5949
  streamConversionRequestIds = new Set;
5357
- responsesStreamConversionRequestIds = new Set;
5950
+ responsesRequestModels = new Map;
5358
5951
  responsesRequestMessages = new Map;
5359
5952
  responsesRequestConversationIds = new Map;
5360
5953
  responsesHistoryByResponseId = new Map;
@@ -5384,7 +5977,7 @@ var OpenAIMessagesToChatPlugin = definePlugin(class {
5384
5977
  this.adaptedRequestIds.delete(ctx.requestId);
5385
5978
  this.responsesAdaptedRequestIds.delete(ctx.requestId);
5386
5979
  this.streamConversionRequestIds.delete(ctx.requestId);
5387
- this.responsesStreamConversionRequestIds.delete(ctx.requestId);
5980
+ this.responsesRequestModels.delete(ctx.requestId);
5388
5981
  this.responsesRequestMessages.delete(ctx.requestId);
5389
5982
  this.responsesRequestConversationIds.delete(ctx.requestId);
5390
5983
  this.validationErrors.delete(ctx.requestId);
@@ -5414,6 +6007,7 @@ var OpenAIMessagesToChatPlugin = definePlugin(class {
5414
6007
  ctx.url.pathname = "/v1/chat/completions";
5415
6008
  ctx.body = this.normalizeAssistantToolCallReasoningContent(validation.body);
5416
6009
  this.responsesAdaptedRequestIds.add(ctx.requestId);
6010
+ this.responsesRequestModels.set(ctx.requestId, String(validation.body.model));
5417
6011
  this.responsesRequestMessages.set(ctx.requestId, this.extractMessagesFromDowngradedBody(validation.body));
5418
6012
  if (stateReference.conversationId) {
5419
6013
  this.responsesRequestConversationIds.set(ctx.requestId, stateReference.conversationId);
@@ -5443,20 +6037,21 @@ var OpenAIMessagesToChatPlugin = definePlugin(class {
5443
6037
  if (!isRecord4(parsedBody)) {
5444
6038
  return response;
5445
6039
  }
5446
- const converted = this.convertChatCompletionToResponsesPayload(parsedBody);
5447
- if (!converted) {
5448
- return response;
6040
+ const converted = this.convertChatCompletionToResponsesPayload(parsedBody, this.responsesRequestModels.get(ctx.requestId) ?? "");
6041
+ if (converted.status !== "failed") {
6042
+ const conversationId = this.responsesRequestConversationIds.get(ctx.requestId);
6043
+ const baseMessages = this.responsesRequestMessages.get(ctx.requestId) ?? [];
6044
+ const assistantMessage = this.extractAssistantMessageFromChatCompletionPayload(parsedBody);
6045
+ const history = assistantMessage ? [...baseMessages, assistantMessage] : baseMessages;
6046
+ this.storeResponsesState(String(converted.id), history, conversationId);
5449
6047
  }
5450
- const responseId = typeof converted.id === "string" ? converted.id : this.toResponsesId(parsedBody.id);
5451
- const conversationId = this.responsesRequestConversationIds.get(ctx.requestId);
5452
- const baseMessages = this.responsesRequestMessages.get(ctx.requestId) ?? [];
5453
- const assistantMessage = this.extractAssistantMessageFromChatCompletionPayload(parsedBody);
5454
- const history = assistantMessage ? [...baseMessages, assistantMessage] : baseMessages;
5455
- this.storeResponsesState(responseId, history, conversationId);
6048
+ const headers = new Headers(response.headers);
6049
+ headers.delete("content-length");
6050
+ headers.delete("content-encoding");
5456
6051
  return new Response(JSON.stringify(converted), {
5457
6052
  status: response.status,
5458
6053
  statusText: response.statusText,
5459
- headers: response.headers
6054
+ headers
5460
6055
  });
5461
6056
  }
5462
6057
  if (!this.adaptedRequestIds.has(ctx.requestId)) {
@@ -5476,20 +6071,21 @@ var OpenAIMessagesToChatPlugin = definePlugin(class {
5476
6071
  });
5477
6072
  hooks.onStreamChunk.tapPromise({ name: "openai-messages-to-chat", stage: 10 }, async (envelope, ctx) => {
5478
6073
  const chunk = structuredClone(envelope.json);
5479
- if (chunk === undefined)
5480
- return null;
5481
6074
  const converterContext = { ...ctx, sseEvent: envelope };
5482
6075
  if (this.responsesAdaptedRequestIds.has(ctx.requestId)) {
5483
- if (!this.messagesCompatibilityNormalizer.isOpenAIStreamChunk(chunk)) {
5484
- return null;
6076
+ if (envelope.data === "[DONE]")
6077
+ return [];
6078
+ const encoder = this.getResponsesEncoder(ctx, isRecord4(chunk) ? chunk.model : undefined);
6079
+ let normalized = chunk;
6080
+ if (!ctx.streamState.has("responses_legacy_message") && isRecord4(chunk) && Array.isArray(chunk.choices) && chunk.choices.length === 1 && isRecord4(chunk.choices[0])) {
6081
+ const choice = chunk.choices[0];
6082
+ const delta = isRecord4(choice.delta) ? choice.delta : {};
6083
+ normalized = { ...chunk, choices: [{ ...choice, delta: { ...delta, content: delta.content ?? "" } }] };
6084
+ ctx.streamState.set("responses_legacy_message", true);
5485
6085
  }
5486
- const converted = this.convertChatCompletionChunkToResponsesEvents(chunk, ctx);
5487
- if (converted.length > 0) {
5488
- this.responsesStreamConversionRequestIds.add(ctx.requestId);
5489
- }
5490
- return protocolSSEOutput(converted, "responses", envelope);
6086
+ return protocolSSEOutput(encoder.push(normalized), "responses", envelope);
5491
6087
  }
5492
- if (!this.adaptedRequestIds.has(ctx.requestId)) {
6088
+ if (chunk === undefined || !this.adaptedRequestIds.has(ctx.requestId)) {
5493
6089
  return null;
5494
6090
  }
5495
6091
  if (!this.messagesCompatibilityNormalizer.isOpenAIStreamChunk(chunk)) {
@@ -5503,10 +6099,13 @@ var OpenAIMessagesToChatPlugin = definePlugin(class {
5503
6099
  return protocolSSEOutput(this.protocolConversion.ensureMessagesStreamCompatibility(converted, ctx), "anthropic", envelope);
5504
6100
  });
5505
6101
  hooks.onFlushStream.tapPromise({ name: "openai-messages-to-chat", stage: 10 }, async (chunks, ctx) => {
5506
- if (this.responsesStreamConversionRequestIds.has(ctx.requestId)) {
5507
- this.responsesStreamConversionRequestIds.delete(ctx.requestId);
5508
- const completionEvents = this.buildResponsesStreamCompletionEvents(ctx);
5509
- this.persistResponsesStateFromStream(ctx);
6102
+ if (this.responsesAdaptedRequestIds.has(ctx.requestId)) {
6103
+ const completionEvents = this.getResponsesEncoder(ctx).finish();
6104
+ for (const event of completionEvents) {
6105
+ if (isRecord4(event.response) && event.response.status !== "failed") {
6106
+ this.persistResponsesStateFromResult(ctx.requestId, event.response);
6107
+ }
6108
+ }
5510
6109
  return [...chunks, ...protocolSSEOutput(completionEvents, "responses")];
5511
6110
  }
5512
6111
  if (!this.streamConversionRequestIds.has(ctx.requestId)) {
@@ -5521,7 +6120,7 @@ var OpenAIMessagesToChatPlugin = definePlugin(class {
5521
6120
  this.adaptedRequestIds.delete(ctx.requestId);
5522
6121
  this.responsesAdaptedRequestIds.delete(ctx.requestId);
5523
6122
  this.streamConversionRequestIds.delete(ctx.requestId);
5524
- this.responsesStreamConversionRequestIds.delete(ctx.requestId);
6123
+ this.responsesRequestModels.delete(ctx.requestId);
5525
6124
  this.responsesRequestMessages.delete(ctx.requestId);
5526
6125
  this.responsesRequestConversationIds.delete(ctx.requestId);
5527
6126
  this.validationErrors.delete(ctx.requestId);
@@ -5531,7 +6130,7 @@ var OpenAIMessagesToChatPlugin = definePlugin(class {
5531
6130
  this.adaptedRequestIds.clear();
5532
6131
  this.responsesAdaptedRequestIds.clear();
5533
6132
  this.streamConversionRequestIds.clear();
5534
- this.responsesStreamConversionRequestIds.clear();
6133
+ this.responsesRequestModels.clear();
5535
6134
  this.responsesRequestMessages.clear();
5536
6135
  this.responsesRequestConversationIds.clear();
5537
6136
  this.responsesHistoryByResponseId.clear();
@@ -5734,32 +6333,40 @@ var OpenAIMessagesToChatPlugin = definePlugin(class {
5734
6333
  }
5735
6334
  return null;
5736
6335
  }
5737
- persistResponsesStateFromStream(ctx) {
5738
- const streamState = ctx.streamState.get("responses_stream_state");
5739
- if (!streamState) {
6336
+ persistResponsesStateFromResult(requestId, response) {
6337
+ if (response.status === "failed" || !Array.isArray(response.output))
5740
6338
  return;
5741
- }
5742
- const baseMessages = this.responsesRequestMessages.get(ctx.requestId) ?? [];
5743
- const conversationId = this.responsesRequestConversationIds.get(ctx.requestId);
5744
- let history = baseMessages;
5745
- if (streamState.messageText.length > 0 || streamState.toolCalls.length > 0) {
5746
- const assistantMessage = {
5747
- role: "assistant",
5748
- content: streamState.messageText.length > 0 ? streamState.messageText : null
5749
- };
5750
- if (streamState.toolCalls.length > 0) {
5751
- assistantMessage.tool_calls = streamState.toolCalls.map((toolCall) => ({
5752
- id: toolCall.id,
5753
- type: "function",
5754
- function: {
5755
- name: toolCall.name,
5756
- arguments: toolCall.arguments
5757
- }
5758
- }));
5759
- }
5760
- history = [...baseMessages, assistantMessage];
5761
- }
5762
- this.storeResponsesState(streamState.responseId, history, conversationId);
6339
+ const assistant = { role: "assistant", content: null };
6340
+ const text = [];
6341
+ const reasoning = [];
6342
+ const tools = [];
6343
+ for (const item of response.output) {
6344
+ if (!isRecord4(item))
6345
+ continue;
6346
+ if (item.type === "message" && Array.isArray(item.content)) {
6347
+ for (const part of item.content) {
6348
+ if (isRecord4(part) && part.type === "output_text" && typeof part.text === "string")
6349
+ text.push(part.text);
6350
+ if (isRecord4(part) && part.type === "refusal" && typeof part.refusal === "string")
6351
+ assistant.refusal = part.refusal;
6352
+ }
6353
+ } else if (item.type === "function_call") {
6354
+ tools.push({ id: item.call_id, type: "function", function: { name: item.name, arguments: item.arguments } });
6355
+ } else if (item.type === "reasoning" && Array.isArray(item.summary)) {
6356
+ for (const part of item.summary)
6357
+ if (isRecord4(part) && typeof part.text === "string")
6358
+ reasoning.push(part.text);
6359
+ }
6360
+ }
6361
+ if (text.length)
6362
+ assistant.content = text.join("");
6363
+ if (tools.length)
6364
+ assistant.tool_calls = tools;
6365
+ if (reasoning.length)
6366
+ assistant.reasoning_content = reasoning.join("");
6367
+ const base = this.responsesRequestMessages.get(requestId) ?? [];
6368
+ const history = text.length || tools.length || reasoning.length || assistant.refusal ? [...base, assistant] : base;
6369
+ this.storeResponsesState(String(response.id), history, this.responsesRequestConversationIds.get(requestId));
5763
6370
  }
5764
6371
  storeResponsesState(responseId, history, conversationId) {
5765
6372
  const normalizedResponseId = this.normalizeNonEmptyString(responseId);
@@ -5887,500 +6494,41 @@ var OpenAIMessagesToChatPlugin = definePlugin(class {
5887
6494
  }
5888
6495
  return { message: rawMessage, changed: false };
5889
6496
  }
5890
- convertChatCompletionToResponsesPayload(payload) {
5891
- if (payload.object === "response") {
6497
+ convertChatCompletionToResponsesPayload(payload, requestModel) {
6498
+ if (payload.object === "response")
5892
6499
  return payload;
5893
- }
5894
- if (!Array.isArray(payload.choices)) {
5895
- return null;
5896
- }
5897
- const choices = payload.choices.filter((choice) => isRecord4(choice) && isRecord4(choice.message));
5898
- if (choices.length === 0) {
5899
- return null;
5900
- }
5901
- const responseId = this.toResponsesId(payload.id);
5902
- const createdAt = typeof payload.created === "number" ? payload.created : Math.floor(Date.now() / 1000);
5903
- const model = typeof payload.model === "string" ? payload.model : "";
5904
- const output = [];
5905
- const finishReasons = [];
5906
- for (let choiceIndex = 0;choiceIndex < choices.length; choiceIndex += 1) {
5907
- const choice = choices[choiceIndex];
5908
- const message = choice.message;
5909
- const content = this.normalizeChatMessageContentToResponseOutputText(message.content);
5910
- const messageId = choices.length === 1 ? `${responseId}_msg` : `${responseId}_msg_${choiceIndex}`;
5911
- const messageContent = content ? [
5912
- {
5913
- type: "output_text",
5914
- text: content,
5915
- annotations: []
5916
- }
5917
- ] : [];
5918
- output.push({
5919
- id: messageId,
5920
- type: "message",
5921
- role: "assistant",
5922
- status: "completed",
5923
- content: messageContent
5924
- });
5925
- const toolCalls = this.normalizeToolCalls(message.tool_calls) ?? [];
5926
- for (let toolCallIndex = 0;toolCallIndex < toolCalls.length; toolCallIndex += 1) {
5927
- const toolCall = toolCalls[toolCallIndex];
5928
- const toolFunction = isRecord4(toolCall.function) ? toolCall.function : undefined;
5929
- const toolCallId = typeof toolCall.id === "string" && toolCall.id.trim().length > 0 ? toolCall.id.trim() : `${responseId}_choice_${choiceIndex}_call_${toolCallIndex}`;
5930
- const argumentsPayload = typeof toolFunction?.arguments === "string" ? toolFunction.arguments : JSON.stringify(toolFunction?.arguments ?? {});
5931
- output.push({
5932
- id: toolCallId,
5933
- type: "function_call",
5934
- call_id: toolCallId,
5935
- name: typeof toolFunction?.name === "string" ? toolFunction.name : "",
5936
- arguments: argumentsPayload,
5937
- status: "completed"
5938
- });
5939
- }
5940
- if (typeof choice.finish_reason === "string" && choice.finish_reason.length > 0) {
5941
- finishReasons.push(choice.finish_reason);
5942
- }
5943
- }
5944
- const usage = isRecord4(payload.usage) ? payload.usage : {};
5945
- const terminal = this.aggregateTerminalFromFinishReasons(finishReasons);
5946
- const converted = {
5947
- id: responseId,
5948
- object: "response",
5949
- created_at: createdAt,
5950
- model,
5951
- status: this.terminalStatusFromType(terminal.type),
5952
- output,
5953
- usage: {
5954
- input_tokens: typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0,
5955
- output_tokens: typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0,
5956
- total_tokens: typeof usage.total_tokens === "number" ? usage.total_tokens : (typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0) + (typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0)
5957
- }
5958
- };
5959
- if (terminal.type === "response.incomplete") {
5960
- converted.incomplete_details = {
5961
- reason: terminal.reason
5962
- };
5963
- }
5964
- if (terminal.type === "response.failed") {
5965
- converted.error = {
5966
- code: "completion_terminated",
5967
- message: terminal.reason
5968
- };
5969
- }
5970
- if (finishReasons.length > 0) {
5971
- converted.metadata = {
5972
- finish_reason: finishReasons[0],
5973
- finish_reasons: finishReasons
5974
- };
5975
- }
5976
- return converted;
5977
- }
5978
- convertChatCompletionChunkToResponsesEvents(chunk, ctx) {
5979
- if (!isRecord4(chunk) || !Array.isArray(chunk.choices)) {
5980
- return [];
5981
- }
5982
- const state = this.getOrCreateResponsesStreamState(chunk, ctx.streamState);
5983
- const events = [];
5984
- if (ctx.streamState.get("responses_stream_started") !== true) {
5985
- events.push({
5986
- type: "response.created",
5987
- response: {
5988
- id: state.responseId,
5989
- object: "response",
5990
- created_at: state.createdAt,
5991
- model: state.model,
5992
- status: "in_progress",
5993
- output: []
5994
- }
5995
- });
5996
- events.push({
5997
- type: "response.in_progress",
5998
- response: {
5999
- id: state.responseId,
6000
- object: "response",
6001
- created_at: state.createdAt,
6002
- model: state.model,
6003
- status: "in_progress",
6004
- output: []
6005
- }
6006
- });
6007
- events.push({
6008
- type: "response.output_item.added",
6009
- response_id: state.responseId,
6010
- output_index: 0,
6011
- item: {
6012
- id: state.messageId,
6013
- type: "message",
6014
- role: "assistant",
6015
- status: "in_progress",
6016
- content: []
6017
- }
6018
- });
6019
- ctx.streamState.set("responses_stream_started", true);
6020
- }
6021
- for (const choice of chunk.choices) {
6022
- if (!isRecord4(choice)) {
6023
- continue;
6024
- }
6025
- const delta = isRecord4(choice.delta) ? choice.delta : undefined;
6026
- if (delta) {
6027
- if (typeof delta.content === "string" && delta.content.length > 0) {
6028
- if (!state.hasTextContentPart) {
6029
- events.push({
6030
- type: "response.content_part.added",
6031
- response_id: state.responseId,
6032
- item_id: state.messageId,
6033
- output_index: 0,
6034
- content_index: 0,
6035
- part: {
6036
- type: "output_text",
6037
- text: "",
6038
- annotations: []
6039
- }
6040
- });
6041
- state.hasTextContentPart = true;
6042
- }
6043
- state.messageText += delta.content;
6044
- events.push({
6045
- type: "response.output_text.delta",
6046
- response_id: state.responseId,
6047
- item_id: state.messageId,
6048
- output_index: 0,
6049
- content_index: 0,
6050
- delta: delta.content
6051
- });
6052
- }
6053
- if (Array.isArray(delta.tool_calls)) {
6054
- for (const rawToolCall of delta.tool_calls) {
6055
- if (!isRecord4(rawToolCall)) {
6056
- continue;
6057
- }
6058
- const toolCallIndex = typeof rawToolCall.index === "number" ? rawToolCall.index : state.toolCalls.length;
6059
- const functionData = isRecord4(rawToolCall.function) ? rawToolCall.function : {};
6060
- const idFromDelta = typeof rawToolCall.id === "string" && rawToolCall.id.trim().length > 0 ? rawToolCall.id.trim() : undefined;
6061
- const existingId = state.toolCallIndexToId.get(toolCallIndex);
6062
- const toolCallId = existingId ?? idFromDelta ?? `${state.responseId}_call_${toolCallIndex}`;
6063
- let toolCall = state.toolCalls.find((item) => item.id === toolCallId);
6064
- const firstSeen = !toolCall;
6065
- if (!toolCall) {
6066
- toolCall = {
6067
- id: toolCallId,
6068
- name: "",
6069
- arguments: ""
6070
- };
6071
- state.toolCalls.push(toolCall);
6072
- state.toolCallIndexToId.set(toolCallIndex, toolCallId);
6073
- }
6074
- if (typeof functionData.name === "string" && functionData.name.length > 0) {
6075
- toolCall.name = functionData.name;
6076
- }
6077
- if (firstSeen) {
6078
- events.push({
6079
- type: "response.output_item.added",
6080
- response_id: state.responseId,
6081
- output_index: state.toolCalls.length,
6082
- item: {
6083
- id: toolCall.id,
6084
- type: "function_call",
6085
- call_id: toolCall.id,
6086
- name: toolCall.name,
6087
- arguments: "",
6088
- status: "in_progress"
6089
- }
6090
- });
6091
- }
6092
- if (typeof functionData.arguments === "string" && functionData.arguments.length > 0) {
6093
- toolCall.arguments += functionData.arguments;
6094
- const toolOutputIndex = state.toolCalls.indexOf(toolCall) + 1;
6095
- events.push({
6096
- type: "response.function_call_arguments.delta",
6097
- response_id: state.responseId,
6098
- item_id: toolCall.id,
6099
- output_index: toolOutputIndex,
6100
- delta: functionData.arguments
6101
- });
6102
- }
6103
- }
6104
- }
6105
- }
6106
- if (typeof choice.finish_reason === "string" && choice.finish_reason.length > 0) {
6107
- state.finishReason = choice.finish_reason;
6108
- const terminal = this.mapFinishReasonToTerminalEvent(choice.finish_reason);
6109
- if (terminal) {
6110
- state.terminalEventType = terminal.type;
6111
- state.terminalReason = terminal.reason;
6112
- }
6113
- }
6114
- }
6115
- if (isRecord4(chunk.usage)) {
6116
- state.usage = {
6117
- input_tokens: typeof chunk.usage.prompt_tokens === "number" ? chunk.usage.prompt_tokens : state.usage.input_tokens,
6118
- output_tokens: typeof chunk.usage.completion_tokens === "number" ? chunk.usage.completion_tokens : state.usage.output_tokens,
6119
- total_tokens: typeof chunk.usage.total_tokens === "number" ? chunk.usage.total_tokens : state.usage.total_tokens
6120
- };
6121
- }
6122
- return events;
6123
- }
6124
- buildResponsesStreamCompletionEvents(ctx) {
6125
- const state = ctx.streamState.get("responses_stream_state");
6126
- if (!state) {
6127
- return [];
6128
- }
6129
- const events = [];
6130
- const output = [];
6131
- const messageContentPart = {
6132
- type: "output_text",
6133
- text: state.messageText,
6134
- annotations: []
6135
- };
6136
- const messageContent = state.hasTextContentPart ? [messageContentPart] : [];
6137
- const messageItem = {
6138
- id: state.messageId,
6139
- type: "message",
6140
- role: "assistant",
6141
- status: "completed",
6142
- content: messageContent
6143
- };
6144
- output.push(messageItem);
6145
- if (state.hasTextContentPart) {
6146
- events.push({
6147
- type: "response.output_text.done",
6148
- response_id: state.responseId,
6149
- item_id: state.messageId,
6150
- output_index: 0,
6151
- content_index: 0,
6152
- text: state.messageText
6153
- });
6154
- events.push({
6155
- type: "response.content_part.done",
6156
- response_id: state.responseId,
6157
- item_id: state.messageId,
6158
- output_index: 0,
6159
- content_index: 0,
6160
- part: messageContentPart
6161
- });
6162
- }
6163
- events.push({
6164
- type: "response.output_item.done",
6165
- response_id: state.responseId,
6166
- output_index: 0,
6167
- item: messageItem
6500
+ const model = typeof payload.model === "string" ? payload.model : requestModel;
6501
+ if (!Array.isArray(payload.choices) || payload.choices.length === 0) {
6502
+ return encodeResponsesResult(payload, "chat_completions", model);
6503
+ }
6504
+ const candidates = payload.choices.map((rawChoice) => {
6505
+ if (!isRecord4(rawChoice) || !isRecord4(rawChoice.message)) {
6506
+ return encodeResponsesResult({ ...payload, choices: [rawChoice] }, "chat_completions", model);
6507
+ }
6508
+ const message = { ...rawChoice.message, content: rawChoice.message.content ?? "" };
6509
+ const toolCalls = this.normalizeToolCalls(message.tool_calls);
6510
+ if (toolCalls !== undefined)
6511
+ message.tool_calls = toolCalls;
6512
+ return encodeResponsesResult({ ...payload, choices: [{ ...rawChoice, index: 0, message }] }, "chat_completions", model);
6168
6513
  });
6169
- for (const toolCall of state.toolCalls) {
6170
- const toolOutputIndex = output.length;
6171
- const toolItem = {
6172
- id: toolCall.id,
6173
- type: "function_call",
6174
- call_id: toolCall.id,
6175
- name: toolCall.name,
6176
- arguments: toolCall.arguments,
6177
- status: "completed"
6178
- };
6179
- events.push({
6180
- type: "response.function_call_arguments.done",
6181
- response_id: state.responseId,
6182
- item_id: toolCall.id,
6183
- output_index: toolOutputIndex,
6184
- arguments: toolCall.arguments
6185
- });
6186
- events.push({
6187
- type: "response.output_item.done",
6188
- response_id: state.responseId,
6189
- output_index: toolOutputIndex,
6190
- item: toolItem
6191
- });
6192
- output.push(toolItem);
6193
- }
6194
- const normalizedUsage = {
6195
- input_tokens: state.usage.input_tokens,
6196
- output_tokens: state.usage.output_tokens,
6197
- total_tokens: state.usage.total_tokens > 0 ? state.usage.total_tokens : state.usage.input_tokens + state.usage.output_tokens
6198
- };
6199
- const terminal = this.resolveTerminalEvent(state);
6200
- const responsePayload = {
6201
- id: state.responseId,
6202
- object: "response",
6203
- created_at: state.createdAt,
6204
- model: state.model,
6205
- status: terminal.status,
6206
- output,
6207
- usage: normalizedUsage,
6208
- metadata: {
6209
- finish_reason: state.finishReason || terminal.reason
6210
- }
6211
- };
6212
- if (terminal.type === "response.incomplete") {
6213
- responsePayload.incomplete_details = {
6214
- reason: terminal.reason
6215
- };
6514
+ const priority = (status) => status === "failed" ? 3 : status === "incomplete" ? 2 : 1;
6515
+ const terminal = candidates.reduce((selected, candidate) => priority(candidate.status) > priority(selected.status) ? candidate : selected);
6516
+ const converted = { ...terminal, id: candidates[0].id, output: candidates.flatMap((candidate) => candidate.output) };
6517
+ const finishReasons = payload.choices.filter(isRecord4).map((choice) => choice.finish_reason);
6518
+ converted.metadata = { finish_reason: finishReasons[0], finish_reasons: finishReasons };
6519
+ if (converted.status === "failed" && isRecord4(converted.error)) {
6520
+ converted.error = { ...converted.error, code: "completion_terminated" };
6216
6521
  }
6217
- if (terminal.type === "response.failed") {
6218
- responsePayload.error = {
6219
- code: "stream_terminated",
6220
- message: terminal.reason
6221
- };
6222
- }
6223
- events.push({
6224
- type: terminal.type,
6225
- response: responsePayload
6226
- });
6227
- return events;
6522
+ return converted;
6228
6523
  }
6229
- getOrCreateResponsesStreamState(chunk, streamState) {
6230
- const existing = streamState.get("responses_stream_state");
6231
- if (existing) {
6524
+ getResponsesEncoder(ctx, upstreamModel) {
6525
+ const existing = ctx.streamState.get("responses_shared_encoder");
6526
+ if (existing)
6232
6527
  return existing;
6233
- }
6234
- const responseId = this.toResponsesId(chunk.id);
6235
- const createdAt = typeof chunk.created === "number" ? chunk.created : Math.floor(Date.now() / 1000);
6236
- const model = typeof chunk.model === "string" ? chunk.model : "";
6237
- const created = {
6238
- responseId,
6239
- messageId: `${responseId}_msg`,
6240
- createdAt,
6241
- model,
6242
- messageText: "",
6243
- finishReason: "",
6244
- usage: {
6245
- input_tokens: 0,
6246
- output_tokens: 0,
6247
- total_tokens: 0
6248
- },
6249
- hasTextContentPart: false,
6250
- toolCalls: [],
6251
- toolCallIndexToId: new Map,
6252
- terminalEventType: null,
6253
- terminalReason: ""
6254
- };
6255
- streamState.set("responses_stream_state", created);
6256
- return created;
6257
- }
6258
- resolveTerminalEvent(state) {
6259
- if (state.terminalEventType) {
6260
- return {
6261
- type: state.terminalEventType,
6262
- status: this.terminalStatusFromType(state.terminalEventType),
6263
- reason: state.terminalReason || state.finishReason || (state.terminalEventType === "response.incomplete" ? "unknown" : "stop")
6264
- };
6265
- }
6266
- const mappedByFinishReason = this.mapFinishReasonToTerminalEvent(state.finishReason);
6267
- if (mappedByFinishReason) {
6268
- return {
6269
- type: mappedByFinishReason.type,
6270
- status: this.terminalStatusFromType(mappedByFinishReason.type),
6271
- reason: mappedByFinishReason.reason
6272
- };
6273
- }
6274
- if (state.messageText.length === 0 && state.toolCalls.length === 0) {
6275
- return {
6276
- type: "response.failed",
6277
- status: "failed",
6278
- reason: "empty_stream_output"
6279
- };
6280
- }
6281
- return {
6282
- type: "response.incomplete",
6283
- status: "incomplete",
6284
- reason: "unknown"
6285
- };
6286
- }
6287
- mapFinishReasonToTerminalEvent(finishReason) {
6288
- const normalizedFinishReason = finishReason.trim();
6289
- if (!normalizedFinishReason) {
6290
- return;
6291
- }
6292
- if (normalizedFinishReason === "stop" || normalizedFinishReason === "tool_calls") {
6293
- return {
6294
- type: "response.completed",
6295
- reason: normalizedFinishReason
6296
- };
6297
- }
6298
- if (normalizedFinishReason === "length") {
6299
- return {
6300
- type: "response.incomplete",
6301
- reason: "max_output_tokens"
6302
- };
6303
- }
6304
- if (normalizedFinishReason === "content_filter") {
6305
- return {
6306
- type: "response.incomplete",
6307
- reason: "content_filter"
6308
- };
6309
- }
6310
- if (normalizedFinishReason === "error") {
6311
- return {
6312
- type: "response.failed",
6313
- reason: "upstream_error"
6314
- };
6315
- }
6316
- return {
6317
- type: "response.completed",
6318
- reason: normalizedFinishReason
6319
- };
6320
- }
6321
- aggregateTerminalFromFinishReasons(finishReasons) {
6322
- let selectedTerminal;
6323
- let selectedPriority = -1;
6324
- for (const finishReason of finishReasons) {
6325
- const mapped = this.mapFinishReasonToTerminalEvent(finishReason);
6326
- if (!mapped) {
6327
- continue;
6328
- }
6329
- const priority = this.terminalPriority(mapped.type);
6330
- if (priority > selectedPriority) {
6331
- selectedTerminal = mapped;
6332
- selectedPriority = priority;
6333
- }
6334
- }
6335
- return selectedTerminal ?? {
6336
- type: "response.completed",
6337
- reason: "stop"
6338
- };
6339
- }
6340
- terminalPriority(type) {
6341
- if (type === "response.failed") {
6342
- return 3;
6343
- }
6344
- if (type === "response.incomplete") {
6345
- return 2;
6346
- }
6347
- return 1;
6348
- }
6349
- terminalStatusFromType(type) {
6350
- if (type === "response.completed") {
6351
- return "completed";
6352
- }
6353
- if (type === "response.incomplete") {
6354
- return "incomplete";
6355
- }
6356
- return "failed";
6357
- }
6358
- normalizeChatMessageContentToResponseOutputText(content) {
6359
- if (typeof content === "string") {
6360
- return content;
6361
- }
6362
- if (!Array.isArray(content)) {
6363
- return "";
6364
- }
6365
- const textSegments = [];
6366
- for (const item of content) {
6367
- if (!isRecord4(item)) {
6368
- continue;
6369
- }
6370
- if (typeof item.text === "string") {
6371
- textSegments.push(item.text);
6372
- }
6373
- }
6374
- return textSegments.join("");
6375
- }
6376
- toResponsesId(rawId) {
6377
- if (typeof rawId !== "string" || rawId.trim().length === 0) {
6378
- return `resp_${Date.now()}`;
6379
- }
6380
- if (rawId.startsWith("resp_")) {
6381
- return rawId;
6382
- }
6383
- return `resp_${rawId}`;
6528
+ const model = typeof upstreamModel === "string" ? upstreamModel : this.responsesRequestModels.get(ctx.requestId) ?? "";
6529
+ const encoder = new ResponsesEventEncoder("chat_completions", model);
6530
+ ctx.streamState.set("responses_shared_encoder", encoder);
6531
+ return encoder;
6384
6532
  }
6385
6533
  normalizeAssistantToolCallReasoningContent(body) {
6386
6534
  const rawMessages = body.messages;
@@ -6494,4 +6642,4 @@ export {
6494
6642
  server_default as default
6495
6643
  };
6496
6644
 
6497
- //# debugId=362FF24FE66CC08264756E2164756E21
6645
+ //# debugId=90A5C3F7A80248BE64756E2164756E21