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