omnigateway 0.1.5 → 0.1.7
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 +55 -2
- package/bin/omni.js +709 -325
- package/gateway.js +1028 -264
- package/package.json +1 -1
- package/public/assets/{Chip-Ccqsb_Mq.js → Chip-C69u2U_b.js} +2 -2
- package/public/assets/{CopyValue-CvEMy9Zo.js → CopyValue-CJy2T0H6.js} +1 -1
- package/public/assets/{Field-Devm4K46.js → Field-Sc8mDqJv.js} +1 -1
- package/public/assets/{Lamp-BXSUIAtG.js → Lamp-7jD_9929.js} +2 -2
- package/public/assets/{Meter-Fn8X7t60.js → Meter-C0nUftAa.js} +2 -2
- package/public/assets/{Modal-C4y9zALa.js → Modal-CiVHSJmm.js} +2 -2
- package/public/assets/Rack-CMqGBP4I.js +147 -0
- package/public/assets/{Readout-BKpOZIGX.js → Readout-Dv2dZWtI.js} +4 -4
- package/public/assets/{States-DBzsiHjQ.js → States-D55MhqMx.js} +10 -10
- package/public/assets/{Table-DPDJeeVc.js → Table-DBB3jdgi.js} +1 -1
- package/public/assets/{Toggle-CBq5XAMU.js → Toggle-BoGjb5oi.js} +3 -3
- package/public/assets/_app-p5Mz7sXD.js +1 -0
- package/public/assets/_app.accounts-B5wMO9TH.js +51 -0
- package/public/assets/_app.console-Bka7AkiL.js +25 -0
- package/public/assets/_app.index-MnQuadXb.js +61 -0
- package/public/assets/_app.keys-BX8k8COP.js +39 -0
- package/public/assets/_app.logs-DD-sd4fn.js +33 -0
- package/public/assets/_app.models-CezFNL6y.js +144 -0
- package/public/assets/{_app.settings-Du5Nsp9f.js → _app.settings-D1taAt8B.js} +3 -3
- package/public/assets/{_app.usage-DKq4Hsia.js → _app.usage-BRhEvwVr.js} +28 -28
- package/public/assets/catalog-C_OQ0icG.js +1 -0
- package/public/assets/index-vCuK1fRQ.js +170 -0
- package/public/assets/{login-Gyf-1fSa.js → login-DaC-wVpI.js} +8 -8
- package/public/assets/{queries-BMwc6EvM.js → queries-2E6-CboE.js} +12 -12
- package/public/assets/{trash-2-DFVxurNH.js → trash-2--aH2iKg-.js} +1 -1
- package/public/index.html +2 -2
- package/public/assets/Rack-CRQSwSeB.js +0 -147
- package/public/assets/_app-VCodWes4.js +0 -1
- package/public/assets/_app.accounts-BP3L84us.js +0 -51
- package/public/assets/_app.index-C5Vd-zlp.js +0 -61
- package/public/assets/_app.keys-0e1BHrIZ.js +0 -39
- package/public/assets/_app.logs-DNSND96R.js +0 -33
- package/public/assets/_app.models-DzFIbnem.js +0 -144
- package/public/assets/catalog-DVRPlBJJ.js +0 -1
- package/public/assets/index-BYyeRTQJ.js +0 -170
package/gateway.js
CHANGED
|
@@ -535,7 +535,7 @@ function flushChunk(parts, chunk) {
|
|
|
535
535
|
}
|
|
536
536
|
function pushCodeUnit(parts, chunk, codeUnit) {
|
|
537
537
|
chunk.push(codeUnit);
|
|
538
|
-
if (chunk.length >=
|
|
538
|
+
if (chunk.length >= CHUNK2)
|
|
539
539
|
flushChunk(parts, chunk);
|
|
540
540
|
}
|
|
541
541
|
function pushCodePoint(parts, chunk, cp) {
|
|
@@ -660,8 +660,8 @@ function decodeUTF16LE(bytes) {
|
|
|
660
660
|
}
|
|
661
661
|
function decodeASCII(bytes) {
|
|
662
662
|
const parts = [];
|
|
663
|
-
for (let i = 0;i < bytes.length; i +=
|
|
664
|
-
const end = Math.min(bytes.length, i +
|
|
663
|
+
for (let i = 0;i < bytes.length; i += CHUNK2) {
|
|
664
|
+
const end = Math.min(bytes.length, i + CHUNK2);
|
|
665
665
|
const codes = new Array(end - i);
|
|
666
666
|
for (let j = i, k2 = 0;j < end; j++, k2++) {
|
|
667
667
|
codes[k2] = bytes[j] & 127;
|
|
@@ -672,8 +672,8 @@ function decodeASCII(bytes) {
|
|
|
672
672
|
}
|
|
673
673
|
function decodeLatin1(bytes) {
|
|
674
674
|
const parts = [];
|
|
675
|
-
for (let i = 0;i < bytes.length; i +=
|
|
676
|
-
const end = Math.min(bytes.length, i +
|
|
675
|
+
for (let i = 0;i < bytes.length; i += CHUNK2) {
|
|
676
|
+
const end = Math.min(bytes.length, i + CHUNK2);
|
|
677
677
|
const codes = new Array(end - i);
|
|
678
678
|
for (let j = i, k2 = 0;j < end; j++, k2++) {
|
|
679
679
|
codes[k2] = bytes[j];
|
|
@@ -689,7 +689,7 @@ function decodeWindows1252(bytes) {
|
|
|
689
689
|
const b = bytes[i];
|
|
690
690
|
const extra = b >= 128 && b <= 159 ? WINDOWS_1252_EXTRA[b] : undefined;
|
|
691
691
|
out += extra !== null && extra !== undefined ? extra : String.fromCharCode(b);
|
|
692
|
-
if (out.length >=
|
|
692
|
+
if (out.length >= CHUNK2) {
|
|
693
693
|
parts.push(out);
|
|
694
694
|
out = "";
|
|
695
695
|
}
|
|
@@ -698,7 +698,7 @@ function decodeWindows1252(bytes) {
|
|
|
698
698
|
parts.push(out);
|
|
699
699
|
return parts.join("");
|
|
700
700
|
}
|
|
701
|
-
var WINDOWS_1252_EXTRA, WINDOWS_1252_REVERSE, _utf8Decoder,
|
|
701
|
+
var WINDOWS_1252_EXTRA, WINDOWS_1252_REVERSE, _utf8Decoder, CHUNK2, REPLACEMENT = 65533;
|
|
702
702
|
var init_lib = __esm(() => {
|
|
703
703
|
WINDOWS_1252_EXTRA = {
|
|
704
704
|
128: "\u20AC",
|
|
@@ -733,7 +733,7 @@ var init_lib = __esm(() => {
|
|
|
733
733
|
for (const [code, char] of Object.entries(WINDOWS_1252_EXTRA)) {
|
|
734
734
|
WINDOWS_1252_REVERSE[char] = Number.parseInt(code, 10);
|
|
735
735
|
}
|
|
736
|
-
|
|
736
|
+
CHUNK2 = 32 * 1024;
|
|
737
737
|
});
|
|
738
738
|
|
|
739
739
|
// node_modules/.bun/token-types@6.1.2/node_modules/token-types/lib/index.js
|
|
@@ -5295,35 +5295,6 @@ function createAdminAuth(store, opts) {
|
|
|
5295
5295
|
}
|
|
5296
5296
|
};
|
|
5297
5297
|
}
|
|
5298
|
-
// packages/control/src/config.ts
|
|
5299
|
-
var MIN_KEY_LENGTH = 16;
|
|
5300
|
-
var DECIMAL_INTEGER = /^\d+$/;
|
|
5301
|
-
function optionalText(value, fallback) {
|
|
5302
|
-
return value?.trim() || fallback;
|
|
5303
|
-
}
|
|
5304
|
-
function loadConfig(env) {
|
|
5305
|
-
const encryptionKey = env.OMNI_ENCRYPTION_KEY;
|
|
5306
|
-
if (typeof encryptionKey !== "string" || encryptionKey.length < MIN_KEY_LENGTH) {
|
|
5307
|
-
throw new Error(`OMNI_ENCRYPTION_KEY must be set to at least ${MIN_KEY_LENGTH} characters. ` + "Generate one with: openssl rand -base64 32");
|
|
5308
|
-
}
|
|
5309
|
-
const host = optionalText(env.OMNI_HOST, "127.0.0.1");
|
|
5310
|
-
const rawPort = env.OMNI_PORT ?? "9000";
|
|
5311
|
-
const port = Number(rawPort);
|
|
5312
|
-
if (!DECIMAL_INTEGER.test(rawPort) || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
5313
|
-
throw new Error(`OMNI_PORT must be an integer between 1 and 65535, got "${rawPort}"`);
|
|
5314
|
-
}
|
|
5315
|
-
const derivedBaseUrl = `http://${host}:${port}`;
|
|
5316
|
-
const baseUrl = optionalText(env.OMNI_BASE_URL, derivedBaseUrl).replace(/\/+$/, "") || derivedBaseUrl;
|
|
5317
|
-
const staticDir = env.OMNI_STATIC_DIR?.trim();
|
|
5318
|
-
return {
|
|
5319
|
-
port,
|
|
5320
|
-
host,
|
|
5321
|
-
databasePath: optionalText(env.OMNI_DB_PATH, "./omnigateway.db"),
|
|
5322
|
-
encryptionKey,
|
|
5323
|
-
baseUrl,
|
|
5324
|
-
staticDir: staticDir === undefined || staticDir.length === 0 ? null : staticDir
|
|
5325
|
-
};
|
|
5326
|
-
}
|
|
5327
5298
|
// packages/ir/src/capabilities.ts
|
|
5328
5299
|
var PROVIDER_CAPABILITIES = {
|
|
5329
5300
|
anthropic: { tools: true, images: true, reasoning: true },
|
|
@@ -5380,7 +5351,134 @@ class GatewayError extends Error {
|
|
|
5380
5351
|
this.retryAfterMs = opts?.retryAfterMs;
|
|
5381
5352
|
}
|
|
5382
5353
|
}
|
|
5354
|
+
// packages/ir/src/logger.ts
|
|
5355
|
+
var LEVELS = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
5356
|
+
var FIELD_ORDER = [
|
|
5357
|
+
"requestId",
|
|
5358
|
+
"surface",
|
|
5359
|
+
"status",
|
|
5360
|
+
"provider",
|
|
5361
|
+
"model",
|
|
5362
|
+
"requestedModel",
|
|
5363
|
+
"credentialId",
|
|
5364
|
+
"apiKeyId",
|
|
5365
|
+
"attempt",
|
|
5366
|
+
"attempts",
|
|
5367
|
+
"code",
|
|
5368
|
+
"retryable",
|
|
5369
|
+
"stream",
|
|
5370
|
+
"inputTokens",
|
|
5371
|
+
"outputTokens",
|
|
5372
|
+
"cacheReadTokens",
|
|
5373
|
+
"cacheWriteTokens",
|
|
5374
|
+
"costUsd",
|
|
5375
|
+
"ttftMs",
|
|
5376
|
+
"durationMs",
|
|
5377
|
+
"retryAfterMs",
|
|
5378
|
+
"count",
|
|
5379
|
+
"rawCount",
|
|
5380
|
+
"dailyCount",
|
|
5381
|
+
"host",
|
|
5382
|
+
"port",
|
|
5383
|
+
"path",
|
|
5384
|
+
"reason"
|
|
5385
|
+
];
|
|
5386
|
+
var REASON_MAX = 200;
|
|
5387
|
+
var LEVEL_COLOR = {
|
|
5388
|
+
debug: "\x1B[2;37m",
|
|
5389
|
+
info: "\x1B[36m",
|
|
5390
|
+
warn: "\x1B[33m",
|
|
5391
|
+
error: "\x1B[31m"
|
|
5392
|
+
};
|
|
5393
|
+
var RESET = "\x1B[0m";
|
|
5394
|
+
var NEEDS_QUOTING = /[\s="]/;
|
|
5395
|
+
function renderValue(value) {
|
|
5396
|
+
if (value === null)
|
|
5397
|
+
return "null";
|
|
5398
|
+
if (typeof value !== "string")
|
|
5399
|
+
return String(value);
|
|
5400
|
+
return NEEDS_QUOTING.test(value) ? JSON.stringify(value) : value;
|
|
5401
|
+
}
|
|
5402
|
+
function formatLine(level, at, msg, fields, color) {
|
|
5403
|
+
const label = level.toUpperCase().padEnd(5, " ");
|
|
5404
|
+
const head = `${new Date(at).toISOString()} ${color ? `${LEVEL_COLOR[level]}${label}${RESET}` : label} ${msg}`;
|
|
5405
|
+
if (fields === undefined)
|
|
5406
|
+
return head;
|
|
5407
|
+
const parts = [];
|
|
5408
|
+
for (const key of FIELD_ORDER) {
|
|
5409
|
+
const value = fields[key];
|
|
5410
|
+
if (value === undefined)
|
|
5411
|
+
continue;
|
|
5412
|
+
const capped = key === "reason" && typeof value === "string" && value.length > REASON_MAX ? `${value.slice(0, REASON_MAX)}\u2026` : value;
|
|
5413
|
+
parts.push(`${key}=${renderValue(capped)}`);
|
|
5414
|
+
}
|
|
5415
|
+
return parts.length === 0 ? head : `${head} ${parts.join(" ")}`;
|
|
5416
|
+
}
|
|
5417
|
+
var ANSI = /\u001B\[[0-9;]*m/g;
|
|
5418
|
+
var HEAD = /^(\S+) (DEBUG|INFO|WARN|ERROR) {1,2}(.*)$/;
|
|
5419
|
+
var FIELDS = / {2}(?=[a-zA-Z]+=)/;
|
|
5420
|
+
function parseLine(raw) {
|
|
5421
|
+
const absent = { raw, at: null, level: null, msg: null };
|
|
5422
|
+
const match = HEAD.exec(raw.replace(ANSI, ""));
|
|
5423
|
+
if (match === null)
|
|
5424
|
+
return absent;
|
|
5425
|
+
const [, instant, label, tail] = match;
|
|
5426
|
+
if (instant === undefined || label === undefined || tail === undefined)
|
|
5427
|
+
return absent;
|
|
5428
|
+
const at = Date.parse(instant);
|
|
5429
|
+
if (Number.isNaN(at))
|
|
5430
|
+
return absent;
|
|
5431
|
+
const msg = tail.split(FIELDS, 1)[0] ?? tail;
|
|
5432
|
+
return { raw, at, level: label.toLowerCase(), msg };
|
|
5433
|
+
}
|
|
5434
|
+
function createLogger(opts) {
|
|
5435
|
+
const threshold = LEVELS[opts.level];
|
|
5436
|
+
const now = opts.now ?? (() => Date.now());
|
|
5437
|
+
const color = opts.color ?? false;
|
|
5438
|
+
const emit = (level, msg, fields) => {
|
|
5439
|
+
if (LEVELS[level] < threshold)
|
|
5440
|
+
return;
|
|
5441
|
+
try {
|
|
5442
|
+
opts.write(formatLine(level, now(), msg, fields, color));
|
|
5443
|
+
} catch {}
|
|
5444
|
+
};
|
|
5445
|
+
return {
|
|
5446
|
+
debug: (msg, fields) => emit("debug", msg, fields),
|
|
5447
|
+
info: (msg, fields) => emit("info", msg, fields),
|
|
5448
|
+
warn: (msg, fields) => emit("warn", msg, fields),
|
|
5449
|
+
error: (msg, fields) => emit("error", msg, fields),
|
|
5450
|
+
enabled: (level) => LEVELS[level] >= threshold
|
|
5451
|
+
};
|
|
5452
|
+
}
|
|
5453
|
+
var noopLogger = {
|
|
5454
|
+
debug: () => {},
|
|
5455
|
+
info: () => {},
|
|
5456
|
+
warn: () => {},
|
|
5457
|
+
error: () => {},
|
|
5458
|
+
enabled: () => false
|
|
5459
|
+
};
|
|
5460
|
+
function parseLogLevel(value) {
|
|
5461
|
+
const normalized = value?.trim().toLowerCase();
|
|
5462
|
+
if (normalized === undefined || normalized.length === 0)
|
|
5463
|
+
return null;
|
|
5464
|
+
return Object.hasOwn(LEVELS, normalized) ? normalized : null;
|
|
5465
|
+
}
|
|
5466
|
+
// packages/ir/src/request.ts
|
|
5467
|
+
function cacheControlOf(block) {
|
|
5468
|
+
return block.type === "thinking" ? undefined : block.cacheControl;
|
|
5469
|
+
}
|
|
5383
5470
|
// packages/ir/src/stream.ts
|
|
5471
|
+
function usageFromPromptTotal(promptTokens, outputTokens, cacheReadTokens, cacheWriteTokens = 0) {
|
|
5472
|
+
return {
|
|
5473
|
+
inputTokens: Math.max(0, promptTokens - cacheReadTokens - cacheWriteTokens),
|
|
5474
|
+
outputTokens,
|
|
5475
|
+
cacheReadTokens,
|
|
5476
|
+
cacheWriteTokens
|
|
5477
|
+
};
|
|
5478
|
+
}
|
|
5479
|
+
function promptTokens(usage) {
|
|
5480
|
+
return usage.inputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
|
|
5481
|
+
}
|
|
5384
5482
|
function collect(events) {
|
|
5385
5483
|
let id = "";
|
|
5386
5484
|
let model = "";
|
|
@@ -5410,7 +5508,7 @@ function collect(events) {
|
|
|
5410
5508
|
else if (ev.delta.type === "thinking" && acc.kind === "thinking")
|
|
5411
5509
|
acc.text += ev.delta.text;
|
|
5412
5510
|
else if (ev.delta.type === "thinkingSignature" && acc.kind === "thinking")
|
|
5413
|
-
acc.signature = ev.delta.signature;
|
|
5511
|
+
acc.signature = (acc.signature ?? "") + ev.delta.signature;
|
|
5414
5512
|
else if (ev.delta.type === "toolJson" && acc.kind === "toolUse")
|
|
5415
5513
|
acc.json += ev.delta.partial;
|
|
5416
5514
|
break;
|
|
@@ -5473,6 +5571,41 @@ function validateRequest(req) {
|
|
|
5473
5571
|
}
|
|
5474
5572
|
return { ...req, messages: cleaned };
|
|
5475
5573
|
}
|
|
5574
|
+
// packages/control/src/config.ts
|
|
5575
|
+
var MIN_KEY_LENGTH = 16;
|
|
5576
|
+
var DECIMAL_INTEGER = /^\d+$/;
|
|
5577
|
+
function optionalText(value, fallback) {
|
|
5578
|
+
return value?.trim() || fallback;
|
|
5579
|
+
}
|
|
5580
|
+
function loadConfig(env) {
|
|
5581
|
+
const encryptionKey = env.OMNI_ENCRYPTION_KEY;
|
|
5582
|
+
if (typeof encryptionKey !== "string" || encryptionKey.length < MIN_KEY_LENGTH) {
|
|
5583
|
+
throw new Error(`OMNI_ENCRYPTION_KEY must be set to at least ${MIN_KEY_LENGTH} characters. ` + "Generate one with: openssl rand -base64 32");
|
|
5584
|
+
}
|
|
5585
|
+
const host = optionalText(env.OMNI_HOST, "127.0.0.1");
|
|
5586
|
+
const rawPort = env.OMNI_PORT ?? "9000";
|
|
5587
|
+
const port = Number(rawPort);
|
|
5588
|
+
if (!DECIMAL_INTEGER.test(rawPort) || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
5589
|
+
throw new Error(`OMNI_PORT must be an integer between 1 and 65535, got "${rawPort}"`);
|
|
5590
|
+
}
|
|
5591
|
+
const derivedBaseUrl = `http://${host}:${port}`;
|
|
5592
|
+
const baseUrl = optionalText(env.OMNI_BASE_URL, derivedBaseUrl).replace(/\/+$/, "") || derivedBaseUrl;
|
|
5593
|
+
const staticDir = env.OMNI_STATIC_DIR?.trim();
|
|
5594
|
+
const logFile = env.OMNI_LOG_FILE?.trim();
|
|
5595
|
+
const rawLogLevel = env.OMNI_LOG_LEVEL?.trim();
|
|
5596
|
+
const logLevel = parseLogLevel(rawLogLevel);
|
|
5597
|
+
return {
|
|
5598
|
+
logLevel: logLevel ?? "info",
|
|
5599
|
+
logLevelFallbackFrom: logLevel === null && rawLogLevel ? rawLogLevel : null,
|
|
5600
|
+
port,
|
|
5601
|
+
host,
|
|
5602
|
+
databasePath: optionalText(env.OMNI_DB_PATH, "./omnigateway.db"),
|
|
5603
|
+
encryptionKey,
|
|
5604
|
+
baseUrl,
|
|
5605
|
+
staticDir: staticDir === undefined || staticDir.length === 0 ? null : staticDir,
|
|
5606
|
+
logFile: logFile === undefined || logFile.length === 0 ? null : logFile
|
|
5607
|
+
};
|
|
5608
|
+
}
|
|
5476
5609
|
// packages/providers/src/body.ts
|
|
5477
5610
|
var BODY_ORDER = {
|
|
5478
5611
|
anthropic: [
|
|
@@ -5559,8 +5692,13 @@ function applyAnthropicSystem(system) {
|
|
|
5559
5692
|
let rewritten = text;
|
|
5560
5693
|
for (const [from, to] of REWRITES)
|
|
5561
5694
|
rewritten = rewritten.replaceAll(from, to);
|
|
5562
|
-
if (rewritten.trim().length > 0)
|
|
5563
|
-
kept.push({
|
|
5695
|
+
if (rewritten.trim().length > 0) {
|
|
5696
|
+
kept.push({
|
|
5697
|
+
type: "text",
|
|
5698
|
+
text: rewritten,
|
|
5699
|
+
...block.cache_control === undefined ? {} : { cache_control: block.cache_control }
|
|
5700
|
+
});
|
|
5701
|
+
}
|
|
5564
5702
|
}
|
|
5565
5703
|
return [{ type: "text", text: billingBlock() }, { type: "text", text: AGENT_PREAMBLE }, ...kept];
|
|
5566
5704
|
}
|
|
@@ -5864,6 +6002,8 @@ async function* decodeAnthropic(messages) {
|
|
|
5864
6002
|
let inputTokens = 0;
|
|
5865
6003
|
let cacheReadTokens = 0;
|
|
5866
6004
|
let cacheWriteTokens = 0;
|
|
6005
|
+
let cacheWrite5mTokens;
|
|
6006
|
+
let cacheWrite1hTokens;
|
|
5867
6007
|
let outputTokens = 0;
|
|
5868
6008
|
let stopReason = "endTurn";
|
|
5869
6009
|
let terminal = false;
|
|
@@ -5877,6 +6017,8 @@ async function* decodeAnthropic(messages) {
|
|
|
5877
6017
|
inputTokens = m.usage?.input_tokens ?? 0;
|
|
5878
6018
|
cacheReadTokens = m.usage?.cache_read_input_tokens ?? 0;
|
|
5879
6019
|
cacheWriteTokens = m.usage?.cache_creation_input_tokens ?? 0;
|
|
6020
|
+
cacheWrite5mTokens = m.usage?.cache_creation?.ephemeral_5m_input_tokens;
|
|
6021
|
+
cacheWrite1hTokens = m.usage?.cache_creation?.ephemeral_1h_input_tokens;
|
|
5880
6022
|
yield { type: "start", id: String(m.id ?? ""), model: String(m.model ?? "") };
|
|
5881
6023
|
break;
|
|
5882
6024
|
}
|
|
@@ -5886,7 +6028,7 @@ async function* decodeAnthropic(messages) {
|
|
|
5886
6028
|
if (cb.type === "text")
|
|
5887
6029
|
yield { type: "blockStart", index, block: { type: "text" } };
|
|
5888
6030
|
else if (cb.type === "thinking")
|
|
5889
|
-
yield { type: "blockStart", index, block: { type: "thinking" } };
|
|
6031
|
+
yield { type: "blockStart", index, block: { type: "thinking", signed: true } };
|
|
5890
6032
|
else if (cb.type === "tool_use")
|
|
5891
6033
|
yield {
|
|
5892
6034
|
type: "blockStart",
|
|
@@ -5906,7 +6048,7 @@ async function* decodeAnthropic(messages) {
|
|
|
5906
6048
|
index,
|
|
5907
6049
|
delta: { type: "thinking", text: delta.thinking ?? "" }
|
|
5908
6050
|
};
|
|
5909
|
-
else if (delta.type === "signature_delta")
|
|
6051
|
+
else if (delta.type === "signature_delta" && (delta.signature ?? "") !== "")
|
|
5910
6052
|
yield {
|
|
5911
6053
|
type: "blockDelta",
|
|
5912
6054
|
index,
|
|
@@ -5936,7 +6078,14 @@ async function* decodeAnthropic(messages) {
|
|
|
5936
6078
|
yield {
|
|
5937
6079
|
type: "end",
|
|
5938
6080
|
stopReason,
|
|
5939
|
-
usage: {
|
|
6081
|
+
usage: {
|
|
6082
|
+
inputTokens,
|
|
6083
|
+
outputTokens,
|
|
6084
|
+
cacheReadTokens,
|
|
6085
|
+
cacheWriteTokens,
|
|
6086
|
+
...cacheWrite5mTokens === undefined ? {} : { cacheWrite5mTokens },
|
|
6087
|
+
...cacheWrite1hTokens === undefined ? {} : { cacheWrite1hTokens }
|
|
6088
|
+
}
|
|
5940
6089
|
};
|
|
5941
6090
|
break;
|
|
5942
6091
|
case "error": {
|
|
@@ -5966,25 +6115,36 @@ async function* decodeAnthropic(messages) {
|
|
|
5966
6115
|
|
|
5967
6116
|
// packages/providers/src/anthropic/wire.ts
|
|
5968
6117
|
var OAUTH_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude.";
|
|
6118
|
+
function wireCacheControl(c) {
|
|
6119
|
+
if (c === undefined)
|
|
6120
|
+
return {};
|
|
6121
|
+
return { cache_control: { type: c.type, ...c.ttl === undefined ? {} : { ttl: c.ttl } } };
|
|
6122
|
+
}
|
|
6123
|
+
function isReplayableThinking(b) {
|
|
6124
|
+
return b.type !== "thinking" || b.signature !== undefined && b.signature !== "";
|
|
6125
|
+
}
|
|
5969
6126
|
function encodeBlock(b) {
|
|
6127
|
+
const cache = wireCacheControl(cacheControlOf(b));
|
|
5970
6128
|
switch (b.type) {
|
|
5971
6129
|
case "text":
|
|
5972
|
-
return { type: "text", text: b.text };
|
|
6130
|
+
return { type: "text", text: b.text, ...cache };
|
|
5973
6131
|
case "image":
|
|
5974
6132
|
return {
|
|
5975
6133
|
type: "image",
|
|
5976
|
-
source: { type: "base64", media_type: b.mediaType, data: b.data }
|
|
6134
|
+
source: { type: "base64", media_type: b.mediaType, data: b.data },
|
|
6135
|
+
...cache
|
|
5977
6136
|
};
|
|
5978
6137
|
case "thinking":
|
|
5979
6138
|
return { type: "thinking", thinking: b.text, signature: b.signature };
|
|
5980
6139
|
case "toolUse":
|
|
5981
|
-
return { type: "tool_use", id: b.id, name: b.name, input: b.input };
|
|
6140
|
+
return { type: "tool_use", id: b.id, name: b.name, input: b.input, ...cache };
|
|
5982
6141
|
case "toolResult":
|
|
5983
6142
|
return {
|
|
5984
6143
|
type: "tool_result",
|
|
5985
6144
|
tool_use_id: b.toolUseId,
|
|
5986
6145
|
content: b.content,
|
|
5987
|
-
is_error: b.isError
|
|
6146
|
+
is_error: b.isError,
|
|
6147
|
+
...cache
|
|
5988
6148
|
};
|
|
5989
6149
|
}
|
|
5990
6150
|
}
|
|
@@ -5992,6 +6152,16 @@ function encodeSystemTurn(content) {
|
|
|
5992
6152
|
return content.flatMap((b) => b.type === "text" ? [b.text] : []).join(`
|
|
5993
6153
|
`);
|
|
5994
6154
|
}
|
|
6155
|
+
function systemCacheControl(req) {
|
|
6156
|
+
const cacheable = req.messages.flatMap((message) => message.content.flatMap((block) => block.type === "thinking" ? [] : [{ role: message.role, block }]));
|
|
6157
|
+
const markedSystemBlocks = cacheable.filter(({ role, block }) => role === "system" && cacheControlOf(block) !== undefined);
|
|
6158
|
+
const final = markedSystemBlocks.at(-1);
|
|
6159
|
+
const promoted = final !== undefined && final.block.type === "text" && cacheable.at(-1) === final ? cacheControlOf(final.block) : undefined;
|
|
6160
|
+
return {
|
|
6161
|
+
...promoted === undefined ? {} : { promoted },
|
|
6162
|
+
lost: markedSystemBlocks.length > (promoted === undefined ? 0 : 1)
|
|
6163
|
+
};
|
|
6164
|
+
}
|
|
5995
6165
|
function encodeToolChoice(c) {
|
|
5996
6166
|
switch (c.type) {
|
|
5997
6167
|
case "auto":
|
|
@@ -6006,16 +6176,39 @@ function encodeToolChoice(c) {
|
|
|
6006
6176
|
}
|
|
6007
6177
|
function toWire(req, model, opts) {
|
|
6008
6178
|
const degradations = [];
|
|
6009
|
-
|
|
6179
|
+
const note = (d) => {
|
|
6180
|
+
if (!degradations.includes(d))
|
|
6181
|
+
degradations.push(d);
|
|
6182
|
+
};
|
|
6183
|
+
let system = req.system?.flatMap((b) => b.type === "text" ? [{ type: "text", text: b.text, ...wireCacheControl(b.cacheControl) }] : []);
|
|
6010
6184
|
if (opts.oauth && system?.[0]?.text !== OAUTH_IDENTITY) {
|
|
6011
6185
|
system = [{ type: "text", text: OAUTH_IDENTITY }, ...system ?? []];
|
|
6012
|
-
|
|
6186
|
+
note("anthropic:oauth-system-prefix");
|
|
6013
6187
|
}
|
|
6188
|
+
const systemCache = systemCacheControl(req);
|
|
6189
|
+
if (systemCache.lost)
|
|
6190
|
+
note("anthropic:system-turn-cache-control-dropped");
|
|
6014
6191
|
const body = {
|
|
6015
6192
|
model,
|
|
6016
|
-
messages: req.messages.
|
|
6193
|
+
messages: req.messages.flatMap((m) => {
|
|
6194
|
+
if (m.role !== "system") {
|
|
6195
|
+
const replayable = m.content.filter(isReplayableThinking);
|
|
6196
|
+
if (replayable.length !== m.content.length)
|
|
6197
|
+
note("anthropic:unsigned-thinking-dropped");
|
|
6198
|
+
if (replayable.length === 0)
|
|
6199
|
+
return [];
|
|
6200
|
+
return [{ role: m.role, content: replayable.map(encodeBlock) }];
|
|
6201
|
+
}
|
|
6202
|
+
return [{ role: m.role, content: encodeSystemTurn(m.content) }];
|
|
6203
|
+
}),
|
|
6017
6204
|
max_tokens: req.maxTokens ?? 4096,
|
|
6018
|
-
stream: req.stream
|
|
6205
|
+
stream: req.stream,
|
|
6206
|
+
...systemCache.promoted === undefined ? {} : {
|
|
6207
|
+
cache_control: {
|
|
6208
|
+
type: systemCache.promoted.type,
|
|
6209
|
+
...systemCache.promoted.ttl === undefined ? {} : { ttl: systemCache.promoted.ttl }
|
|
6210
|
+
}
|
|
6211
|
+
}
|
|
6019
6212
|
};
|
|
6020
6213
|
if (system !== undefined && system.length > 0)
|
|
6021
6214
|
body.system = system;
|
|
@@ -6027,7 +6220,8 @@ function toWire(req, model, opts) {
|
|
|
6027
6220
|
body.tools = req.tools.map((t) => ({
|
|
6028
6221
|
name: t.name,
|
|
6029
6222
|
...t.description === undefined ? {} : { description: t.description },
|
|
6030
|
-
input_schema: t.inputSchema
|
|
6223
|
+
input_schema: t.inputSchema,
|
|
6224
|
+
...wireCacheControl(t.cacheControl)
|
|
6031
6225
|
}));
|
|
6032
6226
|
}
|
|
6033
6227
|
if (req.toolChoice !== undefined)
|
|
@@ -6067,12 +6261,13 @@ var anthropicAdapter = {
|
|
|
6067
6261
|
const { body, degradations } = toWire(req.request, req.model, { oauth });
|
|
6068
6262
|
const withSystem = {
|
|
6069
6263
|
...body,
|
|
6070
|
-
system: applyAnthropicSystem(body.system ?? [])
|
|
6264
|
+
system: applyAnthropicSystem(body.system ?? []),
|
|
6265
|
+
stream: true
|
|
6071
6266
|
};
|
|
6072
6267
|
const protocol = [
|
|
6073
6268
|
["Content-Type", "application/json"],
|
|
6074
6269
|
["anthropic-version", API_VERSION],
|
|
6075
|
-
["Accept",
|
|
6270
|
+
["Accept", "text/event-stream"]
|
|
6076
6271
|
];
|
|
6077
6272
|
const betas = new Set(req.request.betas ?? []);
|
|
6078
6273
|
if (oauth) {
|
|
@@ -6091,6 +6286,7 @@ var anthropicAdapter = {
|
|
|
6091
6286
|
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
6092
6287
|
const bodyString = signAnthropicBody(JSON.stringify(orderFields(withSystem, BODY_ORDER.anthropic)));
|
|
6093
6288
|
const res = await req.http({
|
|
6289
|
+
provider: "anthropic",
|
|
6094
6290
|
url: BASE_URL,
|
|
6095
6291
|
method: "POST",
|
|
6096
6292
|
headers,
|
|
@@ -6111,22 +6307,26 @@ var ANTHROPIC_MODELS = {
|
|
|
6111
6307
|
{
|
|
6112
6308
|
id: "claude-fable-5",
|
|
6113
6309
|
label: "Claude Fable 5",
|
|
6114
|
-
pricing: { input: 10, output: 50, cacheRead: 1 }
|
|
6310
|
+
pricing: { input: 10, output: 50, cacheRead: 1, cacheWrite5m: 12.5, cacheWrite1h: 20 },
|
|
6311
|
+
limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
|
|
6115
6312
|
},
|
|
6116
6313
|
{
|
|
6117
6314
|
id: "claude-opus-5",
|
|
6118
6315
|
label: "Claude Opus 5",
|
|
6119
|
-
pricing: { input: 5, output: 25, cacheRead: 0.5 }
|
|
6316
|
+
pricing: { input: 5, output: 25, cacheRead: 0.5, cacheWrite5m: 6.25, cacheWrite1h: 10 },
|
|
6317
|
+
limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
|
|
6120
6318
|
},
|
|
6121
6319
|
{
|
|
6122
6320
|
id: "claude-sonnet-5",
|
|
6123
6321
|
label: "Claude Sonnet 5",
|
|
6124
|
-
pricing: { input: 3, output: 15, cacheRead: 0.3 }
|
|
6322
|
+
pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 3.75, cacheWrite1h: 6 },
|
|
6323
|
+
limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
|
|
6125
6324
|
},
|
|
6126
6325
|
{
|
|
6127
6326
|
id: "claude-haiku-4-5",
|
|
6128
6327
|
label: "Claude Haiku 4.5",
|
|
6129
|
-
pricing: { input: 1, output: 5, cacheRead: 0.1 }
|
|
6328
|
+
pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
|
|
6329
|
+
limits: { contextWindow: 200000, maxOutputTokens: 64000 }
|
|
6130
6330
|
}
|
|
6131
6331
|
]
|
|
6132
6332
|
};
|
|
@@ -6138,22 +6338,26 @@ var KIMI_MODELS = {
|
|
|
6138
6338
|
{
|
|
6139
6339
|
id: "k3-256k",
|
|
6140
6340
|
label: "Kimi K3 \u2014 256K",
|
|
6141
|
-
pricing: { input: 3, output: 15, cacheRead: 0.3 }
|
|
6341
|
+
pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6342
|
+
limits: { contextWindow: 262144, maxOutputTokens: 131072 }
|
|
6142
6343
|
},
|
|
6143
6344
|
{
|
|
6144
6345
|
id: "k3",
|
|
6145
6346
|
label: "Kimi K3 \u2014 up to 1M",
|
|
6146
|
-
pricing: { input: 3, output: 15, cacheRead: 0.3 }
|
|
6347
|
+
pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6348
|
+
limits: { contextWindow: 1048576, maxOutputTokens: 131072 }
|
|
6147
6349
|
},
|
|
6148
6350
|
{
|
|
6149
6351
|
id: "kimi-for-coding",
|
|
6150
6352
|
label: "Kimi K2.7 Code",
|
|
6151
|
-
pricing: { input: 0.95, output: 4, cacheRead: 0.19 }
|
|
6353
|
+
pricing: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6354
|
+
limits: { contextWindow: 262144, maxOutputTokens: 131072 }
|
|
6152
6355
|
},
|
|
6153
6356
|
{
|
|
6154
6357
|
id: "kimi-for-coding-highspeed",
|
|
6155
6358
|
label: "Kimi K2.7 Code \u2014 High Speed",
|
|
6156
|
-
pricing: { input: 0.95, output: 8, cacheRead: 0.19 }
|
|
6359
|
+
pricing: { input: 0.95, output: 8, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6360
|
+
limits: { contextWindow: 262144, maxOutputTokens: 131072 }
|
|
6157
6361
|
}
|
|
6158
6362
|
]
|
|
6159
6363
|
};
|
|
@@ -6165,22 +6369,30 @@ var OPENAI_MODELS = {
|
|
|
6165
6369
|
{
|
|
6166
6370
|
id: "gpt-5.6",
|
|
6167
6371
|
label: "GPT-5.6 \u2014 routes to Sol",
|
|
6168
|
-
pricing: { input: 5, output: 30, cacheRead: 0.5 }
|
|
6372
|
+
pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6373
|
+
limits: { contextWindow: 922000, maxOutputTokens: 128000 },
|
|
6374
|
+
oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
|
|
6169
6375
|
},
|
|
6170
6376
|
{
|
|
6171
6377
|
id: "gpt-5.6-sol",
|
|
6172
6378
|
label: "GPT-5.6 Sol \u2014 deepest reasoning",
|
|
6173
|
-
pricing: { input: 5, output: 30, cacheRead: 0.5 }
|
|
6379
|
+
pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6380
|
+
limits: { contextWindow: 922000, maxOutputTokens: 128000 },
|
|
6381
|
+
oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
|
|
6174
6382
|
},
|
|
6175
6383
|
{
|
|
6176
6384
|
id: "gpt-5.6-terra",
|
|
6177
6385
|
label: "GPT-5.6 Terra \u2014 balanced",
|
|
6178
|
-
pricing: { input: 2, output: 12, cacheRead: 0.2 }
|
|
6386
|
+
pricing: { input: 2, output: 12, cacheRead: 0.2, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6387
|
+
limits: { contextWindow: 922000, maxOutputTokens: 128000 },
|
|
6388
|
+
oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
|
|
6179
6389
|
},
|
|
6180
6390
|
{
|
|
6181
6391
|
id: "gpt-5.6-luna",
|
|
6182
6392
|
label: "GPT-5.6 Luna \u2014 fastest",
|
|
6183
|
-
pricing: { input: 0.2, output: 1.2, cacheRead: 0.02 }
|
|
6393
|
+
pricing: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6394
|
+
limits: { contextWindow: 922000, maxOutputTokens: 128000 },
|
|
6395
|
+
oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
|
|
6184
6396
|
}
|
|
6185
6397
|
]
|
|
6186
6398
|
};
|
|
@@ -6192,15 +6404,38 @@ var PROVIDER_MODEL_CATALOG = {
|
|
|
6192
6404
|
kimi: KIMI_MODELS
|
|
6193
6405
|
};
|
|
6194
6406
|
function catalogPricing(provider, model) {
|
|
6195
|
-
return PROVIDER_MODEL_CATALOG[provider]
|
|
6407
|
+
return PROVIDER_MODEL_CATALOG[provider]?.models.find((entry) => entry.id === model)?.pricing ?? null;
|
|
6408
|
+
}
|
|
6409
|
+
function catalogLimits(provider, model, auth = "apiKey") {
|
|
6410
|
+
const entry = PROVIDER_MODEL_CATALOG[provider]?.models.find((choice) => choice.id === model);
|
|
6411
|
+
if (entry === undefined)
|
|
6412
|
+
return null;
|
|
6413
|
+
return (auth === "oauth" ? entry.oauthLimits : undefined) ?? entry.limits;
|
|
6196
6414
|
}
|
|
6197
6415
|
// packages/providers/src/http-client.ts
|
|
6198
6416
|
import { request as httpRequest } from "http";
|
|
6199
6417
|
import { request as httpsRequest } from "https";
|
|
6200
6418
|
import { Readable } from "stream";
|
|
6201
|
-
function nodeHttpClient() {
|
|
6419
|
+
function nodeHttpClient(options = {}) {
|
|
6420
|
+
const logger2 = options.logger ?? noopLogger;
|
|
6421
|
+
const now = options.now ?? (() => Date.now());
|
|
6202
6422
|
return (req) => new Promise((resolve, reject) => {
|
|
6203
6423
|
const url = new URL(req.url);
|
|
6424
|
+
const startedAt = now();
|
|
6425
|
+
let traced = false;
|
|
6426
|
+
const trace = (status, failed = false) => {
|
|
6427
|
+
if (traced || !logger2.enabled("debug"))
|
|
6428
|
+
return;
|
|
6429
|
+
traced = true;
|
|
6430
|
+
logger2.debug("upstream http", {
|
|
6431
|
+
provider: req.provider,
|
|
6432
|
+
status,
|
|
6433
|
+
host: url.host,
|
|
6434
|
+
path: url.pathname,
|
|
6435
|
+
durationMs: now() - startedAt,
|
|
6436
|
+
reason: failed ? "transport error" : undefined
|
|
6437
|
+
});
|
|
6438
|
+
};
|
|
6204
6439
|
const send = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
6205
6440
|
const bodyBytes = Buffer.from(req.body, "utf8");
|
|
6206
6441
|
const headers = {};
|
|
@@ -6228,6 +6463,7 @@ function nodeHttpClient() {
|
|
|
6228
6463
|
else if (typeof v === "string")
|
|
6229
6464
|
responseHeaders.set(k, v);
|
|
6230
6465
|
}
|
|
6466
|
+
trace(incoming.statusCode);
|
|
6231
6467
|
resolve({
|
|
6232
6468
|
status: incoming.statusCode ?? 0,
|
|
6233
6469
|
headers: responseHeaders,
|
|
@@ -6243,17 +6479,17 @@ function nodeHttpClient() {
|
|
|
6243
6479
|
});
|
|
6244
6480
|
});
|
|
6245
6481
|
const onAbort = () => outgoing.destroy(new Error("aborted"));
|
|
6246
|
-
if (req.signal.aborted) {
|
|
6247
|
-
outgoing.destroy(new Error("aborted"));
|
|
6248
|
-
reject(new Error("aborted"));
|
|
6249
|
-
return;
|
|
6250
|
-
}
|
|
6251
|
-
req.signal.addEventListener("abort", onAbort, { once: true });
|
|
6252
6482
|
outgoing.on("error", (err) => {
|
|
6253
6483
|
req.signal.removeEventListener("abort", onAbort);
|
|
6484
|
+
trace(undefined, true);
|
|
6254
6485
|
reject(err);
|
|
6255
6486
|
});
|
|
6256
6487
|
outgoing.on("close", () => req.signal.removeEventListener("abort", onAbort));
|
|
6488
|
+
if (req.signal.aborted) {
|
|
6489
|
+
outgoing.destroy(new Error("aborted"));
|
|
6490
|
+
return;
|
|
6491
|
+
}
|
|
6492
|
+
req.signal.addEventListener("abort", onAbort, { once: true });
|
|
6257
6493
|
if (bodyBytes.byteLength > 0)
|
|
6258
6494
|
outgoing.write(bodyBytes);
|
|
6259
6495
|
outgoing.end();
|
|
@@ -6322,12 +6558,8 @@ async function* decodeChat(messages) {
|
|
|
6322
6558
|
yield { type: "start", id: String(d.id ?? ""), model: String(d.model ?? "") };
|
|
6323
6559
|
}
|
|
6324
6560
|
if (d.usage) {
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
outputTokens: d.usage.completion_tokens ?? 0,
|
|
6328
|
-
cacheReadTokens: d.usage.prompt_cache_hit_tokens ?? 0,
|
|
6329
|
-
cacheWriteTokens: 0
|
|
6330
|
-
};
|
|
6561
|
+
const details = d.usage.prompt_tokens_details;
|
|
6562
|
+
usage = usageFromPromptTotal(d.usage.prompt_tokens ?? 0, d.usage.completion_tokens ?? 0, details?.cached_tokens ?? d.usage.prompt_cache_hit_tokens ?? 0, details?.cache_creation_tokens ?? details?.cache_write_tokens ?? 0);
|
|
6331
6563
|
}
|
|
6332
6564
|
const choice = d.choices?.[0];
|
|
6333
6565
|
if (!choice)
|
|
@@ -6453,7 +6685,12 @@ function toChatWire(req, model) {
|
|
|
6453
6685
|
`) });
|
|
6454
6686
|
}
|
|
6455
6687
|
}
|
|
6456
|
-
const body = {
|
|
6688
|
+
const body = {
|
|
6689
|
+
model,
|
|
6690
|
+
messages,
|
|
6691
|
+
stream: req.stream,
|
|
6692
|
+
stream_options: { include_usage: true }
|
|
6693
|
+
};
|
|
6457
6694
|
if (req.maxTokens !== undefined)
|
|
6458
6695
|
body.max_tokens = req.maxTokens;
|
|
6459
6696
|
if (req.temperature !== undefined)
|
|
@@ -6494,6 +6731,7 @@ var kimiAdapter = {
|
|
|
6494
6731
|
const profile = PROFILES.kimi;
|
|
6495
6732
|
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
6496
6733
|
const res = await req.http({
|
|
6734
|
+
provider: "kimi",
|
|
6497
6735
|
url: BASE_URL2,
|
|
6498
6736
|
method: "POST",
|
|
6499
6737
|
headers,
|
|
@@ -6624,12 +6862,7 @@ async function* decodeResponses(messages) {
|
|
|
6624
6862
|
yield {
|
|
6625
6863
|
type: "end",
|
|
6626
6864
|
stopReason,
|
|
6627
|
-
usage:
|
|
6628
|
-
inputTokens: r.usage?.input_tokens ?? 0,
|
|
6629
|
-
outputTokens: r.usage?.output_tokens ?? 0,
|
|
6630
|
-
cacheReadTokens: r.usage?.input_tokens_details?.cached_tokens ?? 0,
|
|
6631
|
-
cacheWriteTokens: 0
|
|
6632
|
-
}
|
|
6865
|
+
usage: usageFromPromptTotal(r.usage?.input_tokens ?? 0, r.usage?.output_tokens ?? 0, r.usage?.input_tokens_details?.cached_tokens ?? r.usage?.prompt_tokens_details?.cached_tokens ?? 0)
|
|
6633
6866
|
};
|
|
6634
6867
|
break;
|
|
6635
6868
|
}
|
|
@@ -6802,6 +7035,7 @@ var openaiAdapter = {
|
|
|
6802
7035
|
const profile = PROFILES.openai;
|
|
6803
7036
|
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
6804
7037
|
const res = await req.http({
|
|
7038
|
+
provider: "openai",
|
|
6805
7039
|
url: oauth ? OAUTH_URL : API_URL,
|
|
6806
7040
|
method: "POST",
|
|
6807
7041
|
headers,
|
|
@@ -6822,13 +7056,14 @@ var ADAPTERS = {
|
|
|
6822
7056
|
kimi: kimiAdapter
|
|
6823
7057
|
};
|
|
6824
7058
|
// packages/control/src/oauth/types.ts
|
|
6825
|
-
async function postJson(deps, url, profile2, opts) {
|
|
7059
|
+
async function postJson(deps, provider, url, profile2, opts) {
|
|
6826
7060
|
const headers = orderHeaders(mergeHeaders(profile2.headers, [
|
|
6827
7061
|
["Content-Type", opts.contentType],
|
|
6828
7062
|
["Accept", "application/json"],
|
|
6829
7063
|
...opts.extraHeaders ?? []
|
|
6830
7064
|
]), profile2.order);
|
|
6831
7065
|
const res = await deps.http({
|
|
7066
|
+
provider,
|
|
6832
7067
|
url,
|
|
6833
7068
|
method: "POST",
|
|
6834
7069
|
headers,
|
|
@@ -6842,13 +7077,14 @@ async function postJson(deps, url, profile2, opts) {
|
|
|
6842
7077
|
} catch {}
|
|
6843
7078
|
return { status: res.status, parsed };
|
|
6844
7079
|
}
|
|
6845
|
-
async function getJson(deps, url, profile2, opts) {
|
|
7080
|
+
async function getJson(deps, provider, url, profile2, opts) {
|
|
6846
7081
|
const headers = orderHeaders(mergeHeaders(profile2.headers, [
|
|
6847
7082
|
["Authorization", `Bearer ${opts.accessToken}`],
|
|
6848
7083
|
["Accept", "application/json"],
|
|
6849
7084
|
...opts.extraHeaders ?? []
|
|
6850
7085
|
]), profile2.order);
|
|
6851
7086
|
const res = await deps.http({
|
|
7087
|
+
provider,
|
|
6852
7088
|
url,
|
|
6853
7089
|
method: "GET",
|
|
6854
7090
|
headers,
|
|
@@ -7042,7 +7278,7 @@ function deviceCodeFrom(value) {
|
|
|
7042
7278
|
};
|
|
7043
7279
|
}
|
|
7044
7280
|
async function post(url, body2, device, deps) {
|
|
7045
|
-
const { status, parsed } = await postJson(deps, url, PROFILES.kimi, {
|
|
7281
|
+
const { status, parsed } = await postJson(deps, "kimi", url, PROFILES.kimi, {
|
|
7046
7282
|
contentType: "application/x-www-form-urlencoded",
|
|
7047
7283
|
body: new URLSearchParams({ ...body2, client_id: CLIENT_ID }).toString(),
|
|
7048
7284
|
extraHeaders: kimiDeviceHeaders(device)
|
|
@@ -7145,7 +7381,7 @@ var kimiOAuth = {
|
|
|
7145
7381
|
if (secrets.accessToken === null)
|
|
7146
7382
|
return null;
|
|
7147
7383
|
const device = deviceFrom(providerData);
|
|
7148
|
-
const { status, parsed } = await getJson(deps, USAGE_URL, PROFILES.kimi, {
|
|
7384
|
+
const { status, parsed } = await getJson(deps, "kimi", USAGE_URL, PROFILES.kimi, {
|
|
7149
7385
|
accessToken: secrets.accessToken,
|
|
7150
7386
|
extraHeaders: kimiDeviceHeaders(device)
|
|
7151
7387
|
});
|
|
@@ -7214,6 +7450,7 @@ function deviceIdFrom(start) {
|
|
|
7214
7450
|
return deviceId;
|
|
7215
7451
|
}
|
|
7216
7452
|
function createConnectFlows(deps) {
|
|
7453
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
7217
7454
|
const flows = createPendingFlows({ now: deps.now, ttlMs: FLOW_TTL_MS });
|
|
7218
7455
|
const pollsInFlight = new Map;
|
|
7219
7456
|
const callbackUri = (provider) => provider === "openai" ? "http://localhost:1455/auth/callback" : "";
|
|
@@ -7258,6 +7495,10 @@ function createConnectFlows(deps) {
|
|
|
7258
7495
|
disabledAt: null,
|
|
7259
7496
|
...result.secrets
|
|
7260
7497
|
});
|
|
7498
|
+
logger2.info("oauth connect completed", {
|
|
7499
|
+
provider: flow.provider,
|
|
7500
|
+
credentialId: id
|
|
7501
|
+
});
|
|
7261
7502
|
return { id };
|
|
7262
7503
|
}
|
|
7263
7504
|
async function pollOutcome(flowId, poll) {
|
|
@@ -7330,6 +7571,62 @@ function createConnectFlows(deps) {
|
|
|
7330
7571
|
}
|
|
7331
7572
|
};
|
|
7332
7573
|
}
|
|
7574
|
+
// packages/control/src/console.ts
|
|
7575
|
+
var UNIT_NAME = "omnigateway.service";
|
|
7576
|
+
var MAX_CONSOLE_LINES = 500;
|
|
7577
|
+
var DEFAULT_CONSOLE_LINES = 200;
|
|
7578
|
+
function resolveConsoleSource(input) {
|
|
7579
|
+
const path = input.logFile?.trim();
|
|
7580
|
+
if (path !== undefined && path.length > 0)
|
|
7581
|
+
return { kind: "file", path };
|
|
7582
|
+
if (input.unitInstalled)
|
|
7583
|
+
return { kind: "journal", unit: UNIT_NAME, scope: input.scope };
|
|
7584
|
+
return { kind: "none" };
|
|
7585
|
+
}
|
|
7586
|
+
function consoleLimit(requested) {
|
|
7587
|
+
const value = requested === undefined ? DEFAULT_CONSOLE_LINES : Number(requested);
|
|
7588
|
+
if (!Number.isFinite(value))
|
|
7589
|
+
return DEFAULT_CONSOLE_LINES;
|
|
7590
|
+
return Math.floor(Math.min(Math.max(1, value), MAX_CONSOLE_LINES));
|
|
7591
|
+
}
|
|
7592
|
+
function keep(line, query) {
|
|
7593
|
+
if (query.since !== undefined && (line.at === null || line.at <= query.since))
|
|
7594
|
+
return false;
|
|
7595
|
+
if (query.level === undefined || line.level === null)
|
|
7596
|
+
return true;
|
|
7597
|
+
return LEVELS[line.level] >= LEVELS[query.level];
|
|
7598
|
+
}
|
|
7599
|
+
var SCAN_FACTOR = 20;
|
|
7600
|
+
var SCAN_LIMIT = 5000;
|
|
7601
|
+
function scanWidth(query) {
|
|
7602
|
+
const filtered = query.level !== undefined || query.since !== undefined;
|
|
7603
|
+
return filtered ? Math.min(query.lines * SCAN_FACTOR, SCAN_LIMIT) : query.lines;
|
|
7604
|
+
}
|
|
7605
|
+
async function readSource(deps, source, lines) {
|
|
7606
|
+
if (source.kind === "none")
|
|
7607
|
+
return "";
|
|
7608
|
+
if (source.kind === "file")
|
|
7609
|
+
return deps.readFile(source.path, lines) ?? "";
|
|
7610
|
+
const scope = source.scope === "system" ? [] : ["--user"];
|
|
7611
|
+
const result = await deps.run([
|
|
7612
|
+
"journalctl",
|
|
7613
|
+
...scope,
|
|
7614
|
+
"-u",
|
|
7615
|
+
source.unit,
|
|
7616
|
+
"-n",
|
|
7617
|
+
String(lines),
|
|
7618
|
+
"--no-pager",
|
|
7619
|
+
"--output=cat"
|
|
7620
|
+
]);
|
|
7621
|
+
return result.code === 0 ? result.stdout : "";
|
|
7622
|
+
}
|
|
7623
|
+
async function readConsole(deps, source, query) {
|
|
7624
|
+
const limited = { ...query, lines: consoleLimit(query.lines) };
|
|
7625
|
+
const text = await readSource(deps, source, scanWidth(limited));
|
|
7626
|
+
const lines = text.split(`
|
|
7627
|
+
`).filter((raw) => raw.trim().length > 0).map(parseLine).filter((line) => keep(line, limited)).slice(-limited.lines);
|
|
7628
|
+
return source.kind === "file" ? { source: "file", path: source.path, lines } : { source: source.kind, lines };
|
|
7629
|
+
}
|
|
7333
7630
|
// node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
7334
7631
|
var exports_external = {};
|
|
7335
7632
|
__export(exports_external, {
|
|
@@ -21633,28 +21930,18 @@ var modelSchema = exports_external.object({
|
|
|
21633
21930
|
costPerMTok: exports_external.object({
|
|
21634
21931
|
input: exports_external.number().min(0),
|
|
21635
21932
|
output: exports_external.number().min(0),
|
|
21636
|
-
cacheRead: exports_external.number().min(0)
|
|
21637
|
-
|
|
21638
|
-
|
|
21639
|
-
tools: exports_external.boolean(),
|
|
21640
|
-
images: exports_external.boolean(),
|
|
21641
|
-
reasoning: exports_external.boolean()
|
|
21642
|
-
})
|
|
21643
|
-
}).or(exports_external.object({
|
|
21644
|
-
provider: providerIdSchema,
|
|
21645
|
-
model: exports_external.string().min(1),
|
|
21646
|
-
tier: exports_external.number().int().min(1),
|
|
21647
|
-
weight: exports_external.number().positive(),
|
|
21648
|
-
costPerMTok: exports_external.object({
|
|
21649
|
-
input: exports_external.number().min(0),
|
|
21650
|
-
output: exports_external.number().min(0)
|
|
21933
|
+
cacheRead: exports_external.number().min(0).optional(),
|
|
21934
|
+
cacheWrite5m: exports_external.number().min(0).optional(),
|
|
21935
|
+
cacheWrite1h: exports_external.number().min(0).optional()
|
|
21651
21936
|
}),
|
|
21937
|
+
contextWindow: exports_external.number().int().positive().optional(),
|
|
21938
|
+
maxOutputTokens: exports_external.number().int().positive().optional(),
|
|
21652
21939
|
capabilities: exports_external.object({
|
|
21653
21940
|
tools: exports_external.boolean(),
|
|
21654
21941
|
images: exports_external.boolean(),
|
|
21655
21942
|
reasoning: exports_external.boolean()
|
|
21656
21943
|
})
|
|
21657
|
-
}))
|
|
21944
|
+
})).min(1, "a virtual model needs at least one target")
|
|
21658
21945
|
});
|
|
21659
21946
|
var keyCreateSchema = exports_external.object({
|
|
21660
21947
|
label: exports_external.string().min(1).default("api key"),
|
|
@@ -21748,6 +22035,12 @@ async function patchCredential(deps, id, input) {
|
|
|
21748
22035
|
...patch.tier === undefined ? {} : { tier: patch.tier },
|
|
21749
22036
|
...patch.weight === undefined ? {} : { weight: patch.weight }
|
|
21750
22037
|
});
|
|
22038
|
+
if (patch.enabled !== undefined && patch.enabled !== existing.enabled) {
|
|
22039
|
+
(deps.logger ?? noopLogger).info(patch.enabled ? "credential enabled" : "credential disabled", {
|
|
22040
|
+
credentialId: id,
|
|
22041
|
+
provider: existing.provider
|
|
22042
|
+
});
|
|
22043
|
+
}
|
|
21751
22044
|
}
|
|
21752
22045
|
async function removeCredential(store, id) {
|
|
21753
22046
|
await store.credentials.remove(id);
|
|
@@ -22009,12 +22302,20 @@ var PREFIX_PROVIDER = [
|
|
|
22009
22302
|
];
|
|
22010
22303
|
function synthesize(provider, model) {
|
|
22011
22304
|
const listed = catalogPricing(provider, model);
|
|
22305
|
+
const limits = catalogLimits(provider, model);
|
|
22012
22306
|
const target = {
|
|
22013
22307
|
provider,
|
|
22014
22308
|
model,
|
|
22015
22309
|
tier: 1,
|
|
22016
22310
|
weight: 1,
|
|
22017
|
-
costPerMTok: listed === null ? { input: 0, output: 0 } : {
|
|
22311
|
+
costPerMTok: listed === null ? { input: 0, output: 0 } : {
|
|
22312
|
+
input: listed.input,
|
|
22313
|
+
output: listed.output,
|
|
22314
|
+
cacheRead: listed.cacheRead,
|
|
22315
|
+
cacheWrite5m: listed.cacheWrite5m,
|
|
22316
|
+
cacheWrite1h: listed.cacheWrite1h
|
|
22317
|
+
},
|
|
22318
|
+
...limits === null ? {} : { contextWindow: limits.contextWindow, maxOutputTokens: limits.maxOutputTokens },
|
|
22018
22319
|
capabilities: PROVIDER_CAPABILITIES[provider]
|
|
22019
22320
|
};
|
|
22020
22321
|
return { id: `${provider}/${model}`, targets: [target], strategy: "score", isAlias: true };
|
|
@@ -23017,7 +23318,9 @@ function createUsageRepo(db) {
|
|
|
23017
23318
|
|
|
23018
23319
|
// packages/store/src/sqlite/store.ts
|
|
23019
23320
|
async function createStore(opts) {
|
|
23321
|
+
const logger2 = opts.logger ?? noopLogger;
|
|
23020
23322
|
const db = openDb(opts.path);
|
|
23323
|
+
logger2.debug("store opened", { path: opts.path });
|
|
23021
23324
|
const listeners = new Set;
|
|
23022
23325
|
const emit = (change) => {
|
|
23023
23326
|
for (const listener of listeners) {
|
|
@@ -23104,7 +23407,7 @@ var REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
|
|
|
23104
23407
|
var SCOPES = "org:create_api_key user:profile user:inference";
|
|
23105
23408
|
var USAGE_URL2 = "https://api.anthropic.com/api/oauth/usage";
|
|
23106
23409
|
async function postToken(body2, deps) {
|
|
23107
|
-
const { status, parsed } = await postJson(deps, TOKEN_URL2, PROFILES.anthropic, {
|
|
23410
|
+
const { status, parsed } = await postJson(deps, "anthropic", TOKEN_URL2, PROFILES.anthropic, {
|
|
23108
23411
|
contentType: "application/json",
|
|
23109
23412
|
body: JSON.stringify({ ...body2, client_id: CLIENT_ID2 })
|
|
23110
23413
|
});
|
|
@@ -23182,7 +23485,7 @@ var anthropicOAuth = {
|
|
|
23182
23485
|
async usage(secrets, deps) {
|
|
23183
23486
|
if (secrets.accessToken === null)
|
|
23184
23487
|
return null;
|
|
23185
|
-
const { status, parsed } = await getJson(deps, USAGE_URL2, PROFILES.anthropic, {
|
|
23488
|
+
const { status, parsed } = await getJson(deps, "anthropic", USAGE_URL2, PROFILES.anthropic, {
|
|
23186
23489
|
accessToken: secrets.accessToken,
|
|
23187
23490
|
extraHeaders: [
|
|
23188
23491
|
["anthropic-beta", "oauth-2025-04-20"],
|
|
@@ -23240,7 +23543,7 @@ function parseTokenResponse(value) {
|
|
|
23240
23543
|
};
|
|
23241
23544
|
}
|
|
23242
23545
|
async function postToken2(body2, deps) {
|
|
23243
|
-
const { status, parsed } = await postJson(deps, TOKEN_URL3, PROFILES.openai, {
|
|
23546
|
+
const { status, parsed } = await postJson(deps, "openai", TOKEN_URL3, PROFILES.openai, {
|
|
23244
23547
|
contentType: "application/x-www-form-urlencoded",
|
|
23245
23548
|
body: new URLSearchParams({ ...body2, client_id: CLIENT_ID3 }).toString()
|
|
23246
23549
|
});
|
|
@@ -23339,7 +23642,7 @@ var openaiOAuth = {
|
|
|
23339
23642
|
if (secrets.accessToken === null)
|
|
23340
23643
|
return null;
|
|
23341
23644
|
const accountId = accountIdFromProviderData(providerData);
|
|
23342
|
-
const { status, parsed } = await getJson(deps, USAGE_URL3, PROFILES.openai, {
|
|
23645
|
+
const { status, parsed } = await getJson(deps, "openai", USAGE_URL3, PROFILES.openai, {
|
|
23343
23646
|
accessToken: secrets.accessToken,
|
|
23344
23647
|
...accountId === null ? {} : { extraHeaders: [["chatgpt-account-id", accountId]] }
|
|
23345
23648
|
});
|
|
@@ -23360,6 +23663,7 @@ var DISPATCH_REFRESH_LEAD_MS = 120000;
|
|
|
23360
23663
|
var SCHEDULER_REFRESH_LEAD_MS = 300000;
|
|
23361
23664
|
// packages/control/src/oauth/refresh.ts
|
|
23362
23665
|
function createRefresher(deps) {
|
|
23666
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
23363
23667
|
const inFlight = new Map;
|
|
23364
23668
|
async function run(credential) {
|
|
23365
23669
|
const secrets = await credential.openForRefresh();
|
|
@@ -23367,11 +23671,20 @@ function createRefresher(deps) {
|
|
|
23367
23671
|
throw new GatewayError("AUTH", `credential ${credential.id} has no refresh token`);
|
|
23368
23672
|
}
|
|
23369
23673
|
const provider = deps.providers[credential.provider];
|
|
23674
|
+
logger2.debug("refreshing credential", {
|
|
23675
|
+
provider: credential.provider,
|
|
23676
|
+
credentialId: credential.id
|
|
23677
|
+
});
|
|
23370
23678
|
let result;
|
|
23371
23679
|
try {
|
|
23372
23680
|
result = await provider.refresh(secrets.refreshToken, { http: deps.http, now: deps.now }, credential.providerData);
|
|
23373
23681
|
} catch (error51) {
|
|
23374
23682
|
if (error51 instanceof GatewayError && error51.code === "AUTH") {
|
|
23683
|
+
logger2.warn("credential disabled: refresh token rejected", {
|
|
23684
|
+
provider: credential.provider,
|
|
23685
|
+
credentialId: credential.id,
|
|
23686
|
+
code: "AUTH"
|
|
23687
|
+
});
|
|
23375
23688
|
await deps.store.credentials.update(credential.id, {
|
|
23376
23689
|
enabled: false,
|
|
23377
23690
|
disabledReason: "tokenRejected",
|
|
@@ -23385,6 +23698,10 @@ function createRefresher(deps) {
|
|
|
23385
23698
|
accountEmail: result.accountEmail ?? credential.accountEmail,
|
|
23386
23699
|
providerData: { ...credential.providerData, ...result.providerData }
|
|
23387
23700
|
});
|
|
23701
|
+
logger2.debug("credential refreshed", {
|
|
23702
|
+
provider: credential.provider,
|
|
23703
|
+
credentialId: credential.id
|
|
23704
|
+
});
|
|
23388
23705
|
return result.secrets;
|
|
23389
23706
|
}
|
|
23390
23707
|
return function refresh(credential) {
|
|
@@ -23424,9 +23741,15 @@ async function probe(deps, credential) {
|
|
|
23424
23741
|
observedAt
|
|
23425
23742
|
}));
|
|
23426
23743
|
await deps.store.credentials.saveQuota(rows);
|
|
23744
|
+
(deps.logger ?? noopLogger).debug("quota snapshot written", {
|
|
23745
|
+
provider: credential.provider,
|
|
23746
|
+
credentialId: credential.id,
|
|
23747
|
+
count: rows.length
|
|
23748
|
+
});
|
|
23427
23749
|
return rows;
|
|
23428
23750
|
}
|
|
23429
23751
|
async function poll(deps) {
|
|
23752
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
23430
23753
|
const now = deps.now();
|
|
23431
23754
|
const credentials = (await deps.store.credentials.list()).filter((c) => c.enabled && c.authType === "oauth" && deps.providers[c.provider].usage !== undefined && (cooldowns.get(c.id) ?? 0) <= now);
|
|
23432
23755
|
let written = 0;
|
|
@@ -23440,12 +23763,15 @@ async function poll(deps) {
|
|
|
23440
23763
|
if (await probe(deps, credential) !== null)
|
|
23441
23764
|
written += 1;
|
|
23442
23765
|
} catch (error51) {
|
|
23443
|
-
|
|
23766
|
+
const rateLimited = error51 instanceof GatewayError && error51.code === "RATE_LIMIT";
|
|
23767
|
+
if (rateLimited) {
|
|
23444
23768
|
cooldowns.set(credential.id, deps.now() + RATE_LIMIT_COOLDOWN_MS);
|
|
23445
23769
|
}
|
|
23446
|
-
|
|
23447
|
-
credentialId: credential.id,
|
|
23770
|
+
logger2.warn(rateLimited ? "quota probe rate limited" : "quota probe failed", {
|
|
23448
23771
|
provider: credential.provider,
|
|
23772
|
+
credentialId: credential.id,
|
|
23773
|
+
code: error51 instanceof GatewayError ? error51.code : "INTERNAL",
|
|
23774
|
+
...rateLimited ? { retryAfterMs: RATE_LIMIT_COOLDOWN_MS } : {},
|
|
23449
23775
|
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
23450
23776
|
});
|
|
23451
23777
|
}
|
|
@@ -23463,6 +23789,43 @@ async function putSettings(store, input) {
|
|
|
23463
23789
|
await store.config.putSettings(settings);
|
|
23464
23790
|
return settings;
|
|
23465
23791
|
}
|
|
23792
|
+
// packages/control/src/tail.ts
|
|
23793
|
+
import { closeSync, fstatSync, openSync, readSync, statSync } from "fs";
|
|
23794
|
+
var CHUNK = 64 * 1024;
|
|
23795
|
+
var MAX_BYTES = 8 * 1024 * 1024;
|
|
23796
|
+
function tailFile(path, lines) {
|
|
23797
|
+
let fd;
|
|
23798
|
+
try {
|
|
23799
|
+
fd = openSync(path, "r");
|
|
23800
|
+
} catch {
|
|
23801
|
+
return null;
|
|
23802
|
+
}
|
|
23803
|
+
try {
|
|
23804
|
+
const size = fstatSync(fd).size;
|
|
23805
|
+
if (size === 0)
|
|
23806
|
+
return "";
|
|
23807
|
+
const budget = Math.min(size, MAX_BYTES);
|
|
23808
|
+
let read = 0;
|
|
23809
|
+
let newlines = 0;
|
|
23810
|
+
const chunks = [];
|
|
23811
|
+
while (read < budget && newlines <= lines) {
|
|
23812
|
+
const span = Math.min(CHUNK, budget - read);
|
|
23813
|
+
const buffer = Buffer.allocUnsafe(span);
|
|
23814
|
+
const position = size - read - span;
|
|
23815
|
+
readSync(fd, buffer, 0, span, position);
|
|
23816
|
+
chunks.unshift(buffer);
|
|
23817
|
+
read += span;
|
|
23818
|
+
for (const byte of buffer)
|
|
23819
|
+
if (byte === 10)
|
|
23820
|
+
newlines++;
|
|
23821
|
+
}
|
|
23822
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
23823
|
+
} catch {
|
|
23824
|
+
return null;
|
|
23825
|
+
} finally {
|
|
23826
|
+
closeSync(fd);
|
|
23827
|
+
}
|
|
23828
|
+
}
|
|
23466
23829
|
// packages/control/src/usage.ts
|
|
23467
23830
|
var MAX_LOG_LIMIT = 500;
|
|
23468
23831
|
var DEFAULT_LOG_LIMIT = 100;
|
|
@@ -37903,7 +38266,7 @@ class ApiKeyRateLimiter {
|
|
|
37903
38266
|
}
|
|
37904
38267
|
|
|
37905
38268
|
// apps/gateway/src/dispatch/snapshotCache.ts
|
|
37906
|
-
function createRoutingSnapshotCache(store) {
|
|
38269
|
+
function createRoutingSnapshotCache(store, logger2 = noopLogger) {
|
|
37907
38270
|
let snapshot = null;
|
|
37908
38271
|
let stale = true;
|
|
37909
38272
|
let version2 = store.routing.version();
|
|
@@ -37942,6 +38305,7 @@ function createRoutingSnapshotCache(store) {
|
|
|
37942
38305
|
return inFlight;
|
|
37943
38306
|
const buildGeneration = generation;
|
|
37944
38307
|
const buildVersion = currentVersion;
|
|
38308
|
+
logger2.debug("routing snapshot cache miss");
|
|
37945
38309
|
inFlight = buildSnapshot(store, now).then(async (next) => {
|
|
37946
38310
|
const completedVersion = store.routing.version();
|
|
37947
38311
|
if (generation !== buildGeneration || completedVersion !== buildVersion) {
|
|
@@ -37961,6 +38325,28 @@ function createRoutingSnapshotCache(store) {
|
|
|
37961
38325
|
}
|
|
37962
38326
|
|
|
37963
38327
|
// apps/gateway/src/ingress/schemas.ts
|
|
38328
|
+
var cacheControlSchema = exports_external.object({
|
|
38329
|
+
type: exports_external.literal("ephemeral"),
|
|
38330
|
+
ttl: exports_external.enum(["5m", "1h"]).optional()
|
|
38331
|
+
});
|
|
38332
|
+
function looseCacheControl(value) {
|
|
38333
|
+
const parsed = cacheControlSchema.safeParse(value);
|
|
38334
|
+
return parsed.success ? irCacheControl(parsed.data) : {};
|
|
38335
|
+
}
|
|
38336
|
+
function irCacheControl(c) {
|
|
38337
|
+
if (c === undefined)
|
|
38338
|
+
return {};
|
|
38339
|
+
return { cacheControl: { type: c.type, ...c.ttl === undefined ? {} : { ttl: c.ttl } } };
|
|
38340
|
+
}
|
|
38341
|
+
function applyMessageCacheControl(blocks, value) {
|
|
38342
|
+
const control = looseCacheControl(value);
|
|
38343
|
+
if (control.cacheControl === undefined)
|
|
38344
|
+
return;
|
|
38345
|
+
const last = blocks.at(-1);
|
|
38346
|
+
if (last === undefined || last.type === "thinking" || cacheControlOf(last) !== undefined)
|
|
38347
|
+
return;
|
|
38348
|
+
Object.assign(last, control);
|
|
38349
|
+
}
|
|
37964
38350
|
function parseOrThrow2(schema, body2) {
|
|
37965
38351
|
const result = schema.safeParse(body2);
|
|
37966
38352
|
if (result.success)
|
|
@@ -38040,6 +38426,7 @@ function apiErrorHandler(input) {
|
|
|
38040
38426
|
|
|
38041
38427
|
// apps/gateway/src/routes/admin.ts
|
|
38042
38428
|
function adminRoutes(deps) {
|
|
38429
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
38043
38430
|
return new Elysia().onError(apiErrorHandler).get("/api/status", async ({ request: request2 }) => {
|
|
38044
38431
|
const token = readCookie(request2, ADMIN_COOKIE);
|
|
38045
38432
|
return {
|
|
@@ -38049,7 +38436,9 @@ function adminRoutes(deps) {
|
|
|
38049
38436
|
}).post("/api/setup", async ({ request: request2, set: set2 }) => {
|
|
38050
38437
|
if (await deps.admin.isConfigured()) {
|
|
38051
38438
|
set2.status = 409;
|
|
38052
|
-
return {
|
|
38439
|
+
return {
|
|
38440
|
+
error: { code: "CONFLICT", message: "an admin password is already configured" }
|
|
38441
|
+
};
|
|
38053
38442
|
}
|
|
38054
38443
|
const body2 = await readJsonRecord(request2);
|
|
38055
38444
|
if (typeof body2?.password !== "string") {
|
|
@@ -38063,18 +38452,24 @@ function adminRoutes(deps) {
|
|
|
38063
38452
|
}
|
|
38064
38453
|
if (!created) {
|
|
38065
38454
|
set2.status = 409;
|
|
38066
|
-
return {
|
|
38455
|
+
return {
|
|
38456
|
+
error: { code: "CONFLICT", message: "an admin password is already configured" }
|
|
38457
|
+
};
|
|
38067
38458
|
}
|
|
38068
38459
|
const token = await deps.admin.login(body2.password);
|
|
38069
38460
|
if (token === null)
|
|
38070
38461
|
throw new GatewayError("INTERNAL", "could not create admin session");
|
|
38462
|
+
logger2.info("admin setup completed");
|
|
38071
38463
|
set2.headers["set-cookie"] = sessionCookie(request2, token, Math.floor(deps.sessionTtlMs / 1000));
|
|
38072
38464
|
return { ok: true };
|
|
38073
38465
|
}).post("/api/login", async ({ request: request2, set: set2 }) => {
|
|
38074
38466
|
const body2 = await readJsonRecord(request2);
|
|
38075
38467
|
const token = typeof body2?.password === "string" ? await deps.admin.login(body2.password) : null;
|
|
38076
|
-
if (token === null)
|
|
38468
|
+
if (token === null) {
|
|
38469
|
+
logger2.info("admin login failed", { reason: "invalid credentials" });
|
|
38077
38470
|
throw new GatewayError("AUTH", "invalid password");
|
|
38471
|
+
}
|
|
38472
|
+
logger2.info("admin login succeeded");
|
|
38078
38473
|
set2.headers["set-cookie"] = sessionCookie(request2, token, Math.floor(deps.sessionTtlMs / 1000));
|
|
38079
38474
|
return { ok: true };
|
|
38080
38475
|
}).post("/api/logout", async ({ request: request2, set: set2 }) => {
|
|
@@ -38142,6 +38537,17 @@ function adminRoutes(deps) {
|
|
|
38142
38537
|
}).get("/api/logs", async ({ request: request2, query }) => {
|
|
38143
38538
|
await requireAdmin(request2, deps.admin);
|
|
38144
38539
|
return { logs: await recentLogs(deps.store, query.limit) };
|
|
38540
|
+
}).get("/api/console", async ({ request: request2, query }) => {
|
|
38541
|
+
await requireAdmin(request2, deps.admin);
|
|
38542
|
+
if (deps.console === undefined)
|
|
38543
|
+
return { source: "none", lines: [] };
|
|
38544
|
+
const level = parseLogLevel(query.level);
|
|
38545
|
+
const since = Number(query.since);
|
|
38546
|
+
return readConsole(deps.console.deps, deps.console.source, {
|
|
38547
|
+
lines: consoleLimit(query.lines),
|
|
38548
|
+
...level === null ? {} : { level },
|
|
38549
|
+
...Number.isFinite(since) && since > 0 ? { since } : {}
|
|
38550
|
+
});
|
|
38145
38551
|
});
|
|
38146
38552
|
}
|
|
38147
38553
|
|
|
@@ -38240,41 +38646,47 @@ function newPendingRequestLog(input) {
|
|
|
38240
38646
|
status: 0
|
|
38241
38647
|
};
|
|
38242
38648
|
}
|
|
38243
|
-
function report(what, requestId, error51) {
|
|
38244
|
-
|
|
38649
|
+
function report(logger2, what, requestId, error51) {
|
|
38650
|
+
logger2.warn(what, {
|
|
38245
38651
|
requestId,
|
|
38246
38652
|
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
38247
38653
|
});
|
|
38248
38654
|
}
|
|
38249
|
-
async function beginLog(store, log, keyId) {
|
|
38655
|
+
async function beginLog(store, log, keyId, logger2 = noopLogger) {
|
|
38250
38656
|
try {
|
|
38251
38657
|
await store.usage.begin({ ...log, state: "pending", apiKeyId: keyId });
|
|
38252
38658
|
} catch (error51) {
|
|
38253
|
-
report("failed to record request start", log.id, error51);
|
|
38659
|
+
report(logger2, "failed to record request start", log.id, error51);
|
|
38254
38660
|
}
|
|
38255
38661
|
}
|
|
38256
|
-
async function routeLog(store, requestId, target) {
|
|
38662
|
+
async function routeLog(store, requestId, target, logger2 = noopLogger) {
|
|
38257
38663
|
try {
|
|
38258
38664
|
await store.usage.route(requestId, target);
|
|
38259
38665
|
} catch (error51) {
|
|
38260
|
-
report("failed to record request route", requestId, error51);
|
|
38666
|
+
report(logger2, "failed to record request route", requestId, error51);
|
|
38261
38667
|
}
|
|
38262
38668
|
}
|
|
38263
|
-
async function finishLog(store, log, keyId) {
|
|
38669
|
+
async function finishLog(store, log, keyId, logger2 = noopLogger) {
|
|
38264
38670
|
try {
|
|
38265
38671
|
await store.usage.append({ ...log, apiKeyId: keyId });
|
|
38266
38672
|
} catch (error51) {
|
|
38267
|
-
report("failed to persist request log", log.id, error51);
|
|
38673
|
+
report(logger2, "failed to persist request log", log.id, error51);
|
|
38268
38674
|
}
|
|
38269
38675
|
}
|
|
38270
38676
|
|
|
38271
38677
|
// apps/gateway/src/dispatch/attempt.ts
|
|
38272
38678
|
async function attempt(opts) {
|
|
38273
38679
|
const { candidate, adapter, http, now, signal, refresh, refreshLeadMs } = opts;
|
|
38680
|
+
const logger2 = opts.logger ?? noopLogger;
|
|
38274
38681
|
const credential = candidate.credential;
|
|
38275
38682
|
let secrets = opts.secrets ?? await credential.openForInference();
|
|
38276
38683
|
const stale = opts.secrets === undefined && credential.authType === "oauth" && credential.expiresAt !== null && credential.expiresAt - refreshLeadMs <= now;
|
|
38277
38684
|
if (stale) {
|
|
38685
|
+
logger2.debug("preemptive credential refresh", {
|
|
38686
|
+
requestId: opts.requestId,
|
|
38687
|
+
provider: credential.provider,
|
|
38688
|
+
credentialId: credential.id
|
|
38689
|
+
});
|
|
38278
38690
|
if (!credential.hasRefreshToken) {
|
|
38279
38691
|
throw new GatewayError("AUTH", "credential expired with no refresh token", {
|
|
38280
38692
|
provider: credential.provider
|
|
@@ -38320,8 +38732,35 @@ function classify(error51) {
|
|
|
38320
38732
|
return { code: "INTERNAL" };
|
|
38321
38733
|
}
|
|
38322
38734
|
|
|
38735
|
+
// apps/gateway/src/dispatch/price.ts
|
|
38736
|
+
var WRITE_OVER_INPUT = {
|
|
38737
|
+
anthropic: { fiveMinute: 1.25, oneHour: 2 },
|
|
38738
|
+
openai: { fiveMinute: 0, oneHour: 0 },
|
|
38739
|
+
kimi: { fiveMinute: 0, oneHour: 0 }
|
|
38740
|
+
};
|
|
38741
|
+
var READ_OVER_INPUT = 0.1;
|
|
38742
|
+
function splitWrites(usage) {
|
|
38743
|
+
const { cacheWrite5mTokens: five, cacheWrite1hTokens: hour, cacheWriteTokens: total } = usage;
|
|
38744
|
+
if (five !== undefined && hour !== undefined)
|
|
38745
|
+
return { fiveMinute: five, oneHour: hour };
|
|
38746
|
+
if (five !== undefined)
|
|
38747
|
+
return { fiveMinute: five, oneHour: Math.max(0, total - five) };
|
|
38748
|
+
if (hour !== undefined)
|
|
38749
|
+
return { fiveMinute: Math.max(0, total - hour), oneHour: hour };
|
|
38750
|
+
return { fiveMinute: total, oneHour: 0 };
|
|
38751
|
+
}
|
|
38752
|
+
function priceOf(prices, usage, provider) {
|
|
38753
|
+
const fallback = WRITE_OVER_INPUT[provider];
|
|
38754
|
+
const readRate = prices.cacheRead ?? prices.input * READ_OVER_INPUT;
|
|
38755
|
+
const write5mRate = prices.cacheWrite5m ?? prices.input * fallback.fiveMinute;
|
|
38756
|
+
const write1hRate = prices.cacheWrite1h ?? prices.input * fallback.oneHour;
|
|
38757
|
+
const writes = splitWrites(usage);
|
|
38758
|
+
return (usage.inputTokens * prices.input + usage.outputTokens * prices.output + usage.cacheReadTokens * readRate + writes.fiveMinute * write5mRate + writes.oneHour * write1hRate) / 1e6;
|
|
38759
|
+
}
|
|
38760
|
+
|
|
38323
38761
|
// apps/gateway/src/dispatch/index.ts
|
|
38324
38762
|
async function dispatch(request2, deps, signal, requestId) {
|
|
38763
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
38325
38764
|
const startedAt = deps.now();
|
|
38326
38765
|
const snapshot = await deps.snapshots.get(startedAt);
|
|
38327
38766
|
const deadlineAt = startedAt + snapshot.settings.requestDeadlineMs;
|
|
@@ -38375,8 +38814,20 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38375
38814
|
now: startedAt,
|
|
38376
38815
|
rand: deps.rand()
|
|
38377
38816
|
});
|
|
38817
|
+
logger2.debug("routing candidates ranked", {
|
|
38818
|
+
requestId,
|
|
38819
|
+
requestedModel: request2.model,
|
|
38820
|
+
model: candidates[0]?.target.model,
|
|
38821
|
+
credentialId: candidates[0]?.credential.id,
|
|
38822
|
+
count: candidates.length
|
|
38823
|
+
});
|
|
38378
38824
|
for (const e of excluded) {
|
|
38379
38825
|
log.degradations.push(`excluded:${e.credentialId}:${e.reason}`);
|
|
38826
|
+
logger2.debug("routing candidate excluded", {
|
|
38827
|
+
requestId,
|
|
38828
|
+
credentialId: e.credentialId,
|
|
38829
|
+
reason: e.reason
|
|
38830
|
+
});
|
|
38380
38831
|
}
|
|
38381
38832
|
if (candidates.length === 0) {
|
|
38382
38833
|
clearDeadline();
|
|
@@ -38407,6 +38858,13 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38407
38858
|
model: candidate.target.model,
|
|
38408
38859
|
credentialId: candidate.credential.id
|
|
38409
38860
|
});
|
|
38861
|
+
logger2.debug("attempt started", {
|
|
38862
|
+
requestId,
|
|
38863
|
+
provider: candidate.target.provider,
|
|
38864
|
+
model: candidate.target.model,
|
|
38865
|
+
credentialId: candidate.credential.id,
|
|
38866
|
+
attempt: i + 1
|
|
38867
|
+
});
|
|
38410
38868
|
log.inputTokens = 0;
|
|
38411
38869
|
log.outputTokens = 0;
|
|
38412
38870
|
log.cacheReadTokens = 0;
|
|
@@ -38428,7 +38886,9 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38428
38886
|
signal: dispatchSignal,
|
|
38429
38887
|
refresh: (credential) => waitForCancellation(deps.refresh(credential), dispatchSignal),
|
|
38430
38888
|
refreshLeadMs: DISPATCH_REFRESH_LEAD_MS,
|
|
38431
|
-
...retrySecrets === undefined ? {} : { secrets: retrySecrets }
|
|
38889
|
+
...retrySecrets === undefined ? {} : { secrets: retrySecrets },
|
|
38890
|
+
logger: logger2,
|
|
38891
|
+
requestId
|
|
38432
38892
|
}), dispatchSignal);
|
|
38433
38893
|
for (const d of result.degradations)
|
|
38434
38894
|
log.degradations.push(d);
|
|
@@ -38438,6 +38898,14 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38438
38898
|
if (event.type === "blockDelta" && !committed) {
|
|
38439
38899
|
committed = true;
|
|
38440
38900
|
log.ttftMs = deps.now() - startedAt;
|
|
38901
|
+
logger2.debug("stream committed", {
|
|
38902
|
+
requestId,
|
|
38903
|
+
provider: candidate.target.provider,
|
|
38904
|
+
model: candidate.target.model,
|
|
38905
|
+
credentialId: candidate.credential.id,
|
|
38906
|
+
attempt: i + 1,
|
|
38907
|
+
ttftMs: log.ttftMs
|
|
38908
|
+
});
|
|
38441
38909
|
for (const buffered of pending)
|
|
38442
38910
|
yield buffered;
|
|
38443
38911
|
pending.length = 0;
|
|
@@ -38448,7 +38916,7 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38448
38916
|
log.outputTokens = event.usage.outputTokens;
|
|
38449
38917
|
log.cacheReadTokens = event.usage.cacheReadTokens;
|
|
38450
38918
|
log.cacheWriteTokens = event.usage.cacheWriteTokens;
|
|
38451
|
-
log.costUsd = priceOf(candidate, event.usage);
|
|
38919
|
+
log.costUsd = priceOf(candidate.target.costPerMTok, event.usage, candidate.target.provider);
|
|
38452
38920
|
}
|
|
38453
38921
|
if (event.type === "error") {
|
|
38454
38922
|
terminal = true;
|
|
@@ -38502,6 +38970,14 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38502
38970
|
lastError = retryAfterMs === undefined ? new GatewayError(code2, message) : new GatewayError(code2, message, { retryAfterMs });
|
|
38503
38971
|
if (code2 === "AUTH" && !committed && !authRefreshRetried && !preemptiveRefreshRequired && candidate.credential.authType === "oauth" && candidate.credential.hasRefreshToken) {
|
|
38504
38972
|
authRefreshRetried = true;
|
|
38973
|
+
logger2.warn("attempt authentication failed; refreshing credential", {
|
|
38974
|
+
requestId,
|
|
38975
|
+
provider: candidate.target.provider,
|
|
38976
|
+
model: candidate.target.model,
|
|
38977
|
+
credentialId: candidate.credential.id,
|
|
38978
|
+
attempt: i + 1,
|
|
38979
|
+
code: code2
|
|
38980
|
+
});
|
|
38505
38981
|
try {
|
|
38506
38982
|
retrySecrets = await waitForCancellation(deps.refresh(candidate.credential), dispatchSignal);
|
|
38507
38983
|
continue;
|
|
@@ -38523,6 +38999,18 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38523
38999
|
...failure.retryAfterMs === undefined ? {} : { retryAfterMs: failure.retryAfterMs },
|
|
38524
39000
|
jitter: deps.rand()
|
|
38525
39001
|
}));
|
|
39002
|
+
if (!committed && RETRYABLE[failure.code] && i + 1 < maxAttempts) {
|
|
39003
|
+
logger2.warn("attempt failed; retrying", {
|
|
39004
|
+
requestId,
|
|
39005
|
+
provider: candidate.target.provider,
|
|
39006
|
+
model: candidate.target.model,
|
|
39007
|
+
credentialId: candidate.credential.id,
|
|
39008
|
+
attempt: i + 1,
|
|
39009
|
+
code: failure.code,
|
|
39010
|
+
retryable: true,
|
|
39011
|
+
retryAfterMs: failure.retryAfterMs
|
|
39012
|
+
});
|
|
39013
|
+
}
|
|
38526
39014
|
if (committed) {
|
|
38527
39015
|
log.status = HTTP_STATUS[failure.code];
|
|
38528
39016
|
log.errorCode = failure.code;
|
|
@@ -38559,11 +39047,6 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38559
39047
|
}
|
|
38560
39048
|
return { events: run(), log: () => log };
|
|
38561
39049
|
}
|
|
38562
|
-
function priceOf(candidate, usage) {
|
|
38563
|
-
const p = candidate.target.costPerMTok;
|
|
38564
|
-
const cacheRate = p.cacheRead ?? p.input * 0.1;
|
|
38565
|
-
return (usage.inputTokens * p.input + usage.outputTokens * p.output + usage.cacheReadTokens * cacheRate) / 1e6;
|
|
38566
|
-
}
|
|
38567
39050
|
function waitForCancellation(promise2, signal) {
|
|
38568
39051
|
if (signal.aborted)
|
|
38569
39052
|
return Promise.reject(signal.reason);
|
|
@@ -38610,6 +39093,9 @@ var frame = (event, data) => ({
|
|
|
38610
39093
|
});
|
|
38611
39094
|
async function* anthropicStream(events, requestId) {
|
|
38612
39095
|
let model = "";
|
|
39096
|
+
const suppressed = new Set;
|
|
39097
|
+
const outIndex = new Map;
|
|
39098
|
+
let nextOutIndex = 0;
|
|
38613
39099
|
for await (const event of events) {
|
|
38614
39100
|
switch (event.type) {
|
|
38615
39101
|
case "start":
|
|
@@ -38624,32 +39110,50 @@ async function* anthropicStream(events, requestId) {
|
|
|
38624
39110
|
content: [],
|
|
38625
39111
|
stop_reason: null,
|
|
38626
39112
|
stop_sequence: null,
|
|
38627
|
-
usage: {
|
|
39113
|
+
usage: {
|
|
39114
|
+
input_tokens: 0,
|
|
39115
|
+
output_tokens: 0,
|
|
39116
|
+
cache_read_input_tokens: 0,
|
|
39117
|
+
cache_creation_input_tokens: 0
|
|
39118
|
+
}
|
|
38628
39119
|
}
|
|
38629
39120
|
});
|
|
38630
39121
|
break;
|
|
38631
39122
|
case "blockStart": {
|
|
38632
39123
|
const b = event.block;
|
|
39124
|
+
if (b.type === "thinking" && b.signed !== true) {
|
|
39125
|
+
suppressed.add(event.index);
|
|
39126
|
+
break;
|
|
39127
|
+
}
|
|
38633
39128
|
const content_block = b.type === "text" ? { type: "text", text: "" } : b.type === "thinking" ? { type: "thinking", thinking: "" } : { type: "tool_use", id: b.id, name: b.name, input: {} };
|
|
39129
|
+
const index = nextOutIndex++;
|
|
39130
|
+
outIndex.set(event.index, index);
|
|
38634
39131
|
yield frame("content_block_start", {
|
|
38635
39132
|
type: "content_block_start",
|
|
38636
|
-
index
|
|
39133
|
+
index,
|
|
38637
39134
|
content_block
|
|
38638
39135
|
});
|
|
38639
39136
|
break;
|
|
38640
39137
|
}
|
|
38641
39138
|
case "blockDelta": {
|
|
39139
|
+
if (suppressed.has(event.index))
|
|
39140
|
+
break;
|
|
38642
39141
|
const d = event.delta;
|
|
38643
39142
|
const delta2 = d.type === "text" ? { type: "text_delta", text: d.text } : d.type === "thinking" ? { type: "thinking_delta", thinking: d.text } : d.type === "thinkingSignature" ? { type: "signature_delta", signature: d.signature } : { type: "input_json_delta", partial_json: d.partial };
|
|
38644
39143
|
yield frame("content_block_delta", {
|
|
38645
39144
|
type: "content_block_delta",
|
|
38646
|
-
index: event.index,
|
|
39145
|
+
index: outIndex.get(event.index) ?? event.index,
|
|
38647
39146
|
delta: delta2
|
|
38648
39147
|
});
|
|
38649
39148
|
break;
|
|
38650
39149
|
}
|
|
38651
39150
|
case "blockEnd":
|
|
38652
|
-
|
|
39151
|
+
if (suppressed.has(event.index))
|
|
39152
|
+
break;
|
|
39153
|
+
yield frame("content_block_stop", {
|
|
39154
|
+
type: "content_block_stop",
|
|
39155
|
+
index: outIndex.get(event.index) ?? event.index
|
|
39156
|
+
});
|
|
38653
39157
|
break;
|
|
38654
39158
|
case "end":
|
|
38655
39159
|
yield frame("message_delta", {
|
|
@@ -38657,7 +39161,9 @@ async function* anthropicStream(events, requestId) {
|
|
|
38657
39161
|
delta: { stop_reason: STOP_REASON2[event.stopReason], stop_sequence: null },
|
|
38658
39162
|
usage: {
|
|
38659
39163
|
input_tokens: event.usage.inputTokens,
|
|
38660
|
-
output_tokens: event.usage.outputTokens
|
|
39164
|
+
output_tokens: event.usage.outputTokens,
|
|
39165
|
+
cache_read_input_tokens: event.usage.cacheReadTokens,
|
|
39166
|
+
cache_creation_input_tokens: event.usage.cacheWriteTokens
|
|
38661
39167
|
}
|
|
38662
39168
|
});
|
|
38663
39169
|
yield frame("message_stop", { type: "message_stop" });
|
|
@@ -38677,7 +39183,7 @@ function anthropicResponse(collected, requestId) {
|
|
|
38677
39183
|
type: "message",
|
|
38678
39184
|
role: "assistant",
|
|
38679
39185
|
model: collected.model,
|
|
38680
|
-
content: collected.content.map((b) => {
|
|
39186
|
+
content: collected.content.filter((b) => b.type !== "thinking" || b.signature !== undefined && b.signature !== "").map((b) => {
|
|
38681
39187
|
switch (b.type) {
|
|
38682
39188
|
case "text":
|
|
38683
39189
|
return { type: "text", text: b.text };
|
|
@@ -38693,7 +39199,9 @@ function anthropicResponse(collected, requestId) {
|
|
|
38693
39199
|
stop_sequence: null,
|
|
38694
39200
|
usage: {
|
|
38695
39201
|
input_tokens: collected.usage.inputTokens,
|
|
38696
|
-
output_tokens: collected.usage.outputTokens
|
|
39202
|
+
output_tokens: collected.usage.outputTokens,
|
|
39203
|
+
cache_read_input_tokens: collected.usage.cacheReadTokens,
|
|
39204
|
+
cache_creation_input_tokens: collected.usage.cacheWriteTokens
|
|
38697
39205
|
}
|
|
38698
39206
|
};
|
|
38699
39207
|
}
|
|
@@ -38702,6 +39210,15 @@ function anthropicErrorBody(code, message) {
|
|
|
38702
39210
|
}
|
|
38703
39211
|
|
|
38704
39212
|
// apps/gateway/src/egress/openai.ts
|
|
39213
|
+
function usageBody(usage) {
|
|
39214
|
+
const prompt = promptTokens(usage);
|
|
39215
|
+
return {
|
|
39216
|
+
prompt_tokens: prompt,
|
|
39217
|
+
completion_tokens: usage.outputTokens,
|
|
39218
|
+
total_tokens: prompt + usage.outputTokens,
|
|
39219
|
+
prompt_tokens_details: { cached_tokens: usage.cacheReadTokens }
|
|
39220
|
+
};
|
|
39221
|
+
}
|
|
38705
39222
|
var FINISH2 = {
|
|
38706
39223
|
endTurn: "stop",
|
|
38707
39224
|
maxTokens: "length",
|
|
@@ -38799,11 +39316,7 @@ async function* openaiStream(events, requestId, created) {
|
|
|
38799
39316
|
break;
|
|
38800
39317
|
case "end":
|
|
38801
39318
|
completed = true;
|
|
38802
|
-
yield emit(chunk(requestId, created, model, { index: 0, delta: {}, finish_reason: FINISH2[event.stopReason] },
|
|
38803
|
-
prompt_tokens: event.usage.inputTokens,
|
|
38804
|
-
completion_tokens: event.usage.outputTokens,
|
|
38805
|
-
total_tokens: event.usage.inputTokens + event.usage.outputTokens
|
|
38806
|
-
}));
|
|
39319
|
+
yield emit(chunk(requestId, created, model, { index: 0, delta: {}, finish_reason: FINISH2[event.stopReason] }, usageBody(event.usage)));
|
|
38807
39320
|
break;
|
|
38808
39321
|
case "error": {
|
|
38809
39322
|
const e = ERROR_TYPE3[event.code];
|
|
@@ -38840,11 +39353,7 @@ function openaiResponse(collected, requestId, created) {
|
|
|
38840
39353
|
finish_reason: FINISH2[collected.stopReason]
|
|
38841
39354
|
}
|
|
38842
39355
|
],
|
|
38843
|
-
usage:
|
|
38844
|
-
prompt_tokens: collected.usage.inputTokens,
|
|
38845
|
-
completion_tokens: collected.usage.outputTokens,
|
|
38846
|
-
total_tokens: collected.usage.inputTokens + collected.usage.outputTokens
|
|
38847
|
-
}
|
|
39356
|
+
usage: usageBody(collected.usage)
|
|
38848
39357
|
};
|
|
38849
39358
|
}
|
|
38850
39359
|
function openaiErrorBody(code, message) {
|
|
@@ -38853,14 +39362,19 @@ function openaiErrorBody(code, message) {
|
|
|
38853
39362
|
}
|
|
38854
39363
|
|
|
38855
39364
|
// apps/gateway/src/ingress/anthropic.ts
|
|
38856
|
-
var textBlock = exports_external.object({
|
|
39365
|
+
var textBlock = exports_external.object({
|
|
39366
|
+
type: exports_external.literal("text"),
|
|
39367
|
+
text: exports_external.string(),
|
|
39368
|
+
cache_control: cacheControlSchema.optional()
|
|
39369
|
+
});
|
|
38857
39370
|
var imageBlock = exports_external.object({
|
|
38858
39371
|
type: exports_external.literal("image"),
|
|
38859
39372
|
source: exports_external.object({
|
|
38860
39373
|
type: exports_external.literal("base64"),
|
|
38861
39374
|
media_type: exports_external.string(),
|
|
38862
39375
|
data: exports_external.string()
|
|
38863
|
-
})
|
|
39376
|
+
}),
|
|
39377
|
+
cache_control: cacheControlSchema.optional()
|
|
38864
39378
|
});
|
|
38865
39379
|
var thinkingBlock = exports_external.object({
|
|
38866
39380
|
type: exports_external.literal("thinking"),
|
|
@@ -38871,13 +39385,15 @@ var toolUseBlock = exports_external.object({
|
|
|
38871
39385
|
type: exports_external.literal("tool_use"),
|
|
38872
39386
|
id: exports_external.string(),
|
|
38873
39387
|
name: exports_external.string(),
|
|
38874
|
-
input: exports_external.record(exports_external.string(), exports_external.unknown()).default({})
|
|
39388
|
+
input: exports_external.record(exports_external.string(), exports_external.unknown()).default({}),
|
|
39389
|
+
cache_control: cacheControlSchema.optional()
|
|
38875
39390
|
});
|
|
38876
39391
|
var toolResultBlock = exports_external.object({
|
|
38877
39392
|
type: exports_external.literal("tool_result"),
|
|
38878
39393
|
tool_use_id: exports_external.string(),
|
|
38879
39394
|
content: exports_external.union([exports_external.string(), exports_external.array(exports_external.unknown())]).optional(),
|
|
38880
|
-
is_error: exports_external.boolean().optional()
|
|
39395
|
+
is_error: exports_external.boolean().optional(),
|
|
39396
|
+
cache_control: cacheControlSchema.optional()
|
|
38881
39397
|
});
|
|
38882
39398
|
var block = exports_external.discriminatedUnion("type", [
|
|
38883
39399
|
textBlock,
|
|
@@ -38901,7 +39417,8 @@ var schema = exports_external.object({
|
|
|
38901
39417
|
tools: exports_external.array(exports_external.object({
|
|
38902
39418
|
name: exports_external.string(),
|
|
38903
39419
|
description: exports_external.string().optional(),
|
|
38904
|
-
input_schema: exports_external.record(exports_external.string(), exports_external.unknown())
|
|
39420
|
+
input_schema: exports_external.record(exports_external.string(), exports_external.unknown()),
|
|
39421
|
+
cache_control: cacheControlSchema.optional()
|
|
38905
39422
|
})).optional(),
|
|
38906
39423
|
tool_choice: exports_external.union([
|
|
38907
39424
|
exports_external.object({ type: exports_external.enum(["auto", "any", "none"]) }),
|
|
@@ -38945,9 +39462,14 @@ function flattenToolResult(content) {
|
|
|
38945
39462
|
function toIrBlock(b) {
|
|
38946
39463
|
switch (b.type) {
|
|
38947
39464
|
case "text":
|
|
38948
|
-
return { type: "text", text: b.text };
|
|
39465
|
+
return { type: "text", text: b.text, ...irCacheControl(b.cache_control) };
|
|
38949
39466
|
case "image":
|
|
38950
|
-
return {
|
|
39467
|
+
return {
|
|
39468
|
+
type: "image",
|
|
39469
|
+
mediaType: b.source.media_type,
|
|
39470
|
+
data: b.source.data,
|
|
39471
|
+
...irCacheControl(b.cache_control)
|
|
39472
|
+
};
|
|
38951
39473
|
case "thinking":
|
|
38952
39474
|
return {
|
|
38953
39475
|
type: "thinking",
|
|
@@ -38955,13 +39477,20 @@ function toIrBlock(b) {
|
|
|
38955
39477
|
...b.signature !== undefined && { signature: b.signature }
|
|
38956
39478
|
};
|
|
38957
39479
|
case "tool_use":
|
|
38958
|
-
return {
|
|
39480
|
+
return {
|
|
39481
|
+
type: "toolUse",
|
|
39482
|
+
id: b.id,
|
|
39483
|
+
name: b.name,
|
|
39484
|
+
input: b.input,
|
|
39485
|
+
...irCacheControl(b.cache_control)
|
|
39486
|
+
};
|
|
38959
39487
|
case "tool_result":
|
|
38960
39488
|
return {
|
|
38961
39489
|
type: "toolResult",
|
|
38962
39490
|
toolUseId: b.tool_use_id,
|
|
38963
39491
|
content: flattenToolResult(b.content),
|
|
38964
|
-
isError: b.is_error ?? false
|
|
39492
|
+
isError: b.is_error ?? false,
|
|
39493
|
+
...irCacheControl(b.cache_control)
|
|
38965
39494
|
};
|
|
38966
39495
|
}
|
|
38967
39496
|
}
|
|
@@ -39008,7 +39537,11 @@ function parseAnthropicRequest(body2, headers) {
|
|
|
39008
39537
|
role: m.role,
|
|
39009
39538
|
content: typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content.map(toIrBlock)
|
|
39010
39539
|
}));
|
|
39011
|
-
const system = parsed.system === undefined ? undefined : typeof parsed.system === "string" ? [{ type: "text", text: parsed.system }] : parsed.system.map((b) => ({
|
|
39540
|
+
const system = parsed.system === undefined ? undefined : typeof parsed.system === "string" ? [{ type: "text", text: parsed.system }] : parsed.system.map((b) => ({
|
|
39541
|
+
type: "text",
|
|
39542
|
+
text: b.text,
|
|
39543
|
+
...irCacheControl(b.cache_control)
|
|
39544
|
+
}));
|
|
39012
39545
|
const request2 = {
|
|
39013
39546
|
model: parsed.model,
|
|
39014
39547
|
messages,
|
|
@@ -39026,7 +39559,8 @@ function parseAnthropicRequest(body2, headers) {
|
|
|
39026
39559
|
request2.tools = parsed.tools.map((t2) => ({
|
|
39027
39560
|
name: t2.name,
|
|
39028
39561
|
...t2.description !== undefined && { description: t2.description },
|
|
39029
|
-
inputSchema: t2.input_schema
|
|
39562
|
+
inputSchema: t2.input_schema,
|
|
39563
|
+
...irCacheControl(t2.cache_control)
|
|
39030
39564
|
}));
|
|
39031
39565
|
}
|
|
39032
39566
|
if (parsed.tool_choice !== undefined)
|
|
@@ -39048,8 +39582,16 @@ function parseAnthropicRequest(body2, headers) {
|
|
|
39048
39582
|
|
|
39049
39583
|
// apps/gateway/src/ingress/openai.ts
|
|
39050
39584
|
var part = exports_external.discriminatedUnion("type", [
|
|
39051
|
-
exports_external.object({
|
|
39052
|
-
|
|
39585
|
+
exports_external.object({
|
|
39586
|
+
type: exports_external.literal("text"),
|
|
39587
|
+
text: exports_external.string(),
|
|
39588
|
+
cache_control: exports_external.unknown().optional()
|
|
39589
|
+
}),
|
|
39590
|
+
exports_external.object({
|
|
39591
|
+
type: exports_external.literal("image_url"),
|
|
39592
|
+
image_url: exports_external.object({ url: exports_external.string() }),
|
|
39593
|
+
cache_control: exports_external.unknown().optional()
|
|
39594
|
+
})
|
|
39053
39595
|
]);
|
|
39054
39596
|
var toolCall = exports_external.object({
|
|
39055
39597
|
id: exports_external.string(),
|
|
@@ -39060,7 +39602,8 @@ var message2 = exports_external.object({
|
|
|
39060
39602
|
role: exports_external.enum(["system", "developer", "user", "assistant", "tool"]),
|
|
39061
39603
|
content: exports_external.union([exports_external.string(), exports_external.array(part), exports_external.null()]).optional(),
|
|
39062
39604
|
tool_calls: exports_external.array(toolCall).optional(),
|
|
39063
|
-
tool_call_id: exports_external.string().optional()
|
|
39605
|
+
tool_call_id: exports_external.string().optional(),
|
|
39606
|
+
cache_control: exports_external.unknown().optional()
|
|
39064
39607
|
});
|
|
39065
39608
|
var schema2 = exports_external.object({
|
|
39066
39609
|
model: exports_external.string().min(1),
|
|
@@ -39075,8 +39618,10 @@ var schema2 = exports_external.object({
|
|
|
39075
39618
|
function: exports_external.object({
|
|
39076
39619
|
name: exports_external.string(),
|
|
39077
39620
|
description: exports_external.string().optional(),
|
|
39078
|
-
parameters: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
39079
|
-
|
|
39621
|
+
parameters: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
|
|
39622
|
+
cache_control: exports_external.unknown().optional()
|
|
39623
|
+
}),
|
|
39624
|
+
cache_control: exports_external.unknown().optional()
|
|
39080
39625
|
})).optional(),
|
|
39081
39626
|
tool_choice: exports_external.union([
|
|
39082
39627
|
exports_external.enum(["auto", "none", "required"]),
|
|
@@ -39114,11 +39659,15 @@ function contentBlocks(content) {
|
|
|
39114
39659
|
return [];
|
|
39115
39660
|
return content.map((p) => {
|
|
39116
39661
|
if (p.type === "text")
|
|
39117
|
-
return { type: "text", text: p.text };
|
|
39662
|
+
return { type: "text", text: p.text, ...looseCacheControl(p.cache_control) };
|
|
39118
39663
|
const { mediaType, data } = parseDataUrl(p.image_url.url);
|
|
39119
|
-
return { type: "image", mediaType, data };
|
|
39664
|
+
return { type: "image", mediaType, data, ...looseCacheControl(p.cache_control) };
|
|
39120
39665
|
});
|
|
39121
39666
|
}
|
|
39667
|
+
function toolCacheControl(outer, inner) {
|
|
39668
|
+
const parsed = looseCacheControl(outer);
|
|
39669
|
+
return parsed.cacheControl === undefined ? looseCacheControl(inner) : parsed;
|
|
39670
|
+
}
|
|
39122
39671
|
function toIrToolChoice2(c) {
|
|
39123
39672
|
if (typeof c === "string") {
|
|
39124
39673
|
if (c === "required")
|
|
@@ -39136,7 +39685,9 @@ function parseOpenAIRequest(body2) {
|
|
|
39136
39685
|
const messages = [];
|
|
39137
39686
|
for (const m of parsed.messages) {
|
|
39138
39687
|
if (m.role === "system" || m.role === "developer") {
|
|
39139
|
-
|
|
39688
|
+
const blocks = contentBlocks(m.content);
|
|
39689
|
+
applyMessageCacheControl(blocks, m.cache_control);
|
|
39690
|
+
system.push(...blocks);
|
|
39140
39691
|
continue;
|
|
39141
39692
|
}
|
|
39142
39693
|
if (m.role === "tool") {
|
|
@@ -39150,7 +39701,8 @@ function parseOpenAIRequest(body2) {
|
|
|
39150
39701
|
type: "toolResult",
|
|
39151
39702
|
toolUseId: m.tool_call_id,
|
|
39152
39703
|
content: typeof m.content === "string" ? m.content : "",
|
|
39153
|
-
isError: false
|
|
39704
|
+
isError: false,
|
|
39705
|
+
...looseCacheControl(m.cache_control)
|
|
39154
39706
|
}
|
|
39155
39707
|
]
|
|
39156
39708
|
});
|
|
@@ -39165,6 +39717,7 @@ function parseOpenAIRequest(body2) {
|
|
|
39165
39717
|
input: parseArguments(call.function.arguments)
|
|
39166
39718
|
});
|
|
39167
39719
|
}
|
|
39720
|
+
applyMessageCacheControl(content, m.cache_control);
|
|
39168
39721
|
if (content.length > 0)
|
|
39169
39722
|
messages.push({ role: m.role, content });
|
|
39170
39723
|
}
|
|
@@ -39190,7 +39743,8 @@ function parseOpenAIRequest(body2) {
|
|
|
39190
39743
|
request2.tools = parsed.tools.map((t2) => ({
|
|
39191
39744
|
name: t2.function.name,
|
|
39192
39745
|
...t2.function.description !== undefined && { description: t2.function.description },
|
|
39193
|
-
inputSchema: t2.function.parameters ?? { type: "object" }
|
|
39746
|
+
inputSchema: t2.function.parameters ?? { type: "object" },
|
|
39747
|
+
...toolCacheControl(t2.cache_control, t2.function.cache_control)
|
|
39194
39748
|
}));
|
|
39195
39749
|
}
|
|
39196
39750
|
if (parsed.tool_choice !== undefined)
|
|
@@ -39203,6 +39757,85 @@ function parseOpenAIRequest(body2) {
|
|
|
39203
39757
|
return validateRequest(request2);
|
|
39204
39758
|
}
|
|
39205
39759
|
|
|
39760
|
+
// apps/gateway/src/routes/models.ts
|
|
39761
|
+
var CREATED_AT = new Date(0).toISOString();
|
|
39762
|
+
function narrower(a, b) {
|
|
39763
|
+
const context = [a.contextWindow, b.contextWindow].filter((n) => n !== undefined);
|
|
39764
|
+
const output = [a.maxOutputTokens, b.maxOutputTokens].filter((n) => n !== undefined);
|
|
39765
|
+
return {
|
|
39766
|
+
...context.length === 0 ? {} : { contextWindow: Math.min(...context) },
|
|
39767
|
+
...output.length === 0 ? {} : { maxOutputTokens: Math.min(...output) }
|
|
39768
|
+
};
|
|
39769
|
+
}
|
|
39770
|
+
function targetLimits(target, auths) {
|
|
39771
|
+
const ways = auths.size === 0 ? ["apiKey"] : [...auths];
|
|
39772
|
+
let listed = {};
|
|
39773
|
+
for (const auth of ways) {
|
|
39774
|
+
const entry = catalogLimits(target.provider, target.model, auth);
|
|
39775
|
+
if (entry === null)
|
|
39776
|
+
continue;
|
|
39777
|
+
listed = narrower(listed, {
|
|
39778
|
+
contextWindow: entry.contextWindow,
|
|
39779
|
+
maxOutputTokens: entry.maxOutputTokens
|
|
39780
|
+
});
|
|
39781
|
+
}
|
|
39782
|
+
const contextWindow = target.contextWindow ?? listed.contextWindow;
|
|
39783
|
+
const maxOutputTokens = target.maxOutputTokens ?? listed.maxOutputTokens;
|
|
39784
|
+
return {
|
|
39785
|
+
...contextWindow === undefined ? {} : { contextWindow },
|
|
39786
|
+
...maxOutputTokens === undefined ? {} : { maxOutputTokens }
|
|
39787
|
+
};
|
|
39788
|
+
}
|
|
39789
|
+
function limitsOf(model, authsByProvider) {
|
|
39790
|
+
let limits = {};
|
|
39791
|
+
for (const target of model.targets) {
|
|
39792
|
+
limits = narrower(limits, targetLimits(target, authsByProvider.get(target.provider) ?? new Set));
|
|
39793
|
+
}
|
|
39794
|
+
return limits;
|
|
39795
|
+
}
|
|
39796
|
+
function displayName(model) {
|
|
39797
|
+
const only = model.targets.length === 1 ? model.targets[0] : undefined;
|
|
39798
|
+
if (only === undefined)
|
|
39799
|
+
return model.id;
|
|
39800
|
+
const labelled = PROVIDER_MODEL_CATALOG[only.provider]?.models.find((choice) => choice.id === only.model);
|
|
39801
|
+
return labelled?.label ?? model.id;
|
|
39802
|
+
}
|
|
39803
|
+
function servingAuths(credentials) {
|
|
39804
|
+
const byProvider = new Map;
|
|
39805
|
+
for (const credential of credentials) {
|
|
39806
|
+
if (!credential.enabled)
|
|
39807
|
+
continue;
|
|
39808
|
+
const ways = byProvider.get(credential.provider) ?? new Set;
|
|
39809
|
+
ways.add(credential.authType);
|
|
39810
|
+
byProvider.set(credential.provider, ways);
|
|
39811
|
+
}
|
|
39812
|
+
return byProvider;
|
|
39813
|
+
}
|
|
39814
|
+
function describeModel(model, credentials) {
|
|
39815
|
+
const limits = limitsOf(model, servingAuths(credentials));
|
|
39816
|
+
return {
|
|
39817
|
+
id: model.id,
|
|
39818
|
+
object: "model",
|
|
39819
|
+
type: "model",
|
|
39820
|
+
display_name: displayName(model),
|
|
39821
|
+
created: 0,
|
|
39822
|
+
created_at: CREATED_AT,
|
|
39823
|
+
owned_by: "omnigateway",
|
|
39824
|
+
...limits.contextWindow === undefined ? {} : { max_input_tokens: limits.contextWindow },
|
|
39825
|
+
...limits.maxOutputTokens === undefined ? {} : { max_tokens: limits.maxOutputTokens }
|
|
39826
|
+
};
|
|
39827
|
+
}
|
|
39828
|
+
function modelListBody(models, credentials) {
|
|
39829
|
+
const data = models.map((model) => describeModel(model, credentials));
|
|
39830
|
+
return {
|
|
39831
|
+
object: "list",
|
|
39832
|
+
data,
|
|
39833
|
+
has_more: false,
|
|
39834
|
+
first_id: data[0]?.id ?? null,
|
|
39835
|
+
last_id: data[data.length - 1]?.id ?? null
|
|
39836
|
+
};
|
|
39837
|
+
}
|
|
39838
|
+
|
|
39206
39839
|
// apps/gateway/src/routes/proxy.ts
|
|
39207
39840
|
var KEEPALIVE_MS = 1e4;
|
|
39208
39841
|
var KEEPALIVE = Symbol("keepalive");
|
|
@@ -39234,9 +39867,9 @@ function asGatewayError(error51) {
|
|
|
39234
39867
|
function sseResponse(frames, onDone, keepaliveMs) {
|
|
39235
39868
|
const encoder2 = new TextEncoder;
|
|
39236
39869
|
let done = null;
|
|
39237
|
-
const runOnce = () => {
|
|
39870
|
+
const runOnce = (cancelled = false) => {
|
|
39238
39871
|
if (done === null)
|
|
39239
|
-
done = onDone();
|
|
39872
|
+
done = onDone(cancelled);
|
|
39240
39873
|
return done;
|
|
39241
39874
|
};
|
|
39242
39875
|
let inflight = null;
|
|
@@ -39269,7 +39902,7 @@ data: ${data}
|
|
|
39269
39902
|
},
|
|
39270
39903
|
async cancel() {
|
|
39271
39904
|
await frames.return(undefined);
|
|
39272
|
-
await runOnce();
|
|
39905
|
+
await runOnce(true);
|
|
39273
39906
|
}
|
|
39274
39907
|
});
|
|
39275
39908
|
return new Response(stream2, { headers: SSE_HEADERS });
|
|
@@ -39280,9 +39913,34 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
39280
39913
|
let keyId = null;
|
|
39281
39914
|
let requestedModel = "";
|
|
39282
39915
|
try {
|
|
39283
|
-
|
|
39916
|
+
let key;
|
|
39917
|
+
try {
|
|
39918
|
+
key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
|
|
39919
|
+
} catch (error51) {
|
|
39920
|
+
if (error51 instanceof GatewayError && error51.code === "AUTH") {
|
|
39921
|
+
deps.logger.warn("authentication rejected", {
|
|
39922
|
+
requestId,
|
|
39923
|
+
surface,
|
|
39924
|
+
reason: "invalid credentials"
|
|
39925
|
+
});
|
|
39926
|
+
}
|
|
39927
|
+
throw error51;
|
|
39928
|
+
}
|
|
39284
39929
|
keyId = key.id;
|
|
39285
|
-
|
|
39930
|
+
try {
|
|
39931
|
+
rateLimiter.consume(key.id, key.rateLimitPerMin);
|
|
39932
|
+
} catch (error51) {
|
|
39933
|
+
if (error51 instanceof GatewayError && error51.code === "RATE_LIMIT") {
|
|
39934
|
+
deps.logger.warn("rate limit rejected", {
|
|
39935
|
+
requestId,
|
|
39936
|
+
surface,
|
|
39937
|
+
apiKeyId: key.id,
|
|
39938
|
+
code: error51.code,
|
|
39939
|
+
retryAfterMs: error51.retryAfterMs
|
|
39940
|
+
});
|
|
39941
|
+
}
|
|
39942
|
+
throw error51;
|
|
39943
|
+
}
|
|
39286
39944
|
const body2 = await request2.json();
|
|
39287
39945
|
const chatRequest = surface === "anthropic" ? parseAnthropicRequest(body2, request2.headers) : parseOpenAIRequest(body2);
|
|
39288
39946
|
if (key.modelAllowlist !== null && !key.modelAllowlist.includes(chatRequest.model)) {
|
|
@@ -39294,7 +39952,7 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
39294
39952
|
...deps,
|
|
39295
39953
|
async onRoute(target) {
|
|
39296
39954
|
if (began) {
|
|
39297
|
-
await routeLog(deps.store, requestId, target);
|
|
39955
|
+
await routeLog(deps.store, requestId, target, deps.logger);
|
|
39298
39956
|
return;
|
|
39299
39957
|
}
|
|
39300
39958
|
began = true;
|
|
@@ -39306,10 +39964,19 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
39306
39964
|
resolvedModel: target.model,
|
|
39307
39965
|
credentialId: target.credentialId
|
|
39308
39966
|
});
|
|
39309
|
-
await beginLog(deps.store, log2, keyId);
|
|
39967
|
+
await beginLog(deps.store, log2, keyId, deps.logger);
|
|
39310
39968
|
}
|
|
39311
39969
|
}, request2.signal, requestId);
|
|
39312
|
-
const log = () =>
|
|
39970
|
+
const log = async (cancelled = false) => {
|
|
39971
|
+
const completed = outcome.log();
|
|
39972
|
+
const wasCancelled = cancelled || request2.signal.aborted;
|
|
39973
|
+
if (wasCancelled && completed.status === 0) {
|
|
39974
|
+
completed.status = 499;
|
|
39975
|
+
completed.errorCode = "interrupted";
|
|
39976
|
+
completed.durationMs = deps.now() - startedAt;
|
|
39977
|
+
}
|
|
39978
|
+
await finishLog(deps.store, completed, keyId, deps.logger);
|
|
39979
|
+
};
|
|
39313
39980
|
if (chatRequest.stream) {
|
|
39314
39981
|
const frames = surface === "anthropic" ? anthropicStream(outcome.events, requestId) : openaiStream(outcome.events, requestId, Math.floor(deps.now() / 1000));
|
|
39315
39982
|
return sseResponse(frames, log, deps.keepaliveMs);
|
|
@@ -39329,41 +39996,55 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
39329
39996
|
});
|
|
39330
39997
|
} catch (error51) {
|
|
39331
39998
|
const gatewayError = asGatewayError(error51);
|
|
39332
|
-
|
|
39999
|
+
const completed = newCompletedRequestLog(requestId, startedAt, {
|
|
39333
40000
|
requestedModel,
|
|
39334
40001
|
status: HTTP_STATUS[gatewayError.code],
|
|
39335
|
-
errorCode: gatewayError.code
|
|
39336
|
-
|
|
40002
|
+
errorCode: gatewayError.code,
|
|
40003
|
+
durationMs: deps.now() - startedAt
|
|
40004
|
+
});
|
|
40005
|
+
await finishLog(deps.store, completed, keyId, deps.logger);
|
|
40006
|
+
deps.logger.warn("request rejected", {
|
|
40007
|
+
requestId,
|
|
40008
|
+
surface,
|
|
40009
|
+
status: completed.status,
|
|
40010
|
+
code: gatewayError.code,
|
|
40011
|
+
reason: gatewayError.message
|
|
40012
|
+
});
|
|
39337
40013
|
return errorResponse(surface, gatewayError.code, gatewayError.message);
|
|
39338
40014
|
}
|
|
39339
40015
|
}
|
|
39340
40016
|
function proxyRoutes(deps) {
|
|
39341
40017
|
const rateLimiter = deps.rateLimiter ?? new ApiKeyRateLimiter(deps.now);
|
|
40018
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
39342
40019
|
const dispatchDeps = {
|
|
39343
40020
|
...deps,
|
|
39344
|
-
|
|
40021
|
+
logger: logger2,
|
|
40022
|
+
snapshots: deps.snapshots ?? createRoutingSnapshotCache(deps.store, logger2),
|
|
39345
40023
|
keepaliveMs: deps.keepaliveMs ?? KEEPALIVE_MS
|
|
39346
40024
|
};
|
|
39347
40025
|
return new Elysia().post("/v1/messages", ({ request: request2 }) => handle(dispatchDeps, rateLimiter, "anthropic", request2)).post("/v1/chat/completions", ({ request: request2 }) => handle(dispatchDeps, rateLimiter, "openai", request2)).get("/v1/models", async ({ request: request2 }) => {
|
|
39348
40026
|
try {
|
|
39349
40027
|
const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
|
|
39350
|
-
const
|
|
40028
|
+
const snapshot = await dispatchDeps.snapshots.get(deps.now());
|
|
40029
|
+
const models = [...snapshot.models.values()];
|
|
39351
40030
|
const visibleModels = key.modelAllowlist === null ? models : models.filter((model) => key.modelAllowlist?.includes(model.id));
|
|
39352
|
-
return Response.json(
|
|
39353
|
-
object: "list",
|
|
39354
|
-
data: visibleModels.map((m) => ({
|
|
39355
|
-
id: m.id,
|
|
39356
|
-
object: "model",
|
|
39357
|
-
created: 0,
|
|
39358
|
-
owned_by: "omnigateway"
|
|
39359
|
-
}))
|
|
39360
|
-
});
|
|
40031
|
+
return Response.json(modelListBody(visibleModels, snapshot.credentials));
|
|
39361
40032
|
} catch (error51) {
|
|
39362
40033
|
const gatewayError = asGatewayError(error51);
|
|
40034
|
+
logger2.error("model listing failed", {
|
|
40035
|
+
status: HTTP_STATUS[gatewayError.code],
|
|
40036
|
+
code: gatewayError.code,
|
|
40037
|
+
reason: gatewayError.message
|
|
40038
|
+
});
|
|
39363
40039
|
return errorResponse("anthropic", gatewayError.code, gatewayError.message);
|
|
39364
40040
|
}
|
|
39365
40041
|
}).onError(({ error: error51 }) => {
|
|
39366
40042
|
const gatewayError = asGatewayError(error51);
|
|
40043
|
+
logger2.error("unhandled proxy route error", {
|
|
40044
|
+
status: HTTP_STATUS[gatewayError.code],
|
|
40045
|
+
code: gatewayError.code,
|
|
40046
|
+
reason: gatewayError.message
|
|
40047
|
+
});
|
|
39367
40048
|
return errorResponse("anthropic", gatewayError.code, gatewayError.message);
|
|
39368
40049
|
});
|
|
39369
40050
|
}
|
|
@@ -39372,18 +40053,20 @@ function proxyRoutes(deps) {
|
|
|
39372
40053
|
var ADMIN_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
|
|
39373
40054
|
function createApp(deps) {
|
|
39374
40055
|
const now = deps.now ?? (() => Date.now());
|
|
40056
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
39375
40057
|
const rand = deps.rand ?? Math.random;
|
|
39376
|
-
const http = deps.http ?? nodeHttpClient();
|
|
40058
|
+
const http = deps.http ?? nodeHttpClient({ logger: logger2, now });
|
|
39377
40059
|
const adapters = deps.adapters ?? ADAPTERS;
|
|
39378
40060
|
const requestId = deps.requestId ?? (() => `req_${crypto.randomUUID()}`);
|
|
39379
40061
|
const rateLimiter = new ApiKeyRateLimiter(now);
|
|
39380
|
-
const snapshots = createRoutingSnapshotCache(deps.store);
|
|
40062
|
+
const snapshots = createRoutingSnapshotCache(deps.store, logger2);
|
|
39381
40063
|
const admin = createAdminAuth(deps.store, { now, sessionTtlMs: ADMIN_SESSION_TTL_MS });
|
|
39382
40064
|
const refresh = deps.refresh ?? createRefresher({
|
|
39383
40065
|
store: deps.store,
|
|
39384
40066
|
providers: OAUTH_PROVIDERS,
|
|
39385
40067
|
http,
|
|
39386
|
-
now
|
|
40068
|
+
now,
|
|
40069
|
+
logger: logger2
|
|
39387
40070
|
});
|
|
39388
40071
|
const staticDir = deps.staticDir ? resolve(deps.staticDir) : undefined;
|
|
39389
40072
|
let staticRoot;
|
|
@@ -39401,13 +40084,22 @@ function createApp(deps) {
|
|
|
39401
40084
|
rand,
|
|
39402
40085
|
refresh,
|
|
39403
40086
|
requestId,
|
|
39404
|
-
rateLimiter
|
|
39405
|
-
|
|
40087
|
+
rateLimiter,
|
|
40088
|
+
logger: logger2
|
|
40089
|
+
})).use(adminRoutes({
|
|
40090
|
+
store: deps.store,
|
|
40091
|
+
admin,
|
|
40092
|
+
now,
|
|
40093
|
+
sessionTtlMs: ADMIN_SESSION_TTL_MS,
|
|
40094
|
+
logger: logger2,
|
|
40095
|
+
...deps.console === undefined ? {} : { console: deps.console }
|
|
40096
|
+
})).use(connectRoutes({
|
|
39406
40097
|
store: deps.store,
|
|
39407
40098
|
admin,
|
|
39408
40099
|
providers: OAUTH_PROVIDERS,
|
|
39409
40100
|
http,
|
|
39410
|
-
now
|
|
40101
|
+
now,
|
|
40102
|
+
logger: logger2
|
|
39411
40103
|
})).get("/*", ({ path, request: request2, set: set2 }) => {
|
|
39412
40104
|
const rawPath = new URL(request2.url).pathname;
|
|
39413
40105
|
const error51 = () => {
|
|
@@ -39465,13 +40157,15 @@ var DAY_MS = 24 * 60 * 60 * 1000;
|
|
|
39465
40157
|
var ROLLUP_RETENTION_DAYS = 400;
|
|
39466
40158
|
async function pruneLogs(store, now) {
|
|
39467
40159
|
const settings = await store.config.getSettings();
|
|
39468
|
-
await store.usage.prune(now - settings.logRetentionDays * DAY_MS);
|
|
39469
|
-
await store.usage.pruneDaily(now - ROLLUP_RETENTION_DAYS * DAY_MS);
|
|
40160
|
+
const raw = await store.usage.prune(now - settings.logRetentionDays * DAY_MS);
|
|
40161
|
+
const daily = await store.usage.pruneDaily(now - ROLLUP_RETENTION_DAYS * DAY_MS);
|
|
40162
|
+
return { raw, daily };
|
|
39470
40163
|
}
|
|
39471
40164
|
function startMaintenance(deps) {
|
|
40165
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
39472
40166
|
const timer = setInterval(() => {
|
|
39473
|
-
pruneLogs(deps.store, deps.now()).catch((error51) => {
|
|
39474
|
-
|
|
40167
|
+
pruneLogs(deps.store, deps.now()).then(({ raw, daily }) => logger2.debug("request logs pruned", { rawCount: raw, dailyCount: daily })).catch((error51) => {
|
|
40168
|
+
logger2.error("log pruning failed", {
|
|
39475
40169
|
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
39476
40170
|
});
|
|
39477
40171
|
});
|
|
@@ -39486,6 +40180,7 @@ function due(credentials, now) {
|
|
|
39486
40180
|
return credentials.filter((c) => c.enabled && c.authType === "oauth" && c.expiresAt !== null && c.expiresAt - SCHEDULER_REFRESH_LEAD_MS <= now);
|
|
39487
40181
|
}
|
|
39488
40182
|
async function sweep(deps) {
|
|
40183
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
39489
40184
|
const credentials = await deps.store.credentials.list();
|
|
39490
40185
|
let refreshed = 0;
|
|
39491
40186
|
for (const credential of due(credentials, deps.now())) {
|
|
@@ -39504,23 +40199,25 @@ async function sweep(deps) {
|
|
|
39504
40199
|
refreshed += 1;
|
|
39505
40200
|
} catch (error51) {
|
|
39506
40201
|
const code = error51 instanceof GatewayError ? error51.code : "INTERNAL";
|
|
39507
|
-
|
|
39508
|
-
credentialId: credential.id,
|
|
40202
|
+
logger2.warn("scheduled token refresh failed", {
|
|
39509
40203
|
provider: credential.provider,
|
|
39510
|
-
|
|
40204
|
+
credentialId: credential.id,
|
|
40205
|
+
code,
|
|
40206
|
+
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
39511
40207
|
});
|
|
39512
40208
|
}
|
|
39513
40209
|
}
|
|
39514
40210
|
return refreshed;
|
|
39515
40211
|
}
|
|
39516
40212
|
function startRefreshScheduler(deps) {
|
|
40213
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
39517
40214
|
let running = false;
|
|
39518
40215
|
const timer = setInterval(() => {
|
|
39519
40216
|
if (running)
|
|
39520
40217
|
return;
|
|
39521
40218
|
running = true;
|
|
39522
40219
|
sweep(deps).catch((error51) => {
|
|
39523
|
-
|
|
40220
|
+
logger2.error("token refresh sweep failed", {
|
|
39524
40221
|
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
39525
40222
|
});
|
|
39526
40223
|
}).finally(() => {
|
|
@@ -39533,6 +40230,7 @@ function startRefreshScheduler(deps) {
|
|
|
39533
40230
|
|
|
39534
40231
|
// apps/gateway/src/quota/poller.ts
|
|
39535
40232
|
async function startQuotaPoller(deps) {
|
|
40233
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
39536
40234
|
const { quotaPollIntervalMs } = await deps.store.config.getSettings();
|
|
39537
40235
|
if (quotaPollIntervalMs <= 0)
|
|
39538
40236
|
return () => {};
|
|
@@ -39542,7 +40240,7 @@ async function startQuotaPoller(deps) {
|
|
|
39542
40240
|
return;
|
|
39543
40241
|
running = true;
|
|
39544
40242
|
poll(deps).catch((error51) => {
|
|
39545
|
-
|
|
40243
|
+
logger2.error("quota poll failed", {
|
|
39546
40244
|
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
39547
40245
|
});
|
|
39548
40246
|
}).finally(() => {
|
|
@@ -39556,62 +40254,128 @@ async function startQuotaPoller(deps) {
|
|
|
39556
40254
|
}
|
|
39557
40255
|
|
|
39558
40256
|
// apps/gateway/src/index.ts
|
|
39559
|
-
|
|
39560
|
-
|
|
39561
|
-
|
|
39562
|
-
|
|
39563
|
-
|
|
39564
|
-
|
|
39565
|
-
|
|
39566
|
-
|
|
39567
|
-
|
|
39568
|
-
}
|
|
39569
|
-
var
|
|
39570
|
-
|
|
39571
|
-
|
|
39572
|
-
|
|
39573
|
-
|
|
39574
|
-
|
|
39575
|
-
|
|
39576
|
-
|
|
39577
|
-
|
|
39578
|
-
|
|
39579
|
-
|
|
39580
|
-
|
|
39581
|
-
|
|
39582
|
-
|
|
39583
|
-
|
|
39584
|
-
|
|
39585
|
-
|
|
39586
|
-
|
|
39587
|
-
|
|
39588
|
-
|
|
39589
|
-
|
|
39590
|
-
|
|
39591
|
-
|
|
39592
|
-
|
|
39593
|
-
|
|
39594
|
-
|
|
39595
|
-
|
|
39596
|
-
|
|
39597
|
-
|
|
39598
|
-
|
|
39599
|
-
|
|
39600
|
-
|
|
39601
|
-
|
|
39602
|
-
|
|
39603
|
-
|
|
39604
|
-
|
|
40257
|
+
function stdoutLogger(level) {
|
|
40258
|
+
return createLogger({
|
|
40259
|
+
level,
|
|
40260
|
+
color: Boolean(process.stdout.isTTY),
|
|
40261
|
+
write: (line) => {
|
|
40262
|
+
process.stdout.write(`${line}
|
|
40263
|
+
`);
|
|
40264
|
+
}
|
|
40265
|
+
});
|
|
40266
|
+
}
|
|
40267
|
+
var logger2 = stdoutLogger("info");
|
|
40268
|
+
function consoleSource(logFile) {
|
|
40269
|
+
const source = resolveConsoleSource({
|
|
40270
|
+
logFile,
|
|
40271
|
+
unitInstalled: process.env.JOURNAL_STREAM !== undefined,
|
|
40272
|
+
scope: process.env.MANAGERPID === undefined ? "system" : "user"
|
|
40273
|
+
});
|
|
40274
|
+
return {
|
|
40275
|
+
source,
|
|
40276
|
+
deps: {
|
|
40277
|
+
readFile: (path, lines) => tailFile(path, lines),
|
|
40278
|
+
run: async (argv) => {
|
|
40279
|
+
const [cmd, ...args] = argv;
|
|
40280
|
+
if (cmd === undefined)
|
|
40281
|
+
return { code: 1, stdout: "", stderr: "empty argv" };
|
|
40282
|
+
const proc = Bun.spawn([cmd, ...args], { stdout: "pipe", stderr: "pipe" });
|
|
40283
|
+
const [stdout, stderr] = await Promise.all([
|
|
40284
|
+
new Response(proc.stdout).text(),
|
|
40285
|
+
new Response(proc.stderr).text()
|
|
40286
|
+
]);
|
|
40287
|
+
return { code: await proc.exited, stdout, stderr };
|
|
40288
|
+
}
|
|
40289
|
+
}
|
|
40290
|
+
};
|
|
40291
|
+
}
|
|
40292
|
+
async function main() {
|
|
40293
|
+
const config2 = loadConfig(process.env);
|
|
40294
|
+
logger2 = stdoutLogger(config2.logLevel);
|
|
40295
|
+
if (config2.logLevelFallbackFrom !== null) {
|
|
40296
|
+
logger2.info("invalid log level; using info", { reason: config2.logLevelFallbackFrom });
|
|
40297
|
+
}
|
|
40298
|
+
logger2.info("omnigateway booting", { host: config2.host, port: config2.port });
|
|
40299
|
+
function dashboardDir() {
|
|
40300
|
+
if (config2.staticDir !== null)
|
|
40301
|
+
return config2.staticDir;
|
|
40302
|
+
const candidates = [
|
|
40303
|
+
resolve2(import.meta.dir, "./public"),
|
|
40304
|
+
resolve2(import.meta.dir, "../../dashboard/dist")
|
|
40305
|
+
];
|
|
40306
|
+
return candidates.find((path) => existsSync(path)) ?? candidates[1];
|
|
40307
|
+
}
|
|
40308
|
+
const encryptionKey = await deriveKey(config2.encryptionKey);
|
|
40309
|
+
const store = await createStore({
|
|
40310
|
+
path: config2.databasePath,
|
|
40311
|
+
encryptionKey,
|
|
40312
|
+
logger: logger2
|
|
40313
|
+
});
|
|
40314
|
+
const swept = await store.usage.sweepPending();
|
|
40315
|
+
if (swept > 0)
|
|
40316
|
+
logger2.info("retired interrupted requests", { count: swept });
|
|
40317
|
+
const console2 = consoleSource(config2.logFile);
|
|
40318
|
+
logger2.info("console log source resolved", { reason: console2.source.kind });
|
|
40319
|
+
const now = () => Date.now();
|
|
40320
|
+
const http = nodeHttpClient({ logger: logger2, now });
|
|
40321
|
+
const refresh = createRefresher({ store, providers: OAUTH_PROVIDERS, http, now, logger: logger2 });
|
|
40322
|
+
const staticDir = dashboardDir();
|
|
40323
|
+
logger2.info(existsSync(staticDir) ? "dashboard directory resolved" : "dashboard directory absent", {
|
|
40324
|
+
path: staticDir
|
|
40325
|
+
});
|
|
40326
|
+
const app = createApp({
|
|
40327
|
+
store,
|
|
40328
|
+
baseUrl: config2.baseUrl,
|
|
40329
|
+
http,
|
|
40330
|
+
now,
|
|
40331
|
+
refresh,
|
|
40332
|
+
staticDir,
|
|
40333
|
+
logger: logger2,
|
|
40334
|
+
console: console2
|
|
40335
|
+
});
|
|
40336
|
+
const stopMaintenance = startMaintenance({ store, now, logger: logger2 });
|
|
40337
|
+
const stopRefreshScheduler = startRefreshScheduler({ store, refresh, now, logger: logger2 });
|
|
40338
|
+
const stopQuotaPoller = await startQuotaPoller({
|
|
40339
|
+
store,
|
|
40340
|
+
providers: OAUTH_PROVIDERS,
|
|
40341
|
+
http,
|
|
40342
|
+
refresh,
|
|
40343
|
+
now,
|
|
40344
|
+
logger: logger2
|
|
40345
|
+
});
|
|
40346
|
+
app.listen({ port: config2.port, hostname: config2.host, idleTimeout: 255 });
|
|
40347
|
+
logger2.info("omnigateway listening", { host: config2.host, port: config2.port });
|
|
40348
|
+
let shuttingDown = false;
|
|
40349
|
+
function exitAfterClosingStore(code) {
|
|
40350
|
+
try {
|
|
40351
|
+
store.close();
|
|
40352
|
+
} finally {
|
|
40353
|
+
process.exit(code);
|
|
40354
|
+
}
|
|
40355
|
+
}
|
|
40356
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
40357
|
+
process.on(signal, () => {
|
|
40358
|
+
if (shuttingDown)
|
|
40359
|
+
exitAfterClosingStore(1);
|
|
40360
|
+
logger2.info("shutdown requested", { reason: signal });
|
|
40361
|
+
shuttingDown = true;
|
|
40362
|
+
stopMaintenance();
|
|
40363
|
+
stopRefreshScheduler();
|
|
40364
|
+
stopQuotaPoller();
|
|
40365
|
+
app.stop().then(() => exitAfterClosingStore(0), (error51) => {
|
|
40366
|
+
logger2.error("shutdown failed", {
|
|
40367
|
+
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
40368
|
+
});
|
|
40369
|
+
return exitAfterClosingStore(1);
|
|
40370
|
+
});
|
|
40371
|
+
});
|
|
39605
40372
|
}
|
|
39606
40373
|
}
|
|
39607
|
-
|
|
39608
|
-
|
|
39609
|
-
|
|
39610
|
-
|
|
39611
|
-
|
|
39612
|
-
stopMaintenance();
|
|
39613
|
-
stopRefreshScheduler();
|
|
39614
|
-
stopQuotaPoller();
|
|
39615
|
-
app.stop().then(() => exitAfterClosingStore(0), () => exitAfterClosingStore(1));
|
|
40374
|
+
try {
|
|
40375
|
+
await main();
|
|
40376
|
+
} catch (error51) {
|
|
40377
|
+
logger2.error("gateway boot failed", {
|
|
40378
|
+
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
39616
40379
|
});
|
|
40380
|
+
process.exit(1);
|
|
39617
40381
|
}
|