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/dist/line.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type LineFields, type LineIdentity, type LineLabels, type LineTone, type Totals, type TranscriptMessage, type TurnStats, type TurnTotalsView } from "./core";
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;
@@ -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>;
@@ -1,7 +1,7 @@
1
- import { type LineFields, type LineIdentity, type LineLabels, type TurnStats, type TurnTotalsView } from "./core";
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 { AffinityReport, CacheStats, CoverageReport, EndpointRow, EndpointSummary, GapReport, LatencyGroupRow, PeriodGranularity, PeriodRow } from "./stats/aggregates";
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", "endpoints", "affinity", "cache", "periods", "latency", "sessions", "turns", "db"];
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" | "endpoint" | "provider" | "turn" | "prune";
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 routing endpoint that served the request. */
54
- routerID: string;
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
- routerID?: string | undefined;
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
- /** Identity changes per `modelKey`, from the affinity report. */
143
- changes?: Map<string, number> | undefined;
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, where it was served from and
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 endpoint, over every model it served. */
152
- export declare function endpointSummaryLines(rows: readonly EndpointSummary[], width: number, limit?: number): PanelLine[];
153
- /** One row per model × endpoint: the endpoints that served each model. */
154
- export declare function endpointsLines(rows: readonly EndpointRow[], width: number, limit?: number): PanelLine[];
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: two compact lines per turn — when and through which
208
- * endpoint, then the prompt volumes — truncated to the sidebar budget.
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;
@@ -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
- * Pure with respect to its inputs (database plus events) so the whole
9
- * recording policy can be unit-tested without the plugin runtime.
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, two compact lines each (when + endpoint, then the prompt volumes).
8
- * Reads the same database, the same buckets and the same formatter as the panel
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
- endpoints: number;
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 or an
82
- * observed endpoint, and how many never settled. A zero in the affinity view
83
- * means "never changed" only when this coverage is complete.
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" | "endpoint";
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 AffinityRow = {
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
- endpointChanges: number;
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 AffinityTransition = {
145
- dimension: "model" | "endpoint";
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
- * Identity changes between consecutive steps of a session, by the two
162
- * dimensions that decide where the work ran: the model and the routing
163
- * endpoint. A change is not by itself a fault — a gateway may re-route
164
- * deliberately — so the report pairs the count with the cache it actually cost.
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 affinityOf(rows: readonly PairRow[]): AffinityReport;
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 EndpointReuseRow = {
177
- /** The routing endpoint, or `(unknown)` when it was never observed. */
178
- endpoint: string;
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
- endpoints: EndpointReuseRow[];
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
- * endpoint. Only pairs of the same model are comparable, which is exactly the
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 EndpointSummary = {
237
- /** The routing endpoint, or undefined for requests whose destination was never observed. */
238
- endpoint: string | undefined;
239
- /** Models this endpoint served. */
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 EndpointRow = EndpointSummary & {
250
+ export type ProviderRow = ProviderSummary & {
250
251
  modelID: string;
251
252
  variant: string | undefined;
252
253
  };
253
- /** One row per endpoint, over every model it served. */
254
- export declare function endpointSummaryOf(rows: readonly StepRow[]): EndpointSummary[];
255
- /** One row per model × endpoint: where each model was served, and how much. */
256
- export declare function endpointBreakdownOf(rows: readonly StepRow[]): EndpointRow[];
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;
@@ -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 = 8;
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. Non-null fields
81
- * discovered later (the routing endpoint) survive a re-record with `NULL`
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
  /**
@@ -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 endpoint or
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 routing endpoint that served the request (`router`). It is the one
36
- * identity that answers "who ran this inference": the provider you configured
37
- * says where the request came from, which is evidence, not an answer.
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
- router?: string | undefined;
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 inference providers is this model actually coming from
110
- * over the selected period".
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 endpoints, most used first. */
235
+ /** Distinct models and providers, most used first. */
256
236
  export declare function filterOptions(db: Database): {
257
237
  models: FilterOption[];
258
- endpoints: FilterOption[];
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 gateway names
5
- * its own endpoint in a response header, a model writes a session title, an
6
- * NDJSON export can come from anywhere. A control character in any of those is
7
- * interpreted by the terminal that renders it (title changes, cursor moves,
8
- * colours), so every such string is stripped of control characters before it
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 LineIdentity, type LineLabels, type TurnStats, type TurnTotalsView } from "./core";
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 endpoint that served it,
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 database is opened lazily and read-only in spirit: the TUI never
15
- * writes usage. When the database cannot be opened, the line falls back to
16
- * host data only and the panel explains itself.
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.