proxitor 0.8.0 → 0.9.0-beta.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 +57 -4
- package/dist/add.mjs +26 -26
- package/dist/add.mjs.map +1 -1
- package/dist/browse.mjs +20 -20
- package/dist/browse.mjs.map +1 -1
- package/dist/cli.mjs +482 -233
- package/dist/cli.mjs.map +1 -1
- package/dist/config.mjs.map +1 -1
- package/dist/config2.mjs +5 -5
- package/dist/config2.mjs.map +1 -1
- package/dist/dist.mjs +558 -548
- package/dist/dist.mjs.map +1 -1
- package/dist/dist2.mjs.map +1 -1
- package/dist/edit.mjs +17 -17
- package/dist/edit.mjs.map +1 -1
- package/dist/list.mjs +4 -4
- package/dist/list.mjs.map +1 -1
- package/dist/prompt.mjs +9 -9
- package/dist/prompt.mjs.map +1 -1
- package/dist/providers.mjs +15 -15
- package/dist/providers.mjs.map +1 -1
- package/dist/remove.mjs +10 -10
- package/dist/remove.mjs.map +1 -1
- package/dist/validate.mjs +8 -8
- package/dist/validate.mjs.map +1 -1
- package/dist/wizard.mjs +31 -31
- package/dist/wizard.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import
|
|
3
|
+
import process$1 from "node:process";
|
|
4
4
|
import os, { homedir } from "node:os";
|
|
5
5
|
import * as tty$1 from "node:tty";
|
|
6
6
|
import tty from "node:tty";
|
|
@@ -10,6 +10,7 @@ import { join, resolve, sep } from "node:path";
|
|
|
10
10
|
import { STATUS_CODES, createServer } from "node:http";
|
|
11
11
|
import { Http2ServerRequest, constants } from "node:http2";
|
|
12
12
|
import { Readable } from "node:stream";
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
13
14
|
//#region \0rolldown/runtime.js
|
|
14
15
|
var __defProp$1 = Object.defineProperty;
|
|
15
16
|
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
@@ -207,7 +208,7 @@ var init_ansi_styles = __esmMin((() => {
|
|
|
207
208
|
}));
|
|
208
209
|
//#endregion
|
|
209
210
|
//#region node_modules/.pnpm/chalk@5.6.2/node_modules/chalk/source/vendor/supports-color/index.js
|
|
210
|
-
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args :
|
|
211
|
+
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : process$1.argv) {
|
|
211
212
|
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
212
213
|
const position = argv.indexOf(prefix + flag);
|
|
213
214
|
const terminatorPosition = argv.indexOf("--");
|
|
@@ -242,7 +243,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
|
242
243
|
if (haveStream && !streamIsTTY && forceColor === void 0) return 0;
|
|
243
244
|
const min = forceColor || 0;
|
|
244
245
|
if (env$1.TERM === "dumb") return min;
|
|
245
|
-
if (
|
|
246
|
+
if (process$1.platform === "win32") {
|
|
246
247
|
const osRelease = os.release().split(".");
|
|
247
248
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
248
249
|
return 1;
|
|
@@ -287,7 +288,7 @@ function createSupportsColor(stream, options = {}) {
|
|
|
287
288
|
}
|
|
288
289
|
var env$1, flagForceColor, supportsColor;
|
|
289
290
|
var init_supports_color = __esmMin((() => {
|
|
290
|
-
({env: env$1} =
|
|
291
|
+
({env: env$1} = process$1);
|
|
291
292
|
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) flagForceColor = 0;
|
|
292
293
|
else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) flagForceColor = 1;
|
|
293
294
|
supportsColor = {
|
|
@@ -7001,6 +7002,7 @@ const string$2 = (params) => {
|
|
|
7001
7002
|
const integer = /^-?\d+$/;
|
|
7002
7003
|
const number$2 = /^-?\d+(?:\.\d+)?$/;
|
|
7003
7004
|
const boolean$1 = /^(?:true|false)$/i;
|
|
7005
|
+
const _null$2 = /^null$/i;
|
|
7004
7006
|
const lowercase = /^[^A-Z]*$/;
|
|
7005
7007
|
const uppercase = /^[^a-z]*$/;
|
|
7006
7008
|
//#endregion
|
|
@@ -7813,6 +7815,22 @@ const $ZodBoolean = /*@__PURE__*/ $constructor("$ZodBoolean", (inst, def) => {
|
|
|
7813
7815
|
return payload;
|
|
7814
7816
|
};
|
|
7815
7817
|
});
|
|
7818
|
+
const $ZodNull = /*@__PURE__*/ $constructor("$ZodNull", (inst, def) => {
|
|
7819
|
+
$ZodType.init(inst, def);
|
|
7820
|
+
inst._zod.pattern = _null$2;
|
|
7821
|
+
inst._zod.values = new Set([null]);
|
|
7822
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
7823
|
+
const input = payload.value;
|
|
7824
|
+
if (input === null) return payload;
|
|
7825
|
+
payload.issues.push({
|
|
7826
|
+
expected: "null",
|
|
7827
|
+
code: "invalid_type",
|
|
7828
|
+
input,
|
|
7829
|
+
inst
|
|
7830
|
+
});
|
|
7831
|
+
return payload;
|
|
7832
|
+
};
|
|
7833
|
+
});
|
|
7816
7834
|
const $ZodUnknown = /*@__PURE__*/ $constructor("$ZodUnknown", (inst, def) => {
|
|
7817
7835
|
$ZodType.init(inst, def);
|
|
7818
7836
|
inst._zod.parse = (payload) => payload;
|
|
@@ -8961,6 +8979,13 @@ function _boolean(Class, params) {
|
|
|
8961
8979
|
});
|
|
8962
8980
|
}
|
|
8963
8981
|
// @__NO_SIDE_EFFECTS__
|
|
8982
|
+
function _null$1(Class, params) {
|
|
8983
|
+
return new Class({
|
|
8984
|
+
type: "null",
|
|
8985
|
+
...normalizeParams(params)
|
|
8986
|
+
});
|
|
8987
|
+
}
|
|
8988
|
+
// @__NO_SIDE_EFFECTS__
|
|
8964
8989
|
function _unknown(Class) {
|
|
8965
8990
|
return new Class({ type: "unknown" });
|
|
8966
8991
|
}
|
|
@@ -9183,7 +9208,7 @@ function initializeContext(params) {
|
|
|
9183
9208
|
external: params?.external ?? void 0
|
|
9184
9209
|
};
|
|
9185
9210
|
}
|
|
9186
|
-
function process$
|
|
9211
|
+
function process$2(schema, ctx, _params = {
|
|
9187
9212
|
path: [],
|
|
9188
9213
|
schemaPath: []
|
|
9189
9214
|
}) {
|
|
@@ -9220,7 +9245,7 @@ function process$1(schema, ctx, _params = {
|
|
|
9220
9245
|
const parent = schema._zod.parent;
|
|
9221
9246
|
if (parent) {
|
|
9222
9247
|
if (!result.ref) result.ref = parent;
|
|
9223
|
-
process$
|
|
9248
|
+
process$2(parent, ctx, params);
|
|
9224
9249
|
ctx.seen.get(parent).isParent = true;
|
|
9225
9250
|
}
|
|
9226
9251
|
}
|
|
@@ -9440,7 +9465,7 @@ const createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
|
|
|
9440
9465
|
...params,
|
|
9441
9466
|
processors
|
|
9442
9467
|
});
|
|
9443
|
-
process$
|
|
9468
|
+
process$2(schema, ctx);
|
|
9444
9469
|
extractDefs(ctx, schema);
|
|
9445
9470
|
return finalize(ctx, schema);
|
|
9446
9471
|
};
|
|
@@ -9452,7 +9477,7 @@ const createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params)
|
|
|
9452
9477
|
io,
|
|
9453
9478
|
processors
|
|
9454
9479
|
});
|
|
9455
|
-
process$
|
|
9480
|
+
process$2(schema, ctx);
|
|
9456
9481
|
extractDefs(ctx, schema);
|
|
9457
9482
|
return finalize(ctx, schema);
|
|
9458
9483
|
};
|
|
@@ -9509,6 +9534,13 @@ const numberProcessor = (schema, ctx, _json, _params) => {
|
|
|
9509
9534
|
const booleanProcessor = (_schema, _ctx, json, _params) => {
|
|
9510
9535
|
json.type = "boolean";
|
|
9511
9536
|
};
|
|
9537
|
+
const nullProcessor = (_schema, ctx, json, _params) => {
|
|
9538
|
+
if (ctx.target === "openapi-3.0") {
|
|
9539
|
+
json.type = "string";
|
|
9540
|
+
json.nullable = true;
|
|
9541
|
+
json.enum = [null];
|
|
9542
|
+
} else json.type = "null";
|
|
9543
|
+
};
|
|
9512
9544
|
const neverProcessor = (_schema, _ctx, json, _params) => {
|
|
9513
9545
|
json.not = {};
|
|
9514
9546
|
};
|
|
@@ -9532,7 +9564,7 @@ const arrayProcessor = (schema, ctx, _json, params) => {
|
|
|
9532
9564
|
if (typeof minimum === "number") json.minItems = minimum;
|
|
9533
9565
|
if (typeof maximum === "number") json.maxItems = maximum;
|
|
9534
9566
|
json.type = "array";
|
|
9535
|
-
json.items = process$
|
|
9567
|
+
json.items = process$2(def.element, ctx, {
|
|
9536
9568
|
...params,
|
|
9537
9569
|
path: [...params.path, "items"]
|
|
9538
9570
|
});
|
|
@@ -9543,7 +9575,7 @@ const objectProcessor = (schema, ctx, _json, params) => {
|
|
|
9543
9575
|
json.type = "object";
|
|
9544
9576
|
json.properties = {};
|
|
9545
9577
|
const shape = def.shape;
|
|
9546
|
-
for (const key in shape) json.properties[key] = process$
|
|
9578
|
+
for (const key in shape) json.properties[key] = process$2(shape[key], ctx, {
|
|
9547
9579
|
...params,
|
|
9548
9580
|
path: [
|
|
9549
9581
|
...params.path,
|
|
@@ -9561,7 +9593,7 @@ const objectProcessor = (schema, ctx, _json, params) => {
|
|
|
9561
9593
|
if (def.catchall?._zod.def.type === "never") json.additionalProperties = false;
|
|
9562
9594
|
else if (!def.catchall) {
|
|
9563
9595
|
if (ctx.io === "output") json.additionalProperties = false;
|
|
9564
|
-
} else if (def.catchall) json.additionalProperties = process$
|
|
9596
|
+
} else if (def.catchall) json.additionalProperties = process$2(def.catchall, ctx, {
|
|
9565
9597
|
...params,
|
|
9566
9598
|
path: [...params.path, "additionalProperties"]
|
|
9567
9599
|
});
|
|
@@ -9569,7 +9601,7 @@ const objectProcessor = (schema, ctx, _json, params) => {
|
|
|
9569
9601
|
const unionProcessor = (schema, ctx, json, params) => {
|
|
9570
9602
|
const def = schema._zod.def;
|
|
9571
9603
|
const isExclusive = def.inclusive === false;
|
|
9572
|
-
const options = def.options.map((x, i) => process$
|
|
9604
|
+
const options = def.options.map((x, i) => process$2(x, ctx, {
|
|
9573
9605
|
...params,
|
|
9574
9606
|
path: [
|
|
9575
9607
|
...params.path,
|
|
@@ -9582,7 +9614,7 @@ const unionProcessor = (schema, ctx, json, params) => {
|
|
|
9582
9614
|
};
|
|
9583
9615
|
const intersectionProcessor = (schema, ctx, json, params) => {
|
|
9584
9616
|
const def = schema._zod.def;
|
|
9585
|
-
const a = process$
|
|
9617
|
+
const a = process$2(def.left, ctx, {
|
|
9586
9618
|
...params,
|
|
9587
9619
|
path: [
|
|
9588
9620
|
...params.path,
|
|
@@ -9590,7 +9622,7 @@ const intersectionProcessor = (schema, ctx, json, params) => {
|
|
|
9590
9622
|
0
|
|
9591
9623
|
]
|
|
9592
9624
|
});
|
|
9593
|
-
const b = process$
|
|
9625
|
+
const b = process$2(def.right, ctx, {
|
|
9594
9626
|
...params,
|
|
9595
9627
|
path: [
|
|
9596
9628
|
...params.path,
|
|
@@ -9608,7 +9640,7 @@ const recordProcessor = (schema, ctx, _json, params) => {
|
|
|
9608
9640
|
const keyType = def.keyType;
|
|
9609
9641
|
const patterns = keyType._zod.bag?.patterns;
|
|
9610
9642
|
if (def.mode === "loose" && patterns && patterns.size > 0) {
|
|
9611
|
-
const valueSchema = process$
|
|
9643
|
+
const valueSchema = process$2(def.valueType, ctx, {
|
|
9612
9644
|
...params,
|
|
9613
9645
|
path: [
|
|
9614
9646
|
...params.path,
|
|
@@ -9619,11 +9651,11 @@ const recordProcessor = (schema, ctx, _json, params) => {
|
|
|
9619
9651
|
json.patternProperties = {};
|
|
9620
9652
|
for (const pattern of patterns) json.patternProperties[pattern.source] = valueSchema;
|
|
9621
9653
|
} else {
|
|
9622
|
-
if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") json.propertyNames = process$
|
|
9654
|
+
if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") json.propertyNames = process$2(def.keyType, ctx, {
|
|
9623
9655
|
...params,
|
|
9624
9656
|
path: [...params.path, "propertyNames"]
|
|
9625
9657
|
});
|
|
9626
|
-
json.additionalProperties = process$
|
|
9658
|
+
json.additionalProperties = process$2(def.valueType, ctx, {
|
|
9627
9659
|
...params,
|
|
9628
9660
|
path: [...params.path, "additionalProperties"]
|
|
9629
9661
|
});
|
|
@@ -9636,7 +9668,7 @@ const recordProcessor = (schema, ctx, _json, params) => {
|
|
|
9636
9668
|
};
|
|
9637
9669
|
const nullableProcessor = (schema, ctx, json, params) => {
|
|
9638
9670
|
const def = schema._zod.def;
|
|
9639
|
-
const inner = process$
|
|
9671
|
+
const inner = process$2(def.innerType, ctx, params);
|
|
9640
9672
|
const seen = ctx.seen.get(schema);
|
|
9641
9673
|
if (ctx.target === "openapi-3.0") {
|
|
9642
9674
|
seen.ref = def.innerType;
|
|
@@ -9645,27 +9677,27 @@ const nullableProcessor = (schema, ctx, json, params) => {
|
|
|
9645
9677
|
};
|
|
9646
9678
|
const nonoptionalProcessor = (schema, ctx, _json, params) => {
|
|
9647
9679
|
const def = schema._zod.def;
|
|
9648
|
-
process$
|
|
9680
|
+
process$2(def.innerType, ctx, params);
|
|
9649
9681
|
const seen = ctx.seen.get(schema);
|
|
9650
9682
|
seen.ref = def.innerType;
|
|
9651
9683
|
};
|
|
9652
9684
|
const defaultProcessor = (schema, ctx, json, params) => {
|
|
9653
9685
|
const def = schema._zod.def;
|
|
9654
|
-
process$
|
|
9686
|
+
process$2(def.innerType, ctx, params);
|
|
9655
9687
|
const seen = ctx.seen.get(schema);
|
|
9656
9688
|
seen.ref = def.innerType;
|
|
9657
9689
|
json.default = JSON.parse(JSON.stringify(def.defaultValue));
|
|
9658
9690
|
};
|
|
9659
9691
|
const prefaultProcessor = (schema, ctx, json, params) => {
|
|
9660
9692
|
const def = schema._zod.def;
|
|
9661
|
-
process$
|
|
9693
|
+
process$2(def.innerType, ctx, params);
|
|
9662
9694
|
const seen = ctx.seen.get(schema);
|
|
9663
9695
|
seen.ref = def.innerType;
|
|
9664
9696
|
if (ctx.io === "input") json._prefault = JSON.parse(JSON.stringify(def.defaultValue));
|
|
9665
9697
|
};
|
|
9666
9698
|
const catchProcessor = (schema, ctx, json, params) => {
|
|
9667
9699
|
const def = schema._zod.def;
|
|
9668
|
-
process$
|
|
9700
|
+
process$2(def.innerType, ctx, params);
|
|
9669
9701
|
const seen = ctx.seen.get(schema);
|
|
9670
9702
|
seen.ref = def.innerType;
|
|
9671
9703
|
let catchValue;
|
|
@@ -9680,20 +9712,20 @@ const pipeProcessor = (schema, ctx, _json, params) => {
|
|
|
9680
9712
|
const def = schema._zod.def;
|
|
9681
9713
|
const inIsTransform = def.in._zod.traits.has("$ZodTransform");
|
|
9682
9714
|
const innerType = ctx.io === "input" ? inIsTransform ? def.out : def.in : def.out;
|
|
9683
|
-
process$
|
|
9715
|
+
process$2(innerType, ctx, params);
|
|
9684
9716
|
const seen = ctx.seen.get(schema);
|
|
9685
9717
|
seen.ref = innerType;
|
|
9686
9718
|
};
|
|
9687
9719
|
const readonlyProcessor = (schema, ctx, json, params) => {
|
|
9688
9720
|
const def = schema._zod.def;
|
|
9689
|
-
process$
|
|
9721
|
+
process$2(def.innerType, ctx, params);
|
|
9690
9722
|
const seen = ctx.seen.get(schema);
|
|
9691
9723
|
seen.ref = def.innerType;
|
|
9692
9724
|
json.readOnly = true;
|
|
9693
9725
|
};
|
|
9694
9726
|
const optionalProcessor = (schema, ctx, _json, params) => {
|
|
9695
9727
|
const def = schema._zod.def;
|
|
9696
|
-
process$
|
|
9728
|
+
process$2(def.innerType, ctx, params);
|
|
9697
9729
|
const seen = ctx.seen.get(schema);
|
|
9698
9730
|
seen.ref = def.innerType;
|
|
9699
9731
|
};
|
|
@@ -10178,6 +10210,14 @@ const ZodBoolean = /*@__PURE__*/ $constructor("ZodBoolean", (inst, def) => {
|
|
|
10178
10210
|
function boolean(params) {
|
|
10179
10211
|
return /* @__PURE__ */ _boolean(ZodBoolean, params);
|
|
10180
10212
|
}
|
|
10213
|
+
const ZodNull = /*@__PURE__*/ $constructor("ZodNull", (inst, def) => {
|
|
10214
|
+
$ZodNull.init(inst, def);
|
|
10215
|
+
ZodType.init(inst, def);
|
|
10216
|
+
inst._zod.processJSONSchema = (ctx, json, params) => nullProcessor(inst, ctx, json, params);
|
|
10217
|
+
});
|
|
10218
|
+
function _null(params) {
|
|
10219
|
+
return /* @__PURE__ */ _null$1(ZodNull, params);
|
|
10220
|
+
}
|
|
10181
10221
|
const ZodUnknown = /*@__PURE__*/ $constructor("ZodUnknown", (inst, def) => {
|
|
10182
10222
|
$ZodUnknown.init(inst, def);
|
|
10183
10223
|
ZodType.init(inst, def);
|
|
@@ -10541,6 +10581,7 @@ function superRefine(fn, params) {
|
|
|
10541
10581
|
}
|
|
10542
10582
|
//#endregion
|
|
10543
10583
|
//#region src/config-schema.ts
|
|
10584
|
+
const OPENROUTER_API_URL = "https://openrouter.ai/api";
|
|
10544
10585
|
const percentileCutoffsSchema = object({
|
|
10545
10586
|
p50: number$1().positive().optional(),
|
|
10546
10587
|
p75: number$1().positive().optional(),
|
|
@@ -10580,15 +10621,23 @@ const providerConfigSchema = object({
|
|
|
10580
10621
|
preferredMinThroughput: union([number$1().positive(), percentileCutoffsSchema]).optional(),
|
|
10581
10622
|
preferredMaxLatency: union([number$1().positive(), percentileCutoffsSchema]).optional()
|
|
10582
10623
|
}).strict();
|
|
10624
|
+
const triStateSchema = _enum([
|
|
10625
|
+
"auto",
|
|
10626
|
+
"always",
|
|
10627
|
+
"never"
|
|
10628
|
+
]);
|
|
10583
10629
|
const modelOverrideSchema = object({
|
|
10584
10630
|
provider: providerConfigSchema.optional(),
|
|
10585
|
-
headers: record(string$1(), string$1()).optional()
|
|
10631
|
+
headers: record(string$1(), string$1()).optional(),
|
|
10632
|
+
cacheControl: triStateSchema.optional(),
|
|
10633
|
+
cacheControlTtl: union([_enum(["5m", "1h"]), _null()]).optional(),
|
|
10634
|
+
sessionId: triStateSchema.optional()
|
|
10586
10635
|
}).strict();
|
|
10587
10636
|
const proxyConfigSchema = object({
|
|
10588
10637
|
host: string$1().min(1).default("0.0.0.0"),
|
|
10589
10638
|
port: number$1().int().min(1).max(65535).default(8828),
|
|
10590
10639
|
openrouterKey: string$1().default(""),
|
|
10591
|
-
openrouterBaseUrl: string$1().url().default(
|
|
10640
|
+
openrouterBaseUrl: string$1().url().default(OPENROUTER_API_URL),
|
|
10592
10641
|
openrouterDataUrl: string$1().url().optional(),
|
|
10593
10642
|
authType: _enum(["bearer", "oauth"]).default("bearer"),
|
|
10594
10643
|
verbose: boolean().default(false),
|
|
@@ -10597,6 +10646,9 @@ const proxyConfigSchema = object({
|
|
|
10597
10646
|
attributionReferer: string$1().min(1).default("https://github.com/neiromaster/proxitor"),
|
|
10598
10647
|
attributionTitle: string$1().min(1).default("proxitor"),
|
|
10599
10648
|
headers: record(string$1(), string$1()).optional(),
|
|
10649
|
+
cacheControl: triStateSchema.default("auto"),
|
|
10650
|
+
cacheControlTtl: _enum(["5m", "1h"]).optional(),
|
|
10651
|
+
sessionId: triStateSchema.default("auto"),
|
|
10600
10652
|
modelOverrides: record(string$1().min(1), modelOverrideSchema).optional()
|
|
10601
10653
|
}).strict();
|
|
10602
10654
|
const DEFAULTS = proxyConfigSchema.parse({});
|
|
@@ -10628,15 +10680,6 @@ function toArray(value) {
|
|
|
10628
10680
|
const arr = Array.isArray(value) ? [...value] : [value];
|
|
10629
10681
|
return arr.length > 0 ? arr : void 0;
|
|
10630
10682
|
}
|
|
10631
|
-
/** Try to parse an ArrayBuffer as JSON. Returns undefined on failure or empty body. */
|
|
10632
|
-
function tryParseBody(raw) {
|
|
10633
|
-
if (raw.byteLength === 0) return void 0;
|
|
10634
|
-
try {
|
|
10635
|
-
return JSON.parse(new TextDecoder().decode(raw));
|
|
10636
|
-
} catch {
|
|
10637
|
-
return;
|
|
10638
|
-
}
|
|
10639
|
-
}
|
|
10640
10683
|
//#endregion
|
|
10641
10684
|
//#region src/config.ts
|
|
10642
10685
|
const ARRAY_FIELDS = [
|
|
@@ -10716,27 +10759,48 @@ function matchScore(pattern, modelName) {
|
|
|
10716
10759
|
function resolveModelConfig(config, modelName) {
|
|
10717
10760
|
const result = {
|
|
10718
10761
|
provider: config.provider,
|
|
10719
|
-
headers: config.headers ? { ...config.headers } : void 0
|
|
10762
|
+
headers: config.headers ? { ...config.headers } : void 0,
|
|
10763
|
+
cacheControl: config.cacheControl,
|
|
10764
|
+
cacheControlTtl: config.cacheControlTtl,
|
|
10765
|
+
sessionId: config.sessionId
|
|
10720
10766
|
};
|
|
10721
10767
|
if (!modelName || !config.modelOverrides) return result;
|
|
10768
|
+
const bestPattern = findBestMatch(Object.keys(config.modelOverrides), modelName);
|
|
10769
|
+
if (bestPattern) applyOverride(result, config.modelOverrides[bestPattern]);
|
|
10770
|
+
return result;
|
|
10771
|
+
}
|
|
10772
|
+
function findBestMatch(patterns, modelName) {
|
|
10722
10773
|
let bestPattern = null;
|
|
10723
10774
|
let bestScore = -1;
|
|
10724
|
-
for (const pattern of
|
|
10775
|
+
for (const pattern of patterns) {
|
|
10725
10776
|
const score = matchScore(pattern, modelName);
|
|
10726
10777
|
if (score > bestScore) {
|
|
10727
10778
|
bestScore = score;
|
|
10728
10779
|
bestPattern = pattern;
|
|
10729
10780
|
}
|
|
10730
10781
|
}
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
}
|
|
10738
|
-
|
|
10739
|
-
|
|
10782
|
+
return bestPattern;
|
|
10783
|
+
}
|
|
10784
|
+
function applyOverride(result, override) {
|
|
10785
|
+
if (!override) return;
|
|
10786
|
+
if (override.provider !== void 0) result.provider = override.provider;
|
|
10787
|
+
if (override.headers) result.headers = {
|
|
10788
|
+
...result.headers ?? {},
|
|
10789
|
+
...override.headers
|
|
10790
|
+
};
|
|
10791
|
+
if (override.cacheControl !== void 0) result.cacheControl = override.cacheControl;
|
|
10792
|
+
if (override.cacheControlTtl === null) result.cacheControlTtl = void 0;
|
|
10793
|
+
else if (override.cacheControlTtl !== void 0) result.cacheControlTtl = override.cacheControlTtl;
|
|
10794
|
+
if (override.sessionId !== void 0) result.sessionId = override.sessionId;
|
|
10795
|
+
}
|
|
10796
|
+
/**
|
|
10797
|
+
* Throw if a URL ends with /v1 — a common misconfiguration after the URL routing change.
|
|
10798
|
+
* Paths like /v1/chat/completions are now forwarded as-is, so including /v1 in the base
|
|
10799
|
+
* URL would produce doubled paths like /v1/v1/chat/completions.
|
|
10800
|
+
*/
|
|
10801
|
+
function throwIfV1Suffix(url, field) {
|
|
10802
|
+
const { pathname } = new URL(url);
|
|
10803
|
+
if (pathname.endsWith("/v1") || pathname.endsWith("/v1/")) throw new Error(`${field} "${url}" ends with /v1 — paths are now forwarded as-is, so this would produce doubled paths like /v1/v1/chat/completions. Remove the /v1 suffix (use "${url.replace(/\/v1\/?$/, "")}")`);
|
|
10740
10804
|
}
|
|
10741
10805
|
async function loadConfig(options) {
|
|
10742
10806
|
let fileConfig = {};
|
|
@@ -10756,6 +10820,8 @@ async function loadConfig(options) {
|
|
|
10756
10820
|
const result = proxyConfigSchema.safeParse(merged);
|
|
10757
10821
|
if (!result.success) throw new ConfigValidationError("(merged config)", result.error);
|
|
10758
10822
|
if (!result.data.openrouterKey) throw new Error("OpenRouter API key is required. Set OPENROUTER_API_KEY env var, pass --openrouter-key flag, or set it in config file.");
|
|
10823
|
+
throwIfV1Suffix(result.data.openrouterBaseUrl, "openrouterBaseUrl");
|
|
10824
|
+
if (result.data.openrouterDataUrl) throwIfV1Suffix(result.data.openrouterDataUrl, "openrouterDataUrl");
|
|
10759
10825
|
return result.data;
|
|
10760
10826
|
}
|
|
10761
10827
|
function getXdgConfigDir() {
|
|
@@ -11648,9 +11714,9 @@ function stringWidth$1(string, options = {}) {
|
|
|
11648
11714
|
return width;
|
|
11649
11715
|
}
|
|
11650
11716
|
function isUnicodeSupported() {
|
|
11651
|
-
const { env } =
|
|
11717
|
+
const { env } = process$1;
|
|
11652
11718
|
const { TERM, TERM_PROGRAM } = env;
|
|
11653
|
-
if (
|
|
11719
|
+
if (process$1.platform !== "win32") return TERM !== "linux";
|
|
11654
11720
|
return Boolean(env.WT_SESSION) || Boolean(env.TERMINUS_SUBLIME) || env.ConEmuTask === "{cmd::Cmder}" || TERM_PROGRAM === "Terminus-Sublime" || TERM_PROGRAM === "vscode" || TERM === "xterm-256color" || TERM === "alacritty" || TERM === "rxvt-unicode" || TERM === "rxvt-unicode-256color" || env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
|
|
11655
11721
|
}
|
|
11656
11722
|
const TYPE_COLOR_MAP = {
|
|
@@ -11810,7 +11876,7 @@ var OpenRouterClient = class {
|
|
|
11810
11876
|
};
|
|
11811
11877
|
//#endregion
|
|
11812
11878
|
//#region src/openrouter/data-client.ts
|
|
11813
|
-
const OPENROUTER_FALLBACK_URL =
|
|
11879
|
+
const OPENROUTER_FALLBACK_URL = OPENROUTER_API_URL;
|
|
11814
11880
|
function isValidProvidersResponse(data) {
|
|
11815
11881
|
return typeof data === "object" && data !== null && "data" in data && Array.isArray(data.data);
|
|
11816
11882
|
}
|
|
@@ -11824,7 +11890,7 @@ function isValidEndpointsResponse(data) {
|
|
|
11824
11890
|
* Client for fetching provider/model data with automatic fallback to OpenRouter.
|
|
11825
11891
|
*
|
|
11826
11892
|
* When the primary API (openrouterDataUrl or openrouterBaseUrl) doesn't support
|
|
11827
|
-
* OpenRouter-specific data endpoints, falls back to https://openrouter.ai/api
|
|
11893
|
+
* OpenRouter-specific data endpoints, falls back to https://openrouter.ai/api
|
|
11828
11894
|
* which hosts public, unauthenticated endpoints for /providers, /models, etc.
|
|
11829
11895
|
*/
|
|
11830
11896
|
var OpenRouterDataClient = class {
|
|
@@ -11840,13 +11906,13 @@ var OpenRouterDataClient = class {
|
|
|
11840
11906
|
this.onFallback = config.onFallback;
|
|
11841
11907
|
}
|
|
11842
11908
|
async fetchProviders() {
|
|
11843
|
-
return (await this.withFallback("/providers", () => this.primaryClient.get("/providers"), isValidProvidersResponse)).data.data;
|
|
11909
|
+
return (await this.withFallback("/v1/providers", () => this.primaryClient.get("/v1/providers"), isValidProvidersResponse)).data.data;
|
|
11844
11910
|
}
|
|
11845
11911
|
async fetchModels() {
|
|
11846
|
-
return (await this.withFallback("/models", () => this.primaryClient.get("/models"), isValidModelsResponse)).data.data;
|
|
11912
|
+
return (await this.withFallback("/v1/models", () => this.primaryClient.get("/v1/models"), isValidModelsResponse)).data.data;
|
|
11847
11913
|
}
|
|
11848
11914
|
async fetchModelEndpoints(author, slug) {
|
|
11849
|
-
const path = `/models/${author}/${slug}/endpoints`;
|
|
11915
|
+
const path = `/v1/models/${author}/${slug}/endpoints`;
|
|
11850
11916
|
return (await this.withFallback(path, () => this.primaryClient.get(path), isValidEndpointsResponse)).data.data.endpoints ?? [];
|
|
11851
11917
|
}
|
|
11852
11918
|
/**
|
|
@@ -12849,7 +12915,7 @@ var compose = (middleware, onError, onNotFound) => {
|
|
|
12849
12915
|
var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
|
|
12850
12916
|
//#endregion
|
|
12851
12917
|
//#region node_modules/.pnpm/hono@4.12.23/node_modules/hono/dist/utils/body.js
|
|
12852
|
-
var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
|
|
12918
|
+
var parseBody$1 = async (request, options = /* @__PURE__ */ Object.create(null)) => {
|
|
12853
12919
|
const { all = false, dot = false } = options;
|
|
12854
12920
|
const contentType = (request instanceof HonoRequest ? request.raw.headers : request.headers).get("Content-Type");
|
|
12855
12921
|
if (contentType?.startsWith("multipart/form-data") || contentType?.startsWith("application/x-www-form-urlencoded")) return parseFormData(request, {
|
|
@@ -13136,7 +13202,7 @@ var HonoRequest = class {
|
|
|
13136
13202
|
return headerData;
|
|
13137
13203
|
}
|
|
13138
13204
|
async parseBody(options) {
|
|
13139
|
-
return parseBody(this, options);
|
|
13205
|
+
return parseBody$1(this, options);
|
|
13140
13206
|
}
|
|
13141
13207
|
#cachedBody = (key) => {
|
|
13142
13208
|
const { bodyCache, raw } = this;
|
|
@@ -14590,6 +14656,9 @@ var Hono = class extends Hono$1 {
|
|
|
14590
14656
|
}
|
|
14591
14657
|
};
|
|
14592
14658
|
//#endregion
|
|
14659
|
+
//#region node_modules/.pnpm/hono@4.12.23/node_modules/hono/dist/helper/factory/index.js
|
|
14660
|
+
var createMiddleware = (middleware) => middleware;
|
|
14661
|
+
//#endregion
|
|
14593
14662
|
//#region src/proxy/headers.ts
|
|
14594
14663
|
const HOP_BY_HOP = new Set([
|
|
14595
14664
|
"connection",
|
|
@@ -14606,7 +14675,9 @@ const STRIP_REQUEST = new Set([
|
|
|
14606
14675
|
"authorization",
|
|
14607
14676
|
"x-api-key",
|
|
14608
14677
|
"host",
|
|
14609
|
-
"content-length"
|
|
14678
|
+
"content-length",
|
|
14679
|
+
"x-claude-code-session-id",
|
|
14680
|
+
"x-session-id"
|
|
14610
14681
|
]);
|
|
14611
14682
|
/** Headers to strip from upstream response before forwarding */
|
|
14612
14683
|
const STRIP_RESPONSE = new Set(["content-length", "content-encoding"]);
|
|
@@ -14621,17 +14692,6 @@ function filterHeaders(incoming, blocklist) {
|
|
|
14621
14692
|
}
|
|
14622
14693
|
return headers;
|
|
14623
14694
|
}
|
|
14624
|
-
/** Build request headers for upstream fetch */
|
|
14625
|
-
function buildRequestHeaders(incoming, config, inject, extraHeaders) {
|
|
14626
|
-
const headers = filterHeaders(incoming, STRIP_REQUEST);
|
|
14627
|
-
headers.Authorization = formatAuthHeader(config.openrouterKey, config.authType);
|
|
14628
|
-
headers["HTTP-Referer"] = config.attributionReferer;
|
|
14629
|
-
headers["X-OpenRouter-Title"] = config.attributionTitle;
|
|
14630
|
-
headers["Accept-Encoding"] = "identity";
|
|
14631
|
-
if (extraHeaders) Object.assign(headers, extraHeaders);
|
|
14632
|
-
if (inject) headers["Content-Type"] = "application/json";
|
|
14633
|
-
return headers;
|
|
14634
|
-
}
|
|
14635
14695
|
/** Filter response headers and add SSE-friendly defaults */
|
|
14636
14696
|
function buildResponseHeaders(from) {
|
|
14637
14697
|
const headers = filterHeaders(from, STRIP_RESPONSE);
|
|
@@ -14640,6 +14700,49 @@ function buildResponseHeaders(from) {
|
|
|
14640
14700
|
return headers;
|
|
14641
14701
|
}
|
|
14642
14702
|
//#endregion
|
|
14703
|
+
//#region src/proxy/middleware/build-upstream-req.ts
|
|
14704
|
+
const PROTO_POLLUTION_KEYS = new Set([
|
|
14705
|
+
"__proto__",
|
|
14706
|
+
"constructor",
|
|
14707
|
+
"prototype"
|
|
14708
|
+
]);
|
|
14709
|
+
function resolveForwardBody(c) {
|
|
14710
|
+
if (c.var.bodyMutated && c.var.parsedBody) try {
|
|
14711
|
+
return new TextEncoder().encode(JSON.stringify(c.var.parsedBody)).buffer;
|
|
14712
|
+
} catch (err) {
|
|
14713
|
+
logger.warn(withReq(c.var.reqId, `Failed to re-serialize mutated body (${err instanceof Error ? err.message : "unknown"}); forwarding raw body as-is`));
|
|
14714
|
+
return c.var.rawBody;
|
|
14715
|
+
}
|
|
14716
|
+
return c.var.rawBody;
|
|
14717
|
+
}
|
|
14718
|
+
function applyProxyHeaders(headers, config) {
|
|
14719
|
+
headers.Authorization = formatAuthHeader(config.openrouterKey, config.authType);
|
|
14720
|
+
headers["HTTP-Referer"] = config.attributionReferer;
|
|
14721
|
+
headers["X-OpenRouter-Title"] = config.attributionTitle;
|
|
14722
|
+
headers["Accept-Encoding"] = "identity";
|
|
14723
|
+
}
|
|
14724
|
+
function applyExtraHeaders(headers, extraHeaders) {
|
|
14725
|
+
if (!extraHeaders) return;
|
|
14726
|
+
for (const [key, value] of Object.entries(extraHeaders)) {
|
|
14727
|
+
if (PROTO_POLLUTION_KEYS.has(key)) continue;
|
|
14728
|
+
headers[key] = value;
|
|
14729
|
+
}
|
|
14730
|
+
}
|
|
14731
|
+
function forceJsonContentType(headers) {
|
|
14732
|
+
for (const key of Object.keys(headers)) if (key.toLowerCase() === "content-type") delete headers[key];
|
|
14733
|
+
headers["Content-Type"] = "application/json";
|
|
14734
|
+
}
|
|
14735
|
+
const buildUpstreamReq = createMiddleware(async (c, next) => {
|
|
14736
|
+
c.set("forwardBody", resolveForwardBody(c));
|
|
14737
|
+
const headers = filterHeaders(c.req.raw.headers, STRIP_REQUEST);
|
|
14738
|
+
applyProxyHeaders(headers, c.var.config);
|
|
14739
|
+
applyExtraHeaders(headers, c.var.resolvedConfig.headers);
|
|
14740
|
+
if (c.var.effectiveSessionId !== void 0) headers["x-session-id"] = c.var.effectiveSessionId;
|
|
14741
|
+
if (c.var.bodyMutated) forceJsonContentType(headers);
|
|
14742
|
+
c.set("upstreamHeaders", headers);
|
|
14743
|
+
await next();
|
|
14744
|
+
});
|
|
14745
|
+
//#endregion
|
|
14643
14746
|
//#region src/proxy/cache-logging.ts
|
|
14644
14747
|
function extractCacheUsage(bodyText) {
|
|
14645
14748
|
try {
|
|
@@ -14754,130 +14857,10 @@ function buildUpstreamResponseWithLogging(upstream, method, reqId) {
|
|
|
14754
14857
|
});
|
|
14755
14858
|
}
|
|
14756
14859
|
//#endregion
|
|
14757
|
-
//#region src/proxy/
|
|
14758
|
-
/** Extract the model name from a raw request body. Returns undefined if not parseable or absent. */
|
|
14759
|
-
function extractModel(rawBody) {
|
|
14760
|
-
const json = tryParseBody(rawBody);
|
|
14761
|
-
return typeof json?.model === "string" ? json.model : void 0;
|
|
14762
|
-
}
|
|
14763
|
-
/** Inject provider routing into request body, always overwriting existing value */
|
|
14764
|
-
function injectProvider(rawBody, providerRouting) {
|
|
14765
|
-
if (rawBody.byteLength === 0) throw new Error("Request body is empty; cannot inject provider");
|
|
14766
|
-
let json;
|
|
14767
|
-
try {
|
|
14768
|
-
json = JSON.parse(new TextDecoder().decode(rawBody));
|
|
14769
|
-
} catch (parseError) {
|
|
14770
|
-
throw new Error("Request body is not valid JSON; cannot inject provider", { cause: parseError });
|
|
14771
|
-
}
|
|
14772
|
-
const modified = {
|
|
14773
|
-
...json,
|
|
14774
|
-
provider: providerRouting
|
|
14775
|
-
};
|
|
14776
|
-
return new TextEncoder().encode(JSON.stringify(modified)).buffer;
|
|
14777
|
-
}
|
|
14778
|
-
//#endregion
|
|
14779
|
-
//#region src/proxy/paths.ts
|
|
14860
|
+
//#region src/proxy/utils/error.ts
|
|
14780
14861
|
/**
|
|
14781
|
-
* Paths where provider routing is injected into the request body.
|
|
14782
|
-
* All three are OpenRouter-supported endpoints:
|
|
14783
|
-
* /v1/chat/completions — OpenAI Chat Completions
|
|
14784
|
-
* /v1/responses — OpenAI Responses API
|
|
14785
|
-
* /v1/messages — Anthropic Messages API
|
|
14786
|
-
*/
|
|
14787
|
-
const INJECT_PATHS = new Set([
|
|
14788
|
-
"/v1/chat/completions",
|
|
14789
|
-
"/v1/responses",
|
|
14790
|
-
"/v1/messages"
|
|
14791
|
-
]);
|
|
14792
|
-
/** Check if this request should have provider routing injected */
|
|
14793
|
-
function shouldInject(method, path) {
|
|
14794
|
-
return method === "POST" && INJECT_PATHS.has(path);
|
|
14795
|
-
}
|
|
14796
|
-
/** Strip /v1 prefix from path: /v1/chat/completions → /chat/completions */
|
|
14797
|
-
function toUpstreamPath(pathname) {
|
|
14798
|
-
if (pathname.startsWith("/v1")) return pathname.slice(3);
|
|
14799
|
-
return pathname;
|
|
14800
|
-
}
|
|
14801
|
-
/** Build full upstream URL from request and config */
|
|
14802
|
-
function buildUpstreamUrl(requestUrl, config) {
|
|
14803
|
-
const { pathname } = new URL(requestUrl);
|
|
14804
|
-
return `${config.openrouterBaseUrl}${toUpstreamPath(pathname)}`;
|
|
14805
|
-
}
|
|
14806
|
-
//#endregion
|
|
14807
|
-
//#region src/proxy.ts
|
|
14808
|
-
function readRequestBody(method, raw, inject, providerRouting) {
|
|
14809
|
-
if (["GET", "HEAD"].includes(method)) return void 0;
|
|
14810
|
-
if (inject) return injectProvider(raw, providerRouting);
|
|
14811
|
-
return raw.byteLength > 0 ? raw : void 0;
|
|
14812
|
-
}
|
|
14813
|
-
async function fetchUpstream(url, method, headers, body, signal) {
|
|
14814
|
-
return fetch(url, {
|
|
14815
|
-
method,
|
|
14816
|
-
headers,
|
|
14817
|
-
body,
|
|
14818
|
-
signal,
|
|
14819
|
-
duplex: body ? "half" : void 0
|
|
14820
|
-
});
|
|
14821
|
-
}
|
|
14822
|
-
async function readRawBody(request, reqId) {
|
|
14823
|
-
try {
|
|
14824
|
-
return {
|
|
14825
|
-
ok: true,
|
|
14826
|
-
body: await request.arrayBuffer()
|
|
14827
|
-
};
|
|
14828
|
-
} catch (err) {
|
|
14829
|
-
const message = err instanceof Error ? err.message : "Failed to read request body";
|
|
14830
|
-
logger.error(withReq(reqId, message));
|
|
14831
|
-
return {
|
|
14832
|
-
ok: false,
|
|
14833
|
-
response: Response.json({ error: {
|
|
14834
|
-
message,
|
|
14835
|
-
type: "proxy_request_error"
|
|
14836
|
-
} }, { status: 400 })
|
|
14837
|
-
};
|
|
14838
|
-
}
|
|
14839
|
-
}
|
|
14840
|
-
function resolveRequest(rawBody, config, method, path, reqId) {
|
|
14841
|
-
const modelName = extractModel(rawBody);
|
|
14842
|
-
const resolved = resolveModelConfig(config, modelName);
|
|
14843
|
-
const providerRouting = buildProviderRouting(resolved.provider);
|
|
14844
|
-
const inject = shouldInject(method, path) && providerRouting !== void 0;
|
|
14845
|
-
let body;
|
|
14846
|
-
try {
|
|
14847
|
-
body = readRequestBody(method, rawBody, inject, providerRouting);
|
|
14848
|
-
} catch (err) {
|
|
14849
|
-
const message = err instanceof Error ? err.message : "Failed to process request body";
|
|
14850
|
-
logger.error(withReq(reqId, message));
|
|
14851
|
-
return {
|
|
14852
|
-
inject,
|
|
14853
|
-
body: void 0,
|
|
14854
|
-
modelName,
|
|
14855
|
-
headers: resolved.headers,
|
|
14856
|
-
error: new Response(JSON.stringify({ error: {
|
|
14857
|
-
message,
|
|
14858
|
-
type: "proxy_request_error"
|
|
14859
|
-
} }), {
|
|
14860
|
-
status: 400,
|
|
14861
|
-
headers: { "Content-Type": "application/json" }
|
|
14862
|
-
})
|
|
14863
|
-
};
|
|
14864
|
-
}
|
|
14865
|
-
return {
|
|
14866
|
-
inject,
|
|
14867
|
-
body,
|
|
14868
|
-
modelName,
|
|
14869
|
-
headers: resolved.headers
|
|
14870
|
-
};
|
|
14871
|
-
}
|
|
14872
|
-
/**
|
|
14873
|
-
* Extract a readable error detail from an upstream response body.
|
|
14874
|
-
*
|
|
14875
14862
|
* OpenRouter error format:
|
|
14876
|
-
* { error: { code
|
|
14877
|
-
*
|
|
14878
|
-
* - `error.message` — human-readable summary
|
|
14879
|
-
* - `error.metadata.provider_name` — which provider caused it (null = OpenRouter itself)
|
|
14880
|
-
* - `error.metadata.raw` — the original provider error (most specific cause)
|
|
14863
|
+
* { error: { code, message, metadata: { raw, provider_name } } }
|
|
14881
14864
|
*/
|
|
14882
14865
|
function formatMetadata(meta) {
|
|
14883
14866
|
const parts = [];
|
|
@@ -14904,41 +14887,315 @@ function extractErrorDetail(bodyText) {
|
|
|
14904
14887
|
} catch {}
|
|
14905
14888
|
return bodyText;
|
|
14906
14889
|
}
|
|
14907
|
-
|
|
14908
|
-
|
|
14909
|
-
|
|
14910
|
-
|
|
14911
|
-
|
|
14912
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
14913
|
-
logger.warn(withReq(reqId, `Aborted: ${method} ${path}`));
|
|
14914
|
-
return new Response(null, { status: 499 });
|
|
14915
|
-
}
|
|
14916
|
-
logger.error(withReq(reqId, "Upstream fetch error:"), err);
|
|
14890
|
+
//#endregion
|
|
14891
|
+
//#region src/proxy/middleware/forward-request.ts
|
|
14892
|
+
function buildErrorResponse(err, ctx) {
|
|
14893
|
+
if (err instanceof TypeError) {
|
|
14894
|
+
logger.error(withReq(ctx.reqId, "Upstream fetch error:"), err);
|
|
14917
14895
|
return Response.json({ error: {
|
|
14918
14896
|
message: "Proxy failed to reach upstream",
|
|
14919
14897
|
type: "proxy_upstream_error"
|
|
14920
14898
|
} }, { status: 502 });
|
|
14921
14899
|
}
|
|
14922
|
-
if (
|
|
14923
|
-
|
|
14924
|
-
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
14930
|
-
|
|
14931
|
-
|
|
14932
|
-
|
|
14933
|
-
|
|
14934
|
-
|
|
14900
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
14901
|
+
logger.warn(withReq(ctx.reqId, `Aborted: ${ctx.method} ${ctx.path}`));
|
|
14902
|
+
return new Response(null, { status: 499 });
|
|
14903
|
+
}
|
|
14904
|
+
throw err;
|
|
14905
|
+
}
|
|
14906
|
+
async function buildUpstreamErrorResponse(upstream, ctx) {
|
|
14907
|
+
const bodyText = await upstream.text();
|
|
14908
|
+
const detail = extractErrorDetail(bodyText);
|
|
14909
|
+
const truncated = detail.length > 300 ? `${detail.slice(0, 300)}…` : detail;
|
|
14910
|
+
(upstream.status >= 500 ? logger.error : logger.warn)(withReq(ctx.reqId, `${ctx.method} ${ctx.path} ← ${upstream.status} (${Date.now() - ctx.startedAt}ms): ${truncated}`));
|
|
14911
|
+
const responseHeaders = buildResponseHeaders(upstream.headers);
|
|
14912
|
+
if (ctx.method === "HEAD") return new Response(null, {
|
|
14913
|
+
status: upstream.status,
|
|
14914
|
+
headers: responseHeaders
|
|
14915
|
+
});
|
|
14916
|
+
return new Response(bodyText, {
|
|
14917
|
+
status: upstream.status,
|
|
14918
|
+
headers: responseHeaders
|
|
14919
|
+
});
|
|
14920
|
+
}
|
|
14921
|
+
const forwardRequest = createMiddleware(async (c) => {
|
|
14922
|
+
const { upstreamUrl, forwardBody, upstreamHeaders, reqId, path, startedAt, method } = c.var;
|
|
14923
|
+
const ctx = {
|
|
14924
|
+
reqId,
|
|
14925
|
+
method,
|
|
14926
|
+
path,
|
|
14927
|
+
startedAt,
|
|
14928
|
+
upstreamShort: upstreamUrl.replace(/^https?:\/\//, ""),
|
|
14929
|
+
modelLog: c.var.modelName ? ` model=${c.var.modelName}` : "",
|
|
14930
|
+
bodyMutated: c.var.bodyMutated
|
|
14931
|
+
};
|
|
14932
|
+
const controller = new AbortController();
|
|
14933
|
+
const onClientAbort = () => controller.abort();
|
|
14934
|
+
c.req.raw.signal.addEventListener("abort", onClientAbort);
|
|
14935
|
+
logger.info(withReq(reqId, `${method} ${path} → ${ctx.upstreamShort}${ctx.bodyMutated ? " [inject]" : ""}${ctx.modelLog}`));
|
|
14936
|
+
let upstream;
|
|
14937
|
+
try {
|
|
14938
|
+
upstream = await fetch(upstreamUrl, {
|
|
14939
|
+
method,
|
|
14940
|
+
headers: upstreamHeaders,
|
|
14941
|
+
body: forwardBody,
|
|
14942
|
+
signal: controller.signal,
|
|
14943
|
+
...forwardBody ? { duplex: "half" } : {}
|
|
14935
14944
|
});
|
|
14945
|
+
} catch (err) {
|
|
14946
|
+
return buildErrorResponse(err, ctx);
|
|
14947
|
+
} finally {
|
|
14948
|
+
c.req.raw.signal.removeEventListener("abort", onClientAbort);
|
|
14936
14949
|
}
|
|
14950
|
+
if (upstream.status >= 400) return buildUpstreamErrorResponse(upstream, ctx);
|
|
14937
14951
|
logger.info(withReq(reqId, `${method} ${path} ← ${upstream.status} (${Date.now() - startedAt}ms)`));
|
|
14938
14952
|
return buildUpstreamResponseWithLogging(upstream, method, reqId);
|
|
14953
|
+
});
|
|
14954
|
+
//#endregion
|
|
14955
|
+
//#region src/proxy/paths.ts
|
|
14956
|
+
const ENDPOINT_MAP = {
|
|
14957
|
+
"/v1/chat/completions": "chat-completions",
|
|
14958
|
+
"/v1/responses": "responses",
|
|
14959
|
+
"/v1/messages": "messages"
|
|
14960
|
+
};
|
|
14961
|
+
const INJECT_PATHS = new Set(Object.keys(ENDPOINT_MAP));
|
|
14962
|
+
/** Endpoints that are natively Anthropic (cache_control is always safe regardless of model). */
|
|
14963
|
+
const ANTHROPIC_NATIVE_ENDPOINTS = new Set(["messages", "responses"]);
|
|
14964
|
+
function classifyEndpoint(pathname) {
|
|
14965
|
+
return ENDPOINT_MAP[pathname] ?? "other";
|
|
14966
|
+
}
|
|
14967
|
+
function buildUpstreamUrl(pathname, config) {
|
|
14968
|
+
return `${config.openrouterBaseUrl}${pathname}`;
|
|
14969
|
+
}
|
|
14970
|
+
//#endregion
|
|
14971
|
+
//#region src/proxy/utils/model.ts
|
|
14972
|
+
function isAnthropicModel(modelName) {
|
|
14973
|
+
const lower = modelName.toLowerCase();
|
|
14974
|
+
return lower.startsWith("anthropic/claude") || lower.startsWith("claude-");
|
|
14975
|
+
}
|
|
14976
|
+
//#endregion
|
|
14977
|
+
//#region src/proxy/utils/cache-control.ts
|
|
14978
|
+
function isAnthropicEndpoint(modelName, path) {
|
|
14979
|
+
const endpoint = classifyEndpoint(path);
|
|
14980
|
+
return ANTHROPIC_NATIVE_ENDPOINTS.has(endpoint) || isAnthropicModel(modelName ?? "");
|
|
14939
14981
|
}
|
|
14982
|
+
function shouldInjectCacheControl(mode, modelName, path) {
|
|
14983
|
+
if (mode === "never") return false;
|
|
14984
|
+
if (mode === "always") return true;
|
|
14985
|
+
return isAnthropicEndpoint(modelName, path);
|
|
14986
|
+
}
|
|
14987
|
+
const TTL_SECONDS = {
|
|
14988
|
+
"5m": 300,
|
|
14989
|
+
"1h": 3600
|
|
14990
|
+
};
|
|
14991
|
+
/**
|
|
14992
|
+
* Build cache_control value for injection.
|
|
14993
|
+
* Merges existing cache_control with configured TTL.
|
|
14994
|
+
* If TTL is configured and the endpoint is Anthropic, it always overrides.
|
|
14995
|
+
*/
|
|
14996
|
+
function buildCacheControl(existing, ttl, isAnthropic) {
|
|
14997
|
+
const result = existing !== null && typeof existing === "object" && !Array.isArray(existing) ? { ...existing } : { type: "ephemeral" };
|
|
14998
|
+
if (!("type" in result)) result.type = "ephemeral";
|
|
14999
|
+
if (ttl && isAnthropic) result.ttl = TTL_SECONDS[ttl];
|
|
15000
|
+
return result;
|
|
15001
|
+
}
|
|
15002
|
+
//#endregion
|
|
15003
|
+
//#region src/proxy/middleware/inject-cache-control.ts
|
|
15004
|
+
const injectCacheControl = createMiddleware(async (c, next) => {
|
|
15005
|
+
const resolved = c.var.resolvedConfig;
|
|
15006
|
+
const parsedBody = c.var.parsedBody;
|
|
15007
|
+
if (!parsedBody) {
|
|
15008
|
+
await next();
|
|
15009
|
+
return;
|
|
15010
|
+
}
|
|
15011
|
+
if (shouldInjectCacheControl(resolved.cacheControl, c.var.modelName, c.var.path)) {
|
|
15012
|
+
const isAnthropic = isAnthropicEndpoint(c.var.modelName, c.var.path);
|
|
15013
|
+
parsedBody.cache_control = buildCacheControl(parsedBody.cache_control, resolved.cacheControlTtl, isAnthropic);
|
|
15014
|
+
c.set("bodyMutated", true);
|
|
15015
|
+
}
|
|
15016
|
+
await next();
|
|
15017
|
+
});
|
|
15018
|
+
//#endregion
|
|
15019
|
+
//#region src/proxy/middleware/inject-provider.ts
|
|
15020
|
+
const injectProvider = createMiddleware(async (c, next) => {
|
|
15021
|
+
const resolved = c.var.resolvedConfig;
|
|
15022
|
+
const parsedBody = c.var.parsedBody;
|
|
15023
|
+
if (!parsedBody || !resolved.provider) {
|
|
15024
|
+
await next();
|
|
15025
|
+
return;
|
|
15026
|
+
}
|
|
15027
|
+
const providerRouting = buildProviderRouting(resolved.provider);
|
|
15028
|
+
if (providerRouting !== void 0) {
|
|
15029
|
+
parsedBody.provider = providerRouting;
|
|
15030
|
+
c.set("parsedBody", parsedBody);
|
|
15031
|
+
c.set("bodyMutated", true);
|
|
15032
|
+
}
|
|
15033
|
+
await next();
|
|
15034
|
+
});
|
|
15035
|
+
//#endregion
|
|
15036
|
+
//#region src/proxy/utils/session-id.ts
|
|
15037
|
+
const PROXY_SESSION_ID = crypto.randomUUID();
|
|
15038
|
+
function extractConversationFingerprint(parsedBody, path) {
|
|
15039
|
+
let system;
|
|
15040
|
+
let user;
|
|
15041
|
+
switch (classifyEndpoint(path)) {
|
|
15042
|
+
case "responses":
|
|
15043
|
+
system = parsedBody.instructions;
|
|
15044
|
+
user = parsedBody.input;
|
|
15045
|
+
break;
|
|
15046
|
+
case "messages": {
|
|
15047
|
+
system = parsedBody.system;
|
|
15048
|
+
const messages = parsedBody.messages;
|
|
15049
|
+
if (Array.isArray(messages)) user = messages.find((m) => m.role === "user" && m.content != null)?.content;
|
|
15050
|
+
break;
|
|
15051
|
+
}
|
|
15052
|
+
default: {
|
|
15053
|
+
const messages = parsedBody.messages;
|
|
15054
|
+
if (Array.isArray(messages)) {
|
|
15055
|
+
const firstSystem = messages.find((m) => (m.role === "system" || m.role === "developer") && m.content != null);
|
|
15056
|
+
const firstUser = messages.find((m) => m.role === "user" && m.content != null);
|
|
15057
|
+
system = firstSystem?.content;
|
|
15058
|
+
user = firstUser?.content;
|
|
15059
|
+
}
|
|
15060
|
+
}
|
|
15061
|
+
}
|
|
15062
|
+
if (system == null && user == null) return null;
|
|
15063
|
+
const safeStringify = (value) => {
|
|
15064
|
+
try {
|
|
15065
|
+
return JSON.stringify(value);
|
|
15066
|
+
} catch {
|
|
15067
|
+
return "";
|
|
15068
|
+
}
|
|
15069
|
+
};
|
|
15070
|
+
const hash = createHash("sha256");
|
|
15071
|
+
hash.update(String(parsedBody.model ?? ""));
|
|
15072
|
+
if (system != null) hash.update(safeStringify(system));
|
|
15073
|
+
if (user != null) hash.update(safeStringify(user));
|
|
15074
|
+
return hash.digest("hex");
|
|
15075
|
+
}
|
|
15076
|
+
/**
|
|
15077
|
+
* Derive session ID for sticky routing from multiple sources:
|
|
15078
|
+
*
|
|
15079
|
+
* 1. `x-claude-code-session-id` header — Claude Code
|
|
15080
|
+
* 2. `session_id` from parsed body — Codex CLI (Responses API)
|
|
15081
|
+
* 3. hash(model + system + user) — content-based, per-conversation
|
|
15082
|
+
* 4. crypto.randomUUID() — fallback when no content available
|
|
15083
|
+
*/
|
|
15084
|
+
function deriveSessionId(incomingHeaders, parsedBody, path, mode) {
|
|
15085
|
+
if (mode === "never") return void 0;
|
|
15086
|
+
const fromHeader = incomingHeaders.get("x-claude-code-session-id");
|
|
15087
|
+
if (fromHeader) return fromHeader.slice(0, 256);
|
|
15088
|
+
if (parsedBody && typeof parsedBody.session_id === "string" && parsedBody.session_id.length > 0) return parsedBody.session_id;
|
|
15089
|
+
if (parsedBody) {
|
|
15090
|
+
const fingerprint = extractConversationFingerprint(parsedBody, path);
|
|
15091
|
+
if (fingerprint) return fingerprint;
|
|
15092
|
+
}
|
|
15093
|
+
return PROXY_SESSION_ID;
|
|
15094
|
+
}
|
|
15095
|
+
//#endregion
|
|
15096
|
+
//#region src/proxy/middleware/inject-session-id.ts
|
|
15097
|
+
const injectSessionId = createMiddleware(async (c, next) => {
|
|
15098
|
+
const mode = c.var.resolvedConfig.sessionId;
|
|
15099
|
+
const sessionId = deriveSessionId(c.req.raw.headers, c.var.parsedBody, c.var.path, mode);
|
|
15100
|
+
if (sessionId) c.set("effectiveSessionId", sessionId);
|
|
15101
|
+
await next();
|
|
15102
|
+
});
|
|
15103
|
+
//#endregion
|
|
15104
|
+
//#region src/proxy/middleware/parse-body.ts
|
|
15105
|
+
const decoder = new TextDecoder();
|
|
15106
|
+
const parseBody = createMiddleware(async (c, next) => {
|
|
15107
|
+
const rawBody = c.var.rawBody;
|
|
15108
|
+
if (!rawBody || rawBody.byteLength === 0) {
|
|
15109
|
+
c.set("parsedBody", void 0);
|
|
15110
|
+
c.set("modelName", void 0);
|
|
15111
|
+
await next();
|
|
15112
|
+
return;
|
|
15113
|
+
}
|
|
15114
|
+
try {
|
|
15115
|
+
const json = JSON.parse(decoder.decode(rawBody));
|
|
15116
|
+
c.set("parsedBody", json);
|
|
15117
|
+
c.set("modelName", typeof json.model === "string" ? json.model : void 0);
|
|
15118
|
+
} catch {
|
|
15119
|
+
logger.debug(withReq(c.var.reqId, "Failed to parse request body as JSON — skipping injection"));
|
|
15120
|
+
c.set("parsedBody", void 0);
|
|
15121
|
+
c.set("modelName", void 0);
|
|
15122
|
+
}
|
|
15123
|
+
await next();
|
|
15124
|
+
});
|
|
15125
|
+
//#endregion
|
|
15126
|
+
//#region src/proxy/middleware/read-body.ts
|
|
15127
|
+
function parseBodyLimit(limit) {
|
|
15128
|
+
const match = limit.match(/^(\d+)\s*(kb|mb|gb)$/i);
|
|
15129
|
+
if (!match) return Number.MAX_SAFE_INTEGER;
|
|
15130
|
+
const n = parseInt(match[1], 10);
|
|
15131
|
+
switch (match[2].toLowerCase()) {
|
|
15132
|
+
case "kb": return n * 1024;
|
|
15133
|
+
case "mb": return n * 1024 * 1024;
|
|
15134
|
+
case "gb": return n * 1024 * 1024 * 1024;
|
|
15135
|
+
default: return Number.MAX_SAFE_INTEGER;
|
|
15136
|
+
}
|
|
15137
|
+
}
|
|
15138
|
+
const readBody = createMiddleware(async (c, next) => {
|
|
15139
|
+
const method = c.var.method;
|
|
15140
|
+
if (method === "GET" || method === "HEAD") {
|
|
15141
|
+
c.set("rawBody", void 0);
|
|
15142
|
+
await next();
|
|
15143
|
+
return;
|
|
15144
|
+
}
|
|
15145
|
+
try {
|
|
15146
|
+
const body = await c.req.raw.arrayBuffer();
|
|
15147
|
+
const maxBytes = parseBodyLimit(c.var.config.bodyLimit);
|
|
15148
|
+
if (body.byteLength > maxBytes) return c.json({ error: {
|
|
15149
|
+
message: "Request body too large",
|
|
15150
|
+
type: "proxy_request_error"
|
|
15151
|
+
} }, { status: 413 });
|
|
15152
|
+
c.set("rawBody", body.byteLength > 0 ? body : void 0);
|
|
15153
|
+
} catch (err) {
|
|
15154
|
+
const internalMessage = err instanceof Error ? err.message : "Failed to read request body";
|
|
15155
|
+
logger.error(withReq(c.var.reqId, internalMessage));
|
|
15156
|
+
return c.json({ error: {
|
|
15157
|
+
message: "Failed to read request body",
|
|
15158
|
+
type: "proxy_request_error"
|
|
15159
|
+
} }, { status: 400 });
|
|
15160
|
+
}
|
|
15161
|
+
await next();
|
|
15162
|
+
});
|
|
15163
|
+
//#endregion
|
|
15164
|
+
//#region src/proxy/middleware/resolve-config.ts
|
|
15165
|
+
const resolveConfig = createMiddleware(async (c, next) => {
|
|
15166
|
+
const resolved = resolveModelConfig(c.var.config, c.var.modelName);
|
|
15167
|
+
c.set("resolvedConfig", resolved);
|
|
15168
|
+
await next();
|
|
15169
|
+
});
|
|
15170
|
+
//#endregion
|
|
15171
|
+
//#region src/proxy/middleware/setup-request.ts
|
|
15172
|
+
const setupRequest = createMiddleware(async (c, next) => {
|
|
15173
|
+
const method = c.req.method;
|
|
15174
|
+
const { pathname, search } = new URL(c.req.url);
|
|
15175
|
+
const path = `${pathname}${search}`;
|
|
15176
|
+
c.set("reqId", requestId());
|
|
15177
|
+
c.set("method", method);
|
|
15178
|
+
c.set("path", path);
|
|
15179
|
+
c.set("upstreamUrl", buildUpstreamUrl(path, c.var.config));
|
|
15180
|
+
c.set("startedAt", Date.now());
|
|
15181
|
+
c.set("bodyMutated", false);
|
|
15182
|
+
await next();
|
|
15183
|
+
});
|
|
15184
|
+
//#endregion
|
|
15185
|
+
//#region src/proxy.ts
|
|
15186
|
+
const injectChain = [
|
|
15187
|
+
parseBody,
|
|
15188
|
+
resolveConfig,
|
|
15189
|
+
injectProvider,
|
|
15190
|
+
injectCacheControl,
|
|
15191
|
+
injectSessionId
|
|
15192
|
+
];
|
|
14940
15193
|
function createProxyServer(config, onReady) {
|
|
14941
15194
|
const app = new Hono();
|
|
15195
|
+
app.use("*", async (c, next) => {
|
|
15196
|
+
c.set("config", config);
|
|
15197
|
+
await next();
|
|
15198
|
+
});
|
|
14942
15199
|
app.get("/health", (c) => {
|
|
14943
15200
|
const globalRouting = buildProviderRouting(config.provider);
|
|
14944
15201
|
return c.json({
|
|
@@ -14948,23 +15205,15 @@ function createProxyServer(config, onReady) {
|
|
|
14948
15205
|
modelOverrides: config.modelOverrides ? Object.keys(config.modelOverrides) : []
|
|
14949
15206
|
});
|
|
14950
15207
|
});
|
|
14951
|
-
app.
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
const
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
if (resolved.error) return resolved.error;
|
|
14961
|
-
const headers = buildRequestHeaders(c.req.raw.headers, config, resolved.inject, resolved.headers);
|
|
14962
|
-
const controller = new AbortController();
|
|
14963
|
-
c.req.raw.signal.addEventListener("abort", () => controller.abort());
|
|
14964
|
-
const upstreamShort = upstreamUrl.replace(/^https?:\/\//, "");
|
|
14965
|
-
const modelLog = resolved.modelName ? ` model=${resolved.modelName}` : "";
|
|
14966
|
-
logger.info(withReq(reqId, `${method} ${path} → ${upstreamShort}${resolved.inject ? " [inject]" : ""}${modelLog}`));
|
|
14967
|
-
return executeUpstream(upstreamUrl, method, headers, resolved.body, controller.signal, path, startedAt, reqId);
|
|
15208
|
+
for (const path of INJECT_PATHS) app.post(path, setupRequest, readBody, ...injectChain, buildUpstreamReq, forwardRequest);
|
|
15209
|
+
app.all("*", setupRequest, readBody, resolveConfig, buildUpstreamReq, forwardRequest);
|
|
15210
|
+
app.onError((err, c) => {
|
|
15211
|
+
const reqId = c.var.reqId ?? "unknown";
|
|
15212
|
+
logger.error(withReq(String(reqId), `Unhandled error: ${err.message}`));
|
|
15213
|
+
return Response.json({ error: {
|
|
15214
|
+
message: "Internal proxy error",
|
|
15215
|
+
type: "proxy_internal_error"
|
|
15216
|
+
} }, { status: 500 });
|
|
14968
15217
|
});
|
|
14969
15218
|
return serve({
|
|
14970
15219
|
fetch: app.fetch,
|
|
@@ -14996,7 +15245,7 @@ function startProxyServer(config, onReady) {
|
|
|
14996
15245
|
}
|
|
14997
15246
|
//#endregion
|
|
14998
15247
|
//#region src/version.ts
|
|
14999
|
-
const version = "0.
|
|
15248
|
+
const version = "0.9.0-beta.1";
|
|
15000
15249
|
//#endregion
|
|
15001
15250
|
//#region src/cli.ts
|
|
15002
15251
|
const argv = process.argv.slice(2);
|
|
@@ -15097,8 +15346,8 @@ const withClient = (fn) => async (args) => {
|
|
|
15097
15346
|
authType: cfg.authType,
|
|
15098
15347
|
onFallback: (path) => {
|
|
15099
15348
|
let endpoint;
|
|
15100
|
-
if (path === "/providers") endpoint = "providers";
|
|
15101
|
-
else if (path === "/models") endpoint = "models";
|
|
15349
|
+
if (path === "/v1/providers") endpoint = "providers";
|
|
15350
|
+
else if (path === "/v1/models") endpoint = "models";
|
|
15102
15351
|
else endpoint = "model providers";
|
|
15103
15352
|
logger.warn(`Custom API did not return ${endpoint}, using OpenRouter data as fallback`);
|
|
15104
15353
|
}
|