opencode-tokenwatch 0.5.0 → 0.6.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.en.md CHANGED
@@ -4,29 +4,33 @@
4
4
 
5
5
  ![Sidebar](./assets/sidebar.png)
6
6
 
7
- Real-time token usage, cache analytics & performance dashboard plugin for OpenCode CLI.
7
+ A terminal monitoring plugin for OpenCode CLI that displays real-time token usage, cache hit rate, and model throughput in the sidebar, with one-click interactive browser reports.
8
8
 
9
- ## Features
9
+ ---
10
10
 
11
- - **Sidebar panel** — Session-level and per-model real-time stats (requests, tokens, cache, cost)
12
- - **Cache hit rate** — Per-model tracking with trend indicators (↑/↓) and global weighted total
13
- - **Performance metrics** — TTFT / TPS / End-to-end latency + P50/P95/P99 latency percentiles
14
- - **Token distribution** — Per-role breakdown (system / user / tool / output)
15
- - **Cost tracking** — Per-model cost display (requires provider billing data)
16
- - **Error rate tracking** — Detects failed requests and computes real-time error rate
17
- - **`/usage` command** — HTML Report → JSON Export → Text Report → Settings
18
- - **HTML report** — Interactive ECharts dashboard: token trends, performance comparison, TPS ranking, error rate analysis — auto-opened in browser
19
- - **Persistent stats** — Performance metrics accumulate forever in a dedicated file, unaffected by session resets
20
- - **Multi-level collapse** — Panel, models, and sub-blocks collapsible with persisted state
21
- - **Language switching** — Auto-detect or manually switch between Chinese and English
11
+ ## Key Features
22
12
 
23
- ## Install
13
+ - **Real-time Sidebar Dashboard**: Check input/output tokens, estimated cost, cache hit rate, and average TTFT & generation speed per model during sessions.
14
+ - **Cache Hit Rate & Trends**: Track cache efficiency per model, with arrows showing whether the hit rate for recent requests is trending up or down.
15
+ - **Token Breakdown**: Automatically breaks down tokens into system prompt, user input, tool execution, and assistant output to identify major context consumers.
16
+ - **Interactive Usage Reports**: Type `/usage` in the terminal to launch an ECharts interactive dashboard in your browser with historical trends, cross-model comparisons, and failed request stats; export to JSON or Markdown with one click.
17
+ - **Dual-Host Compatibility**: Seamlessly works with both OpenCode 1.x and OpenCode 2 (beta). Automatically detects the host environment with zero manual configuration.
18
+
19
+ ---
20
+
21
+ ## Installation & Setup
22
+
23
+ ### 1. Install
24
+
25
+ Run in your project directory:
24
26
 
25
27
  ```sh
26
28
  npm install opencode-tokenwatch
27
29
  ```
28
30
 
29
- Add to `opencode.json` or `opencode.jsonc`:
31
+ ### 2. Enable Plugin
32
+
33
+ Add the plugin entry to `opencode.json` (or `opencode.jsonc`):
30
34
 
31
35
  ```json
32
36
  {
@@ -35,45 +39,35 @@ Add to `opencode.json` or `opencode.jsonc`:
35
39
  }
36
40
  ```
37
41
 
38
- ## Configuration
42
+ Restart OpenCode and the TokenWatch panel will load in the sidebar automatically.
39
43
 
40
- In OpenCode TUI, run `/usage` → **Settings** to interactively toggle display items and switch language. Settings are persisted automatically — no need to edit config files manually.
44
+ ---
41
45
 
42
46
  ## Usage
43
47
 
44
- In OpenCode TUI, run `/usage`:
48
+ Type `/usage` in the terminal input to open the action menu:
45
49
 
46
- - **HTML Report** Pick a date range, generates a dashboard and opens it in browser
47
- - **JSON Export** Exports full usage data to `~/.opencode/reports/`
48
- - **Text Report** Exports Markdown report to `~/.opencode/reports/`
49
- - **Settings** — Toggle sidebar blocks, switch language
50
+ - **HTML Report**: Select a date range to generate and open an interactive chart in your default browser.
51
+ - **Export Data**: Export structured JSON or Markdown reports, saved to `~/.opencode/reports/` by default.
52
+ - **Settings**: Interactively toggle sidebar sections or switch language in the popup menu. Changes are saved automatically without editing configuration files.
50
53
 
