@skaile/transform-proxy 0.1.0 → 0.2.1

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.
@@ -304,60 +304,60 @@ var UnsupportedContentError = class extends Error {
304
304
  }
305
305
  };
306
306
  // src/wire/anthropic.ts
307
- const isObj = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
307
+ const isObj$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
308
308
  /** Block types that carry attachments the proxy cannot pseudonymize. */
309
309
  const REFUSED_BLOCKS = {
310
310
  image: true,
311
311
  document: true,
312
312
  search_result: true
313
313
  };
314
- const signatureKey = (signature) => `anthropic:${signature}`;
315
- function refuse(block) {
314
+ const signatureKey$1 = (signature) => `anthropic:${signature}`;
315
+ function refuse$1(block) {
316
316
  if (typeof block.type === "string" && REFUSED_BLOCKS[block.type] === true) throw new UnsupportedContentError(`Anthropic ${block.type} content cannot be transformed`);
317
317
  }
318
- async function outbound(transform, text, leaf) {
318
+ async function outbound$2(transform, text, leaf) {
319
319
  return text === "" ? text : transform.outbound(text, leaf);
320
320
  }
321
321
  /** A string, or an array of blocks whose `text` blocks are rewritten. */
322
322
  async function rewriteText(content, leaf, transform) {
323
- if (typeof content === "string") return outbound(transform, content, leaf);
323
+ if (typeof content === "string") return outbound$2(transform, content, leaf);
324
324
  if (!Array.isArray(content)) return content;
325
325
  const out = [];
326
326
  for (const block of content) {
327
- if (!isObj(block)) {
327
+ if (!isObj$2(block)) {
328
328
  out.push(block);
329
329
  continue;
330
330
  }
331
- refuse(block);
331
+ refuse$1(block);
332
332
  out.push(block.type === "text" && typeof block.text === "string" ? {
333
333
  ...block,
334
- text: await outbound(transform, block.text, leaf)
334
+ text: await outbound$2(transform, block.text, leaf)
335
335
  } : block);
336
336
  }
337
337
  return out;
338
338
  }
339
339
  /** Every string leaf of a JSON value (tool-call input); keys stay as they are. */
340
- async function rewriteStrings(value, leaf, transform) {
341
- if (typeof value === "string") return outbound(transform, value, leaf);
340
+ async function rewriteStrings$2(value, leaf, transform) {
341
+ if (typeof value === "string") return outbound$2(transform, value, leaf);
342
342
  if (Array.isArray(value)) {
343
343
  const out = [];
344
- for (const item of value) out.push(await rewriteStrings(item, leaf, transform));
344
+ for (const item of value) out.push(await rewriteStrings$2(item, leaf, transform));
345
345
  return out;
346
346
  }
347
- if (isObj(value)) {
347
+ if (isObj$2(value)) {
348
348
  const out = {};
349
- for (const [key, item] of Object.entries(value)) out[key] = await rewriteStrings(item, leaf, transform);
349
+ for (const [key, item] of Object.entries(value)) out[key] = await rewriteStrings$2(item, leaf, transform);
350
350
  return out;
351
351
  }
352
352
  return value;
353
353
  }
354
354
  /** Every string leaf of a JSON value, restored inbound. */
355
- function restoreStrings(value, transform) {
355
+ function restoreStrings$1(value, transform) {
356
356
  if (typeof value === "string") return transform.inbound(value);
357
- if (Array.isArray(value)) return value.map((item) => restoreStrings(item, transform));
358
- if (isObj(value)) {
357
+ if (Array.isArray(value)) return value.map((item) => restoreStrings$1(item, transform));
358
+ if (isObj$2(value)) {
359
359
  const out = {};
360
- for (const [key, item] of Object.entries(value)) out[key] = restoreStrings(item, transform);
360
+ for (const [key, item] of Object.entries(value)) out[key] = restoreStrings$1(item, transform);
361
361
  return out;
362
362
  }
363
363
  return value;
@@ -365,7 +365,7 @@ function restoreStrings(value, transform) {
365
365
  /** A thinking block: swaps in the cached model text when its signature is known, else rewrites it. */
366
366
  async function rewriteThinking(block, ctx) {
367
367
  if (typeof block.signature === "string") {
368
- const cached = ctx.signatures.get(signatureKey(block.signature));
368
+ const cached = ctx.signatures.get(signatureKey$1(block.signature));
369
369
  if (cached !== void 0) return {
370
370
  ...block,
371
371
  thinking: cached
@@ -374,25 +374,25 @@ async function rewriteThinking(block, ctx) {
374
374
  if (typeof block.thinking !== "string") return block;
375
375
  return {
376
376
  ...block,
377
- thinking: await outbound(ctx.transform, block.thinking, { kind: "thinking" })
377
+ thinking: await outbound$2(ctx.transform, block.thinking, { kind: "thinking" })
378
378
  };
379
379
  }
380
- async function rewriteContent(content, role, toolNames, ctx) {
380
+ async function rewriteContent$2(content, role, toolNames, ctx) {
381
381
  const textKind = role === "assistant" ? "assistant" : "user";
382
- if (typeof content === "string") return outbound(ctx.transform, content, { kind: textKind });
382
+ if (typeof content === "string") return outbound$2(ctx.transform, content, { kind: textKind });
383
383
  if (!Array.isArray(content)) return content;
384
384
  const out = [];
385
385
  for (const block of content) {
386
- if (!isObj(block)) {
386
+ if (!isObj$2(block)) {
387
387
  out.push(block);
388
388
  continue;
389
389
  }
390
- refuse(block);
390
+ refuse$1(block);
391
391
  switch (block.type) {
392
392
  case "text":
393
393
  out.push(typeof block.text === "string" ? {
394
394
  ...block,
395
- text: await outbound(ctx.transform, block.text, { kind: textKind })
395
+ text: await outbound$2(ctx.transform, block.text, { kind: textKind })
396
396
  } : block);
397
397
  break;
398
398
  case "thinking":
@@ -402,7 +402,7 @@ async function rewriteContent(content, role, toolNames, ctx) {
402
402
  const toolName = typeof block.name === "string" ? block.name : void 0;
403
403
  out.push({
404
404
  ...block,
405
- input: await rewriteStrings(block.input, {
405
+ input: await rewriteStrings$2(block.input, {
406
406
  kind: "toolCall",
407
407
  toolName
408
408
  }, ctx.transform)
@@ -429,18 +429,23 @@ async function rewriteContent(content, role, toolNames, ctx) {
429
429
  function toolNamesById(messages) {
430
430
  const names = /* @__PURE__ */ new Map();
431
431
  for (const message of messages) {
432
- if (!isObj(message) || !Array.isArray(message.content)) continue;
433
- for (const block of message.content) if (isObj(block) && block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") names.set(block.id, block.name);
432
+ if (!isObj$2(message) || !Array.isArray(message.content)) continue;
433
+ for (const block of message.content) if (isObj$2(block) && block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") names.set(block.id, block.name);
434
434
  }
435
435
  return names;
436
436
  }
437
+ /**
438
+ * Appends the instruction after the host's system prompt. Gateways that serve only Claude Code
439
+ * traffic check that the system prompt starts with the Claude Code blocks (TeamClaude answers
440
+ * 429 for Sonnet otherwise), so the host's leading blocks must stay first.
441
+ */
437
442
  function withInstruction(system, instruction) {
438
443
  if (system === void 0) return instruction;
439
- if (typeof system === "string") return `${instruction}\n\n${system}`;
440
- if (Array.isArray(system)) return [{
444
+ if (typeof system === "string") return `${system}\n\n${instruction}`;
445
+ if (Array.isArray(system)) return [...system, {
441
446
  type: "text",
442
447
  text: instruction
443
- }, ...system];
448
+ }];
444
449
  return system;
445
450
  }
446
451
  function newBlockState(kind, transform) {
@@ -465,7 +470,7 @@ function tailDelta(kind, tail) {
465
470
  partial_json: tail
466
471
  };
467
472
  }
468
- function createStreamTransformer(ctx) {
473
+ function createStreamTransformer$2(ctx) {
469
474
  const blocks = /* @__PURE__ */ new Map();
470
475
  const handle = (block) => {
471
476
  let payload;
@@ -474,12 +479,12 @@ function createStreamTransformer(ctx) {
474
479
  } catch {
475
480
  return block.raw;
476
481
  }
477
- if (!isObj(payload)) return block.raw;
482
+ if (!isObj$2(payload)) return block.raw;
478
483
  const index = typeof payload.index === "number" ? payload.index : void 0;
479
484
  switch (payload.type) {
480
485
  case "content_block_start": {
481
486
  const content = payload.content_block;
482
- if (index === void 0 || !isObj(content)) return block.raw;
487
+ if (index === void 0 || !isObj$2(content)) return block.raw;
483
488
  if (content.type === "text" || content.type === "thinking" || content.type === "tool_use") {
484
489
  const kind = content.type === "tool_use" ? "tool" : content.type;
485
490
  blocks.set(index, newBlockState(kind, ctx.transform));
@@ -489,7 +494,7 @@ function createStreamTransformer(ctx) {
489
494
  case "content_block_delta": {
490
495
  const state = index === void 0 ? void 0 : blocks.get(index);
491
496
  const delta = payload.delta;
492
- if (state === void 0 || !isObj(delta)) return block.raw;
497
+ if (state === void 0 || !isObj$2(delta)) return block.raw;
493
498
  if (state.kind === "text" && delta.type === "text_delta" && typeof delta.text === "string") return formatSseEvent(block.event, {
494
499
  ...payload,
495
500
  delta: {
@@ -508,7 +513,7 @@ function createStreamTransformer(ctx) {
508
513
  });
509
514
  }
510
515
  if (state.kind === "thinking" && delta.type === "signature_delta" && typeof delta.signature === "string") {
511
- ctx.signatures.set(signatureKey(delta.signature), state.raw);
516
+ ctx.signatures.set(signatureKey$1(delta.signature), state.raw);
512
517
  return block.raw;
513
518
  }
514
519
  if (state.kind === "tool" && delta.type === "input_json_delta" && typeof delta.partial_json === "string") return formatSseEvent(block.event, {
@@ -552,31 +557,31 @@ function createStreamTransformer(ctx) {
552
557
  }
553
558
  const anthropicWalker = {
554
559
  async request(body, ctx) {
555
- if (!isObj(body)) return body;
560
+ if (!isObj$2(body)) return body;
556
561
  const out = { ...body };
557
562
  if (body.system !== void 0) out.system = await rewriteText(body.system, { kind: "system" }, ctx.transform);
558
563
  if (ctx.transform.instruction !== void 0) out.system = withInstruction(out.system, ctx.transform.instruction);
559
564
  if (Array.isArray(body.messages)) {
560
565
  const toolNames = toolNamesById(body.messages);
561
566
  const messages = [];
562
- for (const message of body.messages) messages.push(isObj(message) ? {
567
+ for (const message of body.messages) messages.push(isObj$2(message) ? {
563
568
  ...message,
564
- content: await rewriteContent(message.content, message.role, toolNames, ctx)
569
+ content: await rewriteContent$2(message.content, message.role, toolNames, ctx)
565
570
  } : message);
566
571
  out.messages = messages;
567
572
  }
568
573
  return out;
569
574
  },
570
575
  response(body, ctx) {
571
- if (!isObj(body) || !Array.isArray(body.content)) return body;
576
+ if (!isObj$2(body) || !Array.isArray(body.content)) return body;
572
577
  const content = body.content.map((block) => {
573
- if (!isObj(block)) return block;
578
+ if (!isObj$2(block)) return block;
574
579
  if (block.type === "text" && typeof block.text === "string") return {
575
580
  ...block,
576
581
  text: ctx.transform.inbound(block.text)
577
582
  };
578
583
  if (block.type === "thinking" && typeof block.thinking === "string") {
579
- if (typeof block.signature === "string") ctx.signatures.set(signatureKey(block.signature), block.thinking);
584
+ if (typeof block.signature === "string") ctx.signatures.set(signatureKey$1(block.signature), block.thinking);
580
585
  return {
581
586
  ...block,
582
587
  thinking: ctx.transform.inbound(block.thinking)
@@ -584,7 +589,7 @@ const anthropicWalker = {
584
589
  }
585
590
  if (block.type === "tool_use") return {
586
591
  ...block,
587
- input: restoreStrings(block.input, ctx.transform)
592
+ input: restoreStrings$1(block.input, ctx.transform)
588
593
  };
589
594
  return block;
590
595
  });
@@ -593,6 +598,651 @@ const anthropicWalker = {
593
598
  content
594
599
  };
595
600
  },
601
+ stream(ctx) {
602
+ return createStreamTransformer$2(ctx);
603
+ }
604
+ };
605
+ // src/wire/openai-chat.ts
606
+ const isObj$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
607
+ /** Content part types that carry attachments the proxy cannot pseudonymize. */
608
+ const REFUSED_PARTS$1 = {
609
+ image_url: true,
610
+ input_audio: true,
611
+ file: true
612
+ };
613
+ /** Reasoning fields used by OpenAI-compatible providers. */
614
+ const REASONING_FIELDS = ["reasoning_content", "reasoning"];
615
+ async function outbound$1(transform, text, leaf) {
616
+ return text === "" ? text : transform.outbound(text, leaf);
617
+ }
618
+ /** A string, or an array of parts whose `text` parts are rewritten. */
619
+ async function rewriteContent$1(content, leaf, transform) {
620
+ if (typeof content === "string") return outbound$1(transform, content, leaf);
621
+ if (!Array.isArray(content)) return content;
622
+ const out = [];
623
+ for (const part of content) {
624
+ if (!isObj$1(part)) {
625
+ out.push(part);
626
+ continue;
627
+ }
628
+ if (typeof part.type === "string" && REFUSED_PARTS$1[part.type] === true) throw new UnsupportedContentError(`OpenAI chat ${part.type} content cannot be transformed`);
629
+ out.push(part.type === "text" && typeof part.text === "string" ? {
630
+ ...part,
631
+ text: await outbound$1(transform, part.text, leaf)
632
+ } : part);
633
+ }
634
+ return out;
635
+ }
636
+ async function rewriteStrings$1(value, leaf, transform) {
637
+ if (typeof value === "string") return outbound$1(transform, value, leaf);
638
+ if (Array.isArray(value)) {
639
+ const out = [];
640
+ for (const item of value) out.push(await rewriteStrings$1(item, leaf, transform));
641
+ return out;
642
+ }
643
+ if (isObj$1(value)) {
644
+ const out = {};
645
+ for (const [key, item] of Object.entries(value)) out[key] = await rewriteStrings$1(item, leaf, transform);
646
+ return out;
647
+ }
648
+ return value;
649
+ }
650
+ function restoreStrings(value, transform) {
651
+ if (typeof value === "string") return transform.inbound(value);
652
+ if (Array.isArray(value)) return value.map((item) => restoreStrings(item, transform));
653
+ if (isObj$1(value)) {
654
+ const out = {};
655
+ for (const [key, item] of Object.entries(value)) out[key] = restoreStrings(item, transform);
656
+ return out;
657
+ }
658
+ return value;
659
+ }
660
+ /** Tool-call arguments are a JSON string: rewrite its string leaves, or the raw text when it is not JSON. */
661
+ async function rewriteArguments$1(args, leaf, transform) {
662
+ let parsed;
663
+ try {
664
+ parsed = JSON.parse(args);
665
+ } catch {
666
+ return outbound$1(transform, args, leaf);
667
+ }
668
+ return JSON.stringify(await rewriteStrings$1(parsed, leaf, transform));
669
+ }
670
+ function restoreArguments$1(args, transform) {
671
+ let parsed;
672
+ try {
673
+ parsed = JSON.parse(args);
674
+ } catch {
675
+ return transform.inbound(args);
676
+ }
677
+ return JSON.stringify(restoreStrings(parsed, transform));
678
+ }
679
+ async function rewriteToolCalls(calls, transform, names) {
680
+ if (!Array.isArray(calls)) return calls;
681
+ const out = [];
682
+ for (const call of calls) {
683
+ if (!isObj$1(call) || !isObj$1(call.function)) {
684
+ out.push(call);
685
+ continue;
686
+ }
687
+ const fn = call.function;
688
+ const toolName = typeof fn.name === "string" ? fn.name : void 0;
689
+ if (typeof call.id === "string" && toolName !== void 0) names.set(call.id, toolName);
690
+ out.push(typeof fn.arguments === "string" ? {
691
+ ...call,
692
+ function: {
693
+ ...fn,
694
+ arguments: await rewriteArguments$1(fn.arguments, {
695
+ kind: "toolCall",
696
+ toolName
697
+ }, transform)
698
+ }
699
+ } : call);
700
+ }
701
+ return out;
702
+ }
703
+ async function rewriteMessage(message, transform, names) {
704
+ const out = { ...message };
705
+ switch (message.role) {
706
+ case "system":
707
+ case "developer":
708
+ out.content = await rewriteContent$1(message.content, { kind: "system" }, transform);
709
+ break;
710
+ case "user":
711
+ out.content = await rewriteContent$1(message.content, { kind: "user" }, transform);
712
+ break;
713
+ case "assistant":
714
+ for (const field of REASONING_FIELDS) {
715
+ const value = message[field];
716
+ if (typeof value === "string") out[field] = await outbound$1(transform, value, { kind: "thinking" });
717
+ }
718
+ out.content = await rewriteContent$1(message.content, { kind: "assistant" }, transform);
719
+ out.tool_calls = await rewriteToolCalls(message.tool_calls, transform, names);
720
+ if (message.tool_calls === void 0) delete out.tool_calls;
721
+ break;
722
+ case "tool": {
723
+ const toolName = typeof message.tool_call_id === "string" ? names.get(message.tool_call_id) : void 0;
724
+ out.content = await rewriteContent$1(message.content, {
725
+ kind: "toolResult",
726
+ toolName
727
+ }, transform);
728
+ break;
729
+ }
730
+ default: return message;
731
+ }
732
+ if (message.content === void 0) delete out.content;
733
+ return out;
734
+ }
735
+ /** The delta carrying every held tail of one choice, or undefined when nothing is held. */
736
+ function flushChoice(state) {
737
+ const delta = {};
738
+ if (state.content !== void 0) {
739
+ const tail = state.content.flush();
740
+ if (tail !== "") delta.content = tail;
741
+ }
742
+ for (const [field, restore] of state.reasoning) {
743
+ const tail = restore.flush();
744
+ if (tail !== "") delta[field] = tail;
745
+ }
746
+ const tools = [];
747
+ for (const [index, restore] of state.tools) {
748
+ const tail = restore.flush();
749
+ if (tail !== "") tools.push({
750
+ index,
751
+ function: { arguments: tail }
752
+ });
753
+ }
754
+ if (tools.length > 0) delta.tool_calls = tools;
755
+ state.content = void 0;
756
+ state.reasoning.clear();
757
+ state.tools.clear();
758
+ return Object.keys(delta).length === 0 ? void 0 : delta;
759
+ }
760
+ function createStreamTransformer$1(ctx) {
761
+ const choices = /* @__PURE__ */ new Map();
762
+ /** The latest chunk's envelope (id, model, ...), reused for tail chunks. */
763
+ let envelope = { object: "chat.completion.chunk" };
764
+ const choiceState = (index) => {
765
+ let state = choices.get(index);
766
+ if (state === void 0) {
767
+ state = {
768
+ reasoning: /* @__PURE__ */ new Map(),
769
+ tools: /* @__PURE__ */ new Map()
770
+ };
771
+ choices.set(index, state);
772
+ }
773
+ return state;
774
+ };
775
+ const tailEvent = (event, index, delta) => formatSseEvent(event, {
776
+ ...envelope,
777
+ choices: [{
778
+ index,
779
+ delta,
780
+ finish_reason: null
781
+ }]
782
+ });
783
+ const flushAll = (event) => {
784
+ let out = "";
785
+ for (const [index, state] of choices) {
786
+ const delta = flushChoice(state);
787
+ if (delta !== void 0) out += tailEvent(event, index, delta);
788
+ }
789
+ choices.clear();
790
+ return out;
791
+ };
792
+ const rewriteDelta = (delta, state) => {
793
+ const out = { ...delta };
794
+ let changed = false;
795
+ if (typeof delta.content === "string") {
796
+ state.content ??= ctx.transform.inboundStream();
797
+ out.content = state.content.push(delta.content);
798
+ changed = true;
799
+ }
800
+ for (const field of REASONING_FIELDS) {
801
+ const value = delta[field];
802
+ if (typeof value !== "string") continue;
803
+ let restore = state.reasoning.get(field);
804
+ if (restore === void 0) {
805
+ restore = ctx.transform.inboundStream();
806
+ state.reasoning.set(field, restore);
807
+ }
808
+ out[field] = restore.push(value);
809
+ changed = true;
810
+ }
811
+ if (Array.isArray(delta.tool_calls)) out.tool_calls = delta.tool_calls.map((call) => {
812
+ if (!isObj$1(call) || !isObj$1(call.function) || typeof call.function.arguments !== "string") return call;
813
+ const index = typeof call.index === "number" ? call.index : 0;
814
+ let restore = state.tools.get(index);
815
+ if (restore === void 0) {
816
+ restore = createJsonArgRestorer(() => ctx.transform.inboundStream());
817
+ state.tools.set(index, restore);
818
+ }
819
+ changed = true;
820
+ return {
821
+ ...call,
822
+ function: {
823
+ ...call.function,
824
+ arguments: restore.push(call.function.arguments)
825
+ }
826
+ };
827
+ });
828
+ return {
829
+ delta: out,
830
+ changed
831
+ };
832
+ };
833
+ const handle = (block) => {
834
+ if (block.data.trim() === "[DONE]") return flushAll(block.event) + block.raw;
835
+ let payload;
836
+ try {
837
+ payload = JSON.parse(block.data);
838
+ } catch {
839
+ return block.raw;
840
+ }
841
+ if (!isObj$1(payload) || !Array.isArray(payload.choices)) return block.raw;
842
+ const { choices: _choices, ...rest } = payload;
843
+ envelope = rest;
844
+ let changed = false;
845
+ let before = "";
846
+ const rewritten = payload.choices.map((choice) => {
847
+ if (!isObj$1(choice)) return choice;
848
+ const index = typeof choice.index === "number" ? choice.index : 0;
849
+ const state = choiceState(index);
850
+ let next = choice;
851
+ if (isObj$1(choice.delta)) {
852
+ const result = rewriteDelta(choice.delta, state);
853
+ if (result.changed) {
854
+ next = {
855
+ ...choice,
856
+ delta: result.delta
857
+ };
858
+ changed = true;
859
+ }
860
+ }
861
+ if (choice.finish_reason !== null && choice.finish_reason !== void 0) {
862
+ const tail = flushChoice(state);
863
+ choices.delete(index);
864
+ if (tail !== void 0) before += tailEvent(block.event, index, tail);
865
+ }
866
+ return next;
867
+ });
868
+ return before + (changed ? formatSseEvent(block.event, {
869
+ ...payload,
870
+ choices: rewritten
871
+ }) : block.raw);
872
+ };
873
+ return createSseFramer(handle, () => flushAll(void 0));
874
+ }
875
+ const openaiChatWalker = {
876
+ async request(body, ctx) {
877
+ if (!isObj$1(body)) return body;
878
+ const out = { ...body };
879
+ if (Array.isArray(body.messages)) {
880
+ const names = /* @__PURE__ */ new Map();
881
+ const messages = [];
882
+ for (const message of body.messages) messages.push(isObj$1(message) ? await rewriteMessage(message, ctx.transform, names) : message);
883
+ if (ctx.transform.instruction !== void 0) messages.unshift({
884
+ role: "system",
885
+ content: ctx.transform.instruction
886
+ });
887
+ out.messages = messages;
888
+ }
889
+ return out;
890
+ },
891
+ response(body, ctx) {
892
+ if (!isObj$1(body) || !Array.isArray(body.choices)) return body;
893
+ const choices = body.choices.map((choice) => {
894
+ if (!isObj$1(choice) || !isObj$1(choice.message)) return choice;
895
+ const message = { ...choice.message };
896
+ if (typeof message.content === "string") message.content = ctx.transform.inbound(message.content);
897
+ for (const field of REASONING_FIELDS) {
898
+ const value = message[field];
899
+ if (typeof value === "string") message[field] = ctx.transform.inbound(value);
900
+ }
901
+ if (Array.isArray(message.tool_calls)) message.tool_calls = message.tool_calls.map((call) => {
902
+ if (!isObj$1(call) || !isObj$1(call.function) || typeof call.function.arguments !== "string") return call;
903
+ return {
904
+ ...call,
905
+ function: {
906
+ ...call.function,
907
+ arguments: restoreArguments$1(call.function.arguments, ctx.transform)
908
+ }
909
+ };
910
+ });
911
+ return {
912
+ ...choice,
913
+ message
914
+ };
915
+ });
916
+ return {
917
+ ...body,
918
+ choices
919
+ };
920
+ },
921
+ stream(ctx) {
922
+ return createStreamTransformer$1(ctx);
923
+ }
924
+ };
925
+ // src/wire/openai-responses.ts
926
+ const isObj = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
927
+ /** Part types that carry attachments the proxy cannot pseudonymize. */
928
+ const REFUSED_PARTS = {
929
+ input_image: true,
930
+ input_file: true,
931
+ input_audio: true
932
+ };
933
+ const signatureKey = (itemId) => `responses:${itemId}`;
934
+ function refuse(part) {
935
+ if (typeof part.type === "string" && REFUSED_PARTS[part.type] === true) throw new UnsupportedContentError(`OpenAI responses ${part.type} content cannot be transformed`);
936
+ }
937
+ async function outbound(transform, text, leaf) {
938
+ return text === "" ? text : transform.outbound(text, leaf);
939
+ }
940
+ /** A string, or an array of parts whose `input_text`/`output_text` parts are rewritten. */
941
+ async function rewriteContent(content, leaf, transform) {
942
+ if (typeof content === "string") return outbound(transform, content, leaf);
943
+ if (!Array.isArray(content)) return content;
944
+ const out = [];
945
+ for (const part of content) {
946
+ if (!isObj(part)) {
947
+ out.push(part);
948
+ continue;
949
+ }
950
+ refuse(part);
951
+ out.push((part.type === "input_text" || part.type === "output_text") && typeof part.text === "string" ? {
952
+ ...part,
953
+ text: await outbound(transform, part.text, leaf)
954
+ } : part);
955
+ }
956
+ return out;
957
+ }
958
+ async function rewriteStrings(value, leaf, transform) {
959
+ if (typeof value === "string") return outbound(transform, value, leaf);
960
+ if (Array.isArray(value)) {
961
+ const out = [];
962
+ for (const item of value) out.push(await rewriteStrings(item, leaf, transform));
963
+ return out;
964
+ }
965
+ if (isObj(value)) {
966
+ const out = {};
967
+ for (const [key, item] of Object.entries(value)) out[key] = await rewriteStrings(item, leaf, transform);
968
+ return out;
969
+ }
970
+ return value;
971
+ }
972
+ /** Tool-call arguments: a JSON string whose string leaves are rewritten; raw text when it is not JSON. */
973
+ async function rewriteArguments(args, leaf, transform) {
974
+ let parsed;
975
+ try {
976
+ parsed = JSON.parse(args);
977
+ } catch {
978
+ return outbound(transform, args, leaf);
979
+ }
980
+ return JSON.stringify(await rewriteStrings(parsed, leaf, transform));
981
+ }
982
+ /** Restores the string values of a JSON text, keeping its formatting; restored originals are JSON-escaped. */
983
+ function restoreArguments(args, transform) {
984
+ const restorer = createJsonArgRestorer(() => transform.inboundStream());
985
+ return restorer.push(args) + restorer.flush();
986
+ }
987
+ function summaryTexts(item) {
988
+ if (!Array.isArray(item.summary)) return void 0;
989
+ const texts = [];
990
+ for (const part of item.summary) {
991
+ if (!isObj(part) || typeof part.text !== "string") return void 0;
992
+ texts.push(part.text);
993
+ }
994
+ return texts;
995
+ }
996
+ function cachedSummary(item, ctx) {
997
+ if (typeof item.id !== "string") return void 0;
998
+ const cached = ctx.signatures.get(signatureKey(item.id));
999
+ if (cached === void 0) return void 0;
1000
+ const texts = JSON.parse(cached);
1001
+ if (!Array.isArray(texts) || !Array.isArray(item.summary) || texts.length !== item.summary.length) return void 0;
1002
+ return texts;
1003
+ }
1004
+ async function rewriteReasoning(item, ctx) {
1005
+ if (!Array.isArray(item.summary)) return item;
1006
+ const cached = cachedSummary(item, ctx);
1007
+ const summary = [];
1008
+ for (const [i, part] of item.summary.entries()) if (!isObj(part) || typeof part.text !== "string") summary.push(part);
1009
+ else if (cached !== void 0) summary.push({
1010
+ ...part,
1011
+ text: cached[i]
1012
+ });
1013
+ else summary.push({
1014
+ ...part,
1015
+ text: await outbound(ctx.transform, part.text, { kind: "thinking" })
1016
+ });
1017
+ return {
1018
+ ...item,
1019
+ summary
1020
+ };
1021
+ }
1022
+ function roleKind(role) {
1023
+ if (role === "developer" || role === "system") return "system";
1024
+ if (role === "user") return "user";
1025
+ if (role === "assistant") return "assistant";
1026
+ }
1027
+ function toolNamesByCallId(items) {
1028
+ const names = /* @__PURE__ */ new Map();
1029
+ for (const item of items) if (isObj(item) && item.type === "function_call" && typeof item.call_id === "string" && typeof item.name === "string") names.set(item.call_id, item.name);
1030
+ return names;
1031
+ }
1032
+ async function rewriteItem(item, toolNames, ctx) {
1033
+ if (item.type === "message" || item.type === void 0 && item.role !== void 0) {
1034
+ const kind = roleKind(item.role);
1035
+ if (kind === void 0) return item;
1036
+ return {
1037
+ ...item,
1038
+ content: await rewriteContent(item.content, { kind }, ctx.transform)
1039
+ };
1040
+ }
1041
+ switch (item.type) {
1042
+ case "function_call": {
1043
+ if (typeof item.arguments !== "string") return item;
1044
+ const toolName = typeof item.name === "string" ? item.name : void 0;
1045
+ return {
1046
+ ...item,
1047
+ arguments: await rewriteArguments(item.arguments, {
1048
+ kind: "toolCall",
1049
+ toolName
1050
+ }, ctx.transform)
1051
+ };
1052
+ }
1053
+ case "function_call_output": {
1054
+ const toolName = typeof item.call_id === "string" ? toolNames.get(item.call_id) : void 0;
1055
+ return {
1056
+ ...item,
1057
+ output: await rewriteContent(item.output, {
1058
+ kind: "toolResult",
1059
+ toolName
1060
+ }, ctx.transform)
1061
+ };
1062
+ }
1063
+ case "reasoning": return rewriteReasoning(item, ctx);
1064
+ default: return item;
1065
+ }
1066
+ }
1067
+ /** Restores one output item; reasoning items store their raw summary in the signature cache first. */
1068
+ function restoreItem(item, ctx) {
1069
+ if (!isObj(item)) return item;
1070
+ const transform = ctx.transform;
1071
+ switch (item.type) {
1072
+ case "message":
1073
+ if (!Array.isArray(item.content)) return item;
1074
+ return {
1075
+ ...item,
1076
+ content: item.content.map((part) => restorePart(part, transform))
1077
+ };
1078
+ case "reasoning": {
1079
+ const texts = summaryTexts(item);
1080
+ if (texts === void 0) return item;
1081
+ if (typeof item.id === "string") ctx.signatures.set(signatureKey(item.id), JSON.stringify(texts));
1082
+ return {
1083
+ ...item,
1084
+ summary: item.summary.map((part) => ({
1085
+ ...part,
1086
+ text: transform.inbound(part.text)
1087
+ }))
1088
+ };
1089
+ }
1090
+ case "function_call": return typeof item.arguments === "string" ? {
1091
+ ...item,
1092
+ arguments: restoreArguments(item.arguments, transform)
1093
+ } : item;
1094
+ default: return item;
1095
+ }
1096
+ }
1097
+ function restorePart(part, transform) {
1098
+ return isObj(part) && part.type === "output_text" && typeof part.text === "string" ? {
1099
+ ...part,
1100
+ text: transform.inbound(part.text)
1101
+ } : part;
1102
+ }
1103
+ function restoreResponse(body, ctx) {
1104
+ const out = { ...body };
1105
+ if (Array.isArray(body.output)) out.output = body.output.map((item) => restoreItem(item, ctx));
1106
+ if (typeof body.output_text === "string") out.output_text = ctx.transform.inbound(body.output_text);
1107
+ return out;
1108
+ }
1109
+ function createStreamTransformer(ctx) {
1110
+ const streams = /* @__PURE__ */ new Map();
1111
+ const open = (key, factory) => {
1112
+ let state = streams.get(key);
1113
+ if (state === void 0) {
1114
+ state = { restore: factory() };
1115
+ streams.set(key, state);
1116
+ }
1117
+ return state;
1118
+ };
1119
+ /** Emits the tail delta (if any) followed by the rewritten `.done` event. */
1120
+ const closeWith = (block, payload, key, deltaType, done) => {
1121
+ const state = streams.get(key);
1122
+ streams.delete(key);
1123
+ let out = "";
1124
+ const tail = state?.restore.flush() ?? "";
1125
+ if (tail !== "") {
1126
+ const { text: _text, arguments: _args, ...rest } = payload;
1127
+ out += formatSseEvent(block.event === void 0 ? void 0 : deltaType, {
1128
+ ...rest,
1129
+ type: deltaType,
1130
+ delta: tail
1131
+ });
1132
+ }
1133
+ return out + formatSseEvent(block.event, done);
1134
+ };
1135
+ const handle = (block) => {
1136
+ let payload;
1137
+ try {
1138
+ payload = JSON.parse(block.data);
1139
+ } catch {
1140
+ return block.raw;
1141
+ }
1142
+ if (!isObj(payload)) return block.raw;
1143
+ const itemId = typeof payload.item_id === "string" ? payload.item_id : void 0;
1144
+ const textKey = `text:${itemId}:${String(payload.content_index)}`;
1145
+ const summaryKey = `summary:${itemId}:${String(payload.summary_index)}`;
1146
+ const argsKey = `args:${itemId}`;
1147
+ const textStream = () => ctx.transform.inboundStream();
1148
+ const argStream = () => createJsonArgRestorer(() => ctx.transform.inboundStream());
1149
+ switch (payload.type) {
1150
+ case "response.output_text.delta":
1151
+ if (itemId === void 0 || typeof payload.delta !== "string") return block.raw;
1152
+ return formatSseEvent(block.event, {
1153
+ ...payload,
1154
+ delta: open(textKey, textStream).restore.push(payload.delta)
1155
+ });
1156
+ case "response.reasoning_summary_text.delta":
1157
+ if (itemId === void 0 || typeof payload.delta !== "string") return block.raw;
1158
+ return formatSseEvent(block.event, {
1159
+ ...payload,
1160
+ delta: open(summaryKey, textStream).restore.push(payload.delta)
1161
+ });
1162
+ case "response.function_call_arguments.delta":
1163
+ if (itemId === void 0 || typeof payload.delta !== "string") return block.raw;
1164
+ return formatSseEvent(block.event, {
1165
+ ...payload,
1166
+ delta: open(argsKey, argStream).restore.push(payload.delta)
1167
+ });
1168
+ case "response.output_text.done":
1169
+ if (itemId === void 0 || typeof payload.text !== "string") return block.raw;
1170
+ return closeWith(block, payload, textKey, "response.output_text.delta", {
1171
+ ...payload,
1172
+ text: ctx.transform.inbound(payload.text)
1173
+ });
1174
+ case "response.reasoning_summary_text.done":
1175
+ if (itemId === void 0 || typeof payload.text !== "string") return block.raw;
1176
+ return closeWith(block, payload, summaryKey, "response.reasoning_summary_text.delta", {
1177
+ ...payload,
1178
+ text: ctx.transform.inbound(payload.text)
1179
+ });
1180
+ case "response.function_call_arguments.done":
1181
+ if (itemId === void 0 || typeof payload.arguments !== "string") return block.raw;
1182
+ return closeWith(block, payload, argsKey, "response.function_call_arguments.delta", {
1183
+ ...payload,
1184
+ arguments: restoreArguments(payload.arguments, ctx.transform)
1185
+ });
1186
+ case "response.content_part.done": return isObj(payload.part) ? formatSseEvent(block.event, {
1187
+ ...payload,
1188
+ part: restorePart(payload.part, ctx.transform)
1189
+ }) : block.raw;
1190
+ case "response.reasoning_summary_part.done": return isObj(payload.part) && typeof payload.part.text === "string" ? formatSseEvent(block.event, {
1191
+ ...payload,
1192
+ part: {
1193
+ ...payload.part,
1194
+ text: ctx.transform.inbound(payload.part.text)
1195
+ }
1196
+ }) : block.raw;
1197
+ case "response.output_item.done": return isObj(payload.item) ? formatSseEvent(block.event, {
1198
+ ...payload,
1199
+ item: restoreItem(payload.item, ctx)
1200
+ }) : block.raw;
1201
+ case "response.completed":
1202
+ case "response.incomplete": return isObj(payload.response) ? formatSseEvent(block.event, {
1203
+ ...payload,
1204
+ response: restoreResponse(payload.response, ctx)
1205
+ }) : block.raw;
1206
+ default: return block.raw;
1207
+ }
1208
+ };
1209
+ const finish = () => {
1210
+ let out = "";
1211
+ for (const [key, state] of streams) {
1212
+ const tail = state.restore.flush();
1213
+ if (tail === "") continue;
1214
+ const [kind, itemId] = key.split(":");
1215
+ const type = kind === "text" ? "response.output_text.delta" : kind === "summary" ? "response.reasoning_summary_text.delta" : "response.function_call_arguments.delta";
1216
+ out += formatSseEvent(type, {
1217
+ type,
1218
+ item_id: itemId,
1219
+ delta: tail
1220
+ });
1221
+ }
1222
+ streams.clear();
1223
+ return out;
1224
+ };
1225
+ return createSseFramer(handle, finish);
1226
+ }
1227
+ const openaiResponsesWalker = {
1228
+ async request(body, ctx) {
1229
+ if (!isObj(body)) return body;
1230
+ const out = { ...body };
1231
+ if (typeof body.instructions === "string") out.instructions = await outbound(ctx.transform, body.instructions, { kind: "system" });
1232
+ const instruction = ctx.transform.instruction;
1233
+ if (instruction !== void 0) out.instructions = typeof out.instructions === "string" && out.instructions !== "" ? `${instruction}\n\n${out.instructions}` : instruction;
1234
+ if (typeof body.input === "string") out.input = await outbound(ctx.transform, body.input, { kind: "user" });
1235
+ else if (Array.isArray(body.input)) {
1236
+ const toolNames = toolNamesByCallId(body.input);
1237
+ const input = [];
1238
+ for (const item of body.input) input.push(isObj(item) ? await rewriteItem(item, toolNames, ctx) : item);
1239
+ out.input = input;
1240
+ }
1241
+ return out;
1242
+ },
1243
+ response(body, ctx) {
1244
+ return isObj(body) ? restoreResponse(body, ctx) : body;
1245
+ },
596
1246
  stream(ctx) {
597
1247
  return createStreamTransformer(ctx);
598
1248
  }
@@ -600,18 +1250,30 @@ const anthropicWalker = {
600
1250
  // src/routes.ts
601
1251
  /**
602
1252
  * The route table. Routes are matched in order, so longer suffixes come first.
603
- * Add the OpenAI walkers here (`/chat/completions`, `/responses`) when they exist. Until then
604
- * those paths, and embeddings, answer 404 `unsupported_route`.
1253
+ * Any other path, embeddings included, answers 404 `unsupported_route`.
605
1254
  */
606
- const WIRE_ROUTES = [{
607
- suffix: "/messages/count_tokens",
608
- wire: anthropicWalker,
609
- rewriteResponse: false
610
- }, {
611
- suffix: "/messages",
612
- wire: anthropicWalker,
613
- rewriteResponse: true
614
- }];
1255
+ const WIRE_ROUTES = [
1256
+ {
1257
+ suffix: "/messages/count_tokens",
1258
+ wire: anthropicWalker,
1259
+ rewriteResponse: false
1260
+ },
1261
+ {
1262
+ suffix: "/messages",
1263
+ wire: anthropicWalker,
1264
+ rewriteResponse: true
1265
+ },
1266
+ {
1267
+ suffix: "/chat/completions",
1268
+ wire: openaiChatWalker,
1269
+ rewriteResponse: true
1270
+ },
1271
+ {
1272
+ suffix: "/responses",
1273
+ wire: openaiResponsesWalker,
1274
+ rewriteResponse: true
1275
+ }
1276
+ ];
615
1277
  /** Classifies a request by method and path. `undefined` means the route is unsupported. */
616
1278
  function classify(method, pathname) {
617
1279
  if (method === "GET" && pathname.endsWith("/models")) return { kind: "passthrough" };
@@ -1057,4 +1719,4 @@ function requireStringList(value, field) {
1057
1719
  function isRecord(value) {
1058
1720
  return typeof value === "object" && value !== null && !Array.isArray(value);
1059
1721
  }
1060
- export { silentLogger as _, parseProxyConfig as a, SESSION_HEADER as c, createTransformProxy as d, anthropicWalker as f, errorMessage as g, composeTransforms as h, loadProxyConfig as i, TOKEN_HEADER as l, createSessionFacts as m, configCandidates as n, resolveProxyConfig as o, UnsupportedContentError as p, findProxyConfig as r, PIPELINE_HEADER as s, CONFIG_FILE_NAME as t, UPSTREAM_HEADER as u };
1722
+ export { composeTransforms as _, parseProxyConfig as a, SESSION_HEADER as c, createTransformProxy as d, openaiResponsesWalker as f, createSessionFacts as g, UnsupportedContentError as h, loadProxyConfig as i, TOKEN_HEADER as l, anthropicWalker as m, configCandidates as n, resolveProxyConfig as o, openaiChatWalker as p, findProxyConfig as r, PIPELINE_HEADER as s, CONFIG_FILE_NAME as t, UPSTREAM_HEADER as u, errorMessage as v, silentLogger as y };