proxitor 0.18.0 → 0.19.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.
package/README.md CHANGED
@@ -197,7 +197,9 @@ Common flags: `--port`, `--host`, `--config <path>`, `--openrouter-key <key>` /
197
197
 
198
198
  **Anthropic returns `400` about mixed TTLs when `cacheControlTtl: 1h`.** Set `rewriteBlockTtl: auto` (or `always`) to normalize the client's block-level `cache_control` breakpoints to the same TTL — see the [configuration reference](./docs/configuration.md#prompt-caching).
199
199
 
200
- **OpenRouter returns `400 invalid_prompt | Invalid Responses API request` on `/v1/responses`.** Some clients send Responses `input` items without the `type` field OpenRouter requires. `normalizeResponses: auto` (the default) tags them, lifts `role:"system"` into `instructions`, and adds the `id`/`status` OpenRouter wants on assistant history. Set it to `skip` only for raw passthrough.
200
+ **OpenRouter returns `400 invalid_prompt | Invalid Responses API request` on `/v1/responses`.** Some clients send Responses `input` items without the `type` field OpenRouter requires. `normalizeResponses: true` (the default; off only for raw passthrough) tags them, lifts `role:"system"` into `instructions`, and adds the `id`/`status` OpenRouter wants on assistant history. It acts on `/v1/responses` only.
201
+
202
+ **Strict providers reject `role:"system"` inside `/v1/messages`.** Some clients (e.g. an injected `SessionStart` hook payload) place a `role:"system"` item mid-thread in `messages`; the Anthropic Messages API allows only `user`/`assistant` there, so providers like OpenRouter → GLM return `400 ... messages[n].role: Input should be 'user' or 'assistant'`. Enable `normalizeMessages: true` (off by default; Fixes menu or per-model override) to lift each such item's text into the top-level `system` field and drop it from `messages`. It acts on `/v1/messages` only.
201
203
 
202
204
  **The provider keeps switching between requests.** Make sure `sessionId` is not `skip` — both `auto` (default) and `always` inject a sticky session ID; without it OpenRouter only pins after the first cache hit.
203
205
 
package/dist/cli.mjs CHANGED
@@ -16293,7 +16293,8 @@ const modelOverrideSchema = object({
16293
16293
  cacheControlTtl: ttlSchema.optional(),
16294
16294
  rewriteBlockTtl: triStateSchema.optional(),
16295
16295
  sessionId: triStateSchema.optional(),
16296
- normalizeResponses: triStateSchema.optional(),
16296
+ normalizeResponses: boolean().optional(),
16297
+ normalizeMessages: boolean().optional(),
16297
16298
  normalizeVolatileSystem: boolean().optional()
16298
16299
  }).strict();
