omnigateway 0.9.1 → 0.9.2
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 +12 -4
- package/bin/omni.js +37 -5
- package/gateway.js +618 -62
- package/package.json +1 -1
- package/public/assets/{Chip-DJkUCwNJ.js → Chip-Did4Zhvj.js} +1 -1
- package/public/assets/{CopyValue-CJ3ZYgvs.js → CopyValue-Cjg9QXrD.js} +1 -1
- package/public/assets/{Lamp-D7hJcQ90.js → Lamp-C2Q3t5Mn.js} +1 -1
- package/public/assets/{Rack-CaxD9IY1.js → Rack-D0_N-xsd.js} +1 -1
- package/public/assets/{RequestTable-bXapHDrR.js → RequestTable-i9paP_7e.js} +1 -1
- package/public/assets/{SummaryDeck-Dt_qbFtg.js → SummaryDeck-g4yLDeXB.js} +1 -1
- package/public/assets/{Toggle-C_iC6W50.js → Toggle-D-NDlneh.js} +1 -1
- package/public/assets/{TokenBreakdown-C96x_g6d.js → TokenBreakdown-D5ZIngM6.js} +1 -1
- package/public/assets/{WindowChart-D4dcv4DX.js → WindowChart-BsonO5Py.js} +1 -1
- package/public/assets/{_app.accounts-CGFOyYZc.js → _app.accounts-DFhyE62a.js} +1 -1
- package/public/assets/{_app.console-DrCT8eLd.js → _app.console-BfHcfQMp.js} +1 -1
- package/public/assets/{_app.database-D32je-GL.js → _app.database-Cgw9z3rK.js} +1 -1
- package/public/assets/{_app.index-BG6mGxkv.js → _app.index-Blw3S4-3.js} +1 -1
- package/public/assets/{_app.keys-C1AjjeHc.js → _app.keys-DrdfnfIk.js} +1 -1
- package/public/assets/{_app.logs-BDLj_H_j.js → _app.logs-DUOqlJzB.js} +1 -1
- package/public/assets/{_app.models-Cgg7gVSF.js → _app.models-Dm83HRaM.js} +1 -1
- package/public/assets/{_app.plugins._pluginId-C5FSNOfN.js → _app.plugins._pluginId-BmJGDYxw.js} +1 -1
- package/public/assets/{_app.settings-B7lKNDoh.js → _app.settings-66rm4vHJ.js} +1 -1
- package/public/assets/{_app.usage-DjqlIHDL.js → _app.usage-BCufy5XR.js} +1 -1
- package/public/assets/{chevron-right-DJj6SEIT.js → chevron-right-BZ4QmUDM.js} +1 -1
- package/public/assets/{client-Q2fvA2EK.js → client-De1_cEH5.js} +1 -1
- package/public/assets/{index-D1ewvjU0.js → index-PP8vsvw3.js} +3 -3
- package/public/assets/{login-Br_AimWR.js → login-CkZlwcdd.js} +1 -1
- package/public/assets/plus-D5AwIXj3.js +1 -0
- package/public/assets/{preload-helper-92RRH0nq.js → preload-helper-BC9bKUg1.js} +1 -1
- package/public/assets/{shared-Cp_kzH_a.js → shared-C47BfV_u.js} +1 -1
- package/public/assets/{trash-2-Chg8CwQf.js → trash-2-BLc5o7DN.js} +1 -1
- package/public/index.html +4 -4
- package/public/assets/plus-CCwmaxw3.js +0 -1
package/gateway.js
CHANGED
|
@@ -29520,6 +29520,19 @@ function loadConfig(env) {
|
|
|
29520
29520
|
const bodyLoggingAllowed = TRUTHY.has((env.OMNI_BODY_LOGGING_ALLOWED ?? "").trim().toLowerCase());
|
|
29521
29521
|
const rawLogLevel = env.OMNI_LOG_LEVEL?.trim();
|
|
29522
29522
|
const logLevel = parseLogLevel(rawLogLevel);
|
|
29523
|
+
const metricsToken = env.OMNI_METRICS_TOKEN?.trim() || null;
|
|
29524
|
+
const rawMaxSeries = env.OMNI_METRICS_MAX_SERIES ?? "5000";
|
|
29525
|
+
const metricsMaxSeries = Number(rawMaxSeries);
|
|
29526
|
+
if (!DECIMAL_INTEGER.test(rawMaxSeries) || metricsMaxSeries < 1) {
|
|
29527
|
+
throw new Error(`OMNI_METRICS_MAX_SERIES must be a positive integer, got "${rawMaxSeries}"`);
|
|
29528
|
+
}
|
|
29529
|
+
const otlpEndpoint = env.OMNI_OTLP_ENDPOINT?.trim().replace(/\/+$/, "") || null;
|
|
29530
|
+
const otlpHeaders = env.OMNI_OTLP_HEADERS?.trim() || null;
|
|
29531
|
+
const rawTraceSample = env.OMNI_TRACE_SAMPLE ?? "1.0";
|
|
29532
|
+
const traceSample = Number(rawTraceSample);
|
|
29533
|
+
if (!Number.isFinite(traceSample) || traceSample < 0 || traceSample > 1) {
|
|
29534
|
+
throw new Error(`OMNI_TRACE_SAMPLE must be between 0 and 1, got "${rawTraceSample}"`);
|
|
29535
|
+
}
|
|
29523
29536
|
return {
|
|
29524
29537
|
logLevel: logLevel ?? "info",
|
|
29525
29538
|
logLevelFallbackFrom: logLevel === null && rawLogLevel ? rawLogLevel : null,
|
|
@@ -29533,7 +29546,12 @@ function loadConfig(env) {
|
|
|
29533
29546
|
logFile: logFile === undefined || logFile.length === 0 ? null : logFile,
|
|
29534
29547
|
clusterMode,
|
|
29535
29548
|
databaseUrl: clusterMode ? databaseUrl ?? null : null,
|
|
29536
|
-
redisUrl: clusterMode ? redisUrl ?? null : null
|
|
29549
|
+
redisUrl: clusterMode ? redisUrl ?? null : null,
|
|
29550
|
+
metricsToken,
|
|
29551
|
+
metricsMaxSeries,
|
|
29552
|
+
otlpEndpoint,
|
|
29553
|
+
otlpHeaders,
|
|
29554
|
+
traceSample
|
|
29537
29555
|
};
|
|
29538
29556
|
}
|
|
29539
29557
|
// packages/control/src/oauth/pending.ts
|
|
@@ -29732,7 +29750,8 @@ function codecAdapter(id, capabilities2, codec2, origins) {
|
|
|
29732
29750
|
const res = await req.http({
|
|
29733
29751
|
provider: id,
|
|
29734
29752
|
...sendable,
|
|
29735
|
-
signal: req.signal
|
|
29753
|
+
signal: req.signal,
|
|
29754
|
+
...req.requestId === undefined ? {} : { requestId: req.requestId }
|
|
29736
29755
|
});
|
|
29737
29756
|
if (res.status < 200 || res.status >= 300) {
|
|
29738
29757
|
const text = await res.text().catch(() => "");
|
|
@@ -33117,15 +33136,28 @@ function nodeHttpClient(options = {}) {
|
|
|
33117
33136
|
const startedAt = now();
|
|
33118
33137
|
let traced = false;
|
|
33119
33138
|
const trace = (status, failed = false) => {
|
|
33120
|
-
if (traced
|
|
33139
|
+
if (traced)
|
|
33121
33140
|
return;
|
|
33122
33141
|
traced = true;
|
|
33142
|
+
const durationMs = now() - startedAt;
|
|
33143
|
+
try {
|
|
33144
|
+
options.onResponseHead?.({
|
|
33145
|
+
provider: req.provider,
|
|
33146
|
+
host: url2.host,
|
|
33147
|
+
path: url2.pathname,
|
|
33148
|
+
...status === undefined ? {} : { status },
|
|
33149
|
+
durationMs,
|
|
33150
|
+
...req.requestId === undefined ? {} : { requestId: req.requestId }
|
|
33151
|
+
});
|
|
33152
|
+
} catch {}
|
|
33153
|
+
if (!logger2.enabled("debug"))
|
|
33154
|
+
return;
|
|
33123
33155
|
logger2.debug("upstream http", {
|
|
33124
33156
|
provider: req.provider,
|
|
33125
33157
|
status,
|
|
33126
33158
|
host: url2.host,
|
|
33127
33159
|
path: url2.pathname,
|
|
33128
|
-
durationMs
|
|
33160
|
+
durationMs,
|
|
33129
33161
|
reason: failed ? "transport error" : undefined
|
|
33130
33162
|
});
|
|
33131
33163
|
};
|
|
@@ -36847,6 +36879,7 @@ var WINDOW_MS2 = {
|
|
|
36847
36879
|
"1w": 7 * 24 * 60 * 60 * 1000
|
|
36848
36880
|
};
|
|
36849
36881
|
// apps/gateway/src/app.ts
|
|
36882
|
+
import { timingSafeEqual } from "crypto";
|
|
36850
36883
|
import { existsSync, realpathSync as realpathSync3 } from "fs";
|
|
36851
36884
|
import { resolve as resolve3, sep as sep3 } from "path";
|
|
36852
36885
|
|
|
@@ -51324,11 +51357,13 @@ class ApiKeyRateLimiter {
|
|
|
51324
51357
|
store;
|
|
51325
51358
|
now;
|
|
51326
51359
|
logger;
|
|
51360
|
+
onRejected;
|
|
51327
51361
|
constructor(deps) {
|
|
51328
51362
|
this.store = deps.store;
|
|
51329
51363
|
this.now = deps.now;
|
|
51330
51364
|
this.logger = deps.logger ?? noopLogger;
|
|
51331
51365
|
this.coord = deps.coord ?? memoryCoord();
|
|
51366
|
+
this.onRejected = deps.onRejected;
|
|
51332
51367
|
}
|
|
51333
51368
|
async admit(keyId, limits, requestId) {
|
|
51334
51369
|
if (!anyLimit(limits))
|
|
@@ -51403,6 +51438,9 @@ class ApiKeyRateLimiter {
|
|
|
51403
51438
|
const exact = await this.exactReset(keyId, window2, now, requestId);
|
|
51404
51439
|
const resolved = exact === null ? violation : { ...violation, resetAt: exact };
|
|
51405
51440
|
const headroom = exact === null || window2 === null ? decision.headroom : withReset(decision.headroom, window2, exact);
|
|
51441
|
+
try {
|
|
51442
|
+
this.onRejected?.(violation.dimension, violation.window);
|
|
51443
|
+
} catch {}
|
|
51406
51444
|
throw new RateLimitExceeded(retryAfterMs(resolved, now), headroom);
|
|
51407
51445
|
}
|
|
51408
51446
|
async exactReset(keyId, window2, now, requestId) {
|
|
@@ -51501,6 +51539,61 @@ function withReset(headroom, window2, resetAt) {
|
|
|
51501
51539
|
return corrected;
|
|
51502
51540
|
}
|
|
51503
51541
|
|
|
51542
|
+
// apps/gateway/src/dispatch/loadRegistry.ts
|
|
51543
|
+
var PREFIX6 = "load:";
|
|
51544
|
+
var SLOT_TTL_MS = 300000;
|
|
51545
|
+
function createLoadRegistry(coord = memoryCoord()) {
|
|
51546
|
+
const local = new Map;
|
|
51547
|
+
const providers = new Map;
|
|
51548
|
+
let remote = new Map;
|
|
51549
|
+
return {
|
|
51550
|
+
acquire(credentialId, model, provider) {
|
|
51551
|
+
const key = healthKey(credentialId, model);
|
|
51552
|
+
local.set(key, (local.get(key) ?? 0) + 1);
|
|
51553
|
+
if (provider !== undefined)
|
|
51554
|
+
providers.set(provider, (providers.get(provider) ?? 0) + 1);
|
|
51555
|
+
coord.gauge.acquire(PREFIX6 + key, SLOT_TTL_MS);
|
|
51556
|
+
let released = false;
|
|
51557
|
+
return () => {
|
|
51558
|
+
if (released)
|
|
51559
|
+
return;
|
|
51560
|
+
released = true;
|
|
51561
|
+
const next = (local.get(key) ?? 0) - 1;
|
|
51562
|
+
if (next > 0)
|
|
51563
|
+
local.set(key, next);
|
|
51564
|
+
else
|
|
51565
|
+
local.delete(key);
|
|
51566
|
+
if (provider !== undefined) {
|
|
51567
|
+
const providerCount = (providers.get(provider) ?? 0) - 1;
|
|
51568
|
+
if (providerCount > 0)
|
|
51569
|
+
providers.set(provider, providerCount);
|
|
51570
|
+
else
|
|
51571
|
+
providers.delete(provider);
|
|
51572
|
+
}
|
|
51573
|
+
coord.gauge.release(PREFIX6 + key);
|
|
51574
|
+
};
|
|
51575
|
+
},
|
|
51576
|
+
counts() {
|
|
51577
|
+
const out = new Map(local);
|
|
51578
|
+
for (const [key, count] of remote) {
|
|
51579
|
+
if (count > (out.get(key) ?? 0))
|
|
51580
|
+
out.set(key, count);
|
|
51581
|
+
}
|
|
51582
|
+
return out;
|
|
51583
|
+
},
|
|
51584
|
+
localCounts() {
|
|
51585
|
+
return new Map(providers);
|
|
51586
|
+
},
|
|
51587
|
+
async refresh() {
|
|
51588
|
+
const held = await coord.gauge.snapshot(PREFIX6);
|
|
51589
|
+
const sample = new Map;
|
|
51590
|
+
for (const [key, count] of held)
|
|
51591
|
+
sample.set(key.slice(PREFIX6.length), count);
|
|
51592
|
+
remote = sample;
|
|
51593
|
+
}
|
|
51594
|
+
};
|
|
51595
|
+
}
|
|
51596
|
+
|
|
51504
51597
|
// apps/gateway/src/dispatch/snapshotCache.ts
|
|
51505
51598
|
function createRoutingSnapshotCache(store, logger2 = noopLogger) {
|
|
51506
51599
|
let snapshot = null;
|
|
@@ -55473,7 +55566,7 @@ async function routeLog(store, requestId, target, logger2 = noopLogger, broadcas
|
|
|
55473
55566
|
}
|
|
55474
55567
|
}
|
|
55475
55568
|
}
|
|
55476
|
-
async function finishLog(store, log, keyId, logger2 = noopLogger, bodies, debit, emit, broadcast) {
|
|
55569
|
+
async function finishLog(store, log, keyId, logger2 = noopLogger, bodies, debit, emit, broadcast, telemetry, trace) {
|
|
55477
55570
|
let appended = true;
|
|
55478
55571
|
try {
|
|
55479
55572
|
await store.usage.append({ ...log, apiKeyId: keyId });
|
|
@@ -55517,15 +55610,108 @@ async function finishLog(store, log, keyId, logger2 = noopLogger, bodies, debit,
|
|
|
55517
55610
|
report(logger2, "failed to publish a resource invalidation", log.id, error61);
|
|
55518
55611
|
}
|
|
55519
55612
|
}
|
|
55520
|
-
if (bodies
|
|
55521
|
-
|
|
55613
|
+
if (bodies !== undefined) {
|
|
55614
|
+
try {
|
|
55615
|
+
await bodies(log);
|
|
55616
|
+
} catch (error61) {
|
|
55617
|
+
report(logger2, "failed to persist request bodies", log.id, error61);
|
|
55618
|
+
}
|
|
55619
|
+
}
|
|
55522
55620
|
try {
|
|
55523
|
-
|
|
55621
|
+
telemetry?.record(log, keyId, trace ?? null);
|
|
55524
55622
|
} catch (error61) {
|
|
55525
|
-
report(logger2, "failed to
|
|
55623
|
+
report(logger2, "failed to record request telemetry", log.id, error61);
|
|
55624
|
+
}
|
|
55625
|
+
try {
|
|
55626
|
+
telemetry?.flush(log.id, trace ?? null);
|
|
55627
|
+
} catch (error61) {
|
|
55628
|
+
report(logger2, "failed to export request telemetry", log.id, error61);
|
|
55526
55629
|
}
|
|
55527
55630
|
}
|
|
55528
55631
|
|
|
55632
|
+
// apps/gateway/src/telemetry/spans.ts
|
|
55633
|
+
var TRACEPARENT = /^[\da-f]{2}-([\da-f]{32})-([\da-f]{16})-([\da-f]{2})(?:-[\da-f]+)*$/i;
|
|
55634
|
+
var ZERO_TRACE = "0".repeat(32);
|
|
55635
|
+
var ZERO_SPAN = "0".repeat(16);
|
|
55636
|
+
function parseTraceparent(value) {
|
|
55637
|
+
if (value === null)
|
|
55638
|
+
return null;
|
|
55639
|
+
const match = TRACEPARENT.exec(value);
|
|
55640
|
+
const traceId = match?.[1]?.toLowerCase();
|
|
55641
|
+
const parentSpanId = match?.[2]?.toLowerCase();
|
|
55642
|
+
const flags = match?.[3];
|
|
55643
|
+
if (traceId === undefined || parentSpanId === undefined || flags === undefined)
|
|
55644
|
+
return null;
|
|
55645
|
+
if (traceId === ZERO_TRACE || parentSpanId === ZERO_SPAN)
|
|
55646
|
+
return null;
|
|
55647
|
+
return { traceId, parentSpanId, sampled: (Number.parseInt(flags, 16) & 1) === 1 };
|
|
55648
|
+
}
|
|
55649
|
+
function randomHex(bytes) {
|
|
55650
|
+
const out = new Uint8Array(bytes);
|
|
55651
|
+
crypto.getRandomValues(out);
|
|
55652
|
+
return [...out].map((byte2) => byte2.toString(16).padStart(2, "0")).join("");
|
|
55653
|
+
}
|
|
55654
|
+
function createTrace(opts) {
|
|
55655
|
+
const id = opts.id ?? (() => randomHex(8));
|
|
55656
|
+
return {
|
|
55657
|
+
startedAt: opts.startedAt,
|
|
55658
|
+
traceId: opts.traceparent?.traceId ?? `${id()}${id()}`.slice(0, 32),
|
|
55659
|
+
parentSpanId: opts.traceparent?.parentSpanId ?? null,
|
|
55660
|
+
spanIds: [],
|
|
55661
|
+
spans: [],
|
|
55662
|
+
activeAttempt: null,
|
|
55663
|
+
routeSpan: null,
|
|
55664
|
+
nextId: id
|
|
55665
|
+
};
|
|
55666
|
+
}
|
|
55667
|
+
function addSpan(trace, name, parent, startMs, endMs, attrs) {
|
|
55668
|
+
try {
|
|
55669
|
+
const id = trace.spans.length;
|
|
55670
|
+
trace.spanIds.push(trace.nextId());
|
|
55671
|
+
trace.spans.push({ id, parent, name, startMs, endMs, attrs });
|
|
55672
|
+
return id;
|
|
55673
|
+
} catch {
|
|
55674
|
+
return -1;
|
|
55675
|
+
}
|
|
55676
|
+
}
|
|
55677
|
+
function otlpAttrs(attrs) {
|
|
55678
|
+
const out = [];
|
|
55679
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
55680
|
+
if (value === undefined)
|
|
55681
|
+
continue;
|
|
55682
|
+
out.push(typeof value === "number" ? { key, value: { intValue: String(value) } } : { key, value: { stringValue: value } });
|
|
55683
|
+
}
|
|
55684
|
+
return out;
|
|
55685
|
+
}
|
|
55686
|
+
function encodeTrace(trace, serviceVersion) {
|
|
55687
|
+
const spans = trace.spans.map((span) => {
|
|
55688
|
+
const parentSpanId = span.parent === null ? trace.parentSpanId : trace.spanIds[span.parent] ?? trace.parentSpanId;
|
|
55689
|
+
return {
|
|
55690
|
+
traceId: trace.traceId,
|
|
55691
|
+
spanId: trace.spanIds[span.id] ?? trace.nextId(),
|
|
55692
|
+
...parentSpanId === null ? {} : { parentSpanId },
|
|
55693
|
+
name: span.name,
|
|
55694
|
+
startTimeUnixNano: String(BigInt(Math.round(trace.startedAt + span.startMs)) * 1000000n),
|
|
55695
|
+
endTimeUnixNano: String(BigInt(Math.round(trace.startedAt + span.endMs)) * 1000000n),
|
|
55696
|
+
attributes: otlpAttrs(span.attrs),
|
|
55697
|
+
status: { code: span.attrs.code === undefined ? 0 : 2 }
|
|
55698
|
+
};
|
|
55699
|
+
});
|
|
55700
|
+
return {
|
|
55701
|
+
resourceSpans: [
|
|
55702
|
+
{
|
|
55703
|
+
resource: {
|
|
55704
|
+
attributes: [
|
|
55705
|
+
{ key: "service.name", value: { stringValue: "omnigateway" } },
|
|
55706
|
+
{ key: "service.version", value: { stringValue: serviceVersion } }
|
|
55707
|
+
]
|
|
55708
|
+
},
|
|
55709
|
+
scopeSpans: [{ spans }]
|
|
55710
|
+
}
|
|
55711
|
+
]
|
|
55712
|
+
};
|
|
55713
|
+
}
|
|
55714
|
+
|
|
55529
55715
|
// apps/gateway/src/dispatch/attempt.ts
|
|
55530
55716
|
async function attempt(opts) {
|
|
55531
55717
|
const { candidate, adapter, http, now, signal, refresh, refreshLeadMs } = opts;
|
|
@@ -55708,6 +55894,7 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
55708
55894
|
return fail(code, describeError(error61, "unresolvable model"));
|
|
55709
55895
|
}
|
|
55710
55896
|
await deps.loadRegistry.refresh();
|
|
55897
|
+
const routeStartedAt = deps.now();
|
|
55711
55898
|
const { candidates, excluded } = rank({
|
|
55712
55899
|
request: dispatchRequest,
|
|
55713
55900
|
model,
|
|
@@ -55717,6 +55904,15 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
55717
55904
|
load: deps.loadRegistry.counts(),
|
|
55718
55905
|
providers: deps.providers
|
|
55719
55906
|
});
|
|
55907
|
+
if (deps.trace !== null && deps.trace !== undefined) {
|
|
55908
|
+
deps.trace.routeSpan = addSpan(deps.trace, "dispatch.route", 0, routeStartedAt - startedAt, deps.now() - startedAt, {
|
|
55909
|
+
candidates: candidates.length,
|
|
55910
|
+
...candidates[0] === undefined ? {} : {
|
|
55911
|
+
chosen_provider: candidates[0].target.provider,
|
|
55912
|
+
chosen_model: candidates[0].target.model
|
|
55913
|
+
}
|
|
55914
|
+
});
|
|
55915
|
+
}
|
|
55720
55916
|
logger2.debug("routing candidates ranked", {
|
|
55721
55917
|
requestId,
|
|
55722
55918
|
requestedModel: request2.model,
|
|
@@ -55744,7 +55940,7 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
55744
55940
|
const eager = {
|
|
55745
55941
|
credentialId: head.credential.id,
|
|
55746
55942
|
model: head.target.model,
|
|
55747
|
-
release: deps.loadRegistry.acquire(head.credential.id, head.target.model)
|
|
55943
|
+
release: deps.loadRegistry.acquire(head.credential.id, head.target.model, head.target.provider)
|
|
55748
55944
|
};
|
|
55749
55945
|
let eagerHeld = true;
|
|
55750
55946
|
releaseOnAbort = () => eager.release();
|
|
@@ -55755,7 +55951,14 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
55755
55951
|
const persistHealth = async (candidate, transition) => {
|
|
55756
55952
|
const credentialId = candidate.credential.id;
|
|
55757
55953
|
const { model: model2 } = candidate.target;
|
|
55758
|
-
await deps.store.credentials.updateHealth(credentialId, model2, (current) =>
|
|
55954
|
+
await deps.store.credentials.updateHealth(credentialId, model2, (current) => {
|
|
55955
|
+
const before = current ?? blankHealth(credentialId, model2);
|
|
55956
|
+
const after = transition(before);
|
|
55957
|
+
try {
|
|
55958
|
+
deps.telemetry?.breaker(candidate.target.provider, before.breakerState, after.breakerState);
|
|
55959
|
+
} catch {}
|
|
55960
|
+
return after;
|
|
55961
|
+
});
|
|
55759
55962
|
};
|
|
55760
55963
|
async function* run() {
|
|
55761
55964
|
try {
|
|
@@ -55774,7 +55977,17 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
55774
55977
|
const adoptable = eagerHeld && eager.credentialId === candidate.credential.id && eager.model === candidate.target.model;
|
|
55775
55978
|
if (adoptable)
|
|
55776
55979
|
eagerHeld = false;
|
|
55777
|
-
const releaseSlot = adoptable ? eager.release : deps.loadRegistry.acquire(candidate.credential.id, candidate.target.model);
|
|
55980
|
+
const releaseSlot = adoptable ? eager.release : deps.loadRegistry.acquire(candidate.credential.id, candidate.target.model, candidate.target.provider);
|
|
55981
|
+
const attemptStartedAt = deps.now();
|
|
55982
|
+
let attemptCode;
|
|
55983
|
+
if (deps.trace !== null && deps.trace !== undefined) {
|
|
55984
|
+
deps.trace.activeAttempt = addSpan(deps.trace, "dispatch.attempt", 0, attemptStartedAt - startedAt, attemptStartedAt - startedAt, {
|
|
55985
|
+
attempt: i + 1,
|
|
55986
|
+
provider: candidate.target.provider,
|
|
55987
|
+
model: candidate.target.model,
|
|
55988
|
+
credential_id: candidate.credential.id
|
|
55989
|
+
});
|
|
55990
|
+
}
|
|
55778
55991
|
try {
|
|
55779
55992
|
log.attempts = i + 1;
|
|
55780
55993
|
log.credentialId = candidate.credential.id;
|
|
@@ -55838,6 +56051,9 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
55838
56051
|
if (event.type === "blockDelta" && !committed) {
|
|
55839
56052
|
committed = true;
|
|
55840
56053
|
log.ttftMs = deps.now() - startedAt;
|
|
56054
|
+
if (deps.trace !== null && deps.trace !== undefined && deps.trace.activeAttempt !== null) {
|
|
56055
|
+
addSpan(deps.trace, "stream.commit", deps.trace.activeAttempt, log.ttftMs, log.ttftMs, { provider: candidate.target.provider, model: candidate.target.model });
|
|
56056
|
+
}
|
|
55841
56057
|
logger2.debug("stream committed", {
|
|
55842
56058
|
requestId,
|
|
55843
56059
|
provider: candidate.target.provider,
|
|
@@ -55920,6 +56136,7 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
55920
56136
|
throw signal.reason;
|
|
55921
56137
|
const classifiedError = deadlineAt !== null && dispatchSignal.aborted ? { code: "TIMEOUT" } : classify(error61);
|
|
55922
56138
|
const { code: code2 } = classifiedError;
|
|
56139
|
+
attemptCode = code2;
|
|
55923
56140
|
const message2 = describeError(error61, "attempt failed");
|
|
55924
56141
|
lastError = rewrap(classifiedError, message2);
|
|
55925
56142
|
if (error61 instanceof GatewayError)
|
|
@@ -55993,6 +56210,15 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
55993
56210
|
}
|
|
55994
56211
|
}
|
|
55995
56212
|
} finally {
|
|
56213
|
+
if (deps.trace !== null && deps.trace !== undefined && deps.trace.activeAttempt !== null) {
|
|
56214
|
+
const span = deps.trace.spans[deps.trace.activeAttempt];
|
|
56215
|
+
if (span !== undefined) {
|
|
56216
|
+
span.endMs = deps.now() - startedAt;
|
|
56217
|
+
if (attemptCode !== undefined)
|
|
56218
|
+
span.attrs = { ...span.attrs, code: attemptCode };
|
|
56219
|
+
}
|
|
56220
|
+
deps.trace.activeAttempt = null;
|
|
56221
|
+
}
|
|
55996
56222
|
releaseSlot();
|
|
55997
56223
|
}
|
|
55998
56224
|
}
|
|
@@ -56064,48 +56290,6 @@ function waitForCancellation(promise2, signal) {
|
|
|
56064
56290
|
});
|
|
56065
56291
|
}
|
|
56066
56292
|
|
|
56067
|
-
// apps/gateway/src/dispatch/loadRegistry.ts
|
|
56068
|
-
var PREFIX6 = "load:";
|
|
56069
|
-
var SLOT_TTL_MS = 300000;
|
|
56070
|
-
function createLoadRegistry(coord = memoryCoord()) {
|
|
56071
|
-
const local = new Map;
|
|
56072
|
-
let remote = new Map;
|
|
56073
|
-
return {
|
|
56074
|
-
acquire(credentialId, model) {
|
|
56075
|
-
const key = healthKey(credentialId, model);
|
|
56076
|
-
local.set(key, (local.get(key) ?? 0) + 1);
|
|
56077
|
-
coord.gauge.acquire(PREFIX6 + key, SLOT_TTL_MS);
|
|
56078
|
-
let released = false;
|
|
56079
|
-
return () => {
|
|
56080
|
-
if (released)
|
|
56081
|
-
return;
|
|
56082
|
-
released = true;
|
|
56083
|
-
const next = (local.get(key) ?? 0) - 1;
|
|
56084
|
-
if (next > 0)
|
|
56085
|
-
local.set(key, next);
|
|
56086
|
-
else
|
|
56087
|
-
local.delete(key);
|
|
56088
|
-
coord.gauge.release(PREFIX6 + key);
|
|
56089
|
-
};
|
|
56090
|
-
},
|
|
56091
|
-
counts() {
|
|
56092
|
-
const out = new Map(local);
|
|
56093
|
-
for (const [key, count] of remote) {
|
|
56094
|
-
if (count > (out.get(key) ?? 0))
|
|
56095
|
-
out.set(key, count);
|
|
56096
|
-
}
|
|
56097
|
-
return out;
|
|
56098
|
-
},
|
|
56099
|
-
async refresh() {
|
|
56100
|
-
const held = await coord.gauge.snapshot(PREFIX6);
|
|
56101
|
-
const sample = new Map;
|
|
56102
|
-
for (const [key, count] of held)
|
|
56103
|
-
sample.set(key.slice(PREFIX6.length), count);
|
|
56104
|
-
remote = sample;
|
|
56105
|
-
}
|
|
56106
|
-
};
|
|
56107
|
-
}
|
|
56108
|
-
|
|
56109
56293
|
// apps/gateway/src/ingress/anthropicTools.ts
|
|
56110
56294
|
var TOOL_FIELDS = {
|
|
56111
56295
|
cache_control: cacheControlSchema.nullable(),
|
|
@@ -57330,6 +57514,7 @@ async function bodyCollectorFor(deps, key) {
|
|
|
57330
57514
|
async function handle(deps, rateLimiter, surface, request2) {
|
|
57331
57515
|
const requestId = deps.requestId();
|
|
57332
57516
|
const startedAt = deps.now();
|
|
57517
|
+
const trace = deps.telemetry?.startRequest(requestId, startedAt, request2.headers.get("traceparent"), surface, deps.rand()) ?? null;
|
|
57333
57518
|
let keyId = null;
|
|
57334
57519
|
let requestedModel = "";
|
|
57335
57520
|
let outcome = null;
|
|
@@ -57411,6 +57596,7 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
57411
57596
|
const dispatched = await dispatch(chatRequest, {
|
|
57412
57597
|
...deps,
|
|
57413
57598
|
...captured === null ? {} : { http: captured.wrap(deps.http) },
|
|
57599
|
+
trace,
|
|
57414
57600
|
async onRoute(target) {
|
|
57415
57601
|
if (began) {
|
|
57416
57602
|
await routeLog(deps.store, requestId, target, deps.logger, deps.broadcaster);
|
|
@@ -57448,7 +57634,7 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
57448
57634
|
reportRejection(deps.logger, requestId, completed, gatewayError, surface);
|
|
57449
57635
|
}
|
|
57450
57636
|
logged = true;
|
|
57451
|
-
await finishLog(deps.store, completed, keyId, deps.logger, writeBodies, debit, deps.emit, deps.broadcaster);
|
|
57637
|
+
await finishLog(deps.store, completed, keyId, deps.logger, writeBodies, debit, deps.emit, deps.broadcaster, deps.telemetry, trace);
|
|
57452
57638
|
};
|
|
57453
57639
|
if (chatRequest.stream) {
|
|
57454
57640
|
const frames = surface === "anthropic" ? anthropicStream(dispatched.events, requestId) : surface === "responses" ? responsesStream(dispatched.events, render2()) : openaiStream(dispatched.events, requestId, Math.floor(deps.now() / 1000));
|
|
@@ -57489,7 +57675,7 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
57489
57675
|
if (collector !== null && !logged)
|
|
57490
57676
|
collector.client.response = rejection;
|
|
57491
57677
|
if (!logged)
|
|
57492
|
-
await finishLog(deps.store, completed, keyId, deps.logger, writeBodies, debit, deps.emit, deps.broadcaster);
|
|
57678
|
+
await finishLog(deps.store, completed, keyId, deps.logger, writeBodies, debit, deps.emit, deps.broadcaster, deps.telemetry, trace);
|
|
57493
57679
|
if (!cancelled)
|
|
57494
57680
|
reportRejection(deps.logger, requestId, completed, gatewayError, surface);
|
|
57495
57681
|
return jsonResponse(rejection, HTTP_STATUS[gatewayError.code], {
|
|
@@ -58457,7 +58643,7 @@ function createRing(limits) {
|
|
|
58457
58643
|
}
|
|
58458
58644
|
|
|
58459
58645
|
// apps/gateway/src/version.ts
|
|
58460
|
-
var VERSION = "0.9.
|
|
58646
|
+
var VERSION = "0.9.2";
|
|
58461
58647
|
|
|
58462
58648
|
// apps/gateway/src/app.ts
|
|
58463
58649
|
var ADMIN_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
|
|
@@ -58495,10 +58681,22 @@ function createApp(deps) {
|
|
|
58495
58681
|
const nodeId = deps.nodeId ?? crypto.randomUUID();
|
|
58496
58682
|
const logger2 = deps.logger ?? noopLogger;
|
|
58497
58683
|
const rand = deps.rand ?? Math.random;
|
|
58498
|
-
const http = deps.http ?? nodeHttpClient({
|
|
58684
|
+
const http = deps.http ?? nodeHttpClient({
|
|
58685
|
+
logger: logger2,
|
|
58686
|
+
now,
|
|
58687
|
+
...deps.telemetry === undefined ? {} : { onResponseHead: deps.telemetry.httpHead }
|
|
58688
|
+
});
|
|
58499
58689
|
const adapters = deps.adapters ?? ADAPTERS;
|
|
58500
58690
|
const requestId = deps.requestId ?? (() => `req_${crypto.randomUUID()}`);
|
|
58501
|
-
const
|
|
58691
|
+
const loadRegistry = deps.loadRegistry ?? createLoadRegistry(coord);
|
|
58692
|
+
const telemetry = deps.telemetry;
|
|
58693
|
+
const rateLimiter = deps.rateLimiter ?? new ApiKeyRateLimiter({
|
|
58694
|
+
store: deps.store,
|
|
58695
|
+
now,
|
|
58696
|
+
logger: logger2,
|
|
58697
|
+
coord,
|
|
58698
|
+
...telemetry === undefined ? {} : { onRejected: telemetry.rateLimit }
|
|
58699
|
+
});
|
|
58502
58700
|
const snapshots = createRoutingSnapshotCache(deps.store, logger2);
|
|
58503
58701
|
const ROUTING_TOPIC = "routing";
|
|
58504
58702
|
deps.store.routing.subscribe((change) => {
|
|
@@ -58563,7 +58761,17 @@ function createApp(deps) {
|
|
|
58563
58761
|
ok: true,
|
|
58564
58762
|
mode: deps.mode ?? "single",
|
|
58565
58763
|
nodeId,
|
|
58566
|
-
coord:
|
|
58764
|
+
coord: deps.coordHealthy?.() === false ? "fallback" : "ok"
|
|
58765
|
+
})).use(deps.metricsToken === undefined || telemetry === undefined ? new Elysia : new Elysia().get("/metrics", ({ request: request2 }) => {
|
|
58766
|
+
const supplied = request2.headers.get("authorization");
|
|
58767
|
+
const expected = `Bearer ${deps.metricsToken}`;
|
|
58768
|
+
const actualBytes = Buffer.from(supplied ?? "");
|
|
58769
|
+
const expectedBytes = Buffer.from(expected);
|
|
58770
|
+
if (actualBytes.length !== expectedBytes.length || !timingSafeEqual(actualBytes, expectedBytes)) {
|
|
58771
|
+
return new Response(null, { status: 401 });
|
|
58772
|
+
}
|
|
58773
|
+
const coordFallback = deps.coordHealthy?.() === false;
|
|
58774
|
+
return new Response(telemetry.scrape(loadRegistry.localCounts?.() ?? new Map, registry2.stats(), coordFallback), { headers: { "content-type": "text/plain; version=0.0.4; charset=utf-8" } });
|
|
58567
58775
|
})).use(proxyRoutes({
|
|
58568
58776
|
store: deps.store,
|
|
58569
58777
|
snapshots,
|
|
@@ -58576,9 +58784,10 @@ function createApp(deps) {
|
|
|
58576
58784
|
rateLimiter,
|
|
58577
58785
|
logger: logger2,
|
|
58578
58786
|
coord,
|
|
58579
|
-
|
|
58787
|
+
loadRegistry,
|
|
58580
58788
|
bodyLoggingAllowed: deps.bodyLoggingAllowed === true,
|
|
58581
58789
|
...deps.emit === undefined ? {} : { emit: deps.emit },
|
|
58790
|
+
...telemetry === undefined ? {} : { telemetry },
|
|
58582
58791
|
broadcaster
|
|
58583
58792
|
})).use(adminRoutes({
|
|
58584
58793
|
store: deps.store,
|
|
@@ -59847,6 +60056,340 @@ function startJournalPoll(deps, source, logger2) {
|
|
|
59847
60056
|
};
|
|
59848
60057
|
}
|
|
59849
60058
|
|
|
60059
|
+
// apps/gateway/src/telemetry/otlp.ts
|
|
60060
|
+
function createOtlpExporter(opts) {
|
|
60061
|
+
const queue = [];
|
|
60062
|
+
const send = opts.fetch ?? fetch;
|
|
60063
|
+
const schedule = opts.schedule ?? ((run, ms) => {
|
|
60064
|
+
const timer = setInterval(run, ms);
|
|
60065
|
+
timer.unref?.();
|
|
60066
|
+
return () => clearInterval(timer);
|
|
60067
|
+
});
|
|
60068
|
+
let reporting = false;
|
|
60069
|
+
const exporter = {
|
|
60070
|
+
enqueue(trace) {
|
|
60071
|
+
try {
|
|
60072
|
+
const encoded = JSON.stringify(encodeTrace(trace, opts.version ?? "unknown"));
|
|
60073
|
+
if (queue.length >= opts.capacity) {
|
|
60074
|
+
opts.registry.add("omni_otlp_spans_dropped_total", { reason: "queue_full" });
|
|
60075
|
+
return;
|
|
60076
|
+
}
|
|
60077
|
+
queue.push(encoded);
|
|
60078
|
+
} catch {
|
|
60079
|
+
opts.registry.add("omni_otlp_spans_dropped_total", { reason: "encode" });
|
|
60080
|
+
}
|
|
60081
|
+
},
|
|
60082
|
+
async flush() {
|
|
60083
|
+
if (queue.length === 0)
|
|
60084
|
+
return;
|
|
60085
|
+
const encoded = queue.splice(0, opts.batchMax);
|
|
60086
|
+
try {
|
|
60087
|
+
const bodies = encoded.map((body2) => JSON.parse(body2));
|
|
60088
|
+
const resourceSpans = bodies.flatMap((body2) => body2.resourceSpans);
|
|
60089
|
+
const response = await send(`${opts.endpoint.replace(/\/+$/, "")}/v1/traces`, {
|
|
60090
|
+
method: "POST",
|
|
60091
|
+
headers: { "content-type": "application/json", ...opts.headers },
|
|
60092
|
+
body: JSON.stringify({ resourceSpans })
|
|
60093
|
+
});
|
|
60094
|
+
if (!response.ok)
|
|
60095
|
+
throw new Error(`collector returned ${response.status}`);
|
|
60096
|
+
reporting = false;
|
|
60097
|
+
} catch {
|
|
60098
|
+
if (!reporting)
|
|
60099
|
+
opts.logger?.warn("trace export failed", { reason: "collector unavailable" });
|
|
60100
|
+
reporting = true;
|
|
60101
|
+
}
|
|
60102
|
+
},
|
|
60103
|
+
queued: () => queue.length,
|
|
60104
|
+
stop() {
|
|
60105
|
+
cancel();
|
|
60106
|
+
}
|
|
60107
|
+
};
|
|
60108
|
+
const cancel = schedule(() => void exporter.flush(), opts.intervalMs);
|
|
60109
|
+
return exporter;
|
|
60110
|
+
}
|
|
60111
|
+
|
|
60112
|
+
// apps/gateway/src/telemetry/registry.ts
|
|
60113
|
+
var DEFINITIONS = {
|
|
60114
|
+
omni_requests_total: { help: "Requests completed by this process.", type: "counter" },
|
|
60115
|
+
omni_request_duration_seconds: { help: "Request duration in seconds.", type: "histogram" },
|
|
60116
|
+
omni_ttft_seconds: { help: "Streaming time to first token in seconds.", type: "histogram" },
|
|
60117
|
+
omni_tokens_total: { help: "Billable tokens completed by this process.", type: "counter" },
|
|
60118
|
+
omni_cost_usd_total: { help: "Request cost in US dollars.", type: "counter" },
|
|
60119
|
+
omni_upstream_duration_seconds: {
|
|
60120
|
+
help: "Provider response-head duration in seconds.",
|
|
60121
|
+
type: "histogram"
|
|
60122
|
+
},
|
|
60123
|
+
omni_inflight: { help: "Provider requests in flight in this process.", type: "gauge" },
|
|
60124
|
+
omni_breaker_open: { help: "Credential breakers observed open in this process.", type: "gauge" },
|
|
60125
|
+
omni_ratelimit_rejected_total: { help: "API-key rate-limit refusals.", type: "counter" },
|
|
60126
|
+
omni_stream_connections: { help: "Live stream connections in this process.", type: "gauge" },
|
|
60127
|
+
omni_stream_queued: { help: "Stream frames queued in this process.", type: "gauge" },
|
|
60128
|
+
omni_stream_dropped_total: { help: "Stream frames dropped in this process.", type: "counter" },
|
|
60129
|
+
omni_coord_fallback: {
|
|
60130
|
+
help: "Whether this process is using coordination fallback.",
|
|
60131
|
+
type: "gauge"
|
|
60132
|
+
},
|
|
60133
|
+
omni_metrics_series_folded_total: {
|
|
60134
|
+
help: "Metric series folded into api_key_id other.",
|
|
60135
|
+
type: "counter"
|
|
60136
|
+
},
|
|
60137
|
+
omni_otlp_spans_dropped_total: { help: "Spans dropped before OTLP export.", type: "counter" },
|
|
60138
|
+
omni_build_info: { help: "Gateway build information.", type: "gauge" }
|
|
60139
|
+
};
|
|
60140
|
+
var HISTOGRAM_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30, 60];
|
|
60141
|
+
function labelKey(labels) {
|
|
60142
|
+
return Object.entries(labels).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key.length}:${key}${value.length}:${value}`).join("");
|
|
60143
|
+
}
|
|
60144
|
+
function createMetricRegistry(opts) {
|
|
60145
|
+
const series = new Map;
|
|
60146
|
+
const rawSeries = new Set;
|
|
60147
|
+
const folded = new Set;
|
|
60148
|
+
const normalize = (name, labels) => {
|
|
60149
|
+
const copy = { ...labels };
|
|
60150
|
+
const raw = `${name}:${labelKey(copy)}`;
|
|
60151
|
+
if (rawSeries.has(raw))
|
|
60152
|
+
return copy;
|
|
60153
|
+
if (rawSeries.size < opts.maxSeries || copy.api_key_id === undefined) {
|
|
60154
|
+
rawSeries.add(raw);
|
|
60155
|
+
return copy;
|
|
60156
|
+
}
|
|
60157
|
+
copy.api_key_id = "other";
|
|
60158
|
+
if (!folded.has(raw)) {
|
|
60159
|
+
folded.add(raw);
|
|
60160
|
+
change("omni_metrics_series_folded_total", {}, 1, false);
|
|
60161
|
+
}
|
|
60162
|
+
return copy;
|
|
60163
|
+
};
|
|
60164
|
+
const change = (name, labels, value, replace) => {
|
|
60165
|
+
const normalized = normalize(name, labels);
|
|
60166
|
+
const key = labelKey(normalized);
|
|
60167
|
+
const table = series.get(name) ?? new Map;
|
|
60168
|
+
const current = table.get(key) ?? { labels: normalized, value: 0 };
|
|
60169
|
+
current.value = replace ? value : current.value + value;
|
|
60170
|
+
table.set(key, current);
|
|
60171
|
+
series.set(name, table);
|
|
60172
|
+
return current;
|
|
60173
|
+
};
|
|
60174
|
+
return {
|
|
60175
|
+
add(name, labels, value = 1) {
|
|
60176
|
+
change(name, labels, value, false);
|
|
60177
|
+
},
|
|
60178
|
+
set(name, labels, value) {
|
|
60179
|
+
change(name, labels, value, true);
|
|
60180
|
+
},
|
|
60181
|
+
observe(name, value, labels) {
|
|
60182
|
+
const current = change(name, labels, value, false);
|
|
60183
|
+
current.count = (current.count ?? 0) + 1;
|
|
60184
|
+
const buckets = current.buckets ?? HISTOGRAM_BUCKETS.map(() => 0);
|
|
60185
|
+
for (let i = 0;i < HISTOGRAM_BUCKETS.length; i++) {
|
|
60186
|
+
if (value <= HISTOGRAM_BUCKETS[i])
|
|
60187
|
+
buckets[i] = (buckets[i] ?? 0) + 1;
|
|
60188
|
+
}
|
|
60189
|
+
current.buckets = buckets;
|
|
60190
|
+
},
|
|
60191
|
+
value(name, labels) {
|
|
60192
|
+
return series.get(name)?.get(labelKey(labels))?.value ?? 0;
|
|
60193
|
+
},
|
|
60194
|
+
snapshot() {
|
|
60195
|
+
return {
|
|
60196
|
+
at: opts.now(),
|
|
60197
|
+
metrics: Object.entries(DEFINITIONS).map(([name, definition]) => ({
|
|
60198
|
+
name,
|
|
60199
|
+
...definition,
|
|
60200
|
+
samples: [...series.get(name)?.values() ?? []].map((sample) => definition.type === "histogram" ? {
|
|
60201
|
+
labels: sample.labels,
|
|
60202
|
+
value: sample.value,
|
|
60203
|
+
sum: sample.value,
|
|
60204
|
+
count: sample.count ?? 0,
|
|
60205
|
+
buckets: sample.buckets ?? HISTOGRAM_BUCKETS.map(() => 0)
|
|
60206
|
+
} : { labels: sample.labels, value: sample.value })
|
|
60207
|
+
})).filter((metric) => metric.samples.length > 0)
|
|
60208
|
+
};
|
|
60209
|
+
}
|
|
60210
|
+
};
|
|
60211
|
+
}
|
|
60212
|
+
|
|
60213
|
+
// apps/gateway/src/telemetry/render.ts
|
|
60214
|
+
function escapeLabel(value) {
|
|
60215
|
+
return value.replaceAll("\\", "\\\\").replaceAll(`
|
|
60216
|
+
`, "\\n").replaceAll('"', "\\\"");
|
|
60217
|
+
}
|
|
60218
|
+
function labels(labels2, extra) {
|
|
60219
|
+
const entries = Object.entries(labels2);
|
|
60220
|
+
if (extra !== undefined)
|
|
60221
|
+
entries.push([extra[0], extra[1]]);
|
|
60222
|
+
if (entries.length === 0)
|
|
60223
|
+
return "";
|
|
60224
|
+
return `{${entries.map(([key, value]) => `${key}="${escapeLabel(value)}"`).join(",")}}`;
|
|
60225
|
+
}
|
|
60226
|
+
function number4(value) {
|
|
60227
|
+
if (value === Number.POSITIVE_INFINITY)
|
|
60228
|
+
return "+Inf";
|
|
60229
|
+
if (value === Number.NEGATIVE_INFINITY)
|
|
60230
|
+
return "-Inf";
|
|
60231
|
+
if (Number.isNaN(value))
|
|
60232
|
+
return "NaN";
|
|
60233
|
+
return String(value);
|
|
60234
|
+
}
|
|
60235
|
+
function renderPrometheus(snapshot) {
|
|
60236
|
+
const out = [];
|
|
60237
|
+
for (const metric of snapshot.metrics) {
|
|
60238
|
+
out.push(`# HELP ${metric.name} ${metric.help}`, `# TYPE ${metric.name} ${metric.type}`);
|
|
60239
|
+
for (const sample of metric.samples) {
|
|
60240
|
+
if (metric.type !== "histogram" || !("buckets" in sample)) {
|
|
60241
|
+
out.push(`${metric.name}${labels(sample.labels)} ${number4(sample.value)}`);
|
|
60242
|
+
continue;
|
|
60243
|
+
}
|
|
60244
|
+
for (let i = 0;i < HISTOGRAM_BUCKETS.length; i++) {
|
|
60245
|
+
out.push(`${metric.name}_bucket${labels(sample.labels, ["le", String(HISTOGRAM_BUCKETS[i])])} ${number4(sample.buckets[i] ?? 0)}`);
|
|
60246
|
+
}
|
|
60247
|
+
out.push(`${metric.name}_bucket${labels(sample.labels, ["le", "+Inf"])} ${sample.count}`, `${metric.name}_sum${labels(sample.labels)} ${number4(sample.sum)}`, `${metric.name}_count${labels(sample.labels)} ${sample.count}`);
|
|
60248
|
+
}
|
|
60249
|
+
}
|
|
60250
|
+
return `${out.join(`
|
|
60251
|
+
`)}
|
|
60252
|
+
`;
|
|
60253
|
+
}
|
|
60254
|
+
|
|
60255
|
+
// apps/gateway/src/telemetry/index.ts
|
|
60256
|
+
function parseOtlpHeaders(value) {
|
|
60257
|
+
const headers = {};
|
|
60258
|
+
if (value === null)
|
|
60259
|
+
return headers;
|
|
60260
|
+
for (const entry of value.split(",")) {
|
|
60261
|
+
const at = entry.indexOf("=");
|
|
60262
|
+
if (at <= 0)
|
|
60263
|
+
continue;
|
|
60264
|
+
const key = entry.slice(0, at).trim();
|
|
60265
|
+
if (key.length > 0)
|
|
60266
|
+
headers[key] = entry.slice(at + 1).trim();
|
|
60267
|
+
}
|
|
60268
|
+
return headers;
|
|
60269
|
+
}
|
|
60270
|
+
function codeAttr(value) {
|
|
60271
|
+
return value === "interrupted" || /^[A-Z_]{1,32}$/.test(value) ? { code: value } : {};
|
|
60272
|
+
}
|
|
60273
|
+
function createTelemetry(opts) {
|
|
60274
|
+
const registry2 = createMetricRegistry({ maxSeries: opts.maxSeries, now: opts.now });
|
|
60275
|
+
const traces = new Map;
|
|
60276
|
+
const openBreakers = new Map;
|
|
60277
|
+
const exporter = opts.exporter ?? (opts.otlpEndpoint === null ? null : createOtlpExporter({
|
|
60278
|
+
endpoint: opts.otlpEndpoint,
|
|
60279
|
+
headers: opts.otlpHeaders,
|
|
60280
|
+
capacity: 1e4,
|
|
60281
|
+
batchMax: 512,
|
|
60282
|
+
intervalMs: 5000,
|
|
60283
|
+
registry: registry2,
|
|
60284
|
+
...opts.logger === undefined ? {} : { logger: opts.logger },
|
|
60285
|
+
version: opts.version
|
|
60286
|
+
}));
|
|
60287
|
+
if (opts.metricsEnabled)
|
|
60288
|
+
registry2.set("omni_build_info", { version: opts.version }, 1);
|
|
60289
|
+
return {
|
|
60290
|
+
metricsEnabled: opts.metricsEnabled,
|
|
60291
|
+
tracingEnabled: exporter !== null,
|
|
60292
|
+
registry: registry2,
|
|
60293
|
+
startRequest(requestId, startedAt, traceparent, surface, rand) {
|
|
60294
|
+
if (exporter === null)
|
|
60295
|
+
return null;
|
|
60296
|
+
const inbound = parseTraceparent(traceparent);
|
|
60297
|
+
if (inbound !== null ? !inbound.sampled : rand >= opts.traceSample)
|
|
60298
|
+
return null;
|
|
60299
|
+
const trace = createTrace({ startedAt, traceparent: inbound });
|
|
60300
|
+
addSpan(trace, "gateway.request", null, 0, 0, { surface });
|
|
60301
|
+
traces.set(requestId, trace);
|
|
60302
|
+
return trace;
|
|
60303
|
+
},
|
|
60304
|
+
record(log, keyId, trace) {
|
|
60305
|
+
if (opts.metricsEnabled) {
|
|
60306
|
+
const provider = log.resolvedProvider ?? "";
|
|
60307
|
+
const model = log.resolvedModel ?? log.requestedModel;
|
|
60308
|
+
const key = keyId ?? "";
|
|
60309
|
+
registry2.add("omni_requests_total", {
|
|
60310
|
+
provider,
|
|
60311
|
+
model,
|
|
60312
|
+
status: String(log.status),
|
|
60313
|
+
code: log.errorCode ?? "",
|
|
60314
|
+
api_key_id: key
|
|
60315
|
+
});
|
|
60316
|
+
registry2.observe("omni_request_duration_seconds", log.durationMs / 1000, {
|
|
60317
|
+
provider,
|
|
60318
|
+
model
|
|
60319
|
+
});
|
|
60320
|
+
if (log.ttftMs !== null)
|
|
60321
|
+
registry2.observe("omni_ttft_seconds", log.ttftMs / 1000, { provider, model });
|
|
60322
|
+
for (const [kind, value] of [
|
|
60323
|
+
["input", log.inputTokens],
|
|
60324
|
+
["output", log.outputTokens],
|
|
60325
|
+
["cache_read", log.cacheReadTokens],
|
|
60326
|
+
["cache_write", log.cacheWriteTokens]
|
|
60327
|
+
]) {
|
|
60328
|
+
registry2.add("omni_tokens_total", { provider, model, api_key_id: key, kind }, value);
|
|
60329
|
+
}
|
|
60330
|
+
registry2.add("omni_cost_usd_total", { provider, model, api_key_id: key }, log.costUsd);
|
|
60331
|
+
}
|
|
60332
|
+
if (trace !== null) {
|
|
60333
|
+
const root = trace.spans[0];
|
|
60334
|
+
if (root !== undefined) {
|
|
60335
|
+
root.endMs = log.durationMs;
|
|
60336
|
+
root.attrs = {
|
|
60337
|
+
...root.attrs,
|
|
60338
|
+
requested_model: log.requestedModel,
|
|
60339
|
+
api_key_id: keyId ?? "",
|
|
60340
|
+
status: log.status,
|
|
60341
|
+
...log.errorCode === null ? {} : codeAttr(log.errorCode)
|
|
60342
|
+
};
|
|
60343
|
+
}
|
|
60344
|
+
}
|
|
60345
|
+
},
|
|
60346
|
+
flush(requestId, trace) {
|
|
60347
|
+
traces.delete(requestId);
|
|
60348
|
+
if (trace !== null)
|
|
60349
|
+
exporter?.enqueue(trace);
|
|
60350
|
+
},
|
|
60351
|
+
httpHead(event) {
|
|
60352
|
+
if (opts.metricsEnabled)
|
|
60353
|
+
registry2.observe("omni_upstream_duration_seconds", event.durationMs / 1000, {
|
|
60354
|
+
provider: event.provider
|
|
60355
|
+
});
|
|
60356
|
+
const trace = event.requestId === undefined ? undefined : traces.get(event.requestId);
|
|
60357
|
+
if (trace === undefined)
|
|
60358
|
+
return;
|
|
60359
|
+
const endMs = Math.max(0, opts.now() - trace.startedAt);
|
|
60360
|
+
addSpan(trace, "provider.http", trace.activeAttempt ?? null, Math.max(0, endMs - event.durationMs), endMs, {
|
|
60361
|
+
provider: event.provider,
|
|
60362
|
+
host: event.host,
|
|
60363
|
+
path: event.path,
|
|
60364
|
+
...event.status === undefined ? {} : { status: event.status }
|
|
60365
|
+
});
|
|
60366
|
+
},
|
|
60367
|
+
breaker(provider, before, after) {
|
|
60368
|
+
if (!opts.metricsEnabled || before === after)
|
|
60369
|
+
return;
|
|
60370
|
+
const count = Math.max(0, (openBreakers.get(provider) ?? 0) + (after === "open" ? 1 : before === "open" ? -1 : 0));
|
|
60371
|
+
openBreakers.set(provider, count);
|
|
60372
|
+
registry2.set("omni_breaker_open", { provider }, count);
|
|
60373
|
+
},
|
|
60374
|
+
rateLimit(dimension, window2) {
|
|
60375
|
+
if (opts.metricsEnabled)
|
|
60376
|
+
registry2.add("omni_ratelimit_rejected_total", { dimension, window: window2 ?? "none" });
|
|
60377
|
+
},
|
|
60378
|
+
scrape(localInflight, streams, coordFallback) {
|
|
60379
|
+
for (const [provider, value] of localInflight)
|
|
60380
|
+
registry2.set("omni_inflight", { provider }, value);
|
|
60381
|
+
registry2.set("omni_stream_connections", {}, streams.connections);
|
|
60382
|
+
registry2.set("omni_stream_queued", {}, streams.queued);
|
|
60383
|
+
registry2.set("omni_stream_dropped_total", {}, streams.dropped);
|
|
60384
|
+
registry2.set("omni_coord_fallback", {}, coordFallback ? 1 : 0);
|
|
60385
|
+
return renderPrometheus(registry2.snapshot());
|
|
60386
|
+
},
|
|
60387
|
+
stop() {
|
|
60388
|
+
exporter?.stop();
|
|
60389
|
+
}
|
|
60390
|
+
};
|
|
60391
|
+
}
|
|
60392
|
+
|
|
59850
60393
|
// apps/gateway/src/index.ts
|
|
59851
60394
|
function stdoutLogger(level) {
|
|
59852
60395
|
return createLogger({
|
|
@@ -59915,7 +60458,17 @@ async function main() {
|
|
|
59915
60458
|
logger2.info("retired interrupted requests", { count: swept });
|
|
59916
60459
|
const console2 = consoleSource(config2.logFile);
|
|
59917
60460
|
logger2.info("console log source resolved", { reason: console2.source.kind });
|
|
59918
|
-
const
|
|
60461
|
+
const telemetry = createTelemetry({
|
|
60462
|
+
metricsEnabled: config2.metricsToken !== null,
|
|
60463
|
+
maxSeries: config2.metricsMaxSeries,
|
|
60464
|
+
otlpEndpoint: config2.otlpEndpoint,
|
|
60465
|
+
otlpHeaders: parseOtlpHeaders(config2.otlpHeaders),
|
|
60466
|
+
traceSample: config2.traceSample,
|
|
60467
|
+
now,
|
|
60468
|
+
version: VERSION,
|
|
60469
|
+
logger: logger2
|
|
60470
|
+
});
|
|
60471
|
+
const http = nodeHttpClient({ logger: logger2, now, onResponseHead: telemetry.httpHead });
|
|
59919
60472
|
const shared = config2.clusterMode && config2.redisUrl !== null ? redisCoord({ url: config2.redisUrl, logger: logger2, now }) : null;
|
|
59920
60473
|
const coord = shared ?? memoryCoord({ now });
|
|
59921
60474
|
const lease = { coord, nodeId };
|
|
@@ -59986,6 +60539,8 @@ async function main() {
|
|
|
59986
60539
|
broadcaster,
|
|
59987
60540
|
ring: streamRing,
|
|
59988
60541
|
channels: pluginChannels,
|
|
60542
|
+
telemetry,
|
|
60543
|
+
...config2.metricsToken === null ? {} : { metricsToken: config2.metricsToken },
|
|
59989
60544
|
bodyLoggingAllowed: config2.bodyLoggingAllowed,
|
|
59990
60545
|
plugins: loadedPlugins.plugins.map((plugin) => ({ id: plugin.id, routes: plugin.routes })),
|
|
59991
60546
|
pluginUi: loadedPlugins.plugins,
|
|
@@ -60043,6 +60598,7 @@ async function main() {
|
|
|
60043
60598
|
streamRegistry.closeAll(1001, "restart");
|
|
60044
60599
|
streamRegistry.stop();
|
|
60045
60600
|
broadcaster.stop();
|
|
60601
|
+
telemetry.stop();
|
|
60046
60602
|
pluginChannels.stop();
|
|
60047
60603
|
}
|
|
60048
60604
|
],
|