@chenlongapps/opencode-token-usage 0.2.5 → 0.3.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 +9 -7
- package/README.zh-CN.md +9 -7
- package/ROADMAP.md +11 -6
- package/dist/controller.js +1 -1
- package/dist/pricing.d.ts +142 -0
- package/dist/pricing.js +488 -0
- package/dist/source.d.ts +3 -3
- package/dist/source.js +8 -3
- package/dist/usage.d.ts +14 -2
- package/dist/usage.js +37 -12
- package/docs/pricing.md +282 -0
- package/docs/verification.md +9 -5
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
<p align="center">A session-tree token usage monitor for OpenCode 2.</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://www.npmjs.com/package/@chenlongapps/opencode-token-usage"><img alt="npm" src="https://img.shields.io/npm/v/%40chenlongapps%2Fopencode-token-usage?style=flat-square&logo=npm" /></a>
|
|
7
|
-
<a href="https://opencode.ai/"><img alt="OpenCode 2" src="https://img.shields.io/badge/OpenCode-2-5A67D8?style=flat-square" /></a>
|
|
8
|
-
<a href="https://github.com/chenlongapps/opencode-token-usage/actions/workflows/ci.yml"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/chenlongapps/opencode-token-usage/ci.yml?style=flat-square&branch=main&label=ci" /></a>
|
|
9
|
-
<a href="LICENSE"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-yellow?style=flat-square" /></a>
|
|
6
|
+
<a href="https://www.npmjs.com/package/@chenlongapps/opencode-token-usage" target="_blank" rel="noopener noreferrer"><img alt="npm" src="https://img.shields.io/npm/v/%40chenlongapps%2Fopencode-token-usage?style=flat-square&logo=npm" /></a>
|
|
7
|
+
<a href="https://opencode.ai/" target="_blank" rel="noopener noreferrer"><img alt="OpenCode 2" src="https://img.shields.io/badge/OpenCode-2-5A67D8?style=flat-square" /></a>
|
|
8
|
+
<a href="https://github.com/chenlongapps/opencode-token-usage/actions/workflows/ci.yml" target="_blank" rel="noopener noreferrer"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/chenlongapps/opencode-token-usage/ci.yml?style=flat-square&branch=main&label=ci" /></a>
|
|
9
|
+
<a href="LICENSE" target="_blank" rel="noopener noreferrer"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-yellow?style=flat-square" /></a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -50,7 +50,7 @@ For remote sessions, add the package name to `plugins` in your local `~/.config/
|
|
|
50
50
|
| `Total` | Sum of all five token categories |
|
|
51
51
|
| `Context` | Latest context usage after the most recent completed compaction in the viewed session; not aggregated across the subtree |
|
|
52
52
|
| `Steps` | Assistant message count across the session tree, including subagents; follows OpenCode's own stats definition, so compaction and user messages are not steps |
|
|
53
|
-
| `Cost` | Estimated cost
|
|
53
|
+
| `Cost` | Estimated cost across the tree, pricing each assistant and compaction call with its actual model |
|
|
54
54
|
| `TPS` | Generation throughput for `Output + Reasoning` across the tree; live estimates are marked with `~` |
|
|
55
55
|
| `TTFT` | Average time to first token across measurable assistant steps in the tree |
|
|
56
56
|
|
|
@@ -60,7 +60,9 @@ For remote sessions, add the package name to `plugins` in your local `~/.config/
|
|
|
60
60
|
- A fork is a separate session tree. Inherited message copies are attributed only to their original source to prevent double counting.
|
|
61
61
|
- `Context` only searches messages after the most recent compaction with `status === "completed"` and is hidden when reliable usage or a model context limit is unavailable.
|
|
62
62
|
- `Steps` counts every assistant message in the tree, whether or not it reported usage, and reuses the fork-copy de-duplication so inherited history is never counted twice.
|
|
63
|
-
- `Cost`
|
|
63
|
+
- `Cost` prices every message with its recorded model. A complete applicable price resolved by OpenCode takes precedence; when OpenCode has no usable price, the plugin falls back to a packaged snapshot of manufacturer prices.
|
|
64
|
+
- Gateway models can use manufacturer fallback prices through exact model IDs and documented aliases. The fallback does not include gateway markups, regional premiums, discounts, tool fees, or taxes, so Cost is an estimate rather than a provider bill.
|
|
65
|
+
- Confirmed free usage displays `$0.00`; unavailable prices display `—`; known subtotals with unpriced messages are marked `partial`. See the [built-in price snapshot](docs/pricing.md) for coverage, sources, and limitations.
|
|
64
66
|
- Initial read failures display `Unavailable`. Later failures retain the last complete snapshot, display `Not updated`, and retry automatically.
|
|
65
67
|
|
|
66
68
|
### Development
|
|
@@ -73,7 +75,7 @@ npm run build
|
|
|
73
75
|
npm run test:smoke
|
|
74
76
|
```
|
|
75
77
|
|
|
76
|
-
`test:smoke` packages the real artifact and validates loading, refreshes, subagent aggregation, model switching, TPS, and TTFT against an isolated OpenCode instance and a local mock provider. It requires Python 3, an available local port, and npm network access. It never modifies your existing OpenCode configuration or calls paid models.
|
|
78
|
+
`test:smoke` packages the real artifact and validates loading, refreshes, subagent aggregation, per-message pricing, official-price fallback, model switching, TPS, and TTFT against an isolated OpenCode instance and a local mock provider. It requires Python 3, an available local port, and npm network access. It never modifies your existing OpenCode configuration or calls paid models.
|
|
77
79
|
|
|
78
80
|
To load the plugin from source, build the project and add the repository's absolute path to `plugins` in the target project.
|
|
79
81
|
|
package/README.zh-CN.md
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
<p align="center">面向 OpenCode 2 的会话树 token 用量监视器。</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://www.npmjs.com/package/@chenlongapps/opencode-token-usage"><img alt="npm" src="https://img.shields.io/npm/v/%40chenlongapps%2Fopencode-token-usage?style=flat-square&logo=npm" /></a>
|
|
7
|
-
<a href="https://opencode.ai/"><img alt="OpenCode 2" src="https://img.shields.io/badge/OpenCode-2-5A67D8?style=flat-square" /></a>
|
|
8
|
-
<a href="https://github.com/chenlongapps/opencode-token-usage/actions/workflows/ci.yml"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/chenlongapps/opencode-token-usage/ci.yml?style=flat-square&branch=main&label=ci" /></a>
|
|
9
|
-
<a href="LICENSE"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-yellow?style=flat-square" /></a>
|
|
6
|
+
<a href="https://www.npmjs.com/package/@chenlongapps/opencode-token-usage" target="_blank" rel="noopener noreferrer"><img alt="npm" src="https://img.shields.io/npm/v/%40chenlongapps%2Fopencode-token-usage?style=flat-square&logo=npm" /></a>
|
|
7
|
+
<a href="https://opencode.ai/" target="_blank" rel="noopener noreferrer"><img alt="OpenCode 2" src="https://img.shields.io/badge/OpenCode-2-5A67D8?style=flat-square" /></a>
|
|
8
|
+
<a href="https://github.com/chenlongapps/opencode-token-usage/actions/workflows/ci.yml" target="_blank" rel="noopener noreferrer"><img alt="CI" src="https://img.shields.io/github/actions/workflow/status/chenlongapps/opencode-token-usage/ci.yml?style=flat-square&branch=main&label=ci" /></a>
|
|
9
|
+
<a href="LICENSE" target="_blank" rel="noopener noreferrer"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-yellow?style=flat-square" /></a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -50,7 +50,7 @@ opencode plugin add @chenlongapps/opencode-token-usage
|
|
|
50
50
|
| `Total` | 五类 token 之和 |
|
|
51
51
|
| `Context` | 当前会话最后一次已完成压缩后的最新上下文用量,不随子树累计 |
|
|
52
52
|
| `Steps` | 全树(含子代理)的 assistant 消息数,口径与 OpenCode 自带统计一致,compaction 与用户消息不计为 step |
|
|
53
|
-
| `Cost` |
|
|
53
|
+
| `Cost` | 按每条 assistant 与 compaction 消息实际模型估算的全树费用 |
|
|
54
54
|
| `TPS` | 全树 `Output + Reasoning` 的生成速度;生成中的估算值带 `~` 标记 |
|
|
55
55
|
| `TTFT` | 全树中可测 assistant step 的平均首 token 时间 |
|
|
56
56
|
|
|
@@ -60,7 +60,9 @@ opencode plugin add @chenlongapps/opencode-token-usage
|
|
|
60
60
|
- 分叉会话是独立会话树。继承消息副本只归原始来源,避免重复计费。
|
|
61
61
|
- `Context` 仅搜索最后一次 `status === "completed"` 的 compaction 之后;没有可靠用量或模型上限时隐藏。
|
|
62
62
|
- `Steps` 统计树中全部 assistant 消息,无论是否已上报用量;复用分叉副本去重,继承历史不会重复计数。
|
|
63
|
-
- `Cost`
|
|
63
|
+
- `Cost` 按每条消息记录的实际模型分别计算。OpenCode 当前解析出的完整适用价格优先;OpenCode 没有可用价格时,回退到插件内置的厂商官方价格快照。
|
|
64
|
+
- 网关模型可通过精确模型 ID 和已记录别名使用厂商价格,但不包含网关加价、区域溢价、折扣、工具费和税费,因此 Cost 是估算而非提供商账单。
|
|
65
|
+
- 确认免费时显示 `$0.00`;价格不可用时显示 `—`;只有部分消息可计算时在已知小计后标注 `partial`。覆盖范围、来源和限制见[内置官方价格快照](docs/pricing.md)。
|
|
64
66
|
- 首次读取失败显示 `Unavailable`;后续失败保留上次完整快照、标注 `Not updated` 并自动重试。
|
|
65
67
|
|
|
66
68
|
### 开发
|
|
@@ -73,7 +75,7 @@ npm run build
|
|
|
73
75
|
npm run test:smoke
|
|
74
76
|
```
|
|
75
77
|
|
|
76
|
-
`test:smoke` 会打包真实产物,并在隔离的 OpenCode
|
|
78
|
+
`test:smoke` 会打包真实产物,并在隔离的 OpenCode 与本地模拟提供商中验证加载、刷新、子代理累计、逐消息计价、官方价格补全、模型切换、TPS 和 TTFT。它需要 Python 3、可用的本地端口和 npm 网络访问,不会修改现有 OpenCode 配置或调用付费模型。
|
|
77
79
|
|
|
78
80
|
从源码加载时,先构建项目,再将仓库绝对路径加入目标项目的 `plugins`。
|
|
79
81
|
|
package/ROADMAP.md
CHANGED
|
@@ -68,13 +68,19 @@ TTFT 2.7s
|
|
|
68
68
|
Steps 1,234
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
## v0.3.0 —
|
|
71
|
+
## v0.3.0 — 官方价格补全
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
目标:按每条消息实际使用的模型统计费用,并在 OpenCode 缺少价格时使用厂商官方价格补全。
|
|
74
74
|
|
|
75
|
-
* [
|
|
76
|
-
* [
|
|
77
|
-
|
|
75
|
+
* [x] 内置可追溯的厂商官方价格快照
|
|
76
|
+
* [x] 首版收录最近半年发布且具有明确标准 token 定价的模型
|
|
77
|
+
* [x] OpenCode 完整适用价格优先,缺失时整条消息回退到官方价格
|
|
78
|
+
* [x] 按 assistant 与 compaction 消息实际模型分别计算
|
|
79
|
+
* [x] 支持价格阶梯、缓存读写和 Reasoning 费用
|
|
80
|
+
* [x] 支持网关模型到厂商模型的精确 ID 与明确别名映射
|
|
81
|
+
* [x] 区分免费、部分可计算和价格不可用
|
|
82
|
+
* [x] 已收录模型永久保留,后续持续核验价格和来源
|
|
83
|
+
* [x] 通过单元测试与 OpenCode 2.0.11 打包集成验证
|
|
78
84
|
|
|
79
85
|
## v0.4.0 — 用量命令
|
|
80
86
|
|
|
@@ -88,5 +94,4 @@ Steps 1,234
|
|
|
88
94
|
|
|
89
95
|
## 后续评估
|
|
90
96
|
|
|
91
|
-
* [ ] 公开价格目录与更新策略
|
|
92
97
|
* [x] 首次 npm 发布:`@chenlongapps/opencode-token-usage@0.2.1`
|
package/dist/controller.js
CHANGED
|
@@ -137,7 +137,7 @@ export class UsageController {
|
|
|
137
137
|
this.performanceTimer = undefined;
|
|
138
138
|
this.update({
|
|
139
139
|
status: "ready",
|
|
140
|
-
summary: summarize(messages, snapshot.model.
|
|
140
|
+
summary: summarize(messages, snapshot.model.catalog),
|
|
141
141
|
context: contextUsage(viewedMessages(snapshot), snapshot.model.context),
|
|
142
142
|
performance: this.performance.summary(messages, this.sessions),
|
|
143
143
|
model: snapshot.model.label,
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { ModelRef, Price } from "./usage.js";
|
|
2
|
+
export interface OfficialPriceEntry {
|
|
3
|
+
/** Canonical model ID used by the model manufacturer. */
|
|
4
|
+
id: string;
|
|
5
|
+
/** First public release date, used to enforce the snapshot's inclusion window. */
|
|
6
|
+
released: string;
|
|
7
|
+
/** Exact alternate IDs used by supported gateways or manufacturer aliases. */
|
|
8
|
+
aliases?: readonly string[];
|
|
9
|
+
/** Standard, synchronous API token rates in USD per million tokens. */
|
|
10
|
+
prices: readonly Price[];
|
|
11
|
+
/** Manufacturer pricing page used to verify the rates. */
|
|
12
|
+
source: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const OFFICIAL_PRICE_SNAPSHOT: {
|
|
15
|
+
readonly verified: "2026-09-23";
|
|
16
|
+
readonly newModelCutoff: "2026-03-22";
|
|
17
|
+
readonly entries: ({
|
|
18
|
+
id: string;
|
|
19
|
+
released: string;
|
|
20
|
+
source: string;
|
|
21
|
+
prices: ({
|
|
22
|
+
input: number;
|
|
23
|
+
output: number;
|
|
24
|
+
cache: {
|
|
25
|
+
read: number;
|
|
26
|
+
};
|
|
27
|
+
tier?: never;
|
|
28
|
+
} | {
|
|
29
|
+
tier: {
|
|
30
|
+
type: "context";
|
|
31
|
+
size: number;
|
|
32
|
+
};
|
|
33
|
+
input: number;
|
|
34
|
+
output: number;
|
|
35
|
+
cache: {
|
|
36
|
+
read: number;
|
|
37
|
+
};
|
|
38
|
+
})[];
|
|
39
|
+
aliases?: never;
|
|
40
|
+
} | {
|
|
41
|
+
id: string;
|
|
42
|
+
released: string;
|
|
43
|
+
source: string;
|
|
44
|
+
prices: ({
|
|
45
|
+
input: number;
|
|
46
|
+
output: number;
|
|
47
|
+
tier?: never;
|
|
48
|
+
} | {
|
|
49
|
+
tier: {
|
|
50
|
+
type: "context";
|
|
51
|
+
size: number;
|
|
52
|
+
};
|
|
53
|
+
input: number;
|
|
54
|
+
output: number;
|
|
55
|
+
})[];
|
|
56
|
+
aliases?: never;
|
|
57
|
+
} | {
|
|
58
|
+
id: string;
|
|
59
|
+
released: string;
|
|
60
|
+
source: string;
|
|
61
|
+
prices: ({
|
|
62
|
+
input: number;
|
|
63
|
+
output: number;
|
|
64
|
+
cache: {
|
|
65
|
+
read: number;
|
|
66
|
+
write: number;
|
|
67
|
+
};
|
|
68
|
+
tier?: never;
|
|
69
|
+
} | {
|
|
70
|
+
tier: {
|
|
71
|
+
type: "context";
|
|
72
|
+
size: number;
|
|
73
|
+
};
|
|
74
|
+
input: number;
|
|
75
|
+
output: number;
|
|
76
|
+
cache: {
|
|
77
|
+
read: number;
|
|
78
|
+
write: number;
|
|
79
|
+
};
|
|
80
|
+
})[];
|
|
81
|
+
aliases?: never;
|
|
82
|
+
} | {
|
|
83
|
+
id: string;
|
|
84
|
+
aliases: string[];
|
|
85
|
+
released: string;
|
|
86
|
+
source: string;
|
|
87
|
+
prices: {
|
|
88
|
+
input: number;
|
|
89
|
+
output: number;
|
|
90
|
+
cache: {
|
|
91
|
+
read: number;
|
|
92
|
+
write: number;
|
|
93
|
+
};
|
|
94
|
+
}[];
|
|
95
|
+
} | {
|
|
96
|
+
id: string;
|
|
97
|
+
aliases: string[];
|
|
98
|
+
released: string;
|
|
99
|
+
source: string;
|
|
100
|
+
prices: ({
|
|
101
|
+
input: number;
|
|
102
|
+
output: number;
|
|
103
|
+
tier?: never;
|
|
104
|
+
} | {
|
|
105
|
+
tier: {
|
|
106
|
+
type: "context";
|
|
107
|
+
size: number;
|
|
108
|
+
};
|
|
109
|
+
input: number;
|
|
110
|
+
output: number;
|
|
111
|
+
})[];
|
|
112
|
+
} | {
|
|
113
|
+
id: string;
|
|
114
|
+
aliases: string[];
|
|
115
|
+
released: string;
|
|
116
|
+
source: string;
|
|
117
|
+
prices: ({
|
|
118
|
+
input: number;
|
|
119
|
+
output: number;
|
|
120
|
+
cache: {
|
|
121
|
+
read: number;
|
|
122
|
+
};
|
|
123
|
+
tier?: never;
|
|
124
|
+
} | {
|
|
125
|
+
tier: {
|
|
126
|
+
type: "context";
|
|
127
|
+
size: number;
|
|
128
|
+
};
|
|
129
|
+
input: number;
|
|
130
|
+
output: number;
|
|
131
|
+
cache: {
|
|
132
|
+
read: number;
|
|
133
|
+
};
|
|
134
|
+
})[];
|
|
135
|
+
})[];
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Resolves exact manufacturer IDs through common gateway wrappers. It never
|
|
139
|
+
* performs substring or model-name matching, so similarly named variants do
|
|
140
|
+
* not silently inherit a different model's price.
|
|
141
|
+
*/
|
|
142
|
+
export declare function officialPrice(model: ModelRef): OfficialPriceEntry | undefined;
|