@cesarus777/opencode-cgp 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +9 -3
- package/dist/cli.js +674 -21
- package/dist/core.js +49 -4
- package/dist/index.js +570 -9
- package/dist/rpc.js +129 -2
- package/dist/tui.js +128 -5
- package/dist/types/adapter.d.ts +2 -0
- package/dist/types/core.d.ts +1 -0
- package/dist/types/dashboard/command.d.ts +1 -1
- package/dist/types/dashboard/connection.d.ts +2 -0
- package/dist/types/dashboard/controller.d.ts +3 -0
- package/dist/types/dashboard/models.d.ts +42 -0
- package/dist/types/dashboard/modelview.d.ts +30 -0
- package/dist/types/dashboard/port.d.ts +6 -0
- package/dist/types/dashboard/terminal.d.ts +5 -0
- package/dist/types/dashboard/view.d.ts +3 -1
- package/dist/types/history.d.ts +1 -0
- package/dist/types/modelanalytics.d.ts +8 -0
- package/dist/types/modelanalyticsserver.d.ts +16 -0
- package/dist/types/modelseries.d.ts +43 -0
- package/dist/types/rpc.d.ts +28 -0
- package/dist/types/tui/chartformat.d.ts +1 -1
- package/package.json +1 -1
package/dist/rpc.js
CHANGED
|
@@ -43,6 +43,12 @@ function isHistoryRead(value) {
|
|
|
43
43
|
function isHistoryResult(value) {
|
|
44
44
|
return valid(value, historyResultSchema);
|
|
45
45
|
}
|
|
46
|
+
function isAnalyticsRead(value) {
|
|
47
|
+
return valid(value, analyticsReadSchema) && record(value) && typeof value.from === "number" && typeof value.to === "number" && value.from < value.to && value.to - value.from <= 604800000 && !(value.model !== undefined && value.unknownModel === true) && (!(value.modelCursor || value.requestCursor) || typeof value.snapshotId === "string");
|
|
48
|
+
}
|
|
49
|
+
function isAnalyticsResult(value) {
|
|
50
|
+
return valid(value, analyticsResultSchema);
|
|
51
|
+
}
|
|
46
52
|
function record(x) {
|
|
47
53
|
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
48
54
|
}
|
|
@@ -100,7 +106,7 @@ var count, text, enumeration = (...values) => ({ type: "string", enum: values })
|
|
|
100
106
|
type: "array",
|
|
101
107
|
items,
|
|
102
108
|
maxItems
|
|
103
|
-
}), measurement, row, batchFields, readSchema, batchSchema, resultSchema, monitorRpc, nonnegative, performanceSchema, performanceRow, performanceBatchSchema, performanceResultSchema, performanceRpc, chartPointSchema, chartRequestSchema, historyReadSchema, historyResultSchema, historyRpc;
|
|
109
|
+
}), measurement, row, batchFields, readSchema, batchSchema, resultSchema, monitorRpc, nonnegative, performanceSchema, performanceRow, performanceBatchSchema, performanceResultSchema, performanceRpc, chartPointSchema, chartRequestSchema, historyReadSchema, historyResultSchema, historyRpc, modelSummarySchema, analyticsReadSchema, analyticsResultSchema, analyticsRpc;
|
|
104
110
|
var init_rpc = __esm(() => {
|
|
105
111
|
count = {
|
|
106
112
|
type: "integer",
|
|
@@ -336,6 +342,123 @@ var init_rpc = __esm(() => {
|
|
|
336
342
|
methods: { read: { input: historyReadSchema, output: historyResultSchema } },
|
|
337
343
|
events: {}
|
|
338
344
|
});
|
|
345
|
+
modelSummarySchema = object({
|
|
346
|
+
model: text,
|
|
347
|
+
sessions: count,
|
|
348
|
+
completedRequests: count,
|
|
349
|
+
partialRequests: count,
|
|
350
|
+
legacyAttributionRequests: count,
|
|
351
|
+
tpsRequests: count,
|
|
352
|
+
ttftRequests: count,
|
|
353
|
+
excludedTpsRequests: count,
|
|
354
|
+
excludedTtftRequests: count,
|
|
355
|
+
outputTokens: nonnegative,
|
|
356
|
+
durationMs: nonnegative,
|
|
357
|
+
averageTps: nonnegative,
|
|
358
|
+
meanTtftMs: nonnegative,
|
|
359
|
+
medianTtftMs: nonnegative,
|
|
360
|
+
p95TtftMs: nonnegative
|
|
361
|
+
}, [
|
|
362
|
+
"sessions",
|
|
363
|
+
"completedRequests",
|
|
364
|
+
"partialRequests",
|
|
365
|
+
"legacyAttributionRequests",
|
|
366
|
+
"tpsRequests",
|
|
367
|
+
"ttftRequests",
|
|
368
|
+
"excludedTpsRequests",
|
|
369
|
+
"excludedTtftRequests",
|
|
370
|
+
"outputTokens",
|
|
371
|
+
"durationMs"
|
|
372
|
+
]);
|
|
373
|
+
analyticsReadSchema = object({
|
|
374
|
+
schemaVersion: { const: 1 },
|
|
375
|
+
from: count,
|
|
376
|
+
to: count,
|
|
377
|
+
buckets: { type: "integer", minimum: 1, maximum: 240 },
|
|
378
|
+
agent: text,
|
|
379
|
+
model: text,
|
|
380
|
+
unknownModel: { type: "boolean" },
|
|
381
|
+
sort: enumeration("requests", "tps", "ttft", "identity"),
|
|
382
|
+
snapshotId: text,
|
|
383
|
+
modelCursor: text,
|
|
384
|
+
requestCursor: text
|
|
385
|
+
}, ["schemaVersion", "from", "to", "buckets"]);
|
|
386
|
+
analyticsResultSchema = object({
|
|
387
|
+
schemaVersion: { const: 1 },
|
|
388
|
+
generatedAt: count,
|
|
389
|
+
expiresAt: count,
|
|
390
|
+
snapshotId: text,
|
|
391
|
+
enabled: { type: "boolean" },
|
|
392
|
+
from: count,
|
|
393
|
+
to: count,
|
|
394
|
+
agent: text,
|
|
395
|
+
models: array(modelSummarySchema, 50),
|
|
396
|
+
totalModels: count,
|
|
397
|
+
nextModelsCursor: text,
|
|
398
|
+
selected: modelSummarySchema,
|
|
399
|
+
buckets: array(object({ from: nonnegative, to: nonnegative, summary: modelSummarySchema }, ["from", "to"]), 240),
|
|
400
|
+
requests: array(object({
|
|
401
|
+
location: text,
|
|
402
|
+
sessionId: text,
|
|
403
|
+
requestId: text,
|
|
404
|
+
startedAt: count,
|
|
405
|
+
completedAt: count,
|
|
406
|
+
model: text,
|
|
407
|
+
agent: text,
|
|
408
|
+
attribution: enumeration("request", "legacy-record", "unknown"),
|
|
409
|
+
partial: { type: "boolean" },
|
|
410
|
+
outputTokens: nonnegative,
|
|
411
|
+
ttftMs: nonnegative
|
|
412
|
+
}, [
|
|
413
|
+
"location",
|
|
414
|
+
"sessionId",
|
|
415
|
+
"requestId",
|
|
416
|
+
"startedAt",
|
|
417
|
+
"completedAt",
|
|
418
|
+
"attribution",
|
|
419
|
+
"partial"
|
|
420
|
+
]), 50),
|
|
421
|
+
nextRequestsCursor: text,
|
|
422
|
+
coverage: object({
|
|
423
|
+
source: enumeration("retained-request-records"),
|
|
424
|
+
scannedRecords: count,
|
|
425
|
+
unscannedRecords: count,
|
|
426
|
+
duplicateRecords: count,
|
|
427
|
+
conflictingRequests: count,
|
|
428
|
+
missingIdentityRecords: count,
|
|
429
|
+
incompleteRequests: count,
|
|
430
|
+
invalidTimingRequests: count,
|
|
431
|
+
gapRecords: count,
|
|
432
|
+
maxRequests: count,
|
|
433
|
+
partial: { type: "boolean" }
|
|
434
|
+
}),
|
|
435
|
+
metadata: historyResultSchema.properties.metadata ?? {},
|
|
436
|
+
gaps: array(count, 128),
|
|
437
|
+
omittedGaps: count
|
|
438
|
+
}, [
|
|
439
|
+
"schemaVersion",
|
|
440
|
+
"generatedAt",
|
|
441
|
+
"expiresAt",
|
|
442
|
+
"snapshotId",
|
|
443
|
+
"enabled",
|
|
444
|
+
"from",
|
|
445
|
+
"to",
|
|
446
|
+
"models",
|
|
447
|
+
"totalModels",
|
|
448
|
+
"buckets",
|
|
449
|
+
"requests",
|
|
450
|
+
"coverage",
|
|
451
|
+
"metadata",
|
|
452
|
+
"gaps",
|
|
453
|
+
"omittedGaps"
|
|
454
|
+
]);
|
|
455
|
+
analyticsRpc = Rpc.define({
|
|
456
|
+
id: "context-guard.analytics.v1",
|
|
457
|
+
methods: {
|
|
458
|
+
read: { input: analyticsReadSchema, output: analyticsResultSchema }
|
|
459
|
+
},
|
|
460
|
+
events: {}
|
|
461
|
+
});
|
|
339
462
|
});
|
|
340
463
|
init_rpc();
|
|
341
464
|
|
|
@@ -355,7 +478,11 @@ export {
|
|
|
355
478
|
isHistoryResult,
|
|
356
479
|
isHistoryRead,
|
|
357
480
|
isBatch,
|
|
481
|
+
isAnalyticsResult,
|
|
482
|
+
isAnalyticsRead,
|
|
358
483
|
historyRpc,
|
|
359
484
|
historyReadSchema,
|
|
360
|
-
batchSchema
|
|
485
|
+
batchSchema,
|
|
486
|
+
analyticsRpc,
|
|
487
|
+
analyticsReadSchema
|
|
361
488
|
};
|
package/dist/tui.js
CHANGED
|
@@ -43,6 +43,12 @@ function isHistoryRead(value) {
|
|
|
43
43
|
function isHistoryResult(value) {
|
|
44
44
|
return valid(value, historyResultSchema);
|
|
45
45
|
}
|
|
46
|
+
function isAnalyticsRead(value) {
|
|
47
|
+
return valid(value, analyticsReadSchema) && record(value) && typeof value.from === "number" && typeof value.to === "number" && value.from < value.to && value.to - value.from <= 604800000 && !(value.model !== undefined && value.unknownModel === true) && (!(value.modelCursor || value.requestCursor) || typeof value.snapshotId === "string");
|
|
48
|
+
}
|
|
49
|
+
function isAnalyticsResult(value) {
|
|
50
|
+
return valid(value, analyticsResultSchema);
|
|
51
|
+
}
|
|
46
52
|
function record(x) {
|
|
47
53
|
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
48
54
|
}
|
|
@@ -100,7 +106,7 @@ var count, text, enumeration = (...values) => ({ type: "string", enum: values })
|
|
|
100
106
|
type: "array",
|
|
101
107
|
items,
|
|
102
108
|
maxItems
|
|
103
|
-
}), measurement, row, batchFields, readSchema, batchSchema, resultSchema, monitorRpc, nonnegative, performanceSchema, performanceRow, performanceBatchSchema, performanceResultSchema, performanceRpc, chartPointSchema, chartRequestSchema, historyReadSchema, historyResultSchema, historyRpc;
|
|
109
|
+
}), measurement, row, batchFields, readSchema, batchSchema, resultSchema, monitorRpc, nonnegative, performanceSchema, performanceRow, performanceBatchSchema, performanceResultSchema, performanceRpc, chartPointSchema, chartRequestSchema, historyReadSchema, historyResultSchema, historyRpc, modelSummarySchema, analyticsReadSchema, analyticsResultSchema, analyticsRpc;
|
|
104
110
|
var init_rpc = __esm(() => {
|
|
105
111
|
count = {
|
|
106
112
|
type: "integer",
|
|
@@ -336,6 +342,123 @@ var init_rpc = __esm(() => {
|
|
|
336
342
|
methods: { read: { input: historyReadSchema, output: historyResultSchema } },
|
|
337
343
|
events: {}
|
|
338
344
|
});
|
|
345
|
+
modelSummarySchema = object({
|
|
346
|
+
model: text,
|
|
347
|
+
sessions: count,
|
|
348
|
+
completedRequests: count,
|
|
349
|
+
partialRequests: count,
|
|
350
|
+
legacyAttributionRequests: count,
|
|
351
|
+
tpsRequests: count,
|
|
352
|
+
ttftRequests: count,
|
|
353
|
+
excludedTpsRequests: count,
|
|
354
|
+
excludedTtftRequests: count,
|
|
355
|
+
outputTokens: nonnegative,
|
|
356
|
+
durationMs: nonnegative,
|
|
357
|
+
averageTps: nonnegative,
|
|
358
|
+
meanTtftMs: nonnegative,
|
|
359
|
+
medianTtftMs: nonnegative,
|
|
360
|
+
p95TtftMs: nonnegative
|
|
361
|
+
}, [
|
|
362
|
+
"sessions",
|
|
363
|
+
"completedRequests",
|
|
364
|
+
"partialRequests",
|
|
365
|
+
"legacyAttributionRequests",
|
|
366
|
+
"tpsRequests",
|
|
367
|
+
"ttftRequests",
|
|
368
|
+
"excludedTpsRequests",
|
|
369
|
+
"excludedTtftRequests",
|
|
370
|
+
"outputTokens",
|
|
371
|
+
"durationMs"
|
|
372
|
+
]);
|
|
373
|
+
analyticsReadSchema = object({
|
|
374
|
+
schemaVersion: { const: 1 },
|
|
375
|
+
from: count,
|
|
376
|
+
to: count,
|
|
377
|
+
buckets: { type: "integer", minimum: 1, maximum: 240 },
|
|
378
|
+
agent: text,
|
|
379
|
+
model: text,
|
|
380
|
+
unknownModel: { type: "boolean" },
|
|
381
|
+
sort: enumeration("requests", "tps", "ttft", "identity"),
|
|
382
|
+
snapshotId: text,
|
|
383
|
+
modelCursor: text,
|
|
384
|
+
requestCursor: text
|
|
385
|
+
}, ["schemaVersion", "from", "to", "buckets"]);
|
|
386
|
+
analyticsResultSchema = object({
|
|
387
|
+
schemaVersion: { const: 1 },
|
|
388
|
+
generatedAt: count,
|
|
389
|
+
expiresAt: count,
|
|
390
|
+
snapshotId: text,
|
|
391
|
+
enabled: { type: "boolean" },
|
|
392
|
+
from: count,
|
|
393
|
+
to: count,
|
|
394
|
+
agent: text,
|
|
395
|
+
models: array(modelSummarySchema, 50),
|
|
396
|
+
totalModels: count,
|
|
397
|
+
nextModelsCursor: text,
|
|
398
|
+
selected: modelSummarySchema,
|
|
399
|
+
buckets: array(object({ from: nonnegative, to: nonnegative, summary: modelSummarySchema }, ["from", "to"]), 240),
|
|
400
|
+
requests: array(object({
|
|
401
|
+
location: text,
|
|
402
|
+
sessionId: text,
|
|
403
|
+
requestId: text,
|
|
404
|
+
startedAt: count,
|
|
405
|
+
completedAt: count,
|
|
406
|
+
model: text,
|
|
407
|
+
agent: text,
|
|
408
|
+
attribution: enumeration("request", "legacy-record", "unknown"),
|
|
409
|
+
partial: { type: "boolean" },
|
|
410
|
+
outputTokens: nonnegative,
|
|
411
|
+
ttftMs: nonnegative
|
|
412
|
+
}, [
|
|
413
|
+
"location",
|
|
414
|
+
"sessionId",
|
|
415
|
+
"requestId",
|
|
416
|
+
"startedAt",
|
|
417
|
+
"completedAt",
|
|
418
|
+
"attribution",
|
|
419
|
+
"partial"
|
|
420
|
+
]), 50),
|
|
421
|
+
nextRequestsCursor: text,
|
|
422
|
+
coverage: object({
|
|
423
|
+
source: enumeration("retained-request-records"),
|
|
424
|
+
scannedRecords: count,
|
|
425
|
+
unscannedRecords: count,
|
|
426
|
+
duplicateRecords: count,
|
|
427
|
+
conflictingRequests: count,
|
|
428
|
+
missingIdentityRecords: count,
|
|
429
|
+
incompleteRequests: count,
|
|
430
|
+
invalidTimingRequests: count,
|
|
431
|
+
gapRecords: count,
|
|
432
|
+
maxRequests: count,
|
|
433
|
+
partial: { type: "boolean" }
|
|
434
|
+
}),
|
|
435
|
+
metadata: historyResultSchema.properties.metadata ?? {},
|
|
436
|
+
gaps: array(count, 128),
|
|
437
|
+
omittedGaps: count
|
|
438
|
+
}, [
|
|
439
|
+
"schemaVersion",
|
|
440
|
+
"generatedAt",
|
|
441
|
+
"expiresAt",
|
|
442
|
+
"snapshotId",
|
|
443
|
+
"enabled",
|
|
444
|
+
"from",
|
|
445
|
+
"to",
|
|
446
|
+
"models",
|
|
447
|
+
"totalModels",
|
|
448
|
+
"buckets",
|
|
449
|
+
"requests",
|
|
450
|
+
"coverage",
|
|
451
|
+
"metadata",
|
|
452
|
+
"gaps",
|
|
453
|
+
"omittedGaps"
|
|
454
|
+
]);
|
|
455
|
+
analyticsRpc = Rpc.define({
|
|
456
|
+
id: "context-guard.analytics.v1",
|
|
457
|
+
methods: {
|
|
458
|
+
read: { input: analyticsReadSchema, output: analyticsResultSchema }
|
|
459
|
+
},
|
|
460
|
+
events: {}
|
|
461
|
+
});
|
|
339
462
|
});
|
|
340
463
|
|
|
341
464
|
// src/family.ts
|
|
@@ -834,9 +957,9 @@ var init_format = __esm(() => {
|
|
|
834
957
|
});
|
|
835
958
|
|
|
836
959
|
// src/tui/chartformat.ts
|
|
837
|
-
function columns2(points, from, to, width, gaps = []) {
|
|
960
|
+
function columns2(points, from, to, width, gaps = [], halfOpen = false) {
|
|
838
961
|
const size = Math.max(1, Math.min(120, Math.floor(width))), values = Array.from({ length: size });
|
|
839
|
-
const column = (time) => Math.max(0, Math.min(size - 1, Math.floor((time - from) / Math.max(1, to - from) * (size - 1))));
|
|
962
|
+
const column = (time) => Math.max(0, Math.min(size - 1, Math.floor((time - from) / Math.max(1, to - from) * (halfOpen ? size : size - 1))));
|
|
840
963
|
let min = Infinity, max = -Infinity;
|
|
841
964
|
for (const point of points) {
|
|
842
965
|
if (point.time < from || point.time > to || point.value === undefined || !Number.isFinite(point.value) || point.value < 0)
|
|
@@ -860,12 +983,12 @@ function sparkline(points, from, to, width, gaps = []) {
|
|
|
860
983
|
chars[column(time)] = "\u2506";
|
|
861
984
|
return { line: chars.join(""), ...min === Infinity ? {} : { min, max } };
|
|
862
985
|
}
|
|
863
|
-
function metricPlot(points, from, to, width, gaps = []) {
|
|
986
|
+
function metricPlot(points, from, to, width, gaps = [], halfOpen = false) {
|
|
864
987
|
const label = (value) => value >= 1e6 ? `${(value / 1e6).toFixed(1)}M` : value >= 1000 ? `${(value / 1000).toFixed(1)}k` : value.toFixed(1);
|
|
865
988
|
const peak = Math.max(0, ...points.filter((p) => p.time >= from && p.time <= to && Number.isFinite(p.value)).map((p) => p.value ?? 0));
|
|
866
989
|
const scale = Math.max(1, peak);
|
|
867
990
|
const gutter = width >= 20 ? Math.max(label(scale).length, 3) : 0;
|
|
868
|
-
const { values, column } = columns2(points, from, to, Math.max(1, width - gutter - 2), gaps);
|
|
991
|
+
const { values, column } = columns2(points, from, to, Math.max(1, halfOpen ? Math.min(points.length, width - gutter - 2) : width - gutter - 2), gaps, halfOpen);
|
|
869
992
|
const gapColumns = new Set(gaps.filter((time) => time >= from && time <= to).map(column));
|
|
870
993
|
const height = 6;
|
|
871
994
|
const blocks = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
|
package/dist/types/adapter.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type HistoryRpcPort } from "./historyserver.js";
|
|
2
|
+
import { type AnalyticsRpcPort } from "./modelanalyticsserver.js";
|
|
2
3
|
import { type SnapshotRpcPort } from "./snapshotserver.js";
|
|
3
4
|
import type { Plugin } from "@opencode/plugin";
|
|
4
5
|
import type { TelemetrySink } from "./types.js";
|
|
5
6
|
export interface Host {
|
|
6
7
|
rpc?: SnapshotRpcPort;
|
|
7
8
|
historyRpc?: HistoryRpcPort;
|
|
9
|
+
analyticsRpc?: AnalyticsRpcPort;
|
|
8
10
|
app: {
|
|
9
11
|
version: string;
|
|
10
12
|
};
|
package/dist/types/core.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DashboardConnectionOptions } from "./connection.js";
|
|
2
|
-
export declare const dashboardHelp = "Usage: context-guard dashboard tui [options]\nOpen a read-only live terminal dashboard for an existing OpenCode server.\n\nStart here\n 1. Keep an OpenCode server running with Context Guard registered.\n 2.
|
|
2
|
+
export declare const dashboardHelp = "Usage: context-guard dashboard tui [options]\nOpen a read-only live terminal dashboard for an existing OpenCode server.\n\nStart here\n 1. Keep an OpenCode server running with Context Guard registered.\n 2. Open a separate terminal tab, window or pane for the dashboard.\n 3. In the same project directory, run: context-guard dashboard tui\n Keep working in OpenCode; the dashboard reads live updates from its server.\n\nOptions\n --directory PATH Owning project directory (local default: current directory)\n --server URL Connect to an explicit server; requires --directory with\n an absolute server-side project path. Use HTTPS except\n on loopback. Credentials in URLs are rejected.\n -h, --help Show this help\n\nExamples\n context-guard dashboard tui\n context-guard dashboard tui --directory /absolute/project\n context-guard dashboard tui --server https://server.example --directory /server/project\n\nRequirements\n Interactive terminal and Bun >=1.3.0. The OpenTUI renderer is installed\n automatically with CGP. This command never starts a server.\n\nAuthentication (explicit servers)\n CONTEXT_GUARD_PASSWORD Server password\n CONTEXT_GUARD_USERNAME Server username (default: opencode)\n\nKeys\n 1/2/3/4 overview/detail/history/models; arrows or j/k move; Enter detail/chart;\n Models: Left/Right metric, [/] bucket, d requests, n/p pages, s sort;\n / session search; f filters (Models: agent); l location; w time window;\n r refresh; ? help; q or Ctrl+C quit.";
|
|
3
3
|
export declare function parseDashboardArgs(args: readonly string[], cwd: string, env: Readonly<Record<string, string | undefined>>): DashboardConnectionOptions | "help";
|
|
4
4
|
export declare function checkTerminalRuntime(version: string, interactive: boolean): void;
|
|
5
5
|
export declare function loadTerminalRenderer(): Promise<typeof import("@opentui/core")>;
|
|
@@ -2,6 +2,7 @@ import { Service } from "@opencode/client/service";
|
|
|
2
2
|
import type { HistoryChartRead } from "../historycharts.js";
|
|
3
3
|
import type { SnapshotTransport } from "../tui/transport.js";
|
|
4
4
|
import type { DashboardPort, SessionPageInput } from "./port.js";
|
|
5
|
+
import type { AnalyticsRead } from "../modelseries.js";
|
|
5
6
|
export interface DashboardConnectionOptions {
|
|
6
7
|
readonly directory: string;
|
|
7
8
|
readonly server?: string;
|
|
@@ -32,5 +33,6 @@ export declare function connectDashboard(options: DashboardConnectionOptions, di
|
|
|
32
33
|
readonly transport: SnapshotTransport;
|
|
33
34
|
}>;
|
|
34
35
|
history(input: HistoryChartRead, signal: AbortSignal): Promise<import("../historycharts.js").HistoryChartResult>;
|
|
36
|
+
analytics?(input: AnalyticsRead, signal: AbortSignal): Promise<import("../modelseries.js").AnalyticsResult>;
|
|
35
37
|
}>;
|
|
36
38
|
export type DashboardConnection = Awaited<ReturnType<typeof connectDashboard>>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type InventoryLimits } from "./inventory.js";
|
|
2
2
|
import type { DashboardPort } from "./port.js";
|
|
3
|
+
import { ModelsController } from "./models.js";
|
|
3
4
|
import { type DashboardView, type DashboardScreen, type DashboardFilters, type HistoryWindow } from "./view.js";
|
|
4
5
|
export interface DashboardControllerOptions {
|
|
5
6
|
readonly limits?: InventoryLimits;
|
|
@@ -32,6 +33,7 @@ export declare class DashboardController {
|
|
|
32
33
|
private refreshTimer;
|
|
33
34
|
private ageTimer;
|
|
34
35
|
private view;
|
|
36
|
+
readonly models: ModelsController;
|
|
35
37
|
constructor(port: DashboardPort, options?: DashboardControllerOptions);
|
|
36
38
|
private createLocation;
|
|
37
39
|
getView(): DashboardView;
|
|
@@ -44,6 +46,7 @@ export declare class DashboardController {
|
|
|
44
46
|
setScreen(screen: DashboardScreen): void;
|
|
45
47
|
setWindow(windowMs: HistoryWindow): void;
|
|
46
48
|
setFilters(filters: Partial<DashboardFilters>): void;
|
|
49
|
+
private configureModels;
|
|
47
50
|
refresh(): Promise<void>;
|
|
48
51
|
private current;
|
|
49
52
|
private scan;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AnalyticsRead, AnalyticsResult } from "../modelseries.js";
|
|
2
|
+
import { type DashboardPort } from "./port.js";
|
|
3
|
+
import type { HistoryWindow } from "./view.js";
|
|
4
|
+
export interface DashboardModels {
|
|
5
|
+
status: "idle" | "loading" | "ready" | "disabled" | "unsupported" | "unavailable";
|
|
6
|
+
stale: boolean;
|
|
7
|
+
sort: NonNullable<AnalyticsRead["sort"]>;
|
|
8
|
+
windowMs: HistoryWindow;
|
|
9
|
+
modelPage: number;
|
|
10
|
+
requestPage: number;
|
|
11
|
+
result?: AnalyticsResult;
|
|
12
|
+
}
|
|
13
|
+
/** Location-scoped request state; it never opens a monitor/event subscription. */
|
|
14
|
+
export declare class ModelsController {
|
|
15
|
+
private readonly changed;
|
|
16
|
+
private port;
|
|
17
|
+
private active;
|
|
18
|
+
private key;
|
|
19
|
+
private agent;
|
|
20
|
+
private seedModel;
|
|
21
|
+
private selection;
|
|
22
|
+
private resolution;
|
|
23
|
+
private from;
|
|
24
|
+
private to;
|
|
25
|
+
private generation;
|
|
26
|
+
private abort;
|
|
27
|
+
private pending;
|
|
28
|
+
private modelPages;
|
|
29
|
+
private requestPages;
|
|
30
|
+
private state;
|
|
31
|
+
constructor(changed: () => void);
|
|
32
|
+
getView(): DashboardModels;
|
|
33
|
+
configure(port: DashboardPort, active: boolean, windowMs: HistoryWindow, agent: string, seedModel: string, now: number): void;
|
|
34
|
+
cancel(): void;
|
|
35
|
+
stop(): void;
|
|
36
|
+
refresh(now: number): Promise<void>;
|
|
37
|
+
setWidth(width: number): void;
|
|
38
|
+
select(model: string): void;
|
|
39
|
+
sort(): void;
|
|
40
|
+
page(kind: "models" | "requests", direction: -1 | 1): void;
|
|
41
|
+
private query;
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ModelSummary } from "../modelanalytics.js";
|
|
2
|
+
import type { DashboardView } from "./view.js";
|
|
3
|
+
export declare const modelMetrics: readonly [{
|
|
4
|
+
readonly key: "averageTps";
|
|
5
|
+
readonly label: "Weighted request TPS";
|
|
6
|
+
readonly unit: "t/s";
|
|
7
|
+
readonly divisor: 1;
|
|
8
|
+
}, {
|
|
9
|
+
readonly key: "medianTtftMs";
|
|
10
|
+
readonly label: "TTFT median ~";
|
|
11
|
+
readonly unit: "s";
|
|
12
|
+
readonly divisor: 1000;
|
|
13
|
+
}, {
|
|
14
|
+
readonly key: "p95TtftMs";
|
|
15
|
+
readonly label: "TTFT p95 ~";
|
|
16
|
+
readonly unit: "s";
|
|
17
|
+
readonly divisor: 1000;
|
|
18
|
+
}, {
|
|
19
|
+
readonly key: "completedRequests";
|
|
20
|
+
readonly label: "Completed requests";
|
|
21
|
+
readonly unit: "requests";
|
|
22
|
+
readonly divisor: 1;
|
|
23
|
+
}, {
|
|
24
|
+
readonly key: "outputTokens";
|
|
25
|
+
readonly label: "Reported output";
|
|
26
|
+
readonly unit: "tokens";
|
|
27
|
+
readonly divisor: 1;
|
|
28
|
+
}];
|
|
29
|
+
export declare function terminalModelRow(model: Readonly<ModelSummary>, focused: boolean): string[];
|
|
30
|
+
export declare function terminalModels(view: DashboardView, width: number, detail: boolean, metricIndex: number, sample: number, focus: number, requests: boolean): string[];
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { ExecutionStatus } from "../family.js";
|
|
2
2
|
import type { HistoryChartRead, HistoryChartResult } from "../historycharts.js";
|
|
3
3
|
import type { SnapshotTransport } from "../tui/transport.js";
|
|
4
|
+
import type { AnalyticsRead, AnalyticsResult } from "../modelseries.js";
|
|
5
|
+
export declare class AnalyticsUnavailable extends Error {
|
|
6
|
+
readonly reason: "unsupported" | "unavailable";
|
|
7
|
+
constructor(reason: "unsupported" | "unavailable");
|
|
8
|
+
}
|
|
4
9
|
/** The inventory boundary deliberately excludes titles, metadata, and content. */
|
|
5
10
|
export interface DashboardSession {
|
|
6
11
|
readonly sessionId: string;
|
|
@@ -34,6 +39,7 @@ export interface DashboardPort {
|
|
|
34
39
|
readonly transport: SnapshotTransport;
|
|
35
40
|
}>;
|
|
36
41
|
history(input: HistoryChartRead, signal: AbortSignal): Promise<HistoryChartResult>;
|
|
42
|
+
analytics?(input: AnalyticsRead, signal: AbortSignal): Promise<AnalyticsResult>;
|
|
37
43
|
}
|
|
38
44
|
export interface InventoryNode extends DashboardSession {
|
|
39
45
|
readonly status: ExecutionStatus;
|
|
@@ -15,6 +15,11 @@ export declare class TerminalShell {
|
|
|
15
15
|
scroll: number;
|
|
16
16
|
metric: number;
|
|
17
17
|
sample: number;
|
|
18
|
+
modelDetail: boolean;
|
|
19
|
+
modelRequests: boolean;
|
|
20
|
+
modelFocus: number;
|
|
21
|
+
private modelFocusId;
|
|
22
|
+
private modelViewport;
|
|
18
23
|
help: boolean;
|
|
19
24
|
editor: Editor | undefined;
|
|
20
25
|
input: string;
|
|
@@ -3,12 +3,13 @@ import type { MonitorHealth } from "../snapshot.js";
|
|
|
3
3
|
import type { HistoryChartResult } from "../historycharts.js";
|
|
4
4
|
import type { Connection } from "../tui/transport.js";
|
|
5
5
|
import type { InventoryNotice } from "./inventory.js";
|
|
6
|
+
import type { DashboardModels } from "./models.js";
|
|
6
7
|
export type Immutable<T> = T extends object ? {
|
|
7
8
|
readonly [K in keyof T]: Immutable<T[K]>;
|
|
8
9
|
} : T;
|
|
9
10
|
export declare const historyWindows: readonly [300000, 3600000, 86400000, 604800000];
|
|
10
11
|
export type HistoryWindow = (typeof historyWindows)[number];
|
|
11
|
-
export type DashboardScreen = "overview" | "detail" | "history";
|
|
12
|
+
export type DashboardScreen = "overview" | "detail" | "history" | "models";
|
|
12
13
|
export interface DashboardFilters {
|
|
13
14
|
readonly search: string;
|
|
14
15
|
readonly agent: string;
|
|
@@ -72,6 +73,7 @@ export type DashboardView = Immutable<{
|
|
|
72
73
|
selectedSessionId?: string;
|
|
73
74
|
selected?: DashboardRow;
|
|
74
75
|
history: DashboardHistory;
|
|
76
|
+
models: DashboardModels;
|
|
75
77
|
notices: readonly string[];
|
|
76
78
|
}>;
|
|
77
79
|
/** Clone before freezing so transport-owned objects never become UI-owned state. */
|
package/dist/types/history.d.ts
CHANGED
|
@@ -57,4 +57,12 @@ export interface ModelAnalyticsResult {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
/** Aggregate raw request records before any chart downsampling or session limits. */
|
|
60
|
+
export declare function analyzeModelRequests(records: readonly HistoryRecord[], options: ModelAnalyticsOptions): ModelAnalyticsResult & {
|
|
61
|
+
requests: CompletedAnalyticsRequest[];
|
|
62
|
+
};
|
|
63
|
+
export interface CompletedAnalyticsRequest extends AnalyticsRequest {
|
|
64
|
+
completedAt: number;
|
|
65
|
+
}
|
|
66
|
+
/** Summarize already reconciled requests, including independent metric eligibility. */
|
|
67
|
+
export declare function summarizeModelRequests(requests: readonly CompletedAnalyticsRequest[]): ModelSummary[];
|
|
60
68
|
export declare function aggregateModels(records: readonly HistoryRecord[], options: ModelAnalyticsOptions): ModelAnalyticsResult;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RpcHandlers, RpcRegistration } from "@opencode/plugin/promise/rpc";
|
|
2
|
+
import { readPerformanceHistory } from "./history.js";
|
|
3
|
+
import { type AnalyticsRead, type AnalyticsResult } from "./modelseries.js";
|
|
4
|
+
import { analyticsRpc } from "./rpc.js";
|
|
5
|
+
export interface AnalyticsRpcPort {
|
|
6
|
+
register(definition: typeof analyticsRpc, handlers: RpcHandlers<typeof analyticsRpc>): Promise<Pick<RpcRegistration<typeof analyticsRpc>, "dispose">>;
|
|
7
|
+
}
|
|
8
|
+
/** One instance owns exactly one server-selected location/archive; no client paths. */
|
|
9
|
+
export declare function createAnalyticsService(path: string, location: string, enabled: boolean, options?: {
|
|
10
|
+
now?: () => number;
|
|
11
|
+
read?: typeof readPerformanceHistory;
|
|
12
|
+
}): {
|
|
13
|
+
read(input: AnalyticsRead, signal: AbortSignal): Promise<AnalyticsResult>;
|
|
14
|
+
dispose(): void;
|
|
15
|
+
};
|
|
16
|
+
export declare function serveModelAnalytics(port: AnalyticsRpcPort | undefined, path: string, location: string, enabled: boolean): Promise<() => void>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type CompletedAnalyticsRequest, type ModelAnalyticsResult, type ModelSummary } from "./modelanalytics.js";
|
|
2
|
+
import type { HistoryMetadata } from "./history.js";
|
|
3
|
+
export interface ModelBucket {
|
|
4
|
+
from: number;
|
|
5
|
+
to: number;
|
|
6
|
+
summary?: ModelSummary;
|
|
7
|
+
}
|
|
8
|
+
export interface AnalyticsRead {
|
|
9
|
+
schemaVersion: 1;
|
|
10
|
+
from: number;
|
|
11
|
+
to: number;
|
|
12
|
+
buckets: number;
|
|
13
|
+
agent?: string;
|
|
14
|
+
model?: string;
|
|
15
|
+
unknownModel?: boolean;
|
|
16
|
+
sort?: "requests" | "tps" | "ttft" | "identity";
|
|
17
|
+
snapshotId?: string;
|
|
18
|
+
modelCursor?: string;
|
|
19
|
+
requestCursor?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface AnalyticsResult {
|
|
22
|
+
schemaVersion: 1;
|
|
23
|
+
generatedAt: number;
|
|
24
|
+
expiresAt: number;
|
|
25
|
+
snapshotId: string;
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
from: number;
|
|
28
|
+
to: number;
|
|
29
|
+
agent?: string;
|
|
30
|
+
models: ModelSummary[];
|
|
31
|
+
totalModels: number;
|
|
32
|
+
nextModelsCursor?: string;
|
|
33
|
+
selected?: ModelSummary;
|
|
34
|
+
buckets: ModelBucket[];
|
|
35
|
+
requests: CompletedAnalyticsRequest[];
|
|
36
|
+
nextRequestsCursor?: string;
|
|
37
|
+
coverage: ModelAnalyticsResult["coverage"];
|
|
38
|
+
metadata: HistoryMetadata;
|
|
39
|
+
gaps: number[];
|
|
40
|
+
omittedGaps: number;
|
|
41
|
+
}
|
|
42
|
+
/** Requests belong to half-open completion buckets; rates never average rates. */
|
|
43
|
+
export declare function bucketModelRequests(requests: readonly CompletedAnalyticsRequest[], from: number, to: number, count: number): ModelBucket[];
|
package/dist/types/rpc.d.ts
CHANGED
|
@@ -124,6 +124,34 @@ export declare const historyRpc: {
|
|
|
124
124
|
};
|
|
125
125
|
export declare function isHistoryRead(value: unknown): value is import("./historycharts.js").HistoryChartRead;
|
|
126
126
|
export declare function isHistoryResult(value: unknown): value is import("./historycharts.js").HistoryChartResult;
|
|
127
|
+
export declare const analyticsReadSchema: {
|
|
128
|
+
type: "object";
|
|
129
|
+
properties: Record<string, object>;
|
|
130
|
+
required: string[];
|
|
131
|
+
additionalProperties: boolean;
|
|
132
|
+
};
|
|
133
|
+
export declare const analyticsRpc: {
|
|
134
|
+
readonly id: "context-guard.analytics.v1";
|
|
135
|
+
readonly methods: {
|
|
136
|
+
readonly read: {
|
|
137
|
+
readonly input: {
|
|
138
|
+
type: "object";
|
|
139
|
+
properties: Record<string, object>;
|
|
140
|
+
required: string[];
|
|
141
|
+
additionalProperties: boolean;
|
|
142
|
+
};
|
|
143
|
+
readonly output: {
|
|
144
|
+
type: "object";
|
|
145
|
+
properties: Record<string, object>;
|
|
146
|
+
required: string[];
|
|
147
|
+
additionalProperties: boolean;
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
readonly events: {};
|
|
152
|
+
};
|
|
153
|
+
export declare function isAnalyticsRead(value: unknown): value is import("./modelseries.js").AnalyticsRead;
|
|
154
|
+
export declare function isAnalyticsResult(value: unknown): value is import("./modelseries.js").AnalyticsResult;
|
|
127
155
|
export declare function isRead(value: unknown): value is SnapshotRead;
|
|
128
156
|
export declare function isBatch(value: unknown): value is SnapshotBatch;
|
|
129
157
|
export declare function isResult(value: unknown): value is SnapshotReadResult;
|
|
@@ -42,5 +42,5 @@ export declare const chartMetrics: readonly [{
|
|
|
42
42
|
export declare function metricPlot(points: readonly {
|
|
43
43
|
time: number;
|
|
44
44
|
value?: number;
|
|
45
|
-
}[], from: number, to: number, width: number, gaps?: readonly number[]): string[];
|
|
45
|
+
}[], from: number, to: number, width: number, gaps?: readonly number[], halfOpen?: boolean): string[];
|
|
46
46
|
export declare function historyChartLines(result: HistoryChartResult, width: number, from: number, to: number, metricIndex?: number): string[];
|