opencode-usage-insights 0.1.0 → 0.1.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/README.md +58 -54
- package/dist/core.d.ts +19 -31
- package/dist/index.d.ts +5 -3
- package/dist/index.js +36 -249
- package/dist/index.js.map +9 -10
- package/dist/line.d.ts +1 -2
- package/dist/panel-build.d.ts +3 -1
- package/dist/panel-data.d.ts +23 -19
- package/dist/recorder.d.ts +4 -17
- package/dist/sidebar.d.ts +3 -3
- package/dist/stats/aggregates.d.ts +38 -37
- package/dist/stats/db.d.ts +8 -17
- package/dist/stats/queries.d.ts +10 -30
- package/dist/text.d.ts +5 -8
- package/dist/tui.d.ts +7 -12
- package/dist/tui.js +168 -367
- package/dist/tui.js.map +13 -13
- package/package.json +2 -2
- package/dist/endpoint.d.ts +0 -46
package/dist/index.js
CHANGED
|
@@ -70,13 +70,9 @@ var TOKEN = /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/;
|
|
|
70
70
|
function isToken(value, limit = 64) {
|
|
71
71
|
return value.length > 0 && value.length <= limit && TOKEN.test(value);
|
|
72
72
|
}
|
|
73
|
-
function normalizeToken(value, limit = 64) {
|
|
74
|
-
const trimmed = value.trim().toLowerCase();
|
|
75
|
-
return isToken(trimmed, limit) ? trimmed : undefined;
|
|
76
|
-
}
|
|
77
73
|
|
|
78
74
|
// src/stats/db.ts
|
|
79
|
-
var SCHEMA_VERSION =
|
|
75
|
+
var SCHEMA_VERSION = 9;
|
|
80
76
|
var LABEL_LIMIT = 4 * TEXT_LIMIT;
|
|
81
77
|
var SCHEMA = `
|
|
82
78
|
CREATE TABLE IF NOT EXISTS meta (
|
|
@@ -118,13 +114,11 @@ CREATE TABLE IF NOT EXISTS steps (
|
|
|
118
114
|
streamed_at INTEGER,
|
|
119
115
|
completed_at INTEGER,
|
|
120
116
|
finish TEXT,
|
|
121
|
-
router TEXT,
|
|
122
117
|
recorded_at INTEGER NOT NULL
|
|
123
118
|
);
|
|
124
119
|
CREATE INDEX IF NOT EXISTS steps_session ON steps(session_id, created_at);
|
|
125
120
|
CREATE INDEX IF NOT EXISTS steps_model ON steps(provider_id, model_id, variant);
|
|
126
121
|
CREATE INDEX IF NOT EXISTS steps_completed ON steps(completed_at);
|
|
127
|
-
CREATE INDEX IF NOT EXISTS steps_router ON steps(router);
|
|
128
122
|
-- Every range filter and every ordering compares the step's own clock, which
|
|
129
123
|
-- falls back to the recording time for a step that was already in flight when
|
|
130
124
|
-- the plugin loaded. The index has to cover that expression, or every ranged
|
|
@@ -155,10 +149,7 @@ function jsonText(value) {
|
|
|
155
149
|
return null;
|
|
156
150
|
}
|
|
157
151
|
}
|
|
158
|
-
var
|
|
159
|
-
["router", "ALTER TABLE steps ADD COLUMN router TEXT"]
|
|
160
|
-
];
|
|
161
|
-
var REMOVED_COLUMNS = ["provider_state", "upstream", "upstream_source", "upstream_model", "tps"];
|
|
152
|
+
var REMOVED_COLUMNS = ["provider_state", "upstream", "upstream_source", "upstream_model", "tps", "router"];
|
|
162
153
|
var REMOVED_EVENT_KINDS = ["execution", "step-failed", "model-selected", "usage-recorded"];
|
|
163
154
|
function dropUnused(db) {
|
|
164
155
|
const objects = db.query("SELECT name, type FROM sqlite_master WHERE type IN ('table', 'index')").all();
|
|
@@ -168,6 +159,8 @@ function dropUnused(db) {
|
|
|
168
159
|
db.exec("DROP TABLE http_responses");
|
|
169
160
|
if (indexes.has("steps_upstream"))
|
|
170
161
|
db.exec("DROP INDEX steps_upstream");
|
|
162
|
+
if (indexes.has("steps_router"))
|
|
163
|
+
db.exec("DROP INDEX steps_router");
|
|
171
164
|
const columns = new Set(db.query("PRAGMA table_info(steps)").all().map((column) => column.name));
|
|
172
165
|
for (const name of REMOVED_COLUMNS) {
|
|
173
166
|
if (columns.has(name))
|
|
@@ -177,30 +170,9 @@ function dropUnused(db) {
|
|
|
177
170
|
const kinds = REMOVED_EVENT_KINDS.map((kind) => `'${kind}'`).join(", ");
|
|
178
171
|
db.exec(`DELETE FROM events WHERE kind IN (${kinds})`);
|
|
179
172
|
}
|
|
180
|
-
db.exec("UPDATE steps SET router = NULL WHERE router IN ('opencode', 'openrouter', 'vercel-ai-gateway')");
|
|
181
|
-
}
|
|
182
|
-
function schemaVersion(db) {
|
|
183
|
-
const row = db.query("SELECT value FROM meta WHERE key = 'schema_version'").get();
|
|
184
|
-
const parsed = row === null ? Number.NaN : Number.parseInt(row.value, 10);
|
|
185
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
186
|
-
}
|
|
187
|
-
function normalizeRouters(db) {
|
|
188
|
-
db.exec("UPDATE steps SET router = lower(router) WHERE router IS NOT NULL AND router <> lower(router)");
|
|
189
173
|
}
|
|
190
174
|
function migrate(db) {
|
|
191
|
-
const version = schemaVersion(db);
|
|
192
|
-
const steps = db.query("PRAGMA table_info(steps)").all();
|
|
193
|
-
const existing = new Set(steps.map((column) => column.name));
|
|
194
|
-
for (const [name, statement, backfill] of MIGRATIONS) {
|
|
195
|
-
if (existing.has(name))
|
|
196
|
-
continue;
|
|
197
|
-
db.exec(statement);
|
|
198
|
-
if (backfill !== undefined)
|
|
199
|
-
db.exec(backfill);
|
|
200
|
-
}
|
|
201
175
|
dropUnused(db);
|
|
202
|
-
if (version < 7)
|
|
203
|
-
normalizeRouters(db);
|
|
204
176
|
}
|
|
205
177
|
var POSIX = process.platform !== "win32";
|
|
206
178
|
function hardenDirectory(path) {
|
|
@@ -264,11 +236,11 @@ function recordStep(db, record) {
|
|
|
264
236
|
db.query(`INSERT INTO steps (
|
|
265
237
|
message_id, session_id, parent_id, provider_id, model_id, variant,
|
|
266
238
|
input_tokens, output_tokens, reasoning_tokens, cache_read, cache_write, cost,
|
|
267
|
-
created_at, streamed_at, completed_at, finish,
|
|
239
|
+
created_at, streamed_at, completed_at, finish, recorded_at
|
|
268
240
|
) VALUES (
|
|
269
241
|
$messageID, $sessionID, $parentID, $providerID, $modelID, $variant,
|
|
270
242
|
$input, $output, $reasoning, $cacheRead, $cacheWrite, $cost,
|
|
271
|
-
$createdAt, $streamedAt, $completedAt, $finish, $
|
|
243
|
+
$createdAt, $streamedAt, $completedAt, $finish, $recordedAt
|
|
272
244
|
)
|
|
273
245
|
ON CONFLICT(message_id) DO UPDATE SET
|
|
274
246
|
session_id = excluded.session_id,
|
|
@@ -286,12 +258,11 @@ function recordStep(db, record) {
|
|
|
286
258
|
streamed_at = COALESCE(excluded.streamed_at, steps.streamed_at),
|
|
287
259
|
completed_at = COALESCE(excluded.completed_at, steps.completed_at),
|
|
288
260
|
finish = COALESCE(excluded.finish, steps.finish),
|
|
289
|
-
router = COALESCE(excluded.router, steps.router),
|
|
290
261
|
recorded_at = excluded.recorded_at`).run({
|
|
291
262
|
$messageID: record.messageID,
|
|
292
263
|
$sessionID: record.sessionID,
|
|
293
264
|
$parentID: record.parentID ?? null,
|
|
294
|
-
$providerID: record.providerID
|
|
265
|
+
$providerID: record.providerID !== undefined && isToken(record.providerID) ? record.providerID : null,
|
|
295
266
|
$modelID: record.modelID ?? null,
|
|
296
267
|
$variant: record.variant ?? null,
|
|
297
268
|
$input: record.input ?? 0,
|
|
@@ -304,7 +275,6 @@ function recordStep(db, record) {
|
|
|
304
275
|
$streamedAt: record.streamedAt ?? null,
|
|
305
276
|
$completedAt: record.completedAt ?? null,
|
|
306
277
|
$finish: record.finish === undefined ? null : sanitizeLabel(record.finish, 64),
|
|
307
|
-
$router: record.router === undefined ? null : normalizeToken(record.router) ?? null,
|
|
308
278
|
$recordedAt: record.recordedAt ?? Date.now()
|
|
309
279
|
});
|
|
310
280
|
}
|
|
@@ -344,15 +314,6 @@ function upsertSession(db, record) {
|
|
|
344
314
|
$updatedAt: record.updatedAt ?? null
|
|
345
315
|
});
|
|
346
316
|
}
|
|
347
|
-
function setStepRouterForSessionModel(db, sessionID, modelID, info) {
|
|
348
|
-
const router = info.router === undefined ? undefined : normalizeToken(info.router);
|
|
349
|
-
if (router === undefined)
|
|
350
|
-
return 0;
|
|
351
|
-
const result = db.query(`UPDATE steps
|
|
352
|
-
SET router = $router
|
|
353
|
-
WHERE session_id = $sessionID AND router IS NULL AND ($modelID IS NULL OR model_id = $modelID)`).run({ $router: router, $sessionID: sessionID, $modelID: modelID ?? null });
|
|
354
|
-
return result.changes;
|
|
355
|
-
}
|
|
356
317
|
|
|
357
318
|
// src/core.ts
|
|
358
319
|
var PROMPT_BOUNDARIES = new Set(["user", "synthetic", "system"]);
|
|
@@ -401,7 +362,7 @@ function totalize(steps) {
|
|
|
401
362
|
}
|
|
402
363
|
return totals;
|
|
403
364
|
}
|
|
404
|
-
function
|
|
365
|
+
function identityKey(model) {
|
|
405
366
|
if (!model)
|
|
406
367
|
return;
|
|
407
368
|
return `${model.providerID}/${model.id}${model.variant ? `#${model.variant}` : ""}`;
|
|
@@ -418,14 +379,16 @@ function stepEnd(message) {
|
|
|
418
379
|
return;
|
|
419
380
|
return time.completed ?? time.streamed ?? time.created;
|
|
420
381
|
}
|
|
421
|
-
function cacheDrop(reference, current) {
|
|
382
|
+
function cacheDrop(reference, current, scope = "provider") {
|
|
422
383
|
if (!reference)
|
|
423
384
|
return;
|
|
424
385
|
const before = reference.model;
|
|
425
386
|
const after = current.model;
|
|
426
387
|
if (!before || !after)
|
|
427
388
|
return;
|
|
428
|
-
if (
|
|
389
|
+
if (scope === "provider" && before.providerID !== after.providerID)
|
|
390
|
+
return;
|
|
391
|
+
if (before.id !== after.id || (before.variant ?? "") !== (after.variant ?? "")) {
|
|
429
392
|
return;
|
|
430
393
|
}
|
|
431
394
|
const drop = cacheRead(reference) - cacheRead(current);
|
|
@@ -482,8 +445,8 @@ function computeTurn(messages, session) {
|
|
|
482
445
|
}
|
|
483
446
|
const provider = lastStep.model?.providerID;
|
|
484
447
|
const previousProvider = previousStep?.model?.providerID;
|
|
485
|
-
const key =
|
|
486
|
-
const previousKey =
|
|
448
|
+
const key = identityKey(lastStep.model);
|
|
449
|
+
const previousKey = identityKey(previousStep?.model);
|
|
487
450
|
return {
|
|
488
451
|
endAt: stepEnd(lastStep),
|
|
489
452
|
startAt: stepCreated(settled[0] ?? lastStep),
|
|
@@ -492,9 +455,6 @@ function computeTurn(messages, session) {
|
|
|
492
455
|
providerID: provider,
|
|
493
456
|
modelID: lastStep.model?.id,
|
|
494
457
|
variant: lastStep.model?.variant,
|
|
495
|
-
router: undefined,
|
|
496
|
-
identityChanged: false,
|
|
497
|
-
affinityKey: key,
|
|
498
458
|
providerChanged: previousProvider !== undefined && provider !== undefined && previousProvider !== provider,
|
|
499
459
|
modelChanged: previousKey !== undefined && key !== undefined && previousKey !== key && previousProvider === provider,
|
|
500
460
|
totals: totalize(settled),
|
|
@@ -503,8 +463,7 @@ function computeTurn(messages, session) {
|
|
|
503
463
|
drops,
|
|
504
464
|
cacheDropTokens: drops.reduce((sum, drop) => sum + drop.tokens, 0),
|
|
505
465
|
cacheExpired: drops.length > 0,
|
|
506
|
-
steps: settled.length
|
|
507
|
-
stepIDs: settled.map((step) => step.id)
|
|
466
|
+
steps: settled.length
|
|
508
467
|
};
|
|
509
468
|
}
|
|
510
469
|
function promptGap(current, previous) {
|
|
@@ -542,7 +501,7 @@ var DEFAULT_FIELDS = {
|
|
|
542
501
|
turn: true,
|
|
543
502
|
session: true,
|
|
544
503
|
reasoning: false,
|
|
545
|
-
cost:
|
|
504
|
+
cost: true,
|
|
546
505
|
cacheExpiry: true
|
|
547
506
|
};
|
|
548
507
|
function formatClock(ms) {
|
|
@@ -601,7 +560,7 @@ function displayTotals(totals) {
|
|
|
601
560
|
prompt
|
|
602
561
|
};
|
|
603
562
|
}
|
|
604
|
-
function totalsText(prefix, totals, labels, fields, stepCount = 0) {
|
|
563
|
+
function totalsText(prefix, totals, labels, fields, stepCount = 0, cost = totals.cost) {
|
|
605
564
|
const display = displayTotals(totals);
|
|
606
565
|
const input = display.prompt === display.input ? `${labels.input} ${formatTokens(display.input)}` : `${labels.input} ${formatTokens(display.input)} (${formatTokens(display.prompt)})`;
|
|
607
566
|
const parts = [
|
|
@@ -611,8 +570,8 @@ function totalsText(prefix, totals, labels, fields, stepCount = 0) {
|
|
|
611
570
|
];
|
|
612
571
|
if (fields.reasoning)
|
|
613
572
|
parts.push(`${labels.reasoning} ${formatTokens(display.reasoning)}`);
|
|
614
|
-
if (fields.cost)
|
|
615
|
-
parts.push(`${labels.cost} ${formatCost(
|
|
573
|
+
if (fields.cost && cost > 0)
|
|
574
|
+
parts.push(`${labels.cost} ${formatCost(cost)}`);
|
|
616
575
|
const steps = stepCount > 1 ? `${stepCount} ${stepCount === 1 ? labels.step : labels.steps}` : "";
|
|
617
576
|
const head = [prefix, steps].filter((piece) => piece !== "").join(" ");
|
|
618
577
|
const numbers = parts.join(", ");
|
|
@@ -624,7 +583,6 @@ function formatLineSegments(stats, options) {
|
|
|
624
583
|
const labels = { ...DEFAULT_LABELS, ...options?.labels };
|
|
625
584
|
const fields = { ...DEFAULT_FIELDS, ...options?.fields };
|
|
626
585
|
const turnTotals = options?.turnTotals ?? "latest";
|
|
627
|
-
const identity = options?.identity ?? "endpoint";
|
|
628
586
|
const segments = [];
|
|
629
587
|
if (fields.clock && stats.endAt !== undefined) {
|
|
630
588
|
segments.push({ text: formatClock(stats.endAt), tone: "muted" });
|
|
@@ -632,11 +590,7 @@ function formatLineSegments(stats, options) {
|
|
|
632
590
|
if (fields.delta && stats.promptGapMs !== undefined) {
|
|
633
591
|
segments.push({ text: `${labels.delta} ${formatDuration(stats.promptGapMs)}`, tone: "muted" });
|
|
634
592
|
}
|
|
635
|
-
if (
|
|
636
|
-
segments.push({ text: stats.router, tone: stats.identityChanged ? "warning" : "base" });
|
|
637
|
-
if (stats.identityChanged)
|
|
638
|
-
segments.push({ text: labels.providerChanged, tone: "warning" });
|
|
639
|
-
} else if (stats.providerID !== undefined) {
|
|
593
|
+
if (stats.providerID !== undefined) {
|
|
640
594
|
let text = stats.providerID;
|
|
641
595
|
if (fields.model && stats.modelID !== undefined)
|
|
642
596
|
text += `/${stats.modelID}`;
|
|
@@ -650,10 +604,13 @@ function formatLineSegments(stats, options) {
|
|
|
650
604
|
}
|
|
651
605
|
if (fields.turn && stats.totals.steps > 0) {
|
|
652
606
|
const totals = turnTotals === "sum" ? stats.totals : stats.latest;
|
|
653
|
-
segments.push({
|
|
607
|
+
segments.push({
|
|
608
|
+
text: totalsText(labels.turn, totals, labels, fields, turnTotals === "sum" ? stats.totals.steps : stats.steps, stats.totals.cost),
|
|
609
|
+
tone: "base"
|
|
610
|
+
});
|
|
654
611
|
}
|
|
655
612
|
if (fields.session && stats.session) {
|
|
656
|
-
segments.push({ text: totalsText(labels.session, stats.session, labels, fields), tone: "muted" });
|
|
613
|
+
segments.push({ text: totalsText(labels.session, stats.session, labels, fields, 0, stats.session.cost), tone: "muted" });
|
|
657
614
|
}
|
|
658
615
|
if (fields.cacheExpiry && stats.cacheExpired) {
|
|
659
616
|
const count = stats.drops.length > 1 ? `\xD7${stats.drops.length}` : "";
|
|
@@ -719,9 +676,9 @@ function buildWhere(range, filters, column = STEP_TIME) {
|
|
|
719
676
|
clauses.push(`${MODEL_KEY_SQL} = ?`);
|
|
720
677
|
params.push(filters.modelKey);
|
|
721
678
|
}
|
|
722
|
-
if (filters?.
|
|
723
|
-
clauses.push("
|
|
724
|
-
params.push(filters.
|
|
679
|
+
if (filters?.provider !== undefined && filters.provider !== "") {
|
|
680
|
+
clauses.push("provider_id = ?");
|
|
681
|
+
params.push(filters.provider);
|
|
725
682
|
}
|
|
726
683
|
if (filters?.sessionID !== undefined && filters.sessionID !== "") {
|
|
727
684
|
clauses.push("session_id = ?");
|
|
@@ -758,34 +715,32 @@ function overview(db, range, filters) {
|
|
|
758
715
|
function models(db, range, filters) {
|
|
759
716
|
const filter = buildWhere(range, filters);
|
|
760
717
|
const rows = db.query(`SELECT provider_id, model_id, variant,
|
|
761
|
-
GROUP_CONCAT(DISTINCT router) AS router_list,
|
|
762
718
|
${USAGE_SELECT},
|
|
763
719
|
MIN(${STEP_TIME}) AS first_at,
|
|
764
720
|
MAX(COALESCE(completed_at, recorded_at)) AS last_at
|
|
765
721
|
FROM steps ${filter.where}
|
|
766
722
|
GROUP BY provider_id, model_id, variant
|
|
767
723
|
ORDER BY steps DESC, cached DESC`).all(...filter.params);
|
|
768
|
-
const
|
|
724
|
+
const models = new Map;
|
|
769
725
|
for (const row of rows) {
|
|
770
726
|
const modelID = row.model_id ?? "(unknown)";
|
|
771
727
|
const variant = row.variant ?? undefined;
|
|
772
728
|
const key = `${modelID}#${variant ?? ""}`;
|
|
773
|
-
let model =
|
|
729
|
+
let model = models.get(key);
|
|
774
730
|
if (!model) {
|
|
775
731
|
model = { modelID, variant, providers: [], totals: emptyUsage() };
|
|
776
|
-
|
|
732
|
+
models.set(key, model);
|
|
777
733
|
}
|
|
778
734
|
const usage = usageFrom(row);
|
|
779
735
|
model.providers.push({
|
|
780
736
|
...usage,
|
|
781
737
|
providerID: row.provider_id ?? "(unknown)",
|
|
782
|
-
routers: row.router_list ? row.router_list.split(",").filter(Boolean) : [],
|
|
783
738
|
firstAt: row.first_at ?? undefined,
|
|
784
739
|
lastAt: row.last_at ?? undefined
|
|
785
740
|
});
|
|
786
741
|
addUsage(model.totals, usage);
|
|
787
742
|
}
|
|
788
|
-
return [...
|
|
743
|
+
return [...models.values()].sort((a, b) => billedTokens(b.totals) - billedTokens(a.totals));
|
|
789
744
|
}
|
|
790
745
|
function emptyUsage() {
|
|
791
746
|
return { steps: 0, input: 0, cached: 0, cacheWrite: 0, output: 0, reasoning: 0, cost: 0 };
|
|
@@ -918,7 +873,7 @@ function turns(db, options) {
|
|
|
918
873
|
}
|
|
919
874
|
const steps = db.query(`SELECT message_id, session_id, provider_id, model_id, variant,
|
|
920
875
|
input_tokens, cache_read, cache_write, output_tokens, reasoning_tokens, cost,
|
|
921
|
-
created_at, streamed_at, recorded_at, completed_at, finish
|
|
876
|
+
created_at, streamed_at, recorded_at, completed_at, finish
|
|
922
877
|
FROM steps ${clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : ""}
|
|
923
878
|
ORDER BY session_id, ${STEP_TIME}`).all(...params);
|
|
924
879
|
const promptClause = options?.sessionID === undefined ? "" : `WHERE session_id = ?${floor === undefined ? "" : " AND created_at >= ?"}`;
|
|
@@ -957,18 +912,9 @@ function turns(db, options) {
|
|
|
957
912
|
}
|
|
958
913
|
reference = message;
|
|
959
914
|
}
|
|
960
|
-
const lastWith = (pick) => {
|
|
961
|
-
for (let index = bucket.length - 1;index >= 0; index -= 1) {
|
|
962
|
-
const value = pick(bucket[index]);
|
|
963
|
-
if (value !== null)
|
|
964
|
-
return value;
|
|
965
|
-
}
|
|
966
|
-
return;
|
|
967
|
-
};
|
|
968
915
|
const providerID = last.provider_id ?? undefined;
|
|
969
916
|
const modelID = last.model_id ?? undefined;
|
|
970
917
|
const variant = last.variant ?? undefined;
|
|
971
|
-
const router = lastWith((row) => row.router);
|
|
972
918
|
let produced = 0;
|
|
973
919
|
let window = 0;
|
|
974
920
|
for (const row of bucket) {
|
|
@@ -986,8 +932,6 @@ function turns(db, options) {
|
|
|
986
932
|
providerID,
|
|
987
933
|
modelID,
|
|
988
934
|
variant,
|
|
989
|
-
router,
|
|
990
|
-
routerChanged: previous?.router !== undefined && router !== undefined && previous.router !== router,
|
|
991
935
|
providerChanged: previous?.providerID !== undefined && providerID !== undefined && previous.providerID !== providerID,
|
|
992
936
|
modelChanged: previous !== undefined && previous.providerID === providerID && `${previous.modelID ?? ""}#${previous.variant ?? ""}` !== `${modelID ?? ""}#${variant ?? ""}`,
|
|
993
937
|
steps: bucket.length,
|
|
@@ -1031,127 +975,9 @@ function promptFloor(db, sessionID, count) {
|
|
|
1031
975
|
const rows = db.query("SELECT created_at FROM prompts WHERE session_id = ? AND created_at IS NOT NULL ORDER BY created_at DESC LIMIT ?").all(sessionID, count);
|
|
1032
976
|
return rows.length === count ? rows[count - 1].created_at : undefined;
|
|
1033
977
|
}
|
|
1034
|
-
function routerBefore(db, sessionID, at) {
|
|
1035
|
-
if (at === undefined)
|
|
1036
|
-
return;
|
|
1037
|
-
const row = db.query(`SELECT router FROM steps
|
|
1038
|
-
WHERE session_id = $sessionID AND router IS NOT NULL AND COALESCE(created_at, recorded_at) < $at
|
|
1039
|
-
ORDER BY COALESCE(created_at, recorded_at) DESC LIMIT 1`).get({ $sessionID: sessionID, $at: at });
|
|
1040
|
-
return row?.router ?? undefined;
|
|
1041
|
-
}
|
|
1042
|
-
function routerForSteps(db, sessionID, messageIDs) {
|
|
1043
|
-
if (messageIDs.length === 0)
|
|
1044
|
-
return;
|
|
1045
|
-
const placeholders = messageIDs.map(() => "?").join(", ");
|
|
1046
|
-
const row = db.query(`SELECT router FROM steps
|
|
1047
|
-
WHERE session_id = ? AND message_id IN (${placeholders}) AND router IS NOT NULL
|
|
1048
|
-
ORDER BY recorded_at DESC LIMIT 1`).get(sessionID, ...messageIDs);
|
|
1049
|
-
return row?.router ?? undefined;
|
|
1050
|
-
}
|
|
1051
978
|
function filterOptions(db) {
|
|
1052
979
|
const pick = (column) => db.query(`SELECT ${column} AS value, COUNT(*) AS steps FROM steps WHERE ${column} IS NOT NULL GROUP BY ${column} ORDER BY steps DESC`).all().map((row) => ({ value: row.value, steps: row.steps }));
|
|
1053
|
-
return { models: pick("model_id"),
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
// src/endpoint.ts
|
|
1057
|
-
var ENDPOINT_HEADER = "x-opencode-endpoint-id";
|
|
1058
|
-
var ENDPOINT_MAX = 64;
|
|
1059
|
-
var KNOWN_ENDPOINT_HOSTS = [
|
|
1060
|
-
["api.fireworks.ai", "fireworks"],
|
|
1061
|
-
["api.deepseek.com", "deepseek"],
|
|
1062
|
-
["api.together.xyz", "together"],
|
|
1063
|
-
["api.groq.com", "groq"],
|
|
1064
|
-
["api.cerebras.ai", "cerebras"],
|
|
1065
|
-
["api.sambanova.ai", "sambanova"],
|
|
1066
|
-
["api.hyperbolic.xyz", "hyperbolic"],
|
|
1067
|
-
["api.deepinfra.com", "deepinfra"],
|
|
1068
|
-
["api.baseten.co", "baseten"],
|
|
1069
|
-
["api.novita.ai", "novita"],
|
|
1070
|
-
["api.parasail.io", "parasail"],
|
|
1071
|
-
["api.moonshot.ai", "moonshot"],
|
|
1072
|
-
["api.moonshot.cn", "moonshot"],
|
|
1073
|
-
["api.z.ai", "zhipu"],
|
|
1074
|
-
["open.bigmodel.cn", "zhipu"],
|
|
1075
|
-
["api.minimax.chat", "minimax"],
|
|
1076
|
-
["dashscope.aliyuncs.com", "alibaba"],
|
|
1077
|
-
["api.x.ai", "xai"],
|
|
1078
|
-
["api.anthropic.com", "anthropic"],
|
|
1079
|
-
["api.openai.com", "openai"],
|
|
1080
|
-
["openai.azure.com", "azure"],
|
|
1081
|
-
["bedrock-runtime.*.amazonaws.com", "bedrock"],
|
|
1082
|
-
["generativelanguage.googleapis.com", "google"],
|
|
1083
|
-
["aiplatform.googleapis.com", "vertex"],
|
|
1084
|
-
["api.nvidia.com", "nvidia"],
|
|
1085
|
-
["api.mistral.ai", "mistral"],
|
|
1086
|
-
["api.cloudflare.com", "cloudflare"]
|
|
1087
|
-
];
|
|
1088
|
-
function readHeader(headers, name) {
|
|
1089
|
-
const get = headers.get;
|
|
1090
|
-
if (typeof get === "function") {
|
|
1091
|
-
const value = get.call(headers, name);
|
|
1092
|
-
return value === null || value === "" ? undefined : value;
|
|
1093
|
-
}
|
|
1094
|
-
for (const key of Object.keys(headers)) {
|
|
1095
|
-
if (key.toLowerCase() !== name)
|
|
1096
|
-
continue;
|
|
1097
|
-
const value = headers[key];
|
|
1098
|
-
return value ? value : undefined;
|
|
1099
|
-
}
|
|
1100
|
-
return;
|
|
1101
|
-
}
|
|
1102
|
-
function inferFromHeaders(headers) {
|
|
1103
|
-
if (!headers)
|
|
1104
|
-
return;
|
|
1105
|
-
const value = readHeader(headers, ENDPOINT_HEADER);
|
|
1106
|
-
if (value === undefined)
|
|
1107
|
-
return;
|
|
1108
|
-
const router = normalizeToken(value, ENDPOINT_MAX);
|
|
1109
|
-
if (router === undefined)
|
|
1110
|
-
return;
|
|
1111
|
-
return { router, source: `header:${ENDPOINT_HEADER}` };
|
|
1112
|
-
}
|
|
1113
|
-
function hostMatches(host, pattern) {
|
|
1114
|
-
const hostLabels = host.split(".");
|
|
1115
|
-
const patternLabels = pattern.split(".");
|
|
1116
|
-
if (patternLabels.includes("*")) {
|
|
1117
|
-
if (hostLabels.length !== patternLabels.length)
|
|
1118
|
-
return false;
|
|
1119
|
-
return patternLabels.every((label, index) => label === "*" || label === hostLabels[index]);
|
|
1120
|
-
}
|
|
1121
|
-
if (host === pattern)
|
|
1122
|
-
return true;
|
|
1123
|
-
if (host.endsWith(`.${pattern}`))
|
|
1124
|
-
return true;
|
|
1125
|
-
return host.endsWith(`-${pattern}`);
|
|
1126
|
-
}
|
|
1127
|
-
function inferFromUrl(url) {
|
|
1128
|
-
if (url === undefined)
|
|
1129
|
-
return;
|
|
1130
|
-
let host;
|
|
1131
|
-
try {
|
|
1132
|
-
host = new URL(url).hostname.toLowerCase();
|
|
1133
|
-
} catch {
|
|
1134
|
-
return;
|
|
1135
|
-
}
|
|
1136
|
-
if (host === "")
|
|
1137
|
-
return;
|
|
1138
|
-
for (const [suffix, router] of KNOWN_ENDPOINT_HOSTS) {
|
|
1139
|
-
if (hostMatches(host, suffix))
|
|
1140
|
-
return { router, source: `endpoint:${host}` };
|
|
1141
|
-
}
|
|
1142
|
-
return;
|
|
1143
|
-
}
|
|
1144
|
-
function mergeEndpoint(...candidates) {
|
|
1145
|
-
const merged = {};
|
|
1146
|
-
for (const candidate of candidates) {
|
|
1147
|
-
if (candidate === undefined)
|
|
1148
|
-
continue;
|
|
1149
|
-
if (merged.router === undefined && candidate.router !== undefined)
|
|
1150
|
-
merged.router = candidate.router;
|
|
1151
|
-
if (merged.source === undefined && candidate.source !== undefined)
|
|
1152
|
-
merged.source = candidate.source;
|
|
1153
|
-
}
|
|
1154
|
-
return merged.router === undefined ? undefined : merged;
|
|
980
|
+
return { models: pick("model_id"), providers: pick("provider_id") };
|
|
1155
981
|
}
|
|
1156
982
|
|
|
1157
983
|
// src/recorder.ts
|
|
@@ -1160,7 +986,6 @@ function createRecorder(db, options) {
|
|
|
1160
986
|
const MAX_PENDING = 256;
|
|
1161
987
|
const pending = new Map;
|
|
1162
988
|
const lastModel = new Map;
|
|
1163
|
-
const known = new Map;
|
|
1164
989
|
const now = options?.now ?? (() => Date.now());
|
|
1165
990
|
const MAX_DEFERRED = 256;
|
|
1166
991
|
const deferred = [];
|
|
@@ -1211,26 +1036,11 @@ function createRecorder(db, options) {
|
|
|
1211
1036
|
}
|
|
1212
1037
|
function forget(sessionID) {
|
|
1213
1038
|
lastModel.delete(sessionID);
|
|
1214
|
-
known.delete(sessionID);
|
|
1215
1039
|
for (const [messageID, step] of pending) {
|
|
1216
1040
|
if (step.sessionID === sessionID)
|
|
1217
1041
|
pending.delete(messageID);
|
|
1218
1042
|
}
|
|
1219
1043
|
}
|
|
1220
|
-
function strength(info) {
|
|
1221
|
-
if (info?.router === undefined)
|
|
1222
|
-
return 0;
|
|
1223
|
-
return info.source?.startsWith("header:") === true ? 2 : 1;
|
|
1224
|
-
}
|
|
1225
|
-
function noteRouter(sessionID, modelID, info) {
|
|
1226
|
-
const previous = known.get(sessionID);
|
|
1227
|
-
const merged = strength(previous?.info) > strength(info) ? previous.info : mergeEndpoint(info, previous?.info);
|
|
1228
|
-
const info2 = merged ?? info;
|
|
1229
|
-
const settled = previous !== undefined && previous.modelID === modelID && previous.info.router === info2.router;
|
|
1230
|
-
remember(known, sessionID, { modelID, info: info2 }, MAX_TRACKED_SESSIONS);
|
|
1231
|
-
if (!settled)
|
|
1232
|
-
write(() => setStepRouterForSessionModel(db, sessionID, modelID, info2));
|
|
1233
|
-
}
|
|
1234
1044
|
function recordPendingStep(messageID, data, finish, at) {
|
|
1235
1045
|
const sessionID = data.sessionID;
|
|
1236
1046
|
if (typeof sessionID !== "string")
|
|
@@ -1238,8 +1048,6 @@ function createRecorder(db, options) {
|
|
|
1238
1048
|
const info = pending.get(messageID);
|
|
1239
1049
|
pending.delete(messageID);
|
|
1240
1050
|
const model = info?.model ?? lastModel.get(sessionID);
|
|
1241
|
-
const learned = known.get(sessionID);
|
|
1242
|
-
const matched = learned !== undefined && (learned.modelID === undefined || learned.modelID === model?.id) ? learned.info : undefined;
|
|
1243
1051
|
write(() => recordStep(db, {
|
|
1244
1052
|
messageID,
|
|
1245
1053
|
sessionID,
|
|
@@ -1256,7 +1064,6 @@ function createRecorder(db, options) {
|
|
|
1256
1064
|
streamedAt: info?.streamed,
|
|
1257
1065
|
completedAt: at,
|
|
1258
1066
|
finish,
|
|
1259
|
-
router: matched?.router,
|
|
1260
1067
|
recordedAt: now()
|
|
1261
1068
|
}));
|
|
1262
1069
|
}
|
|
@@ -1372,16 +1179,7 @@ function createRecorder(db, options) {
|
|
|
1372
1179
|
}
|
|
1373
1180
|
return found;
|
|
1374
1181
|
}
|
|
1375
|
-
|
|
1376
|
-
const sessionID = event.sessionID;
|
|
1377
|
-
if (typeof sessionID !== "string")
|
|
1378
|
-
return;
|
|
1379
|
-
const inferred = mergeEndpoint(inferFromHeaders(event.response?.headers), inferFromUrl(event.request?.url));
|
|
1380
|
-
if (inferred === undefined)
|
|
1381
|
-
return;
|
|
1382
|
-
noteRouter(sessionID, event.model?.id, inferred);
|
|
1383
|
-
}
|
|
1384
|
-
return { handleEvent, handleHttpResponse, pendingFor };
|
|
1182
|
+
return { handleEvent, pendingFor };
|
|
1385
1183
|
}
|
|
1386
1184
|
|
|
1387
1185
|
// src/index.ts
|
|
@@ -1415,18 +1213,7 @@ var src_default = Plugin.define({
|
|
|
1415
1213
|
}
|
|
1416
1214
|
} catch {}
|
|
1417
1215
|
})();
|
|
1418
|
-
let hook;
|
|
1419
|
-
try {
|
|
1420
|
-
hook = await ctx.session.hook("http.response", (event) => {
|
|
1421
|
-
try {
|
|
1422
|
-
recorder.handleHttpResponse(event);
|
|
1423
|
-
} catch {}
|
|
1424
|
-
});
|
|
1425
|
-
} catch {
|
|
1426
|
-
hook = undefined;
|
|
1427
|
-
}
|
|
1428
1216
|
return async () => {
|
|
1429
|
-
await hook?.dispose();
|
|
1430
1217
|
abort.abort();
|
|
1431
1218
|
await stream;
|
|
1432
1219
|
db.close();
|
|
@@ -1437,4 +1224,4 @@ export {
|
|
|
1437
1224
|
src_default as default
|
|
1438
1225
|
};
|
|
1439
1226
|
|
|
1440
|
-
//# debugId=
|
|
1227
|
+
//# debugId=CB2A8AAFDC0E182864756E2164756E21
|