omnigateway 0.1.5 → 0.1.6
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 +35 -1
- package/bin/omni.js +263 -105
- package/gateway.js +844 -246
- package/package.json +1 -1
- package/public/assets/{CopyValue-CvEMy9Zo.js → CopyValue-cAwgC1sU.js} +1 -1
- package/public/assets/{Modal-C4y9zALa.js → Modal-Dqir9wBz.js} +1 -1
- package/public/assets/{Rack-CRQSwSeB.js → Rack-XtfiZ6GC.js} +1 -1
- package/public/assets/{Toggle-CBq5XAMU.js → Toggle-BhViDMm9.js} +1 -1
- package/public/assets/{_app-VCodWes4.js → _app-B8lqq_ks.js} +1 -1
- package/public/assets/{_app.accounts-BP3L84us.js → _app.accounts-DSMHSYBF.js} +1 -1
- package/public/assets/{_app.index-C5Vd-zlp.js → _app.index-DXH5sH41.js} +1 -1
- package/public/assets/{_app.keys-0e1BHrIZ.js → _app.keys-DdEyVy_b.js} +1 -1
- package/public/assets/{_app.logs-DNSND96R.js → _app.logs-Camxditb.js} +1 -1
- package/public/assets/_app.models-CuD-JtGS.js +144 -0
- package/public/assets/{_app.settings-Du5Nsp9f.js → _app.settings-DA1aYcR4.js} +1 -1
- package/public/assets/{_app.usage-DKq4Hsia.js → _app.usage-DAyYUfIi.js} +1 -1
- package/public/assets/catalog-C_OQ0icG.js +1 -0
- package/public/assets/{index-BYyeRTQJ.js → index-BtjyVE1i.js} +2 -2
- package/public/assets/{login-Gyf-1fSa.js → login-DXHM-Noj.js} +1 -1
- package/public/assets/{trash-2-DFVxurNH.js → trash-2-JF_uX2gE.js} +1 -1
- package/public/index.html +1 -1
- package/public/assets/_app.models-DzFIbnem.js +0 -144
- package/public/assets/catalog-DVRPlBJJ.js +0 -1
package/gateway.js
CHANGED
|
@@ -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,117 @@ 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
|
+
function createLogger(opts) {
|
|
5418
|
+
const threshold = LEVELS[opts.level];
|
|
5419
|
+
const now = opts.now ?? (() => Date.now());
|
|
5420
|
+
const color = opts.color ?? false;
|
|
5421
|
+
const emit = (level, msg, fields) => {
|
|
5422
|
+
if (LEVELS[level] < threshold)
|
|
5423
|
+
return;
|
|
5424
|
+
try {
|
|
5425
|
+
opts.write(formatLine(level, now(), msg, fields, color));
|
|
5426
|
+
} catch {}
|
|
5427
|
+
};
|
|
5428
|
+
return {
|
|
5429
|
+
debug: (msg, fields) => emit("debug", msg, fields),
|
|
5430
|
+
info: (msg, fields) => emit("info", msg, fields),
|
|
5431
|
+
warn: (msg, fields) => emit("warn", msg, fields),
|
|
5432
|
+
error: (msg, fields) => emit("error", msg, fields),
|
|
5433
|
+
enabled: (level) => LEVELS[level] >= threshold
|
|
5434
|
+
};
|
|
5435
|
+
}
|
|
5436
|
+
var noopLogger = {
|
|
5437
|
+
debug: () => {},
|
|
5438
|
+
info: () => {},
|
|
5439
|
+
warn: () => {},
|
|
5440
|
+
error: () => {},
|
|
5441
|
+
enabled: () => false
|
|
5442
|
+
};
|
|
5443
|
+
function parseLogLevel(value) {
|
|
5444
|
+
const normalized = value?.trim().toLowerCase();
|
|
5445
|
+
if (normalized === undefined || normalized.length === 0)
|
|
5446
|
+
return null;
|
|
5447
|
+
return Object.hasOwn(LEVELS, normalized) ? normalized : null;
|
|
5448
|
+
}
|
|
5449
|
+
// packages/ir/src/request.ts
|
|
5450
|
+
function cacheControlOf(block) {
|
|
5451
|
+
return block.type === "thinking" ? undefined : block.cacheControl;
|
|
5452
|
+
}
|
|
5383
5453
|
// packages/ir/src/stream.ts
|
|
5454
|
+
function usageFromPromptTotal(promptTokens, outputTokens, cacheReadTokens, cacheWriteTokens = 0) {
|
|
5455
|
+
return {
|
|
5456
|
+
inputTokens: Math.max(0, promptTokens - cacheReadTokens - cacheWriteTokens),
|
|
5457
|
+
outputTokens,
|
|
5458
|
+
cacheReadTokens,
|
|
5459
|
+
cacheWriteTokens
|
|
5460
|
+
};
|
|
5461
|
+
}
|
|
5462
|
+
function promptTokens(usage) {
|
|
5463
|
+
return usage.inputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
|
|
5464
|
+
}
|
|
5384
5465
|
function collect(events) {
|
|
5385
5466
|
let id = "";
|
|
5386
5467
|
let model = "";
|
|
@@ -5473,6 +5554,39 @@ function validateRequest(req) {
|
|
|
5473
5554
|
}
|
|
5474
5555
|
return { ...req, messages: cleaned };
|
|
5475
5556
|
}
|
|
5557
|
+
// packages/control/src/config.ts
|
|
5558
|
+
var MIN_KEY_LENGTH = 16;
|
|
5559
|
+
var DECIMAL_INTEGER = /^\d+$/;
|
|
5560
|
+
function optionalText(value, fallback) {
|
|
5561
|
+
return value?.trim() || fallback;
|
|
5562
|
+
}
|
|
5563
|
+
function loadConfig(env) {
|
|
5564
|
+
const encryptionKey = env.OMNI_ENCRYPTION_KEY;
|
|
5565
|
+
if (typeof encryptionKey !== "string" || encryptionKey.length < MIN_KEY_LENGTH) {
|
|
5566
|
+
throw new Error(`OMNI_ENCRYPTION_KEY must be set to at least ${MIN_KEY_LENGTH} characters. ` + "Generate one with: openssl rand -base64 32");
|
|
5567
|
+
}
|
|
5568
|
+
const host = optionalText(env.OMNI_HOST, "127.0.0.1");
|
|
5569
|
+
const rawPort = env.OMNI_PORT ?? "9000";
|
|
5570
|
+
const port = Number(rawPort);
|
|
5571
|
+
if (!DECIMAL_INTEGER.test(rawPort) || !Number.isInteger(port) || port < 1 || port > 65535) {
|
|
5572
|
+
throw new Error(`OMNI_PORT must be an integer between 1 and 65535, got "${rawPort}"`);
|
|
5573
|
+
}
|
|
5574
|
+
const derivedBaseUrl = `http://${host}:${port}`;
|
|
5575
|
+
const baseUrl = optionalText(env.OMNI_BASE_URL, derivedBaseUrl).replace(/\/+$/, "") || derivedBaseUrl;
|
|
5576
|
+
const staticDir = env.OMNI_STATIC_DIR?.trim();
|
|
5577
|
+
const rawLogLevel = env.OMNI_LOG_LEVEL?.trim();
|
|
5578
|
+
const logLevel = parseLogLevel(rawLogLevel);
|
|
5579
|
+
return {
|
|
5580
|
+
logLevel: logLevel ?? "info",
|
|
5581
|
+
logLevelFallbackFrom: logLevel === null && rawLogLevel ? rawLogLevel : null,
|
|
5582
|
+
port,
|
|
5583
|
+
host,
|
|
5584
|
+
databasePath: optionalText(env.OMNI_DB_PATH, "./omnigateway.db"),
|
|
5585
|
+
encryptionKey,
|
|
5586
|
+
baseUrl,
|
|
5587
|
+
staticDir: staticDir === undefined || staticDir.length === 0 ? null : staticDir
|
|
5588
|
+
};
|
|
5589
|
+
}
|
|
5476
5590
|
// packages/providers/src/body.ts
|
|
5477
5591
|
var BODY_ORDER = {
|
|
5478
5592
|
anthropic: [
|
|
@@ -5559,8 +5673,13 @@ function applyAnthropicSystem(system) {
|
|
|
5559
5673
|
let rewritten = text;
|
|
5560
5674
|
for (const [from, to] of REWRITES)
|
|
5561
5675
|
rewritten = rewritten.replaceAll(from, to);
|
|
5562
|
-
if (rewritten.trim().length > 0)
|
|
5563
|
-
kept.push({
|
|
5676
|
+
if (rewritten.trim().length > 0) {
|
|
5677
|
+
kept.push({
|
|
5678
|
+
type: "text",
|
|
5679
|
+
text: rewritten,
|
|
5680
|
+
...block.cache_control === undefined ? {} : { cache_control: block.cache_control }
|
|
5681
|
+
});
|
|
5682
|
+
}
|
|
5564
5683
|
}
|
|
5565
5684
|
return [{ type: "text", text: billingBlock() }, { type: "text", text: AGENT_PREAMBLE }, ...kept];
|
|
5566
5685
|
}
|
|
@@ -5864,6 +5983,8 @@ async function* decodeAnthropic(messages) {
|
|
|
5864
5983
|
let inputTokens = 0;
|
|
5865
5984
|
let cacheReadTokens = 0;
|
|
5866
5985
|
let cacheWriteTokens = 0;
|
|
5986
|
+
let cacheWrite5mTokens;
|
|
5987
|
+
let cacheWrite1hTokens;
|
|
5867
5988
|
let outputTokens = 0;
|
|
5868
5989
|
let stopReason = "endTurn";
|
|
5869
5990
|
let terminal = false;
|
|
@@ -5877,6 +5998,8 @@ async function* decodeAnthropic(messages) {
|
|
|
5877
5998
|
inputTokens = m.usage?.input_tokens ?? 0;
|
|
5878
5999
|
cacheReadTokens = m.usage?.cache_read_input_tokens ?? 0;
|
|
5879
6000
|
cacheWriteTokens = m.usage?.cache_creation_input_tokens ?? 0;
|
|
6001
|
+
cacheWrite5mTokens = m.usage?.cache_creation?.ephemeral_5m_input_tokens;
|
|
6002
|
+
cacheWrite1hTokens = m.usage?.cache_creation?.ephemeral_1h_input_tokens;
|
|
5880
6003
|
yield { type: "start", id: String(m.id ?? ""), model: String(m.model ?? "") };
|
|
5881
6004
|
break;
|
|
5882
6005
|
}
|
|
@@ -5936,7 +6059,14 @@ async function* decodeAnthropic(messages) {
|
|
|
5936
6059
|
yield {
|
|
5937
6060
|
type: "end",
|
|
5938
6061
|
stopReason,
|
|
5939
|
-
usage: {
|
|
6062
|
+
usage: {
|
|
6063
|
+
inputTokens,
|
|
6064
|
+
outputTokens,
|
|
6065
|
+
cacheReadTokens,
|
|
6066
|
+
cacheWriteTokens,
|
|
6067
|
+
...cacheWrite5mTokens === undefined ? {} : { cacheWrite5mTokens },
|
|
6068
|
+
...cacheWrite1hTokens === undefined ? {} : { cacheWrite1hTokens }
|
|
6069
|
+
}
|
|
5940
6070
|
};
|
|
5941
6071
|
break;
|
|
5942
6072
|
case "error": {
|
|
@@ -5966,25 +6096,33 @@ async function* decodeAnthropic(messages) {
|
|
|
5966
6096
|
|
|
5967
6097
|
// packages/providers/src/anthropic/wire.ts
|
|
5968
6098
|
var OAUTH_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude.";
|
|
6099
|
+
function wireCacheControl(c) {
|
|
6100
|
+
if (c === undefined)
|
|
6101
|
+
return {};
|
|
6102
|
+
return { cache_control: { type: c.type, ...c.ttl === undefined ? {} : { ttl: c.ttl } } };
|
|
6103
|
+
}
|
|
5969
6104
|
function encodeBlock(b) {
|
|
6105
|
+
const cache = wireCacheControl(cacheControlOf(b));
|
|
5970
6106
|
switch (b.type) {
|
|
5971
6107
|
case "text":
|
|
5972
|
-
return { type: "text", text: b.text };
|
|
6108
|
+
return { type: "text", text: b.text, ...cache };
|
|
5973
6109
|
case "image":
|
|
5974
6110
|
return {
|
|
5975
6111
|
type: "image",
|
|
5976
|
-
source: { type: "base64", media_type: b.mediaType, data: b.data }
|
|
6112
|
+
source: { type: "base64", media_type: b.mediaType, data: b.data },
|
|
6113
|
+
...cache
|
|
5977
6114
|
};
|
|
5978
6115
|
case "thinking":
|
|
5979
6116
|
return { type: "thinking", thinking: b.text, signature: b.signature };
|
|
5980
6117
|
case "toolUse":
|
|
5981
|
-
return { type: "tool_use", id: b.id, name: b.name, input: b.input };
|
|
6118
|
+
return { type: "tool_use", id: b.id, name: b.name, input: b.input, ...cache };
|
|
5982
6119
|
case "toolResult":
|
|
5983
6120
|
return {
|
|
5984
6121
|
type: "tool_result",
|
|
5985
6122
|
tool_use_id: b.toolUseId,
|
|
5986
6123
|
content: b.content,
|
|
5987
|
-
is_error: b.isError
|
|
6124
|
+
is_error: b.isError,
|
|
6125
|
+
...cache
|
|
5988
6126
|
};
|
|
5989
6127
|
}
|
|
5990
6128
|
}
|
|
@@ -6006,14 +6144,25 @@ function encodeToolChoice(c) {
|
|
|
6006
6144
|
}
|
|
6007
6145
|
function toWire(req, model, opts) {
|
|
6008
6146
|
const degradations = [];
|
|
6009
|
-
|
|
6147
|
+
const note = (d) => {
|
|
6148
|
+
if (!degradations.includes(d))
|
|
6149
|
+
degradations.push(d);
|
|
6150
|
+
};
|
|
6151
|
+
let system = req.system?.flatMap((b) => b.type === "text" ? [{ type: "text", text: b.text, ...wireCacheControl(b.cacheControl) }] : []);
|
|
6010
6152
|
if (opts.oauth && system?.[0]?.text !== OAUTH_IDENTITY) {
|
|
6011
6153
|
system = [{ type: "text", text: OAUTH_IDENTITY }, ...system ?? []];
|
|
6012
|
-
|
|
6154
|
+
note("anthropic:oauth-system-prefix");
|
|
6013
6155
|
}
|
|
6014
6156
|
const body = {
|
|
6015
6157
|
model,
|
|
6016
|
-
messages: req.messages.map((m) =>
|
|
6158
|
+
messages: req.messages.map((m) => {
|
|
6159
|
+
if (m.role !== "system")
|
|
6160
|
+
return { role: m.role, content: m.content.map(encodeBlock) };
|
|
6161
|
+
if (m.content.some((b) => cacheControlOf(b) !== undefined)) {
|
|
6162
|
+
note("anthropic:system-turn-cache-control-dropped");
|
|
6163
|
+
}
|
|
6164
|
+
return { role: m.role, content: encodeSystemTurn(m.content) };
|
|
6165
|
+
}),
|
|
6017
6166
|
max_tokens: req.maxTokens ?? 4096,
|
|
6018
6167
|
stream: req.stream
|
|
6019
6168
|
};
|
|
@@ -6027,7 +6176,8 @@ function toWire(req, model, opts) {
|
|
|
6027
6176
|
body.tools = req.tools.map((t) => ({
|
|
6028
6177
|
name: t.name,
|
|
6029
6178
|
...t.description === undefined ? {} : { description: t.description },
|
|
6030
|
-
input_schema: t.inputSchema
|
|
6179
|
+
input_schema: t.inputSchema,
|
|
6180
|
+
...wireCacheControl(t.cacheControl)
|
|
6031
6181
|
}));
|
|
6032
6182
|
}
|
|
6033
6183
|
if (req.toolChoice !== undefined)
|
|
@@ -6067,12 +6217,13 @@ var anthropicAdapter = {
|
|
|
6067
6217
|
const { body, degradations } = toWire(req.request, req.model, { oauth });
|
|
6068
6218
|
const withSystem = {
|
|
6069
6219
|
...body,
|
|
6070
|
-
system: applyAnthropicSystem(body.system ?? [])
|
|
6220
|
+
system: applyAnthropicSystem(body.system ?? []),
|
|
6221
|
+
stream: true
|
|
6071
6222
|
};
|
|
6072
6223
|
const protocol = [
|
|
6073
6224
|
["Content-Type", "application/json"],
|
|
6074
6225
|
["anthropic-version", API_VERSION],
|
|
6075
|
-
["Accept",
|
|
6226
|
+
["Accept", "text/event-stream"]
|
|
6076
6227
|
];
|
|
6077
6228
|
const betas = new Set(req.request.betas ?? []);
|
|
6078
6229
|
if (oauth) {
|
|
@@ -6091,6 +6242,7 @@ var anthropicAdapter = {
|
|
|
6091
6242
|
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
6092
6243
|
const bodyString = signAnthropicBody(JSON.stringify(orderFields(withSystem, BODY_ORDER.anthropic)));
|
|
6093
6244
|
const res = await req.http({
|
|
6245
|
+
provider: "anthropic",
|
|
6094
6246
|
url: BASE_URL,
|
|
6095
6247
|
method: "POST",
|
|
6096
6248
|
headers,
|
|
@@ -6111,22 +6263,26 @@ var ANTHROPIC_MODELS = {
|
|
|
6111
6263
|
{
|
|
6112
6264
|
id: "claude-fable-5",
|
|
6113
6265
|
label: "Claude Fable 5",
|
|
6114
|
-
pricing: { input: 10, output: 50, cacheRead: 1 }
|
|
6266
|
+
pricing: { input: 10, output: 50, cacheRead: 1, cacheWrite5m: 12.5, cacheWrite1h: 20 },
|
|
6267
|
+
limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
|
|
6115
6268
|
},
|
|
6116
6269
|
{
|
|
6117
6270
|
id: "claude-opus-5",
|
|
6118
6271
|
label: "Claude Opus 5",
|
|
6119
|
-
pricing: { input: 5, output: 25, cacheRead: 0.5 }
|
|
6272
|
+
pricing: { input: 5, output: 25, cacheRead: 0.5, cacheWrite5m: 6.25, cacheWrite1h: 10 },
|
|
6273
|
+
limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
|
|
6120
6274
|
},
|
|
6121
6275
|
{
|
|
6122
6276
|
id: "claude-sonnet-5",
|
|
6123
6277
|
label: "Claude Sonnet 5",
|
|
6124
|
-
pricing: { input: 3, output: 15, cacheRead: 0.3 }
|
|
6278
|
+
pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 3.75, cacheWrite1h: 6 },
|
|
6279
|
+
limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
|
|
6125
6280
|
},
|
|
6126
6281
|
{
|
|
6127
6282
|
id: "claude-haiku-4-5",
|
|
6128
6283
|
label: "Claude Haiku 4.5",
|
|
6129
|
-
pricing: { input: 1, output: 5, cacheRead: 0.1 }
|
|
6284
|
+
pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
|
|
6285
|
+
limits: { contextWindow: 200000, maxOutputTokens: 64000 }
|
|
6130
6286
|
}
|
|
6131
6287
|
]
|
|
6132
6288
|
};
|
|
@@ -6138,22 +6294,26 @@ var KIMI_MODELS = {
|
|
|
6138
6294
|
{
|
|
6139
6295
|
id: "k3-256k",
|
|
6140
6296
|
label: "Kimi K3 \u2014 256K",
|
|
6141
|
-
pricing: { input: 3, output: 15, cacheRead: 0.3 }
|
|
6297
|
+
pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6298
|
+
limits: { contextWindow: 262144, maxOutputTokens: 131072 }
|
|
6142
6299
|
},
|
|
6143
6300
|
{
|
|
6144
6301
|
id: "k3",
|
|
6145
6302
|
label: "Kimi K3 \u2014 up to 1M",
|
|
6146
|
-
pricing: { input: 3, output: 15, cacheRead: 0.3 }
|
|
6303
|
+
pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6304
|
+
limits: { contextWindow: 1048576, maxOutputTokens: 131072 }
|
|
6147
6305
|
},
|
|
6148
6306
|
{
|
|
6149
6307
|
id: "kimi-for-coding",
|
|
6150
6308
|
label: "Kimi K2.7 Code",
|
|
6151
|
-
pricing: { input: 0.95, output: 4, cacheRead: 0.19 }
|
|
6309
|
+
pricing: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6310
|
+
limits: { contextWindow: 262144, maxOutputTokens: 131072 }
|
|
6152
6311
|
},
|
|
6153
6312
|
{
|
|
6154
6313
|
id: "kimi-for-coding-highspeed",
|
|
6155
6314
|
label: "Kimi K2.7 Code \u2014 High Speed",
|
|
6156
|
-
pricing: { input: 0.95, output: 8, cacheRead: 0.19 }
|
|
6315
|
+
pricing: { input: 0.95, output: 8, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6316
|
+
limits: { contextWindow: 262144, maxOutputTokens: 131072 }
|
|
6157
6317
|
}
|
|
6158
6318
|
]
|
|
6159
6319
|
};
|
|
@@ -6165,22 +6325,30 @@ var OPENAI_MODELS = {
|
|
|
6165
6325
|
{
|
|
6166
6326
|
id: "gpt-5.6",
|
|
6167
6327
|
label: "GPT-5.6 \u2014 routes to Sol",
|
|
6168
|
-
pricing: { input: 5, output: 30, cacheRead: 0.5 }
|
|
6328
|
+
pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6329
|
+
limits: { contextWindow: 922000, maxOutputTokens: 128000 },
|
|
6330
|
+
oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
|
|
6169
6331
|
},
|
|
6170
6332
|
{
|
|
6171
6333
|
id: "gpt-5.6-sol",
|
|
6172
6334
|
label: "GPT-5.6 Sol \u2014 deepest reasoning",
|
|
6173
|
-
pricing: { input: 5, output: 30, cacheRead: 0.5 }
|
|
6335
|
+
pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6336
|
+
limits: { contextWindow: 922000, maxOutputTokens: 128000 },
|
|
6337
|
+
oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
|
|
6174
6338
|
},
|
|
6175
6339
|
{
|
|
6176
6340
|
id: "gpt-5.6-terra",
|
|
6177
6341
|
label: "GPT-5.6 Terra \u2014 balanced",
|
|
6178
|
-
pricing: { input: 2, output: 12, cacheRead: 0.2 }
|
|
6342
|
+
pricing: { input: 2, output: 12, cacheRead: 0.2, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6343
|
+
limits: { contextWindow: 922000, maxOutputTokens: 128000 },
|
|
6344
|
+
oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
|
|
6179
6345
|
},
|
|
6180
6346
|
{
|
|
6181
6347
|
id: "gpt-5.6-luna",
|
|
6182
6348
|
label: "GPT-5.6 Luna \u2014 fastest",
|
|
6183
|
-
pricing: { input: 0.2, output: 1.2, cacheRead: 0.02 }
|
|
6349
|
+
pricing: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite5m: 0, cacheWrite1h: 0 },
|
|
6350
|
+
limits: { contextWindow: 922000, maxOutputTokens: 128000 },
|
|
6351
|
+
oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
|
|
6184
6352
|
}
|
|
6185
6353
|
]
|
|
6186
6354
|
};
|
|
@@ -6192,15 +6360,38 @@ var PROVIDER_MODEL_CATALOG = {
|
|
|
6192
6360
|
kimi: KIMI_MODELS
|
|
6193
6361
|
};
|
|
6194
6362
|
function catalogPricing(provider, model) {
|
|
6195
|
-
return PROVIDER_MODEL_CATALOG[provider]
|
|
6363
|
+
return PROVIDER_MODEL_CATALOG[provider]?.models.find((entry) => entry.id === model)?.pricing ?? null;
|
|
6364
|
+
}
|
|
6365
|
+
function catalogLimits(provider, model, auth = "apiKey") {
|
|
6366
|
+
const entry = PROVIDER_MODEL_CATALOG[provider]?.models.find((choice) => choice.id === model);
|
|
6367
|
+
if (entry === undefined)
|
|
6368
|
+
return null;
|
|
6369
|
+
return (auth === "oauth" ? entry.oauthLimits : undefined) ?? entry.limits;
|
|
6196
6370
|
}
|
|
6197
6371
|
// packages/providers/src/http-client.ts
|
|
6198
6372
|
import { request as httpRequest } from "http";
|
|
6199
6373
|
import { request as httpsRequest } from "https";
|
|
6200
6374
|
import { Readable } from "stream";
|
|
6201
|
-
function nodeHttpClient() {
|
|
6375
|
+
function nodeHttpClient(options = {}) {
|
|
6376
|
+
const logger2 = options.logger ?? noopLogger;
|
|
6377
|
+
const now = options.now ?? (() => Date.now());
|
|
6202
6378
|
return (req) => new Promise((resolve, reject) => {
|
|
6203
6379
|
const url = new URL(req.url);
|
|
6380
|
+
const startedAt = now();
|
|
6381
|
+
let traced = false;
|
|
6382
|
+
const trace = (status, failed = false) => {
|
|
6383
|
+
if (traced || !logger2.enabled("debug"))
|
|
6384
|
+
return;
|
|
6385
|
+
traced = true;
|
|
6386
|
+
logger2.debug("upstream http", {
|
|
6387
|
+
provider: req.provider,
|
|
6388
|
+
status,
|
|
6389
|
+
host: url.host,
|
|
6390
|
+
path: url.pathname,
|
|
6391
|
+
durationMs: now() - startedAt,
|
|
6392
|
+
reason: failed ? "transport error" : undefined
|
|
6393
|
+
});
|
|
6394
|
+
};
|
|
6204
6395
|
const send = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
6205
6396
|
const bodyBytes = Buffer.from(req.body, "utf8");
|
|
6206
6397
|
const headers = {};
|
|
@@ -6228,6 +6419,7 @@ function nodeHttpClient() {
|
|
|
6228
6419
|
else if (typeof v === "string")
|
|
6229
6420
|
responseHeaders.set(k, v);
|
|
6230
6421
|
}
|
|
6422
|
+
trace(incoming.statusCode);
|
|
6231
6423
|
resolve({
|
|
6232
6424
|
status: incoming.statusCode ?? 0,
|
|
6233
6425
|
headers: responseHeaders,
|
|
@@ -6243,17 +6435,17 @@ function nodeHttpClient() {
|
|
|
6243
6435
|
});
|
|
6244
6436
|
});
|
|
6245
6437
|
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
6438
|
outgoing.on("error", (err) => {
|
|
6253
6439
|
req.signal.removeEventListener("abort", onAbort);
|
|
6440
|
+
trace(undefined, true);
|
|
6254
6441
|
reject(err);
|
|
6255
6442
|
});
|
|
6256
6443
|
outgoing.on("close", () => req.signal.removeEventListener("abort", onAbort));
|
|
6444
|
+
if (req.signal.aborted) {
|
|
6445
|
+
outgoing.destroy(new Error("aborted"));
|
|
6446
|
+
return;
|
|
6447
|
+
}
|
|
6448
|
+
req.signal.addEventListener("abort", onAbort, { once: true });
|
|
6257
6449
|
if (bodyBytes.byteLength > 0)
|
|
6258
6450
|
outgoing.write(bodyBytes);
|
|
6259
6451
|
outgoing.end();
|
|
@@ -6322,12 +6514,8 @@ async function* decodeChat(messages) {
|
|
|
6322
6514
|
yield { type: "start", id: String(d.id ?? ""), model: String(d.model ?? "") };
|
|
6323
6515
|
}
|
|
6324
6516
|
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
|
-
};
|
|
6517
|
+
const details = d.usage.prompt_tokens_details;
|
|
6518
|
+
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
6519
|
}
|
|
6332
6520
|
const choice = d.choices?.[0];
|
|
6333
6521
|
if (!choice)
|
|
@@ -6453,7 +6641,12 @@ function toChatWire(req, model) {
|
|
|
6453
6641
|
`) });
|
|
6454
6642
|
}
|
|
6455
6643
|
}
|
|
6456
|
-
const body = {
|
|
6644
|
+
const body = {
|
|
6645
|
+
model,
|
|
6646
|
+
messages,
|
|
6647
|
+
stream: req.stream,
|
|
6648
|
+
stream_options: { include_usage: true }
|
|
6649
|
+
};
|
|
6457
6650
|
if (req.maxTokens !== undefined)
|
|
6458
6651
|
body.max_tokens = req.maxTokens;
|
|
6459
6652
|
if (req.temperature !== undefined)
|
|
@@ -6494,6 +6687,7 @@ var kimiAdapter = {
|
|
|
6494
6687
|
const profile = PROFILES.kimi;
|
|
6495
6688
|
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
6496
6689
|
const res = await req.http({
|
|
6690
|
+
provider: "kimi",
|
|
6497
6691
|
url: BASE_URL2,
|
|
6498
6692
|
method: "POST",
|
|
6499
6693
|
headers,
|
|
@@ -6624,12 +6818,7 @@ async function* decodeResponses(messages) {
|
|
|
6624
6818
|
yield {
|
|
6625
6819
|
type: "end",
|
|
6626
6820
|
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
|
-
}
|
|
6821
|
+
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
6822
|
};
|
|
6634
6823
|
break;
|
|
6635
6824
|
}
|
|
@@ -6802,6 +6991,7 @@ var openaiAdapter = {
|
|
|
6802
6991
|
const profile = PROFILES.openai;
|
|
6803
6992
|
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
6804
6993
|
const res = await req.http({
|
|
6994
|
+
provider: "openai",
|
|
6805
6995
|
url: oauth ? OAUTH_URL : API_URL,
|
|
6806
6996
|
method: "POST",
|
|
6807
6997
|
headers,
|
|
@@ -6822,13 +7012,14 @@ var ADAPTERS = {
|
|
|
6822
7012
|
kimi: kimiAdapter
|
|
6823
7013
|
};
|
|
6824
7014
|
// packages/control/src/oauth/types.ts
|
|
6825
|
-
async function postJson(deps, url, profile2, opts) {
|
|
7015
|
+
async function postJson(deps, provider, url, profile2, opts) {
|
|
6826
7016
|
const headers = orderHeaders(mergeHeaders(profile2.headers, [
|
|
6827
7017
|
["Content-Type", opts.contentType],
|
|
6828
7018
|
["Accept", "application/json"],
|
|
6829
7019
|
...opts.extraHeaders ?? []
|
|
6830
7020
|
]), profile2.order);
|
|
6831
7021
|
const res = await deps.http({
|
|
7022
|
+
provider,
|
|
6832
7023
|
url,
|
|
6833
7024
|
method: "POST",
|
|
6834
7025
|
headers,
|
|
@@ -6842,13 +7033,14 @@ async function postJson(deps, url, profile2, opts) {
|
|
|
6842
7033
|
} catch {}
|
|
6843
7034
|
return { status: res.status, parsed };
|
|
6844
7035
|
}
|
|
6845
|
-
async function getJson(deps, url, profile2, opts) {
|
|
7036
|
+
async function getJson(deps, provider, url, profile2, opts) {
|
|
6846
7037
|
const headers = orderHeaders(mergeHeaders(profile2.headers, [
|
|
6847
7038
|
["Authorization", `Bearer ${opts.accessToken}`],
|
|
6848
7039
|
["Accept", "application/json"],
|
|
6849
7040
|
...opts.extraHeaders ?? []
|
|
6850
7041
|
]), profile2.order);
|
|
6851
7042
|
const res = await deps.http({
|
|
7043
|
+
provider,
|
|
6852
7044
|
url,
|
|
6853
7045
|
method: "GET",
|
|
6854
7046
|
headers,
|
|
@@ -7042,7 +7234,7 @@ function deviceCodeFrom(value) {
|
|
|
7042
7234
|
};
|
|
7043
7235
|
}
|
|
7044
7236
|
async function post(url, body2, device, deps) {
|
|
7045
|
-
const { status, parsed } = await postJson(deps, url, PROFILES.kimi, {
|
|
7237
|
+
const { status, parsed } = await postJson(deps, "kimi", url, PROFILES.kimi, {
|
|
7046
7238
|
contentType: "application/x-www-form-urlencoded",
|
|
7047
7239
|
body: new URLSearchParams({ ...body2, client_id: CLIENT_ID }).toString(),
|
|
7048
7240
|
extraHeaders: kimiDeviceHeaders(device)
|
|
@@ -7145,7 +7337,7 @@ var kimiOAuth = {
|
|
|
7145
7337
|
if (secrets.accessToken === null)
|
|
7146
7338
|
return null;
|
|
7147
7339
|
const device = deviceFrom(providerData);
|
|
7148
|
-
const { status, parsed } = await getJson(deps, USAGE_URL, PROFILES.kimi, {
|
|
7340
|
+
const { status, parsed } = await getJson(deps, "kimi", USAGE_URL, PROFILES.kimi, {
|
|
7149
7341
|
accessToken: secrets.accessToken,
|
|
7150
7342
|
extraHeaders: kimiDeviceHeaders(device)
|
|
7151
7343
|
});
|
|
@@ -7214,6 +7406,7 @@ function deviceIdFrom(start) {
|
|
|
7214
7406
|
return deviceId;
|
|
7215
7407
|
}
|
|
7216
7408
|
function createConnectFlows(deps) {
|
|
7409
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
7217
7410
|
const flows = createPendingFlows({ now: deps.now, ttlMs: FLOW_TTL_MS });
|
|
7218
7411
|
const pollsInFlight = new Map;
|
|
7219
7412
|
const callbackUri = (provider) => provider === "openai" ? "http://localhost:1455/auth/callback" : "";
|
|
@@ -7258,6 +7451,10 @@ function createConnectFlows(deps) {
|
|
|
7258
7451
|
disabledAt: null,
|
|
7259
7452
|
...result.secrets
|
|
7260
7453
|
});
|
|
7454
|
+
logger2.info("oauth connect completed", {
|
|
7455
|
+
provider: flow.provider,
|
|
7456
|
+
credentialId: id
|
|
7457
|
+
});
|
|
7261
7458
|
return { id };
|
|
7262
7459
|
}
|
|
7263
7460
|
async function pollOutcome(flowId, poll) {
|
|
@@ -21633,28 +21830,18 @@ var modelSchema = exports_external.object({
|
|
|
21633
21830
|
costPerMTok: exports_external.object({
|
|
21634
21831
|
input: exports_external.number().min(0),
|
|
21635
21832
|
output: exports_external.number().min(0),
|
|
21636
|
-
cacheRead: exports_external.number().min(0)
|
|
21833
|
+
cacheRead: exports_external.number().min(0).optional(),
|
|
21834
|
+
cacheWrite5m: exports_external.number().min(0).optional(),
|
|
21835
|
+
cacheWrite1h: exports_external.number().min(0).optional()
|
|
21637
21836
|
}),
|
|
21837
|
+
contextWindow: exports_external.number().int().positive().optional(),
|
|
21838
|
+
maxOutputTokens: exports_external.number().int().positive().optional(),
|
|
21638
21839
|
capabilities: exports_external.object({
|
|
21639
21840
|
tools: exports_external.boolean(),
|
|
21640
21841
|
images: exports_external.boolean(),
|
|
21641
21842
|
reasoning: exports_external.boolean()
|
|
21642
21843
|
})
|
|
21643
|
-
}).
|
|
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)
|
|
21651
|
-
}),
|
|
21652
|
-
capabilities: exports_external.object({
|
|
21653
|
-
tools: exports_external.boolean(),
|
|
21654
|
-
images: exports_external.boolean(),
|
|
21655
|
-
reasoning: exports_external.boolean()
|
|
21656
|
-
})
|
|
21657
|
-
}))).min(1, "a virtual model needs at least one target")
|
|
21844
|
+
})).min(1, "a virtual model needs at least one target")
|
|
21658
21845
|
});
|
|
21659
21846
|
var keyCreateSchema = exports_external.object({
|
|
21660
21847
|
label: exports_external.string().min(1).default("api key"),
|
|
@@ -21748,6 +21935,12 @@ async function patchCredential(deps, id, input) {
|
|
|
21748
21935
|
...patch.tier === undefined ? {} : { tier: patch.tier },
|
|
21749
21936
|
...patch.weight === undefined ? {} : { weight: patch.weight }
|
|
21750
21937
|
});
|
|
21938
|
+
if (patch.enabled !== undefined && patch.enabled !== existing.enabled) {
|
|
21939
|
+
(deps.logger ?? noopLogger).info(patch.enabled ? "credential enabled" : "credential disabled", {
|
|
21940
|
+
credentialId: id,
|
|
21941
|
+
provider: existing.provider
|
|
21942
|
+
});
|
|
21943
|
+
}
|
|
21751
21944
|
}
|
|
21752
21945
|
async function removeCredential(store, id) {
|
|
21753
21946
|
await store.credentials.remove(id);
|
|
@@ -22009,12 +22202,20 @@ var PREFIX_PROVIDER = [
|
|
|
22009
22202
|
];
|
|
22010
22203
|
function synthesize(provider, model) {
|
|
22011
22204
|
const listed = catalogPricing(provider, model);
|
|
22205
|
+
const limits = catalogLimits(provider, model);
|
|
22012
22206
|
const target = {
|
|
22013
22207
|
provider,
|
|
22014
22208
|
model,
|
|
22015
22209
|
tier: 1,
|
|
22016
22210
|
weight: 1,
|
|
22017
|
-
costPerMTok: listed === null ? { input: 0, output: 0 } : {
|
|
22211
|
+
costPerMTok: listed === null ? { input: 0, output: 0 } : {
|
|
22212
|
+
input: listed.input,
|
|
22213
|
+
output: listed.output,
|
|
22214
|
+
cacheRead: listed.cacheRead,
|
|
22215
|
+
cacheWrite5m: listed.cacheWrite5m,
|
|
22216
|
+
cacheWrite1h: listed.cacheWrite1h
|
|
22217
|
+
},
|
|
22218
|
+
...limits === null ? {} : { contextWindow: limits.contextWindow, maxOutputTokens: limits.maxOutputTokens },
|
|
22018
22219
|
capabilities: PROVIDER_CAPABILITIES[provider]
|
|
22019
22220
|
};
|
|
22020
22221
|
return { id: `${provider}/${model}`, targets: [target], strategy: "score", isAlias: true };
|
|
@@ -23017,7 +23218,9 @@ function createUsageRepo(db) {
|
|
|
23017
23218
|
|
|
23018
23219
|
// packages/store/src/sqlite/store.ts
|
|
23019
23220
|
async function createStore(opts) {
|
|
23221
|
+
const logger2 = opts.logger ?? noopLogger;
|
|
23020
23222
|
const db = openDb(opts.path);
|
|
23223
|
+
logger2.debug("store opened", { path: opts.path });
|
|
23021
23224
|
const listeners = new Set;
|
|
23022
23225
|
const emit = (change) => {
|
|
23023
23226
|
for (const listener of listeners) {
|
|
@@ -23104,7 +23307,7 @@ var REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
|
|
|
23104
23307
|
var SCOPES = "org:create_api_key user:profile user:inference";
|
|
23105
23308
|
var USAGE_URL2 = "https://api.anthropic.com/api/oauth/usage";
|
|
23106
23309
|
async function postToken(body2, deps) {
|
|
23107
|
-
const { status, parsed } = await postJson(deps, TOKEN_URL2, PROFILES.anthropic, {
|
|
23310
|
+
const { status, parsed } = await postJson(deps, "anthropic", TOKEN_URL2, PROFILES.anthropic, {
|
|
23108
23311
|
contentType: "application/json",
|
|
23109
23312
|
body: JSON.stringify({ ...body2, client_id: CLIENT_ID2 })
|
|
23110
23313
|
});
|
|
@@ -23182,7 +23385,7 @@ var anthropicOAuth = {
|
|
|
23182
23385
|
async usage(secrets, deps) {
|
|
23183
23386
|
if (secrets.accessToken === null)
|
|
23184
23387
|
return null;
|
|
23185
|
-
const { status, parsed } = await getJson(deps, USAGE_URL2, PROFILES.anthropic, {
|
|
23388
|
+
const { status, parsed } = await getJson(deps, "anthropic", USAGE_URL2, PROFILES.anthropic, {
|
|
23186
23389
|
accessToken: secrets.accessToken,
|
|
23187
23390
|
extraHeaders: [
|
|
23188
23391
|
["anthropic-beta", "oauth-2025-04-20"],
|
|
@@ -23240,7 +23443,7 @@ function parseTokenResponse(value) {
|
|
|
23240
23443
|
};
|
|
23241
23444
|
}
|
|
23242
23445
|
async function postToken2(body2, deps) {
|
|
23243
|
-
const { status, parsed } = await postJson(deps, TOKEN_URL3, PROFILES.openai, {
|
|
23446
|
+
const { status, parsed } = await postJson(deps, "openai", TOKEN_URL3, PROFILES.openai, {
|
|
23244
23447
|
contentType: "application/x-www-form-urlencoded",
|
|
23245
23448
|
body: new URLSearchParams({ ...body2, client_id: CLIENT_ID3 }).toString()
|
|
23246
23449
|
});
|
|
@@ -23339,7 +23542,7 @@ var openaiOAuth = {
|
|
|
23339
23542
|
if (secrets.accessToken === null)
|
|
23340
23543
|
return null;
|
|
23341
23544
|
const accountId = accountIdFromProviderData(providerData);
|
|
23342
|
-
const { status, parsed } = await getJson(deps, USAGE_URL3, PROFILES.openai, {
|
|
23545
|
+
const { status, parsed } = await getJson(deps, "openai", USAGE_URL3, PROFILES.openai, {
|
|
23343
23546
|
accessToken: secrets.accessToken,
|
|
23344
23547
|
...accountId === null ? {} : { extraHeaders: [["chatgpt-account-id", accountId]] }
|
|
23345
23548
|
});
|
|
@@ -23360,6 +23563,7 @@ var DISPATCH_REFRESH_LEAD_MS = 120000;
|
|
|
23360
23563
|
var SCHEDULER_REFRESH_LEAD_MS = 300000;
|
|
23361
23564
|
// packages/control/src/oauth/refresh.ts
|
|
23362
23565
|
function createRefresher(deps) {
|
|
23566
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
23363
23567
|
const inFlight = new Map;
|
|
23364
23568
|
async function run(credential) {
|
|
23365
23569
|
const secrets = await credential.openForRefresh();
|
|
@@ -23367,11 +23571,20 @@ function createRefresher(deps) {
|
|
|
23367
23571
|
throw new GatewayError("AUTH", `credential ${credential.id} has no refresh token`);
|
|
23368
23572
|
}
|
|
23369
23573
|
const provider = deps.providers[credential.provider];
|
|
23574
|
+
logger2.debug("refreshing credential", {
|
|
23575
|
+
provider: credential.provider,
|
|
23576
|
+
credentialId: credential.id
|
|
23577
|
+
});
|
|
23370
23578
|
let result;
|
|
23371
23579
|
try {
|
|
23372
23580
|
result = await provider.refresh(secrets.refreshToken, { http: deps.http, now: deps.now }, credential.providerData);
|
|
23373
23581
|
} catch (error51) {
|
|
23374
23582
|
if (error51 instanceof GatewayError && error51.code === "AUTH") {
|
|
23583
|
+
logger2.warn("credential disabled: refresh token rejected", {
|
|
23584
|
+
provider: credential.provider,
|
|
23585
|
+
credentialId: credential.id,
|
|
23586
|
+
code: "AUTH"
|
|
23587
|
+
});
|
|
23375
23588
|
await deps.store.credentials.update(credential.id, {
|
|
23376
23589
|
enabled: false,
|
|
23377
23590
|
disabledReason: "tokenRejected",
|
|
@@ -23385,6 +23598,10 @@ function createRefresher(deps) {
|
|
|
23385
23598
|
accountEmail: result.accountEmail ?? credential.accountEmail,
|
|
23386
23599
|
providerData: { ...credential.providerData, ...result.providerData }
|
|
23387
23600
|
});
|
|
23601
|
+
logger2.debug("credential refreshed", {
|
|
23602
|
+
provider: credential.provider,
|
|
23603
|
+
credentialId: credential.id
|
|
23604
|
+
});
|
|
23388
23605
|
return result.secrets;
|
|
23389
23606
|
}
|
|
23390
23607
|
return function refresh(credential) {
|
|
@@ -23424,9 +23641,15 @@ async function probe(deps, credential) {
|
|
|
23424
23641
|
observedAt
|
|
23425
23642
|
}));
|
|
23426
23643
|
await deps.store.credentials.saveQuota(rows);
|
|
23644
|
+
(deps.logger ?? noopLogger).debug("quota snapshot written", {
|
|
23645
|
+
provider: credential.provider,
|
|
23646
|
+
credentialId: credential.id,
|
|
23647
|
+
count: rows.length
|
|
23648
|
+
});
|
|
23427
23649
|
return rows;
|
|
23428
23650
|
}
|
|
23429
23651
|
async function poll(deps) {
|
|
23652
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
23430
23653
|
const now = deps.now();
|
|
23431
23654
|
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
23655
|
let written = 0;
|
|
@@ -23440,12 +23663,15 @@ async function poll(deps) {
|
|
|
23440
23663
|
if (await probe(deps, credential) !== null)
|
|
23441
23664
|
written += 1;
|
|
23442
23665
|
} catch (error51) {
|
|
23443
|
-
|
|
23666
|
+
const rateLimited = error51 instanceof GatewayError && error51.code === "RATE_LIMIT";
|
|
23667
|
+
if (rateLimited) {
|
|
23444
23668
|
cooldowns.set(credential.id, deps.now() + RATE_LIMIT_COOLDOWN_MS);
|
|
23445
23669
|
}
|
|
23446
|
-
|
|
23447
|
-
credentialId: credential.id,
|
|
23670
|
+
logger2.warn(rateLimited ? "quota probe rate limited" : "quota probe failed", {
|
|
23448
23671
|
provider: credential.provider,
|
|
23672
|
+
credentialId: credential.id,
|
|
23673
|
+
code: error51 instanceof GatewayError ? error51.code : "INTERNAL",
|
|
23674
|
+
...rateLimited ? { retryAfterMs: RATE_LIMIT_COOLDOWN_MS } : {},
|
|
23449
23675
|
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
23450
23676
|
});
|
|
23451
23677
|
}
|
|
@@ -37903,7 +38129,7 @@ class ApiKeyRateLimiter {
|
|
|
37903
38129
|
}
|
|
37904
38130
|
|
|
37905
38131
|
// apps/gateway/src/dispatch/snapshotCache.ts
|
|
37906
|
-
function createRoutingSnapshotCache(store) {
|
|
38132
|
+
function createRoutingSnapshotCache(store, logger2 = noopLogger) {
|
|
37907
38133
|
let snapshot = null;
|
|
37908
38134
|
let stale = true;
|
|
37909
38135
|
let version2 = store.routing.version();
|
|
@@ -37942,6 +38168,7 @@ function createRoutingSnapshotCache(store) {
|
|
|
37942
38168
|
return inFlight;
|
|
37943
38169
|
const buildGeneration = generation;
|
|
37944
38170
|
const buildVersion = currentVersion;
|
|
38171
|
+
logger2.debug("routing snapshot cache miss");
|
|
37945
38172
|
inFlight = buildSnapshot(store, now).then(async (next) => {
|
|
37946
38173
|
const completedVersion = store.routing.version();
|
|
37947
38174
|
if (generation !== buildGeneration || completedVersion !== buildVersion) {
|
|
@@ -37961,6 +38188,28 @@ function createRoutingSnapshotCache(store) {
|
|
|
37961
38188
|
}
|
|
37962
38189
|
|
|
37963
38190
|
// apps/gateway/src/ingress/schemas.ts
|
|
38191
|
+
var cacheControlSchema = exports_external.object({
|
|
38192
|
+
type: exports_external.literal("ephemeral"),
|
|
38193
|
+
ttl: exports_external.enum(["5m", "1h"]).optional()
|
|
38194
|
+
});
|
|
38195
|
+
function looseCacheControl(value) {
|
|
38196
|
+
const parsed = cacheControlSchema.safeParse(value);
|
|
38197
|
+
return parsed.success ? irCacheControl(parsed.data) : {};
|
|
38198
|
+
}
|
|
38199
|
+
function irCacheControl(c) {
|
|
38200
|
+
if (c === undefined)
|
|
38201
|
+
return {};
|
|
38202
|
+
return { cacheControl: { type: c.type, ...c.ttl === undefined ? {} : { ttl: c.ttl } } };
|
|
38203
|
+
}
|
|
38204
|
+
function applyMessageCacheControl(blocks, value) {
|
|
38205
|
+
const control = looseCacheControl(value);
|
|
38206
|
+
if (control.cacheControl === undefined)
|
|
38207
|
+
return;
|
|
38208
|
+
const last = blocks.at(-1);
|
|
38209
|
+
if (last === undefined || last.type === "thinking" || cacheControlOf(last) !== undefined)
|
|
38210
|
+
return;
|
|
38211
|
+
Object.assign(last, control);
|
|
38212
|
+
}
|
|
37964
38213
|
function parseOrThrow2(schema, body2) {
|
|
37965
38214
|
const result = schema.safeParse(body2);
|
|
37966
38215
|
if (result.success)
|
|
@@ -38040,6 +38289,7 @@ function apiErrorHandler(input) {
|
|
|
38040
38289
|
|
|
38041
38290
|
// apps/gateway/src/routes/admin.ts
|
|
38042
38291
|
function adminRoutes(deps) {
|
|
38292
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
38043
38293
|
return new Elysia().onError(apiErrorHandler).get("/api/status", async ({ request: request2 }) => {
|
|
38044
38294
|
const token = readCookie(request2, ADMIN_COOKIE);
|
|
38045
38295
|
return {
|
|
@@ -38068,13 +38318,17 @@ function adminRoutes(deps) {
|
|
|
38068
38318
|
const token = await deps.admin.login(body2.password);
|
|
38069
38319
|
if (token === null)
|
|
38070
38320
|
throw new GatewayError("INTERNAL", "could not create admin session");
|
|
38321
|
+
logger2.info("admin setup completed");
|
|
38071
38322
|
set2.headers["set-cookie"] = sessionCookie(request2, token, Math.floor(deps.sessionTtlMs / 1000));
|
|
38072
38323
|
return { ok: true };
|
|
38073
38324
|
}).post("/api/login", async ({ request: request2, set: set2 }) => {
|
|
38074
38325
|
const body2 = await readJsonRecord(request2);
|
|
38075
38326
|
const token = typeof body2?.password === "string" ? await deps.admin.login(body2.password) : null;
|
|
38076
|
-
if (token === null)
|
|
38327
|
+
if (token === null) {
|
|
38328
|
+
logger2.info("admin login failed", { reason: "invalid credentials" });
|
|
38077
38329
|
throw new GatewayError("AUTH", "invalid password");
|
|
38330
|
+
}
|
|
38331
|
+
logger2.info("admin login succeeded");
|
|
38078
38332
|
set2.headers["set-cookie"] = sessionCookie(request2, token, Math.floor(deps.sessionTtlMs / 1000));
|
|
38079
38333
|
return { ok: true };
|
|
38080
38334
|
}).post("/api/logout", async ({ request: request2, set: set2 }) => {
|
|
@@ -38240,41 +38494,47 @@ function newPendingRequestLog(input) {
|
|
|
38240
38494
|
status: 0
|
|
38241
38495
|
};
|
|
38242
38496
|
}
|
|
38243
|
-
function report(what, requestId, error51) {
|
|
38244
|
-
|
|
38497
|
+
function report(logger2, what, requestId, error51) {
|
|
38498
|
+
logger2.warn(what, {
|
|
38245
38499
|
requestId,
|
|
38246
38500
|
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
38247
38501
|
});
|
|
38248
38502
|
}
|
|
38249
|
-
async function beginLog(store, log, keyId) {
|
|
38503
|
+
async function beginLog(store, log, keyId, logger2 = noopLogger) {
|
|
38250
38504
|
try {
|
|
38251
38505
|
await store.usage.begin({ ...log, state: "pending", apiKeyId: keyId });
|
|
38252
38506
|
} catch (error51) {
|
|
38253
|
-
report("failed to record request start", log.id, error51);
|
|
38507
|
+
report(logger2, "failed to record request start", log.id, error51);
|
|
38254
38508
|
}
|
|
38255
38509
|
}
|
|
38256
|
-
async function routeLog(store, requestId, target) {
|
|
38510
|
+
async function routeLog(store, requestId, target, logger2 = noopLogger) {
|
|
38257
38511
|
try {
|
|
38258
38512
|
await store.usage.route(requestId, target);
|
|
38259
38513
|
} catch (error51) {
|
|
38260
|
-
report("failed to record request route", requestId, error51);
|
|
38514
|
+
report(logger2, "failed to record request route", requestId, error51);
|
|
38261
38515
|
}
|
|
38262
38516
|
}
|
|
38263
|
-
async function finishLog(store, log, keyId) {
|
|
38517
|
+
async function finishLog(store, log, keyId, logger2 = noopLogger) {
|
|
38264
38518
|
try {
|
|
38265
38519
|
await store.usage.append({ ...log, apiKeyId: keyId });
|
|
38266
38520
|
} catch (error51) {
|
|
38267
|
-
report("failed to persist request log", log.id, error51);
|
|
38521
|
+
report(logger2, "failed to persist request log", log.id, error51);
|
|
38268
38522
|
}
|
|
38269
38523
|
}
|
|
38270
38524
|
|
|
38271
38525
|
// apps/gateway/src/dispatch/attempt.ts
|
|
38272
38526
|
async function attempt(opts) {
|
|
38273
38527
|
const { candidate, adapter, http, now, signal, refresh, refreshLeadMs } = opts;
|
|
38528
|
+
const logger2 = opts.logger ?? noopLogger;
|
|
38274
38529
|
const credential = candidate.credential;
|
|
38275
38530
|
let secrets = opts.secrets ?? await credential.openForInference();
|
|
38276
38531
|
const stale = opts.secrets === undefined && credential.authType === "oauth" && credential.expiresAt !== null && credential.expiresAt - refreshLeadMs <= now;
|
|
38277
38532
|
if (stale) {
|
|
38533
|
+
logger2.debug("preemptive credential refresh", {
|
|
38534
|
+
requestId: opts.requestId,
|
|
38535
|
+
provider: credential.provider,
|
|
38536
|
+
credentialId: credential.id
|
|
38537
|
+
});
|
|
38278
38538
|
if (!credential.hasRefreshToken) {
|
|
38279
38539
|
throw new GatewayError("AUTH", "credential expired with no refresh token", {
|
|
38280
38540
|
provider: credential.provider
|
|
@@ -38320,8 +38580,35 @@ function classify(error51) {
|
|
|
38320
38580
|
return { code: "INTERNAL" };
|
|
38321
38581
|
}
|
|
38322
38582
|
|
|
38583
|
+
// apps/gateway/src/dispatch/price.ts
|
|
38584
|
+
var WRITE_OVER_INPUT = {
|
|
38585
|
+
anthropic: { fiveMinute: 1.25, oneHour: 2 },
|
|
38586
|
+
openai: { fiveMinute: 0, oneHour: 0 },
|
|
38587
|
+
kimi: { fiveMinute: 0, oneHour: 0 }
|
|
38588
|
+
};
|
|
38589
|
+
var READ_OVER_INPUT = 0.1;
|
|
38590
|
+
function splitWrites(usage) {
|
|
38591
|
+
const { cacheWrite5mTokens: five, cacheWrite1hTokens: hour, cacheWriteTokens: total } = usage;
|
|
38592
|
+
if (five !== undefined && hour !== undefined)
|
|
38593
|
+
return { fiveMinute: five, oneHour: hour };
|
|
38594
|
+
if (five !== undefined)
|
|
38595
|
+
return { fiveMinute: five, oneHour: Math.max(0, total - five) };
|
|
38596
|
+
if (hour !== undefined)
|
|
38597
|
+
return { fiveMinute: Math.max(0, total - hour), oneHour: hour };
|
|
38598
|
+
return { fiveMinute: total, oneHour: 0 };
|
|
38599
|
+
}
|
|
38600
|
+
function priceOf(prices, usage, provider) {
|
|
38601
|
+
const fallback = WRITE_OVER_INPUT[provider];
|
|
38602
|
+
const readRate = prices.cacheRead ?? prices.input * READ_OVER_INPUT;
|
|
38603
|
+
const write5mRate = prices.cacheWrite5m ?? prices.input * fallback.fiveMinute;
|
|
38604
|
+
const write1hRate = prices.cacheWrite1h ?? prices.input * fallback.oneHour;
|
|
38605
|
+
const writes = splitWrites(usage);
|
|
38606
|
+
return (usage.inputTokens * prices.input + usage.outputTokens * prices.output + usage.cacheReadTokens * readRate + writes.fiveMinute * write5mRate + writes.oneHour * write1hRate) / 1e6;
|
|
38607
|
+
}
|
|
38608
|
+
|
|
38323
38609
|
// apps/gateway/src/dispatch/index.ts
|
|
38324
38610
|
async function dispatch(request2, deps, signal, requestId) {
|
|
38611
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
38325
38612
|
const startedAt = deps.now();
|
|
38326
38613
|
const snapshot = await deps.snapshots.get(startedAt);
|
|
38327
38614
|
const deadlineAt = startedAt + snapshot.settings.requestDeadlineMs;
|
|
@@ -38375,8 +38662,20 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38375
38662
|
now: startedAt,
|
|
38376
38663
|
rand: deps.rand()
|
|
38377
38664
|
});
|
|
38665
|
+
logger2.debug("routing candidates ranked", {
|
|
38666
|
+
requestId,
|
|
38667
|
+
requestedModel: request2.model,
|
|
38668
|
+
model: candidates[0]?.target.model,
|
|
38669
|
+
credentialId: candidates[0]?.credential.id,
|
|
38670
|
+
count: candidates.length
|
|
38671
|
+
});
|
|
38378
38672
|
for (const e of excluded) {
|
|
38379
38673
|
log.degradations.push(`excluded:${e.credentialId}:${e.reason}`);
|
|
38674
|
+
logger2.debug("routing candidate excluded", {
|
|
38675
|
+
requestId,
|
|
38676
|
+
credentialId: e.credentialId,
|
|
38677
|
+
reason: e.reason
|
|
38678
|
+
});
|
|
38380
38679
|
}
|
|
38381
38680
|
if (candidates.length === 0) {
|
|
38382
38681
|
clearDeadline();
|
|
@@ -38407,6 +38706,13 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38407
38706
|
model: candidate.target.model,
|
|
38408
38707
|
credentialId: candidate.credential.id
|
|
38409
38708
|
});
|
|
38709
|
+
logger2.debug("attempt started", {
|
|
38710
|
+
requestId,
|
|
38711
|
+
provider: candidate.target.provider,
|
|
38712
|
+
model: candidate.target.model,
|
|
38713
|
+
credentialId: candidate.credential.id,
|
|
38714
|
+
attempt: i + 1
|
|
38715
|
+
});
|
|
38410
38716
|
log.inputTokens = 0;
|
|
38411
38717
|
log.outputTokens = 0;
|
|
38412
38718
|
log.cacheReadTokens = 0;
|
|
@@ -38428,7 +38734,9 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38428
38734
|
signal: dispatchSignal,
|
|
38429
38735
|
refresh: (credential) => waitForCancellation(deps.refresh(credential), dispatchSignal),
|
|
38430
38736
|
refreshLeadMs: DISPATCH_REFRESH_LEAD_MS,
|
|
38431
|
-
...retrySecrets === undefined ? {} : { secrets: retrySecrets }
|
|
38737
|
+
...retrySecrets === undefined ? {} : { secrets: retrySecrets },
|
|
38738
|
+
logger: logger2,
|
|
38739
|
+
requestId
|
|
38432
38740
|
}), dispatchSignal);
|
|
38433
38741
|
for (const d of result.degradations)
|
|
38434
38742
|
log.degradations.push(d);
|
|
@@ -38438,6 +38746,14 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38438
38746
|
if (event.type === "blockDelta" && !committed) {
|
|
38439
38747
|
committed = true;
|
|
38440
38748
|
log.ttftMs = deps.now() - startedAt;
|
|
38749
|
+
logger2.debug("stream committed", {
|
|
38750
|
+
requestId,
|
|
38751
|
+
provider: candidate.target.provider,
|
|
38752
|
+
model: candidate.target.model,
|
|
38753
|
+
credentialId: candidate.credential.id,
|
|
38754
|
+
attempt: i + 1,
|
|
38755
|
+
ttftMs: log.ttftMs
|
|
38756
|
+
});
|
|
38441
38757
|
for (const buffered of pending)
|
|
38442
38758
|
yield buffered;
|
|
38443
38759
|
pending.length = 0;
|
|
@@ -38448,7 +38764,7 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38448
38764
|
log.outputTokens = event.usage.outputTokens;
|
|
38449
38765
|
log.cacheReadTokens = event.usage.cacheReadTokens;
|
|
38450
38766
|
log.cacheWriteTokens = event.usage.cacheWriteTokens;
|
|
38451
|
-
log.costUsd = priceOf(candidate, event.usage);
|
|
38767
|
+
log.costUsd = priceOf(candidate.target.costPerMTok, event.usage, candidate.target.provider);
|
|
38452
38768
|
}
|
|
38453
38769
|
if (event.type === "error") {
|
|
38454
38770
|
terminal = true;
|
|
@@ -38502,6 +38818,14 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38502
38818
|
lastError = retryAfterMs === undefined ? new GatewayError(code2, message) : new GatewayError(code2, message, { retryAfterMs });
|
|
38503
38819
|
if (code2 === "AUTH" && !committed && !authRefreshRetried && !preemptiveRefreshRequired && candidate.credential.authType === "oauth" && candidate.credential.hasRefreshToken) {
|
|
38504
38820
|
authRefreshRetried = true;
|
|
38821
|
+
logger2.warn("attempt authentication failed; refreshing credential", {
|
|
38822
|
+
requestId,
|
|
38823
|
+
provider: candidate.target.provider,
|
|
38824
|
+
model: candidate.target.model,
|
|
38825
|
+
credentialId: candidate.credential.id,
|
|
38826
|
+
attempt: i + 1,
|
|
38827
|
+
code: code2
|
|
38828
|
+
});
|
|
38505
38829
|
try {
|
|
38506
38830
|
retrySecrets = await waitForCancellation(deps.refresh(candidate.credential), dispatchSignal);
|
|
38507
38831
|
continue;
|
|
@@ -38523,6 +38847,18 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38523
38847
|
...failure.retryAfterMs === undefined ? {} : { retryAfterMs: failure.retryAfterMs },
|
|
38524
38848
|
jitter: deps.rand()
|
|
38525
38849
|
}));
|
|
38850
|
+
if (!committed && RETRYABLE[failure.code] && i + 1 < maxAttempts) {
|
|
38851
|
+
logger2.warn("attempt failed; retrying", {
|
|
38852
|
+
requestId,
|
|
38853
|
+
provider: candidate.target.provider,
|
|
38854
|
+
model: candidate.target.model,
|
|
38855
|
+
credentialId: candidate.credential.id,
|
|
38856
|
+
attempt: i + 1,
|
|
38857
|
+
code: failure.code,
|
|
38858
|
+
retryable: true,
|
|
38859
|
+
retryAfterMs: failure.retryAfterMs
|
|
38860
|
+
});
|
|
38861
|
+
}
|
|
38526
38862
|
if (committed) {
|
|
38527
38863
|
log.status = HTTP_STATUS[failure.code];
|
|
38528
38864
|
log.errorCode = failure.code;
|
|
@@ -38559,11 +38895,6 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
38559
38895
|
}
|
|
38560
38896
|
return { events: run(), log: () => log };
|
|
38561
38897
|
}
|
|
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
38898
|
function waitForCancellation(promise2, signal) {
|
|
38568
38899
|
if (signal.aborted)
|
|
38569
38900
|
return Promise.reject(signal.reason);
|
|
@@ -38624,7 +38955,12 @@ async function* anthropicStream(events, requestId) {
|
|
|
38624
38955
|
content: [],
|
|
38625
38956
|
stop_reason: null,
|
|
38626
38957
|
stop_sequence: null,
|
|
38627
|
-
usage: {
|
|
38958
|
+
usage: {
|
|
38959
|
+
input_tokens: 0,
|
|
38960
|
+
output_tokens: 0,
|
|
38961
|
+
cache_read_input_tokens: 0,
|
|
38962
|
+
cache_creation_input_tokens: 0
|
|
38963
|
+
}
|
|
38628
38964
|
}
|
|
38629
38965
|
});
|
|
38630
38966
|
break;
|
|
@@ -38657,7 +38993,9 @@ async function* anthropicStream(events, requestId) {
|
|
|
38657
38993
|
delta: { stop_reason: STOP_REASON2[event.stopReason], stop_sequence: null },
|
|
38658
38994
|
usage: {
|
|
38659
38995
|
input_tokens: event.usage.inputTokens,
|
|
38660
|
-
output_tokens: event.usage.outputTokens
|
|
38996
|
+
output_tokens: event.usage.outputTokens,
|
|
38997
|
+
cache_read_input_tokens: event.usage.cacheReadTokens,
|
|
38998
|
+
cache_creation_input_tokens: event.usage.cacheWriteTokens
|
|
38661
38999
|
}
|
|
38662
39000
|
});
|
|
38663
39001
|
yield frame("message_stop", { type: "message_stop" });
|
|
@@ -38693,7 +39031,9 @@ function anthropicResponse(collected, requestId) {
|
|
|
38693
39031
|
stop_sequence: null,
|
|
38694
39032
|
usage: {
|
|
38695
39033
|
input_tokens: collected.usage.inputTokens,
|
|
38696
|
-
output_tokens: collected.usage.outputTokens
|
|
39034
|
+
output_tokens: collected.usage.outputTokens,
|
|
39035
|
+
cache_read_input_tokens: collected.usage.cacheReadTokens,
|
|
39036
|
+
cache_creation_input_tokens: collected.usage.cacheWriteTokens
|
|
38697
39037
|
}
|
|
38698
39038
|
};
|
|
38699
39039
|
}
|
|
@@ -38702,6 +39042,15 @@ function anthropicErrorBody(code, message) {
|
|
|
38702
39042
|
}
|
|
38703
39043
|
|
|
38704
39044
|
// apps/gateway/src/egress/openai.ts
|
|
39045
|
+
function usageBody(usage) {
|
|
39046
|
+
const prompt = promptTokens(usage);
|
|
39047
|
+
return {
|
|
39048
|
+
prompt_tokens: prompt,
|
|
39049
|
+
completion_tokens: usage.outputTokens,
|
|
39050
|
+
total_tokens: prompt + usage.outputTokens,
|
|
39051
|
+
prompt_tokens_details: { cached_tokens: usage.cacheReadTokens }
|
|
39052
|
+
};
|
|
39053
|
+
}
|
|
38705
39054
|
var FINISH2 = {
|
|
38706
39055
|
endTurn: "stop",
|
|
38707
39056
|
maxTokens: "length",
|
|
@@ -38799,11 +39148,7 @@ async function* openaiStream(events, requestId, created) {
|
|
|
38799
39148
|
break;
|
|
38800
39149
|
case "end":
|
|
38801
39150
|
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
|
-
}));
|
|
39151
|
+
yield emit(chunk(requestId, created, model, { index: 0, delta: {}, finish_reason: FINISH2[event.stopReason] }, usageBody(event.usage)));
|
|
38807
39152
|
break;
|
|
38808
39153
|
case "error": {
|
|
38809
39154
|
const e = ERROR_TYPE3[event.code];
|
|
@@ -38840,11 +39185,7 @@ function openaiResponse(collected, requestId, created) {
|
|
|
38840
39185
|
finish_reason: FINISH2[collected.stopReason]
|
|
38841
39186
|
}
|
|
38842
39187
|
],
|
|
38843
|
-
usage:
|
|
38844
|
-
prompt_tokens: collected.usage.inputTokens,
|
|
38845
|
-
completion_tokens: collected.usage.outputTokens,
|
|
38846
|
-
total_tokens: collected.usage.inputTokens + collected.usage.outputTokens
|
|
38847
|
-
}
|
|
39188
|
+
usage: usageBody(collected.usage)
|
|
38848
39189
|
};
|
|
38849
39190
|
}
|
|
38850
39191
|
function openaiErrorBody(code, message) {
|
|
@@ -38853,14 +39194,19 @@ function openaiErrorBody(code, message) {
|
|
|
38853
39194
|
}
|
|
38854
39195
|
|
|
38855
39196
|
// apps/gateway/src/ingress/anthropic.ts
|
|
38856
|
-
var textBlock = exports_external.object({
|
|
39197
|
+
var textBlock = exports_external.object({
|
|
39198
|
+
type: exports_external.literal("text"),
|
|
39199
|
+
text: exports_external.string(),
|
|
39200
|
+
cache_control: cacheControlSchema.optional()
|
|
39201
|
+
});
|
|
38857
39202
|
var imageBlock = exports_external.object({
|
|
38858
39203
|
type: exports_external.literal("image"),
|
|
38859
39204
|
source: exports_external.object({
|
|
38860
39205
|
type: exports_external.literal("base64"),
|
|
38861
39206
|
media_type: exports_external.string(),
|
|
38862
39207
|
data: exports_external.string()
|
|
38863
|
-
})
|
|
39208
|
+
}),
|
|
39209
|
+
cache_control: cacheControlSchema.optional()
|
|
38864
39210
|
});
|
|
38865
39211
|
var thinkingBlock = exports_external.object({
|
|
38866
39212
|
type: exports_external.literal("thinking"),
|
|
@@ -38871,13 +39217,15 @@ var toolUseBlock = exports_external.object({
|
|
|
38871
39217
|
type: exports_external.literal("tool_use"),
|
|
38872
39218
|
id: exports_external.string(),
|
|
38873
39219
|
name: exports_external.string(),
|
|
38874
|
-
input: exports_external.record(exports_external.string(), exports_external.unknown()).default({})
|
|
39220
|
+
input: exports_external.record(exports_external.string(), exports_external.unknown()).default({}),
|
|
39221
|
+
cache_control: cacheControlSchema.optional()
|
|
38875
39222
|
});
|
|
38876
39223
|
var toolResultBlock = exports_external.object({
|
|
38877
39224
|
type: exports_external.literal("tool_result"),
|
|
38878
39225
|
tool_use_id: exports_external.string(),
|
|
38879
39226
|
content: exports_external.union([exports_external.string(), exports_external.array(exports_external.unknown())]).optional(),
|
|
38880
|
-
is_error: exports_external.boolean().optional()
|
|
39227
|
+
is_error: exports_external.boolean().optional(),
|
|
39228
|
+
cache_control: cacheControlSchema.optional()
|
|
38881
39229
|
});
|
|
38882
39230
|
var block = exports_external.discriminatedUnion("type", [
|
|
38883
39231
|
textBlock,
|
|
@@ -38901,7 +39249,8 @@ var schema = exports_external.object({
|
|
|
38901
39249
|
tools: exports_external.array(exports_external.object({
|
|
38902
39250
|
name: exports_external.string(),
|
|
38903
39251
|
description: exports_external.string().optional(),
|
|
38904
|
-
input_schema: exports_external.record(exports_external.string(), exports_external.unknown())
|
|
39252
|
+
input_schema: exports_external.record(exports_external.string(), exports_external.unknown()),
|
|
39253
|
+
cache_control: cacheControlSchema.optional()
|
|
38905
39254
|
})).optional(),
|
|
38906
39255
|
tool_choice: exports_external.union([
|
|
38907
39256
|
exports_external.object({ type: exports_external.enum(["auto", "any", "none"]) }),
|
|
@@ -38945,9 +39294,14 @@ function flattenToolResult(content) {
|
|
|
38945
39294
|
function toIrBlock(b) {
|
|
38946
39295
|
switch (b.type) {
|
|
38947
39296
|
case "text":
|
|
38948
|
-
return { type: "text", text: b.text };
|
|
39297
|
+
return { type: "text", text: b.text, ...irCacheControl(b.cache_control) };
|
|
38949
39298
|
case "image":
|
|
38950
|
-
return {
|
|
39299
|
+
return {
|
|
39300
|
+
type: "image",
|
|
39301
|
+
mediaType: b.source.media_type,
|
|
39302
|
+
data: b.source.data,
|
|
39303
|
+
...irCacheControl(b.cache_control)
|
|
39304
|
+
};
|
|
38951
39305
|
case "thinking":
|
|
38952
39306
|
return {
|
|
38953
39307
|
type: "thinking",
|
|
@@ -38955,13 +39309,20 @@ function toIrBlock(b) {
|
|
|
38955
39309
|
...b.signature !== undefined && { signature: b.signature }
|
|
38956
39310
|
};
|
|
38957
39311
|
case "tool_use":
|
|
38958
|
-
return {
|
|
39312
|
+
return {
|
|
39313
|
+
type: "toolUse",
|
|
39314
|
+
id: b.id,
|
|
39315
|
+
name: b.name,
|
|
39316
|
+
input: b.input,
|
|
39317
|
+
...irCacheControl(b.cache_control)
|
|
39318
|
+
};
|
|
38959
39319
|
case "tool_result":
|
|
38960
39320
|
return {
|
|
38961
39321
|
type: "toolResult",
|
|
38962
39322
|
toolUseId: b.tool_use_id,
|
|
38963
39323
|
content: flattenToolResult(b.content),
|
|
38964
|
-
isError: b.is_error ?? false
|
|
39324
|
+
isError: b.is_error ?? false,
|
|
39325
|
+
...irCacheControl(b.cache_control)
|
|
38965
39326
|
};
|
|
38966
39327
|
}
|
|
38967
39328
|
}
|
|
@@ -39008,7 +39369,11 @@ function parseAnthropicRequest(body2, headers) {
|
|
|
39008
39369
|
role: m.role,
|
|
39009
39370
|
content: typeof m.content === "string" ? [{ type: "text", text: m.content }] : m.content.map(toIrBlock)
|
|
39010
39371
|
}));
|
|
39011
|
-
const system = parsed.system === undefined ? undefined : typeof parsed.system === "string" ? [{ type: "text", text: parsed.system }] : parsed.system.map((b) => ({
|
|
39372
|
+
const system = parsed.system === undefined ? undefined : typeof parsed.system === "string" ? [{ type: "text", text: parsed.system }] : parsed.system.map((b) => ({
|
|
39373
|
+
type: "text",
|
|
39374
|
+
text: b.text,
|
|
39375
|
+
...irCacheControl(b.cache_control)
|
|
39376
|
+
}));
|
|
39012
39377
|
const request2 = {
|
|
39013
39378
|
model: parsed.model,
|
|
39014
39379
|
messages,
|
|
@@ -39026,7 +39391,8 @@ function parseAnthropicRequest(body2, headers) {
|
|
|
39026
39391
|
request2.tools = parsed.tools.map((t2) => ({
|
|
39027
39392
|
name: t2.name,
|
|
39028
39393
|
...t2.description !== undefined && { description: t2.description },
|
|
39029
|
-
inputSchema: t2.input_schema
|
|
39394
|
+
inputSchema: t2.input_schema,
|
|
39395
|
+
...irCacheControl(t2.cache_control)
|
|
39030
39396
|
}));
|
|
39031
39397
|
}
|
|
39032
39398
|
if (parsed.tool_choice !== undefined)
|
|
@@ -39048,8 +39414,16 @@ function parseAnthropicRequest(body2, headers) {
|
|
|
39048
39414
|
|
|
39049
39415
|
// apps/gateway/src/ingress/openai.ts
|
|
39050
39416
|
var part = exports_external.discriminatedUnion("type", [
|
|
39051
|
-
exports_external.object({
|
|
39052
|
-
|
|
39417
|
+
exports_external.object({
|
|
39418
|
+
type: exports_external.literal("text"),
|
|
39419
|
+
text: exports_external.string(),
|
|
39420
|
+
cache_control: exports_external.unknown().optional()
|
|
39421
|
+
}),
|
|
39422
|
+
exports_external.object({
|
|
39423
|
+
type: exports_external.literal("image_url"),
|
|
39424
|
+
image_url: exports_external.object({ url: exports_external.string() }),
|
|
39425
|
+
cache_control: exports_external.unknown().optional()
|
|
39426
|
+
})
|
|
39053
39427
|
]);
|
|
39054
39428
|
var toolCall = exports_external.object({
|
|
39055
39429
|
id: exports_external.string(),
|
|
@@ -39060,7 +39434,8 @@ var message2 = exports_external.object({
|
|
|
39060
39434
|
role: exports_external.enum(["system", "developer", "user", "assistant", "tool"]),
|
|
39061
39435
|
content: exports_external.union([exports_external.string(), exports_external.array(part), exports_external.null()]).optional(),
|
|
39062
39436
|
tool_calls: exports_external.array(toolCall).optional(),
|
|
39063
|
-
tool_call_id: exports_external.string().optional()
|
|
39437
|
+
tool_call_id: exports_external.string().optional(),
|
|
39438
|
+
cache_control: exports_external.unknown().optional()
|
|
39064
39439
|
});
|
|
39065
39440
|
var schema2 = exports_external.object({
|
|
39066
39441
|
model: exports_external.string().min(1),
|
|
@@ -39075,8 +39450,10 @@ var schema2 = exports_external.object({
|
|
|
39075
39450
|
function: exports_external.object({
|
|
39076
39451
|
name: exports_external.string(),
|
|
39077
39452
|
description: exports_external.string().optional(),
|
|
39078
|
-
parameters: exports_external.record(exports_external.string(), exports_external.unknown()).optional()
|
|
39079
|
-
|
|
39453
|
+
parameters: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
|
|
39454
|
+
cache_control: exports_external.unknown().optional()
|
|
39455
|
+
}),
|
|
39456
|
+
cache_control: exports_external.unknown().optional()
|
|
39080
39457
|
})).optional(),
|
|
39081
39458
|
tool_choice: exports_external.union([
|
|
39082
39459
|
exports_external.enum(["auto", "none", "required"]),
|
|
@@ -39114,11 +39491,15 @@ function contentBlocks(content) {
|
|
|
39114
39491
|
return [];
|
|
39115
39492
|
return content.map((p) => {
|
|
39116
39493
|
if (p.type === "text")
|
|
39117
|
-
return { type: "text", text: p.text };
|
|
39494
|
+
return { type: "text", text: p.text, ...looseCacheControl(p.cache_control) };
|
|
39118
39495
|
const { mediaType, data } = parseDataUrl(p.image_url.url);
|
|
39119
|
-
return { type: "image", mediaType, data };
|
|
39496
|
+
return { type: "image", mediaType, data, ...looseCacheControl(p.cache_control) };
|
|
39120
39497
|
});
|
|
39121
39498
|
}
|
|
39499
|
+
function toolCacheControl(outer, inner) {
|
|
39500
|
+
const parsed = looseCacheControl(outer);
|
|
39501
|
+
return parsed.cacheControl === undefined ? looseCacheControl(inner) : parsed;
|
|
39502
|
+
}
|
|
39122
39503
|
function toIrToolChoice2(c) {
|
|
39123
39504
|
if (typeof c === "string") {
|
|
39124
39505
|
if (c === "required")
|
|
@@ -39136,7 +39517,9 @@ function parseOpenAIRequest(body2) {
|
|
|
39136
39517
|
const messages = [];
|
|
39137
39518
|
for (const m of parsed.messages) {
|
|
39138
39519
|
if (m.role === "system" || m.role === "developer") {
|
|
39139
|
-
|
|
39520
|
+
const blocks = contentBlocks(m.content);
|
|
39521
|
+
applyMessageCacheControl(blocks, m.cache_control);
|
|
39522
|
+
system.push(...blocks);
|
|
39140
39523
|
continue;
|
|
39141
39524
|
}
|
|
39142
39525
|
if (m.role === "tool") {
|
|
@@ -39150,7 +39533,8 @@ function parseOpenAIRequest(body2) {
|
|
|
39150
39533
|
type: "toolResult",
|
|
39151
39534
|
toolUseId: m.tool_call_id,
|
|
39152
39535
|
content: typeof m.content === "string" ? m.content : "",
|
|
39153
|
-
isError: false
|
|
39536
|
+
isError: false,
|
|
39537
|
+
...looseCacheControl(m.cache_control)
|
|
39154
39538
|
}
|
|
39155
39539
|
]
|
|
39156
39540
|
});
|
|
@@ -39165,6 +39549,7 @@ function parseOpenAIRequest(body2) {
|
|
|
39165
39549
|
input: parseArguments(call.function.arguments)
|
|
39166
39550
|
});
|
|
39167
39551
|
}
|
|
39552
|
+
applyMessageCacheControl(content, m.cache_control);
|
|
39168
39553
|
if (content.length > 0)
|
|
39169
39554
|
messages.push({ role: m.role, content });
|
|
39170
39555
|
}
|
|
@@ -39190,7 +39575,8 @@ function parseOpenAIRequest(body2) {
|
|
|
39190
39575
|
request2.tools = parsed.tools.map((t2) => ({
|
|
39191
39576
|
name: t2.function.name,
|
|
39192
39577
|
...t2.function.description !== undefined && { description: t2.function.description },
|
|
39193
|
-
inputSchema: t2.function.parameters ?? { type: "object" }
|
|
39578
|
+
inputSchema: t2.function.parameters ?? { type: "object" },
|
|
39579
|
+
...toolCacheControl(t2.cache_control, t2.function.cache_control)
|
|
39194
39580
|
}));
|
|
39195
39581
|
}
|
|
39196
39582
|
if (parsed.tool_choice !== undefined)
|
|
@@ -39203,6 +39589,85 @@ function parseOpenAIRequest(body2) {
|
|
|
39203
39589
|
return validateRequest(request2);
|
|
39204
39590
|
}
|
|
39205
39591
|
|
|
39592
|
+
// apps/gateway/src/routes/models.ts
|
|
39593
|
+
var CREATED_AT = new Date(0).toISOString();
|
|
39594
|
+
function narrower(a, b) {
|
|
39595
|
+
const context = [a.contextWindow, b.contextWindow].filter((n) => n !== undefined);
|
|
39596
|
+
const output = [a.maxOutputTokens, b.maxOutputTokens].filter((n) => n !== undefined);
|
|
39597
|
+
return {
|
|
39598
|
+
...context.length === 0 ? {} : { contextWindow: Math.min(...context) },
|
|
39599
|
+
...output.length === 0 ? {} : { maxOutputTokens: Math.min(...output) }
|
|
39600
|
+
};
|
|
39601
|
+
}
|
|
39602
|
+
function targetLimits(target, auths) {
|
|
39603
|
+
const ways = auths.size === 0 ? ["apiKey"] : [...auths];
|
|
39604
|
+
let listed = {};
|
|
39605
|
+
for (const auth of ways) {
|
|
39606
|
+
const entry = catalogLimits(target.provider, target.model, auth);
|
|
39607
|
+
if (entry === null)
|
|
39608
|
+
continue;
|
|
39609
|
+
listed = narrower(listed, {
|
|
39610
|
+
contextWindow: entry.contextWindow,
|
|
39611
|
+
maxOutputTokens: entry.maxOutputTokens
|
|
39612
|
+
});
|
|
39613
|
+
}
|
|
39614
|
+
const contextWindow = target.contextWindow ?? listed.contextWindow;
|
|
39615
|
+
const maxOutputTokens = target.maxOutputTokens ?? listed.maxOutputTokens;
|
|
39616
|
+
return {
|
|
39617
|
+
...contextWindow === undefined ? {} : { contextWindow },
|
|
39618
|
+
...maxOutputTokens === undefined ? {} : { maxOutputTokens }
|
|
39619
|
+
};
|
|
39620
|
+
}
|
|
39621
|
+
function limitsOf(model, authsByProvider) {
|
|
39622
|
+
let limits = {};
|
|
39623
|
+
for (const target of model.targets) {
|
|
39624
|
+
limits = narrower(limits, targetLimits(target, authsByProvider.get(target.provider) ?? new Set));
|
|
39625
|
+
}
|
|
39626
|
+
return limits;
|
|
39627
|
+
}
|
|
39628
|
+
function displayName(model) {
|
|
39629
|
+
const only = model.targets.length === 1 ? model.targets[0] : undefined;
|
|
39630
|
+
if (only === undefined)
|
|
39631
|
+
return model.id;
|
|
39632
|
+
const labelled = PROVIDER_MODEL_CATALOG[only.provider]?.models.find((choice) => choice.id === only.model);
|
|
39633
|
+
return labelled?.label ?? model.id;
|
|
39634
|
+
}
|
|
39635
|
+
function servingAuths(credentials) {
|
|
39636
|
+
const byProvider = new Map;
|
|
39637
|
+
for (const credential of credentials) {
|
|
39638
|
+
if (!credential.enabled)
|
|
39639
|
+
continue;
|
|
39640
|
+
const ways = byProvider.get(credential.provider) ?? new Set;
|
|
39641
|
+
ways.add(credential.authType);
|
|
39642
|
+
byProvider.set(credential.provider, ways);
|
|
39643
|
+
}
|
|
39644
|
+
return byProvider;
|
|
39645
|
+
}
|
|
39646
|
+
function describeModel(model, credentials) {
|
|
39647
|
+
const limits = limitsOf(model, servingAuths(credentials));
|
|
39648
|
+
return {
|
|
39649
|
+
id: model.id,
|
|
39650
|
+
object: "model",
|
|
39651
|
+
type: "model",
|
|
39652
|
+
display_name: displayName(model),
|
|
39653
|
+
created: 0,
|
|
39654
|
+
created_at: CREATED_AT,
|
|
39655
|
+
owned_by: "omnigateway",
|
|
39656
|
+
...limits.contextWindow === undefined ? {} : { max_input_tokens: limits.contextWindow },
|
|
39657
|
+
...limits.maxOutputTokens === undefined ? {} : { max_tokens: limits.maxOutputTokens }
|
|
39658
|
+
};
|
|
39659
|
+
}
|
|
39660
|
+
function modelListBody(models, credentials) {
|
|
39661
|
+
const data = models.map((model) => describeModel(model, credentials));
|
|
39662
|
+
return {
|
|
39663
|
+
object: "list",
|
|
39664
|
+
data,
|
|
39665
|
+
has_more: false,
|
|
39666
|
+
first_id: data[0]?.id ?? null,
|
|
39667
|
+
last_id: data[data.length - 1]?.id ?? null
|
|
39668
|
+
};
|
|
39669
|
+
}
|
|
39670
|
+
|
|
39206
39671
|
// apps/gateway/src/routes/proxy.ts
|
|
39207
39672
|
var KEEPALIVE_MS = 1e4;
|
|
39208
39673
|
var KEEPALIVE = Symbol("keepalive");
|
|
@@ -39234,9 +39699,9 @@ function asGatewayError(error51) {
|
|
|
39234
39699
|
function sseResponse(frames, onDone, keepaliveMs) {
|
|
39235
39700
|
const encoder2 = new TextEncoder;
|
|
39236
39701
|
let done = null;
|
|
39237
|
-
const runOnce = () => {
|
|
39702
|
+
const runOnce = (cancelled = false) => {
|
|
39238
39703
|
if (done === null)
|
|
39239
|
-
done = onDone();
|
|
39704
|
+
done = onDone(cancelled);
|
|
39240
39705
|
return done;
|
|
39241
39706
|
};
|
|
39242
39707
|
let inflight = null;
|
|
@@ -39269,7 +39734,7 @@ data: ${data}
|
|
|
39269
39734
|
},
|
|
39270
39735
|
async cancel() {
|
|
39271
39736
|
await frames.return(undefined);
|
|
39272
|
-
await runOnce();
|
|
39737
|
+
await runOnce(true);
|
|
39273
39738
|
}
|
|
39274
39739
|
});
|
|
39275
39740
|
return new Response(stream2, { headers: SSE_HEADERS });
|
|
@@ -39280,9 +39745,34 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
39280
39745
|
let keyId = null;
|
|
39281
39746
|
let requestedModel = "";
|
|
39282
39747
|
try {
|
|
39283
|
-
|
|
39748
|
+
let key;
|
|
39749
|
+
try {
|
|
39750
|
+
key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
|
|
39751
|
+
} catch (error51) {
|
|
39752
|
+
if (error51 instanceof GatewayError && error51.code === "AUTH") {
|
|
39753
|
+
deps.logger.warn("authentication rejected", {
|
|
39754
|
+
requestId,
|
|
39755
|
+
surface,
|
|
39756
|
+
reason: "invalid credentials"
|
|
39757
|
+
});
|
|
39758
|
+
}
|
|
39759
|
+
throw error51;
|
|
39760
|
+
}
|
|
39284
39761
|
keyId = key.id;
|
|
39285
|
-
|
|
39762
|
+
try {
|
|
39763
|
+
rateLimiter.consume(key.id, key.rateLimitPerMin);
|
|
39764
|
+
} catch (error51) {
|
|
39765
|
+
if (error51 instanceof GatewayError && error51.code === "RATE_LIMIT") {
|
|
39766
|
+
deps.logger.warn("rate limit rejected", {
|
|
39767
|
+
requestId,
|
|
39768
|
+
surface,
|
|
39769
|
+
apiKeyId: key.id,
|
|
39770
|
+
code: error51.code,
|
|
39771
|
+
retryAfterMs: error51.retryAfterMs
|
|
39772
|
+
});
|
|
39773
|
+
}
|
|
39774
|
+
throw error51;
|
|
39775
|
+
}
|
|
39286
39776
|
const body2 = await request2.json();
|
|
39287
39777
|
const chatRequest = surface === "anthropic" ? parseAnthropicRequest(body2, request2.headers) : parseOpenAIRequest(body2);
|
|
39288
39778
|
if (key.modelAllowlist !== null && !key.modelAllowlist.includes(chatRequest.model)) {
|
|
@@ -39294,7 +39784,7 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
39294
39784
|
...deps,
|
|
39295
39785
|
async onRoute(target) {
|
|
39296
39786
|
if (began) {
|
|
39297
|
-
await routeLog(deps.store, requestId, target);
|
|
39787
|
+
await routeLog(deps.store, requestId, target, deps.logger);
|
|
39298
39788
|
return;
|
|
39299
39789
|
}
|
|
39300
39790
|
began = true;
|
|
@@ -39306,10 +39796,45 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
39306
39796
|
resolvedModel: target.model,
|
|
39307
39797
|
credentialId: target.credentialId
|
|
39308
39798
|
});
|
|
39309
|
-
await beginLog(deps.store, log2, keyId);
|
|
39799
|
+
await beginLog(deps.store, log2, keyId, deps.logger);
|
|
39310
39800
|
}
|
|
39311
39801
|
}, request2.signal, requestId);
|
|
39312
|
-
const log = () =>
|
|
39802
|
+
const log = async (cancelled = false) => {
|
|
39803
|
+
const completed = outcome.log();
|
|
39804
|
+
const wasCancelled = cancelled || request2.signal.aborted;
|
|
39805
|
+
if (wasCancelled && completed.status === 0) {
|
|
39806
|
+
completed.status = 499;
|
|
39807
|
+
completed.errorCode = "interrupted";
|
|
39808
|
+
completed.durationMs = deps.now() - startedAt;
|
|
39809
|
+
}
|
|
39810
|
+
await finishLog(deps.store, completed, keyId, deps.logger);
|
|
39811
|
+
const fields = {
|
|
39812
|
+
requestId,
|
|
39813
|
+
surface,
|
|
39814
|
+
status: completed.status,
|
|
39815
|
+
provider: completed.resolvedProvider ?? undefined,
|
|
39816
|
+
model: completed.resolvedModel ?? undefined,
|
|
39817
|
+
requestedModel: completed.requestedModel,
|
|
39818
|
+
credentialId: completed.credentialId ?? undefined,
|
|
39819
|
+
apiKeyId: keyId ?? undefined,
|
|
39820
|
+
attempts: completed.attempts,
|
|
39821
|
+
code: completed.errorCode ?? undefined,
|
|
39822
|
+
stream: chatRequest.stream,
|
|
39823
|
+
inputTokens: completed.inputTokens,
|
|
39824
|
+
outputTokens: completed.outputTokens,
|
|
39825
|
+
cacheReadTokens: completed.cacheReadTokens,
|
|
39826
|
+
cacheWriteTokens: completed.cacheWriteTokens,
|
|
39827
|
+
costUsd: completed.costUsd,
|
|
39828
|
+
ttftMs: completed.ttftMs,
|
|
39829
|
+
durationMs: completed.durationMs
|
|
39830
|
+
};
|
|
39831
|
+
if (wasCancelled)
|
|
39832
|
+
deps.logger.debug("request cancelled", fields);
|
|
39833
|
+
else if (completed.status >= 400)
|
|
39834
|
+
deps.logger.error("request failed", fields);
|
|
39835
|
+
else
|
|
39836
|
+
deps.logger.info("request done", fields);
|
|
39837
|
+
};
|
|
39313
39838
|
if (chatRequest.stream) {
|
|
39314
39839
|
const frames = surface === "anthropic" ? anthropicStream(outcome.events, requestId) : openaiStream(outcome.events, requestId, Math.floor(deps.now() / 1000));
|
|
39315
39840
|
return sseResponse(frames, log, deps.keepaliveMs);
|
|
@@ -39329,41 +39854,59 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
39329
39854
|
});
|
|
39330
39855
|
} catch (error51) {
|
|
39331
39856
|
const gatewayError = asGatewayError(error51);
|
|
39332
|
-
|
|
39857
|
+
const completed = newCompletedRequestLog(requestId, startedAt, {
|
|
39333
39858
|
requestedModel,
|
|
39334
39859
|
status: HTTP_STATUS[gatewayError.code],
|
|
39335
|
-
errorCode: gatewayError.code
|
|
39336
|
-
|
|
39860
|
+
errorCode: gatewayError.code,
|
|
39861
|
+
durationMs: deps.now() - startedAt
|
|
39862
|
+
});
|
|
39863
|
+
await finishLog(deps.store, completed, keyId, deps.logger);
|
|
39864
|
+
deps.logger.error("request failed", {
|
|
39865
|
+
requestId,
|
|
39866
|
+
surface,
|
|
39867
|
+
status: completed.status,
|
|
39868
|
+
requestedModel,
|
|
39869
|
+
apiKeyId: keyId ?? undefined,
|
|
39870
|
+
code: gatewayError.code,
|
|
39871
|
+
attempts: completed.attempts,
|
|
39872
|
+
durationMs: completed.durationMs,
|
|
39873
|
+
reason: gatewayError.message
|
|
39874
|
+
});
|
|
39337
39875
|
return errorResponse(surface, gatewayError.code, gatewayError.message);
|
|
39338
39876
|
}
|
|
39339
39877
|
}
|
|
39340
39878
|
function proxyRoutes(deps) {
|
|
39341
39879
|
const rateLimiter = deps.rateLimiter ?? new ApiKeyRateLimiter(deps.now);
|
|
39880
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
39342
39881
|
const dispatchDeps = {
|
|
39343
39882
|
...deps,
|
|
39344
|
-
|
|
39883
|
+
logger: logger2,
|
|
39884
|
+
snapshots: deps.snapshots ?? createRoutingSnapshotCache(deps.store, logger2),
|
|
39345
39885
|
keepaliveMs: deps.keepaliveMs ?? KEEPALIVE_MS
|
|
39346
39886
|
};
|
|
39347
39887
|
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
39888
|
try {
|
|
39349
39889
|
const key = await authenticateApiKey(deps.store, apiKeyHeader(request2.headers));
|
|
39350
|
-
const
|
|
39890
|
+
const snapshot = await dispatchDeps.snapshots.get(deps.now());
|
|
39891
|
+
const models = [...snapshot.models.values()];
|
|
39351
39892
|
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
|
-
});
|
|
39893
|
+
return Response.json(modelListBody(visibleModels, snapshot.credentials));
|
|
39361
39894
|
} catch (error51) {
|
|
39362
39895
|
const gatewayError = asGatewayError(error51);
|
|
39896
|
+
logger2.error("model listing failed", {
|
|
39897
|
+
status: HTTP_STATUS[gatewayError.code],
|
|
39898
|
+
code: gatewayError.code,
|
|
39899
|
+
reason: gatewayError.message
|
|
39900
|
+
});
|
|
39363
39901
|
return errorResponse("anthropic", gatewayError.code, gatewayError.message);
|
|
39364
39902
|
}
|
|
39365
39903
|
}).onError(({ error: error51 }) => {
|
|
39366
39904
|
const gatewayError = asGatewayError(error51);
|
|
39905
|
+
logger2.error("unhandled proxy route error", {
|
|
39906
|
+
status: HTTP_STATUS[gatewayError.code],
|
|
39907
|
+
code: gatewayError.code,
|
|
39908
|
+
reason: gatewayError.message
|
|
39909
|
+
});
|
|
39367
39910
|
return errorResponse("anthropic", gatewayError.code, gatewayError.message);
|
|
39368
39911
|
});
|
|
39369
39912
|
}
|
|
@@ -39372,18 +39915,20 @@ function proxyRoutes(deps) {
|
|
|
39372
39915
|
var ADMIN_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
|
|
39373
39916
|
function createApp(deps) {
|
|
39374
39917
|
const now = deps.now ?? (() => Date.now());
|
|
39918
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
39375
39919
|
const rand = deps.rand ?? Math.random;
|
|
39376
|
-
const http = deps.http ?? nodeHttpClient();
|
|
39920
|
+
const http = deps.http ?? nodeHttpClient({ logger: logger2, now });
|
|
39377
39921
|
const adapters = deps.adapters ?? ADAPTERS;
|
|
39378
39922
|
const requestId = deps.requestId ?? (() => `req_${crypto.randomUUID()}`);
|
|
39379
39923
|
const rateLimiter = new ApiKeyRateLimiter(now);
|
|
39380
|
-
const snapshots = createRoutingSnapshotCache(deps.store);
|
|
39924
|
+
const snapshots = createRoutingSnapshotCache(deps.store, logger2);
|
|
39381
39925
|
const admin = createAdminAuth(deps.store, { now, sessionTtlMs: ADMIN_SESSION_TTL_MS });
|
|
39382
39926
|
const refresh = deps.refresh ?? createRefresher({
|
|
39383
39927
|
store: deps.store,
|
|
39384
39928
|
providers: OAUTH_PROVIDERS,
|
|
39385
39929
|
http,
|
|
39386
|
-
now
|
|
39930
|
+
now,
|
|
39931
|
+
logger: logger2
|
|
39387
39932
|
});
|
|
39388
39933
|
const staticDir = deps.staticDir ? resolve(deps.staticDir) : undefined;
|
|
39389
39934
|
let staticRoot;
|
|
@@ -39401,13 +39946,21 @@ function createApp(deps) {
|
|
|
39401
39946
|
rand,
|
|
39402
39947
|
refresh,
|
|
39403
39948
|
requestId,
|
|
39404
|
-
rateLimiter
|
|
39405
|
-
|
|
39949
|
+
rateLimiter,
|
|
39950
|
+
logger: logger2
|
|
39951
|
+
})).use(adminRoutes({
|
|
39952
|
+
store: deps.store,
|
|
39953
|
+
admin,
|
|
39954
|
+
now,
|
|
39955
|
+
sessionTtlMs: ADMIN_SESSION_TTL_MS,
|
|
39956
|
+
logger: logger2
|
|
39957
|
+
})).use(connectRoutes({
|
|
39406
39958
|
store: deps.store,
|
|
39407
39959
|
admin,
|
|
39408
39960
|
providers: OAUTH_PROVIDERS,
|
|
39409
39961
|
http,
|
|
39410
|
-
now
|
|
39962
|
+
now,
|
|
39963
|
+
logger: logger2
|
|
39411
39964
|
})).get("/*", ({ path, request: request2, set: set2 }) => {
|
|
39412
39965
|
const rawPath = new URL(request2.url).pathname;
|
|
39413
39966
|
const error51 = () => {
|
|
@@ -39465,13 +40018,15 @@ var DAY_MS = 24 * 60 * 60 * 1000;
|
|
|
39465
40018
|
var ROLLUP_RETENTION_DAYS = 400;
|
|
39466
40019
|
async function pruneLogs(store, now) {
|
|
39467
40020
|
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);
|
|
40021
|
+
const raw = await store.usage.prune(now - settings.logRetentionDays * DAY_MS);
|
|
40022
|
+
const daily = await store.usage.pruneDaily(now - ROLLUP_RETENTION_DAYS * DAY_MS);
|
|
40023
|
+
return { raw, daily };
|
|
39470
40024
|
}
|
|
39471
40025
|
function startMaintenance(deps) {
|
|
40026
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
39472
40027
|
const timer = setInterval(() => {
|
|
39473
|
-
pruneLogs(deps.store, deps.now()).catch((error51) => {
|
|
39474
|
-
|
|
40028
|
+
pruneLogs(deps.store, deps.now()).then(({ raw, daily }) => logger2.debug("request logs pruned", { rawCount: raw, dailyCount: daily })).catch((error51) => {
|
|
40029
|
+
logger2.error("log pruning failed", {
|
|
39475
40030
|
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
39476
40031
|
});
|
|
39477
40032
|
});
|
|
@@ -39486,6 +40041,7 @@ function due(credentials, now) {
|
|
|
39486
40041
|
return credentials.filter((c) => c.enabled && c.authType === "oauth" && c.expiresAt !== null && c.expiresAt - SCHEDULER_REFRESH_LEAD_MS <= now);
|
|
39487
40042
|
}
|
|
39488
40043
|
async function sweep(deps) {
|
|
40044
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
39489
40045
|
const credentials = await deps.store.credentials.list();
|
|
39490
40046
|
let refreshed = 0;
|
|
39491
40047
|
for (const credential of due(credentials, deps.now())) {
|
|
@@ -39504,23 +40060,25 @@ async function sweep(deps) {
|
|
|
39504
40060
|
refreshed += 1;
|
|
39505
40061
|
} catch (error51) {
|
|
39506
40062
|
const code = error51 instanceof GatewayError ? error51.code : "INTERNAL";
|
|
39507
|
-
|
|
39508
|
-
credentialId: credential.id,
|
|
40063
|
+
logger2.warn("scheduled token refresh failed", {
|
|
39509
40064
|
provider: credential.provider,
|
|
39510
|
-
|
|
40065
|
+
credentialId: credential.id,
|
|
40066
|
+
code,
|
|
40067
|
+
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
39511
40068
|
});
|
|
39512
40069
|
}
|
|
39513
40070
|
}
|
|
39514
40071
|
return refreshed;
|
|
39515
40072
|
}
|
|
39516
40073
|
function startRefreshScheduler(deps) {
|
|
40074
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
39517
40075
|
let running = false;
|
|
39518
40076
|
const timer = setInterval(() => {
|
|
39519
40077
|
if (running)
|
|
39520
40078
|
return;
|
|
39521
40079
|
running = true;
|
|
39522
40080
|
sweep(deps).catch((error51) => {
|
|
39523
|
-
|
|
40081
|
+
logger2.error("token refresh sweep failed", {
|
|
39524
40082
|
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
39525
40083
|
});
|
|
39526
40084
|
}).finally(() => {
|
|
@@ -39533,6 +40091,7 @@ function startRefreshScheduler(deps) {
|
|
|
39533
40091
|
|
|
39534
40092
|
// apps/gateway/src/quota/poller.ts
|
|
39535
40093
|
async function startQuotaPoller(deps) {
|
|
40094
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
39536
40095
|
const { quotaPollIntervalMs } = await deps.store.config.getSettings();
|
|
39537
40096
|
if (quotaPollIntervalMs <= 0)
|
|
39538
40097
|
return () => {};
|
|
@@ -39542,7 +40101,7 @@ async function startQuotaPoller(deps) {
|
|
|
39542
40101
|
return;
|
|
39543
40102
|
running = true;
|
|
39544
40103
|
poll(deps).catch((error51) => {
|
|
39545
|
-
|
|
40104
|
+
logger2.error("quota poll failed", {
|
|
39546
40105
|
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
39547
40106
|
});
|
|
39548
40107
|
}).finally(() => {
|
|
@@ -39556,62 +40115,101 @@ async function startQuotaPoller(deps) {
|
|
|
39556
40115
|
}
|
|
39557
40116
|
|
|
39558
40117
|
// 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
|
-
now
|
|
39596
|
-
});
|
|
39597
|
-
|
|
39598
|
-
|
|
39599
|
-
|
|
39600
|
-
|
|
39601
|
-
|
|
39602
|
-
|
|
39603
|
-
|
|
39604
|
-
|
|
40118
|
+
function stdoutLogger(level) {
|
|
40119
|
+
return createLogger({
|
|
40120
|
+
level,
|
|
40121
|
+
color: Boolean(process.stdout.isTTY),
|
|
40122
|
+
write: (line) => {
|
|
40123
|
+
process.stdout.write(`${line}
|
|
40124
|
+
`);
|
|
40125
|
+
}
|
|
40126
|
+
});
|
|
40127
|
+
}
|
|
40128
|
+
var logger2 = stdoutLogger("info");
|
|
40129
|
+
async function main() {
|
|
40130
|
+
const config2 = loadConfig(process.env);
|
|
40131
|
+
logger2 = stdoutLogger(config2.logLevel);
|
|
40132
|
+
if (config2.logLevelFallbackFrom !== null) {
|
|
40133
|
+
logger2.info("invalid log level; using info", { reason: config2.logLevelFallbackFrom });
|
|
40134
|
+
}
|
|
40135
|
+
logger2.info("omnigateway booting", { host: config2.host, port: config2.port });
|
|
40136
|
+
function dashboardDir() {
|
|
40137
|
+
if (config2.staticDir !== null)
|
|
40138
|
+
return config2.staticDir;
|
|
40139
|
+
const candidates = [
|
|
40140
|
+
resolve2(import.meta.dir, "./public"),
|
|
40141
|
+
resolve2(import.meta.dir, "../../dashboard/dist")
|
|
40142
|
+
];
|
|
40143
|
+
return candidates.find((path) => existsSync(path)) ?? candidates[1];
|
|
40144
|
+
}
|
|
40145
|
+
const encryptionKey = await deriveKey(config2.encryptionKey);
|
|
40146
|
+
const store = await createStore({
|
|
40147
|
+
path: config2.databasePath,
|
|
40148
|
+
encryptionKey,
|
|
40149
|
+
logger: logger2
|
|
40150
|
+
});
|
|
40151
|
+
const swept = await store.usage.sweepPending();
|
|
40152
|
+
if (swept > 0)
|
|
40153
|
+
logger2.info("retired interrupted requests", { count: swept });
|
|
40154
|
+
const now = () => Date.now();
|
|
40155
|
+
const http = nodeHttpClient({ logger: logger2, now });
|
|
40156
|
+
const refresh = createRefresher({ store, providers: OAUTH_PROVIDERS, http, now, logger: logger2 });
|
|
40157
|
+
const staticDir = dashboardDir();
|
|
40158
|
+
logger2.info(existsSync(staticDir) ? "dashboard directory resolved" : "dashboard directory absent", {
|
|
40159
|
+
path: staticDir
|
|
40160
|
+
});
|
|
40161
|
+
const app = createApp({
|
|
40162
|
+
store,
|
|
40163
|
+
baseUrl: config2.baseUrl,
|
|
40164
|
+
http,
|
|
40165
|
+
now,
|
|
40166
|
+
refresh,
|
|
40167
|
+
staticDir,
|
|
40168
|
+
logger: logger2
|
|
40169
|
+
});
|
|
40170
|
+
const stopMaintenance = startMaintenance({ store, now, logger: logger2 });
|
|
40171
|
+
const stopRefreshScheduler = startRefreshScheduler({ store, refresh, now, logger: logger2 });
|
|
40172
|
+
const stopQuotaPoller = await startQuotaPoller({
|
|
40173
|
+
store,
|
|
40174
|
+
providers: OAUTH_PROVIDERS,
|
|
40175
|
+
http,
|
|
40176
|
+
refresh,
|
|
40177
|
+
now,
|
|
40178
|
+
logger: logger2
|
|
40179
|
+
});
|
|
40180
|
+
app.listen({ port: config2.port, hostname: config2.host, idleTimeout: 255 });
|
|
40181
|
+
logger2.info("omnigateway listening", { host: config2.host, port: config2.port });
|
|
40182
|
+
let shuttingDown = false;
|
|
40183
|
+
function exitAfterClosingStore(code) {
|
|
40184
|
+
try {
|
|
40185
|
+
store.close();
|
|
40186
|
+
} finally {
|
|
40187
|
+
process.exit(code);
|
|
40188
|
+
}
|
|
40189
|
+
}
|
|
40190
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
40191
|
+
process.on(signal, () => {
|
|
40192
|
+
if (shuttingDown)
|
|
40193
|
+
exitAfterClosingStore(1);
|
|
40194
|
+
logger2.info("shutdown requested", { reason: signal });
|
|
40195
|
+
shuttingDown = true;
|
|
40196
|
+
stopMaintenance();
|
|
40197
|
+
stopRefreshScheduler();
|
|
40198
|
+
stopQuotaPoller();
|
|
40199
|
+
app.stop().then(() => exitAfterClosingStore(0), (error51) => {
|
|
40200
|
+
logger2.error("shutdown failed", {
|
|
40201
|
+
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
40202
|
+
});
|
|
40203
|
+
return exitAfterClosingStore(1);
|
|
40204
|
+
});
|
|
40205
|
+
});
|
|
39605
40206
|
}
|
|
39606
40207
|
}
|
|
39607
|
-
|
|
39608
|
-
|
|
39609
|
-
|
|
39610
|
-
|
|
39611
|
-
|
|
39612
|
-
stopMaintenance();
|
|
39613
|
-
stopRefreshScheduler();
|
|
39614
|
-
stopQuotaPoller();
|
|
39615
|
-
app.stop().then(() => exitAfterClosingStore(0), () => exitAfterClosingStore(1));
|
|
40208
|
+
try {
|
|
40209
|
+
await main();
|
|
40210
|
+
} catch (error51) {
|
|
40211
|
+
logger2.error("gateway boot failed", {
|
|
40212
|
+
reason: error51 instanceof Error ? error51.message : "unknown"
|
|
39616
40213
|
});
|
|
40214
|
+
process.exit(1);
|
|
39617
40215
|
}
|