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/line.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type LineFields, type
|
|
1
|
+
import { type LineFields, type LineLabels, type LineTone, type Totals, type TranscriptMessage, type TurnStats, type TurnTotalsView } from "./core";
|
|
2
2
|
/**
|
|
3
3
|
* Adapter between the host's session messages and the pure turn accounting in
|
|
4
4
|
* `core`, plus the plain-text parts the TUI line is made of.
|
|
@@ -66,7 +66,6 @@ export declare function lineParts(stats: TurnStats | undefined, options?: {
|
|
|
66
66
|
labels?: Partial<LineLabels>;
|
|
67
67
|
fields?: Partial<LineFields>;
|
|
68
68
|
turnTotals?: TurnTotalsView;
|
|
69
|
-
identity?: LineIdentity;
|
|
70
69
|
}): LinePart[];
|
|
71
70
|
/** Plain text of the same line, useful for tests and logs. */
|
|
72
71
|
export declare function lineText(parts: readonly LinePart[]): string;
|
package/dist/panel-build.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite";
|
|
2
|
-
import { type CacheStats, type PairRow } from "./stats/aggregates";
|
|
2
|
+
import { type CacheStats, type PairRow, type SwitchRow } from "./stats/aggregates";
|
|
3
3
|
import { type Filters } from "./stats/queries";
|
|
4
4
|
import type { DbInfo } from "./stats/maintenance";
|
|
5
5
|
import { type Granularity, type LineFormatOptions, type PanelLine, type PanelRow, type PanelView, type RangeKey } from "./panel-data";
|
|
@@ -42,3 +42,5 @@ export declare function detailLabel(row: PanelRow): string;
|
|
|
42
42
|
export declare function buildPanel(request: PanelRequest): PanelLine[];
|
|
43
43
|
/** Token reuse per model, for the models view and the cache view. */
|
|
44
44
|
export declare function cacheByModel(pairs: readonly PairRow[]): Map<string, CacheStats>;
|
|
45
|
+
/** The switch summary per `modelKey`, as the models view reads it. */
|
|
46
|
+
export declare function switchesByModel(rows: readonly SwitchRow[]): Map<string, SwitchRow>;
|
package/dist/panel-data.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type LineFields, type
|
|
1
|
+
import { type LineFields, type LineLabels, type TurnStats, type TurnTotalsView } from "./core";
|
|
2
2
|
import type { CacheDropRow, LatencyReport, ModelUsage, Overview, Range, SessionUsage, TurnUsage, TtlEvidenceRow } from "./stats/queries";
|
|
3
3
|
import type { DbInfo } from "./stats/maintenance";
|
|
4
|
-
import type {
|
|
4
|
+
import type { CacheStats, CoverageReport, ProviderRow, ProviderSummary, GapReport, LatencyGroupRow, PeriodGranularity, PeriodRow, SwitchRow } from "./stats/aggregates";
|
|
5
5
|
/**
|
|
6
6
|
* View models and formatting for the stats panel. Everything here is pure:
|
|
7
7
|
* the panel component feeds query results in and renders the lines out, which
|
|
@@ -9,7 +9,7 @@ import type { AffinityReport, CacheStats, CoverageReport, EndpointRow, EndpointS
|
|
|
9
9
|
* identity the cursor selects and Enter drills into.
|
|
10
10
|
*/
|
|
11
11
|
export declare const PANEL_NAME = "usage-insights";
|
|
12
|
-
export declare const PANEL_VIEWS: readonly ["overview", "models", "
|
|
12
|
+
export declare const PANEL_VIEWS: readonly ["overview", "models", "providers", "cache", "periods", "latency", "sessions", "turns", "db"];
|
|
13
13
|
export type PanelView = (typeof PANEL_VIEWS)[number];
|
|
14
14
|
export declare const RANGE_KEYS: readonly ["session", "today", "7d", "30d", "all"];
|
|
15
15
|
export type RangeKey = (typeof RANGE_KEYS)[number];
|
|
@@ -18,7 +18,7 @@ export type Granularity = (typeof GRANULARITIES)[number];
|
|
|
18
18
|
export type PanelTone = "base" | "muted" | "warning" | "accent";
|
|
19
19
|
/** What a selectable line stands for; Enter drills into it. */
|
|
20
20
|
export type PanelRow = {
|
|
21
|
-
kind: "model" | "session" | "period" | "
|
|
21
|
+
kind: "model" | "session" | "period" | "provider" | "turn" | "prune";
|
|
22
22
|
value: string;
|
|
23
23
|
variant?: string | undefined;
|
|
24
24
|
};
|
|
@@ -50,8 +50,8 @@ export type PanelSettings = {
|
|
|
50
50
|
turnsScope: TurnsScope;
|
|
51
51
|
/** Empty string means "no filter". */
|
|
52
52
|
modelID: string;
|
|
53
|
-
/** The
|
|
54
|
-
|
|
53
|
+
/** The provider the model ran through. */
|
|
54
|
+
providerID: string;
|
|
55
55
|
};
|
|
56
56
|
export declare const DEFAULT_SETTINGS: PanelSettings;
|
|
57
57
|
/** Merge persisted state with the defaults, dropping anything unrecognized. */
|
|
@@ -122,7 +122,7 @@ export declare function viewBar(view: PanelView, width?: number): PanelLine;
|
|
|
122
122
|
export declare function keysFor(of: PanelView): string;
|
|
123
123
|
export declare function filterSummary(filters: {
|
|
124
124
|
modelID?: string | undefined;
|
|
125
|
-
|
|
125
|
+
providerID?: string | undefined;
|
|
126
126
|
}): string | undefined;
|
|
127
127
|
export declare function granularityLabel(granularity: PeriodGranularity): string;
|
|
128
128
|
/** Help overlay: the keys and the exact meaning of every number. */
|
|
@@ -139,20 +139,19 @@ export declare function overviewLines(data: {
|
|
|
139
139
|
export type ModelExtras = {
|
|
140
140
|
/** Reuse stats per `modelKey`, computed from the step facts. */
|
|
141
141
|
cache?: Map<string, CacheStats> | undefined;
|
|
142
|
-
/**
|
|
143
|
-
|
|
142
|
+
/** Switches per `modelKey`, computed from the consecutive step pairs. */
|
|
143
|
+
switches?: Map<string, SwitchRow> | undefined;
|
|
144
144
|
};
|
|
145
145
|
/**
|
|
146
|
-
* Usage per model with the cache it actually got,
|
|
147
|
-
* how often its identity changed. One line per model; `providers` keeps the
|
|
146
|
+
* Usage per model with the cache it actually got, the providers it ran through
|
|
147
|
+
* and how often its identity changed. One line per model; `providers` keeps the
|
|
148
148
|
* per-provider detail.
|
|
149
149
|
*/
|
|
150
150
|
export declare function modelsLines(rows: readonly ModelUsage[], width: number, extras?: ModelExtras, limit?: number): PanelLine[];
|
|
151
|
-
/** One row per
|
|
152
|
-
export declare function
|
|
153
|
-
/** One row per model ×
|
|
154
|
-
export declare function
|
|
155
|
-
export declare function affinityLines(report: AffinityReport, width: number, limit?: number): PanelLine[];
|
|
151
|
+
/** One row per provider, over every model it served. */
|
|
152
|
+
export declare function providerSummaryLines(rows: readonly ProviderSummary[], width: number, limit?: number): PanelLine[];
|
|
153
|
+
/** One row per model × provider: the providers each model ran through. */
|
|
154
|
+
export declare function providerLines(rows: readonly ProviderRow[], width: number, limit?: number): PanelLine[];
|
|
156
155
|
/** One model's reuse row, shared by the models and cache views. */
|
|
157
156
|
export type CacheModelRow = {
|
|
158
157
|
usage: {
|
|
@@ -186,7 +185,6 @@ export type LineFormatOptions = {
|
|
|
186
185
|
labels?: Partial<LineLabels> | undefined;
|
|
187
186
|
fields?: Partial<LineFields> | undefined;
|
|
188
187
|
turnTotals?: TurnTotalsView | undefined;
|
|
189
|
-
identity?: LineIdentity | undefined;
|
|
190
188
|
};
|
|
191
189
|
/** The line's own view of a recorded turn, so both render identically. */
|
|
192
190
|
export declare function turnStats(turn: TurnUsage): TurnStats;
|
|
@@ -204,8 +202,14 @@ export declare function turnsLines(rows: readonly TurnUsage[], width: number, op
|
|
|
204
202
|
*/
|
|
205
203
|
export declare function sessionSummaryLines(usage: Overview, budget?: number): PanelLine[];
|
|
206
204
|
/**
|
|
207
|
-
* The sidebar history:
|
|
208
|
-
*
|
|
205
|
+
* The sidebar history: a compact block per turn — when and through which
|
|
206
|
+
* provider, then the prompt volumes, then the cache hit, the rate and the cost.
|
|
207
|
+
*
|
|
208
|
+
* The model gets a line of its own at both ends of the block and on every turn
|
|
209
|
+
* that changed it. The ends matter because the reader cannot tell whether a
|
|
210
|
+
* block is the whole history or a window of a long session: the newest turn
|
|
211
|
+
* names the model in use now, the oldest names the one the window started with,
|
|
212
|
+
* and everything between them is read against the nearest line below it.
|
|
209
213
|
*/
|
|
210
214
|
export declare function sidebarLines(rows: readonly TurnUsage[], options?: LineFormatOptions & {
|
|
211
215
|
budget?: number | undefined;
|
package/dist/recorder.d.ts
CHANGED
|
@@ -5,8 +5,10 @@ import type { Database } from "bun:sqlite";
|
|
|
5
5
|
* unexpected shape, so unknown events are ignored and malformed payloads are
|
|
6
6
|
* dropped rather than thrown.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
* Everything written comes from the event stream: the step's provider, model
|
|
9
|
+
* and variant, its tokens, its timing and its outcome. Pure with respect to
|
|
10
|
+
* its inputs (database plus events) so the whole recording policy can be
|
|
11
|
+
* unit-tested without the plugin runtime.
|
|
10
12
|
*/
|
|
11
13
|
export type SessionModelRef = {
|
|
12
14
|
id: string;
|
|
@@ -48,19 +50,6 @@ export type RecorderEvent = {
|
|
|
48
50
|
created?: number | undefined;
|
|
49
51
|
data?: RecorderEventData | undefined;
|
|
50
52
|
};
|
|
51
|
-
export type HttpResponseInput = {
|
|
52
|
-
sessionID?: string | undefined;
|
|
53
|
-
kind?: string | undefined;
|
|
54
|
-
model?: SessionModelRef | undefined;
|
|
55
|
-
request?: {
|
|
56
|
-
method?: string | undefined;
|
|
57
|
-
url?: string | undefined;
|
|
58
|
-
} | undefined;
|
|
59
|
-
response?: {
|
|
60
|
-
status?: number | undefined;
|
|
61
|
-
headers?: Headers | Record<string, string | undefined> | undefined;
|
|
62
|
-
} | undefined;
|
|
63
|
-
};
|
|
64
53
|
export type PendingStep = {
|
|
65
54
|
messageID: string;
|
|
66
55
|
sessionID: string;
|
|
@@ -75,8 +64,6 @@ export type RecorderOptions = {
|
|
|
75
64
|
export type Recorder = {
|
|
76
65
|
/** Apply one session event. Unknown or malformed events are ignored. */
|
|
77
66
|
handleEvent(event: RecorderEvent): void;
|
|
78
|
-
/** Learn the routing endpoint from one model response. */
|
|
79
|
-
handleHttpResponse(event: HttpResponseInput): void;
|
|
80
67
|
pendingFor(sessionID: string): PendingStep | undefined;
|
|
81
68
|
};
|
|
82
69
|
export declare function createRecorder(db: Database, options?: RecorderOptions): Recorder;
|
package/dist/sidebar.d.ts
CHANGED
|
@@ -4,9 +4,9 @@ import type { Context } from "@opencode/plugin/tui/context";
|
|
|
4
4
|
import { type LineFormatOptions } from "./panel-data";
|
|
5
5
|
/**
|
|
6
6
|
* Sidebar history: the session's accumulated usage, then the last finished
|
|
7
|
-
* turns
|
|
8
|
-
* Reads the same database, the same buckets and the same
|
|
9
|
-
* and the live line, and refreshes whenever a turn ends.
|
|
7
|
+
* turns — when and through which provider, the model when it changed, and the
|
|
8
|
+
* prompt volumes. Reads the same database, the same buckets and the same
|
|
9
|
+
* formatter as the panel and the live line, and refreshes whenever a turn ends.
|
|
10
10
|
*/
|
|
11
11
|
export type TurnsSidebarDeps = {
|
|
12
12
|
db: () => Database | undefined;
|
|
@@ -33,7 +33,6 @@ export type StepRow = {
|
|
|
33
33
|
providerID: string | undefined;
|
|
34
34
|
modelID: string | undefined;
|
|
35
35
|
variant: string | undefined;
|
|
36
|
-
router: string | undefined;
|
|
37
36
|
input: number;
|
|
38
37
|
cached: number;
|
|
39
38
|
cacheWrite: number;
|
|
@@ -69,18 +68,18 @@ export declare function cachePairs(db: Database, options?: Options): PairRow[];
|
|
|
69
68
|
export type CoverageReport = {
|
|
70
69
|
steps: number;
|
|
71
70
|
models: number;
|
|
72
|
-
|
|
71
|
+
/** Providers the steps ran through. A step whose provider is unknown is counted nowhere. */
|
|
72
|
+
providers: number;
|
|
73
73
|
missingModel: number;
|
|
74
|
-
missingEndpoint: number;
|
|
75
74
|
unsettled: number;
|
|
76
75
|
missingTps: number;
|
|
77
76
|
firstAt: number | undefined;
|
|
78
77
|
lastAt: number | undefined;
|
|
79
78
|
};
|
|
80
79
|
/**
|
|
81
|
-
* What the panel cannot answer honestly: how many steps lack a model
|
|
82
|
-
*
|
|
83
|
-
*
|
|
80
|
+
* What the panel cannot answer honestly: how many steps lack a model, and how
|
|
81
|
+
* many never settled. A zero in the switch counts means "never switched" only
|
|
82
|
+
* when this coverage is complete.
|
|
84
83
|
*/
|
|
85
84
|
export declare function coverageOf(db: Database, options?: Options): CoverageReport;
|
|
86
85
|
/** The steps a latency report is built from. It reads no generated tokens. */
|
|
@@ -103,7 +102,7 @@ export type LatencyGroupRow = {
|
|
|
103
102
|
stream: LatencyStats;
|
|
104
103
|
tps: LatencyStats;
|
|
105
104
|
};
|
|
106
|
-
export type Dimension = "model" | "provider"
|
|
105
|
+
export type Dimension = "model" | "provider";
|
|
107
106
|
/** Wall, streaming window and throughput percentiles per group. */
|
|
108
107
|
export declare function latencyGroupsOf(rows: readonly StepRow[], by: Dimension): LatencyGroupRow[];
|
|
109
108
|
export type CacheStats = {
|
|
@@ -130,26 +129,23 @@ export type CacheModelRow = {
|
|
|
130
129
|
* range. A boundary step is not part of the range, so it is left out.
|
|
131
130
|
*/
|
|
132
131
|
export declare function cacheStatsByModel(rows: readonly PairRow[]): CacheModelRow[];
|
|
133
|
-
export type
|
|
132
|
+
export type SwitchRow = {
|
|
134
133
|
modelID: string;
|
|
135
134
|
variant: string | undefined;
|
|
136
135
|
steps: number;
|
|
137
136
|
pairs: number;
|
|
137
|
+
/** Steps whose model, variant or provider differed from the step before. */
|
|
138
138
|
changes: number;
|
|
139
|
+
/** Of those, the ones whose model (or variant) differed. */
|
|
139
140
|
modelChanges: number;
|
|
140
|
-
|
|
141
|
+
/** Of those, the ones whose provider differed. */
|
|
142
|
+
providerChanges: number;
|
|
143
|
+
/** Cache the changes cost: only a provider switch can forfeit a reusable cache. */
|
|
141
144
|
cacheLost: number;
|
|
142
145
|
cacheLostEvents: number;
|
|
143
146
|
};
|
|
144
|
-
export type
|
|
145
|
-
|
|
146
|
-
from: string;
|
|
147
|
-
to: string;
|
|
148
|
-
count: number;
|
|
149
|
-
};
|
|
150
|
-
export type AffinityReport = {
|
|
151
|
-
rows: AffinityRow[];
|
|
152
|
-
transitions: AffinityTransition[];
|
|
147
|
+
export type SwitchReport = {
|
|
148
|
+
rows: SwitchRow[];
|
|
153
149
|
totals: {
|
|
154
150
|
pairs: number;
|
|
155
151
|
changes: number;
|
|
@@ -158,12 +154,17 @@ export type AffinityReport = {
|
|
|
158
154
|
};
|
|
159
155
|
};
|
|
160
156
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
157
|
+
* Switches between consecutive steps of a session, by the two dimensions that
|
|
158
|
+
* decide which cache holds the work: the model (with its variant) and the
|
|
159
|
+
* provider.
|
|
160
|
+
*
|
|
161
|
+
* A switch is not by itself a fault — it is usually the user choosing another
|
|
162
|
+
* model — so the report only names a cost where one is measurable: a provider
|
|
163
|
+
* switch that leaves the model intact forfeits the cache the previous provider
|
|
164
|
+
* held. A model switch has no measurable cost by definition, because two models
|
|
165
|
+
* never share a cache to lose.
|
|
165
166
|
*/
|
|
166
|
-
export declare function
|
|
167
|
+
export declare function switchesOf(rows: readonly PairRow[]): SwitchReport;
|
|
167
168
|
export type GapBucket = {
|
|
168
169
|
label: string;
|
|
169
170
|
samples: number;
|
|
@@ -173,9 +174,9 @@ export type GapBucket = {
|
|
|
173
174
|
lostTokens: number;
|
|
174
175
|
retainedRatio: number | undefined;
|
|
175
176
|
};
|
|
176
|
-
export type
|
|
177
|
-
/** The
|
|
178
|
-
|
|
177
|
+
export type ProviderReuseRow = {
|
|
178
|
+
/** The provider, or `(unknown)` when a step ran without one. */
|
|
179
|
+
provider: string;
|
|
179
180
|
samples: number;
|
|
180
181
|
retained: number;
|
|
181
182
|
drops: number;
|
|
@@ -184,14 +185,14 @@ export type EndpointReuseRow = {
|
|
|
184
185
|
};
|
|
185
186
|
export type GapReport = {
|
|
186
187
|
buckets: GapBucket[];
|
|
187
|
-
|
|
188
|
+
providers: ProviderReuseRow[];
|
|
188
189
|
samples: number;
|
|
189
190
|
};
|
|
190
191
|
/** Idle-gap buckets, coarse enough to read at a glance. */
|
|
191
192
|
export declare const GAP_BUCKETS: ReadonlyArray<readonly [string, number, number | undefined]>;
|
|
192
193
|
/**
|
|
193
194
|
* How long an idle gap the prompt cache survives, per gap bucket and per
|
|
194
|
-
*
|
|
195
|
+
* provider. Only pairs of the same model are comparable, which is exactly the
|
|
195
196
|
* set the cache rule already uses.
|
|
196
197
|
*/
|
|
197
198
|
export declare function gapReportOf(rows: readonly PairRow[]): GapReport;
|
|
@@ -233,10 +234,10 @@ export type CacheDropRow = {
|
|
|
233
234
|
export declare function cacheDropsOf(db: Database, rows: readonly PairRow[], options?: {
|
|
234
235
|
ttlHintMs?: number;
|
|
235
236
|
}): CacheDropRow[];
|
|
236
|
-
export type
|
|
237
|
-
/** The
|
|
238
|
-
|
|
239
|
-
/** Models this
|
|
237
|
+
export type ProviderSummary = {
|
|
238
|
+
/** The provider, or undefined for steps recorded without one. */
|
|
239
|
+
provider: string | undefined;
|
|
240
|
+
/** Models this provider served. */
|
|
240
241
|
models: string[];
|
|
241
242
|
share: number;
|
|
242
243
|
usage: Usage;
|
|
@@ -246,14 +247,14 @@ export type EndpointSummary = {
|
|
|
246
247
|
firstAt: number | undefined;
|
|
247
248
|
lastAt: number | undefined;
|
|
248
249
|
};
|
|
249
|
-
export type
|
|
250
|
+
export type ProviderRow = ProviderSummary & {
|
|
250
251
|
modelID: string;
|
|
251
252
|
variant: string | undefined;
|
|
252
253
|
};
|
|
253
|
-
/** One row per
|
|
254
|
-
export declare function
|
|
255
|
-
/** One row per model ×
|
|
256
|
-
export declare function
|
|
254
|
+
/** One row per provider, over every model it served. */
|
|
255
|
+
export declare function providerSummaryOf(rows: readonly StepRow[]): ProviderSummary[];
|
|
256
|
+
/** One row per model × provider: through which provider each model ran, and how much. */
|
|
257
|
+
export declare function providerBreakdownOf(rows: readonly StepRow[]): ProviderRow[];
|
|
257
258
|
export type PeriodGranularity = "day" | "week" | "month";
|
|
258
259
|
export type PeriodRow = {
|
|
259
260
|
key: string;
|
package/dist/stats/db.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
|
-
import type { EndpointInfo } from "../endpoint";
|
|
3
2
|
/**
|
|
4
3
|
* Persistent usage store. One SQLite database holds every recorded step,
|
|
5
4
|
* prompt, event and session row — and nothing else: the recorder only writes
|
|
@@ -15,7 +14,7 @@ import type { EndpointInfo } from "../endpoint";
|
|
|
15
14
|
* - `openStatsDbWrite` is a short-lived maintenance connection (prune, vacuum)
|
|
16
15
|
* that gives up quickly instead of holding the recorder up.
|
|
17
16
|
*/
|
|
18
|
-
export declare const SCHEMA_VERSION =
|
|
17
|
+
export declare const SCHEMA_VERSION = 9;
|
|
19
18
|
/** Tables carried by `exportNdjson` / `importNdjson`, in import-safe order. */
|
|
20
19
|
export declare const TABLES: readonly ["sessions", "prompts", "events", "steps"];
|
|
21
20
|
export type TableName = (typeof TABLES)[number];
|
|
@@ -23,6 +22,7 @@ export type StepRecord = {
|
|
|
23
22
|
messageID: string;
|
|
24
23
|
sessionID: string;
|
|
25
24
|
parentID?: string | undefined;
|
|
25
|
+
/** The provider the model ran through, as OpenCode configured it (`opencode-go`). */
|
|
26
26
|
providerID?: string | undefined;
|
|
27
27
|
modelID?: string | undefined;
|
|
28
28
|
variant?: string | undefined;
|
|
@@ -36,8 +36,6 @@ export type StepRecord = {
|
|
|
36
36
|
streamedAt?: number | undefined;
|
|
37
37
|
completedAt?: number | undefined;
|
|
38
38
|
finish?: string | undefined;
|
|
39
|
-
/** Gateway routing endpoint that served the request (`orcarouter-dsv4.1flash`). */
|
|
40
|
-
router?: string | undefined;
|
|
41
39
|
/** Clock of the recording itself, defaults to `Date.now()`. */
|
|
42
40
|
recordedAt?: number | undefined;
|
|
43
41
|
};
|
|
@@ -77,25 +75,18 @@ export declare function openStatsDbReadOnly(path: string): Database;
|
|
|
77
75
|
export declare function openStatsDbWrite(path: string): Database;
|
|
78
76
|
/**
|
|
79
77
|
* Insert or refresh an assistant step. Idempotent by message ID so replayed
|
|
80
|
-
* events, restarts and NDJSON imports never duplicate usage.
|
|
81
|
-
*
|
|
78
|
+
* events, restarts and NDJSON imports never duplicate usage. A non-null field
|
|
79
|
+
* (the provider, the model, the finish reason) survives a re-record with `NULL`
|
|
82
80
|
* through COALESCE.
|
|
81
|
+
*
|
|
82
|
+
* The provider is the identity every view groups by, so it is validated as a
|
|
83
|
+
* token before it is stored: a value that is not a provider id is recorded as
|
|
84
|
+
* absent instead of becoming a grouping key.
|
|
83
85
|
*/
|
|
84
86
|
export declare function recordStep(db: Database, record: StepRecord): void;
|
|
85
87
|
export declare function recordPrompt(db: Database, record: PromptRecord): void;
|
|
86
88
|
export declare function recordEvent(db: Database, record: EventRecord): void;
|
|
87
89
|
export declare function upsertSession(db: Database, record: SessionRecord): void;
|
|
88
|
-
/**
|
|
89
|
-
* Attach the endpoint discovered after the step was recorded. A step that
|
|
90
|
-
* already carries the same endpoint is left alone: the recorder runs on the
|
|
91
|
-
* request path, and a no-op write there is a cost with no reader.
|
|
92
|
-
*/
|
|
93
|
-
export declare function setStepRouter(db: Database, messageID: string, info: EndpointInfo): void;
|
|
94
|
-
/**
|
|
95
|
-
* Apply the endpoint learned from a compaction to every step of that session
|
|
96
|
-
* and model that still lacks one. Returns the number of rows touched.
|
|
97
|
-
*/
|
|
98
|
-
export declare function setStepRouterForSessionModel(db: Database, sessionID: string, modelID: string | undefined, info: EndpointInfo): number;
|
|
99
90
|
/** Dump every table as NDJSON (`{"table": "...", "row": {...}}` per line). */
|
|
100
91
|
export declare function exportNdjson(db: Database): string;
|
|
101
92
|
/**
|
package/dist/stats/queries.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type Range = {
|
|
|
18
18
|
to?: number | undefined;
|
|
19
19
|
};
|
|
20
20
|
/**
|
|
21
|
-
* Panel filters. They narrow every aggregation to one model, one
|
|
21
|
+
* Panel filters. They narrow every aggregation to one model, one provider or
|
|
22
22
|
* one session, and are applied with the same `WHERE` builder the range uses so
|
|
23
23
|
* a filtered view and a drill-down agree.
|
|
24
24
|
*/
|
|
@@ -32,11 +32,12 @@ export type Filters = {
|
|
|
32
32
|
*/
|
|
33
33
|
modelKey?: string | undefined;
|
|
34
34
|
/**
|
|
35
|
-
* The
|
|
36
|
-
*
|
|
37
|
-
*
|
|
35
|
+
* The provider the model ran through, as OpenCode configured it
|
|
36
|
+
* (`opencode-go`, `deepseek`, `deepinfra`). It is the identity every view
|
|
37
|
+
* groups by: a gateway may re-route the request, but the upstream that
|
|
38
|
+
* actually ran it is not observable.
|
|
38
39
|
*/
|
|
39
|
-
|
|
40
|
+
provider?: string | undefined;
|
|
40
41
|
/**
|
|
41
42
|
* One session, exactly. Used by the `session` range and by the overview's
|
|
42
43
|
* "this session" block, which must not pick up the steps of every other
|
|
@@ -93,8 +94,6 @@ export type Overview = Usage & {
|
|
|
93
94
|
export declare function overview(db: Database, range?: Range, filters?: Filters): Overview;
|
|
94
95
|
export type ProviderUsage = Usage & {
|
|
95
96
|
providerID: string;
|
|
96
|
-
/** Gateway routing endpoints that served the requests. */
|
|
97
|
-
routers: string[];
|
|
98
97
|
firstAt: number | undefined;
|
|
99
98
|
lastAt: number | undefined;
|
|
100
99
|
};
|
|
@@ -106,8 +105,8 @@ export type ModelUsage = {
|
|
|
106
105
|
};
|
|
107
106
|
/**
|
|
108
107
|
* Usage per model, each with the providers that served it. This is the view
|
|
109
|
-
* that answers "which
|
|
110
|
-
*
|
|
108
|
+
* that answers "through which provider is this model actually coming, over the
|
|
109
|
+
* selected period".
|
|
111
110
|
*/
|
|
112
111
|
export declare function models(db: Database, range?: Range, filters?: Filters): ModelUsage[];
|
|
113
112
|
export type TimelineBucket = {
|
|
@@ -196,9 +195,6 @@ export type TurnUsage = {
|
|
|
196
195
|
providerID: string | undefined;
|
|
197
196
|
modelID: string | undefined;
|
|
198
197
|
variant: string | undefined;
|
|
199
|
-
router: string | undefined;
|
|
200
|
-
/** The displayed identity differs from the previous turn of the session. */
|
|
201
|
-
routerChanged: boolean;
|
|
202
198
|
providerChanged: boolean;
|
|
203
199
|
modelChanged: boolean;
|
|
204
200
|
steps: number;
|
|
@@ -232,28 +228,12 @@ export declare function turns(db: Database, options?: {
|
|
|
232
228
|
filters?: Filters | undefined;
|
|
233
229
|
limit?: number | undefined;
|
|
234
230
|
}): TurnUsage[];
|
|
235
|
-
/**
|
|
236
|
-
* Routing endpoint of the newest step recorded before `at`. The TUI compares
|
|
237
|
-
* it with the endpoint of the turn it is about to render, because the same
|
|
238
|
-
* provider and model can be routed to a different endpoint per request.
|
|
239
|
-
*/
|
|
240
|
-
export declare function routerBefore(db: Database, sessionID: string, at: number | undefined): string | undefined;
|
|
241
|
-
/**
|
|
242
|
-
* Routing endpoint of one turn: the newest endpoint the recorder stored for
|
|
243
|
-
* the given steps of a session. The TUI reads this for the telemetry line
|
|
244
|
-
* because the line renders from the TUI process while the recorder (the server
|
|
245
|
-
* process) is still writing the step that just finished. Scoped to the turn's
|
|
246
|
-
* own steps on purpose: the same provider and model can be routed to a
|
|
247
|
-
* different endpoint per request, so the newest endpoint of the session is not
|
|
248
|
-
* necessarily the one that served the turn being rendered.
|
|
249
|
-
*/
|
|
250
|
-
export declare function routerForSteps(db: Database, sessionID: string, messageIDs: readonly string[]): string | undefined;
|
|
251
231
|
export type FilterOption = {
|
|
252
232
|
value: string;
|
|
253
233
|
steps: number;
|
|
254
234
|
};
|
|
255
|
-
/** Distinct models and
|
|
235
|
+
/** Distinct models and providers, most used first. */
|
|
256
236
|
export declare function filterOptions(db: Database): {
|
|
257
237
|
models: FilterOption[];
|
|
258
|
-
|
|
238
|
+
providers: FilterOption[];
|
|
259
239
|
};
|
package/dist/text.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Text hygiene for everything that reaches a terminal.
|
|
3
3
|
*
|
|
4
|
-
* Some of what the plugin displays is not written by the user: a
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* is stored or drawn.
|
|
4
|
+
* Some of what the plugin displays is not written by the user: a model writes
|
|
5
|
+
* a session title, an NDJSON export can come from anywhere. A control
|
|
6
|
+
* character in any of those is interpreted by the terminal that renders it
|
|
7
|
+
* (title changes, cursor moves, colours), so every such string is stripped of
|
|
8
|
+
* control characters before it is stored or drawn.
|
|
10
9
|
*
|
|
11
10
|
* The rule is deliberately blunt: drop C0 controls, DEL and the C1 block, keep
|
|
12
11
|
* everything else (the text is otherwise arbitrary Unicode), and cap the
|
|
@@ -24,5 +23,3 @@ export declare function sanitizeText(value: string, limit?: number): string;
|
|
|
24
23
|
/** `sanitizeText` without the layout, for a stored label: trimmed as well. */
|
|
25
24
|
export declare function sanitizeLabel(value: string, limit?: number): string;
|
|
26
25
|
export declare function isToken(value: string, limit?: number): boolean;
|
|
27
|
-
/** The lowercase form of a token identity, so the same endpoint groups once. */
|
|
28
|
-
export declare function normalizeToken(value: string, limit?: number): string | undefined;
|
package/dist/tui.d.ts
CHANGED
|
@@ -1,31 +1,26 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
2
|
import { Plugin } from "@opencode/plugin/tui";
|
|
3
|
-
import { type LineFields, type
|
|
3
|
+
import { type LineFields, type LineLabels, type TurnStats, type TurnTotalsView } from "./core";
|
|
4
4
|
import { type PanelView, type RangeKey } from "./panel-data";
|
|
5
5
|
/**
|
|
6
6
|
* TUI side of the plugin. Two contributions:
|
|
7
7
|
*
|
|
8
8
|
* - `session.composer.top`: one telemetry line after every turn with the end
|
|
9
|
-
* time, the delta since the previous prompt, the
|
|
10
|
-
* turn and session token totals, and cache warnings.
|
|
9
|
+
* time, the delta since the previous prompt, the provider and model that ran
|
|
10
|
+
* it, turn and session token totals, and cache warnings.
|
|
11
11
|
* - `session.panel` + the `/stats` slash command: the statistics panel over
|
|
12
12
|
* the database written by the server entry.
|
|
13
13
|
*
|
|
14
|
-
* The
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* The line is computed from the host's transcript alone; the database is opened
|
|
15
|
+
* lazily for the panel and the sidebar, read-only in spirit: the TUI never
|
|
16
|
+
* writes usage. When the database cannot be opened, the line still works and
|
|
17
|
+
* the panel explains itself.
|
|
17
18
|
*/
|
|
18
19
|
export type Options = {
|
|
19
20
|
/** Turn the TUI side off: no line and no panel. */
|
|
20
21
|
enabled?: boolean | undefined;
|
|
21
22
|
/** SQLite database path override; must match the server entry. */
|
|
22
23
|
db?: string | undefined;
|
|
23
|
-
/**
|
|
24
|
-
* Which identity the line leads with: `endpoint` (default) shows the gateway
|
|
25
|
-
* endpoint that served the turn (`orcarouter-dsv4.1flash`) and warns when it
|
|
26
|
-
* changes; `model` shows `provider/model#variant` as before.
|
|
27
|
-
*/
|
|
28
|
-
identity?: LineIdentity | undefined;
|
|
29
24
|
/**
|
|
30
25
|
* How the turn segment counts prompt usage: `latest` (default) shows the
|
|
31
26
|
* prompt size the turn ended with, `sum` adds every step of the turn.
|