omnigateway 0.4.13 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -1
- package/bin/omni.js +176 -72
- package/gateway.js +1145 -126
- package/package.json +1 -1
- package/public/assets/{CopyValue-C2DkO9Yz.js → CopyValue-dQWAaTxf.js} +3 -3
- package/public/assets/{Field-B79Yxdit.js → Field-DTQ2LPzA.js} +1 -1
- package/public/assets/{Match-BWa6L0L1.js → Match-C85GObiU.js} +1 -1
- package/public/assets/{Panel-CoulMx6R.js → Panel-CQVz9yeR.js} +2 -2
- package/public/assets/Rack-V32CHmN6.js +316 -0
- package/public/assets/{Readout-D1XEfywe.js → Readout-DJR1qKja.js} +1 -1
- package/public/assets/States-CauIsA0N.js +54 -0
- package/public/assets/{Toggle-CRrH9nDt.js → Toggle-BQI8jz09.js} +1 -1
- package/public/assets/{TokenBreakdown-C2NsJnqO.js → TokenBreakdown-B2_gfuqt.js} +1 -1
- package/public/assets/_app-DzzXdoyU.js +1 -0
- package/public/assets/_app.accounts-owsnxlbn.js +64 -0
- package/public/assets/_app.console-bzKNP_8Y.js +44 -0
- package/public/assets/_app.database-CsJbkedE.js +24 -0
- package/public/assets/_app.index-D4ydl2mY.js +62 -0
- package/public/assets/_app.keys-DKX0smty.js +82 -0
- package/public/assets/_app.logs-CFFsj2zR.js +68 -0
- package/public/assets/_app.models-B0ZxWGHV.js +148 -0
- package/public/assets/{_app.plugins._pluginId-BOXRBToU.js → _app.plugins._pluginId-C5X4B1cE.js} +3 -3
- package/public/assets/{_app.settings-DoaOUmc1.js → _app.settings-Yg_6az-L.js} +4 -4
- package/public/assets/_app.usage-BRIz_GCn.js +83 -0
- package/public/assets/index-DiCU8bLB.js +178 -0
- package/public/assets/{login-B9CJqU--.js → login-B4LoiALI.js} +3 -3
- package/public/assets/plus-xu9reaJp.js +1 -0
- package/public/assets/queries-DuSqKybM.js +145 -0
- package/public/assets/{shared-Cct_4xwp.js → shared-DYkFSPbd.js} +4 -4
- package/public/assets/stream-Dz5HjHBh.js +1 -0
- package/public/assets/{trash-2-DwMK-JrZ.js → trash-2-DbutVvFY.js} +1 -1
- package/public/index.html +3 -3
- package/public/shared/dashboard-sdk.js +1 -1
- package/public/assets/Rack-HM36SprU.js +0 -314
- package/public/assets/States-DtkkMzEu.js +0 -54
- package/public/assets/_app-DVpIsUid.js +0 -1
- package/public/assets/_app.accounts-64d_cxir.js +0 -64
- package/public/assets/_app.console-BBuBmLRI.js +0 -44
- package/public/assets/_app.database-J9Ujio8l.js +0 -24
- package/public/assets/_app.index-BX4yKEXO.js +0 -62
- package/public/assets/_app.keys-2sKwhZJc.js +0 -76
- package/public/assets/_app.logs-DuHXYrZU.js +0 -68
- package/public/assets/_app.models-B0kTfPtn.js +0 -148
- package/public/assets/_app.usage-B5sxB40E.js +0 -83
- package/public/assets/index-iCfSaRSG.js +0 -178
- package/public/assets/plus-DKapAGoR.js +0 -1
- package/public/assets/queries-CX9mStps.js +0 -145
package/gateway.js
CHANGED
|
@@ -2763,8 +2763,8 @@ function checkBytes(buffer, headers, options) {
|
|
|
2763
2763
|
return true;
|
|
2764
2764
|
}
|
|
2765
2765
|
function hasUnknownFileSize(tokenizer) {
|
|
2766
|
-
const
|
|
2767
|
-
return !Number.isFinite(
|
|
2766
|
+
const fileSize2 = tokenizer.fileInfo.size;
|
|
2767
|
+
return !Number.isFinite(fileSize2) || fileSize2 === Number.MAX_SAFE_INTEGER;
|
|
2768
2768
|
}
|
|
2769
2769
|
function hasExceededUnknownSizeScanBudget(tokenizer, startOffset, maximumBytes) {
|
|
2770
2770
|
return hasUnknownFileSize(tokenizer) && tokenizer.position - startOffset > maximumBytes;
|
|
@@ -2822,8 +2822,8 @@ function mergeByteChunks(chunks, totalLength) {
|
|
|
2822
2822
|
return merged;
|
|
2823
2823
|
}
|
|
2824
2824
|
function getMaximumZipBufferedReadLength(tokenizer) {
|
|
2825
|
-
const
|
|
2826
|
-
const remainingBytes = Number.isFinite(
|
|
2825
|
+
const fileSize2 = tokenizer.fileInfo.size;
|
|
2826
|
+
const remainingBytes = Number.isFinite(fileSize2) ? Math.max(0, fileSize2 - tokenizer.position) : Number.MAX_SAFE_INTEGER;
|
|
2827
2827
|
return Math.min(remainingBytes, maximumZipBufferedReadSizeInBytes);
|
|
2828
2828
|
}
|
|
2829
2829
|
function isRecoverableZipError(error51) {
|
|
@@ -3561,11 +3561,11 @@ function normalizeMpegOffsetTolerance(mpegOffsetTolerance) {
|
|
|
3561
3561
|
}
|
|
3562
3562
|
return Math.max(0, Math.min(maximumMpegOffsetTolerance, Math.trunc(mpegOffsetTolerance)));
|
|
3563
3563
|
}
|
|
3564
|
-
function getKnownFileSizeOrMaximum(
|
|
3565
|
-
if (!Number.isFinite(
|
|
3564
|
+
function getKnownFileSizeOrMaximum(fileSize2) {
|
|
3565
|
+
if (!Number.isFinite(fileSize2)) {
|
|
3566
3566
|
return Number.MAX_SAFE_INTEGER;
|
|
3567
3567
|
}
|
|
3568
|
-
return Math.max(0,
|
|
3568
|
+
return Math.max(0, fileSize2);
|
|
3569
3569
|
}
|
|
3570
3570
|
function importAtRuntime(specifier) {
|
|
3571
3571
|
return import(specifier);
|
|
@@ -4804,8 +4804,8 @@ class FileTypeParser {
|
|
|
4804
4804
|
};
|
|
4805
4805
|
detectImprecise = async (tokenizer) => {
|
|
4806
4806
|
this.buffer = new Uint8Array(reasonableDetectionSizeInBytes);
|
|
4807
|
-
const
|
|
4808
|
-
await tokenizer.peekBuffer(this.buffer, { length: Math.min(8,
|
|
4807
|
+
const fileSize2 = getKnownFileSizeOrMaximum(tokenizer.fileInfo.size);
|
|
4808
|
+
await tokenizer.peekBuffer(this.buffer, { length: Math.min(8, fileSize2), mayBeLess: true });
|
|
4809
4809
|
if (this.check([0, 0, 1, 186]) || this.check([0, 0, 1, 179])) {
|
|
4810
4810
|
return {
|
|
4811
4811
|
ext: "mpg",
|
|
@@ -4830,7 +4830,7 @@ class FileTypeParser {
|
|
|
4830
4830
|
mime: "image/x-icon"
|
|
4831
4831
|
};
|
|
4832
4832
|
}
|
|
4833
|
-
await tokenizer.peekBuffer(this.buffer, { length: Math.min(2 + this.options.mpegOffsetTolerance,
|
|
4833
|
+
await tokenizer.peekBuffer(this.buffer, { length: Math.min(2 + this.options.mpegOffsetTolerance, fileSize2), mayBeLess: true });
|
|
4834
4834
|
if (this.buffer.length >= 2 + this.options.mpegOffsetTolerance) {
|
|
4835
4835
|
for (let depth = 0;depth <= this.options.mpegOffsetTolerance; ++depth) {
|
|
4836
4836
|
const type = this.scanMpeg(depth);
|
|
@@ -5760,7 +5760,6 @@ function loadConfig(env) {
|
|
|
5760
5760
|
const baseUrl = optionalText(env.OMNI_BASE_URL, derivedBaseUrl).replace(/\/+$/, "") || derivedBaseUrl;
|
|
5761
5761
|
const staticDir = env.OMNI_STATIC_DIR?.trim();
|
|
5762
5762
|
const logFile = env.OMNI_LOG_FILE?.trim();
|
|
5763
|
-
const exposeClaudeCodeAliases = TRUTHY.has((env.OMNI_EXPOSE_CLAUDE_CODE_ALIASES ?? "").trim().toLowerCase());
|
|
5764
5763
|
const bodyLoggingAllowed = TRUTHY.has((env.OMNI_BODY_LOGGING_ALLOWED ?? "").trim().toLowerCase());
|
|
5765
5764
|
const rawLogLevel = env.OMNI_LOG_LEVEL?.trim();
|
|
5766
5765
|
const logLevel = parseLogLevel(rawLogLevel);
|
|
@@ -5773,7 +5772,6 @@ function loadConfig(env) {
|
|
|
5773
5772
|
encryptionKey,
|
|
5774
5773
|
baseUrl,
|
|
5775
5774
|
staticDir: staticDir === undefined || staticDir.length === 0 ? null : staticDir,
|
|
5776
|
-
exposeClaudeCodeAliases,
|
|
5777
5775
|
bodyLoggingAllowed,
|
|
5778
5776
|
logFile: logFile === undefined || logFile.length === 0 ? null : logFile
|
|
5779
5777
|
};
|
|
@@ -23973,6 +23971,7 @@ var targetSchema = exports_external.discriminatedUnion("provider", [
|
|
|
23973
23971
|
}),
|
|
23974
23972
|
contextWindow: exports_external.number().int().positive().optional(),
|
|
23975
23973
|
maxOutputTokens: exports_external.number().int().positive().optional(),
|
|
23974
|
+
credentialId: exports_external.string().trim().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/, "credentialId must be an account id").optional(),
|
|
23976
23975
|
capabilities: exports_external.object({
|
|
23977
23976
|
tools: exports_external.boolean(),
|
|
23978
23977
|
images: exports_external.boolean(),
|
|
@@ -23994,6 +23993,7 @@ var targetSchema = exports_external.discriminatedUnion("provider", [
|
|
|
23994
23993
|
}),
|
|
23995
23994
|
contextWindow: exports_external.number().int().positive().optional(),
|
|
23996
23995
|
maxOutputTokens: exports_external.number().int().positive().optional(),
|
|
23996
|
+
credentialId: exports_external.string().trim().min(1).max(64).regex(/^[A-Za-z0-9_-]+$/, "credentialId must be an account id").optional(),
|
|
23997
23997
|
capabilities: exports_external.object({
|
|
23998
23998
|
tools: exports_external.boolean(),
|
|
23999
23999
|
images: exports_external.boolean(),
|
|
@@ -24002,9 +24002,7 @@ var targetSchema = exports_external.discriminatedUnion("provider", [
|
|
|
24002
24002
|
}).strict()
|
|
24003
24003
|
]);
|
|
24004
24004
|
var modelSchema = exports_external.object({
|
|
24005
|
-
id: exports_external.string().min(1)
|
|
24006
|
-
message: 'model id must not start with "claude/": that prefix is reserved for discovery mirrors'
|
|
24007
|
-
}),
|
|
24005
|
+
id: exports_external.string().min(1),
|
|
24008
24006
|
strategy: exports_external.enum(["score", "priority", "roundRobin", "weighted"]),
|
|
24009
24007
|
isAlias: exports_external.boolean(),
|
|
24010
24008
|
targets: exports_external.array(targetSchema).min(1, "a virtual model needs at least one target")
|
|
@@ -24016,6 +24014,9 @@ var keyCreateSchema = exports_external.object({
|
|
|
24016
24014
|
bodyLoggingOptOut: exports_external.boolean().default(false)
|
|
24017
24015
|
}).strict();
|
|
24018
24016
|
var keyLimitsSchema = exports_external.object({ limits: limitConfigSchema }).strict();
|
|
24017
|
+
var keyModelsSchema = exports_external.object({
|
|
24018
|
+
modelAllowlist: exports_external.array(exports_external.string().min(1)).nullable()
|
|
24019
|
+
}).strict();
|
|
24019
24020
|
var retentionSchema = exports_external.object({
|
|
24020
24021
|
keepLatest: exports_external.number().int().min(1).max(100),
|
|
24021
24022
|
maxAgeDays: exports_external.number().int().min(1).max(3650)
|
|
@@ -24113,13 +24114,65 @@ async function readSource(deps, source, lines) {
|
|
|
24113
24114
|
]);
|
|
24114
24115
|
return result.code === 0 ? result.stdout : "";
|
|
24115
24116
|
}
|
|
24117
|
+
function parseConsoleLines(text, query) {
|
|
24118
|
+
const lines = consoleLimit(query.lines);
|
|
24119
|
+
return text.split(`
|
|
24120
|
+
`).filter((raw) => raw.trim().length > 0).map(parseLine).filter((line) => keep(line, query)).slice(-lines);
|
|
24121
|
+
}
|
|
24116
24122
|
async function readConsole(deps, source, query) {
|
|
24117
24123
|
const limited = { ...query, lines: consoleLimit(query.lines) };
|
|
24118
24124
|
const text = await readSource(deps, source, scanWidth(limited));
|
|
24119
|
-
const lines = text
|
|
24120
|
-
`).filter((raw) => raw.trim().length > 0).map(parseLine).filter((line) => keep(line, limited)).slice(-limited.lines);
|
|
24125
|
+
const lines = parseConsoleLines(text, limited);
|
|
24121
24126
|
return source.kind === "file" ? { source: "file", path: source.path, lines } : { source: source.kind, lines };
|
|
24122
24127
|
}
|
|
24128
|
+
// packages/store/src/types.ts
|
|
24129
|
+
var WINDOW_DURATION_MS = {
|
|
24130
|
+
fiveHour: 5 * 60 * 60 * 1000,
|
|
24131
|
+
daily: 24 * 60 * 60 * 1000,
|
|
24132
|
+
weekly: 7 * 24 * 60 * 60 * 1000
|
|
24133
|
+
};
|
|
24134
|
+
function durationFor(windowType, windowMs) {
|
|
24135
|
+
return windowMs !== null && windowMs > 0 ? windowMs : WINDOW_DURATION_MS[windowType];
|
|
24136
|
+
}
|
|
24137
|
+
var SAME_WINDOW_TOLERANCE_MS = 60000;
|
|
24138
|
+
function sameWindow(a, b) {
|
|
24139
|
+
if (a === null || b === null)
|
|
24140
|
+
return a === b;
|
|
24141
|
+
return Math.abs(a - b) <= SAME_WINDOW_TOLERANCE_MS;
|
|
24142
|
+
}
|
|
24143
|
+
var READ_OVER_INPUT = 0.1;
|
|
24144
|
+
function cacheReadRate(prices) {
|
|
24145
|
+
return prices.cacheRead ?? prices.input * READ_OVER_INPUT;
|
|
24146
|
+
}
|
|
24147
|
+
function servesTarget(target, account) {
|
|
24148
|
+
if (account.provider !== target.provider)
|
|
24149
|
+
return false;
|
|
24150
|
+
if (target.provider === "custom" && account.providerData.endpointId !== target.endpointId) {
|
|
24151
|
+
return false;
|
|
24152
|
+
}
|
|
24153
|
+
return target.credentialId === undefined || account.id === target.credentialId;
|
|
24154
|
+
}
|
|
24155
|
+
function resolvePin(target, accounts) {
|
|
24156
|
+
if (target.credentialId === undefined)
|
|
24157
|
+
return;
|
|
24158
|
+
return accounts.find((account) => servesTarget(target, account));
|
|
24159
|
+
}
|
|
24160
|
+
var DEFAULT_SETTINGS = {
|
|
24161
|
+
weights: { tier: 10, health: 3, quota: 2, load: 2, cost: 1, latency: 1 },
|
|
24162
|
+
maxAttempts: 3,
|
|
24163
|
+
requestDeadlineMs: 120000,
|
|
24164
|
+
breakerThreshold: 3,
|
|
24165
|
+
breakerCooldownMs: 30000,
|
|
24166
|
+
logRetentionDays: 30,
|
|
24167
|
+
quotaPollIntervalMs: 300000,
|
|
24168
|
+
rtkEnabled: false,
|
|
24169
|
+
autoCacheEnabled: true,
|
|
24170
|
+
bodyLoggingEnabled: false,
|
|
24171
|
+
bodyLoggingCaptureStreamChunks: false,
|
|
24172
|
+
snapshotKeepLatest: 5,
|
|
24173
|
+
snapshotMaxAgeDays: 30
|
|
24174
|
+
};
|
|
24175
|
+
|
|
24123
24176
|
// packages/router/src/snapshot.ts
|
|
24124
24177
|
var HEALTH_KEY_SEP = "::";
|
|
24125
24178
|
function healthKey(credentialId, model) {
|
|
@@ -24179,12 +24232,11 @@ function eligible(input) {
|
|
|
24179
24232
|
const excluded = [];
|
|
24180
24233
|
for (const target of model.targets) {
|
|
24181
24234
|
const missing = needNative && !ANTHROPIC_NATIVE_TOOLS[target.provider] ? "anthropicTools" : ["tools", "images", "reasoning"].find((cap) => need[cap] && !target.capabilities[cap]);
|
|
24235
|
+
let pinSeen = false;
|
|
24182
24236
|
for (const credential of snapshot.credentials) {
|
|
24183
|
-
if (credential
|
|
24237
|
+
if (!servesTarget(target, credential))
|
|
24184
24238
|
continue;
|
|
24185
|
-
|
|
24186
|
-
continue;
|
|
24187
|
-
}
|
|
24239
|
+
pinSeen = target.credentialId !== undefined;
|
|
24188
24240
|
const drop = (reason) => {
|
|
24189
24241
|
excluded.push({ credentialId: credential.id, model: target.model, reason });
|
|
24190
24242
|
};
|
|
@@ -24221,45 +24273,17 @@ function eligible(input) {
|
|
|
24221
24273
|
}
|
|
24222
24274
|
pairs.push({ credential, target });
|
|
24223
24275
|
}
|
|
24276
|
+
if (target.credentialId !== undefined && !pinSeen) {
|
|
24277
|
+
excluded.push({
|
|
24278
|
+
credentialId: target.credentialId,
|
|
24279
|
+
model: target.model,
|
|
24280
|
+
reason: "pin:missing"
|
|
24281
|
+
});
|
|
24282
|
+
}
|
|
24224
24283
|
}
|
|
24225
24284
|
return { pairs, excluded };
|
|
24226
24285
|
}
|
|
24227
24286
|
|
|
24228
|
-
// packages/store/src/types.ts
|
|
24229
|
-
var WINDOW_DURATION_MS = {
|
|
24230
|
-
fiveHour: 5 * 60 * 60 * 1000,
|
|
24231
|
-
daily: 24 * 60 * 60 * 1000,
|
|
24232
|
-
weekly: 7 * 24 * 60 * 60 * 1000
|
|
24233
|
-
};
|
|
24234
|
-
function durationFor(windowType, windowMs) {
|
|
24235
|
-
return windowMs !== null && windowMs > 0 ? windowMs : WINDOW_DURATION_MS[windowType];
|
|
24236
|
-
}
|
|
24237
|
-
var SAME_WINDOW_TOLERANCE_MS = 60000;
|
|
24238
|
-
function sameWindow(a, b) {
|
|
24239
|
-
if (a === null || b === null)
|
|
24240
|
-
return a === b;
|
|
24241
|
-
return Math.abs(a - b) <= SAME_WINDOW_TOLERANCE_MS;
|
|
24242
|
-
}
|
|
24243
|
-
var READ_OVER_INPUT = 0.1;
|
|
24244
|
-
function cacheReadRate(prices) {
|
|
24245
|
-
return prices.cacheRead ?? prices.input * READ_OVER_INPUT;
|
|
24246
|
-
}
|
|
24247
|
-
var DEFAULT_SETTINGS = {
|
|
24248
|
-
weights: { tier: 10, health: 3, quota: 2, load: 2, cost: 1, latency: 1 },
|
|
24249
|
-
maxAttempts: 3,
|
|
24250
|
-
requestDeadlineMs: 120000,
|
|
24251
|
-
breakerThreshold: 3,
|
|
24252
|
-
breakerCooldownMs: 30000,
|
|
24253
|
-
logRetentionDays: 30,
|
|
24254
|
-
quotaPollIntervalMs: 300000,
|
|
24255
|
-
rtkEnabled: false,
|
|
24256
|
-
autoCacheEnabled: true,
|
|
24257
|
-
bodyLoggingEnabled: false,
|
|
24258
|
-
bodyLoggingCaptureStreamChunks: false,
|
|
24259
|
-
snapshotKeepLatest: 5,
|
|
24260
|
-
snapshotMaxAgeDays: 30
|
|
24261
|
-
};
|
|
24262
|
-
|
|
24263
24287
|
// packages/router/src/quota.ts
|
|
24264
24288
|
var UNKNOWN_QUOTA = 0.5;
|
|
24265
24289
|
var QUOTA_FLOOR = 0.1;
|
|
@@ -26055,6 +26079,12 @@ function createKeyRepo(db, logger2 = noopLogger) {
|
|
|
26055
26079
|
id
|
|
26056
26080
|
]);
|
|
26057
26081
|
},
|
|
26082
|
+
async setModelAllowlist(id, modelAllowlist) {
|
|
26083
|
+
db.run("UPDATE api_keys SET model_allowlist = ? WHERE id = ?", [
|
|
26084
|
+
modelAllowlist === null ? null : JSON.stringify(modelAllowlist),
|
|
26085
|
+
id
|
|
26086
|
+
]);
|
|
26087
|
+
},
|
|
26058
26088
|
async revoke(id) {
|
|
26059
26089
|
db.run("UPDATE api_keys SET revoked_at = ? WHERE id = ?", [Date.now(), id]);
|
|
26060
26090
|
}
|
|
@@ -26683,6 +26713,7 @@ async function createStore(opts) {
|
|
|
26683
26713
|
findByHash: (hash3) => handle.keys.findByHash(hash3),
|
|
26684
26714
|
create: (input) => handle.keys.create(input),
|
|
26685
26715
|
setLimits: (id, limits) => handle.keys.setLimits(id, limits),
|
|
26716
|
+
setModelAllowlist: (id, modelAllowlist) => handle.keys.setModelAllowlist(id, modelAllowlist),
|
|
26686
26717
|
revoke: (id) => handle.keys.revoke(id)
|
|
26687
26718
|
},
|
|
26688
26719
|
usage: {
|
|
@@ -27352,6 +27383,14 @@ async function setKeyLimits(store, id, input, now = Date.now()) {
|
|
|
27352
27383
|
await store.keys.setLimits(id, body2.limits);
|
|
27353
27384
|
return toSummary(store, { ...key, limits: body2.limits }, now);
|
|
27354
27385
|
}
|
|
27386
|
+
async function setKeyModels(store, id, input, now = Date.now()) {
|
|
27387
|
+
const body2 = parseOrThrow(keyModelsSchema, input);
|
|
27388
|
+
const key = (await store.keys.list()).find((entry) => entry.id === id);
|
|
27389
|
+
if (key === undefined)
|
|
27390
|
+
throw new GatewayError("BAD_REQUEST", "no such api key");
|
|
27391
|
+
await store.keys.setModelAllowlist(id, body2.modelAllowlist);
|
|
27392
|
+
return toSummary(store, { ...key, modelAllowlist: body2.modelAllowlist }, now);
|
|
27393
|
+
}
|
|
27355
27394
|
async function revokeKey(store, id) {
|
|
27356
27395
|
await store.keys.revoke(id);
|
|
27357
27396
|
}
|
|
@@ -27412,8 +27451,8 @@ function narrower(a, b) {
|
|
|
27412
27451
|
...output.length === 0 ? {} : { maxOutputTokens: Math.min(...output) }
|
|
27413
27452
|
};
|
|
27414
27453
|
}
|
|
27415
|
-
function targetLimits(target, auths) {
|
|
27416
|
-
const ways = auths.size === 0 ? ["apiKey"] : [...auths];
|
|
27454
|
+
function targetLimits(target, auths, pinned) {
|
|
27455
|
+
const ways = pinned !== undefined ? [pinned.authType] : auths.size === 0 ? ["apiKey"] : [...auths];
|
|
27417
27456
|
let listed = {};
|
|
27418
27457
|
for (const auth of ways) {
|
|
27419
27458
|
const entry = catalogLimits(target.provider, target.model, auth);
|
|
@@ -27433,20 +27472,22 @@ function targetLimits(target, auths) {
|
|
|
27433
27472
|
}
|
|
27434
27473
|
function servingAuths(credentials) {
|
|
27435
27474
|
const byProvider = new Map;
|
|
27475
|
+
const routable = [];
|
|
27436
27476
|
for (const credential of credentials) {
|
|
27437
27477
|
if (!credential.enabled)
|
|
27438
27478
|
continue;
|
|
27439
27479
|
const ways = byProvider.get(credential.provider) ?? new Set;
|
|
27440
27480
|
ways.add(credential.authType);
|
|
27441
27481
|
byProvider.set(credential.provider, ways);
|
|
27482
|
+
routable.push(credential);
|
|
27442
27483
|
}
|
|
27443
|
-
return byProvider;
|
|
27484
|
+
return { byProvider, routable };
|
|
27444
27485
|
}
|
|
27445
27486
|
function resolveModelLimits(model, credentials) {
|
|
27446
|
-
const
|
|
27487
|
+
const { byProvider, routable } = servingAuths(credentials);
|
|
27447
27488
|
let limits = {};
|
|
27448
27489
|
for (const target of model.targets) {
|
|
27449
|
-
limits = narrower(limits, targetLimits(target,
|
|
27490
|
+
limits = narrower(limits, targetLimits(target, byProvider.get(target.provider) ?? new Set, resolvePin(target, routable)));
|
|
27450
27491
|
}
|
|
27451
27492
|
return limits;
|
|
27452
27493
|
}
|
|
@@ -27484,15 +27525,18 @@ function unreachable(model, credentials, stored) {
|
|
|
27484
27525
|
const held = heldAuths(credentials);
|
|
27485
27526
|
const grandfathered = new Set((stored?.targets ?? []).map(pairOf));
|
|
27486
27527
|
for (const target of model.targets) {
|
|
27487
|
-
|
|
27528
|
+
const pinned = resolvePin(target, credentials);
|
|
27529
|
+
if (pinned === undefined && grandfathered.has(pairOf(target)))
|
|
27488
27530
|
continue;
|
|
27489
|
-
const have = held.get(target.provider);
|
|
27531
|
+
const have = pinned === undefined ? held.get(target.provider) : new Set([pinned.authType]);
|
|
27490
27532
|
if (have === undefined)
|
|
27491
27533
|
continue;
|
|
27492
27534
|
const reach = catalogModelAuths(target.provider, target.model);
|
|
27493
27535
|
if (reach.some((auth) => have.has(auth)))
|
|
27494
27536
|
continue;
|
|
27495
|
-
|
|
27537
|
+
const holds = pinned === undefined ? `every ${target.provider} credential here is ${phrase([...have])}` : `this target is pinned to "${pinned.label}", which is ${phrase([...have])}`;
|
|
27538
|
+
const fix = pinned === undefined ? "connect the other kind, or pick a model this one can reach" : "pin it to the other kind, or pick a model this account can reach";
|
|
27539
|
+
return new GatewayError("BAD_REQUEST", `${target.provider} serves "${target.model}" to ${phrase(reach)} credentials only, ` + `and ${holds} \u2014 ${fix}`);
|
|
27496
27540
|
}
|
|
27497
27541
|
return null;
|
|
27498
27542
|
}
|
|
@@ -27532,14 +27576,15 @@ import { join as join3 } from "path";
|
|
|
27532
27576
|
|
|
27533
27577
|
// packages/plugin-api/src/version.ts
|
|
27534
27578
|
var PLUGIN_API_VERSION = 1;
|
|
27535
|
-
var DASHBOARD_SDK_VERSION = "0.1.
|
|
27579
|
+
var DASHBOARD_SDK_VERSION = "0.1.2";
|
|
27536
27580
|
// packages/plugin-api/src/manifest.ts
|
|
27537
27581
|
var CAPABILITIES = [
|
|
27538
27582
|
"storage",
|
|
27539
27583
|
"files",
|
|
27540
27584
|
"net:outbound",
|
|
27541
27585
|
"events:request",
|
|
27542
|
-
"events:limit"
|
|
27586
|
+
"events:limit",
|
|
27587
|
+
"channels"
|
|
27543
27588
|
];
|
|
27544
27589
|
var ID_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
|
|
27545
27590
|
var idSchema = exports_external.string().regex(ID_PATTERN, "id must match /^[a-z][a-z0-9-]{0,31}$/");
|
|
@@ -28713,9 +28758,11 @@ var KEY_PLACEHOLDER = "<your OmniGateway key>";
|
|
|
28713
28758
|
async function describeModelsForSetup(store) {
|
|
28714
28759
|
const models = await listModels(store);
|
|
28715
28760
|
const credentials = (await listCredentials(store)).map((credential) => ({
|
|
28761
|
+
id: credential.id,
|
|
28716
28762
|
provider: credential.provider,
|
|
28717
28763
|
authType: credential.authType,
|
|
28718
|
-
enabled: credential.enabled
|
|
28764
|
+
enabled: credential.enabled,
|
|
28765
|
+
providerData: credential.providerData
|
|
28719
28766
|
}));
|
|
28720
28767
|
return models.map((model) => ({
|
|
28721
28768
|
model,
|
|
@@ -28751,8 +28798,7 @@ function claudeSettings(described, input, mapping, existing) {
|
|
|
28751
28798
|
const visibleId = (slot, id) => {
|
|
28752
28799
|
if (!ids.has(id))
|
|
28753
28800
|
throw new Error(`${slot} names unknown virtual model "${id}"`);
|
|
28754
|
-
|
|
28755
|
-
return useMirror ? `claude/${id}` : id;
|
|
28801
|
+
return id;
|
|
28756
28802
|
};
|
|
28757
28803
|
const settings = settingsObject(existing);
|
|
28758
28804
|
const currentEnv = settings.env;
|
|
@@ -28861,6 +28907,43 @@ function tailFile(path, lines) {
|
|
|
28861
28907
|
closeSync(fd);
|
|
28862
28908
|
}
|
|
28863
28909
|
}
|
|
28910
|
+
function fileSize(path) {
|
|
28911
|
+
try {
|
|
28912
|
+
const stat = statSync2(path);
|
|
28913
|
+
return stat.isFile() ? stat.size : null;
|
|
28914
|
+
} catch {
|
|
28915
|
+
return null;
|
|
28916
|
+
}
|
|
28917
|
+
}
|
|
28918
|
+
function readFrom(path, offset) {
|
|
28919
|
+
let fd;
|
|
28920
|
+
try {
|
|
28921
|
+
fd = openSync(path, "r");
|
|
28922
|
+
} catch {
|
|
28923
|
+
return null;
|
|
28924
|
+
}
|
|
28925
|
+
try {
|
|
28926
|
+
const size = fstatSync(fd).size;
|
|
28927
|
+
const shrank = size < offset;
|
|
28928
|
+
const from = shrank ? 0 : offset;
|
|
28929
|
+
const available = size - from;
|
|
28930
|
+
if (available <= 0)
|
|
28931
|
+
return { text: "", offset: size, gap: shrank };
|
|
28932
|
+
const span = Math.min(available, MAX_BYTES);
|
|
28933
|
+
const start = from + (available - span);
|
|
28934
|
+
const buffer = Buffer.allocUnsafe(span);
|
|
28935
|
+
const read = readSync(fd, buffer, 0, span, start);
|
|
28936
|
+
return {
|
|
28937
|
+
text: buffer.subarray(0, read).toString("utf8"),
|
|
28938
|
+
offset: start + read,
|
|
28939
|
+
gap: shrank || span < available
|
|
28940
|
+
};
|
|
28941
|
+
} catch {
|
|
28942
|
+
return null;
|
|
28943
|
+
} finally {
|
|
28944
|
+
closeSync(fd);
|
|
28945
|
+
}
|
|
28946
|
+
}
|
|
28864
28947
|
// packages/control/src/usage.ts
|
|
28865
28948
|
var MAX_LOG_LIMIT = 500;
|
|
28866
28949
|
var DEFAULT_LOG_LIMIT = 100;
|
|
@@ -44472,6 +44555,7 @@ function createQuiesceLatch() {
|
|
|
44472
44555
|
// apps/gateway/src/routes/admin.ts
|
|
44473
44556
|
function adminRoutes(deps) {
|
|
44474
44557
|
const logger2 = deps.logger ?? noopLogger;
|
|
44558
|
+
const changed = (topic) => deps.broadcaster?.invalidate(topic);
|
|
44475
44559
|
return new Elysia().onError(apiErrorHandler).get("/api/status", async ({ request: request2 }) => {
|
|
44476
44560
|
const token = readCookie(request2, ADMIN_COOKIE);
|
|
44477
44561
|
return {
|
|
@@ -44531,9 +44615,9 @@ function adminRoutes(deps) {
|
|
|
44531
44615
|
const body2 = await readJsonRecord(request2);
|
|
44532
44616
|
if (body2 === null)
|
|
44533
44617
|
throw new GatewayError("BAD_REQUEST", "credential body is required");
|
|
44534
|
-
|
|
44535
|
-
|
|
44536
|
-
};
|
|
44618
|
+
const credential = await createApiKeyCredential(deps.store, { provider: body2.provider, apiKey: body2.apiKey, ...body2 }, logger2);
|
|
44619
|
+
changed("res:credentials");
|
|
44620
|
+
return { credential };
|
|
44537
44621
|
}).get("/api/credentials/health", async ({ request: request2 }) => {
|
|
44538
44622
|
await requireAdmin(request2, deps.admin);
|
|
44539
44623
|
return credentialHealth(deps);
|
|
@@ -44547,10 +44631,12 @@ function adminRoutes(deps) {
|
|
|
44547
44631
|
}).patch("/api/credentials/:id", async ({ request: request2, params }) => {
|
|
44548
44632
|
await requireAdmin(request2, deps.admin);
|
|
44549
44633
|
await patchCredential(deps, params.id, await readJson(request2));
|
|
44634
|
+
changed("res:credentials");
|
|
44550
44635
|
return { ok: true };
|
|
44551
44636
|
}).delete("/api/credentials/:id", async ({ request: request2, params }) => {
|
|
44552
44637
|
await requireAdmin(request2, deps.admin);
|
|
44553
44638
|
await removeCredential(deps.store, params.id);
|
|
44639
|
+
changed("res:credentials");
|
|
44554
44640
|
return { ok: true };
|
|
44555
44641
|
}).get("/api/models", async ({ request: request2 }) => {
|
|
44556
44642
|
await requireAdmin(request2, deps.admin);
|
|
@@ -44558,10 +44644,12 @@ function adminRoutes(deps) {
|
|
|
44558
44644
|
}).put("/api/models/:id", async ({ request: request2, params }) => {
|
|
44559
44645
|
await requireAdmin(request2, deps.admin);
|
|
44560
44646
|
await putModel(deps.store, params.id, await readJson(request2));
|
|
44647
|
+
changed("res:models");
|
|
44561
44648
|
return { ok: true };
|
|
44562
44649
|
}).delete("/api/models/:id", async ({ request: request2, params }) => {
|
|
44563
44650
|
await requireAdmin(request2, deps.admin);
|
|
44564
44651
|
await removeModel(deps.store, params.id);
|
|
44652
|
+
changed("res:models");
|
|
44565
44653
|
return { ok: true };
|
|
44566
44654
|
}).get("/api/agent-setup", async ({ request: request2, query }) => {
|
|
44567
44655
|
await requireAdmin(request2, deps.admin);
|
|
@@ -44573,10 +44661,7 @@ function adminRoutes(deps) {
|
|
|
44573
44661
|
try {
|
|
44574
44662
|
return {
|
|
44575
44663
|
client,
|
|
44576
|
-
files: await setupFiles(deps.store, client, {
|
|
44577
|
-
baseUrl: deps.baseUrl,
|
|
44578
|
-
discoveryMirrors: deps.discoveryMirrors === true
|
|
44579
|
-
}, {
|
|
44664
|
+
files: await setupFiles(deps.store, client, { baseUrl: deps.baseUrl }, {
|
|
44580
44665
|
defaultModel,
|
|
44581
44666
|
...query.fableModel ? { fableModel: query.fableModel } : {},
|
|
44582
44667
|
...query.opusModel ? { opusModel: query.opusModel } : {},
|
|
@@ -44592,13 +44677,23 @@ function adminRoutes(deps) {
|
|
|
44592
44677
|
return { keys: await listKeys(deps.store) };
|
|
44593
44678
|
}).post("/api/keys", async ({ request: request2 }) => {
|
|
44594
44679
|
await requireAdmin(request2, deps.admin);
|
|
44595
|
-
|
|
44680
|
+
const created = await createKey(deps.store, await readJson(request2));
|
|
44681
|
+
changed("res:keys");
|
|
44682
|
+
return created;
|
|
44596
44683
|
}).put("/api/keys/:id/limits", async ({ request: request2, params }) => {
|
|
44597
44684
|
await requireAdmin(request2, deps.admin);
|
|
44598
|
-
|
|
44685
|
+
const key = await setKeyLimits(deps.store, params.id, await readJson(request2));
|
|
44686
|
+
changed("res:keys");
|
|
44687
|
+
return key;
|
|
44688
|
+
}).put("/api/keys/:id/models", async ({ request: request2, params }) => {
|
|
44689
|
+
await requireAdmin(request2, deps.admin);
|
|
44690
|
+
const key = await setKeyModels(deps.store, params.id, await readJson(request2));
|
|
44691
|
+
changed("res:keys");
|
|
44692
|
+
return key;
|
|
44599
44693
|
}).delete("/api/keys/:id", async ({ request: request2, params }) => {
|
|
44600
44694
|
await requireAdmin(request2, deps.admin);
|
|
44601
44695
|
await revokeKey(deps.store, params.id);
|
|
44696
|
+
changed("res:keys");
|
|
44602
44697
|
return { ok: true };
|
|
44603
44698
|
}).get("/api/settings", async ({ request: request2 }) => {
|
|
44604
44699
|
await requireAdmin(request2, deps.admin);
|
|
@@ -44609,6 +44704,7 @@ function adminRoutes(deps) {
|
|
|
44609
44704
|
}).put("/api/settings", async ({ request: request2 }) => {
|
|
44610
44705
|
await requireAdmin(request2, deps.admin);
|
|
44611
44706
|
await putSettings(deps.store, await readJson(request2));
|
|
44707
|
+
changed("res:settings");
|
|
44612
44708
|
return { ok: true };
|
|
44613
44709
|
}).post("/api/models/:id/dry-run", async ({ request: request2, params }) => {
|
|
44614
44710
|
await requireAdmin(request2, deps.admin);
|
|
@@ -44693,6 +44789,7 @@ async function swap(deps, logger2, label2, operation) {
|
|
|
44693
44789
|
reason: label2
|
|
44694
44790
|
});
|
|
44695
44791
|
}
|
|
44792
|
+
deps.broadcaster?.invalidateAll();
|
|
44696
44793
|
return result;
|
|
44697
44794
|
} catch (error51) {
|
|
44698
44795
|
if (error51 instanceof SwapFailedError) {
|
|
@@ -46857,24 +46954,44 @@ function report(logger2, what, requestId, error51) {
|
|
|
46857
46954
|
reason: describeError(error51, "unknown")
|
|
46858
46955
|
});
|
|
46859
46956
|
}
|
|
46860
|
-
async function beginLog(store, log, keyId, logger2 = noopLogger) {
|
|
46957
|
+
async function beginLog(store, log, keyId, logger2 = noopLogger, broadcast) {
|
|
46958
|
+
let began = false;
|
|
46861
46959
|
try {
|
|
46862
46960
|
await store.usage.begin({ ...log, state: "pending", apiKeyId: keyId });
|
|
46961
|
+
began = true;
|
|
46863
46962
|
} catch (error51) {
|
|
46864
46963
|
report(logger2, "failed to record request start", log.id, error51);
|
|
46865
46964
|
}
|
|
46965
|
+
if (began && broadcast !== undefined) {
|
|
46966
|
+
try {
|
|
46967
|
+
broadcast.invalidate("res:logs");
|
|
46968
|
+
} catch (error51) {
|
|
46969
|
+
report(logger2, "failed to publish a resource invalidation", log.id, error51);
|
|
46970
|
+
}
|
|
46971
|
+
}
|
|
46866
46972
|
}
|
|
46867
|
-
async function routeLog(store, requestId, target, logger2 = noopLogger) {
|
|
46973
|
+
async function routeLog(store, requestId, target, logger2 = noopLogger, broadcast) {
|
|
46974
|
+
let routed = false;
|
|
46868
46975
|
try {
|
|
46869
46976
|
await store.usage.route(requestId, target);
|
|
46977
|
+
routed = true;
|
|
46870
46978
|
} catch (error51) {
|
|
46871
46979
|
report(logger2, "failed to record request route", requestId, error51);
|
|
46872
46980
|
}
|
|
46981
|
+
if (routed && broadcast !== undefined) {
|
|
46982
|
+
try {
|
|
46983
|
+
broadcast.invalidate("res:logs");
|
|
46984
|
+
} catch (error51) {
|
|
46985
|
+
report(logger2, "failed to publish a resource invalidation", requestId, error51);
|
|
46986
|
+
}
|
|
46987
|
+
}
|
|
46873
46988
|
}
|
|
46874
|
-
async function finishLog(store, log, keyId, logger2 = noopLogger, bodies, debit, emit) {
|
|
46989
|
+
async function finishLog(store, log, keyId, logger2 = noopLogger, bodies, debit, emit, broadcast) {
|
|
46990
|
+
let appended = true;
|
|
46875
46991
|
try {
|
|
46876
46992
|
await store.usage.append({ ...log, apiKeyId: keyId });
|
|
46877
46993
|
} catch (error51) {
|
|
46994
|
+
appended = false;
|
|
46878
46995
|
report(logger2, "failed to persist request log", log.id, error51);
|
|
46879
46996
|
}
|
|
46880
46997
|
if (keyId !== null && debit !== undefined) {
|
|
@@ -46905,6 +47022,14 @@ async function finishLog(store, log, keyId, logger2 = noopLogger, bodies, debit,
|
|
|
46905
47022
|
report(logger2, "failed to emit plugin event", log.id, error51);
|
|
46906
47023
|
}
|
|
46907
47024
|
}
|
|
47025
|
+
if (appended && broadcast !== undefined) {
|
|
47026
|
+
try {
|
|
47027
|
+
broadcast.invalidate("res:usage");
|
|
47028
|
+
broadcast.invalidate("res:logs");
|
|
47029
|
+
} catch (error51) {
|
|
47030
|
+
report(logger2, "failed to publish a resource invalidation", log.id, error51);
|
|
47031
|
+
}
|
|
47032
|
+
}
|
|
46908
47033
|
if (bodies === undefined)
|
|
46909
47034
|
return;
|
|
46910
47035
|
try {
|
|
@@ -47631,7 +47756,6 @@ function mcpServerNames(body2) {
|
|
|
47631
47756
|
}
|
|
47632
47757
|
|
|
47633
47758
|
// apps/gateway/src/ingress/model.ts
|
|
47634
|
-
var DISCOVERY_PREFIX = "claude/";
|
|
47635
47759
|
var ONE_M_SUFFIX2 = "[1m]";
|
|
47636
47760
|
function normalizeClientModel(raw, betas2 = []) {
|
|
47637
47761
|
let model = raw.trim();
|
|
@@ -47643,11 +47767,6 @@ function normalizeClientModel(raw, betas2 = []) {
|
|
|
47643
47767
|
wantsOneM = true;
|
|
47644
47768
|
}
|
|
47645
47769
|
}
|
|
47646
|
-
if (model.toLowerCase().startsWith(DISCOVERY_PREFIX)) {
|
|
47647
|
-
const stripped = model.slice(DISCOVERY_PREFIX.length);
|
|
47648
|
-
if (stripped.length > 0)
|
|
47649
|
-
model = stripped;
|
|
47650
|
-
}
|
|
47651
47770
|
const merged = [...betas2];
|
|
47652
47771
|
if (wantsOneM && !merged.includes(CONTEXT_1M_BETA))
|
|
47653
47772
|
merged.push(CONTEXT_1M_BETA);
|
|
@@ -48294,29 +48413,8 @@ function describeModel(model, credentials) {
|
|
|
48294
48413
|
...limits.maxOutputTokens === undefined ? {} : { max_tokens: limits.maxOutputTokens }
|
|
48295
48414
|
};
|
|
48296
48415
|
}
|
|
48297
|
-
|
|
48298
|
-
|
|
48299
|
-
function discoveryMirrors(data) {
|
|
48300
|
-
const taken = new Set(data.map((entry) => entry.id));
|
|
48301
|
-
const mirrors = [];
|
|
48302
|
-
for (const entry of data) {
|
|
48303
|
-
if (ALREADY_CLAUDE.test(entry.id))
|
|
48304
|
-
continue;
|
|
48305
|
-
const id = `${MIRROR_PREFIX}${entry.id}`;
|
|
48306
|
-
if (taken.has(id))
|
|
48307
|
-
continue;
|
|
48308
|
-
mirrors.push({
|
|
48309
|
-
...entry,
|
|
48310
|
-
id,
|
|
48311
|
-
root: entry.id,
|
|
48312
|
-
display_name: `${entry.display_name} (OmniGateway)`
|
|
48313
|
-
});
|
|
48314
|
-
}
|
|
48315
|
-
return mirrors;
|
|
48316
|
-
}
|
|
48317
|
-
function modelListBody(models, credentials, options = {}) {
|
|
48318
|
-
const described = models.map((model) => describeModel(model, credentials));
|
|
48319
|
-
const data = options.discoveryMirrors === true ? [...described, ...discoveryMirrors(described)] : described;
|
|
48416
|
+
function modelListBody(models, credentials) {
|
|
48417
|
+
const data = models.map((model) => describeModel(model, credentials));
|
|
48320
48418
|
return {
|
|
48321
48419
|
object: "list",
|
|
48322
48420
|
data,
|
|
@@ -48516,7 +48614,7 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
48516
48614
|
...captured === null ? {} : { http: captured.wrap(deps.http) },
|
|
48517
48615
|
async onRoute(target) {
|
|
48518
48616
|
if (began) {
|
|
48519
|
-
await routeLog(deps.store, requestId, target, deps.logger);
|
|
48617
|
+
await routeLog(deps.store, requestId, target, deps.logger, deps.broadcaster);
|
|
48520
48618
|
return;
|
|
48521
48619
|
}
|
|
48522
48620
|
began = true;
|
|
@@ -48528,7 +48626,7 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
48528
48626
|
resolvedModel: target.model,
|
|
48529
48627
|
credentialId: target.credentialId
|
|
48530
48628
|
});
|
|
48531
|
-
await beginLog(deps.store, log2, keyId, deps.logger);
|
|
48629
|
+
await beginLog(deps.store, log2, keyId, deps.logger, deps.broadcaster);
|
|
48532
48630
|
}
|
|
48533
48631
|
}, request2.signal, requestId);
|
|
48534
48632
|
outcome = dispatched;
|
|
@@ -48551,7 +48649,7 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
48551
48649
|
reportRejection(deps.logger, requestId, completed, gatewayError, surface);
|
|
48552
48650
|
}
|
|
48553
48651
|
logged = true;
|
|
48554
|
-
await finishLog(deps.store, completed, keyId, deps.logger, writeBodies, debit, deps.emit);
|
|
48652
|
+
await finishLog(deps.store, completed, keyId, deps.logger, writeBodies, debit, deps.emit, deps.broadcaster);
|
|
48555
48653
|
};
|
|
48556
48654
|
if (chatRequest.stream) {
|
|
48557
48655
|
const frames = surface === "anthropic" ? anthropicStream(dispatched.events, requestId) : openaiStream(dispatched.events, requestId, Math.floor(deps.now() / 1000));
|
|
@@ -48592,7 +48690,7 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
48592
48690
|
if (collector !== null && !logged)
|
|
48593
48691
|
collector.client.response = rejection;
|
|
48594
48692
|
if (!logged)
|
|
48595
|
-
await finishLog(deps.store, completed, keyId, deps.logger, writeBodies, debit, deps.emit);
|
|
48693
|
+
await finishLog(deps.store, completed, keyId, deps.logger, writeBodies, debit, deps.emit, deps.broadcaster);
|
|
48596
48694
|
if (!cancelled)
|
|
48597
48695
|
reportRejection(deps.logger, requestId, completed, gatewayError, surface);
|
|
48598
48696
|
return jsonResponse(rejection, HTTP_STATUS[gatewayError.code], {
|
|
@@ -48626,9 +48724,7 @@ function proxyRoutes(deps) {
|
|
|
48626
48724
|
const snapshot = await dispatchDeps.snapshots.get(deps.now());
|
|
48627
48725
|
const models = [...snapshot.models.values()];
|
|
48628
48726
|
const visibleModels = key.modelAllowlist === null ? models : models.filter((model) => key.modelAllowlist?.includes(model.id));
|
|
48629
|
-
return Response.json(modelListBody(visibleModels, snapshot.credentials
|
|
48630
|
-
discoveryMirrors: deps.discoveryMirrors === true
|
|
48631
|
-
}));
|
|
48727
|
+
return Response.json(modelListBody(visibleModels, snapshot.credentials));
|
|
48632
48728
|
} catch (error51) {
|
|
48633
48729
|
const gatewayError = asGatewayError(error51);
|
|
48634
48730
|
logger2.error("model listing failed", {
|
|
@@ -48668,6 +48764,684 @@ function proxyRoutes(deps) {
|
|
|
48668
48764
|
});
|
|
48669
48765
|
}
|
|
48670
48766
|
|
|
48767
|
+
// apps/gateway/src/stream/broadcaster.ts
|
|
48768
|
+
var INVALIDATION_FLOORS = {
|
|
48769
|
+
"res:usage": 1000,
|
|
48770
|
+
"res:logs": 1000,
|
|
48771
|
+
"res:quota": 5000,
|
|
48772
|
+
"res:credentials": 5000
|
|
48773
|
+
};
|
|
48774
|
+
var DEFAULT_FLOOR_MS = 1000;
|
|
48775
|
+
var GLOBAL_INVALIDATE = "res:*";
|
|
48776
|
+
function createBroadcaster(deps) {
|
|
48777
|
+
const streams = new Set;
|
|
48778
|
+
return {
|
|
48779
|
+
invalidate(topic, keys) {
|
|
48780
|
+
deps.coalescer.emit(topic, keys === undefined ? undefined : { keys });
|
|
48781
|
+
},
|
|
48782
|
+
invalidateAll() {
|
|
48783
|
+
deps.registry.publish(GLOBAL_INVALIDATE, {
|
|
48784
|
+
type: "event",
|
|
48785
|
+
topic: GLOBAL_INVALIDATE
|
|
48786
|
+
});
|
|
48787
|
+
},
|
|
48788
|
+
declareStream(topic) {
|
|
48789
|
+
streams.add(topic);
|
|
48790
|
+
},
|
|
48791
|
+
declared(topic) {
|
|
48792
|
+
return streams.has(topic);
|
|
48793
|
+
},
|
|
48794
|
+
stream(topic, payload) {
|
|
48795
|
+
const seq = deps.ring.push(topic, payload);
|
|
48796
|
+
deps.registry.publish(topic, { type: "event", topic, seq, payload });
|
|
48797
|
+
},
|
|
48798
|
+
resetStream(topic) {
|
|
48799
|
+
deps.ring.reset(topic);
|
|
48800
|
+
},
|
|
48801
|
+
stop() {
|
|
48802
|
+
deps.coalescer.stop();
|
|
48803
|
+
}
|
|
48804
|
+
};
|
|
48805
|
+
}
|
|
48806
|
+
|
|
48807
|
+
// apps/gateway/src/stream/protocol.ts
|
|
48808
|
+
function topicClass(topic) {
|
|
48809
|
+
if (topic.startsWith("res:") && topic.length > 4)
|
|
48810
|
+
return "res";
|
|
48811
|
+
if (topic.startsWith("stream:") && topic.length > 7)
|
|
48812
|
+
return "stream";
|
|
48813
|
+
if (topic.startsWith("plugin:")) {
|
|
48814
|
+
const rest = topic.slice(7);
|
|
48815
|
+
const colon = rest.indexOf(":");
|
|
48816
|
+
return colon > 0 && colon < rest.length - 1 ? "plugin" : null;
|
|
48817
|
+
}
|
|
48818
|
+
return null;
|
|
48819
|
+
}
|
|
48820
|
+
var MAX_TOPIC = 128;
|
|
48821
|
+
function isRecord7(value) {
|
|
48822
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
48823
|
+
}
|
|
48824
|
+
function parseClientFrame(raw) {
|
|
48825
|
+
if (!isRecord7(raw))
|
|
48826
|
+
return null;
|
|
48827
|
+
const { id, type, topic } = raw;
|
|
48828
|
+
if (id !== undefined && typeof id !== "string")
|
|
48829
|
+
return null;
|
|
48830
|
+
if (typeof topic !== "string" || topic.length === 0 || topic.length > MAX_TOPIC)
|
|
48831
|
+
return null;
|
|
48832
|
+
if (topicClass(topic) === null)
|
|
48833
|
+
return null;
|
|
48834
|
+
const head = id === undefined ? { topic } : { id, topic };
|
|
48835
|
+
if (type === "unsubscribe")
|
|
48836
|
+
return { ...head, type: "unsubscribe" };
|
|
48837
|
+
if (type === "send") {
|
|
48838
|
+
return "payload" in raw ? { ...head, type: "send", payload: raw.payload } : { ...head, type: "send" };
|
|
48839
|
+
}
|
|
48840
|
+
if (type === "subscribe") {
|
|
48841
|
+
const { sinceSeq } = raw;
|
|
48842
|
+
if (sinceSeq === undefined)
|
|
48843
|
+
return { ...head, type: "subscribe" };
|
|
48844
|
+
if (typeof sinceSeq !== "number" || !Number.isInteger(sinceSeq) || sinceSeq < 0)
|
|
48845
|
+
return null;
|
|
48846
|
+
return { ...head, type: "subscribe", sinceSeq };
|
|
48847
|
+
}
|
|
48848
|
+
return null;
|
|
48849
|
+
}
|
|
48850
|
+
|
|
48851
|
+
// apps/gateway/src/routes/stream.ts
|
|
48852
|
+
function authorised(channels, principal, topic) {
|
|
48853
|
+
const kind = topicClass(topic);
|
|
48854
|
+
if (kind === null)
|
|
48855
|
+
return false;
|
|
48856
|
+
if (kind === "plugin") {
|
|
48857
|
+
if (!channels.opened(topic))
|
|
48858
|
+
return false;
|
|
48859
|
+
if (principal.kind === "admin")
|
|
48860
|
+
return true;
|
|
48861
|
+
return topic.startsWith(`plugin:${principal.pluginId}:`);
|
|
48862
|
+
}
|
|
48863
|
+
return principal.kind === "admin";
|
|
48864
|
+
}
|
|
48865
|
+
function streamRoutes(deps) {
|
|
48866
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
48867
|
+
const nextId = deps.connectionId ?? (() => `ws_${crypto.randomUUID()}`);
|
|
48868
|
+
const resolved = new WeakMap;
|
|
48869
|
+
const ids = new WeakMap;
|
|
48870
|
+
const send = (ws, frame2) => {
|
|
48871
|
+
ws.send(JSON.stringify(frame2));
|
|
48872
|
+
};
|
|
48873
|
+
return new Elysia().onError(apiErrorHandler).ws("/api/stream", {
|
|
48874
|
+
beforeHandle: async ({ request: request2 }) => {
|
|
48875
|
+
if (resolved.has(request2))
|
|
48876
|
+
return;
|
|
48877
|
+
const bearer = request2.headers.get("authorization");
|
|
48878
|
+
const cookie = readCookie(request2, ADMIN_COOKIE);
|
|
48879
|
+
if (bearer !== null && cookie !== null) {
|
|
48880
|
+
throw new GatewayError("AUTH", "present one credential, not two");
|
|
48881
|
+
}
|
|
48882
|
+
await requireAdmin(request2, deps.admin);
|
|
48883
|
+
if (cookie === null)
|
|
48884
|
+
throw new GatewayError("AUTH", "admin session required");
|
|
48885
|
+
resolved.set(request2, {
|
|
48886
|
+
principal: { kind: "admin" },
|
|
48887
|
+
revalidate: () => deps.admin.verify(cookie)
|
|
48888
|
+
});
|
|
48889
|
+
},
|
|
48890
|
+
open(ws) {
|
|
48891
|
+
const credential = resolved.get(ws.data.request);
|
|
48892
|
+
if (credential === undefined) {
|
|
48893
|
+
ws.close(1011, "unauthenticated");
|
|
48894
|
+
return;
|
|
48895
|
+
}
|
|
48896
|
+
const id = nextId();
|
|
48897
|
+
ids.set(ws.raw, id);
|
|
48898
|
+
deps.registry.add(id, ws.raw, credential);
|
|
48899
|
+
deps.registry.subscribe(id, GLOBAL_INVALIDATE);
|
|
48900
|
+
},
|
|
48901
|
+
message(ws, raw) {
|
|
48902
|
+
const id = ids.get(ws.raw);
|
|
48903
|
+
if (id === undefined)
|
|
48904
|
+
return;
|
|
48905
|
+
const frame2 = parseClientFrame(raw);
|
|
48906
|
+
if (frame2 === null) {
|
|
48907
|
+
send(ws, { type: "error", message: "malformed frame" });
|
|
48908
|
+
return;
|
|
48909
|
+
}
|
|
48910
|
+
const principal = deps.registry.principal(id);
|
|
48911
|
+
if (principal === null)
|
|
48912
|
+
return;
|
|
48913
|
+
const head = frame2.id === undefined ? {} : { id: frame2.id };
|
|
48914
|
+
if (!authorised(deps.channels, principal, frame2.topic)) {
|
|
48915
|
+
send(ws, { ...head, type: "error", topic: frame2.topic, message: "not permitted" });
|
|
48916
|
+
return;
|
|
48917
|
+
}
|
|
48918
|
+
if (frame2.type === "unsubscribe") {
|
|
48919
|
+
deps.registry.unsubscribe(id, frame2.topic);
|
|
48920
|
+
send(ws, { ...head, type: "ack", topic: frame2.topic });
|
|
48921
|
+
return;
|
|
48922
|
+
}
|
|
48923
|
+
if (frame2.type === "send") {
|
|
48924
|
+
if (topicClass(frame2.topic) !== "plugin") {
|
|
48925
|
+
send(ws, { ...head, type: "error", topic: frame2.topic, message: "topic is read-only" });
|
|
48926
|
+
return;
|
|
48927
|
+
}
|
|
48928
|
+
if (!deps.channels.deliver(frame2.topic, id, frame2.payload)) {
|
|
48929
|
+
send(ws, {
|
|
48930
|
+
...head,
|
|
48931
|
+
type: "error",
|
|
48932
|
+
topic: frame2.topic,
|
|
48933
|
+
message: "subscribe before sending"
|
|
48934
|
+
});
|
|
48935
|
+
return;
|
|
48936
|
+
}
|
|
48937
|
+
send(ws, { ...head, type: "ack", topic: frame2.topic });
|
|
48938
|
+
return;
|
|
48939
|
+
}
|
|
48940
|
+
if (topicClass(frame2.topic) === "stream" && !deps.broadcaster.declared(frame2.topic)) {
|
|
48941
|
+
send(ws, { ...head, type: "error", topic: frame2.topic, message: "no source" });
|
|
48942
|
+
return;
|
|
48943
|
+
}
|
|
48944
|
+
deps.registry.subscribe(id, frame2.topic);
|
|
48945
|
+
send(ws, { ...head, type: "ack", topic: frame2.topic });
|
|
48946
|
+
if (frame2.sinceSeq !== undefined) {
|
|
48947
|
+
const slice = deps.ring.since(frame2.topic, frame2.sinceSeq);
|
|
48948
|
+
if (slice.kind === "gap") {
|
|
48949
|
+
send(ws, { type: "gap", topic: frame2.topic, seq: slice.seq });
|
|
48950
|
+
} else {
|
|
48951
|
+
for (const item of slice.frames) {
|
|
48952
|
+
send(ws, { type: "event", topic: frame2.topic, seq: item.seq, payload: item.payload });
|
|
48953
|
+
}
|
|
48954
|
+
}
|
|
48955
|
+
}
|
|
48956
|
+
},
|
|
48957
|
+
pong(ws) {
|
|
48958
|
+
const id = ids.get(ws.raw);
|
|
48959
|
+
if (id !== undefined)
|
|
48960
|
+
deps.registry.pong(id);
|
|
48961
|
+
},
|
|
48962
|
+
drain(ws) {
|
|
48963
|
+
const id = ids.get(ws.raw);
|
|
48964
|
+
if (id !== undefined)
|
|
48965
|
+
deps.registry.drain(id);
|
|
48966
|
+
},
|
|
48967
|
+
close(ws) {
|
|
48968
|
+
const id = ids.get(ws.raw);
|
|
48969
|
+
if (id === undefined)
|
|
48970
|
+
return;
|
|
48971
|
+
deps.channels.closed(id, deps.registry.topics(id));
|
|
48972
|
+
deps.registry.remove(id);
|
|
48973
|
+
ids.delete(ws.raw);
|
|
48974
|
+
logger2.debug("stream closed");
|
|
48975
|
+
}
|
|
48976
|
+
}).get("/api/stream", ({ set: set2 }) => {
|
|
48977
|
+
set2.status = 426;
|
|
48978
|
+
return { error: { code: "BAD_REQUEST", message: "websocket upgrade required" } };
|
|
48979
|
+
});
|
|
48980
|
+
}
|
|
48981
|
+
|
|
48982
|
+
// apps/gateway/src/stream/channels.ts
|
|
48983
|
+
var NAME_PATTERN = /^[a-z0-9][a-z0-9:._-]{0,63}$/;
|
|
48984
|
+
var defaultScheduler = (run) => {
|
|
48985
|
+
const timer = setTimeout(run, 0);
|
|
48986
|
+
timer.unref?.();
|
|
48987
|
+
return () => clearTimeout(timer);
|
|
48988
|
+
};
|
|
48989
|
+
function createChannelRegistry(deps) {
|
|
48990
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
48991
|
+
const scheduler = deps.scheduler ?? defaultScheduler;
|
|
48992
|
+
const channels = new Map;
|
|
48993
|
+
let handlerErrors = 0;
|
|
48994
|
+
let live = true;
|
|
48995
|
+
let cancelReport;
|
|
48996
|
+
const errorsSinceReport = new Map;
|
|
48997
|
+
const report2 = () => {
|
|
48998
|
+
cancelReport = undefined;
|
|
48999
|
+
if (!live)
|
|
49000
|
+
return;
|
|
49001
|
+
for (const [pluginId, count] of errorsSinceReport) {
|
|
49002
|
+
logger2.warn("plugin channel handler failed", { plugin: pluginId, count });
|
|
49003
|
+
}
|
|
49004
|
+
errorsSinceReport.clear();
|
|
49005
|
+
};
|
|
49006
|
+
const countFailure = (pluginId) => {
|
|
49007
|
+
handlerErrors++;
|
|
49008
|
+
errorsSinceReport.set(pluginId, (errorsSinceReport.get(pluginId) ?? 0) + 1);
|
|
49009
|
+
if (cancelReport !== undefined || !live)
|
|
49010
|
+
return;
|
|
49011
|
+
cancelReport = scheduler(report2);
|
|
49012
|
+
};
|
|
49013
|
+
const holds = (connectionId, topic) => deps.sockets.has(connectionId, topic);
|
|
49014
|
+
const openChannel = (pluginId, name) => {
|
|
49015
|
+
if (!NAME_PATTERN.test(name)) {
|
|
49016
|
+
throw new Error(`channel name must match ${String(NAME_PATTERN)}, got ${name}`);
|
|
49017
|
+
}
|
|
49018
|
+
const topic = `plugin:${pluginId}:${name}`;
|
|
49019
|
+
const existing = channels.get(topic);
|
|
49020
|
+
if (existing !== undefined)
|
|
49021
|
+
return existing.facade;
|
|
49022
|
+
const channel = {
|
|
49023
|
+
pluginId,
|
|
49024
|
+
topic,
|
|
49025
|
+
onMessage: [],
|
|
49026
|
+
onClose: [],
|
|
49027
|
+
facade: { onMessage: () => {}, send: () => {}, onClose: () => {} }
|
|
49028
|
+
};
|
|
49029
|
+
channel.facade = {
|
|
49030
|
+
onMessage(handler) {
|
|
49031
|
+
channel.onMessage.push(handler);
|
|
49032
|
+
},
|
|
49033
|
+
onClose(handler) {
|
|
49034
|
+
channel.onClose.push(handler);
|
|
49035
|
+
},
|
|
49036
|
+
send(connectionId, payload) {
|
|
49037
|
+
if (!live)
|
|
49038
|
+
return;
|
|
49039
|
+
if (!holds(connectionId, topic))
|
|
49040
|
+
return;
|
|
49041
|
+
deps.sockets.sendTo(connectionId, { type: "event", topic, payload });
|
|
49042
|
+
}
|
|
49043
|
+
};
|
|
49044
|
+
channels.set(topic, channel);
|
|
49045
|
+
return channel.facade;
|
|
49046
|
+
};
|
|
49047
|
+
return {
|
|
49048
|
+
for(pluginId) {
|
|
49049
|
+
return { open: (name) => openChannel(pluginId, name) };
|
|
49050
|
+
},
|
|
49051
|
+
opened(topic) {
|
|
49052
|
+
return channels.has(topic);
|
|
49053
|
+
},
|
|
49054
|
+
deliver(topic, connectionId, payload) {
|
|
49055
|
+
if (!live)
|
|
49056
|
+
return false;
|
|
49057
|
+
const channel = channels.get(topic);
|
|
49058
|
+
if (channel === undefined)
|
|
49059
|
+
return false;
|
|
49060
|
+
if (!holds(connectionId, topic))
|
|
49061
|
+
return false;
|
|
49062
|
+
for (const handler of channel.onMessage) {
|
|
49063
|
+
try {
|
|
49064
|
+
handler({ connectionId, payload });
|
|
49065
|
+
} catch {
|
|
49066
|
+
countFailure(channel.pluginId);
|
|
49067
|
+
}
|
|
49068
|
+
}
|
|
49069
|
+
return true;
|
|
49070
|
+
},
|
|
49071
|
+
closed(connectionId, topics) {
|
|
49072
|
+
if (!live)
|
|
49073
|
+
return;
|
|
49074
|
+
for (const topic of topics) {
|
|
49075
|
+
const channel = channels.get(topic);
|
|
49076
|
+
if (channel === undefined)
|
|
49077
|
+
continue;
|
|
49078
|
+
for (const handler of channel.onClose) {
|
|
49079
|
+
try {
|
|
49080
|
+
handler(connectionId);
|
|
49081
|
+
} catch {
|
|
49082
|
+
countFailure(channel.pluginId);
|
|
49083
|
+
}
|
|
49084
|
+
}
|
|
49085
|
+
}
|
|
49086
|
+
},
|
|
49087
|
+
stats() {
|
|
49088
|
+
return { channels: channels.size, handlerErrors };
|
|
49089
|
+
},
|
|
49090
|
+
stop() {
|
|
49091
|
+
live = false;
|
|
49092
|
+
channels.clear();
|
|
49093
|
+
cancelReport?.();
|
|
49094
|
+
cancelReport = undefined;
|
|
49095
|
+
errorsSinceReport.clear();
|
|
49096
|
+
}
|
|
49097
|
+
};
|
|
49098
|
+
}
|
|
49099
|
+
|
|
49100
|
+
// apps/gateway/src/stream/coalescer.ts
|
|
49101
|
+
var defaultSchedule = (run, ms) => {
|
|
49102
|
+
const timer = setTimeout(run, ms);
|
|
49103
|
+
return () => clearTimeout(timer);
|
|
49104
|
+
};
|
|
49105
|
+
function createCoalescer(deps) {
|
|
49106
|
+
const now = deps.now ?? Date.now;
|
|
49107
|
+
const schedule = deps.schedule ?? defaultSchedule;
|
|
49108
|
+
const lastSent = new Map;
|
|
49109
|
+
const pending = new Map;
|
|
49110
|
+
const floorFor = (topic) => deps.floors[topic] ?? deps.defaultFloorMs;
|
|
49111
|
+
const send = (topic, payload) => {
|
|
49112
|
+
lastSent.set(topic, now());
|
|
49113
|
+
deps.sink(topic, payload);
|
|
49114
|
+
};
|
|
49115
|
+
const fire = (topic) => {
|
|
49116
|
+
const item = pending.get(topic);
|
|
49117
|
+
if (item === undefined)
|
|
49118
|
+
return;
|
|
49119
|
+
pending.delete(topic);
|
|
49120
|
+
send(topic, item.payload);
|
|
49121
|
+
};
|
|
49122
|
+
return {
|
|
49123
|
+
emit(topic, payload) {
|
|
49124
|
+
const floor = floorFor(topic);
|
|
49125
|
+
const previous = lastSent.get(topic);
|
|
49126
|
+
const elapsed = previous === undefined ? Number.POSITIVE_INFINITY : now() - previous;
|
|
49127
|
+
const waiting = pending.get(topic);
|
|
49128
|
+
if (waiting !== undefined) {
|
|
49129
|
+
waiting.payload = payload;
|
|
49130
|
+
return;
|
|
49131
|
+
}
|
|
49132
|
+
if (elapsed >= floor) {
|
|
49133
|
+
send(topic, payload);
|
|
49134
|
+
return;
|
|
49135
|
+
}
|
|
49136
|
+
const cancel = schedule(() => fire(topic), floor - elapsed);
|
|
49137
|
+
pending.set(topic, { payload, cancel });
|
|
49138
|
+
},
|
|
49139
|
+
flush() {
|
|
49140
|
+
for (const [topic, item] of [...pending]) {
|
|
49141
|
+
item.cancel();
|
|
49142
|
+
pending.delete(topic);
|
|
49143
|
+
send(topic, item.payload);
|
|
49144
|
+
}
|
|
49145
|
+
},
|
|
49146
|
+
stop() {
|
|
49147
|
+
for (const item of pending.values())
|
|
49148
|
+
item.cancel();
|
|
49149
|
+
pending.clear();
|
|
49150
|
+
lastSent.clear();
|
|
49151
|
+
}
|
|
49152
|
+
};
|
|
49153
|
+
}
|
|
49154
|
+
|
|
49155
|
+
// apps/gateway/src/stream/registry.ts
|
|
49156
|
+
var DEFAULT_QUEUE_CAPACITY = 1000;
|
|
49157
|
+
var MAX_TOPICS_PER_CONNECTION = 256;
|
|
49158
|
+
var HIGH_WATER_BYTES = 512 * 1024;
|
|
49159
|
+
var HEARTBEAT_MS = 20000;
|
|
49160
|
+
var PONG_DEADLINE_MS = 60000;
|
|
49161
|
+
var CHECK_STALE_MS = 60000;
|
|
49162
|
+
var CLOSE_UNAUTHENTICATED = 4401;
|
|
49163
|
+
function createSocketRegistry(deps = {}) {
|
|
49164
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
49165
|
+
const onDetach = deps.onDetach;
|
|
49166
|
+
const now = deps.now ?? Date.now;
|
|
49167
|
+
const schedule = deps.schedule ?? ((run, ms) => {
|
|
49168
|
+
const timer = setTimeout(run, ms);
|
|
49169
|
+
timer.unref?.();
|
|
49170
|
+
return () => clearTimeout(timer);
|
|
49171
|
+
});
|
|
49172
|
+
const capacity = deps.queueCapacity ?? DEFAULT_QUEUE_CAPACITY;
|
|
49173
|
+
const heartbeatMs = deps.heartbeatMs ?? HEARTBEAT_MS;
|
|
49174
|
+
const pongDeadlineMs = deps.pongDeadlineMs ?? PONG_DEADLINE_MS;
|
|
49175
|
+
const checkStaleMs = deps.checkStaleMs ?? CHECK_STALE_MS;
|
|
49176
|
+
const connections = new Map;
|
|
49177
|
+
const index = new Map;
|
|
49178
|
+
let dropped = 0;
|
|
49179
|
+
let live = true;
|
|
49180
|
+
let cancelTick;
|
|
49181
|
+
let droppedSinceReport = 0;
|
|
49182
|
+
const flush = (connection) => {
|
|
49183
|
+
while (connection.queue.length > 0) {
|
|
49184
|
+
if ((connection.socket.getBufferedAmount?.() ?? 0) >= HIGH_WATER_BYTES)
|
|
49185
|
+
return;
|
|
49186
|
+
const frame2 = connection.queue[0];
|
|
49187
|
+
if (frame2 === undefined)
|
|
49188
|
+
break;
|
|
49189
|
+
let status2;
|
|
49190
|
+
try {
|
|
49191
|
+
status2 = connection.socket.send(JSON.stringify(frame2));
|
|
49192
|
+
} catch {
|
|
49193
|
+
return;
|
|
49194
|
+
}
|
|
49195
|
+
if (status2 === 0)
|
|
49196
|
+
return;
|
|
49197
|
+
connection.queue.shift();
|
|
49198
|
+
}
|
|
49199
|
+
};
|
|
49200
|
+
const deliver = (connection, frame2) => {
|
|
49201
|
+
if (connection.queue.length >= capacity) {
|
|
49202
|
+
connection.queue.shift();
|
|
49203
|
+
dropped++;
|
|
49204
|
+
droppedSinceReport++;
|
|
49205
|
+
}
|
|
49206
|
+
connection.queue.push(frame2);
|
|
49207
|
+
flush(connection);
|
|
49208
|
+
};
|
|
49209
|
+
const detach = (connection) => {
|
|
49210
|
+
for (const topic of connection.topics) {
|
|
49211
|
+
const subscribers = index.get(topic);
|
|
49212
|
+
if (subscribers === undefined)
|
|
49213
|
+
continue;
|
|
49214
|
+
subscribers.delete(connection.id);
|
|
49215
|
+
if (subscribers.size === 0)
|
|
49216
|
+
index.delete(topic);
|
|
49217
|
+
}
|
|
49218
|
+
connections.delete(connection.id);
|
|
49219
|
+
};
|
|
49220
|
+
const closeOne = (connection, code, reason) => {
|
|
49221
|
+
onDetach?.(connection.id, [...connection.topics]);
|
|
49222
|
+
detach(connection);
|
|
49223
|
+
try {
|
|
49224
|
+
connection.socket.close(code, reason);
|
|
49225
|
+
} catch {}
|
|
49226
|
+
};
|
|
49227
|
+
const tick = () => {
|
|
49228
|
+
cancelTick = undefined;
|
|
49229
|
+
if (!live)
|
|
49230
|
+
return;
|
|
49231
|
+
const at = now();
|
|
49232
|
+
for (const connection of [...connections.values()]) {
|
|
49233
|
+
if (at - connection.lastPongAt > pongDeadlineMs) {
|
|
49234
|
+
closeOne(connection, 1001, "pong deadline");
|
|
49235
|
+
continue;
|
|
49236
|
+
}
|
|
49237
|
+
try {
|
|
49238
|
+
connection.socket.ping?.();
|
|
49239
|
+
} catch {
|
|
49240
|
+
closeOne(connection, 1001, "ping failed");
|
|
49241
|
+
continue;
|
|
49242
|
+
}
|
|
49243
|
+
if (connection.checking) {
|
|
49244
|
+
if (at - connection.checkingSince < checkStaleMs)
|
|
49245
|
+
continue;
|
|
49246
|
+
connection.generation += 1;
|
|
49247
|
+
connection.checking = false;
|
|
49248
|
+
}
|
|
49249
|
+
connection.checking = true;
|
|
49250
|
+
connection.checkingSince = at;
|
|
49251
|
+
const generation = connection.generation;
|
|
49252
|
+
connection.credential.revalidate().then((valid) => {
|
|
49253
|
+
if (connection.generation !== generation)
|
|
49254
|
+
return;
|
|
49255
|
+
connection.checking = false;
|
|
49256
|
+
if (!valid && connections.has(connection.id)) {
|
|
49257
|
+
closeOne(connection, CLOSE_UNAUTHENTICATED, "session expired");
|
|
49258
|
+
}
|
|
49259
|
+
}).catch(() => {
|
|
49260
|
+
if (connection.generation !== generation)
|
|
49261
|
+
return;
|
|
49262
|
+
connection.checking = false;
|
|
49263
|
+
});
|
|
49264
|
+
}
|
|
49265
|
+
if (droppedSinceReport > 0) {
|
|
49266
|
+
logger2.warn("stream queue overflowed", { count: droppedSinceReport });
|
|
49267
|
+
droppedSinceReport = 0;
|
|
49268
|
+
}
|
|
49269
|
+
arm();
|
|
49270
|
+
};
|
|
49271
|
+
const arm = () => {
|
|
49272
|
+
if (!live || cancelTick !== undefined)
|
|
49273
|
+
return;
|
|
49274
|
+
cancelTick = schedule(tick, heartbeatMs);
|
|
49275
|
+
};
|
|
49276
|
+
arm();
|
|
49277
|
+
return {
|
|
49278
|
+
add(id, socket, credential) {
|
|
49279
|
+
if (!live) {
|
|
49280
|
+
socket.close(1001, "shutting down");
|
|
49281
|
+
return;
|
|
49282
|
+
}
|
|
49283
|
+
connections.set(id, {
|
|
49284
|
+
id,
|
|
49285
|
+
socket,
|
|
49286
|
+
credential,
|
|
49287
|
+
topics: new Set,
|
|
49288
|
+
queue: [],
|
|
49289
|
+
lastPongAt: now(),
|
|
49290
|
+
checking: false,
|
|
49291
|
+
checkingSince: 0,
|
|
49292
|
+
generation: 0
|
|
49293
|
+
});
|
|
49294
|
+
},
|
|
49295
|
+
remove(id) {
|
|
49296
|
+
const connection = connections.get(id);
|
|
49297
|
+
if (connection !== undefined)
|
|
49298
|
+
detach(connection);
|
|
49299
|
+
},
|
|
49300
|
+
subscribe(id, topic) {
|
|
49301
|
+
const connection = connections.get(id);
|
|
49302
|
+
if (connection === undefined)
|
|
49303
|
+
return;
|
|
49304
|
+
if (connection.topics.size >= MAX_TOPICS_PER_CONNECTION && !connection.topics.has(topic)) {
|
|
49305
|
+
return;
|
|
49306
|
+
}
|
|
49307
|
+
connection.topics.add(topic);
|
|
49308
|
+
const subscribers = index.get(topic) ?? new Set;
|
|
49309
|
+
subscribers.add(id);
|
|
49310
|
+
index.set(topic, subscribers);
|
|
49311
|
+
},
|
|
49312
|
+
unsubscribe(id, topic) {
|
|
49313
|
+
const connection = connections.get(id);
|
|
49314
|
+
if (connection === undefined)
|
|
49315
|
+
return;
|
|
49316
|
+
connection.topics.delete(topic);
|
|
49317
|
+
const subscribers = index.get(topic);
|
|
49318
|
+
if (subscribers === undefined)
|
|
49319
|
+
return;
|
|
49320
|
+
subscribers.delete(id);
|
|
49321
|
+
if (subscribers.size === 0)
|
|
49322
|
+
index.delete(topic);
|
|
49323
|
+
},
|
|
49324
|
+
topics(id) {
|
|
49325
|
+
return [...connections.get(id)?.topics ?? []];
|
|
49326
|
+
},
|
|
49327
|
+
has(id, topic) {
|
|
49328
|
+
return connections.get(id)?.topics.has(topic) ?? false;
|
|
49329
|
+
},
|
|
49330
|
+
principal(id) {
|
|
49331
|
+
return connections.get(id)?.credential.principal ?? null;
|
|
49332
|
+
},
|
|
49333
|
+
pong(id) {
|
|
49334
|
+
const connection = connections.get(id);
|
|
49335
|
+
if (connection !== undefined)
|
|
49336
|
+
connection.lastPongAt = now();
|
|
49337
|
+
},
|
|
49338
|
+
drain(id) {
|
|
49339
|
+
const connection = connections.get(id);
|
|
49340
|
+
if (connection !== undefined)
|
|
49341
|
+
flush(connection);
|
|
49342
|
+
},
|
|
49343
|
+
publish(topic, frame2) {
|
|
49344
|
+
const subscribers = index.get(topic);
|
|
49345
|
+
if (subscribers === undefined)
|
|
49346
|
+
return;
|
|
49347
|
+
for (const id of subscribers) {
|
|
49348
|
+
const connection = connections.get(id);
|
|
49349
|
+
if (connection !== undefined)
|
|
49350
|
+
deliver(connection, frame2);
|
|
49351
|
+
}
|
|
49352
|
+
},
|
|
49353
|
+
sendTo(id, frame2) {
|
|
49354
|
+
const connection = connections.get(id);
|
|
49355
|
+
if (connection !== undefined)
|
|
49356
|
+
deliver(connection, frame2);
|
|
49357
|
+
},
|
|
49358
|
+
closeAll(code, reason) {
|
|
49359
|
+
for (const connection of [...connections.values()])
|
|
49360
|
+
closeOne(connection, code, reason);
|
|
49361
|
+
index.clear();
|
|
49362
|
+
},
|
|
49363
|
+
stats() {
|
|
49364
|
+
let queued = 0;
|
|
49365
|
+
for (const connection of connections.values())
|
|
49366
|
+
queued += connection.queue.length;
|
|
49367
|
+
return { connections: connections.size, dropped, queued };
|
|
49368
|
+
},
|
|
49369
|
+
stop() {
|
|
49370
|
+
live = false;
|
|
49371
|
+
cancelTick?.();
|
|
49372
|
+
cancelTick = undefined;
|
|
49373
|
+
}
|
|
49374
|
+
};
|
|
49375
|
+
}
|
|
49376
|
+
|
|
49377
|
+
// apps/gateway/src/stream/ring.ts
|
|
49378
|
+
function sizeOf(payload) {
|
|
49379
|
+
try {
|
|
49380
|
+
return JSON.stringify(payload)?.length ?? 0;
|
|
49381
|
+
} catch {
|
|
49382
|
+
return 0;
|
|
49383
|
+
}
|
|
49384
|
+
}
|
|
49385
|
+
function createRing(limits) {
|
|
49386
|
+
const topics = new Map;
|
|
49387
|
+
const stateFor = (topic) => {
|
|
49388
|
+
const existing = topics.get(topic);
|
|
49389
|
+
if (existing !== undefined)
|
|
49390
|
+
return existing;
|
|
49391
|
+
const created = { entries: [], bytes: 0, seq: 0 };
|
|
49392
|
+
topics.set(topic, created);
|
|
49393
|
+
return created;
|
|
49394
|
+
};
|
|
49395
|
+
return {
|
|
49396
|
+
push(topic, payload) {
|
|
49397
|
+
const state = stateFor(topic);
|
|
49398
|
+
state.seq += 1;
|
|
49399
|
+
const bytes = sizeOf(payload);
|
|
49400
|
+
state.entries.push({ seq: state.seq, payload, bytes });
|
|
49401
|
+
state.bytes += bytes;
|
|
49402
|
+
while (state.entries.length > limits.frames || state.bytes > limits.bytes && state.entries.length > 1) {
|
|
49403
|
+
const evicted = state.entries.shift();
|
|
49404
|
+
if (evicted === undefined)
|
|
49405
|
+
break;
|
|
49406
|
+
state.bytes -= evicted.bytes;
|
|
49407
|
+
}
|
|
49408
|
+
return state.seq;
|
|
49409
|
+
},
|
|
49410
|
+
since(topic, sinceSeq) {
|
|
49411
|
+
const state = topics.get(topic);
|
|
49412
|
+
if (state === undefined) {
|
|
49413
|
+
return sinceSeq === 0 ? { kind: "frames", frames: [] } : { kind: "gap", seq: 0 };
|
|
49414
|
+
}
|
|
49415
|
+
if (sinceSeq > state.seq)
|
|
49416
|
+
return { kind: "gap", seq: state.seq };
|
|
49417
|
+
if (sinceSeq === state.seq)
|
|
49418
|
+
return { kind: "frames", frames: [] };
|
|
49419
|
+
const oldest = state.entries[0];
|
|
49420
|
+
if (oldest === undefined)
|
|
49421
|
+
return { kind: "gap", seq: state.seq };
|
|
49422
|
+
if (oldest.seq > sinceSeq + 1)
|
|
49423
|
+
return { kind: "gap", seq: state.seq };
|
|
49424
|
+
return {
|
|
49425
|
+
kind: "frames",
|
|
49426
|
+
frames: state.entries.filter((entry) => entry.seq > sinceSeq).map(({ seq, payload }) => ({ seq, payload }))
|
|
49427
|
+
};
|
|
49428
|
+
},
|
|
49429
|
+
head(topic) {
|
|
49430
|
+
return topics.get(topic)?.seq ?? 0;
|
|
49431
|
+
},
|
|
49432
|
+
reset(topic) {
|
|
49433
|
+
const state = topics.get(topic);
|
|
49434
|
+
if (state === undefined)
|
|
49435
|
+
return;
|
|
49436
|
+
state.entries = [];
|
|
49437
|
+
state.bytes = 0;
|
|
49438
|
+
},
|
|
49439
|
+
drop(topic) {
|
|
49440
|
+
topics.delete(topic);
|
|
49441
|
+
}
|
|
49442
|
+
};
|
|
49443
|
+
}
|
|
49444
|
+
|
|
48671
49445
|
// apps/gateway/src/app.ts
|
|
48672
49446
|
var ADMIN_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
|
|
48673
49447
|
var QUIESCE_MESSAGE = "the gateway is briefly quiesced for database maintenance";
|
|
@@ -48717,6 +49491,29 @@ function createApp(deps) {
|
|
|
48717
49491
|
} catch {}
|
|
48718
49492
|
}
|
|
48719
49493
|
const latch = deps.latch ?? createQuiesceLatch();
|
|
49494
|
+
let channelsRef;
|
|
49495
|
+
const registry2 = deps.registry ?? createSocketRegistry({
|
|
49496
|
+
logger: logger2,
|
|
49497
|
+
now,
|
|
49498
|
+
onDetach: (id, topics) => channelsRef?.closed(id, topics)
|
|
49499
|
+
});
|
|
49500
|
+
const channels = deps.channels ?? createChannelRegistry({ sockets: registry2, logger: logger2 });
|
|
49501
|
+
channelsRef = channels;
|
|
49502
|
+
const ring = deps.ring ?? createRing({ frames: 500, bytes: 2 * 1024 * 1024 });
|
|
49503
|
+
const broadcaster = deps.broadcaster ?? createBroadcaster({
|
|
49504
|
+
registry: registry2,
|
|
49505
|
+
ring,
|
|
49506
|
+
coalescer: createCoalescer({
|
|
49507
|
+
floors: INVALIDATION_FLOORS,
|
|
49508
|
+
defaultFloorMs: DEFAULT_FLOOR_MS,
|
|
49509
|
+
now,
|
|
49510
|
+
sink: (topic, payload) => registry2.publish(topic, {
|
|
49511
|
+
type: "event",
|
|
49512
|
+
topic,
|
|
49513
|
+
...payload === undefined ? {} : { payload }
|
|
49514
|
+
})
|
|
49515
|
+
})
|
|
49516
|
+
});
|
|
48720
49517
|
const admitted = new WeakMap;
|
|
48721
49518
|
return new Elysia().onRequest(({ request: request2 }) => {
|
|
48722
49519
|
const path = new URL(request2.url).pathname;
|
|
@@ -48741,18 +49538,18 @@ function createApp(deps) {
|
|
|
48741
49538
|
rateLimiter,
|
|
48742
49539
|
logger: logger2,
|
|
48743
49540
|
...deps.loadRegistry === undefined ? {} : { loadRegistry: deps.loadRegistry },
|
|
48744
|
-
discoveryMirrors: deps.discoveryMirrors === true,
|
|
48745
49541
|
bodyLoggingAllowed: deps.bodyLoggingAllowed === true,
|
|
48746
|
-
...deps.emit === undefined ? {} : { emit: deps.emit }
|
|
49542
|
+
...deps.emit === undefined ? {} : { emit: deps.emit },
|
|
49543
|
+
broadcaster
|
|
48747
49544
|
})).use(adminRoutes({
|
|
48748
49545
|
store: deps.store,
|
|
48749
49546
|
admin,
|
|
48750
49547
|
baseUrl: deps.baseUrl,
|
|
48751
|
-
discoveryMirrors: deps.discoveryMirrors === true,
|
|
48752
49548
|
bodyLoggingAllowed: deps.bodyLoggingAllowed === true,
|
|
48753
49549
|
now,
|
|
48754
49550
|
sessionTtlMs: ADMIN_SESSION_TTL_MS,
|
|
48755
49551
|
logger: logger2,
|
|
49552
|
+
broadcaster,
|
|
48756
49553
|
...deps.console === undefined ? {} : { console: deps.console }
|
|
48757
49554
|
})).use(databaseRoutes({
|
|
48758
49555
|
store: deps.store,
|
|
@@ -48760,6 +49557,7 @@ function createApp(deps) {
|
|
|
48760
49557
|
admin,
|
|
48761
49558
|
latch,
|
|
48762
49559
|
snapshots,
|
|
49560
|
+
broadcaster,
|
|
48763
49561
|
fs: deps.databaseFs ?? nodeDatabaseFs(),
|
|
48764
49562
|
now,
|
|
48765
49563
|
logger: logger2,
|
|
@@ -48779,6 +49577,13 @@ function createApp(deps) {
|
|
|
48779
49577
|
admin,
|
|
48780
49578
|
plugins: deps.pluginUi ?? [],
|
|
48781
49579
|
logger: logger2
|
|
49580
|
+
})).use(streamRoutes({
|
|
49581
|
+
admin,
|
|
49582
|
+
registry: registry2,
|
|
49583
|
+
broadcaster,
|
|
49584
|
+
ring,
|
|
49585
|
+
channels,
|
|
49586
|
+
logger: logger2
|
|
48782
49587
|
})).get("/*", ({ path, request: request2, set: set2 }) => {
|
|
48783
49588
|
const rawPath = new URL(request2.url).pathname;
|
|
48784
49589
|
const error51 = () => {
|
|
@@ -48935,6 +49740,7 @@ async function sweep(deps) {
|
|
|
48935
49740
|
const logger2 = deps.logger ?? noopLogger;
|
|
48936
49741
|
const credentials = await deps.store.credentials.list();
|
|
48937
49742
|
let refreshed = 0;
|
|
49743
|
+
let touched = 0;
|
|
48938
49744
|
for (const credential of due(credentials, deps.now())) {
|
|
48939
49745
|
if (!credential.hasRefreshToken) {
|
|
48940
49746
|
if (credential.expiresAt !== null && credential.expiresAt <= deps.now()) {
|
|
@@ -48943,14 +49749,18 @@ async function sweep(deps) {
|
|
|
48943
49749
|
disabledReason: "expiredNoRefresh",
|
|
48944
49750
|
disabledAt: deps.now()
|
|
48945
49751
|
});
|
|
49752
|
+
touched += 1;
|
|
48946
49753
|
}
|
|
48947
49754
|
continue;
|
|
48948
49755
|
}
|
|
48949
49756
|
try {
|
|
48950
49757
|
await deps.refresh(credential);
|
|
48951
49758
|
refreshed += 1;
|
|
49759
|
+
touched += 1;
|
|
48952
49760
|
} catch (error51) {
|
|
48953
49761
|
const code = error51 instanceof GatewayError ? error51.code : "INTERNAL";
|
|
49762
|
+
if (code === "AUTH")
|
|
49763
|
+
touched += 1;
|
|
48954
49764
|
logger2.warn("scheduled token refresh failed", {
|
|
48955
49765
|
provider: credential.provider,
|
|
48956
49766
|
credentialId: credential.id,
|
|
@@ -48959,6 +49769,8 @@ async function sweep(deps) {
|
|
|
48959
49769
|
});
|
|
48960
49770
|
}
|
|
48961
49771
|
}
|
|
49772
|
+
if (touched > 0)
|
|
49773
|
+
deps.broadcaster?.invalidate("res:credentials");
|
|
48962
49774
|
return refreshed;
|
|
48963
49775
|
}
|
|
48964
49776
|
function startRefreshScheduler(deps) {
|
|
@@ -48982,14 +49794,14 @@ function startRefreshScheduler(deps) {
|
|
|
48982
49794
|
|
|
48983
49795
|
// apps/gateway/src/plugins/events.ts
|
|
48984
49796
|
var DEFAULT_CAPACITY = 1000;
|
|
48985
|
-
var
|
|
49797
|
+
var defaultScheduler2 = (run) => {
|
|
48986
49798
|
const timer = setTimeout(run, 0);
|
|
48987
49799
|
return () => clearTimeout(timer);
|
|
48988
49800
|
};
|
|
48989
49801
|
function createPluginEventBus(opts) {
|
|
48990
49802
|
const logger2 = opts.logger ?? noopLogger;
|
|
48991
49803
|
const capacity = opts.capacity ?? DEFAULT_CAPACITY;
|
|
48992
|
-
const scheduler = opts.scheduler ??
|
|
49804
|
+
const scheduler = opts.scheduler ?? defaultScheduler2;
|
|
48993
49805
|
const requestHandlers = [];
|
|
48994
49806
|
const limitHandlers = [];
|
|
48995
49807
|
const queue = [];
|
|
@@ -49197,6 +50009,7 @@ function buildContext(deps) {
|
|
|
49197
50009
|
const events = createPluginEvents(manifest, deps.events);
|
|
49198
50010
|
return events === undefined ? {} : { events };
|
|
49199
50011
|
})(),
|
|
50012
|
+
...declared.includes("channels") ? { channels: deps.channels.for(manifest.id) } : {},
|
|
49200
50013
|
config: manifest.defaults ?? {}
|
|
49201
50014
|
};
|
|
49202
50015
|
}
|
|
@@ -49277,6 +50090,7 @@ async function loadPlugins(deps) {
|
|
|
49277
50090
|
home,
|
|
49278
50091
|
store: deps.store,
|
|
49279
50092
|
events: deps.events,
|
|
50093
|
+
channels: deps.channels,
|
|
49280
50094
|
logger: logger2,
|
|
49281
50095
|
now
|
|
49282
50096
|
});
|
|
@@ -49325,6 +50139,7 @@ async function startQuotaPoller(deps) {
|
|
|
49325
50139
|
});
|
|
49326
50140
|
}).finally(() => {
|
|
49327
50141
|
running = false;
|
|
50142
|
+
deps.broadcaster?.invalidate("res:quota");
|
|
49328
50143
|
});
|
|
49329
50144
|
};
|
|
49330
50145
|
pass();
|
|
@@ -49333,6 +50148,169 @@ async function startQuotaPoller(deps) {
|
|
|
49333
50148
|
return () => clearInterval(timer);
|
|
49334
50149
|
}
|
|
49335
50150
|
|
|
50151
|
+
// apps/gateway/src/stream/console.ts
|
|
50152
|
+
import { watch } from "fs";
|
|
50153
|
+
var CONSOLE_TOPIC = "stream:console";
|
|
50154
|
+
var CONSOLE_FLOOR_MS = 250;
|
|
50155
|
+
var JOURNAL_POLL_MS = 1000;
|
|
50156
|
+
var REWATCH_ATTEMPTS = 5;
|
|
50157
|
+
var defaultSchedule2 = (run, ms) => {
|
|
50158
|
+
const timer = setTimeout(run, ms);
|
|
50159
|
+
return () => clearTimeout(timer);
|
|
50160
|
+
};
|
|
50161
|
+
function startConsoleStream(deps) {
|
|
50162
|
+
const logger2 = deps.logger ?? noopLogger;
|
|
50163
|
+
const { source } = deps.console;
|
|
50164
|
+
if (source.kind === "none")
|
|
50165
|
+
return () => {};
|
|
50166
|
+
if (source.kind === "journal")
|
|
50167
|
+
return startJournalPoll(deps, source, logger2);
|
|
50168
|
+
return startFileWatch(deps, source, logger2);
|
|
50169
|
+
}
|
|
50170
|
+
function startFileWatch(deps, source, logger2) {
|
|
50171
|
+
const { broadcaster } = deps;
|
|
50172
|
+
const read = deps.read ?? readFrom;
|
|
50173
|
+
const size = deps.size ?? fileSize;
|
|
50174
|
+
const floorMs = deps.floorMs ?? CONSOLE_FLOOR_MS;
|
|
50175
|
+
let offset = size(source.path) ?? 0;
|
|
50176
|
+
let stopped = false;
|
|
50177
|
+
let watcher = null;
|
|
50178
|
+
let rotated = false;
|
|
50179
|
+
let rewatchAttempts = 0;
|
|
50180
|
+
let cancelRewatch = null;
|
|
50181
|
+
const rewatchSchedule = deps.schedule ?? defaultSchedule2;
|
|
50182
|
+
const pump = () => {
|
|
50183
|
+
const delta2 = read(source.path, offset);
|
|
50184
|
+
if (delta2 === null) {
|
|
50185
|
+
rotate();
|
|
50186
|
+
return;
|
|
50187
|
+
}
|
|
50188
|
+
offset = delta2.offset;
|
|
50189
|
+
const lines = parseConsoleLines(delta2.text, { lines: MAX_CONSOLE_LINES });
|
|
50190
|
+
if (lines.length > 0)
|
|
50191
|
+
broadcaster.stream(CONSOLE_TOPIC, { lines });
|
|
50192
|
+
if (delta2.gap || rotated) {
|
|
50193
|
+
rotated = false;
|
|
50194
|
+
broadcaster.resetStream(CONSOLE_TOPIC);
|
|
50195
|
+
}
|
|
50196
|
+
};
|
|
50197
|
+
const coalescer = createCoalescer({
|
|
50198
|
+
floors: { [CONSOLE_TOPIC]: floorMs },
|
|
50199
|
+
defaultFloorMs: floorMs,
|
|
50200
|
+
...deps.now === undefined ? {} : { now: deps.now },
|
|
50201
|
+
...deps.schedule === undefined ? {} : { schedule: deps.schedule },
|
|
50202
|
+
sink: () => pump()
|
|
50203
|
+
});
|
|
50204
|
+
function rotate() {
|
|
50205
|
+
if (stopped)
|
|
50206
|
+
return;
|
|
50207
|
+
offset = 0;
|
|
50208
|
+
rotated = true;
|
|
50209
|
+
watcher?.close();
|
|
50210
|
+
watcher = startWatcher(deps, source.path, onWatchEvent, logger2);
|
|
50211
|
+
if (watcher === null) {
|
|
50212
|
+
if (rewatchAttempts >= REWATCH_ATTEMPTS) {
|
|
50213
|
+
logger2.warn("console stream ended", { path: source.path });
|
|
50214
|
+
return;
|
|
50215
|
+
}
|
|
50216
|
+
rewatchAttempts += 1;
|
|
50217
|
+
cancelRewatch?.();
|
|
50218
|
+
cancelRewatch = rewatchSchedule(() => rotate(), floorMs);
|
|
50219
|
+
return;
|
|
50220
|
+
}
|
|
50221
|
+
rewatchAttempts = 0;
|
|
50222
|
+
coalescer.emit(CONSOLE_TOPIC);
|
|
50223
|
+
}
|
|
50224
|
+
function onWatchEvent(event) {
|
|
50225
|
+
if (event === "rename") {
|
|
50226
|
+
rotate();
|
|
50227
|
+
return;
|
|
50228
|
+
}
|
|
50229
|
+
coalescer.emit(CONSOLE_TOPIC);
|
|
50230
|
+
}
|
|
50231
|
+
watcher = startWatcher(deps, source.path, onWatchEvent, logger2);
|
|
50232
|
+
if (watcher === null) {
|
|
50233
|
+
coalescer.stop();
|
|
50234
|
+
return () => {};
|
|
50235
|
+
}
|
|
50236
|
+
broadcaster.declareStream(CONSOLE_TOPIC);
|
|
50237
|
+
logger2.info("console stream watching", { path: source.path });
|
|
50238
|
+
return () => {
|
|
50239
|
+
stopped = true;
|
|
50240
|
+
cancelRewatch?.();
|
|
50241
|
+
watcher?.close();
|
|
50242
|
+
coalescer.stop();
|
|
50243
|
+
};
|
|
50244
|
+
}
|
|
50245
|
+
function startWatcher(deps, path, onChange, logger2) {
|
|
50246
|
+
const create2 = deps.watch ?? ((target, changed) => {
|
|
50247
|
+
const watcher = watch(target, (event) => changed(event));
|
|
50248
|
+
watcher.on("error", (error51) => {
|
|
50249
|
+
logger2.error("console watch failed", { reason: describeError(error51, "unknown") });
|
|
50250
|
+
});
|
|
50251
|
+
return watcher;
|
|
50252
|
+
});
|
|
50253
|
+
try {
|
|
50254
|
+
return create2(path, onChange);
|
|
50255
|
+
} catch (error51) {
|
|
50256
|
+
logger2.warn("console stream unavailable", {
|
|
50257
|
+
path,
|
|
50258
|
+
reason: describeError(error51, "unknown")
|
|
50259
|
+
});
|
|
50260
|
+
return null;
|
|
50261
|
+
}
|
|
50262
|
+
}
|
|
50263
|
+
function startJournalPoll(deps, source, logger2) {
|
|
50264
|
+
const { broadcaster } = deps;
|
|
50265
|
+
const now = deps.now ?? Date.now;
|
|
50266
|
+
const schedule = deps.schedule ?? defaultSchedule2;
|
|
50267
|
+
const pollMs = deps.pollMs ?? JOURNAL_POLL_MS;
|
|
50268
|
+
let cursor = now();
|
|
50269
|
+
let stopped = false;
|
|
50270
|
+
let cancel = null;
|
|
50271
|
+
let publishedAtCursor = new Set;
|
|
50272
|
+
const arm = () => {
|
|
50273
|
+
if (stopped)
|
|
50274
|
+
return;
|
|
50275
|
+
cancel = schedule(() => void tick(), pollMs);
|
|
50276
|
+
};
|
|
50277
|
+
const tick = async () => {
|
|
50278
|
+
try {
|
|
50279
|
+
const read = await readConsole(deps.console.deps, source, {
|
|
50280
|
+
lines: MAX_CONSOLE_LINES,
|
|
50281
|
+
since: cursor - 1
|
|
50282
|
+
});
|
|
50283
|
+
const fresh = read.lines.filter((line) => line.at !== cursor || !publishedAtCursor.has(line.raw));
|
|
50284
|
+
if (fresh.length > 0) {
|
|
50285
|
+
let newest = cursor;
|
|
50286
|
+
for (const line of fresh) {
|
|
50287
|
+
if (line.at !== null && line.at > newest)
|
|
50288
|
+
newest = line.at;
|
|
50289
|
+
}
|
|
50290
|
+
if (newest > cursor) {
|
|
50291
|
+
cursor = newest;
|
|
50292
|
+
publishedAtCursor = new Set;
|
|
50293
|
+
}
|
|
50294
|
+
for (const line of fresh) {
|
|
50295
|
+
if (line.at === cursor)
|
|
50296
|
+
publishedAtCursor.add(line.raw);
|
|
50297
|
+
}
|
|
50298
|
+
broadcaster.stream(CONSOLE_TOPIC, { lines: fresh });
|
|
50299
|
+
}
|
|
50300
|
+
} catch (error51) {
|
|
50301
|
+
logger2.error("console poll failed", { reason: describeError(error51, "unknown") });
|
|
50302
|
+
} finally {
|
|
50303
|
+
arm();
|
|
50304
|
+
}
|
|
50305
|
+
};
|
|
50306
|
+
broadcaster.declareStream(CONSOLE_TOPIC);
|
|
50307
|
+
arm();
|
|
50308
|
+
return () => {
|
|
50309
|
+
stopped = true;
|
|
50310
|
+
cancel?.();
|
|
50311
|
+
};
|
|
50312
|
+
}
|
|
50313
|
+
|
|
49336
50314
|
// apps/gateway/src/index.ts
|
|
49337
50315
|
function stdoutLogger(level) {
|
|
49338
50316
|
return createLogger({
|
|
@@ -49411,10 +50389,19 @@ async function main() {
|
|
|
49411
50389
|
const installRoot = process.env.OMNI_ROOT ?? dirname5(config2.databasePath);
|
|
49412
50390
|
const pluginRoot = resolve6(installRoot, "plugins");
|
|
49413
50391
|
const pluginEvents = createPluginEventBus({ logger: logger2 });
|
|
50392
|
+
let pluginChannelsRef;
|
|
50393
|
+
const streamRegistry = createSocketRegistry({
|
|
50394
|
+
logger: logger2,
|
|
50395
|
+
now,
|
|
50396
|
+
onDetach: (id, topics) => pluginChannelsRef?.closed(id, topics)
|
|
50397
|
+
});
|
|
50398
|
+
const pluginChannels = createChannelRegistry({ sockets: streamRegistry, logger: logger2 });
|
|
50399
|
+
pluginChannelsRef = pluginChannels;
|
|
49414
50400
|
const loadedPlugins = await loadPlugins({
|
|
49415
50401
|
root: pluginRoot,
|
|
49416
50402
|
store,
|
|
49417
50403
|
events: pluginEvents,
|
|
50404
|
+
channels: pluginChannels,
|
|
49418
50405
|
sdkVersion: DASHBOARD_SDK_VERSION,
|
|
49419
50406
|
logger: logger2,
|
|
49420
50407
|
now
|
|
@@ -49423,6 +50410,21 @@ async function main() {
|
|
|
49423
50410
|
logger2.warn("plugin unavailable", { plugin: failure.id, reason: failure.reason });
|
|
49424
50411
|
}
|
|
49425
50412
|
logger2.info("plugins resolved", { count: loadedPlugins.plugins.length, path: pluginRoot });
|
|
50413
|
+
const streamRing = createRing({ frames: 500, bytes: 2 * 1024 * 1024 });
|
|
50414
|
+
const broadcaster = createBroadcaster({
|
|
50415
|
+
registry: streamRegistry,
|
|
50416
|
+
ring: streamRing,
|
|
50417
|
+
coalescer: createCoalescer({
|
|
50418
|
+
floors: INVALIDATION_FLOORS,
|
|
50419
|
+
defaultFloorMs: DEFAULT_FLOOR_MS,
|
|
50420
|
+
now,
|
|
50421
|
+
sink: (topic, payload) => streamRegistry.publish(topic, {
|
|
50422
|
+
type: "event",
|
|
50423
|
+
topic,
|
|
50424
|
+
...payload === undefined ? {} : { payload }
|
|
50425
|
+
})
|
|
50426
|
+
})
|
|
50427
|
+
});
|
|
49426
50428
|
const app = createApp({
|
|
49427
50429
|
store,
|
|
49428
50430
|
baseUrl: config2.baseUrl,
|
|
@@ -49432,7 +50434,10 @@ async function main() {
|
|
|
49432
50434
|
staticDir,
|
|
49433
50435
|
logger: logger2,
|
|
49434
50436
|
console: console2,
|
|
49435
|
-
|
|
50437
|
+
registry: streamRegistry,
|
|
50438
|
+
broadcaster,
|
|
50439
|
+
ring: streamRing,
|
|
50440
|
+
channels: pluginChannels,
|
|
49436
50441
|
bodyLoggingAllowed: config2.bodyLoggingAllowed,
|
|
49437
50442
|
plugins: loadedPlugins.plugins.map((plugin) => ({ id: plugin.id, routes: plugin.routes })),
|
|
49438
50443
|
pluginUi: loadedPlugins.plugins,
|
|
@@ -49455,20 +50460,34 @@ async function main() {
|
|
|
49455
50460
|
}
|
|
49456
50461
|
});
|
|
49457
50462
|
const stopMaintenance = startMaintenance({ store, now, logger: logger2 });
|
|
49458
|
-
const stopRefreshScheduler = startRefreshScheduler({ store, refresh, now, logger: logger2 });
|
|
50463
|
+
const stopRefreshScheduler = startRefreshScheduler({ store, refresh, now, logger: logger2, broadcaster });
|
|
49459
50464
|
const stopQuotaPoller = await startQuotaPoller({
|
|
49460
50465
|
store,
|
|
49461
50466
|
providers: OAUTH_PROVIDERS,
|
|
49462
50467
|
http,
|
|
49463
50468
|
refresh,
|
|
49464
50469
|
now,
|
|
49465
|
-
logger: logger2
|
|
50470
|
+
logger: logger2,
|
|
50471
|
+
broadcaster
|
|
49466
50472
|
});
|
|
50473
|
+
const stopConsoleStream = startConsoleStream({ console: console2, broadcaster, logger: logger2, now });
|
|
49467
50474
|
app.listen({ port: config2.port, hostname: config2.host, idleTimeout: 255 });
|
|
49468
50475
|
logger2.info("omnigateway listening", { host: config2.host, port: config2.port });
|
|
49469
50476
|
shutdown = createShutdown({
|
|
49470
50477
|
logger: logger2,
|
|
49471
|
-
stopLoops: [
|
|
50478
|
+
stopLoops: [
|
|
50479
|
+
stopMaintenance,
|
|
50480
|
+
stopRefreshScheduler,
|
|
50481
|
+
stopQuotaPoller,
|
|
50482
|
+
stopConsoleStream,
|
|
50483
|
+
() => pluginEvents.stop(),
|
|
50484
|
+
() => {
|
|
50485
|
+
streamRegistry.closeAll(1001, "restart");
|
|
50486
|
+
streamRegistry.stop();
|
|
50487
|
+
broadcaster.stop();
|
|
50488
|
+
pluginChannels.stop();
|
|
50489
|
+
}
|
|
50490
|
+
],
|
|
49472
50491
|
stopServer: async () => {
|
|
49473
50492
|
await app.stop();
|
|
49474
50493
|
},
|