opencode-tokenwatch 0.5.0 → 0.6.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.
@@ -0,0 +1,62 @@
1
+ /**
2
+ * 性能聚合核心 —— perf 追踪器(内存)、持久化统计(store)、报告聚合(report)
3
+ * 三处共用的 Welford 增量聚合 + Reservoir 分位数采样。
4
+ *
5
+ * 口径约定(与宿主官方对齐,详见 AGENTS.md):
6
+ * - tokenTotal 五分量:input + output + reasoning + cache.read + cache.write
7
+ * - TPS:可见输出 tokens / (流式终点 − 流式窗口起点),单位 tok/s
8
+ * - TTFT:首个"首 token"锚点(可见输出 part / delta)− 请求起点
9
+ * - latency:端到端 completed − created(含收尾 settlement)
10
+ */
11
+ import type { LogEntry, ModelPerfStats } from "./format.js";
12
+ /** 线性插值百分位数,输入须为升序数组 */
13
+ export declare function computePercentile(sortedArr: number[], p: number): number | null;
14
+ /** 每 marker 最多保留的原始样本数(Reservoir Sampling,内存有界) */
15
+ export declare const RESERVOIR_SIZE = 500;
16
+ /**
17
+ * Reservoir Sampling:保证每个观测值以相同概率进入样本(算法 R 变体),
18
+ * 使分位数估算在统计意义上无偏。
19
+ */
20
+ export declare function reservoirAdd(reservoir: number[], value: number, totalCount: number): number[];
21
+ /** 单模型聚合累加器(内存态;持久化层可直接序列化本结构) */
22
+ export interface ModelAccumulator {
23
+ model: string;
24
+ providerID: string;
25
+ requestCount: number;
26
+ ttftCount: number;
27
+ tpsCount: number;
28
+ latencyCount: number;
29
+ totalInput: number;
30
+ totalOutput: number;
31
+ totalCacheRead: number;
32
+ totalCacheWrite: number;
33
+ totalCost: number;
34
+ avgTTFT: number | null;
35
+ maxTTFT: number | null;
36
+ minTTFT: number | null;
37
+ avgTPS: number | null;
38
+ maxTPS: number | null;
39
+ minTPS: number | null;
40
+ avgLatency: number | null;
41
+ maxLatency: number | null;
42
+ minLatency: number | null;
43
+ /** TTFT / 端到端延迟原始样本(Reservoir,用于分位数) */
44
+ ttftReservoir: number[];
45
+ latencyReservoir: number[];
46
+ /** 最近一次请求值(侧边栏与宿主 footer 单次口径对照) */
47
+ lastTTFT: number | null;
48
+ lastTPS: number | null;
49
+ lastLatency: number | null;
50
+ }
51
+ export declare function createAccumulator(model: string, providerID: string): ModelAccumulator;
52
+ /**
53
+ * 把一条日志条目增量并入累加器。
54
+ *
55
+ * 均值用 Welford 在线算法;分母是各自的**有效样本数**
56
+ * (ttftCount/tpsCount/latencyCount),缺失指标的请求不会拉低均值。
57
+ * 全零 token(五分量均为 0)的失败请求被跳过 —— 与宿主官方
58
+ * `tokenTotal(msg) <= 0 continue` 的过滤口径一致(含 reasoning)。
59
+ */
60
+ export declare function accumulateEntry(acc: ModelAccumulator, entry: LogEntry): void;
61
+ /** 聚合累加器 → 对外输出的 ModelPerfStats(分位数在此计算) */
62
+ export declare function finalizeAccumulator(acc: ModelAccumulator): ModelPerfStats;
@@ -1,4 +1,4 @@
1
- import type { LogEntry, SessionPerfStats } from "./formatter.js";
1
+ import type { LogEntry, SessionPerfStats } from "./format.js";
2
2
  interface PartEvent {
3
3
  message_id?: string;
4
4
  type?: string;
@@ -39,22 +39,28 @@ interface MessageRemoveEvent {
39
39
  };
40
40
  }
