@jeffusion/bungee-core 6.2.1 → 6.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +46 -45
- package/dist/observability-worker.js +24 -1
- package/dist/plugins/anthropic-request-sanitizer/index.js.map +1 -1
- package/dist/plugins/anthropic-tool-name-transformer/index.js.map +1 -1
- package/dist/plugins/chatgpt-oauth/control.js.map +1 -1
- package/dist/plugins/chatgpt-oauth/index.js.map +1 -1
- package/dist/plugins/chatgpt-oauth/ui/AccountsPage.svelte +1 -1
- package/dist/plugins/codex-router/control.js.map +1 -1
- package/dist/plugins/codex-router/index.js.map +1 -1
- package/dist/plugins/deepseek-reasoning-fix/index.js.map +1 -1
- package/dist/plugins/key-access/control.js.map +1 -1
- package/dist/plugins/key-access/index.js.map +1 -1
- package/dist/plugins/key-access/ingress.js.map +1 -1
- package/dist/plugins/key-rate-limit/index.js.map +1 -1
- package/dist/plugins/llm-protocol-adapter/control.js.map +1 -1
- package/dist/plugins/llm-protocol-adapter/index.js.map +3 -3
- package/dist/plugins/llm-tps/control.js +41 -11
- package/dist/plugins/llm-tps/control.js.map +5 -5
- package/dist/plugins/llm-tps/index.js +20 -1
- package/dist/plugins/llm-tps/index.js.map +6 -6
- package/dist/plugins/llm-tps/manifest.json +20 -6
- package/dist/plugins/llm-tps/ui/TpsModels.svelte +9 -0
- package/dist/plugins/llm-tps/ui/TpsWidget.svelte +63 -58
- package/dist/plugins/llm-tps/ui/presentation.ts +6 -6
- package/dist/plugins/llm-tps/ui/stats-resource.ts +7 -3
- package/dist/plugins/model-mapping/index.js.map +1 -1
- package/dist/plugins/models-dev/index.js.map +1 -1
- package/dist/plugins/signature-repair/index.js.map +1 -1
- package/dist/plugins/token-budget/control.js.map +1 -1
- package/dist/plugins/token-budget/index.js.map +2 -2
- package/dist/plugins/token-metering/index.js +7 -3
- package/dist/plugins/token-metering/index.js.map +5 -5
- package/dist/plugins/token-stats/control.js.map +1 -1
- package/dist/plugins/token-stats/index.js.map +2 -2
- package/dist/plugins/token-stats/ui/TokenStatsPage.svelte +2 -2
- package/dist/plugins/user-auth/control.js.map +1 -1
- package/dist/plugins/user-auth/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
// plugins/llm-tps/contract.ts
|
|
3
3
|
var TPS_RETENTION_MS = 31 * 24 * 60 * 60 * 1000;
|
|
4
4
|
var TPS_REPORTING_INCOMPLETE_KEY = "reporting:incomplete";
|
|
5
|
+
function isUpstreamHost(value) {
|
|
6
|
+
if (typeof value !== "string" || !value || /[\s\u0000-\u001f\u007f/\\@?#]/.test(value))
|
|
7
|
+
return false;
|
|
8
|
+
try {
|
|
9
|
+
return new URL(`http://${value}`).host === value || new URL(`https://${value}`).host === value;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
5
14
|
|
|
6
15
|
// plugins/llm-tps/server/metrics.ts
|
|
7
16
|
function isUrlIdentity(value) {
|
|
@@ -16,11 +25,19 @@ function identifier(value, name) {
|
|
|
16
25
|
if (typeof value !== "string" || !value.trim())
|
|
17
26
|
throw new TypeError(`invalid llm-tps ${name}`);
|
|
18
27
|
}
|
|
28
|
+
function validateUpstreamHost(value) {
|
|
29
|
+
if (!isUpstreamHost(value))
|
|
30
|
+
throw new TypeError("invalid llm-tps upstream host");
|
|
31
|
+
}
|
|
19
32
|
function validateSample(sample) {
|
|
20
33
|
for (const key of ["attemptId", "requestId", "routeId", "endpointId"])
|
|
21
34
|
identifier(sample[key], key);
|
|
35
|
+
if (sample.model !== null)
|
|
36
|
+
identifier(sample.model, "model");
|
|
22
37
|
if (isUrlIdentity(sample.endpointId))
|
|
23
38
|
throw new TypeError("llm-tps endpoint must be opaque");
|
|
39
|
+
if (sample.upstreamHost !== null)
|
|
40
|
+
validateUpstreamHost(sample.upstreamHost);
|
|
24
41
|
safeCount(sample.finishedAtMs, "finishedAtMs");
|
|
25
42
|
safeCount(sample.outputTokens, "outputTokens");
|
|
26
43
|
if (!Number.isFinite(sample.generationDurationMs) || sample.generationDurationMs <= 0)
|
|
@@ -77,8 +94,8 @@ function createObservationAdapter(observation) {
|
|
|
77
94
|
observation.withDatabase((db) => {
|
|
78
95
|
db.transaction(() => {
|
|
79
96
|
db.query(`INSERT OR IGNORE INTO llm_tps_samples
|
|
80
|
-
(attempt_id, request_id, route_id, endpoint_id, finished_at_ms, output_tokens, generation_duration_ms, output_source)
|
|
81
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(sample.attemptId, sample.requestId, sample.routeId, sample.endpointId, sample.finishedAtMs, sample.outputTokens, sample.generationDurationMs, sample.outputSource);
|
|
97
|
+
(attempt_id, request_id, route_id, endpoint_id, finished_at_ms, output_tokens, generation_duration_ms, output_source, model, upstream_host)
|
|
98
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(sample.attemptId, sample.requestId, sample.routeId, sample.endpointId, sample.finishedAtMs, sample.outputTokens, sample.generationDurationMs, sample.outputSource, sample.model, sample.upstreamHost);
|
|
82
99
|
db.query(`DELETE FROM llm_tps_samples WHERE attempt_id IN (
|
|
83
100
|
SELECT attempt_id FROM llm_tps_samples INDEXED BY idx_llm_tps_samples_finished
|
|
84
101
|
WHERE finished_at_ms < ? ORDER BY finished_at_ms LIMIT 500
|
|
@@ -92,22 +109,35 @@ function createObservationAdapter(observation) {
|
|
|
92
109
|
return observation.withDatabase((db) => db.transaction(() => {
|
|
93
110
|
const where = "WHERE finished_at_ms >= ? AND finished_at_ms <= ?";
|
|
94
111
|
const totalRow = db.query(`SELECT ${metrics} FROM llm_tps_samples ${where}`).get(start, input.asOfMs);
|
|
95
|
-
const rows = db.query(`SELECT route_id AS routeId,
|
|
96
|
-
FROM llm_tps_samples ${where} GROUP BY route_id,
|
|
112
|
+
const rows = db.query(`SELECT route_id AS routeId, upstream_host AS upstreamHost, ${metrics}
|
|
113
|
+
FROM llm_tps_samples ${where} AND upstream_host IS NOT NULL GROUP BY route_id, upstream_host`).all(start, input.asOfMs);
|
|
97
114
|
const checked = (row) => {
|
|
98
115
|
if (safeCount(row.invalidSamples, "invalid sample count") !== 0)
|
|
99
116
|
throw new RangeError("invalid persisted llm-tps sample");
|
|
100
117
|
return aggregate(row);
|
|
101
118
|
};
|
|
102
119
|
const total = checked(totalRow);
|
|
120
|
+
const bucketMs = input.range === "1h" ? 60000 : input.range === "12h" ? 1800000 : 3600000;
|
|
121
|
+
const historyRows = db.query(`SELECT CAST(finished_at_ms / ? AS INTEGER) AS bucket, ${metrics}
|
|
122
|
+
FROM llm_tps_samples ${where} GROUP BY bucket ORDER BY bucket`).all(bucketMs, start, input.asOfMs);
|
|
123
|
+
const buckets = new Map(historyRows.map((row) => [safeCount(row.bucket, "bucket"), checked(row)]));
|
|
124
|
+
const firstBucket = Math.floor(Math.max(0, start) / bucketMs), lastBucket = Math.floor(input.asOfMs / bucketMs);
|
|
125
|
+
const history = Array.from({ length: lastBucket - firstBucket + 1 }, (_, index) => ({
|
|
126
|
+
bucketStartMs: (firstBucket + index) * bucketMs,
|
|
127
|
+
...buckets.get(firstBucket + index) ?? { tps: null, outputTokens: 0, generationDurationMs: 0, sampleCount: 0, estimatedSampleCount: 0 }
|
|
128
|
+
}));
|
|
103
129
|
const endpoints = rows.map((row) => {
|
|
104
130
|
identifier(row.routeId, "routeId");
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
131
|
+
validateUpstreamHost(row.upstreamHost);
|
|
132
|
+
return { routeId: row.routeId, upstreamHost: row.upstreamHost, ...checked(row) };
|
|
133
|
+
}).sort((a, b) => b.tps - a.tps || compare(a.routeId, b.routeId) || compare(a.upstreamHost, b.upstreamHost));
|
|
134
|
+
const models = db.query(`SELECT route_id AS routeId, model, ${metrics}
|
|
135
|
+
FROM llm_tps_samples ${where} AND model IS NOT NULL GROUP BY route_id, model`).all(start, input.asOfMs).map((row) => {
|
|
136
|
+
identifier(row.routeId, "routeId");
|
|
137
|
+
identifier(row.model, "model");
|
|
138
|
+
return { routeId: row.routeId, model: row.model, ...checked(row) };
|
|
139
|
+
}).sort((a, b) => b.tps - a.tps || compare(a.routeId, b.routeId) || compare(a.model, b.model));
|
|
140
|
+
return { range: input.range, asOfMs: input.asOfMs, total, endpoints, models, history, reportingIncomplete: false };
|
|
111
141
|
})());
|
|
112
142
|
}
|
|
113
143
|
});
|
|
@@ -209,4 +239,4 @@ export {
|
|
|
209
239
|
control_default as default
|
|
210
240
|
};
|
|
211
241
|
|
|
212
|
-
//# debugId=
|
|
242
|
+
//# debugId=81BE1310B726085964756E2164756E21
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../plugins/llm-tps/contract.ts", "../../../../../plugins/llm-tps/server/metrics.ts", "../../../../../plugins/llm-tps/server/storage.ts", "../../../../../plugins/llm-tps/server/control.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"export type TpsRange = '1h' | '12h' | '24h';\n\nexport interface TpsSample {\n readonly attemptId: string;\n readonly requestId: string;\n readonly routeId: string;\n /** Configured endpoint ID, or an opaque hash when the
|
|
6
|
-
"import { createHash } from 'node:crypto';\nimport type { TokenMeteringResult } from '@jeffusion/bungee-core/plugin';\nimport type
|
|
7
|
-
"import type { PluginObservationStorage, PluginStorage } from '@jeffusion/bungee-core/plugin';\nimport { TPS_RETENTION_MS, type TpsRange, type TpsSample, type TpsStatsResponse } from '../contract';\nimport { aggregate, identifier,
|
|
5
|
+
"export type TpsRange = '1h' | '12h' | '24h';\n\nexport interface TpsSample {\n readonly attemptId: string;\n readonly requestId: string;\n readonly routeId: string;\n /** Configured endpoint ID, or an opaque hash when the upstream identity is a URL. */\n readonly endpointId: string;\n /** Actual outbound URL.host, or null when unavailable. */\n readonly upstreamHost: string | null;\n readonly model: string | null;\n readonly finishedAtMs: number;\n readonly outputTokens: number;\n readonly generationDurationMs: number;\n readonly outputSource: 'official' | 'estimated';\n}\n\nexport interface TpsAggregate {\n readonly tps: number | null;\n readonly outputTokens: number;\n readonly generationDurationMs: number;\n readonly sampleCount: number;\n readonly estimatedSampleCount: number;\n}\n\nexport interface TpsEndpointStats extends TpsAggregate {\n readonly routeId: string;\n readonly upstreamHost: string;\n}\n\nexport interface TpsModelStats extends TpsAggregate {\n readonly routeId: string;\n readonly model: string;\n}\n\nexport interface TpsHistoryPoint extends TpsAggregate {\n readonly bucketStartMs: number;\n}\n\nexport interface TpsStatsResponse {\n readonly range: TpsRange;\n readonly asOfMs: number;\n readonly total: TpsAggregate;\n readonly endpoints: readonly TpsEndpointStats[];\n readonly models: readonly TpsModelStats[];\n readonly history: readonly TpsHistoryPoint[];\n readonly reportingIncomplete: boolean;\n}\n\nexport const TPS_RETENTION_MS = 31 * 24 * 60 * 60 * 1000;\nexport const TPS_REPORTING_INCOMPLETE_KEY = 'reporting:incomplete';\n\n/** Canonical URL.host only: no credentials, path, query or fragment. */\nexport function isUpstreamHost(value: unknown): value is string {\n if (typeof value !== 'string' || !value || /[\\s\\u0000-\\u001f\\u007f/\\\\@?#]/.test(value)) return false;\n try {\n // Either scheme preserves the other scheme's default port in a valid host.\n return new URL(`http://${value}`).host === value || new URL(`https://${value}`).host === value;\n } catch { return false; }\n}\n",
|
|
6
|
+
"import { createHash } from 'node:crypto';\nimport type { TokenMeteringResult } from '@jeffusion/bungee-core/plugin';\nimport { isUpstreamHost, type TpsAggregate, type TpsRange, type TpsSample } from '../contract';\n\nexport function isUrlIdentity(value: string): boolean {\n return /^(?:[a-z][a-z\\d+.-]*:)?\\/\\//i.test(value);\n}\n\nexport function endpointIdentity(upstreamId: string): string {\n return isUrlIdentity(upstreamId)\n ? `endpoint-${createHash('sha256').update(upstreamId).digest('hex').slice(0, 16)}`\n : upstreamId;\n}\n\nexport function safeCount(value: unknown, name: string): number {\n if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) throw new RangeError(`invalid llm-tps ${name}`);\n return value;\n}\n\nexport function identifier(value: unknown, name: string): asserts value is string {\n if (typeof value !== 'string' || !value.trim()) throw new TypeError(`invalid llm-tps ${name}`);\n}\n\n/** Accept only a canonical URL.host, never URL credentials, path, query or fragment. */\nexport function validateUpstreamHost(value: unknown): asserts value is string {\n if (!isUpstreamHost(value)) throw new TypeError('invalid llm-tps upstream host');\n}\n\nexport function validateSample(sample: TpsSample): void {\n for (const key of ['attemptId', 'requestId', 'routeId', 'endpointId'] as const) identifier(sample[key], key);\n if (sample.model !== null) identifier(sample.model, 'model');\n if (isUrlIdentity(sample.endpointId)) throw new TypeError('llm-tps endpoint must be opaque');\n if (sample.upstreamHost !== null) validateUpstreamHost(sample.upstreamHost);\n safeCount(sample.finishedAtMs, 'finishedAtMs');\n safeCount(sample.outputTokens, 'outputTokens');\n if (!Number.isFinite(sample.generationDurationMs) || sample.generationDurationMs <= 0) throw new RangeError('invalid llm-tps duration');\n if (sample.outputSource !== 'official' && sample.outputSource !== 'estimated') throw new TypeError('invalid llm-tps output source');\n if (!Number.isFinite(sample.outputTokens / sample.generationDurationMs * 1000)) throw new RangeError('invalid llm-tps rate');\n}\n\n/** Unknown input usage does not invalidate observable output usage. */\nexport function sampleFromResult(result: TokenMeteringResult, routeId: string): TpsSample | null {\n const output = result.generationOutput;\n if (result.routeId !== routeId || !output\n || (output.source !== 'official' && output.source !== 'estimated')\n || !Number.isSafeInteger(output.tokens) || output.tokens < 0\n || !Number.isFinite(result.generationDurationMs) || result.generationDurationMs! <= 0) return null;\n const sample: TpsSample = {\n attemptId: result.attemptId, requestId: result.requestId, routeId: result.routeId,\n endpointId: endpointIdentity(result.upstreamId), finishedAtMs: result.finishedAtMs,\n upstreamHost: result.upstreamHost ?? null,\n model: typeof result.model === 'string' && result.model.trim() ? result.model : null,\n outputTokens: output.tokens, generationDurationMs: result.generationDurationMs!, outputSource: output.source,\n };\n validateSample(sample);\n return sample;\n}\n\nexport function rangeDuration(range: TpsRange): number {\n if (range === '1h') return 3_600_000;\n if (range === '12h') return 43_200_000;\n if (range === '24h') return 86_400_000;\n throw new RangeError('invalid llm-tps range');\n}\n\nexport function aggregate(row: Record<string, unknown>): TpsAggregate {\n const outputTokens = safeCount(row.outputTokens, 'aggregate tokens');\n const sampleCount = safeCount(row.sampleCount, 'sample count');\n const estimatedSampleCount = safeCount(row.estimatedSampleCount, 'estimated sample count');\n const duration = row.generationDurationMs;\n if (typeof duration !== 'number' || !Number.isFinite(duration) || duration < 0\n || (sampleCount > 0 && duration <= 0) || estimatedSampleCount > sampleCount\n || (sampleCount === 0 && (outputTokens !== 0 || duration !== 0 || estimatedSampleCount !== 0))) throw new RangeError('invalid llm-tps aggregate');\n const tps = sampleCount === 0 ? null : outputTokens / duration * 1000;\n if (tps !== null && (!Number.isFinite(tps) || tps < 0)) throw new RangeError('invalid llm-tps aggregate rate');\n return { tps, outputTokens, generationDurationMs: duration, sampleCount, estimatedSampleCount };\n}\n",
|
|
7
|
+
"import type { PluginObservationStorage, PluginStorage } from '@jeffusion/bungee-core/plugin';\nimport { TPS_RETENTION_MS, type TpsRange, type TpsSample, type TpsStatsResponse } from '../contract';\nimport { aggregate, identifier, rangeDuration, safeCount, validateSample, validateUpstreamHost } from './metrics';\n\nexport interface TpsObservationClient {\n recordSample(sample: TpsSample): Promise<void>;\n queryStats(input: { range: TpsRange; asOfMs: number }): Promise<TpsStatsResponse>;\n}\n\n/** Runtime and control use only the host's namespaced observation adapter. */\nexport function observationClient(storage: PluginStorage): TpsObservationClient {\n if (typeof storage.invokeObservation !== 'function') throw new Error('llm-tps observation storage is unavailable');\n const invoke = storage.invokeObservation.bind(storage);\n return {\n recordSample: sample => invoke<void>('recordSample', [sample]),\n queryStats: input => invoke<TpsStatsResponse>('queryStats', [input]),\n };\n}\n\nconst metrics = `COALESCE(SUM(output_tokens), 0) AS outputTokens,\n COALESCE(SUM(generation_duration_ms), 0) AS generationDurationMs,\n COUNT(*) AS sampleCount, COALESCE(SUM(output_source = 'estimated'), 0) AS estimatedSampleCount,\n COALESCE(SUM(CASE WHEN typeof(output_tokens) != 'integer' OR output_tokens < 0 OR output_tokens > 9007199254740991\n OR typeof(generation_duration_ms) NOT IN ('integer', 'real') OR NOT(generation_duration_ms > 0)\n OR generation_duration_ms > 1.7976931348623157e308 OR output_source NOT IN ('official','estimated')\n OR typeof(finished_at_ms) != 'integer' OR finished_at_ms < 0 OR finished_at_ms > 9007199254740991\n THEN 1 ELSE 0 END), 0) AS invalidSamples`;\n\n/** SQL is confined to the trusted access.db adapter callback. */\nexport function createObservationAdapter(observation: PluginObservationStorage): TpsObservationClient {\n return Object.freeze({\n async recordSample(sample: TpsSample): Promise<void> {\n validateSample(sample);\n observation.withDatabase(db => {\n db.transaction(() => {\n db.query(`INSERT OR IGNORE INTO llm_tps_samples\n (attempt_id, request_id, route_id, endpoint_id, finished_at_ms, output_tokens, generation_duration_ms, output_source, model, upstream_host)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(sample.attemptId, sample.requestId, sample.routeId, sample.endpointId,\n sample.finishedAtMs, sample.outputTokens, sample.generationDurationMs, sample.outputSource, sample.model, sample.upstreamHost);\n db.query(`DELETE FROM llm_tps_samples WHERE attempt_id IN (\n SELECT attempt_id FROM llm_tps_samples INDEXED BY idx_llm_tps_samples_finished\n WHERE finished_at_ms < ? ORDER BY finished_at_ms LIMIT 500\n )`).run(Date.now() - TPS_RETENTION_MS);\n }).immediate();\n });\n },\n async queryStats(input: { range: TpsRange; asOfMs: number }): Promise<TpsStatsResponse> {\n safeCount(input.asOfMs, 'asOfMs');\n const start = input.asOfMs - rangeDuration(input.range);\n return observation.withDatabase(db => db.transaction(() => {\n const where = 'WHERE finished_at_ms >= ? AND finished_at_ms <= ?';\n const totalRow = db.query<Record<string, unknown>, [number, number]>(`SELECT ${metrics} FROM llm_tps_samples ${where}`).get(start, input.asOfMs)!;\n const rows = db.query<Record<string, unknown>, [number, number]>(`SELECT route_id AS routeId, upstream_host AS upstreamHost, ${metrics}\n FROM llm_tps_samples ${where} AND upstream_host IS NOT NULL GROUP BY route_id, upstream_host`).all(start, input.asOfMs);\n const checked = (row: Record<string, unknown>) => {\n if (safeCount(row.invalidSamples, 'invalid sample count') !== 0) throw new RangeError('invalid persisted llm-tps sample');\n return aggregate(row);\n };\n const total = checked(totalRow);\n const bucketMs = input.range === '1h' ? 60_000 : input.range === '12h' ? 1_800_000 : 3_600_000;\n const historyRows = db.query<Record<string, unknown>, [number, number, number]>(\n `SELECT CAST(finished_at_ms / ? AS INTEGER) AS bucket, ${metrics}\n FROM llm_tps_samples ${where} GROUP BY bucket ORDER BY bucket`,\n ).all(bucketMs, start, input.asOfMs);\n const buckets = new Map(historyRows.map(row => [safeCount(row.bucket, 'bucket'), checked(row)]));\n const firstBucket = Math.floor(Math.max(0, start) / bucketMs), lastBucket = Math.floor(input.asOfMs / bucketMs);\n const history = Array.from({ length: lastBucket - firstBucket + 1 }, (_, index) => ({\n bucketStartMs: (firstBucket + index) * bucketMs,\n ...(buckets.get(firstBucket + index) ?? { tps: null, outputTokens: 0, generationDurationMs: 0, sampleCount: 0, estimatedSampleCount: 0 }),\n }));\n const endpoints = rows.map(row => {\n identifier(row.routeId, 'routeId'); validateUpstreamHost(row.upstreamHost);\n return { routeId: row.routeId, upstreamHost: row.upstreamHost, ...checked(row) };\n }).sort((a, b) => (b.tps! - a.tps!) || compare(a.routeId, b.routeId) || compare(a.upstreamHost, b.upstreamHost));\n const models = db.query<Record<string, unknown>, [number, number]>(`SELECT route_id AS routeId, model, ${metrics}\n FROM llm_tps_samples ${where} AND model IS NOT NULL GROUP BY route_id, model`).all(start, input.asOfMs).map(row => {\n identifier(row.routeId, 'routeId'); identifier(row.model, 'model');\n return { routeId: row.routeId, model: row.model, ...checked(row) };\n }).sort((a, b) => (b.tps! - a.tps!) || compare(a.routeId, b.routeId) || compare(a.model, b.model));\n return { range: input.range, asOfMs: input.asOfMs, total, endpoints, models, history, reportingIncomplete: false };\n })());\n },\n });\n}\n\nfunction compare(a: string, b: string): number { return a < b ? -1 : a > b ? 1 : 0; }\n",
|
|
8
8
|
"import type { ControlApiHandlerContext, ControlHostContext, ControlPlugin, PluginControl } from '@jeffusion/bungee-core/plugin';\nimport { TPS_REPORTING_INCOMPLETE_KEY, type TpsRange } from '../contract';\nimport { observationClient } from './storage';\nexport { createObservationAdapter } from './storage';\n\nclass ControlError extends Error {\n constructor(readonly code: 'disposed' | 'request_cancelled' | 'invalid_input') { super(code); }\n}\n\nfunction abortable<T>(operation: Promise<T>, signals: readonly AbortSignal[]): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const listeners: Array<() => void> = [];\n const cleanup = () => signals.forEach((signal, index) => signal.removeEventListener('abort', listeners[index]!));\n const fail = (index: number) => { cleanup(); reject(new ControlError(index < 3 ? 'disposed' : 'request_cancelled')); };\n signals.forEach((signal, index) => {\n const listener = () => fail(index); listeners.push(listener);\n signal.addEventListener('abort', listener, { once: true });\n });\n const cancelled = signals.findIndex(signal => signal.aborted);\n if (cancelled !== -1) fail(cancelled);\n operation.then(value => { cleanup(); resolve(value); }, error => { cleanup(); reject(error); });\n });\n}\n\nexport function createControl(host: ControlHostContext): PluginControl {\n const client = observationClient(host.storage);\n const disposal = new AbortController();\n let disposed = false;\n const assertAlive = (context?: ControlApiHandlerContext) => {\n if (disposed || host.signal.aborted || context?.signal.aborted) throw new ControlError('disposed');\n if (context?.requestSignal.aborted || context?.request.signal.aborted) throw new ControlError('request_cancelled');\n };\n const dispose = () => { disposed = true; disposal.abort(); host.signal.removeEventListener('abort', dispose); };\n host.signal.addEventListener('abort', dispose, { once: true });\n return {\n api: [{ path: '/stats', methods: ['GET'], handler: 'getStats', async invoke(context) {\n try {\n assertAlive(context);\n if (context.request.method !== 'GET') return Response.json({ error: 'method_not_allowed' }, { status: 405, headers: { allow: 'GET' } });\n const params = new URL(context.request.url).searchParams;\n const range = params.get('range') ?? '24h';\n if ([...params.keys()].some(key => key !== 'range') || params.getAll('range').length > 1\n || !['1h', '12h', '24h'].includes(range)) throw new ControlError('invalid_input');\n const readIncomplete = async () => {\n const storage = host.storage;\n const strict = storage.readStrict ? await storage.readStrict<unknown>(TPS_REPORTING_INCOMPLETE_KEY) : null;\n const value = strict === null ? await storage.get<unknown>(TPS_REPORTING_INCOMPLETE_KEY) : strict.found ? strict.value : null;\n if (value !== null && value !== false && value !== true) throw new Error('invalid reporting status');\n return value === true;\n };\n const [stats, incomplete] = await abortable(Promise.all([\n client.queryStats({ range: range as TpsRange, asOfMs: Date.now() }), readIncomplete(),\n ]), [disposal.signal, host.signal, context.signal, context.requestSignal, context.request.signal]);\n assertAlive(context);\n return Response.json({ ...stats, reportingIncomplete: stats.reportingIncomplete || incomplete });\n } catch (error) {\n const code = error instanceof ControlError ? error.code : 'storage_unavailable';\n const status = code === 'invalid_input' || code === 'request_cancelled' ? 400 : code === 'disposed' ? 409 : 503;\n return Response.json({ error: code }, { status });\n }\n } }],\n rpc: [],\n start() { assertAlive(); },\n dispose,\n };\n}\n\nexport default { createControl } satisfies ControlPlugin;\n"
|
|
9
9
|
],
|
|
10
|
-
"mappings": ";;
|
|
11
|
-
"debugId": "
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "81BE1310B726085964756E2164756E21",
|
|
12
12
|
"names": []
|
|
13
13
|
}
|
|
@@ -137,6 +137,15 @@ var TOKEN_STATS_RETENTION_MS = 31 * 24 * HOUR;
|
|
|
137
137
|
// plugins/llm-tps/contract.ts
|
|
138
138
|
var TPS_RETENTION_MS = 31 * 24 * 60 * 60 * 1000;
|
|
139
139
|
var TPS_REPORTING_INCOMPLETE_KEY = "reporting:incomplete";
|
|
140
|
+
function isUpstreamHost(value) {
|
|
141
|
+
if (typeof value !== "string" || !value || /[\s\u0000-\u001f\u007f/\\@?#]/.test(value))
|
|
142
|
+
return false;
|
|
143
|
+
try {
|
|
144
|
+
return new URL(`http://${value}`).host === value || new URL(`https://${value}`).host === value;
|
|
145
|
+
} catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
140
149
|
|
|
141
150
|
// plugins/llm-tps/server/metrics.ts
|
|
142
151
|
import { createHash } from "crypto";
|
|
@@ -155,11 +164,19 @@ function identifier(value, name) {
|
|
|
155
164
|
if (typeof value !== "string" || !value.trim())
|
|
156
165
|
throw new TypeError(`invalid llm-tps ${name}`);
|
|
157
166
|
}
|
|
167
|
+
function validateUpstreamHost(value) {
|
|
168
|
+
if (!isUpstreamHost(value))
|
|
169
|
+
throw new TypeError("invalid llm-tps upstream host");
|
|
170
|
+
}
|
|
158
171
|
function validateSample(sample) {
|
|
159
172
|
for (const key of ["attemptId", "requestId", "routeId", "endpointId"])
|
|
160
173
|
identifier(sample[key], key);
|
|
174
|
+
if (sample.model !== null)
|
|
175
|
+
identifier(sample.model, "model");
|
|
161
176
|
if (isUrlIdentity(sample.endpointId))
|
|
162
177
|
throw new TypeError("llm-tps endpoint must be opaque");
|
|
178
|
+
if (sample.upstreamHost !== null)
|
|
179
|
+
validateUpstreamHost(sample.upstreamHost);
|
|
163
180
|
safeCount(sample.finishedAtMs, "finishedAtMs");
|
|
164
181
|
safeCount(sample.outputTokens, "outputTokens");
|
|
165
182
|
if (!Number.isFinite(sample.generationDurationMs) || sample.generationDurationMs <= 0)
|
|
@@ -179,6 +196,8 @@ function sampleFromResult(result, routeId) {
|
|
|
179
196
|
routeId: result.routeId,
|
|
180
197
|
endpointId: endpointIdentity(result.upstreamId),
|
|
181
198
|
finishedAtMs: result.finishedAtMs,
|
|
199
|
+
upstreamHost: result.upstreamHost ?? null,
|
|
200
|
+
model: typeof result.model === "string" && result.model.trim() ? result.model : null,
|
|
182
201
|
outputTokens: output.tokens,
|
|
183
202
|
generationDurationMs: result.generationDurationMs,
|
|
184
203
|
outputSource: output.source
|
|
@@ -283,4 +302,4 @@ export {
|
|
|
283
302
|
server_default as default
|
|
284
303
|
};
|
|
285
304
|
|
|
286
|
-
//# debugId=
|
|
305
|
+
//# debugId=A9BFFA841EA2D74364756E2164756E21
|