16299
16300
  const proxyConfigSchema = object({
@@ -16313,7 +16314,8 @@ const proxyConfigSchema = object({
16313
16314
  cacheControlTtl: ttlSchema.optional(),
16314
16315
  rewriteBlockTtl: triStateSchema.default("skip"),
16315
16316
  sessionId: triStateSchema.default("auto"),
16316
- normalizeResponses: triStateSchema.default("auto"),
16317
+ normalizeResponses: boolean().default(true),
16318
+ normalizeMessages: boolean().default(false),
16317
16319
  normalizeVolatileSystem: boolean().default(false),
16318
16320
  observability: observabilityConfigSchema,
16319
16321
  modelOverrides: record(string$1().min(1), modelOverrideSchema).optional()
@@ -16491,6 +16493,7 @@ function resolveModelConfig(config, modelName) {
16491
16493
  rewriteBlockTtl: config.rewriteBlockTtl,
16492
16494
  sessionId: config.sessionId,
16493
16495
  normalizeResponses: config.normalizeResponses,
16496
+ normalizeMessages: config.normalizeMessages,
16494
16497
  normalizeVolatileSystem: config.normalizeVolatileSystem
16495
16498
  };
16496
16499
  if (!modelName || !config.modelOverrides) return result;
@@ -16525,6 +16528,7 @@ function applyOverride(result, override) {
16525
16528
  if (override.rewriteBlockTtl !== void 0) result.rewriteBlockTtl = override.rewriteBlockTtl;
16526
16529
  if (override.sessionId !== void 0) result.sessionId = override.sessionId;
16527
16530
  if (override.normalizeResponses !== void 0) result.normalizeResponses = override.normalizeResponses;
16531
+ if (override.normalizeMessages !== void 0) result.normalizeMessages = override.normalizeMessages;
16528
16532
  if (override.normalizeVolatileSystem !== void 0) result.normalizeVolatileSystem = override.normalizeVolatileSystem;
16529
16533
  }
16530
16534
  /** Reject base URLs ending in /v1 — paths are forwarded as-is, so /v1 suffix causes doubled paths. */
@@ -16800,7 +16804,7 @@ const LogTypes = {
16800
16804
  trace: { level: LogLevels.trace },
16801
16805
  verbose: { level: LogLevels.verbose }
16802
16806
  };
16803
- function isPlainObject$1(value) {
16807
+ function isPlainObject$1$1(value) {
16804
16808
  if (value === null || typeof value !== "object") return false;
16805
16809
  const prototype = Object.getPrototypeOf(value);
16806
16810
  if (prototype !== null && prototype !== Object.prototype && Object.getPrototypeOf(prototype) !== null) return false;
@@ -16809,7 +16813,7 @@ function isPlainObject$1(value) {
16809
16813
  return true;
16810
16814
  }
16811
16815
  function _defu(baseObject, defaults, namespace = ".", merger) {
16812
- if (!isPlainObject$1(defaults)) return _defu(baseObject, {}, namespace, merger);
16816
+ if (!isPlainObject$1$1(defaults)) return _defu(baseObject, {}, namespace, merger);
16813
16817
  const object = Object.assign({}, defaults);
16814
16818
  for (const key in baseObject) {
16815
16819
  if (key === "__proto__" || key === "constructor") continue;
@@ -16817,7 +16821,7 @@ function _defu(baseObject, defaults, namespace = ".", merger) {
16817
16821
  if (value === null || value === void 0) continue;
16818
16822
  if (merger && merger(object, key, value, namespace)) continue;
16819
16823
  if (Array.isArray(value) && Array.isArray(object[key])) object[key] = [...value, ...object[key]];
16820
- else if (isPlainObject$1(value) && isPlainObject$1(object[key])) object[key] = _defu(value, object[key], (namespace ? `${namespace}.` : "") + key.toString(), merger);
16824
+ else if (isPlainObject$1$1(value) && isPlainObject$1$1(object[key])) object[key] = _defu(value, object[key], (namespace ? `${namespace}.` : "") + key.toString(), merger);
16821
16825
  else object[key] = value;
16822
16826
  }
16823
16827
  return object;
@@ -18123,12 +18127,58 @@ const REWRITE_HINTS = {
18123
18127
  always: "All models",
18124
18128
  skip: "Leave client block ttl as-is (may mismatch root)"
18125
18129
  };
18126
- /** Shared hint texts for the normalize-responses tri-stateused in the Fixes menu + override editor. */
18130
+ /** Hint texts for the normalize-responses on/off toggle repairs /v1/responses bodies for OpenRouter. */
18127
18131
  const NORMALIZE_RESPONSES_HINTS = {
18128
- auto: "/v1/responses only (default)",
18129
- always: "All endpoints",
18130
- skip: "Off — raw passthrough (OpenRouter may reject)"
18132
+ on: "Repair /v1/responses bodies (type tagging, system→instructions, id/status)",
18133
+ off: "Raw passthrough (OpenRouter may reject malformed /v1/responses)"
18131
18134
  };
18135
+ async function askNormalizeResponses(message, current, opts) {
18136
+ const options = [{
18137
+ value: true,
18138
+ label: "On",
18139
+ hint: NORMALIZE_RESPONSES_HINTS.on
18140
+ }, {
18141
+ value: false,
18142
+ label: "Off",
18143
+ hint: NORMALIZE_RESPONSES_HINTS.off
18144
+ }];
18145
+ if (opts?.removable) options.push({
18146
+ value: "reset",
18147
+ label: "Reset / inherit",
18148
+ hint: opts.resetHint ?? "Remove override"
18149
+ });
18150
+ return await select({
18151
+ message,
18152
+ initialValue: current ?? (opts?.removable ? "reset" : false),
18153
+ options
18154
+ });
18155
+ }
18156
+ /** Hint texts for the normalize-messages on/off toggle — lifts stray role:"system" out of /v1/messages. */
18157
+ const NORMALIZE_MESSAGES_HINTS = {
18158
+ on: "Lift role:system → top-level system (/v1/messages only)",
18159
+ off: "Raw passthrough (strict providers may reject role:system)"
18160
+ };
18161
+ async function askNormalizeMessages(message, current, opts) {
18162
+ const options = [{
18163
+ value: true,
18164
+ label: "On",
18165
+ hint: NORMALIZE_MESSAGES_HINTS.on
18166
+ }, {
18167
+ value: false,
18168
+ label: "Off",
18169
+ hint: NORMALIZE_MESSAGES_HINTS.off
18170
+ }];
18171
+ if (opts?.removable) options.push({
18172
+ value: "reset",
18173
+ label: "Reset / inherit",
18174
+ hint: opts.resetHint ?? "Remove override"
18175
+ });
18176
+ return await select({
18177
+ message,
18178
+ initialValue: current ?? (opts?.removable ? "reset" : false),
18179
+ options
18180
+ });
18181
+ }
18132
18182
  const NORMALIZE_HINTS = {
18133
18183
  on: "Rewrite cch → stable prefix cache",
18134
18184
  off: "Passthrough — rewrite nothing"
@@ -18233,12 +18283,18 @@ async function collectSessionTriState(currentSid) {
18233
18283
  if (sid === "reset") return { sessionId: { remove: true } };
18234
18284
  return { sessionId: { value: sid } };
18235
18285
  }
18236
- async function collectNormalizeResponsesTriState(currentNr) {
18237
- const nr = await askTriState("normalizeResponses mode", currentNr, NORMALIZE_RESPONSES_HINTS, { removable: true });
18286
+ async function collectNormalizeResponses(currentNr) {
18287
+ const nr = await askNormalizeResponses("Repair /v1/responses bodies", currentNr, { removable: true });
18238
18288
  if (typeof nr === "symbol") return null;
18239
18289
  if (nr === "reset") return { normalizeResponses: { remove: true } };
18240
18290
  return { normalizeResponses: { value: nr } };
18241
18291
  }
18292
+ async function collectNormalizeMessages(currentNm) {
18293
+ const nm = await askNormalizeMessages("Lift role:system out of /v1/messages", currentNm, { removable: true });
18294
+ if (typeof nm === "symbol") return null;
18295
+ if (nm === "reset") return { normalizeMessages: { remove: true } };
18296
+ return { normalizeMessages: { value: nm } };
18297
+ }
18242
18298
  /**
18243
18299
  * Collects cacheControl mode, TTL, and rewriteBlockTtl.
18244
18300
  * Mode-cancel aborts; TTL-cancel keeps existing TTL; rewrite-cancel keeps existing rewrite.
@@ -18687,12 +18743,19 @@ async function editSessionId(current) {
18687
18743
  return next;
18688
18744
  }
18689
18745
  async function editNormalizeResponses(current) {
18690
- const result = await collectNormalizeResponsesTriState(current.normalizeResponses);
18746
+ const result = await collectNormalizeResponses(current.normalizeResponses);
18691
18747
  if (result === null) return current;
18692
18748
  const next = { ...current };
18693
18749
  applyField(next, "normalizeResponses", result.normalizeResponses);
18694
18750
  return next;
18695
18751
  }
18752
+ async function editNormalizeMessages(current) {
18753
+ const result = await collectNormalizeMessages(current.normalizeMessages);
18754
+ if (result === null) return current;
18755
+ const next = { ...current };
18756
+ applyField(next, "normalizeMessages", result.normalizeMessages);
18757
+ return next;
18758
+ }
18696
18759
  async function editCacheControl(current, configPath) {
18697
18760
  const globalTtl = readGlobalTtl(configPath);
18698
18761
  const result = await collectCacheTriState(current.cacheControl, current.cacheControlTtl, globalTtl, current.rewriteBlockTtl);
@@ -18800,21 +18863,40 @@ async function cachingCommand(opts) {
18800
18863
  outro("Bye!");
18801
18864
  }
18802
18865
  //#endregion
18866
+ //#region src/commands/config/normalize-messages.ts
18867
+ async function normalizeMessagesCommand(opts) {
18868
+ const configPath = requireConfigPath(opts?.configPath);
18869
+ const raw = readConfigFileRaw(configPath).normalizeMessages;
18870
+ const effective = raw ?? DEFAULTS.normalizeMessages;
18871
+ log.info(`Current: normalizeMessages = ${raw === void 0 ? `(default -> ${effective ? "on" : "off"})` : effective}`);
18872
+ const choice = await askNormalizeMessages("Lift stray role:\"system\" out of /v1/messages into top-level system? Fixes 400 rejections from strict Anthropic-format providers (OpenRouter → GLM et al.). Acts on /v1/messages only.", raw, {
18873
+ removable: true,
18874
+ resetHint: `remove (default: ${DEFAULTS.normalizeMessages ? "on" : "off"})`
18875
+ });
18876
+ if (typeof choice === "symbol") return;
18877
+ const fields = {};
18878
+ fields.normalizeMessages = choice === "reset" ? void 0 : choice;
18879
+ setGlobalConfigFields(configPath, fields);
18880
+ const label = choice === "reset" ? `(default: ${DEFAULTS.normalizeMessages})` : choice;
18881
+ log.success(`normalizeMessages set to ${label}`);
18882
+ }
18883
+ //#endregion
18803
18884
  //#region src/commands/config/normalize-responses.ts
18804
18885
  async function normalizeResponsesCommand(opts) {
18805
18886
  const configPath = requireConfigPath(opts?.configPath);
18806
18887
  const raw = readConfigFileRaw(configPath).normalizeResponses;
18807
18888
  const effective = raw ?? DEFAULTS.normalizeResponses;
18808
- log.info(`Current: normalizeResponses = ${raw === void 0 ? `(default -> ${effective})` : effective}`);
18809
- const result = await askTriState("normalizeResponses mode", raw, NORMALIZE_RESPONSES_HINTS, { removable: true });
18810
- if (typeof result === "symbol") return;
18811
- if (result === "reset") {
18812
- setGlobalConfigField(configPath, "normalizeResponses", void 0);
18813
- log.success("normalizeResponses reset to default (auto)");
18814
- return;
18815
- }
18816
- setGlobalConfigField(configPath, "normalizeResponses", result);
18817
- log.success(`normalizeResponses set to ${result}`);
18889
+ log.info(`Current: normalizeResponses = ${raw === void 0 ? `(default -> ${effective ? "on" : "off"})` : effective}`);
18890
+ const choice = await askNormalizeResponses("Repair /v1/responses request bodies for OpenRouter (tag input types, lift role:\"system\" into instructions, synthesize assistant id/status)? Acts on /v1/responses only.", raw, {
18891
+ removable: true,
18892
+ resetHint: `remove (default: ${DEFAULTS.normalizeResponses ? "on" : "off"})`
18893
+ });
18894
+ if (typeof choice === "symbol") return;
18895
+ const fields = {};
18896
+ fields.normalizeResponses = choice === "reset" ? void 0 : choice;
18897
+ setGlobalConfigFields(configPath, fields);
18898
+ const label = choice === "reset" ? `(default: ${DEFAULTS.normalizeResponses})` : choice;
18899
+ log.success(`normalizeResponses set to ${label}`);
18818
18900
  }
18819
18901
  //#endregion
18820
18902
  //#region src/commands/config/fixes-menu.ts
@@ -18824,6 +18906,11 @@ const FIXES_LEVERS = [{
18824
18906
  label: "Repair /v1/responses bodies (normalizeResponses)",
18825
18907
  global: (configPath) => normalizeResponsesCommand({ configPath }),
18826
18908
  perModel: (current) => editNormalizeResponses(current)
18909
+ }, {
18910
+ value: "normalizeMessages",
18911
+ label: "Lift role:system out of /v1/messages (normalizeMessages)",
18912
+ global: (configPath) => normalizeMessagesCommand({ configPath }),
18913
+ perModel: (current) => editNormalizeMessages(current)
18827
18914
  }];
18828
18915
  async function runFixesLeverMenu(opts) {
18829
18916
  for (;;) {
@@ -18849,7 +18936,8 @@ async function globalFixesMenu(opts) {
18849
18936
  noteTitle: "Fixes",
18850
18937
  backLabel: "← Back",
18851
18938
  renderNote: () => {
18852
- return `normalizeResponses: ${readConfigFileRaw(configPath).normalizeResponses ?? "(default → auto)"}`;
18939
+ const raw = readConfigFileRaw(configPath);
18940
+ return [`normalizeResponses: ${raw.normalizeResponses ?? "(default → on)"}`, `normalizeMessages: ${raw.normalizeMessages ?? "(default → off)"}`].join("\n");
18853
18941
  },
18854
18942
  onLever: (lever) => lever.global(configPath)
18855
18943
  });
@@ -18860,7 +18948,7 @@ async function perModelFixesMenu(opts) {
18860
18948
  await runFixesLeverMenu({
18861
18949
  noteTitle: `Fixes for "${opts.modelKey}"`,
18862
18950
  backLabel: "← Back to override edit",
18863
- renderNote: () => `normalizeResponses: ${current.normalizeResponses ?? "(inherit)"}`,
18951
+ renderNote: () => [`normalizeResponses: ${current.normalizeResponses ?? "(inherit)"}`, `normalizeMessages: ${current.normalizeMessages ?? "(inherit)"}`].join("\n"),
18864
18952
  onLever: async (lever) => {
18865
18953
  const next = await lever.perModel(current);
18866
18954
  if (!overridesEqual(next, current)) {
@@ -18878,7 +18966,7 @@ async function fixesCommand(opts) {
18878
18966
  }
18879
18967
  //#endregion
18880
18968
  //#region src/commands/config/edit.ts
18881
- function nvsHint(value) {
18969
+ function toggleHint(value) {
18882
18970
  if (value === void 0) return "(inherit)";
18883
18971
  return value ? "on" : "off";
18884
18972
  }
@@ -18891,13 +18979,13 @@ function formatOverrideHint(override) {
18891
18979
  }
18892
18980
  if (override.sessionId) parts.push(`session: ${override.sessionId}`);
18893
18981
  if (override.cacheControl) parts.push(`cache: ${override.cacheControl}`);
18894
- if (override.normalizeVolatileSystem !== void 0) parts.push(`normalize: ${nvsHint(override.normalizeVolatileSystem)}`);
18895
- if (override.normalizeResponses) parts.push(`fix: ${override.normalizeResponses}`);
18982
+ if (override.normalizeVolatileSystem !== void 0) parts.push(`normalize: ${toggleHint(override.normalizeVolatileSystem)}`);
18983
+ if (override.normalizeResponses !== void 0) parts.push(`fix: ${toggleHint(override.normalizeResponses)}`);
18896
18984
  if (override.headers) parts.push(`${Object.keys(override.headers).length} header(s)`);
18897
18985
  return parts.join(", ") || "(empty)";
18898
18986
  }
18899
18987
  function formatCachingHint(current) {
18900
- return `cc ${current.cacheControl ?? "inherit"} · ttl ${current.cacheControlTtl ? describeTtl(current.cacheControlTtl) : "inherit"} · sid ${current.sessionId ?? "inherit"} · nvs ${nvsHint(current.normalizeVolatileSystem)}`;
18988
+ return `cc ${current.cacheControl ?? "inherit"} · ttl ${current.cacheControlTtl ? describeTtl(current.cacheControlTtl) : "inherit"} · sid ${current.sessionId ?? "inherit"} · nvs ${toggleHint(current.normalizeVolatileSystem)}`;
18901
18989
  }
18902
18990
  function formatFixesHint(current) {
18903
18991
  return `normalizeResponses: ${current.normalizeResponses ?? "inherit"}`;
@@ -19694,7 +19782,7 @@ async function runConfigMenu(client) {
19694
19782
  }
19695
19783
  //#endregion
19696
19784
  //#region src/version.ts
19697
- const version = "0.18.0";
19785
+ const version = "0.19.1";
19698
19786
  //#endregion
19699
19787
  //#region src/commands/doctor.ts
19700
19788
  const DEFAULT_TIMEOUT_MS = 3e3;
@@ -23656,15 +23744,98 @@ const injectSessionId = createMiddleware(async (c, next) => {
23656
23744
  await next();
23657
23745
  });
23658
23746
  //#endregion
23747
+ //#region src/proxy/utils/messages-system.ts
23748
+ /**
23749
+ * Whether to lift stray `role:"system"` items out of `messages`. The lift is
23750
+ * only valid on `/v1/messages` (where `system` is a top-level field) — never on
23751
+ * chat-completions (system belongs in `messages`) or responses — so it is gated
23752
+ * by endpoint regardless of the on/off setting.
23753
+ */
23754
+ function shouldNormalizeMessages(enabled, path) {
23755
+ return enabled && classifyEndpoint(path) === "messages";
23756
+ }
23757
+ function isPlainObject$1(value) {
23758
+ return value !== null && typeof value === "object" && !Array.isArray(value);
23759
+ }
23760
+ /** Flatten a message content value (string or block array) to plain text. */
23761
+ function contentToText$1(content) {
23762
+ if (typeof content === "string") return content;
23763
+ if (!Array.isArray(content)) return void 0;
23764
+ const parts = [];
23765
+ for (const block of content) {
23766
+ const text = isPlainObject$1(block) ? block.text : void 0;
23767
+ if (typeof text === "string") parts.push(text);
23768
+ }
23769
+ return parts.length > 0 ? parts.join("\n") : void 0;
23770
+ }
23771
+ /**
23772
+ * Append lifted system text to a top-level `system` value, preserving its form:
23773
+ * a block array stays an array (new `{type:"text"}` block), a string stays a
23774
+ * string, and a missing system starts as a plain string. A stray single-block
23775
+ * object (malformed but parseable) is wrapped as a one-element array so its
23776
+ * content is never silently discarded.
23777
+ */
23778
+ function appendSystemText(current, text) {
23779
+ if (Array.isArray(current)) return [...current, {
23780
+ type: "text",
23781
+ text
23782
+ }];
23783
+ if (typeof current === "string") return current.length > 0 ? `${current}\n\n${text}` : text;
23784
+ if (isPlainObject$1(current)) return [current, {
23785
+ type: "text",
23786
+ text
23787
+ }];
23788
+ return text;
23789
+ }
23790
+ /**
23791
+ * Lift every `role:"system"` item in `messages` into the top-level `system`
23792
+ * field. The Anthropic Messages API allows only `user`/`assistant` in
23793
+ * `messages` — a stray `role:"system"` (e.g. injected hook output like a
23794
+ * SessionStart payload) is rejected by strict Anthropic-format providers
23795
+ * (OpenRouter → GLM et al.) with a 400 on `messages[n].role`. System content
23796
+ * belongs in the top-level `system`, so its text is merged there and the item
23797
+ * is dropped from `messages`, which also preserves user/assistant alternation.
23798
+ * Items with no extractable text are dropped. Idempotent; returns whether the
23799
+ * body changed.
23800
+ */
23801
+ function liftSystemMessages(body) {
23802
+ const messages = body.messages;
23803
+ if (!Array.isArray(messages)) return false;
23804
+ let system = body.system;
23805
+ const next = [];
23806
+ let mutated = false;
23807
+ for (const raw of messages) {
23808
+ if (isPlainObject$1(raw) && raw.role === "system") {
23809
+ const text = contentToText$1(raw.content);
23810
+ if (text !== void 0) system = appendSystemText(system, text);
23811
+ mutated = true;
23812
+ continue;
23813
+ }
23814
+ next.push(raw);
23815
+ }
23816
+ if (mutated) {
23817
+ body.messages = next;
23818
+ if (system !== body.system) body.system = system;
23819
+ }
23820
+ return mutated;
23821
+ }
23822
+ //#endregion
23823
+ //#region src/proxy/middleware/normalize-messages.ts
23824
+ /** Lift stray role:"system" items from messages into top-level system (see liftSystemMessages); no-op for non-messages endpoints. */
23825
+ const normalizeMessagesMiddleware = createMiddleware(async (c, next) => {
23826
+ const parsedBody = c.var.parsedBody;
23827
+ if (parsedBody && shouldNormalizeMessages(c.var.resolvedConfig.normalizeMessages, c.req.path) && liftSystemMessages(parsedBody)) c.set("bodyMutated", true);
23828
+ await next();
23829
+ });
23830
+ //#endregion
23659
23831
  //#region src/proxy/utils/responses-input.ts
23660
23832
  /**
23661
- * Whether to normalize a Responses-api body. Mirrors the other tri-states:
23662
- * `auto` acts only on /v1/responses, `always` acts everywhere, `skip` never.
23833
+ * Whether to normalize a Responses-api body. The lift is only valid on
23834
+ * /v1/responses never on messages or chat-completions so it is gated by
23835
+ * endpoint regardless of the on/off setting.
23663
23836
  */
23664
- function shouldNormalizeResponses(mode, path) {
23665
- if (mode === "skip") return false;
23666
- if (mode === "always") return true;
23667
- return classifyEndpoint(path) === "responses";
23837
+ function shouldNormalizeResponses(enabled, path) {
23838
+ return enabled && classifyEndpoint(path) === "responses";
23668
23839
  }
23669
23840
  function isPlainObject(value) {
23670
23841
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -23934,16 +24105,15 @@ var SessionTracker = class {
23934
24105
  };
23935
24106
  //#endregion
23936
24107
  //#region src/proxy/observability/sinks.ts
23937
- const wrap = (code) => (s) => `\x1b[${code}m${s}\x1b[0m`;
23938
- const PAINT = {
23939
- HIT: wrap("32"),
23940
- PARTIAL: wrap("33"),
23941
- MISS: wrap("31"),
23942
- COLD: wrap("2"),
23943
- NOUSAGE: wrap("90")
24108
+ const STYLE = {
24109
+ HIT: "green",
24110
+ PARTIAL: "yellow",
24111
+ MISS: "red",
24112
+ COLD: "dim",
24113
+ NOUSAGE: "gray"
23944
24114
  };
23945
24115
  function colorizeLabel(label, useColor) {
23946
- return useColor ? PAINT[label](label) : label;
24116
+ return useColor ? styleText(STYLE[label], label) : label;
23947
24117
  }
23948
24118
  function formatLine(obs, useColor = false) {
23949
24119
  const { label, hitPct } = obs.outcome;
@@ -24070,6 +24240,7 @@ const injectChain = [
24070
24240
  parseBody,
24071
24241
  resolveConfig,
24072
24242
  injectProvider,
24243
+ normalizeMessagesMiddleware,
24073
24244
  injectCacheControl,
24074
24245
  normalizeVolatileSystemMiddleware,
24075
24246
  normalizeResponsesInputMiddleware,