41
41
  declare class PerfTracker {
42
+ /** 最早的任意 part(含 step 起点)—— 用作 TPS 的流式窗口起点(对齐宿主官方口径) */
42
43
  private firstPartTimes;
44
+ /** 最早的输出 part(text/reasoning)—— 用作 TTFT(用户等待首个可见 token 的时间) */
45
+ private firstOutputTimes;
43
46
  private statsMap;
44
- /** 原始样本串,用于分位数计算,不持久化 */
45
- private ttftSamples;
46
- private latencySamples;
47
+ /** 会话加载令牌:异步重放期间再次切会话时,旧加载结果按令牌过期丢弃 */
48
+ private loadToken;
47
49
  handlePartUpdated(event: PartEvent): void;
48
50
  handleMessageUpdated(event: MessageUpdateEvent): void;
49
51
  private appendLog;
50
52
  handleMessageRemoved(event: MessageRemoveEvent): void;
51
53
  private updateStats;
52
- /** 计算有序数组的指定百分位数(线性插值法) */
53
- private percentile;
54
54
  getSessionStats(): SessionPerfStats;
55
55
  readLogs(last?: number): LogEntry[];
56
56
  reset(): void;
57
- loadSession(sessionID: string): void;
57
+ /**
58
+ * 切换会话:清空内存态后异步重放该会话的 JSONL 历史。
59
+ *
60
+ * 异步化是为了不阻塞 TUI 渲染线程(5MB 日志的同步读可达数十毫秒);
61
+ * 重放完成前若又切换了会话,由 loadToken 令牌丢弃过期结果。
62
+ */
63
+ loadSession(sessionID: string): Promise<void>;
58
64
  }
59
65
  export declare function createPerfTracker(): PerfTracker;
60
66
  export type { PartEvent, PerfTracker };
@@ -1,2 +1,2 @@
1
- import type { CombinedReportData } from "./formatter.js";
1
+ import type { CombinedReportData } from "./format.js";
2
2
  export declare function generateUsageHtml(data: CombinedReportData): string;
@@ -0,0 +1,19 @@
1
+ import type { CombinedReportData, UsageFilters, UsageReport } from "./format.js";
2
+ /** 本地时区 YYYY-MM-DD(toISOString 按 UTC,跨时区会偏一天)—— 全项目统一日期出口 */
3
+ export declare function localDateStr(d: Date): string;
4
+ /** 报告输出目录(~/.opencode/reports) */
5
+ export declare function ensureReportDir(): string;
6
+ /** 用系统默认程序打开文件(多平台,失败静默;spawn detached 不阻塞 TUI) */
7
+ export declare function openInBrowser(filePath: string): void;
8
+ export declare function getRangeSlug(filters: UsageFilters, presetTag?: string): string;
9
+ /** 生成不覆盖已有文件的报告路径(同名追加时间戳,仍冲突则递增后缀) */
10
+ export declare function generateUniqueReportPath(dir: string, rangeSlug: string): string;
11
+ /**
12
+ * 装配完整报告数据。
13
+ *
14
+ * `usage` 由宿主数据源提供(v1 走 SQL、v2 走客户端遍历),
15
+ * 其余全部来自内核,因此两代宿主产出的报告结构完全一致。
16
+ */
17
+ export declare function buildCombinedData(usage: Omit<UsageReport, "filters">): CombinedReportData;
18
+ /** 生成 HTML 报告并写入磁盘,返回文件路径 */
19
+ export declare function writeHtmlReport(data: CombinedReportData, rangeSlug: string): string;
@@ -7,17 +7,17 @@
7
7
  * - 百分位数采用 Reservoir Sampling 保持有界内存占用
8
8
  * - 首次启动时自动从现有 JSONL 日志迁移,不丢失历史数据
9
9
  */
