proxitor 0.17.0 → 0.18.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.
- package/README.md +2 -0
- package/dist/cli.mjs +248 -11
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -197,6 +197,8 @@ 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.
|
|
201
|
+
|
|
200
202
|
**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.
|
|
201
203
|
|
|
202
204
|
**Config edits don't take effect.** They should — proxitor hot-reloads on save. If the file is invalid the proxy keeps the last valid config; `proxitor config validate` shows what was rejected.
|
package/dist/cli.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import { STATUS_CODES, createServer as createServer$1 } from "node:http";
|
|
|
14
14
|
import { Http2ServerRequest, constants } from "node:http2";
|
|
15
15
|
import { Readable } from "node:stream";
|
|
16
16
|
import { readFile, writeFile } from "node:fs/promises";
|
|
17
|
-
import { createHash } from "node:crypto";
|
|
17
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
18
18
|
//#region \0rolldown/runtime.js
|
|
19
19
|
var __defProp = Object.defineProperty;
|
|
20
20
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -12217,7 +12217,7 @@ const allowsEval = /* @__PURE__*/ cached(() => {
|
|
|
12217
12217
|
return false;
|
|
12218
12218
|
}
|
|
12219
12219
|
});
|
|
12220
|
-
function isPlainObject$
|
|
12220
|
+
function isPlainObject$3(o) {
|
|
12221
12221
|
if (isObject(o) === false) return false;
|
|
12222
12222
|
const ctor = o.constructor;
|
|
12223
12223
|
if (ctor === void 0) return true;
|
|
@@ -12228,7 +12228,7 @@ function isPlainObject$2(o) {
|
|
|
12228
12228
|
return true;
|
|
12229
12229
|
}
|
|
12230
12230
|
function shallowClone(o) {
|
|
12231
|
-
if (isPlainObject$
|
|
12231
|
+
if (isPlainObject$3(o)) return { ...o };
|
|
12232
12232
|
if (Array.isArray(o)) return [...o];
|
|
12233
12233
|
if (o instanceof Map) return new Map(o);
|
|
12234
12234
|
if (o instanceof Set) return new Set(o);
|
|
@@ -12311,7 +12311,7 @@ function omit(schema, mask) {
|
|
|
12311
12311
|
}));
|
|
12312
12312
|
}
|
|
12313
12313
|
function extend(schema, shape) {
|
|
12314
|
-
if (!isPlainObject$
|
|
12314
|
+
if (!isPlainObject$3(shape)) throw new Error("Invalid input to extend: expected a plain object");
|
|
12315
12315
|
const checks = schema._zod.def.checks;
|
|
12316
12316
|
if (checks && checks.length > 0) {
|
|
12317
12317
|
const existingShape = schema._zod.def.shape;
|
|
@@ -12327,7 +12327,7 @@ function extend(schema, shape) {
|
|
|
12327
12327
|
} }));
|
|
12328
12328
|
}
|
|
12329
12329
|
function safeExtend(schema, shape) {
|
|
12330
|
-
if (!isPlainObject$
|
|
12330
|
+
if (!isPlainObject$3(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
|
|
12331
12331
|
return clone(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
12332
12332
|
const _shape = {
|
|
12333
12333
|
...schema._zod.def.shape,
|
|
@@ -13862,7 +13862,7 @@ function mergeValues(a, b) {
|
|
|
13862
13862
|
valid: true,
|
|
13863
13863
|
data: a
|
|
13864
13864
|
};
|
|
13865
|
-
if (isPlainObject$
|
|
13865
|
+
if (isPlainObject$3(a) && isPlainObject$3(b)) {
|
|
13866
13866
|
const bKeys = Object.keys(b);
|
|
13867
13867
|
const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
|
|
13868
13868
|
const newObj = {
|
|
@@ -13938,7 +13938,7 @@ const $ZodRecord = /*@__PURE__*/ $constructor("$ZodRecord", (inst, def) => {
|
|
|
13938
13938
|
$ZodType.init(inst, def);
|
|
13939
13939
|
inst._zod.parse = (payload, ctx) => {
|
|
13940
13940
|
const input = payload.value;
|
|
13941
|
-
if (!isPlainObject$
|
|
13941
|
+
if (!isPlainObject$3(input)) {
|
|
13942
13942
|
payload.issues.push({
|
|
13943
13943
|
expected: "record",
|
|
13944
13944
|
code: "invalid_type",
|
|
@@ -16293,6 +16293,7 @@ const modelOverrideSchema = object({
|
|
|
16293
16293
|
cacheControlTtl: ttlSchema.optional(),
|
|
16294
16294
|
rewriteBlockTtl: triStateSchema.optional(),
|
|
16295
16295
|
sessionId: triStateSchema.optional(),
|
|
16296
|
+
normalizeResponses: triStateSchema.optional(),
|
|
16296
16297
|
normalizeVolatileSystem: boolean().optional()
|
|
16297
16298
|
}).strict();
|
|
16298
16299
|
const proxyConfigSchema = object({
|
|
@@ -16312,6 +16313,7 @@ const proxyConfigSchema = object({
|
|
|
16312
16313
|
cacheControlTtl: ttlSchema.optional(),
|
|
16313
16314
|
rewriteBlockTtl: triStateSchema.default("skip"),
|
|
16314
16315
|
sessionId: triStateSchema.default("auto"),
|
|
16316
|
+
normalizeResponses: triStateSchema.default("auto"),
|
|
16315
16317
|
normalizeVolatileSystem: boolean().default(false),
|
|
16316
16318
|
observability: observabilityConfigSchema,
|
|
16317
16319
|
modelOverrides: record(string$1().min(1), modelOverrideSchema).optional()
|
|
@@ -16488,6 +16490,7 @@ function resolveModelConfig(config, modelName) {
|
|
|
16488
16490
|
cacheControlTtl: config.cacheControlTtl,
|
|
16489
16491
|
rewriteBlockTtl: config.rewriteBlockTtl,
|
|
16490
16492
|
sessionId: config.sessionId,
|
|
16493
|
+
normalizeResponses: config.normalizeResponses,
|
|
16491
16494
|
normalizeVolatileSystem: config.normalizeVolatileSystem
|
|
16492
16495
|
};
|
|
16493
16496
|
if (!modelName || !config.modelOverrides) return result;
|
|
@@ -16521,6 +16524,7 @@ function applyOverride(result, override) {
|
|
|
16521
16524
|
if (override.cacheControlTtl !== void 0) result.cacheControlTtl = override.cacheControlTtl;
|
|
16522
16525
|
if (override.rewriteBlockTtl !== void 0) result.rewriteBlockTtl = override.rewriteBlockTtl;
|
|
16523
16526
|
if (override.sessionId !== void 0) result.sessionId = override.sessionId;
|
|
16527
|
+
if (override.normalizeResponses !== void 0) result.normalizeResponses = override.normalizeResponses;
|
|
16524
16528
|
if (override.normalizeVolatileSystem !== void 0) result.normalizeVolatileSystem = override.normalizeVolatileSystem;
|
|
16525
16529
|
}
|
|
16526
16530
|
/** Reject base URLs ending in /v1 — paths are forwarded as-is, so /v1 suffix causes doubled paths. */
|
|
@@ -16822,11 +16826,11 @@ function createDefu(merger) {
|
|
|
16822
16826
|
return (...arguments_) => arguments_.reduce((p, c) => _defu(p, c, "", merger), {});
|
|
16823
16827
|
}
|
|
16824
16828
|
const defu = createDefu();
|
|
16825
|
-
function isPlainObject(obj) {
|
|
16829
|
+
function isPlainObject$2(obj) {
|
|
16826
16830
|
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
16827
16831
|
}
|
|
16828
16832
|
function isLogObj(arg) {
|
|
16829
|
-
if (!isPlainObject(arg)) return false;
|
|
16833
|
+
if (!isPlainObject$2(arg)) return false;
|
|
16830
16834
|
if (!arg.message && !arg.args) return false;
|
|
16831
16835
|
if (arg.stack) return false;
|
|
16832
16836
|
return true;
|
|
@@ -18119,6 +18123,12 @@ const REWRITE_HINTS = {
|
|
|
18119
18123
|
always: "All models",
|
|
18120
18124
|
skip: "Leave client block ttl as-is (may mismatch root)"
|
|
18121
18125
|
};
|
|
18126
|
+
/** Shared hint texts for the normalize-responses tri-state — used in the Fixes menu + override editor. */
|
|
18127
|
+
const NORMALIZE_RESPONSES_HINTS = {
|
|
18128
|
+
auto: "/v1/responses only (default)",
|
|
18129
|
+
always: "All endpoints",
|
|
18130
|
+
skip: "Off — raw passthrough (OpenRouter may reject)"
|
|
18131
|
+
};
|
|
18122
18132
|
const NORMALIZE_HINTS = {
|
|
18123
18133
|
on: "Rewrite cch → stable prefix cache",
|
|
18124
18134
|
off: "Passthrough — rewrite nothing"
|
|
@@ -18223,6 +18233,12 @@ async function collectSessionTriState(currentSid) {
|
|
|
18223
18233
|
if (sid === "reset") return { sessionId: { remove: true } };
|
|
18224
18234
|
return { sessionId: { value: sid } };
|
|
18225
18235
|
}
|
|
18236
|
+
async function collectNormalizeResponsesTriState(currentNr) {
|
|
18237
|
+
const nr = await askTriState("normalizeResponses mode", currentNr, NORMALIZE_RESPONSES_HINTS, { removable: true });
|
|
18238
|
+
if (typeof nr === "symbol") return null;
|
|
18239
|
+
if (nr === "reset") return { normalizeResponses: { remove: true } };
|
|
18240
|
+
return { normalizeResponses: { value: nr } };
|
|
18241
|
+
}
|
|
18226
18242
|
/**
|
|
18227
18243
|
* Collects cacheControl mode, TTL, and rewriteBlockTtl.
|
|
18228
18244
|
* Mode-cancel aborts; TTL-cancel keeps existing TTL; rewrite-cancel keeps existing rewrite.
|
|
@@ -18670,6 +18686,13 @@ async function editSessionId(current) {
|
|
|
18670
18686
|
applyField(next, "sessionId", result.sessionId);
|
|
18671
18687
|
return next;
|
|
18672
18688
|
}
|
|
18689
|
+
async function editNormalizeResponses(current) {
|
|
18690
|
+
const result = await collectNormalizeResponsesTriState(current.normalizeResponses);
|
|
18691
|
+
if (result === null) return current;
|
|
18692
|
+
const next = { ...current };
|
|
18693
|
+
applyField(next, "normalizeResponses", result.normalizeResponses);
|
|
18694
|
+
return next;
|
|
18695
|
+
}
|
|
18673
18696
|
async function editCacheControl(current, configPath) {
|
|
18674
18697
|
const globalTtl = readGlobalTtl(configPath);
|
|
18675
18698
|
const result = await collectCacheTriState(current.cacheControl, current.cacheControlTtl, globalTtl, current.rewriteBlockTtl);
|
|
@@ -18777,6 +18800,83 @@ async function cachingCommand(opts) {
|
|
|
18777
18800
|
outro("Bye!");
|
|
18778
18801
|
}
|
|
18779
18802
|
//#endregion
|
|
18803
|
+
//#region src/commands/config/normalize-responses.ts
|
|
18804
|
+
async function normalizeResponsesCommand(opts) {
|
|
18805
|
+
const configPath = requireConfigPath(opts?.configPath);
|
|
18806
|
+
const raw = readConfigFileRaw(configPath).normalizeResponses;
|
|
18807
|
+
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}`);
|
|
18818
|
+
}
|
|
18819
|
+
//#endregion
|
|
18820
|
+
//#region src/commands/config/fixes-menu.ts
|
|
18821
|
+
/** Compatibility fixes for request bodies OpenRouter rejects. Mirrors caching-menu's lever table. */
|
|
18822
|
+
const FIXES_LEVERS = [{
|
|
18823
|
+
value: "normalizeResponses",
|
|
18824
|
+
label: "Repair /v1/responses bodies (normalizeResponses)",
|
|
18825
|
+
global: (configPath) => normalizeResponsesCommand({ configPath }),
|
|
18826
|
+
perModel: (current) => editNormalizeResponses(current)
|
|
18827
|
+
}];
|
|
18828
|
+
async function runFixesLeverMenu(opts) {
|
|
18829
|
+
for (;;) {
|
|
18830
|
+
note(opts.renderNote(), opts.noteTitle);
|
|
18831
|
+
const choice = await select({
|
|
18832
|
+
message: "Tune which fix?",
|
|
18833
|
+
options: [...FIXES_LEVERS.map((lever) => ({
|
|
18834
|
+
value: lever.value,
|
|
18835
|
+
label: lever.label
|
|
18836
|
+
})), {
|
|
18837
|
+
value: "back",
|
|
18838
|
+
label: opts.backLabel
|
|
18839
|
+
}]
|
|
18840
|
+
});
|
|
18841
|
+
if (isCancel(choice) || choice === "back") return;
|
|
18842
|
+
const chosen = FIXES_LEVERS.find((l) => l.value === choice);
|
|
18843
|
+
if (chosen) await opts.onLever(chosen);
|
|
18844
|
+
}
|
|
18845
|
+
}
|
|
18846
|
+
async function globalFixesMenu(opts) {
|
|
18847
|
+
const configPath = requireConfigPath(opts?.configPath);
|
|
18848
|
+
await runFixesLeverMenu({
|
|
18849
|
+
noteTitle: "Fixes",
|
|
18850
|
+
backLabel: "← Back",
|
|
18851
|
+
renderNote: () => {
|
|
18852
|
+
return `normalizeResponses: ${readConfigFileRaw(configPath).normalizeResponses ?? "(default → auto)"}`;
|
|
18853
|
+
},
|
|
18854
|
+
onLever: (lever) => lever.global(configPath)
|
|
18855
|
+
});
|
|
18856
|
+
}
|
|
18857
|
+
/** Self-persists each lever; returns the latest override. */
|
|
18858
|
+
async function perModelFixesMenu(opts) {
|
|
18859
|
+
let current = opts.current;
|
|
18860
|
+
await runFixesLeverMenu({
|
|
18861
|
+
noteTitle: `Fixes for "${opts.modelKey}"`,
|
|
18862
|
+
backLabel: "← Back to override edit",
|
|
18863
|
+
renderNote: () => `normalizeResponses: ${current.normalizeResponses ?? "(inherit)"}`,
|
|
18864
|
+
onLever: async (lever) => {
|
|
18865
|
+
const next = await lever.perModel(current);
|
|
18866
|
+
if (!overridesEqual(next, current)) {
|
|
18867
|
+
setModelOverride(opts.configPath, opts.modelKey, next);
|
|
18868
|
+
current = next;
|
|
18869
|
+
}
|
|
18870
|
+
}
|
|
18871
|
+
});
|
|
18872
|
+
return current;
|
|
18873
|
+
}
|
|
18874
|
+
async function fixesCommand(opts) {
|
|
18875
|
+
intro("Proxitor · Fixes");
|
|
18876
|
+
await globalFixesMenu(opts);
|
|
18877
|
+
outro("Bye!");
|
|
18878
|
+
}
|
|
18879
|
+
//#endregion
|
|
18780
18880
|
//#region src/commands/config/edit.ts
|
|
18781
18881
|
function nvsHint(value) {
|
|
18782
18882
|
if (value === void 0) return "(inherit)";
|
|
@@ -18792,12 +18892,16 @@ function formatOverrideHint(override) {
|
|
|
18792
18892
|
if (override.sessionId) parts.push(`session: ${override.sessionId}`);
|
|
18793
18893
|
if (override.cacheControl) parts.push(`cache: ${override.cacheControl}`);
|
|
18794
18894
|
if (override.normalizeVolatileSystem !== void 0) parts.push(`normalize: ${nvsHint(override.normalizeVolatileSystem)}`);
|
|
18895
|
+
if (override.normalizeResponses) parts.push(`fix: ${override.normalizeResponses}`);
|
|
18795
18896
|
if (override.headers) parts.push(`${Object.keys(override.headers).length} header(s)`);
|
|
18796
18897
|
return parts.join(", ") || "(empty)";
|
|
18797
18898
|
}
|
|
18798
18899
|
function formatCachingHint(current) {
|
|
18799
18900
|
return `cc ${current.cacheControl ?? "inherit"} · ttl ${current.cacheControlTtl ? describeTtl(current.cacheControlTtl) : "inherit"} · sid ${current.sessionId ?? "inherit"} · nvs ${nvsHint(current.normalizeVolatileSystem)}`;
|
|
18800
18901
|
}
|
|
18902
|
+
function formatFixesHint(current) {
|
|
18903
|
+
return `normalizeResponses: ${current.normalizeResponses ?? "inherit"}`;
|
|
18904
|
+
}
|
|
18801
18905
|
async function editProvider(modelKey, current, client) {
|
|
18802
18906
|
const isPattern = modelKey.includes("*");
|
|
18803
18907
|
const mode = await selectRoutingMode("Routing mode");
|
|
@@ -18851,6 +18955,11 @@ async function editOverrideCommand(client, configPath) {
|
|
|
18851
18955
|
label: "💾 Caching",
|
|
18852
18956
|
hint: formatCachingHint(current)
|
|
18853
18957
|
},
|
|
18958
|
+
{
|
|
18959
|
+
value: "fixes",
|
|
18960
|
+
label: "🛠 Fixes",
|
|
18961
|
+
hint: formatFixesHint(current)
|
|
18962
|
+
},
|
|
18854
18963
|
{
|
|
18855
18964
|
value: "done",
|
|
18856
18965
|
label: "✓ Done"
|
|
@@ -18866,6 +18975,14 @@ async function editOverrideCommand(client, configPath) {
|
|
|
18866
18975
|
});
|
|
18867
18976
|
continue;
|
|
18868
18977
|
}
|
|
18978
|
+
if (field === "fixes") {
|
|
18979
|
+
current = await perModelFixesMenu({
|
|
18980
|
+
modelKey,
|
|
18981
|
+
current,
|
|
18982
|
+
configPath: resolvedConfigPath
|
|
18983
|
+
});
|
|
18984
|
+
continue;
|
|
18985
|
+
}
|
|
18869
18986
|
const before = current;
|
|
18870
18987
|
current = await editProvider(modelKey, current, client);
|
|
18871
18988
|
if (!overridesEqual(current, before)) setModelOverride(resolvedConfigPath, modelKey, current);
|
|
@@ -19493,6 +19610,10 @@ async function runConfigMenu(client) {
|
|
|
19493
19610
|
value: "caching",
|
|
19494
19611
|
label: "💾 Caching"
|
|
19495
19612
|
},
|
|
19613
|
+
{
|
|
19614
|
+
value: "fixes",
|
|
19615
|
+
label: "🛠 Fixes"
|
|
19616
|
+
},
|
|
19496
19617
|
{
|
|
19497
19618
|
value: "_sep2",
|
|
19498
19619
|
label: "── Model Overrides ──",
|
|
@@ -19547,6 +19668,9 @@ async function runConfigMenu(client) {
|
|
|
19547
19668
|
case "caching":
|
|
19548
19669
|
await globalCachingMenu();
|
|
19549
19670
|
break;
|
|
19671
|
+
case "fixes":
|
|
19672
|
+
await globalFixesMenu();
|
|
19673
|
+
break;
|
|
19550
19674
|
case "add":
|
|
19551
19675
|
await addOverrideCommand({ client });
|
|
19552
19676
|
break;
|
|
@@ -19570,7 +19694,7 @@ async function runConfigMenu(client) {
|
|
|
19570
19694
|
}
|
|
19571
19695
|
//#endregion
|
|
19572
19696
|
//#region src/version.ts
|
|
19573
|
-
const version = "0.
|
|
19697
|
+
const version = "0.18.0";
|
|
19574
19698
|
//#endregion
|
|
19575
19699
|
//#region src/commands/doctor.ts
|
|
19576
19700
|
const DEFAULT_TIMEOUT_MS = 3e3;
|
|
@@ -23426,7 +23550,12 @@ function rewriteBlockTtls(body, ttl, isAnthropic) {
|
|
|
23426
23550
|
visitBlocks(body.system);
|
|
23427
23551
|
visitBlocks(body.tools);
|
|
23428
23552
|
const messages = body.messages;
|
|
23429
|
-
for (const m of messages ?? [])
|
|
23553
|
+
for (const m of messages ?? []) {
|
|
23554
|
+
if (m === null || typeof m !== "object" || Array.isArray(m)) continue;
|
|
23555
|
+
rewriteNode(m);
|
|
23556
|
+
visitBlocks(m.content);
|
|
23557
|
+
visitBlocks(m.tool_calls);
|
|
23558
|
+
}
|
|
23430
23559
|
return mutated;
|
|
23431
23560
|
}
|
|
23432
23561
|
//#endregion
|
|
@@ -23527,6 +23656,105 @@ const injectSessionId = createMiddleware(async (c, next) => {
|
|
|
23527
23656
|
await next();
|
|
23528
23657
|
});
|
|
23529
23658
|
//#endregion
|
|
23659
|
+
//#region src/proxy/utils/responses-input.ts
|
|
23660
|
+
/**
|
|
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.
|
|
23663
|
+
*/
|
|
23664
|
+
function shouldNormalizeResponses(mode, path) {
|
|
23665
|
+
if (mode === "skip") return false;
|
|
23666
|
+
if (mode === "always") return true;
|
|
23667
|
+
return classifyEndpoint(path) === "responses";
|
|
23668
|
+
}
|
|
23669
|
+
function isPlainObject(value) {
|
|
23670
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
23671
|
+
}
|
|
23672
|
+
/** Flatten a Responses content value (string or block array) to plain text. */
|
|
23673
|
+
function contentToText(content) {
|
|
23674
|
+
if (typeof content === "string") return content;
|
|
23675
|
+
if (!Array.isArray(content)) return void 0;
|
|
23676
|
+
const parts = [];
|
|
23677
|
+
for (const block of content) {
|
|
23678
|
+
const text = isPlainObject(block) ? block.text : void 0;
|
|
23679
|
+
if (typeof text === "string") parts.push(text);
|
|
23680
|
+
}
|
|
23681
|
+
return parts.length > 0 ? parts.join("\n") : void 0;
|
|
23682
|
+
}
|
|
23683
|
+
function appendInstructions(current, text) {
|
|
23684
|
+
return typeof current === "string" ? `${current}\n\n${text}` : text;
|
|
23685
|
+
}
|
|
23686
|
+
/** Tag a role-bearing message item and complete assistant-history metadata. */
|
|
23687
|
+
function tagMessageItem(obj) {
|
|
23688
|
+
let changed = false;
|
|
23689
|
+
if (typeof obj.role === "string" && obj.type === void 0) {
|
|
23690
|
+
obj.type = "message";
|
|
23691
|
+
changed = true;
|
|
23692
|
+
}
|
|
23693
|
+
if (obj.type === "message" && obj.role === "assistant") {
|
|
23694
|
+
if (obj.id === void 0) {
|
|
23695
|
+
obj.id = `msg_${randomUUID().slice(0, 12)}`;
|
|
23696
|
+
changed = true;
|
|
23697
|
+
}
|
|
23698
|
+
if (obj.status === void 0) {
|
|
23699
|
+
obj.status = "completed";
|
|
23700
|
+
changed = true;
|
|
23701
|
+
}
|
|
23702
|
+
}
|
|
23703
|
+
return changed;
|
|
23704
|
+
}
|
|
23705
|
+
/**
|
|
23706
|
+
* Lift a `role: "system"` item: OpenRouter Responses has no system role in
|
|
23707
|
+
* `input`, so its text moves to `instructions`. Items with no extractable text
|
|
23708
|
+
* (image-only/empty) can't be represented there and are dropped — keeping them
|
|
23709
|
+
* as `role: "system"` would reproduce the rejection this normalizer exists to fix.
|
|
23710
|
+
*/
|
|
23711
|
+
function handleSystemItem(obj, state) {
|
|
23712
|
+
const text = contentToText(obj.content);
|
|
23713
|
+
if (text !== void 0) state.instructions = appendInstructions(state.instructions, text);
|
|
23714
|
+
}
|
|
23715
|
+
/**
|
|
23716
|
+
* Make a Responses body satisfy OpenRouter's `input` schema. Each item is a
|
|
23717
|
+
* `type`-discriminated union, so items missing `type` (OpenAI infers "message")
|
|
23718
|
+
* are rejected as `invalid_prompt`. Also relocates `role: "system"` items to
|
|
23719
|
+
* `instructions` and synthesizes the `id`/`status` assistant history requires.
|
|
23720
|
+
* Idempotent; returns whether the body changed.
|
|
23721
|
+
*/
|
|
23722
|
+
function normalizeResponsesInput(body) {
|
|
23723
|
+
const input = body.input;
|
|
23724
|
+
if (!Array.isArray(input)) return false;
|
|
23725
|
+
let mutated = false;
|
|
23726
|
+
const state = {
|
|
23727
|
+
instructions: body.instructions,
|
|
23728
|
+
next: []
|
|
23729
|
+
};
|
|
23730
|
+
for (const raw of input) {
|
|
23731
|
+
if (!isPlainObject(raw)) {
|
|
23732
|
+
state.next.push(raw);
|
|
23733
|
+
continue;
|
|
23734
|
+
}
|
|
23735
|
+
if (raw.role === "system") {
|
|
23736
|
+
handleSystemItem(raw, state);
|
|
23737
|
+
mutated = true;
|
|
23738
|
+
continue;
|
|
23739
|
+
}
|
|
23740
|
+
if (tagMessageItem(raw)) mutated = true;
|
|
23741
|
+
state.next.push(raw);
|
|
23742
|
+
}
|
|
23743
|
+
if (mutated) {
|
|
23744
|
+
body.input = state.next;
|
|
23745
|
+
if (state.instructions !== void 0) body.instructions = state.instructions;
|
|
23746
|
+
}
|
|
23747
|
+
return mutated;
|
|
23748
|
+
}
|
|
23749
|
+
//#endregion
|
|
23750
|
+
//#region src/proxy/middleware/normalize-responses-input.ts
|
|
23751
|
+
/** Normalize Responses-API input for OpenRouter's schema (see normalizeResponsesInput); no-op for chat/messages. */
|
|
23752
|
+
const normalizeResponsesInputMiddleware = createMiddleware(async (c, next) => {
|
|
23753
|
+
const parsedBody = c.var.parsedBody;
|
|
23754
|
+
if (parsedBody && shouldNormalizeResponses(c.var.resolvedConfig.normalizeResponses, c.req.path) && normalizeResponsesInput(parsedBody)) c.set("bodyMutated", true);
|
|
23755
|
+
await next();
|
|
23756
|
+
});
|
|
23757
|
+
//#endregion
|
|
23530
23758
|
//#region src/proxy/middleware/normalize-volatile-system.ts
|
|
23531
23759
|
/** Normalize Claude Code's per-request volatile hashes in system[0] to constants; they drift every turn and break prefix-cache for non-Anthropic providers:
|
|
23532
23760
|
* - `cch=…` per-turn hash
|
|
@@ -23844,6 +24072,7 @@ const injectChain = [
|
|
|
23844
24072
|
injectProvider,
|
|
23845
24073
|
injectCacheControl,
|
|
23846
24074
|
normalizeVolatileSystemMiddleware,
|
|
24075
|
+
normalizeResponsesInputMiddleware,
|
|
23847
24076
|
injectSessionId
|
|
23848
24077
|
];
|
|
23849
24078
|
function createProxyServer(source, onReady) {
|
|
@@ -24075,6 +24304,14 @@ const configCli = (0, import_cjs.subcommands)({
|
|
|
24075
24304
|
await cachingCommand({ configPath: args.configPath });
|
|
24076
24305
|
}
|
|
24077
24306
|
}),
|
|
24307
|
+
fixes: (0, import_cjs.command)({
|
|
24308
|
+
name: "fixes",
|
|
24309
|
+
description: "Tune compatibility fixes (interactive)",
|
|
24310
|
+
args: { ...configArgs },
|
|
24311
|
+
handler: async (args) => {
|
|
24312
|
+
await fixesCommand({ configPath: args.configPath });
|
|
24313
|
+
}
|
|
24314
|
+
}),
|
|
24078
24315
|
edit: (0, import_cjs.command)({
|
|
24079
24316
|
name: "edit",
|
|
24080
24317
|
description: "Edit an existing model override (interactive)",
|