@chenlongapps/opencode-token-usage 0.4.1 → 0.4.2

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 CHANGED
@@ -83,9 +83,9 @@ Costs follow the sidebar's pricing and `partial`/unavailable/free conventions. C
83
83
  - A fork is a separate session tree. Inherited message copies are attributed only to their original source to prevent double counting.
84
84
  - `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.
85
85
  - `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.
86
- - `Est. Cost` prices every message with its recorded model. A complete non-zero price resolved by OpenCode takes precedence. If OpenCode reports a complete zero price, a complete official snapshot price overrides it; incomplete prices fall back for the whole message without mixing rates.
87
- - Gateway models can use fallback prices through exact model IDs, documented aliases, and known wrappers. A terminal `-free` or `:free` is removed only for an exact base ID lookup; other suffixes are not stripped. The fallback excludes gateway markups, regional premiums, unlisted discounts, tool fees, and taxes, so Est. Cost is an estimate rather than a provider bill.
88
- - 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.
86
+ - `Est. Cost` prices every message with its recorded model. A complete non-zero price resolved by OpenCode takes precedence. If OpenCode reports a complete zero price, a complete first-party snapshot price overrides it; incomplete prices fall back for the whole message without mixing rates.
87
+ - The checked-in fallback snapshot is generated from [models.dev](https://models.dev/api.json) using only reviewed first-party provider/model families; a small set of manufacturer-verified exceptions is kept separately. It covers priced text models, without downloading prices while the plugin runs. Gateway models match exact manufacturer IDs, documented aliases, and known wrappers; only a terminal `-free` or `:free` can be removed for a second exact lookup.
88
+ - Confirmed free usage displays `$0.00`; unavailable prices display `—`; known subtotals with unpriced messages are marked `partial`. The snapshot excludes gateway markups, regional premiums, unlisted discounts, non-text billing, tool fees, and taxes, so Est. Cost is not a provider bill. See [price sources and limitations](docs/pricing.md).
89
89
  - Initial read failures display `Unavailable`. Later failures retain the last complete snapshot, display `Not updated`, and retry automatically.
90
90
 
91
91
  ### Development
@@ -98,7 +98,11 @@ npm run build
98
98
  npm run test:smoke
99
99
  ```
100
100
 
101
- `test:smoke` packages the real artifact and validates loading, refreshes, `/usage`, 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.
101
+ The [price update workflow](.github/workflows/update-prices.yml) checks models.dev every day at 03:17 UTC (or on demand via **Run workflow**). No snapshot diff means no pull request; a diff that passes typecheck, tests, build, and pack validation opens or updates one review-only PR containing `src/prices.generated.ts`. Enable **Allow GitHub Actions to create and approve pull requests** under repository Actions settings. PRs created with `GITHUB_TOKEN` do not trigger a second CI run, so the update workflow runs the checks before opening the PR. Nothing is merged or published automatically.
102
+
103
+ For a manual refresh, run `npm run prices:update` in a networked environment, review the generated diff and exceptions, then run the checks above. Builds and plugin refreshes do not contact models.dev.
104
+
105
+ `test:smoke` packages the real artifact and validates loading, refreshes, `/usage`, subagent aggregation, per-message pricing, first-party 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.
102
106
 
103
107
  To load the plugin from source, build the project and add the repository's absolute path to `plugins` in the target project.
104
108
 
package/README.zh-CN.md CHANGED
@@ -84,8 +84,8 @@ opencode plugin add @chenlongapps/opencode-token-usage
84
84
  - `Context` 仅搜索最后一次 `status === "completed"` 的 compaction 之后;没有可靠用量或模型上限时隐藏。
85
85
  - `Steps` 统计树中全部 assistant 消息,无论是否已上报用量;复用分叉副本去重,继承历史不会重复计数。
86
86
  - `Est. Cost` 按每条消息记录的实际模型分别计算。OpenCode 当前解析出的完整非零价格优先;若 OpenCode 给出完整零价,且内置价格快照可以完整计价,则采用快照价格。价格不完整时整条消息回退,不混用两套费率。
87
- - 网关模型通过精确模型 ID、明确别名和已知包装格式匹配;也会尝试移除终尾 `-free` 或 `:free` 后精确查找基础 ID,其他后缀不会剥离。快照不包含网关加价、区域溢价、未列明的折扣、工具费和税费,因此 Est. Cost 是估算而非提供商账单。
88
- - 确认免费时显示 `$0.00`;价格不可用时显示 `—`;只有部分消息可计算时在已知小计后标注 `partial`。覆盖范围、来源和限制见[内置官方价格快照](docs/pricing.md)。
87
+ - 内置回退快照从 [models.dev](https://models.dev/api.json) 生成,只采用审核过的原厂 Provider 与自家模型系列;少量经原厂核实的例外单独维护。快照覆盖有价格的文本模型,插件运行时不下载价格。网关模型通过精确原厂 ID、明确别名和已知包装格式匹配;只有终尾 `-free` 或 `:free` 会在再次精确查找前移除。
88
+ - 确认免费时显示 `$0.00`;价格不可用时显示 `—`;只有部分消息可计算时在已知小计后标注 `partial`。快照不含网关加价、区域溢价、未列明的折扣、非文本计费、工具费和税费;Est. Cost 是估算而非账单。覆盖范围、来源和限制见[价格来源与限制](docs/pricing.md)。
89
89
  - 首次读取失败显示 `Unavailable`;后续失败保留上次完整快照、标注 `Not updated` 并自动重试。
90
90
 
91
91
  ### 开发
@@ -98,7 +98,11 @@ npm run build
98
98
  npm run test:smoke
99
99
  ```
100
100
 
101
- `test:smoke` 会打包真实产物,并在隔离的 OpenCode 与本地模拟提供商中验证加载、刷新、`/usage`、子代理累计、逐消息计价、官方价格补全、模型切换、TPS 和 TTFT。它需要 Python 3、可用的本地端口和 npm 网络访问,不会修改现有 OpenCode 配置或调用付费模型。
101
+ [价格更新工作流](.github/workflows/update-prices.yml) 每天 UTC 03:17 检查 models.dev,也可通过 **Run workflow** 手动触发。快照无变化就不创建 PR;有变化且通过类型检查、测试、构建和打包检查时,只对 `src/prices.generated.ts` 创建或更新同一个待审 PR。需在仓库 Actions 设置中启用 **Allow GitHub Actions to create and approve pull requests**。使用 `GITHUB_TOKEN` 创建的 PR 不会再次触发 CI,因此更新工作流会先完成检查;不会自动合并或发布。
102
+
103
+ 手动更新时,在联网的维护环境运行 `npm run prices:update`,审阅生成文件及例外差异,再执行上述检查。构建和插件刷新不会请求 models.dev。
104
+
105
+ `test:smoke` 会打包真实产物,并在隔离的 OpenCode 与本地模拟提供商中验证加载、刷新、`/usage`、子代理累计、逐消息计价、原厂价格补全、模型切换、TPS 和 TTFT。它需要 Python 3、可用的本地端口和 npm 网络访问,不会修改现有 OpenCode 配置或调用付费模型。
102
106
 
103
107
  从源码加载时,先构建项目,再将仓库绝对路径加入目标项目的 `plugins`。
104
108
 
package/ROADMAP.md CHANGED
@@ -104,3 +104,7 @@ Steps 1,234
104
104
  ## 后续评估
105
105
 
106
106
  * [x] 首次 npm 发布:`@chenlongapps/opencode-token-usage@0.2.1`
107
+
108
+ ## 待解决问题
109
+
110
+ * [ ] TPS 的实时计算:当前为近似值,后续考虑通过更精确的算法计算
@@ -0,0 +1,6 @@
1
+ /** Gateway slugs that differ from the manufacturer provider ID. The matching
2
+ * code handles identical slugs, Bedrock/Vertex wrappers and terminal free
3
+ * suffixes without entries in this table. */
4
+ export declare const PROVIDER_ALIASES: Readonly<Record<string, string>>;
5
+ /** Exact SKU/version differences, not fuzzy model-name matching. */
6
+ export declare const MODEL_ALIASES: Readonly<Record<string, string>>;
@@ -0,0 +1,32 @@
1
+ /** Gateway slugs that differ from the manufacturer provider ID. The matching
2
+ * code handles identical slugs, Bedrock/Vertex wrappers and terminal free
3
+ * suffixes without entries in this table. */
4
+ export const PROVIDER_ALIASES = {
5
+ "arcee-ai": "arcee",
6
+ "bytedance-seed": "volcengine",
7
+ "meituan": "longcat",
8
+ "mistralai": "mistral",
9
+ "qwen": "alibaba",
10
+ "stepfun": "stepfun-ai",
11
+ "tencent": "tencent-tokenhub",
12
+ "x-ai": "xai",
13
+ "z-ai": "zai",
14
+ };
15
+ /** Exact SKU/version differences, not fuzzy model-name matching. */
16
+ export const MODEL_ALIASES = {
17
+ "alibaba/qwen3.5-plus-20260420": "alibaba/qwen3.5-plus-2026-04-20",
18
+ "alibaba/qwen3.8-max-0902": "alibaba/qwen3.8-max",
19
+ "deepseek/deepseek-flash": "deepseek/deepseek-v4.1-flash",
20
+ "deepseek/deepseek-v4-flash": "deepseek/deepseek-v4.1-flash",
21
+ "deepseek/deepseek-v4-flash-0731": "deepseek/deepseek-v4.1-flash",
22
+ "deepseek/deepseek-v4-flash-vision-exp": "deepseek/deepseek-v4.1-flash",
23
+ "deepseek/deepseek-v4-pro": "deepseek/deepseek-v4-pro-0813",
24
+ "mistral/mistral-medium-3-5": "mistral/mistral-medium-2604",
25
+ "mistral/mistral-medium-3-5-26-04": "mistral/mistral-medium-2604",
26
+ "openai/gpt-chat-latest": "openai/chat-latest",
27
+ "sakana/fugu-max": "sakana/fugu-max-v1.0",
28
+ "sakana/fugu-ultra-v2": "sakana/fugu-ultra-v2.0",
29
+ "upstage/solar-pro4-260806": "upstage/solar-pro4",
30
+ "volcengine/seed-2-1-turbo": "volcengine/dola-seed-2-1-turbo",
31
+ "volcengine/dola-seed-2-1-turbo-260628": "volcengine/dola-seed-2-1-turbo",
32
+ };
@@ -0,0 +1,22 @@
1
+ import type { Price } from "./usage.js";
2
+ /** models.dev has no manufacturer-of-model field. Only these first-party
3
+ * catalogs and model families can supply fallback prices. A manufacturer
4
+ * listing somebody else's model (e.g. Alibaba's Kimi) is not its origin. */
5
+ export declare const ORIGINAL_PROVIDERS: Readonly<Record<string, RegExp>>;
6
+ export interface PriceOverride {
7
+ providerID: string;
8
+ id: string;
9
+ prices: readonly Price[];
10
+ source: string;
11
+ reason: string;
12
+ released?: string;
13
+ /** Exceptional SKUs such as Fast are only available at the first party. */
14
+ providers?: readonly string[];
15
+ }
16
+ /** Rates absent from the first-party models.dev listing or contradicted by a
17
+ * specifically verified manufacturer tariff. Never use a reseller's quote. */
18
+ export declare const OVERRIDES_VERIFIED = "2026-09-23";
19
+ export declare const PRICE_OVERRIDES: readonly PriceOverride[];
20
+ /** A catalog zero may mean an unmetered subscription, or that cache storage is
21
+ * billed by time rather than per write token. Do not invent a free write rate. */
22
+ export declare function adjustOriginalPrice(providerID: string, price: Price): Price;
@@ -0,0 +1,181 @@
1
+ /** models.dev has no manufacturer-of-model field. Only these first-party
2
+ * catalogs and model families can supply fallback prices. A manufacturer
3
+ * listing somebody else's model (e.g. Alibaba's Kimi) is not its origin. */
4
+ export const ORIGINAL_PROVIDERS = {
5
+ ai21: /^jamba-/i,
6
+ alibaba: /^(?:qwen|qwq|qvq)/i,
7
+ anthropic: /^claude-/i,
8
+ arcee: /^trinity-/i,
9
+ cohere: /^(?:command-|north-)/i,
10
+ deepseek: /^deepseek-/i,
11
+ google: /^(?:gemini-|gemma-|deep-research-)/i,
12
+ inception: /^mercury-/i,
13
+ longcat: /^longcat-/i,
14
+ meta: /^muse-/i,
15
+ minimax: /^minimax-/i,
16
+ mistral: /^(?:open-mistral-|open-mixtral-|mistral-|ministral-|magistral-|codestral-|devstral-|labs-devstral-|pixtral-|voxtral-)/i,
17
+ moonshotai: /^kimi-/i,
18
+ nvidia: /^nvidia\/(?:nemotron-|llama-[\w.-]*nemotron-)/i,
19
+ openai: /^(?:gpt-|o\d|chat-latest$)/i,
20
+ perplexity: /^sonar(?:-|$)/i,
21
+ sakana: /^(?:fugu|sakana-namazu)/i,
22
+ "stepfun-ai": /^step-/i,
23
+ // TokenHub's hy3 zero is a subscription-plan quote, not a token tariff;
24
+ // hy3 pay-as-you-go is a reviewed exception below.
25
+ "tencent-tokenhub": /^hy4-preview$/i,
26
+ upstage: /^solar-/i,
27
+ volcengine: /^doubao-seed-/i,
28
+ xai: /^grok-/i,
29
+ xiaomi: /^mimo-/i,
30
+ zai: /^glm-/i,
31
+ };
32
+ /** Rates absent from the first-party models.dev listing or contradicted by a
33
+ * specifically verified manufacturer tariff. Never use a reseller's quote. */
34
+ export const OVERRIDES_VERIFIED = "2026-09-23";
35
+ export const PRICE_OVERRIDES = [
36
+ {
37
+ providerID: "openai", id: "chat-latest", released: "2026-05-05",
38
+ source: "https://developers.openai.com/api/docs/pricing", reason: "Official ChatGPT API SKU missing from the first-party catalog",
39
+ prices: [{ input: 5, output: 30, cache: { read: 0.5 } }],
40
+ },
41
+ // The first-party API records Fast as a mode, but not its long-context
42
+ // schedule. Keep the published Fast schedule restricted to direct API IDs.
43
+ ...[
44
+ ["gpt-5.5", 12.5, 75, 1.25, undefined, undefined, undefined, undefined, undefined],
45
+ ["gpt-5.6-sol", 8, 40, 0.8, 10, 16, 60, 1.6, 20],
46
+ ["gpt-5.6-terra", 4, 24, 0.4, 5, 8, 36, 0.8, 10],
47
+ ["gpt-5.6-luna", 0.4, 2.4, 0.04, 0.5, 0.8, 3.6, 0.08, 1],
48
+ ["gpt-6-astra", 20, 100, 2, 25, 40, 150, 4, 50],
49
+ ["gpt-6-sol", 4, 20, 0.4, 5, 8, 30, 0.8, 10],
50
+ ["gpt-6-luna", 0.2, 1, 0.02, 0.25, 0.4, 1.5, 0.04, 0.5],
51
+ ].map(([id, input, output, read, write, longInput, longOutput, longRead, longWrite]) => ({
52
+ providerID: "openai", id: `${id}-fast`, providers: ["openai"],
53
+ source: "https://developers.openai.com/api/docs/pricing",
54
+ reason: "Published first-party Fast rate and long-context boundary (not in mode.cost)",
55
+ prices: [
56
+ { input, output, cache: { read, ...(write === undefined ? {} : { write }) } },
57
+ { tier: { type: "context", size: 272_000 },
58
+ ...(longInput === undefined ? {} : { input: longInput, output: longOutput, cache: { read: longRead, write: longWrite } }) },
59
+ ],
60
+ })),
61
+ {
62
+ providerID: "deepseek", id: "deepseek-v4.1-flash", released: "2026-09-10",
63
+ source: "https://api-docs.deepseek.com/quick_start/pricing",
64
+ reason: "Peak synchronous price; the hourly billing tier is not recorded in historical messages",
65
+ prices: [{ input: 0.3, output: 1.2, cache: { read: 0.006 } }],
66
+ },
67
+ {
68
+ providerID: "deepseek", id: "deepseek-v4-pro-0813", released: "2026-08-13",
69
+ source: "https://api-docs.deepseek.com/quick_start/pricing",
70
+ reason: "Peak synchronous price; the hourly billing tier is not recorded in historical messages",
71
+ prices: [{ input: 1.32, output: 3.96, cache: { read: 0.044 } }],
72
+ },
73
+ {
74
+ providerID: "moonshotai", id: "kimi-k3",
75
+ source: "https://platform.moonshot.ai/docs/pricing",
76
+ reason: "Published five-minute cache-write rate omitted by models.dev",
77
+ prices: [{ input: 3, output: 15, cache: { read: 0.3, write: 3 } }],
78
+ },
79
+ {
80
+ providerID: "stepfun-ai", id: "step-5-preview",
81
+ source: "https://platform.stepfun.ai/docs/en/guides/pricing/details",
82
+ reason: "Cache-miss input includes new cache writes; the API omits the write rate",
83
+ prices: [{ input: 1, output: 2.7, cache: { read: 0.05, write: 1 } }],
84
+ },
85
+ {
86
+ providerID: "tencent-tokenhub", id: "hy3", released: "2026-07-06",
87
+ source: "https://intl.cloud.tencent.com/document/product/1300/78937",
88
+ reason: "Published pay-as-you-go tariff, rather than the catalog's subscription-plan zero",
89
+ prices: [{ input: 0.132, output: 0.528, cache: { read: 0.033 } }],
90
+ },
91
+ {
92
+ providerID: "longcat", id: "longcat-2.0",
93
+ source: "https://longcat.chat/platform/docs/Pricing/LongCat-2.0.html",
94
+ reason: "Manufacturer's billed promotion, verified 2026-09-23; review on refresh",
95
+ prices: [{ input: 0.3, output: 1.2, cache: { read: 0.006 } }],
96
+ },
97
+ {
98
+ providerID: "mistral", id: "mistral-medium-2604",
99
+ source: "https://docs.mistral.ai/inference/pricing",
100
+ reason: "Manufacturer cache-hit price missing from models.dev",
101
+ prices: [{ input: 1.5, output: 7.5, cache: { read: 0.15 } }],
102
+ },
103
+ {
104
+ providerID: "alibaba", id: "qwen3.7-flash", released: "2026-07-15",
105
+ source: "https://www.alibabacloud.com/help/en/model-studio/model-pricing",
106
+ reason: "International pay-as-you-go SKU absent from the international catalog (not the CN tariff)",
107
+ prices: [
108
+ { input: 0.03, output: 0.13, cache: { read: 0.006 } },
109
+ { tier: { type: "context", size: 32_000 }, input: 0.1, output: 0.4, cache: { read: 0.02 } },
110
+ { tier: { type: "context", size: 256_000 }, input: 0.2, output: 0.8, cache: { read: 0.04 } },
111
+ ],
112
+ },
113
+ {
114
+ providerID: "alibaba", id: "qwen3.8-2.4t-a95b", released: "2026-08-12",
115
+ source: "https://www.alibabacloud.com/help/en/model-studio/model-pricing",
116
+ reason: "Manufacturer's international tariff; this SKU is absent from the international catalog",
117
+ prices: [{ input: 2, output: 6 }],
118
+ },
119
+ {
120
+ providerID: "alibaba", id: "qwen3.8-27b", released: "2026-08-14",
121
+ source: "https://www.alibabacloud.com/help/en/model-studio/model-pricing",
122
+ reason: "Manufacturer's international tariff; this SKU is absent from the international catalog",
123
+ prices: [{ input: 0.5, output: 3, cache: { read: 0.1 } }],
124
+ },
125
+ {
126
+ providerID: "alibaba", id: "qwen3.5-plus-2026-04-20", released: "2026-04-20",
127
+ source: "https://www.alibabacloud.com/help/en/model-studio/model-pricing",
128
+ reason: "Manufacturer's international tariff; this SKU is absent from the international catalog",
129
+ prices: [
130
+ { input: 0.4, output: 2.4 },
131
+ { tier: { type: "context", size: 256_000 }, input: 0.5, output: 3 },
132
+ ],
133
+ },
134
+ {
135
+ providerID: "volcengine", id: "dola-seed-2-1-turbo", released: "2026-06-23",
136
+ source: "https://docs.byteplus.com/en/docs/ModelArk/1099320",
137
+ reason: "BytePlus manufacturer SKU uses a different ID from the regional Volcengine catalog",
138
+ prices: [{ input: 0.5, output: 2.5, cache: { read: 0.1 } }],
139
+ },
140
+ {
141
+ providerID: "sakana", id: "fugu-ultra-v2.0", released: "2026-09-11",
142
+ source: "https://console.sakana.ai/pricing",
143
+ reason: "Manufacturer versioned SKU not present under this ID; higher tier starts at 272K",
144
+ prices: [
145
+ { input: 5, output: 30, cache: { read: 0.5 } },
146
+ { tier: { type: "context", size: 271_999 }, input: 10, output: 45, cache: { read: 1 } },
147
+ ],
148
+ },
149
+ {
150
+ providerID: "sakana", id: "fugu-max-v1.0", released: "2026-09-11",
151
+ source: "https://console.sakana.ai/pricing",
152
+ reason: "Manufacturer versioned SKU absent from models.dev",
153
+ prices: [{ input: 2, output: 6, cache: { read: 0.25 } }],
154
+ },
155
+ ...[
156
+ ["aion-3.0", "2026-05-05", 3, 6, 0.75],
157
+ ["aion-3.0-mini", "2026-05-14", 0.7, 1.4, 0.18],
158
+ ].map(([id, released, input, output, read]) => ({
159
+ providerID: "aion-labs", id, released,
160
+ source: "https://www.aionlabs.ai/docs/models",
161
+ reason: "First-party hosted API price; models.dev has no Aion manufacturer provider",
162
+ prices: [{ input, output, cache: { read } }],
163
+ })),
164
+ ];
165
+ /** A catalog zero may mean an unmetered subscription, or that cache storage is
166
+ * billed by time rather than per write token. Do not invent a free write rate. */
167
+ export function adjustOriginalPrice(providerID, price) {
168
+ const ambiguousWrite = (providerID === "zai" && price.cache?.write === 0)
169
+ || (providerID === "alibaba" && price.cache?.write !== undefined);
170
+ if (ambiguousWrite && price.cache) {
171
+ // Z.ai stores cache by time; Alibaba mixes implicit and explicit writes
172
+ // at distinct rates, which OpenCode's aggregate counter cannot separate.
173
+ const { write: _unrepresentable, ...cache } = price.cache;
174
+ return { ...price, cache };
175
+ }
176
+ if (providerID === "xai" && price.tier?.size === 200_000) {
177
+ // xAI's >=200K bracket begins at 200,000; OpenCode tiers are exclusive.
178
+ return { ...price, tier: { type: "context", size: 199_999 } };
179
+ }
180
+ return price;
181
+ }