10
- import type { LogEntry, ModelPerfStats } from "./formatter.js";
10
+ import type { LogEntry, ModelPerfStats } from "./format.js";
11
11
  /**
12
12
  * 将一条新的日志条目增量更新到持久化统计文件。
13
- * 在 perf-tracker.ts 的 appendLog() 之后调用。
13
+ * 在 perf-tracker 的 appendLog() 之后调用。
14
14
  *
15
- * 设计原则:本函数只做增量更新,迁移逻辑由 readPersistedStats() 负责。
16
- * 这样可以避免迁移与增量更新之间的竞态问题。
15
+ * 增量先累积在内存副本上(至多每 WRITE_COALESCE_MS 落盘一次),
16
+ * 进程正常退出时由 exit 钩子强制刷盘。
17
17
  */
18
18
  export declare function updatePersistedStats(entry: LogEntry): void;
19
19
  /**
20
20
  * 读取所有持久化统计,返回 ModelPerfStats 数组(含分位数)。
21
- * 用于 HTML 报告生成,替代 aggregatePerfStats(readLogs(N)) 的有限窗口方案。
21
+ * 用于 HTML 报告生成,替代有限窗口的日志聚合方案。
22
22
  */
23
23
  export declare function readPersistedStats(): ModelPerfStats[];
package/dist/server.d.ts CHANGED
@@ -1,5 +1,15 @@
1
1
  import type { PluginModule } from "@opencode-ai/plugin";
2
+ /**
3
+ * Server 插件入口。
4
+ *
5
+ * v1 契约禁止 server 与 tui 同时出现,因此 TUI 入口在 ./tui(dist/tui.js)。
6
+ * 这里额外携带 setup,使 opencode2 在以包主入口解析时也能找到 v2 入口:
7
+ * - v1 加载器忽略多余字段,不受影响
8
+ * - setup 内部用动态 import 惰性加载 TUI 装配代码,
9
+ * 避免 server 进程在加载阶段就解析 @opentui 依赖
10
+ */
2
11
  declare const plugin: PluginModule & {
3
12
  id: string;
13
+ setup?: (ctx: any) => Promise<(() => void) | void>;
4
14
  };
5
15
  export default plugin;
package/dist/server.js CHANGED
@@ -3,6 +3,10 @@ var plugin = {
3
3
  id: "opencode-tokenwatch",
4
4
  server: async () => {
5
5
  return {};
6
+ },
7
+ setup: async (ctx) => {
8
+ const mod = await import("./tui.js");
9
+ return mod.default.setup?.(ctx);
6
10
  }
7
11
  };
8
12
  var server_default = plugin;
package/dist/tui.d.ts CHANGED
@@ -1,17 +1,16 @@
1
+ /** @jsxImportSource @opentui/solid */
2
+ /**
3
+ * 分发入口 —— 单一包体同时服务两代宿主。
4
+ *
5
+ * default export 同时携带 `tui` 与 `setup`:
6
+ * - opencode 1.x 加载后调用 `tui(api)`(v1 加载器只认 tui,忽略 setup)
7
+ * - opencode2 加载后调用 `setup(ctx)`(v2 校验只查 id + setup,忽略 tui)
8
+ *
9
+ * 宿主调用哪个入口,本身就是 100% 可靠的代际判断,无需任何启发式探测。
10
+ */
1
11
  import type { TuiPluginModule } from "@opencode-ai/plugin/tui";
2
- export interface TokenMessage {
3
- id: string;
4
- sessionID: string;
5
- providerID: string;
6
- modelID: string;
7
- inputTokens: number;
8
- outputTokens: number;
9
- reasoningTokens: number;
10
- cacheRead: number;
11
- cacheWrite: number;
12
- cost: number;
13
- }
14
12
  declare const plugin: TuiPluginModule & {
15
13
  id: string;
14
+ setup?: (ctx: any) => (() => void) | void;
16
15
  };
17
16
  export default plugin;