51
- ## Data Files
52
-
53
- | File | Path | Description |
54
- |------|------|-------------|
55
- | JSONL log | `~/.opencode/tokenwatch.jsonl` | Raw per-request log |
56
- | Aggregated stats | `~/.opencode/tokenwatch-stats.json` | Persistent performance stats |
57
- | Report output | `~/.opencode/reports/` | HTML / JSON / Markdown reports |
54
+ ---
58
55
 
59
56
  ## Requirements
60
57
 
61
- - OpenCode CLI (with `opencode db` command)
62
- - Node.js 18+
58
+ - **OpenCode CLI**: 1.18+ or OpenCode 2 (`@opencode-ai/cli@beta`)
59
+ - **Node.js**: 18.0.0
63
60
 
64
- ## Build
61
+ ---
65
62
 
66
- ```sh
67
- npm install
68
- npm run build
69
- ```
70
-
71
- ## Related
63
+ ## Related Projects
72
64
 
73
65
  - [opencode-throughput](https://github.com/Howardzhangdqs/opencode-throughput) — Real-time LLM performance monitoring (TTFT/TPS/latency/cost)
74
66
  - [opencode-visual-cache](https://github.com/Hotakus/opencode-visual-cache) — TUI sidebar cache hit rate visualization, token distribution analysis
75
67
  - [magic-context](https://github.com/cortexkit/magic-context/) — Cache-aware infinite context + cross-session memory system
76
68
 
69
+ ---
70
+
77
71
  ## License
78
72
 
79
73
  MIT
package/README.md CHANGED
@@ -4,29 +4,33 @@
4
4
 
5
5
  ![Sidebar](./assets/sidebar.png)
6
6
 
7
- OpenCode CLI 的实时 Token 用量统计、缓存分析与性能指标插件。
7
+ OpenCode CLI的终端监控插件,在侧边栏实时显示Token用量、缓存命中率与模型响应速度,并支持一键生成网页版可视化用量报表。
8
8
 
9
- ## 功能
9
+ ---
10
10
 
11
- - **侧边栏面板** — 会话级与按模型的实时统计(请求数、Token、缓存、成本)
12
- - **缓存命中率** — 按模型实时追踪,带趋势指示器(↑/↓)与全局加权总计
13
- - **性能指标** — TTFT / TPS / 端到端延迟,含 P50/P95/P99 延迟分位数
14
- - **Token 分布** — 按角色(system / user / tool / output)分解用量占比
15
- - **成本追踪** — 按模型实时显示请求成本(需 provider 返回计费数据)
16
- - **错误率统计** — 识别并统计失败请求,实时计算错误率
17
- - **`/usage` 命令** — HTML 报告 → JSON 导出 → 文本报告 → 设置
18
- - **HTML 报告** — 交互式 ECharts 仪表盘:Token 趋势、性能对比、TPS 排名、错误率分析,自动在浏览器打开
19
- - **持久化统计** — 性能指标永久累积写入独立文件,历史数据不受会话重置影响
20
- - **多级折叠** — 面板、模型、子区块均可折叠,状态持久化
21
- - **语言切换** — 中英双语,跟随系统或手动切换
11
+ ## 主要特性
22
12
 
23
- ## 安装
13
+ - **侧边栏实时看板**:会话中随时查看各模型的输入输出Token、预估花费、缓存命中率,以及当前会话的平均首字延迟与生成速率。
14
+ - **缓存命中率与趋势**:按模型追踪缓存利用效率,用箭头标注近几次请求的命中率是在上升还是下降。
15
+ - **Token构成归类**:自动将Token按系统提示词、用户输入、工具调用和模型输出分别统计,清楚定位上下文占用大头。
16
+ - **交互式用量报告**:在终端输入 `/usage` 即可在浏览器中打开ECharts 交互仪表盘,查看历史用量趋势、模型横向对比与失败请求统计;也可一键导出为JSON或Markdown文件。
17
+ - **双版本无缝适配**:同时兼容 OpenCode 1.x 与 OpenCode 2(beta),自动识别宿主环境,无需手动切换或调整配置。
18
+
19
+ ---
20
+
21
+ ## 安装与配置
22
+
23
+ ### 1. 安装
24
+
25
+ 在项目中运行:
24
26
 
25
27
  ```sh
26
28
  npm install opencode-tokenwatch
27
29
  ```
28
30
 
29
- `opencode.json` 或 `opencode.jsonc` 中添加:
31
+ ### 2. 启用插件
32
+
33
+ 在配置文件 `opencode.json`(或 `opencode.jsonc`)中添加插件项:
30
34
 
31
35
  ```json
32
36
  {
@@ -35,38 +39,26 @@ npm install opencode-tokenwatch
35
39
  }
36
40
  ```
37
41
 
38
- ## 配置
42
+ 重新打开 OpenCode,侧边栏便会自动载入 TokenWatch 面板。
39
43
 
40
- 在 OpenCode TUI 中输入 `/usage` → **设置**,可交互式开关各显示项和切换界面语言,配置自动持久化,无需手动编辑配置文件。
44
+ ---
41
45
 
42
- ## 用法
46
+ ## 常用操作
43
47
 
44
- OpenCode TUI 中输入 `/usage`,选择:
48
+ 在终端对话框中输入 `/usage` 即可唤出操作菜单:
45
49
 
46
- - **HTML 报告** — 选择日期范围,生成仪表盘并在浏览器打开
47
- - **JSON 导出** 导出完整用量数据至 `~/.opencode/reports/`
48
- - **文本报告** — 导出 Markdown 格式至 `~/.opencode/reports/`
49
- - **设置** — 开关侧边栏显示项、切换语言
50
+ - **HTML 报告**:选择统计日期范围,在默认浏览器中打开交互式图表。
51
+ - **导出数据**:导出结构化 JSON Markdown 报告,默认保存在 `~/.opencode/reports/` 目录下。
52
+ - **设置**:在弹出的交互菜单中开关各个显示区块或切换中英文显示。改动会自动保存,不需要手动修改配置文件。
50
53
 
51
- ## 数据文件
52
-
53
- | 文件 | 路径 | 说明 |
54
- |------|------|------|
55
- | JSONL 日志 | `~/.opencode/tokenwatch.jsonl` | 原始请求日志 |
56
- | 聚合统计 | `~/.opencode/tokenwatch-stats.json` | 持久化性能统计 |
57
- | 报告输出 | `~/.opencode/reports/` | HTML / JSON / Markdown 报告 |
54
+ ---
58
55
 
59
56
  ## 系统要求
60
57
 
61
- - OpenCode CLI(支持 `opencode db` 命令)
62
- - Node.js 18+
58
+ - **OpenCode CLI**:1.18+ OpenCode 2(`@opencode-ai/cli@beta`)
59
+ - **Node.js**:≥ 18.0.0
63
60
 
64
- ## 构建
65
-
66
- ```sh
67
- npm install
68
- npm run build
69
- ```
61
+ ---
70
62
 
71
63
  ## 相关项目
72
64
 
@@ -74,6 +66,8 @@ npm run build
74
66
  - [opencode-visual-cache](https://github.com/Hotakus/opencode-visual-cache) — TUI 侧边栏缓存命中率可视化,Token 分布分析
75
67
  - [magic-context](https://github.com/cortexkit/magic-context/) — 缓存感知的无限上下文 + 跨会话记忆系统
76
68
 
77
- ## 许可
69
+ ---
70
+
71
+ ## 协议
78
72
 
79
73
  MIT
@@ -0,0 +1,20 @@
1
+ import type { HostAdapter } from "./types.js";
2
+ import type { SidebarConfig, SidebarToggleKey } from "../kernel/config.js";
3
+ import type { UsageFilters } from "../kernel/format.js";
4
+ export interface CommandActions {
5
+ htmlReport(filters?: UsageFilters, presetTag?: string): Promise<void>;
6
+ jsonExport(): Promise<void>;
7
+ textReport(): Promise<void>;
8
+ config(): SidebarConfig;
9
+ toggle(key: SidebarToggleKey): SidebarConfig;
10
+ setLanguage(language: SidebarConfig["language"]): SidebarConfig;
11
+ }
12
+ export declare function createCommandActions(host: HostAdapter): CommandActions;
13
+ /** 报告时间范围预设,供两代宿主菜单共用 */
14
+ export interface RangePreset {
15
+ readonly id: string;
16
+ readonly label: string;
17
+ readonly tag: string;
18
+ readonly filters: UsageFilters;
19
+ }
20
+ export declare function rangePresets(): RangePreset[];
@@ -0,0 +1,7 @@
1
+ import type { HostAdapter } from "./types.js";
2
+ /**
3
+ * 启动 TokenWatch 共享核心,返回卸载函数。
4
+ *
5
+ * 事件 → 聚合/持久化/性能追踪 → 侧边栏渲染 的整条链路在此装配。
6
+ */
7
+ export declare function startTokenWatch(host: HostAdapter): () => void;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * 宿主无关的能力契约。
3
+ *
4
+ * v1(opencode 1.x,TuiPluginApi)与 v2(opencode2,Plugin Context)在
5
+ * 事件模型、插槽命名、主题结构、命令注册、存储 API 上全都不同。
6
+ * 本文件定义两者共同能力的交集,由各自的 adapter 负责映射。
7
+ *
8
+ * 上层 UI / 命令逻辑只依赖本接口,因此可以在两代宿主间完全复用。
9
+ */
10
+ import type { RGBA } from "@opentui/core";
11
+ import type { JSX } from "@opentui/solid";
12
+ import type { UsageFilters, UsageReport } from "../kernel/format.js";
13
+ export type HostKind = "v1" | "v2";
14
+ /** 归一化主题色。v1 扁平、v2 嵌套,由 adapter 各自映射。 */
15
+ export interface ThemeColors {
16
+ readonly primary: RGBA;
17
+ readonly text: RGBA;
18
+ readonly textMuted: RGBA;
19
+ readonly background: RGBA;
20
+ readonly border: RGBA;
21
+ readonly success: RGBA;
22
+ readonly warning: RGBA;
23
+ readonly error: RGBA;
24
+ }
25
+ /** 归一化消息事件(由两代各自的事件形状折叠而来) */
26
+ export interface NormalizedMessageEvent {
27
+ readonly messageID: string;
28
+ readonly sessionID: string;
29
+ readonly role: string;
30
+ readonly providerID: string;
31
+ readonly modelID: string;
32
+ readonly input: number;
33
+ readonly output: number;
34
+ readonly reasoning: number;
35
+ readonly cacheRead: number;
36
+ readonly cacheWrite: number;
37
+ readonly total: number;
38
+ readonly cost: number;
39
+ /** 请求开始时间(epoch ms),供性能追踪计算延迟/TPS */
40
+ readonly timeCreated?: number;
41
+ /** 请求完成时间(epoch ms),缺失时性能追踪跳过该样本 */
42
+ readonly timeCompleted?: number;
43
+ /** 原始事件对象,供性能追踪器读取时间戳等宿主专属字段 */
44
+ readonly raw: any;
45
+ }
46
+ /** 归一化 part 事件(用于 TTFT 计时) */
47
+ export interface NormalizedPartEvent {
48
+ readonly messageID?: string;
49
+ readonly type?: string;
50
+ readonly text?: string;
51
+ readonly timeStart?: number;
52
+ readonly raw: any;
53
+ }
54
+ export interface HostEventHandlers {
55
+ onMessageUpdated(event: NormalizedMessageEvent): void;
56
+ onPartUpdated(event: NormalizedPartEvent): void;
57
+ /** 消息被删除 / 会话数据失效,UI 应重算 */
58
+ onInvalidate(): void;
59
+ }
60
+ /** 侧边栏渲染入参(归一化:两代宿主都提供 sessionID) */
61
+ export interface SidebarInput {
62
+ readonly sessionID: string;
63
+ }
64
+ /** 键值存储(配置与折叠状态) */
65
+ export interface KeyValueStore {
66
+ get<T>(key: string, fallback: T): T;
67
+ set<T>(key: string, value: T): void;
68
+ /** 删除条目;v1 宿主可能不提供(调用方需判空),用于会话消息 KV 的淘汰 */
69
+ delete?(key: string): void;
70
+ }
71
+ export interface CommandSpec {
72
+ readonly id: string;
73
+ readonly title: string;
74
+ readonly description?: string;
75
+ readonly category?: string;
76
+ /** 斜杠命令名(不含前导斜杠) */
77
+ readonly slash?: string;
78
+ readonly run: () => void | Promise<void>;
79
+ }
80
+ export interface SelectOption<Value> {
81
+ readonly title: string;
82
+ readonly value: Value;
83
+ readonly description?: string;
84
+ }
85
+ export type NotifyVariant = "info" | "success" | "warning" | "error";
86
+ /**
87
+ * 历史用量数据源。
88
+ *
89
+ * v1 通过 `opencode db` 执行 SQL(快、功能完整);
90
+ * v2 移除了 db 子命令,只能在客户端遍历会话消息重算(慢、需要缓存与进度提示)。
91
+ */
92
+ export interface UsageDataSource {
93
+ readonly kind: "sql" | "client-scan";
94
+ /**
95
+ * 首次取数是否需要显著耗时。
96
+ * v2 为 true —— 宿主应在调用前提示用户。
97
+ */
98
+ readonly needsFirstRunNotice: boolean;
99
+ /**
100
+ * 本次取数是否会触发冷启动全量扫描。
101
+ * SQL 数据源恒为 false;client-scan 在缓存为空时返回 true。
102
+ */
103
+ readonly isCold: () => boolean;
104
+ getUsageReport(filters: UsageFilters): Promise<UsageReport>;
105
+ }
106
+ /** 宿主能力统一抽象 */
107
+ export interface HostAdapter {
108
+ readonly kind: HostKind;
109
+ /** 宿主版本号,用于诊断与报告元信息 */
110
+ readonly hostVersion: string;
111
+ theme(): ThemeColors;
112
+ store: KeyValueStore;
113
+ /** 订阅事件,返回取消订阅函数 */
114
+ subscribe(handlers: HostEventHandlers): () => void;
115
+ /** 挂载侧边栏 UI,返回卸载函数 */
116
+ registerSidebar(render: (input: SidebarInput) => JSX.Element): () => void;
117
+ /**
118
+ * 注册命令,返回注销函数。
119
+ * v1 经由 host/v1/commands.tsx 用原生 DialogSelect 实现菜单,不走此方法。
120
+ */
121
+ registerCommands?(specs: readonly CommandSpec[]): () => void;
122
+ notify(message: string, variant?: NotifyVariant): void;
123
+ alert(input: {
124
+ title: string;
125
+ message: string;
126
+ }): Promise<void>;
127
+ select<Value>(input: {
128
+ title: string;
129
+ options: readonly SelectOption<Value>[];
130
+ }): Promise<Value | undefined>;
131
+ /** 宿主退出 / 插件卸载时调用 */
132
+ onDispose(fn: () => void): void;
133
+ /** 当前会话消息列表(用于侧边栏 token 分布估算与历史重建) */
134
+ sessionMessages(sessionID: string): readonly any[];
135
+ /**
136
+ * 指定消息的 part 列表,归一化到 v1 part 形状。
137
+ *
138
+ * v1 需要二次查询 `api.state.part(id)`(会话无关,sessionID 忽略);
139
+ * v2 传入 sessionID 时走 O(1) 的 `message.get(sessionID, id)`,
140
+ * 避免跨全部会话线性扫描。
141
+ */
142
+ messageParts(messageID: string, sessionID?: string): readonly any[];
143
+ /** 宿主应用配置(读取 agent prompt 用于 system token 估算) */
144
+ appConfig(): Record<string, unknown>;
145
+ /** 订阅 part 级更新(用于 token 分布重算),返回取消订阅函数 */
146
+ onPartUpdated(handler: () => void): () => void;
147
+ readonly dataSource: UsageDataSource;
148
+ }
@@ -0,0 +1,9 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ /**
3
+ * v1 宿主适配层:把 opencode 1.x 的 `TuiPluginApi` 折叠为统一 `HostAdapter`。
4
+ */
5
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
6
+ import type { HostAdapter, KeyValueStore } from "../types.js";
7
+ /** v1 kv 的 KeyValueStore 折叠(v1/commands.tsx 复用同一份,避免两套实现漂移) */
8
+ export declare function makeStore(api: TuiPluginApi): KeyValueStore;
9
+ export declare function createV1Adapter(api: TuiPluginApi): HostAdapter;
@@ -0,0 +1,9 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ /**
3
+ * v1 命令注册 —— /usage 菜单(HTML 报告 / JSON 导出 / 文本报告 / 设置)。
4
+ *
5
+ * v1 拥有原生 DialogSelect 对话栈,UX 优于通用 select,因此菜单在此原生实现;
6
+ * 实际动作(取数、装配、落盘、打开)全部委托内核层,与 v2 行为一致。
7
+ */
8
+ import type { TuiPluginApi } from "@opencode-ai/plugin/tui";
9
+ export declare function registerCommands(api: TuiPluginApi): Promise<void>;
@@ -1,14 +1,9 @@
1
- import type { DailyBreakdownItem, ErrorStats, ModelBreakdownItem, ProviderBreakdownItem, SessionBreakdownItem, SessionTokenData, UsageFilters, UsageReport } from "./formatter.js";
1
+ import type { DailyBreakdownItem, ErrorStats, ModelBreakdownItem, ProviderBreakdownItem, SessionBreakdownItem, SessionTokenData, UsageFilters, UsageReport } from "../../kernel/format.js";
2
2
  export declare function getPresetRange(preset: "all" | "7d" | "30d" | "month"): Pick<UsageFilters, "startDate" | "endDate">;
3
- export declare function getCurrentSessionStats(sessionId?: string): Promise<SessionTokenData>;
4
3
  export declare function getSummary(filters?: UsageFilters): Promise<SessionTokenData>;
5
4
  export declare function getModelBreakdown(filters?: UsageFilters): Promise<ModelBreakdownItem[]>;
6
5
  export declare function getProviderBreakdown(filters?: UsageFilters): Promise<ProviderBreakdownItem[]>;
7
6
  export declare function getDailyBreakdown(filters?: UsageFilters): Promise<DailyBreakdownItem[]>;
8
7
  export declare function getSessionBreakdown(filters?: UsageFilters): Promise<SessionBreakdownItem[]>;
9
- export declare function getAvailableModels(): Promise<string[]>;
10
- export declare function getAvailableProviders(): Promise<string[]>;
11
- /** 失败请求计数 SQL。1次运行获取成功数+失败数+按模型细分 */
12
8
  export declare function getErrorStats(filters?: UsageFilters): Promise<ErrorStats>;
13
9
  export declare function getUsageReport(filters?: UsageFilters): Promise<UsageReport>;
14
- export declare function exportReportAsCsv(report: UsageReport, section: "models" | "providers" | "daily" | "sessions"): string;
@@ -0,0 +1,63 @@
1
+ import type { HostAdapter } from "../types.js";
2
+ /** v2 插件 Context 的最小结构约束(避免对 beta SDK 的硬类型依赖) */
3
+ export interface V2Context {
4
+ readonly app: {
5
+ readonly version: string;
6
+ readonly channel: string;
7
+ };
8
+ readonly theme: any;
9
+ readonly themeMode: "dark" | "light";
10
+ readonly data: {
11
+ readonly on: (type: string, handler: (event: any) => void) => () => void;
12
+ readonly session: {
13
+ list(): any[];
14
+ get(sessionID: string): any | undefined;
15
+ readonly message: {
16
+ list(sessionID: string): any[];
17
+ get(sessionID: string, messageID: string): any | undefined;
18
+ sync(sessionID: string): Promise<void>;
19
+ invalidate(sessionID: string): void;
20
+ };
21
+ sync(sessionID: string): Promise<void>;
22
+ };
23
+ readonly location: {
24
+ readonly agent: {
25
+ list(location?: any): any[] | undefined;
26
+ };
27
+ };
28
+ };
29
+ readonly ui: {
30
+ readonly slot: (claim: any) => () => void;
31
+ readonly toast: {
32
+ show(options: {
33
+ message: string;
34
+ variant?: string;
35
+ title?: string;
36
+ }): void;
37
+ };
38
+ readonly dialog: {
39
+ alert(options: {
40
+ title: string;
41
+ message: string;
42
+ }): Promise<void>;
43
+ select<Value>(options: {
44
+ title: string;
45
+ options: readonly {
46
+ title: string;
47
+ value: Value;
48
+ description?: string;
49
+ }[];
50
+ }): Promise<Value | undefined>;
51
+ };
52
+ };
53
+ readonly keymap: {
54
+ layer(input: () => any): void;
55
+ };
56
+ readonly storage: {
57
+ store<Value extends object>(key: string, options: {
58
+ readonly initial: Value;
59
+ }): readonly [any, (m: (draft: Value) => void) => Promise<void>];
60
+ };
61
+ readonly client: any;
62
+ }
63
+ export declare function createV2Adapter(ctx: V2Context, dispose: Set<() => void>): HostAdapter;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * v2 命令注册 —— 通过 keymap.layer 暴露 /usage 斜杠命令与面板命令。
3
+ *
4
+ * 菜单用宿主通用 `dialog.select` 实现(v2 无 DialogSelect 对话栈),
5
+ * 动作复用两代共享的 command-actions,行为与 v1 菜单一致。
6
+ */
7
+ import type { HostAdapter } from "../types.js";
8
+ export declare function registerV2Commands(host: HostAdapter): void;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * v2 历史用量数据源 —— 客户端遍历重算。
3
+ *
4
+ * v2 移除了 `opencode db` 子命令,无法再用 SQL 聚合。这里改为:
5
+ * 1. `session.list()` 拿到全部会话(自带 tokens/cost,可快速出总数)
6
+ * 2. 逐会话 `message.list()` 遍历 assistant 消息,按模型 / 供应商 / 日期分解
7
+ *
8
+ * 第 2 步是 O(会话数 × 消息数) 的冷启动开销,因此:
9
+ * - 首次执行前由调用方提示用户(见 `UsageDataSource.needsFirstRunNotice`)
10
+ * - 结果缓存在内存中,同一 TUI 会话内不重复扫描
11
+ * - 每个会话只同步一次,并把让步交给事件循环,避免长时间阻塞渲染
12
+ */
13
+ import type { UsageFilters, UsageReport } from "../../kernel/format.js";
14
+ /**
15
+ * 取用量报告(stale-while-revalidate)。
16
+ *
17
+ * - 冷启动(无缓存):等待全量扫描完成(调用方已先提示用户)。
18
+ * - 缓存可用且数据有更新(markUsageCacheDirty):立即返回旧快照,
19
+ * 同时后台重建 —— 本次报告不卡顿,下次打开即为新数据。
20
+ * - 缓存可用且无更新:直接返回。
21
+ */
22
+ export declare function getUsageReport(ctx: any, filters?: UsageFilters): Promise<UsageReport>;
23
+ /** 有新数据落库(如 session.idle)时由适配层调用:下次取数触发后台重建 */
24
+ export declare function markUsageCacheDirty(): void;
25
+ /** 供命令层判断本次调用是否会触发冷启动扫描 */
26
+ export declare function isUsageCacheCold(): boolean;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 侧边栏配置 —— 宿主无关的持久化设置。
3
+ *
4
+ * 读写只依赖 `KeyValueStore`(v1 的 kv / v2 的 storage.store 在各自 adapter 中折叠),
5
+ * 因此设置项在两代宿主间行为完全一致。
6
+ */
7
+ import type { KeyValueStore } from "../host/types.js";
8
+ export interface SidebarConfig {
9
+ sidebar: {
10
+ showPerformance: boolean;
11
+ showPricing: boolean;
12
+ showTokenDistribution: boolean;
13
+ showTrend: boolean;
14
+ };
15
+ language: "zh" | "en" | "auto";
16
+ }
17
+ export type SidebarToggleKey = keyof SidebarConfig["sidebar"];
18
+ export declare const DEFAULT_CONFIG: SidebarConfig;
19
+ /**
20
+ * 读取配置:默认值 ← 插件配置(opencode.json)← 运行时覆盖(设置菜单)。
21
+ * `pluginConfig` 为宿主声明式配置,v1 读 `state.config`,v2 暂无等价物。
22
+ */
23
+ export declare function loadConfig(store: KeyValueStore, pluginConfig?: Record<string, any>): SidebarConfig;
24
+ export declare function saveConfig(store: KeyValueStore, cfg: SidebarConfig): void;
25
+ /**
26
+ * 递增配置版本号。
27
+ *
28
+ * 侧边栏用轮询版本号的方式感知"设置在别处被改了",
29
+ * 因为 v1 的 kv 不是响应式的。
30
+ */
31
+ export declare function bumpVersion(store: KeyValueStore): void;
32
+ export declare function toggleSidebarSetting(store: KeyValueStore, key: SidebarToggleKey): SidebarConfig;
33
+ export declare function setLanguageSetting(store: KeyValueStore, language: SidebarConfig["language"]): SidebarConfig;
@@ -73,7 +73,7 @@ export interface ErrorStats {
73
73
  successCount: number;
74
74
  /** 失败请求数(tokens.total == 0 的 assistant) */
75
75
  failedCount: number;
76
- /** 失败率:failedCount / (successCount + failedCount) */
76
+ /** 失败率:failedCount / (successCount + failedCount),小数口径(0~1),两代宿主一致 */
77
77
  errorRate: number;
78
78
  /** 按模型细化的失败数 */
79
79
  byModel: Array<{
@@ -91,6 +91,8 @@ export interface UsageReport {
91
91
  daily: DailyBreakdownItem[];
92
92
  sessions: SessionBreakdownItem[];
93
93
  errors?: ErrorStats;
94
+ /** daily 明细被数据源截断(未覆盖全部日期)时为 true,报告 UI 据此提示 */
95
+ dailyTruncated?: boolean;
94
96
  }
95
97
  export declare function formatTokens(n: number): string;
96
98
  export declare function formatCost(n: number): string;
@@ -145,15 +147,10 @@ export interface ModelPerfStats {
145
147
  p99Latency: number | null;
146
148
  /** 该模型加权缓存命中率:cacheRead / (cacheRead + input) */
147
149
  cacheHitRate: number | null;
148
- }
149
- export interface TokenDistribution {
150
- system: number;
151
- user: number;
152
- agent: number;
153
- toolCall: number;
154
- toolResult: number;
155
- output: number;
156
- total: number;
150
+ /** 最近一次请求的 TTFT / TPS / 端到端延迟(与宿主单次口径可直接对照) */
151
+ lastTTFT: number | null;
152
+ lastTPS: number | null;
153
+ lastLatency: number | null;
157
154
  }
158
155
  export interface LogEntry {
159
156
  ts: string;
@@ -187,4 +184,6 @@ export interface CombinedReportData {
187
184
  perfLogs: LogEntry[];
188
185
  perfSummary: ModelPerfStats[];
189
186
  meta: HtmlReportMeta;
187
+ /** daily 明细被数据源截断时为 true(来自 UsageReport),报告时间线区据此提示 */
188
+ dailyTruncated?: boolean;
190
189
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 会话内单条 assistant 消息的归一化内存模型。
3
+ *
4
+ * v1 来自 `message.updated` 事件(`info.tokens.{input,output,reasoning,cache}`);
5
+ * v2 来自 `SessionMessageAssistant.tokens`(`{input,output,reasoning,cache:{read,write}}`)。
6
+ * 两者在各自的 adapter 中折叠为本类型,后续所有聚合逻辑只认这一份结构。
7
+ */
8
+ export interface TokenMessage {
9
+ id: string;
10
+ sessionID: string;
11
+ providerID: string;
12
+ modelID: string;
13
+ inputTokens: number;
14
+ outputTokens: number;
15
+ reasoningTokens: number;
16
+ cacheRead: number;
17
+ cacheWrite: number;
18
+ cost: number;
19
+ }