@chenlongapps/opencode-token-usage 0.4.6 → 0.4.7
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 +5 -3
- package/README.zh-CN.md +5 -3
- package/dist/context-capture.d.ts +5 -3
- package/dist/context-capture.js +31 -14
- package/dist/controller.d.ts +2 -2
- package/dist/controller.js +7 -5
- package/dist/performance.js +5 -3
- package/dist/tui.js +227 -206
- package/dist/usage.d.ts +6 -4
- package/dist/usage.js +11 -8
- package/docs/verification.md +64 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,9 +58,11 @@ The dialog is split into five sections with separate statistics:
|
|
|
58
58
|
|
|
59
59
|
One line under the title names the session and its active model, and the body never repeats the model name. Compact mode uses `K`/`M` abbreviations (`812`, `139.4K`, `3.70M`), hides empty rows and merges the two tool families in Context Breakdown into a single `Tools` row. Detailed mode shows exact numbers, empty rows, and the `System Tools` / `MCP Tools` split. The sidebar keeps exact numbers and its own layout.
|
|
60
60
|
|
|
61
|
-
Costs follow the sidebar's pricing and `partial`/unavailable/free conventions. In detailed mode, By Model shows the applied Input, Output, Reasoning, Cache Read and Cache Write rates in USD per million text tokens. Each rate group names its source (OpenCode or the built-in snapshot), incoming-token tier and call count; a model can have multiple groups. `Incoming` means Input + Cache Read + Cache Write. Missing rates show `—`, confirmed free rates show `$0.00`, and unpriced calls are counted separately. These are the current rates used to estimate recorded calls, not historical provider bills.
|
|
61
|
+
Costs follow the sidebar's pricing and `partial`/unavailable/free conventions. In detailed mode, By Model shows the applied Input, Output, Reasoning, Cache Read and Cache Write rates in USD per million text tokens. Each rate group names its source (OpenCode or the built-in snapshot), incoming-token tier and call count; a model can have multiple groups. `Incoming` means Input + Cache Read + Cache Write. Missing rates show `—`, confirmed free rates show `$0.00`, and unpriced calls are counted separately. These are the current rates used to estimate recorded calls, not historical provider bills.
|
|
62
62
|
|
|
63
|
-
**Context
|
|
63
|
+
**Context Window** requires reported usage and a valid active-model context limit. **Last Request** shows the selected assistant call's reported tokens even when that limit is unknown or source estimates are unavailable. Its timestamp comes from that same message: stream end, completion, then creation time, using the first valid value and hiding the timestamp if none is available. Request selection follows the viewed session's revert cutoff and most recent completed compaction boundary.
|
|
64
|
+
|
|
65
|
+
**Context Breakdown** separates Messages, System Tools, System Prompt, Skills, MCP Tools, and Other. It estimates the text and tool definitions in the viewed session's **latest assembled model request**; its percentages use the sum of those estimates. Its timestamp is the estimate's own capture time, which may refer to a different request than Last Request. These are not provider-reported token counts and do not add up to the measured context window, which also includes output and reasoning. Media and provider-specific framing cannot be measured from the request. A server plugin records only aggregate estimates (not prompt text); sessions without a captured request or an available server RPC show “Source estimates unavailable”.
|
|
64
66
|
|
|
65
67
|
### Metrics
|
|
66
68
|
|
|
@@ -103,7 +105,7 @@ The OpenCode SDK packages are pinned to `2.0.24`, with OpenTUI `0.5.14`. Clean i
|
|
|
103
105
|
|
|
104
106
|
The [price update workflow](.github/workflows/update-prices.yml) checks models.dev every day at 22:00 UTC (or on demand via **Run workflow** on `main`). No snapshot diff means no release. Changes within the reviewed first-party source allowlist pass typecheck, tests, build, and pack validation, then automatically bump the patch version (for example, `0.4.4` → `0.4.5`), commit only `src/prices.generated.ts`, `package.json`, and `package-lock.json`, and atomically push `main` and its version tag. The workflow creates a GitHub Release and explicitly dispatches [`publish.yml`](.github/workflows/publish.yml) on that tag to publish via npm Trusted Publishing/OIDC; no long-lived npm token is needed. GitHub Actions must be allowed to push to `main` and create tags, and npm must trust `publish.yml` with permission to run `npm publish`.
|
|
105
107
|
|
|
106
|
-
Failed validation never bumps the version. An unfinished tagged price release is retried with the same version before checking new prices; registry outages are not treated as missing versions. Publishing skips versions already on npm, prevents moving `latest` backwards, and
|
|
108
|
+
Failed validation never bumps the version. An unfinished tagged price release is retried with the same version before checking new prices; registry outages are not treated as missing versions. Publishing skips versions already on npm, prevents moving `latest` backwards, and polls registry visibility for up to 10 minutes after publication, logging progress. A verification timeout is not proof that `npm publish` failed: check the registry and retry the same tag without bumping the version; already-published versions are not republished. Manufacturer/source allowlist additions, aliases, and manually verified price exceptions still require human review. See the [release guide](docs/releasing.md) for setup, verification timing, and retries.
|
|
107
109
|
|
|
108
110
|
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.
|
|
109
111
|
|
package/README.zh-CN.md
CHANGED
|
@@ -58,9 +58,11 @@ opencode plugin add @chenlongapps/opencode-token-usage
|
|
|
58
58
|
|
|
59
59
|
标题下方一行注明会话与当前活动模型,正文不再重复模型名。紧凑模式使用 `K`/`M` 缩写(`812`、`139.4K`、`3.70M`),隐藏零值行,并把 Context Breakdown 中的两类工具合并为一行 `Tools`;详细模式显示精确数字、零值行及 `System Tools` / `MCP Tools` 拆分。侧边栏仍使用精确数字和原有布局。
|
|
60
60
|
|
|
61
|
-
费用沿用侧边栏的价格及 `partial`、不可定价、免费口径。详细模式下,By Model 显示 Input、Output、Reasoning、Cache Read、Cache Write 的美元/百万文本 token 费率。每组费率注明来源(OpenCode 或内置快照)、传入 token 档位和调用数;同一模型可能有多组。传入 token 为 Input + Cache Read + Cache Write。缺失费率显示 `—`,确认免费显示 `$0.00
|
|
61
|
+
费用沿用侧边栏的价格及 `partial`、不可定价、免费口径。详细模式下,By Model 显示 Input、Output、Reasoning、Cache Read、Cache Write 的美元/百万文本 token 费率。每组费率注明来源(OpenCode 或内置快照)、传入 token 档位和调用数;同一模型可能有多组。传入 token 为 Input + Cache Read + Cache Write。缺失费率显示 `—`,确认免费显示 `$0.00`,无法定价的调用单独计数。这些是当前用于估算已记录调用的费率,不是历史提供商账单。
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
**Context Window** 需要已上报用量和有效的活动模型上下文上限。**Last Request** 展示所选 assistant 调用的实测 token,上限未知或来源估算不可用时仍可显示。其时间取自同一条消息,依次选用有效的流式结束、完成、创建时间;均不可用时隐藏时间。请求选择遵循被查看会话的回退截断和最后一次已完成压缩边界。
|
|
64
|
+
|
|
65
|
+
弹窗中的 **Context Breakdown** 将消息、系统工具、系统提示词、技能、MCP 工具及其他分开展示。它根据被查看会话**最近一次已组装的模型请求**中的文本和工具定义估算,占比的分母是六项估算值之和。其时间为该估算自身的采样时间,可能与 Last Request 对应不同请求。这些不是提供商上报的精确 token,也不会与上方含输出、推理的实测上下文窗口相加一致。媒体内容与提供商额外包装无法准确计量。服务端插件仅保存分类数值,不保存提示词正文;未捕获到请求或服务端 RPC 不可用时显示“来源估算不可用”。
|
|
64
66
|
|
|
65
67
|
### 指标
|
|
66
68
|
|
|
@@ -103,7 +105,7 @@ OpenCode SDK 包精确锁定为 `2.0.24`,OpenTUI 为 `0.5.14`。Node.js 22 下
|
|
|
103
105
|
|
|
104
106
|
[价格更新工作流](.github/workflows/update-prices.yml) 每天 UTC 22:00 检查 models.dev,也可在 `main` 上通过 **Run workflow** 手动触发。快照无变化就不发布。经审核的原厂来源白名单内有变化时,先通过类型检查、测试、构建和打包检查,再自动升级 patch 版本(例如 `0.4.4` → `0.4.5`),只提交 `src/prices.generated.ts`、`package.json` 和 `package-lock.json`,并原子推送 `main` 与版本标签。工作流创建 GitHub Release,显式触发该标签上的 [`publish.yml`](.github/workflows/publish.yml),通过 npm Trusted Publishing/OIDC 发布,无需长期 npm token。仓库必须允许 GitHub Actions 推送 `main` 和创建标签;npm 必须信任 `publish.yml` 并允许执行 `npm publish`。
|
|
105
107
|
|
|
106
|
-
验证失败不会升版本。已打标签但未完成的自动价格发布会优先重试同一版本,再检查新价格;registry 故障不会被当作版本不存在。发布时跳过 npm 已有版本、防止 `latest`
|
|
108
|
+
验证失败不会升版本。已打标签但未完成的自动价格发布会优先重试同一版本,再检查新价格;registry 故障不会被当作版本不存在。发布时跳过 npm 已有版本、防止 `latest` 倒退,并在发布后最多轮询 10 分钟核验 registry 可见性,输出进度。校验超时不代表 `npm publish` 失败:先检查 registry,再重试原标签,不另升版本;已发布版本不会重复发布。新增原厂/来源白名单、别名及人工核实的价格例外仍需人工审核。配置、校验时限和重试方法见[发布说明](docs/releasing.md)。
|
|
107
109
|
|
|
108
110
|
手动更新时,在联网的维护环境运行 `npm run prices:update`,审阅生成文件及例外差异,再执行上述检查。构建和插件刷新不会请求 models.dev。
|
|
109
111
|
|
|
@@ -3,20 +3,21 @@ import type { Plugin } from "@opencode/plugin";
|
|
|
3
3
|
import type { ContextSources } from "./context-sources.js";
|
|
4
4
|
type Request = Pick<SessionContext, "sessionID" | "model" | "system" | "messages" | "tools">;
|
|
5
5
|
type Mcp = {
|
|
6
|
-
list(): Promise<{
|
|
6
|
+
list(...args: Parameters<Plugin.Context["mcp"]["list"]>): Promise<{
|
|
7
7
|
data: readonly {
|
|
8
8
|
name: string;
|
|
9
9
|
}[];
|
|
10
10
|
}>;
|
|
11
11
|
};
|
|
12
12
|
type Storage = Pick<Plugin.Context["storage"], "get" | "set" | "remove">;
|
|
13
|
-
/**
|
|
13
|
+
/** MCP metadata waits are bounded; persistence never delays the model request. */
|
|
14
14
|
export declare class ContextCapture {
|
|
15
15
|
private readonly mcp;
|
|
16
16
|
private readonly storage;
|
|
17
17
|
private readonly ttl;
|
|
18
18
|
private readonly now;
|
|
19
19
|
private readonly elapsed;
|
|
20
|
+
private readonly mcpTimeoutMs;
|
|
20
21
|
private names;
|
|
21
22
|
private expiresAt;
|
|
22
23
|
private listing;
|
|
@@ -31,8 +32,9 @@ export declare class ContextCapture {
|
|
|
31
32
|
storageWrites: number;
|
|
32
33
|
storageWriteMs: number;
|
|
33
34
|
};
|
|
34
|
-
constructor(mcp: Mcp, storage: Storage, ttl?: number, now?: () => number, elapsed?: () => number);
|
|
35
|
+
constructor(mcp: Mcp, storage: Storage, ttl?: number, now?: () => number, elapsed?: () => number, mcpTimeoutMs?: number);
|
|
35
36
|
private servers;
|
|
37
|
+
private readServers;
|
|
36
38
|
invalidateMcp(): void;
|
|
37
39
|
capture(request: Request): Promise<void>;
|
|
38
40
|
private record;
|
package/dist/context-capture.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { estimateContextSources, parseContextSources } from "./context-sources.js";
|
|
2
|
-
/**
|
|
2
|
+
/** MCP metadata waits are bounded; persistence never delays the model request. */
|
|
3
3
|
export class ContextCapture {
|
|
4
4
|
mcp;
|
|
5
5
|
storage;
|
|
6
6
|
ttl;
|
|
7
7
|
now;
|
|
8
8
|
elapsed;
|
|
9
|
+
mcpTimeoutMs;
|
|
9
10
|
names;
|
|
10
11
|
expiresAt = 0;
|
|
11
12
|
listing;
|
|
@@ -14,32 +15,48 @@ export class ContextCapture {
|
|
|
14
15
|
latestEstimate = new Map();
|
|
15
16
|
deleted = new Set();
|
|
16
17
|
timings = { requests: 0, mcpReads: 0, mcpWaitMs: 0, storageWrites: 0, storageWriteMs: 0 };
|
|
17
|
-
constructor(mcp, storage, ttl = 30_000, now = Date.now, elapsed = () => performance.now()) {
|
|
18
|
+
constructor(mcp, storage, ttl = 30_000, now = Date.now, elapsed = () => performance.now(), mcpTimeoutMs = 100) {
|
|
18
19
|
this.mcp = mcp;
|
|
19
20
|
this.storage = storage;
|
|
20
21
|
this.ttl = ttl;
|
|
21
22
|
this.now = now;
|
|
22
23
|
this.elapsed = elapsed;
|
|
24
|
+
this.mcpTimeoutMs = mcpTimeoutMs;
|
|
23
25
|
}
|
|
24
26
|
async servers() {
|
|
25
27
|
if (this.names && this.now() < this.expiresAt)
|
|
26
28
|
return this.names;
|
|
27
29
|
if (!this.listing) {
|
|
28
30
|
this.timings.mcpReads++;
|
|
29
|
-
this.listing = this.
|
|
30
|
-
this.names = result.data.map(server => server.name);
|
|
31
|
-
this.expiresAt = this.now() + this.ttl;
|
|
32
|
-
return this.names;
|
|
33
|
-
}).catch(error => {
|
|
34
|
-
if (this.names) {
|
|
35
|
-
this.expiresAt = this.now() + this.ttl;
|
|
36
|
-
return this.names;
|
|
37
|
-
}
|
|
38
|
-
throw error;
|
|
39
|
-
}).finally(() => { this.listing = undefined; });
|
|
31
|
+
this.listing = this.readServers().finally(() => { this.listing = undefined; });
|
|
40
32
|
}
|
|
41
33
|
return this.listing;
|
|
42
34
|
}
|
|
35
|
+
async readServers() {
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
let timer;
|
|
38
|
+
try {
|
|
39
|
+
const timeout = new Promise((_, reject) => {
|
|
40
|
+
timer = setTimeout(() => {
|
|
41
|
+
const error = new DOMException("MCP metadata read timed out", "TimeoutError");
|
|
42
|
+
reject(error);
|
|
43
|
+
controller.abort(error);
|
|
44
|
+
}, this.mcpTimeoutMs);
|
|
45
|
+
});
|
|
46
|
+
const result = await Promise.race([this.mcp.list(undefined, { signal: controller.signal }), timeout]);
|
|
47
|
+
// Only the bounded read may update the cache, even if cancellation is ignored.
|
|
48
|
+
this.names = result.data.map(server => server.name);
|
|
49
|
+
this.expiresAt = this.now() + this.ttl;
|
|
50
|
+
return this.names;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Reuse stale metadata without renewing it, so the next request can retry.
|
|
54
|
+
return this.names;
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
43
60
|
invalidateMcp() { this.expiresAt = 0; }
|
|
44
61
|
capture(request) {
|
|
45
62
|
const tasks = this.inflight.get(request.sessionID) ?? new Set();
|
|
@@ -64,7 +81,7 @@ export class ContextCapture {
|
|
|
64
81
|
this.timings.requests++;
|
|
65
82
|
this.timings.mcpWaitMs += this.elapsed() - start;
|
|
66
83
|
}
|
|
67
|
-
if (this.deleted.has(request.sessionID))
|
|
84
|
+
if (!names || this.deleted.has(request.sessionID))
|
|
68
85
|
return;
|
|
69
86
|
const value = estimateContextSources(request, names);
|
|
70
87
|
this.latestEstimate.set(request.sessionID, value);
|
package/dist/controller.d.ts
CHANGED
|
@@ -2,13 +2,13 @@ import type { UsageSource } from "./source.js";
|
|
|
2
2
|
import { PerformanceMonitor } from "./performance.js";
|
|
3
3
|
import type { PerformanceSummary } from "./performance.js";
|
|
4
4
|
import type { ContextSources } from "./context-sources.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { ContextUsage, ModelCost, RequestDetails, Summary } from "./usage.js";
|
|
6
6
|
export interface UsageState {
|
|
7
7
|
status: "loading" | "ready" | "stale" | "unavailable";
|
|
8
8
|
summary?: Summary;
|
|
9
9
|
context?: ContextUsage | undefined;
|
|
10
10
|
details?: {
|
|
11
|
-
|
|
11
|
+
request?: RequestDetails | undefined;
|
|
12
12
|
models: readonly ModelCost[];
|
|
13
13
|
sources?: ContextSources | undefined;
|
|
14
14
|
sessionTitle?: string | undefined;
|
package/dist/controller.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadSnapshot, uniqueMessages, viewedMessages } from "./source.js";
|
|
2
2
|
import { PerformanceMonitor, preparePerformance } from "./performance.js";
|
|
3
|
-
import {
|
|
3
|
+
import { contextUsage, requestDetails, summarize, summarizeModels } from "./usage.js";
|
|
4
4
|
const sessionEvents = new Set([
|
|
5
5
|
"session.created", "session.deleted", "session.forked", "session.moved",
|
|
6
6
|
"session.model.selected", "session.agent.selected", "session.step.ended", "session.step.failed",
|
|
@@ -164,18 +164,20 @@ export class UsageController {
|
|
|
164
164
|
this.prepared = preparePerformance(messages);
|
|
165
165
|
clearTimeout(this.performanceTimer);
|
|
166
166
|
this.performanceTimer = undefined;
|
|
167
|
-
const
|
|
167
|
+
const history = viewedMessages(snapshot);
|
|
168
|
+
const session = snapshot.sessions.get(snapshot.viewedID);
|
|
169
|
+
const revertMessageID = session.revert?.messageID;
|
|
168
170
|
this.update({
|
|
169
171
|
status: "ready",
|
|
170
172
|
summary: summarize(messages, snapshot.model.catalog),
|
|
171
|
-
context: context
|
|
172
|
-
...(this.detailed ? { details: {
|
|
173
|
+
context: contextUsage(history, snapshot.model.context, revertMessageID),
|
|
174
|
+
...(this.detailed ? { details: { request: requestDetails(history, revertMessageID), models: summarizeModels(messages, snapshot.model.catalog), sessionTitle: session.title } } : {}),
|
|
173
175
|
performance: this.performance.summaryPrepared(this.prepared, this.sessions),
|
|
174
176
|
model: snapshot.model.label,
|
|
175
177
|
});
|
|
176
178
|
// The server plugin may be absent. An optional RPC must not delay measured usage.
|
|
177
179
|
if (this.detailed && this.source.composition) {
|
|
178
|
-
void this.source.composition(
|
|
180
|
+
void this.source.composition(session, AbortSignal.any([request.signal, AbortSignal.timeout(5_000)]))
|
|
179
181
|
.then(sources => {
|
|
180
182
|
if (this.disposed || generation !== this.generation || this.snapshot !== snapshot || this.state.status !== "ready" || !this.state.details)
|
|
181
183
|
return;
|
package/dist/performance.js
CHANGED
|
@@ -262,6 +262,10 @@ export class PerformanceMonitor {
|
|
|
262
262
|
const cutoff = event.created - LIVE_WINDOW_MS;
|
|
263
263
|
while (step.samples.length > 0 && step.samples[0].time < cutoff)
|
|
264
264
|
step.samples.shift();
|
|
265
|
+
// Advance smoothing once per accepted delta, independently of summary readers.
|
|
266
|
+
const raw = estimatedLiveTps(step.samples);
|
|
267
|
+
if (raw !== undefined)
|
|
268
|
+
step.liveTps = smoothLiveTps(step.liveTps, raw);
|
|
265
269
|
step.last = Math.max(step.last ?? event.created, event.created);
|
|
266
270
|
if (step.first === undefined) {
|
|
267
271
|
step.first = event.created;
|
|
@@ -309,15 +313,13 @@ export class PerformanceMonitor {
|
|
|
309
313
|
for (const step of this.active.values()) {
|
|
310
314
|
if (!sessions.has(step.sessionID) || step.reconciledAt !== undefined)
|
|
311
315
|
continue;
|
|
312
|
-
|
|
313
|
-
if (raw === undefined)
|
|
316
|
+
if (step.liveTps === undefined || step.samples.length < 2)
|
|
314
317
|
continue;
|
|
315
318
|
const first = step.samples[0];
|
|
316
319
|
const last = step.samples[step.samples.length - 1];
|
|
317
320
|
const duration = last.time - first.time;
|
|
318
321
|
if (!finite(duration) || duration <= 0)
|
|
319
322
|
continue;
|
|
320
|
-
step.liveTps = smoothLiveTps(step.liveTps, raw);
|
|
321
323
|
weighted += step.liveTps * duration;
|
|
322
324
|
durationTotal += duration;
|
|
323
325
|
}
|
package/dist/tui.js
CHANGED
|
@@ -297,7 +297,12 @@ function UsageDialog(props) {
|
|
|
297
297
|
}]
|
|
298
298
|
}));
|
|
299
299
|
const theme = () => props.context.theme.text;
|
|
300
|
-
const context = createMemo(() => state().
|
|
300
|
+
const context = createMemo(() => state().context);
|
|
301
|
+
const request = createMemo(() => state().details?.request);
|
|
302
|
+
const requestTime = createMemo(() => {
|
|
303
|
+
const time = request()?.time;
|
|
304
|
+
return time === undefined ? undefined : new Date(time).toLocaleTimeString();
|
|
305
|
+
});
|
|
301
306
|
const models = createMemo(() => state().details?.models ?? []);
|
|
302
307
|
const sources = createMemo(() => state().details?.sources);
|
|
303
308
|
// The host gives this dialog a fixed width; keep every row inside that content box.
|
|
@@ -400,12 +405,12 @@ function UsageDialog(props) {
|
|
|
400
405
|
_el$30 = _$createElement("text"),
|
|
401
406
|
_el$31 = _$createElement("b"),
|
|
402
407
|
_el$33 = _$createElement("text"),
|
|
403
|
-
_el$
|
|
404
|
-
_el$
|
|
405
|
-
_el$
|
|
406
|
-
_el$
|
|
408
|
+
_el$63 = _$createElement("box"),
|
|
409
|
+
_el$64 = _$createElement("text"),
|
|
410
|
+
_el$66 = _$createElement("text"),
|
|
411
|
+
_el$68 = _$createElement("text");
|
|
407
412
|
_$insertNode(_el$28, _el$29);
|
|
408
|
-
_$insertNode(_el$28, _el$
|
|
413
|
+
_$insertNode(_el$28, _el$63);
|
|
409
414
|
_$setProp(_el$28, "paddingLeft", 2);
|
|
410
415
|
_$setProp(_el$28, "paddingRight", 2);
|
|
411
416
|
_$setProp(_el$28, "paddingBottom", 1);
|
|
@@ -430,7 +435,7 @@ function UsageDialog(props) {
|
|
|
430
435
|
_$effect(_$p => _$setProp(_el$35, "fg", theme().muted, _$p));
|
|
431
436
|
return _el$35;
|
|
432
437
|
}
|
|
433
|
-
}), _el$
|
|
438
|
+
}), _el$63);
|
|
434
439
|
_$insert(_el$28, _$createComponent(Show, {
|
|
435
440
|
get when() {
|
|
436
441
|
return state().status !== "ready";
|
|
@@ -441,7 +446,7 @@ function UsageDialog(props) {
|
|
|
441
446
|
_$effect(_$p => _$setProp(_el$36, "fg", theme().muted, _$p));
|
|
442
447
|
return _el$36;
|
|
443
448
|
}
|
|
444
|
-
}), _el$
|
|
449
|
+
}), _el$63);
|
|
445
450
|
_$insert(_el$28, _$createComponent(Show, {
|
|
446
451
|
get when() {
|
|
447
452
|
return state().summary;
|
|
@@ -457,14 +462,15 @@ function UsageDialog(props) {
|
|
|
457
462
|
_el$45 = _$createElement("text"),
|
|
458
463
|
_el$46 = _$createElement("b"),
|
|
459
464
|
_el$48 = _$createElement("box"),
|
|
460
|
-
_el$49 = _$createElement("
|
|
461
|
-
_el$50 = _$createElement("
|
|
462
|
-
_el$
|
|
463
|
-
_el$53 = _$createElement("
|
|
464
|
-
_el$54 = _$createElement("
|
|
465
|
-
_el$
|
|
466
|
-
_el$57 = _$createElement("
|
|
467
|
-
_el$58 = _$createElement("
|
|
465
|
+
_el$49 = _$createElement("box"),
|
|
466
|
+
_el$50 = _$createElement("text"),
|
|
467
|
+
_el$51 = _$createElement("b"),
|
|
468
|
+
_el$53 = _$createElement("box"),
|
|
469
|
+
_el$54 = _$createElement("text"),
|
|
470
|
+
_el$55 = _$createElement("b"),
|
|
471
|
+
_el$57 = _$createElement("box"),
|
|
472
|
+
_el$58 = _$createElement("text"),
|
|
473
|
+
_el$59 = _$createElement("b");
|
|
468
474
|
_$insertNode(_el$37, _el$38);
|
|
469
475
|
_$use(value => {
|
|
470
476
|
scroll = value;
|
|
@@ -478,8 +484,8 @@ function UsageDialog(props) {
|
|
|
478
484
|
_$insertNode(_el$38, _el$39);
|
|
479
485
|
_$insertNode(_el$38, _el$43);
|
|
480
486
|
_$insertNode(_el$38, _el$48);
|
|
481
|
-
_$insertNode(_el$38, _el$
|
|
482
|
-
_$insertNode(_el$38, _el$
|
|
487
|
+
_$insertNode(_el$38, _el$53);
|
|
488
|
+
_$insertNode(_el$38, _el$57);
|
|
483
489
|
_$setProp(_el$38, "gap", 1);
|
|
484
490
|
_$insertNode(_el$39, _el$40);
|
|
485
491
|
_$insertNode(_el$40, _el$41);
|
|
@@ -490,48 +496,48 @@ function UsageDialog(props) {
|
|
|
490
496
|
},
|
|
491
497
|
get fallback() {
|
|
492
498
|
return (() => {
|
|
493
|
-
var _el$
|
|
494
|
-
_$insertNode(_el$
|
|
495
|
-
_$effect(_$p => _$setProp(_el$
|
|
496
|
-
return _el$
|
|
499
|
+
var _el$70 = _$createElement("text");
|
|
500
|
+
_$insertNode(_el$70, _$createTextNode(`Context usage unavailable`));
|
|
501
|
+
_$effect(_$p => _$setProp(_el$70, "fg", theme().muted, _$p));
|
|
502
|
+
return _el$70;
|
|
497
503
|
})();
|
|
498
504
|
},
|
|
499
505
|
children: details => (() => {
|
|
500
|
-
var _el$
|
|
501
|
-
_el$
|
|
502
|
-
_el$
|
|
503
|
-
_el$
|
|
504
|
-
_el$
|
|
505
|
-
_el$
|
|
506
|
-
_el$
|
|
507
|
-
_$insertNode(_el$71, _el$72);
|
|
506
|
+
var _el$72 = _$createElement("box"),
|
|
507
|
+
_el$73 = _$createElement("box"),
|
|
508
|
+
_el$74 = _$createElement("text"),
|
|
509
|
+
_el$76 = _$createElement("text"),
|
|
510
|
+
_el$77 = _$createTextNode(` / `),
|
|
511
|
+
_el$78 = _$createTextNode(` (`),
|
|
512
|
+
_el$79 = _$createTextNode(`%)`);
|
|
508
513
|
_$insertNode(_el$72, _el$73);
|
|
509
|
-
_$insertNode(_el$
|
|
510
|
-
_$
|
|
511
|
-
_$setProp(_el$
|
|
512
|
-
_$setProp(_el$
|
|
513
|
-
_$
|
|
514
|
-
_$
|
|
515
|
-
_$
|
|
516
|
-
_$insertNode(_el$
|
|
517
|
-
_$insertNode(_el$
|
|
518
|
-
_$
|
|
519
|
-
_$setProp(_el$
|
|
520
|
-
_$
|
|
521
|
-
_$insert(_el$
|
|
522
|
-
_$insert(_el$
|
|
523
|
-
_$insert(_el$
|
|
514
|
+
_$insertNode(_el$73, _el$74);
|
|
515
|
+
_$insertNode(_el$73, _el$76);
|
|
516
|
+
_$setProp(_el$73, "flexDirection", "row");
|
|
517
|
+
_$setProp(_el$73, "justifyContent", "space-between");
|
|
518
|
+
_$setProp(_el$73, "gap", 2);
|
|
519
|
+
_$insertNode(_el$74, _$createTextNode(`Used / Limit`));
|
|
520
|
+
_$setProp(_el$74, "flexShrink", 0);
|
|
521
|
+
_$insertNode(_el$76, _el$77);
|
|
522
|
+
_$insertNode(_el$76, _el$78);
|
|
523
|
+
_$insertNode(_el$76, _el$79);
|
|
524
|
+
_$setProp(_el$76, "wrapMode", "word");
|
|
525
|
+
_$setProp(_el$76, "minWidth", 0);
|
|
526
|
+
_$insert(_el$76, () => number(details().used), _el$77);
|
|
527
|
+
_$insert(_el$76, () => number(details().limit), _el$78);
|
|
528
|
+
_$insert(_el$76, () => details().percent.toFixed(1), _el$79);
|
|
529
|
+
_$insert(_el$72, () => barRow(details().percent, windowBar()), null);
|
|
524
530
|
_$effect(_p$ => {
|
|
525
531
|
var _v$21 = theme().muted,
|
|
526
532
|
_v$22 = theme().base;
|
|
527
|
-
_v$21 !== _p$.e && (_p$.e = _$setProp(_el$
|
|
528
|
-
_v$22 !== _p$.t && (_p$.t = _$setProp(_el$
|
|
533
|
+
_v$21 !== _p$.e && (_p$.e = _$setProp(_el$74, "fg", _v$21, _p$.e));
|
|
534
|
+
_v$22 !== _p$.t && (_p$.t = _$setProp(_el$76, "fg", _v$22, _p$.t));
|
|
529
535
|
return _p$;
|
|
530
536
|
}, {
|
|
531
537
|
e: undefined,
|
|
532
538
|
t: undefined
|
|
533
539
|
});
|
|
534
|
-
return _el$
|
|
540
|
+
return _el$72;
|
|
535
541
|
})()
|
|
536
542
|
}), null);
|
|
537
543
|
_$insertNode(_el$43, _el$44);
|
|
@@ -543,46 +549,61 @@ function UsageDialog(props) {
|
|
|
543
549
|
_$insertNode(_el$46, _$createTextNode(`Last Request`));
|
|
544
550
|
_$insert(_el$44, _$createComponent(Show, {
|
|
545
551
|
get when() {
|
|
546
|
-
return
|
|
552
|
+
return requestTime();
|
|
547
553
|
},
|
|
548
|
-
children:
|
|
549
|
-
var _el$
|
|
550
|
-
_$insert(_el$
|
|
551
|
-
_$effect(_$p => _$setProp(_el$
|
|
552
|
-
return _el$
|
|
554
|
+
children: time => (() => {
|
|
555
|
+
var _el$80 = _$createElement("text");
|
|
556
|
+
_$insert(_el$80, time);
|
|
557
|
+
_$effect(_$p => _$setProp(_el$80, "fg", theme().muted, _$p));
|
|
558
|
+
return _el$80;
|
|
553
559
|
})()
|
|
554
560
|
}), null);
|
|
555
561
|
_$insert(_el$43, _$createComponent(Show, {
|
|
556
562
|
get when() {
|
|
557
|
-
return
|
|
563
|
+
return request();
|
|
558
564
|
},
|
|
559
565
|
get fallback() {
|
|
560
566
|
return (() => {
|
|
561
|
-
var _el$
|
|
562
|
-
_$insertNode(_el$
|
|
563
|
-
_$effect(_$p => _$setProp(_el$
|
|
564
|
-
return _el$
|
|
567
|
+
var _el$81 = _$createElement("text");
|
|
568
|
+
_$insertNode(_el$81, _$createTextNode(`Request usage unavailable`));
|
|
569
|
+
_$effect(_$p => _$setProp(_el$81, "fg", theme().muted, _$p));
|
|
570
|
+
return _el$81;
|
|
565
571
|
})();
|
|
566
572
|
},
|
|
567
573
|
children: details => (() => {
|
|
568
|
-
var _el$
|
|
569
|
-
_$insert(_el$
|
|
570
|
-
return _el$
|
|
574
|
+
var _el$83 = _$createElement("box");
|
|
575
|
+
_$insert(_el$83, () => requestRowsList(requestRows(details(), compact())));
|
|
576
|
+
return _el$83;
|
|
571
577
|
})()
|
|
572
578
|
}), null);
|
|
573
579
|
_$insertNode(_el$48, _el$49);
|
|
574
580
|
_$insertNode(_el$49, _el$50);
|
|
575
|
-
_$
|
|
581
|
+
_$setProp(_el$49, "flexDirection", "row");
|
|
582
|
+
_$setProp(_el$49, "justifyContent", "space-between");
|
|
583
|
+
_$setProp(_el$49, "gap", 2);
|
|
584
|
+
_$insertNode(_el$50, _el$51);
|
|
585
|
+
_$insertNode(_el$51, _$createTextNode(`Context Breakdown`));
|
|
586
|
+
_$insert(_el$49, _$createComponent(Show, {
|
|
587
|
+
get when() {
|
|
588
|
+
return sources();
|
|
589
|
+
},
|
|
590
|
+
children: captured => (() => {
|
|
591
|
+
var _el$84 = _$createElement("text");
|
|
592
|
+
_$insert(_el$84, () => new Date(captured().capturedAt).toLocaleTimeString());
|
|
593
|
+
_$effect(_$p => _$setProp(_el$84, "fg", theme().muted, _$p));
|
|
594
|
+
return _el$84;
|
|
595
|
+
})()
|
|
596
|
+
}), null);
|
|
576
597
|
_$insert(_el$48, _$createComponent(Show, {
|
|
577
598
|
get when() {
|
|
578
599
|
return sources();
|
|
579
600
|
},
|
|
580
601
|
get fallback() {
|
|
581
602
|
return (() => {
|
|
582
|
-
var _el$
|
|
583
|
-
_$insertNode(_el$
|
|
584
|
-
_$effect(_$p => _$setProp(_el$
|
|
585
|
-
return _el$
|
|
603
|
+
var _el$85 = _$createElement("text");
|
|
604
|
+
_$insertNode(_el$85, _$createTextNode(`Source estimates unavailable (no request snapshot or RPC)`));
|
|
605
|
+
_$effect(_$p => _$setProp(_el$85, "fg", theme().muted, _$p));
|
|
606
|
+
return _el$85;
|
|
586
607
|
})();
|
|
587
608
|
},
|
|
588
609
|
children: captured => _$createComponent(For, {
|
|
@@ -590,39 +611,39 @@ function UsageDialog(props) {
|
|
|
590
611
|
return breakdownRows(captured(), detailed());
|
|
591
612
|
},
|
|
592
613
|
children: row => (() => {
|
|
593
|
-
var _el$
|
|
594
|
-
_el$86 = _$createElement("box"),
|
|
595
|
-
_el$87 = _$createElement("text"),
|
|
614
|
+
var _el$87 = _$createElement("box"),
|
|
596
615
|
_el$88 = _$createElement("box"),
|
|
597
616
|
_el$89 = _$createElement("text"),
|
|
598
|
-
_el$90 = _$createElement("
|
|
599
|
-
_el$91 = _$
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
_$insertNode(_el$
|
|
603
|
-
_$insertNode(_el$
|
|
604
|
-
_$
|
|
605
|
-
_$
|
|
606
|
-
_$
|
|
607
|
-
_$setProp(_el$
|
|
608
|
-
_$
|
|
609
|
-
_$
|
|
617
|
+
_el$90 = _$createElement("box"),
|
|
618
|
+
_el$91 = _$createElement("text"),
|
|
619
|
+
_el$92 = _$createElement("text"),
|
|
620
|
+
_el$93 = _$createTextNode(`%`);
|
|
621
|
+
_$insertNode(_el$87, _el$88);
|
|
622
|
+
_$insertNode(_el$87, _el$90);
|
|
623
|
+
_$insertNode(_el$87, _el$91);
|
|
624
|
+
_$insertNode(_el$87, _el$92);
|
|
625
|
+
_$setProp(_el$87, "flexDirection", "row");
|
|
626
|
+
_$setProp(_el$87, "gap", 1);
|
|
627
|
+
_$insertNode(_el$88, _el$89);
|
|
628
|
+
_$setProp(_el$88, "width", 14);
|
|
610
629
|
_$setProp(_el$88, "flexShrink", 0);
|
|
611
|
-
_$insert(_el$
|
|
612
|
-
_$setProp(_el$89, "flexShrink", 0);
|
|
613
|
-
_$insert(_el$89, () => number(row.tokens));
|
|
614
|
-
_$insertNode(_el$90, _el$91);
|
|
630
|
+
_$insert(_el$89, () => row.label);
|
|
615
631
|
_$setProp(_el$90, "flexShrink", 0);
|
|
616
|
-
_$insert(_el$90, () => row.percent
|
|
632
|
+
_$insert(_el$90, () => barRow(row.percent, breakdownBar()));
|
|
633
|
+
_$setProp(_el$91, "flexShrink", 0);
|
|
634
|
+
_$insert(_el$91, () => number(row.tokens));
|
|
635
|
+
_$insertNode(_el$92, _el$93);
|
|
636
|
+
_$setProp(_el$92, "flexShrink", 0);
|
|
637
|
+
_$insert(_el$92, () => row.percent.toFixed(1), _el$93);
|
|
617
638
|
_$effect(_p$ => {
|
|
618
639
|
var _v$23 = theme().muted,
|
|
619
640
|
_v$24 = breakdownBar(),
|
|
620
641
|
_v$25 = theme().base,
|
|
621
642
|
_v$26 = theme().muted;
|
|
622
|
-
_v$23 !== _p$.e && (_p$.e = _$setProp(_el$
|
|
623
|
-
_v$24 !== _p$.t && (_p$.t = _$setProp(_el$
|
|
624
|
-
_v$25 !== _p$.a && (_p$.a = _$setProp(_el$
|
|
625
|
-
_v$26 !== _p$.o && (_p$.o = _$setProp(_el$
|
|
643
|
+
_v$23 !== _p$.e && (_p$.e = _$setProp(_el$89, "fg", _v$23, _p$.e));
|
|
644
|
+
_v$24 !== _p$.t && (_p$.t = _$setProp(_el$90, "width", _v$24, _p$.t));
|
|
645
|
+
_v$25 !== _p$.a && (_p$.a = _$setProp(_el$91, "fg", _v$25, _p$.a));
|
|
646
|
+
_v$26 !== _p$.o && (_p$.o = _$setProp(_el$92, "fg", _v$26, _p$.o));
|
|
626
647
|
return _p$;
|
|
627
648
|
}, {
|
|
628
649
|
e: undefined,
|
|
@@ -630,23 +651,23 @@ function UsageDialog(props) {
|
|
|
630
651
|
a: undefined,
|
|
631
652
|
o: undefined
|
|
632
653
|
});
|
|
633
|
-
return _el$
|
|
654
|
+
return _el$87;
|
|
634
655
|
})()
|
|
635
656
|
})
|
|
636
657
|
}), null);
|
|
637
|
-
_$insertNode(_el$52, _el$53);
|
|
638
658
|
_$insertNode(_el$53, _el$54);
|
|
639
|
-
_$insertNode(_el$54,
|
|
640
|
-
_$
|
|
659
|
+
_$insertNode(_el$54, _el$55);
|
|
660
|
+
_$insertNode(_el$55, _$createTextNode(`Session`));
|
|
661
|
+
_$insert(_el$53, _$createComponent(Show, {
|
|
641
662
|
get when() {
|
|
642
663
|
return state().summary;
|
|
643
664
|
},
|
|
644
665
|
get fallback() {
|
|
645
666
|
return (() => {
|
|
646
|
-
var _el$
|
|
647
|
-
_$insertNode(_el$
|
|
648
|
-
_$effect(_$p => _$setProp(_el$
|
|
649
|
-
return _el$
|
|
667
|
+
var _el$94 = _$createElement("text");
|
|
668
|
+
_$insertNode(_el$94, _$createTextNode(`Session usage unavailable`));
|
|
669
|
+
_$effect(_$p => _$setProp(_el$94, "fg", theme().muted, _$p));
|
|
670
|
+
return _el$94;
|
|
650
671
|
})();
|
|
651
672
|
},
|
|
652
673
|
children: summary => _$createComponent(Show, {
|
|
@@ -655,26 +676,26 @@ function UsageDialog(props) {
|
|
|
655
676
|
},
|
|
656
677
|
get fallback() {
|
|
657
678
|
return (() => {
|
|
658
|
-
var _el$
|
|
659
|
-
_el$
|
|
660
|
-
_el$
|
|
661
|
-
_el$
|
|
662
|
-
_el$
|
|
663
|
-
_$insertNode(_el$
|
|
664
|
-
_$insertNode(_el$
|
|
665
|
-
_$insertNode(_el$
|
|
666
|
-
_$insertNode(_el$
|
|
667
|
-
_$setProp(_el$
|
|
668
|
-
_$insert(_el$
|
|
669
|
-
_$insert(_el$
|
|
670
|
-
_$insert(_el$
|
|
671
|
-
_$insert(_el$
|
|
672
|
-
_$insert(_el$
|
|
679
|
+
var _el$96 = _$createElement("text"),
|
|
680
|
+
_el$97 = _$createTextNode(` · `),
|
|
681
|
+
_el$98 = _$createTextNode(` · `),
|
|
682
|
+
_el$99 = _$createTextNode(` tokens · `),
|
|
683
|
+
_el$100 = _$createTextNode(`% cached`);
|
|
684
|
+
_$insertNode(_el$96, _el$97);
|
|
685
|
+
_$insertNode(_el$96, _el$98);
|
|
686
|
+
_$insertNode(_el$96, _el$99);
|
|
687
|
+
_$insertNode(_el$96, _el$100);
|
|
688
|
+
_$setProp(_el$96, "wrapMode", "word");
|
|
689
|
+
_$insert(_el$96, () => countLabel(summary().steps, "step"), _el$97);
|
|
690
|
+
_$insert(_el$96, () => countLabel(summary().calls, "call"), _el$98);
|
|
691
|
+
_$insert(_el$96, () => formatCompact(summary().total), _el$99);
|
|
692
|
+
_$insert(_el$96, () => (summary().cacheRate * 100).toFixed(1), _el$100);
|
|
693
|
+
_$insert(_el$96, (() => {
|
|
673
694
|
var _c$ = _$memo(() => !!formatEstimatedCost(summary().cost, summary().costStatus));
|
|
674
695
|
return () => _c$() ? ` · ${formatEstimatedCost(summary().cost, summary().costStatus)}` : "";
|
|
675
696
|
})(), null);
|
|
676
|
-
_$effect(_$p => _$setProp(_el$
|
|
677
|
-
return _el$
|
|
697
|
+
_$effect(_$p => _$setProp(_el$96, "fg", theme().base, _$p));
|
|
698
|
+
return _el$96;
|
|
678
699
|
})();
|
|
679
700
|
},
|
|
680
701
|
get children() {
|
|
@@ -683,58 +704,58 @@ function UsageDialog(props) {
|
|
|
683
704
|
return summaryRows(summary());
|
|
684
705
|
},
|
|
685
706
|
children: ([label, value]) => (() => {
|
|
686
|
-
var _el$
|
|
687
|
-
_el$
|
|
688
|
-
_el$
|
|
689
|
-
_$insertNode(_el$
|
|
690
|
-
_$insertNode(_el$
|
|
691
|
-
_$setProp(_el$
|
|
692
|
-
_$setProp(_el$
|
|
693
|
-
_$setProp(_el$
|
|
694
|
-
_$setProp(_el$
|
|
695
|
-
_$insert(_el$
|
|
696
|
-
_$insert(_el$
|
|
707
|
+
var _el$101 = _$createElement("box"),
|
|
708
|
+
_el$102 = _$createElement("text"),
|
|
709
|
+
_el$103 = _$createElement("text");
|
|
710
|
+
_$insertNode(_el$101, _el$102);
|
|
711
|
+
_$insertNode(_el$101, _el$103);
|
|
712
|
+
_$setProp(_el$101, "flexDirection", "row");
|
|
713
|
+
_$setProp(_el$101, "justifyContent", "space-between");
|
|
714
|
+
_$setProp(_el$101, "gap", 2);
|
|
715
|
+
_$setProp(_el$102, "flexShrink", 0);
|
|
716
|
+
_$insert(_el$102, label);
|
|
717
|
+
_$insert(_el$103, value);
|
|
697
718
|
_$effect(_p$ => {
|
|
698
719
|
var _v$27 = theme().muted,
|
|
699
720
|
_v$28 = theme().base;
|
|
700
|
-
_v$27 !== _p$.e && (_p$.e = _$setProp(_el$
|
|
701
|
-
_v$28 !== _p$.t && (_p$.t = _$setProp(_el$
|
|
721
|
+
_v$27 !== _p$.e && (_p$.e = _$setProp(_el$102, "fg", _v$27, _p$.e));
|
|
722
|
+
_v$28 !== _p$.t && (_p$.t = _$setProp(_el$103, "fg", _v$28, _p$.t));
|
|
702
723
|
return _p$;
|
|
703
724
|
}, {
|
|
704
725
|
e: undefined,
|
|
705
726
|
t: undefined
|
|
706
727
|
});
|
|
707
|
-
return _el$
|
|
728
|
+
return _el$101;
|
|
708
729
|
})()
|
|
709
730
|
});
|
|
710
731
|
}
|
|
711
732
|
})
|
|
712
733
|
}), null);
|
|
713
|
-
_$insertNode(_el$56, _el$57);
|
|
714
734
|
_$insertNode(_el$57, _el$58);
|
|
715
|
-
_$insertNode(_el$58,
|
|
716
|
-
_$
|
|
735
|
+
_$insertNode(_el$58, _el$59);
|
|
736
|
+
_$insertNode(_el$59, _$createTextNode(`By Model`));
|
|
737
|
+
_$insert(_el$57, _$createComponent(Show, {
|
|
717
738
|
get when() {
|
|
718
739
|
return _$memo(() => !!detailed())() && models().length;
|
|
719
740
|
},
|
|
720
741
|
get children() {
|
|
721
|
-
var _el$
|
|
722
|
-
_$insertNode(_el$
|
|
723
|
-
_$setProp(_el$
|
|
724
|
-
_$effect(_$p => _$setProp(_el$
|
|
725
|
-
return _el$
|
|
742
|
+
var _el$61 = _$createElement("text");
|
|
743
|
+
_$insertNode(_el$61, _$createTextNode(`Rates used for Est. Cost · USD / 1M text tokens · incoming = Input + Cache Read + Cache Write`));
|
|
744
|
+
_$setProp(_el$61, "wrapMode", "word");
|
|
745
|
+
_$effect(_$p => _$setProp(_el$61, "fg", theme().muted, _$p));
|
|
746
|
+
return _el$61;
|
|
726
747
|
}
|
|
727
748
|
}), null);
|
|
728
|
-
_$insert(_el$
|
|
749
|
+
_$insert(_el$57, _$createComponent(Show, {
|
|
729
750
|
get when() {
|
|
730
751
|
return models().length;
|
|
731
752
|
},
|
|
732
753
|
get fallback() {
|
|
733
754
|
return (() => {
|
|
734
|
-
var _el$
|
|
735
|
-
_$insertNode(_el$
|
|
736
|
-
_$effect(_$p => _$setProp(_el$
|
|
737
|
-
return _el$
|
|
755
|
+
var _el$104 = _$createElement("text");
|
|
756
|
+
_$insertNode(_el$104, _$createTextNode(`No model usage yet`));
|
|
757
|
+
_$effect(_$p => _$setProp(_el$104, "fg", theme().muted, _$p));
|
|
758
|
+
return _el$104;
|
|
738
759
|
})();
|
|
739
760
|
},
|
|
740
761
|
get children() {
|
|
@@ -743,29 +764,29 @@ function UsageDialog(props) {
|
|
|
743
764
|
return models();
|
|
744
765
|
},
|
|
745
766
|
children: model => (() => {
|
|
746
|
-
var _el$
|
|
747
|
-
_el$
|
|
748
|
-
_el$106 = _$createElement("text"),
|
|
749
|
-
_el$107 = _$createElement("text"),
|
|
767
|
+
var _el$106 = _$createElement("box"),
|
|
768
|
+
_el$107 = _$createElement("box"),
|
|
750
769
|
_el$108 = _$createElement("text"),
|
|
751
|
-
_el$109 = _$
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
_$insertNode(_el$
|
|
755
|
-
_$insertNode(_el$
|
|
756
|
-
_$
|
|
757
|
-
_$
|
|
758
|
-
_$setProp(_el$
|
|
759
|
-
_$setProp(_el$
|
|
760
|
-
_$setProp(_el$
|
|
761
|
-
_$setProp(_el$
|
|
762
|
-
_$
|
|
763
|
-
_$setProp(_el$
|
|
764
|
-
_$insert(_el$
|
|
765
|
-
_$
|
|
766
|
-
_$insert(_el$
|
|
767
|
-
_$
|
|
768
|
-
_$insert(_el$
|
|
770
|
+
_el$109 = _$createElement("text"),
|
|
771
|
+
_el$110 = _$createElement("text"),
|
|
772
|
+
_el$111 = _$createTextNode(` tokens · `);
|
|
773
|
+
_$insertNode(_el$106, _el$107);
|
|
774
|
+
_$insertNode(_el$106, _el$110);
|
|
775
|
+
_$insertNode(_el$107, _el$108);
|
|
776
|
+
_$insertNode(_el$107, _el$109);
|
|
777
|
+
_$setProp(_el$107, "flexDirection", "row");
|
|
778
|
+
_$setProp(_el$107, "justifyContent", "space-between");
|
|
779
|
+
_$setProp(_el$107, "gap", 2);
|
|
780
|
+
_$setProp(_el$108, "wrapMode", "word");
|
|
781
|
+
_$setProp(_el$108, "minWidth", 0);
|
|
782
|
+
_$setProp(_el$108, "flexGrow", 1);
|
|
783
|
+
_$insert(_el$108, () => model.model);
|
|
784
|
+
_$setProp(_el$109, "flexShrink", 0);
|
|
785
|
+
_$insert(_el$109, () => formatEstimatedCost(model.cost, model.costStatus) ?? "");
|
|
786
|
+
_$insertNode(_el$110, _el$111);
|
|
787
|
+
_$insert(_el$110, () => number(model.tokens), _el$111);
|
|
788
|
+
_$insert(_el$110, () => countLabel(model.calls, "call"), null);
|
|
789
|
+
_$insert(_el$106, _$createComponent(Show, {
|
|
769
790
|
get when() {
|
|
770
791
|
return detailed();
|
|
771
792
|
},
|
|
@@ -775,34 +796,34 @@ function UsageDialog(props) {
|
|
|
775
796
|
return model.appliedRates;
|
|
776
797
|
},
|
|
777
798
|
children: rates => (() => {
|
|
778
|
-
var _el$
|
|
779
|
-
_el$
|
|
780
|
-
_el$
|
|
781
|
-
_el$
|
|
782
|
-
_$insertNode(_el$
|
|
783
|
-
_$setProp(_el$
|
|
784
|
-
_$insertNode(_el$
|
|
785
|
-
_$insertNode(_el$
|
|
786
|
-
_$setProp(_el$
|
|
787
|
-
_$insert(_el$
|
|
788
|
-
_$insert(_el$
|
|
789
|
-
_$insert(_el$
|
|
790
|
-
_$insert(_el$
|
|
791
|
-
_$effect(_$p => _$setProp(_el$
|
|
792
|
-
return _el$
|
|
799
|
+
var _el$114 = _$createElement("box"),
|
|
800
|
+
_el$115 = _$createElement("text"),
|
|
801
|
+
_el$116 = _$createTextNode(` · `),
|
|
802
|
+
_el$117 = _$createTextNode(` · `);
|
|
803
|
+
_$insertNode(_el$114, _el$115);
|
|
804
|
+
_$setProp(_el$114, "marginTop", 1);
|
|
805
|
+
_$insertNode(_el$115, _el$116);
|
|
806
|
+
_$insertNode(_el$115, _el$117);
|
|
807
|
+
_$setProp(_el$115, "wrapMode", "word");
|
|
808
|
+
_$insert(_el$115, () => rates.source, _el$116);
|
|
809
|
+
_$insert(_el$115, () => formatRateTier(rates), _el$117);
|
|
810
|
+
_$insert(_el$115, () => countLabel(rates.calls, "call"), null);
|
|
811
|
+
_$insert(_el$114, () => requestRowsList(rateRows(rates)), null);
|
|
812
|
+
_$effect(_$p => _$setProp(_el$115, "fg", theme().muted, _$p));
|
|
813
|
+
return _el$114;
|
|
793
814
|
})()
|
|
794
815
|
}), _$createComponent(Show, {
|
|
795
816
|
get when() {
|
|
796
817
|
return model.unpricedCalls > 0;
|
|
797
818
|
},
|
|
798
819
|
get children() {
|
|
799
|
-
var _el$
|
|
800
|
-
_el$
|
|
801
|
-
_$insertNode(_el$
|
|
802
|
-
_$setProp(_el$
|
|
803
|
-
_$insert(_el$
|
|
804
|
-
_$effect(_$p => _$setProp(_el$
|
|
805
|
-
return _el$
|
|
820
|
+
var _el$112 = _$createElement("text"),
|
|
821
|
+
_el$113 = _$createTextNode(`Pricing unavailable for `);
|
|
822
|
+
_$insertNode(_el$112, _el$113);
|
|
823
|
+
_$setProp(_el$112, "wrapMode", "word");
|
|
824
|
+
_$insert(_el$112, () => countLabel(model.unpricedCalls, "call"), null);
|
|
825
|
+
_$effect(_$p => _$setProp(_el$112, "fg", theme().muted, _$p));
|
|
826
|
+
return _el$112;
|
|
806
827
|
}
|
|
807
828
|
})];
|
|
808
829
|
}
|
|
@@ -811,16 +832,16 @@ function UsageDialog(props) {
|
|
|
811
832
|
var _v$29 = theme().base,
|
|
812
833
|
_v$30 = theme().base,
|
|
813
834
|
_v$31 = theme().muted;
|
|
814
|
-
_v$29 !== _p$.e && (_p$.e = _$setProp(_el$
|
|
815
|
-
_v$30 !== _p$.t && (_p$.t = _$setProp(_el$
|
|
816
|
-
_v$31 !== _p$.a && (_p$.a = _$setProp(_el$
|
|
835
|
+
_v$29 !== _p$.e && (_p$.e = _$setProp(_el$108, "fg", _v$29, _p$.e));
|
|
836
|
+
_v$30 !== _p$.t && (_p$.t = _$setProp(_el$109, "fg", _v$30, _p$.t));
|
|
837
|
+
_v$31 !== _p$.a && (_p$.a = _$setProp(_el$110, "fg", _v$31, _p$.a));
|
|
817
838
|
return _p$;
|
|
818
839
|
}, {
|
|
819
840
|
e: undefined,
|
|
820
841
|
t: undefined,
|
|
821
842
|
a: undefined
|
|
822
843
|
});
|
|
823
|
-
return _el$
|
|
844
|
+
return _el$106;
|
|
824
845
|
})()
|
|
825
846
|
});
|
|
826
847
|
}
|
|
@@ -835,9 +856,9 @@ function UsageDialog(props) {
|
|
|
835
856
|
_v$10 !== _p$.e && (_p$.e = _$setProp(_el$37, "maxHeight", _v$10, _p$.e));
|
|
836
857
|
_v$11 !== _p$.t && (_p$.t = _$setProp(_el$40, "fg", _v$11, _p$.t));
|
|
837
858
|
_v$12 !== _p$.a && (_p$.a = _$setProp(_el$45, "fg", _v$12, _p$.a));
|
|
838
|
-
_v$13 !== _p$.o && (_p$.o = _$setProp(_el$
|
|
839
|
-
_v$14 !== _p$.i && (_p$.i = _$setProp(_el$
|
|
840
|
-
_v$15 !== _p$.n && (_p$.n = _$setProp(_el$
|
|
859
|
+
_v$13 !== _p$.o && (_p$.o = _$setProp(_el$50, "fg", _v$13, _p$.o));
|
|
860
|
+
_v$14 !== _p$.i && (_p$.i = _$setProp(_el$54, "fg", _v$14, _p$.i));
|
|
861
|
+
_v$15 !== _p$.n && (_p$.n = _$setProp(_el$58, "fg", _v$15, _p$.n));
|
|
841
862
|
return _p$;
|
|
842
863
|
}, {
|
|
843
864
|
e: undefined,
|
|
@@ -849,17 +870,17 @@ function UsageDialog(props) {
|
|
|
849
870
|
});
|
|
850
871
|
return _el$37;
|
|
851
872
|
}
|
|
852
|
-
}), _el$
|
|
853
|
-
_$insertNode(_el$
|
|
854
|
-
_$insertNode(_el$
|
|
855
|
-
_$insertNode(_el$
|
|
856
|
-
_$setProp(_el$
|
|
857
|
-
_$insertNode(_el$
|
|
858
|
-
_$insertNode(_el$
|
|
859
|
-
_$setProp(_el$
|
|
873
|
+
}), _el$63);
|
|
874
|
+
_$insertNode(_el$63, _el$64);
|
|
875
|
+
_$insertNode(_el$63, _el$66);
|
|
876
|
+
_$insertNode(_el$63, _el$68);
|
|
877
|
+
_$setProp(_el$63, "flexDirection", "row");
|
|
878
|
+
_$insertNode(_el$64, _$createTextNode(`↑/↓ scroll · `));
|
|
879
|
+
_$insertNode(_el$66, _$createTextNode(`d details`));
|
|
880
|
+
_$setProp(_el$66, "onMouseUp", event => {
|
|
860
881
|
if (event.button === 0) toggleDetails();
|
|
861
882
|
});
|
|
862
|
-
_$insertNode(_el$
|
|
883
|
+
_$insertNode(_el$68, _$createTextNode(` · esc close`));
|
|
863
884
|
_$effect(_p$ => {
|
|
864
885
|
var _v$16 = theme().base,
|
|
865
886
|
_v$17 = theme().muted,
|
|
@@ -868,9 +889,9 @@ function UsageDialog(props) {
|
|
|
868
889
|
_v$20 = theme().muted;
|
|
869
890
|
_v$16 !== _p$.e && (_p$.e = _$setProp(_el$30, "fg", _v$16, _p$.e));
|
|
870
891
|
_v$17 !== _p$.t && (_p$.t = _$setProp(_el$33, "fg", _v$17, _p$.t));
|
|
871
|
-
_v$18 !== _p$.a && (_p$.a = _$setProp(_el$
|
|
872
|
-
_v$19 !== _p$.o && (_p$.o = _$setProp(_el$
|
|
873
|
-
_v$20 !== _p$.i && (_p$.i = _$setProp(_el$
|
|
892
|
+
_v$18 !== _p$.a && (_p$.a = _$setProp(_el$64, "fg", _v$18, _p$.a));
|
|
893
|
+
_v$19 !== _p$.o && (_p$.o = _$setProp(_el$66, "fg", _v$19, _p$.o));
|
|
894
|
+
_v$20 !== _p$.i && (_p$.i = _$setProp(_el$68, "fg", _v$20, _p$.i));
|
|
874
895
|
return _p$;
|
|
875
896
|
}, {
|
|
876
897
|
e: undefined,
|
package/dist/usage.d.ts
CHANGED
|
@@ -128,11 +128,13 @@ export interface ContextUsage {
|
|
|
128
128
|
limit: number;
|
|
129
129
|
percent: number;
|
|
130
130
|
}
|
|
131
|
-
export interface
|
|
132
|
-
usage: ContextUsage;
|
|
131
|
+
export interface RequestDetails {
|
|
133
132
|
tokens: Tokens;
|
|
133
|
+
total: number;
|
|
134
|
+
/** The selected assistant's stream end, completion, or creation time, in that order. */
|
|
135
|
+
time?: number | undefined;
|
|
134
136
|
}
|
|
135
|
-
export declare function
|
|
137
|
+
export declare function requestDetails(messages: readonly UsageMessage[], revertMessageID?: string): RequestDetails | undefined;
|
|
136
138
|
export declare function contextUsage(messages: readonly UsageMessage[], limit?: number, revertMessageID?: string): ContextUsage | undefined;
|
|
137
139
|
export declare function formatTokens(value: number): string;
|
|
138
140
|
export declare const formatCost: (value: number) => string;
|
|
@@ -152,7 +154,7 @@ export declare function bar(percent: number, width: number): {
|
|
|
152
154
|
* One model request: the five categories of the last assistant call plus its cache rate.
|
|
153
155
|
* Compact mode hides zero rows and drops percentages; detailed mode keeps both.
|
|
154
156
|
*/
|
|
155
|
-
export declare function requestRows(details:
|
|
157
|
+
export declare function requestRows(details: RequestDetails, compact: boolean): readonly (readonly [string, string])[];
|
|
156
158
|
/** Tree summary rows for the dialog's detailed Session view, calls included. */
|
|
157
159
|
export declare function summaryRows(summary: Summary): readonly (readonly [string, string])[];
|
|
158
160
|
/** `1 step` reads better than `1 steps` in the dialog's single-line summaries. */
|
package/dist/usage.js
CHANGED
|
@@ -150,13 +150,12 @@ export function summarizeModels(messages, catalog = new Map()) {
|
|
|
150
150
|
})).sort((a, b) => b.cost - a.cost || a.model.localeCompare(b.model));
|
|
151
151
|
}
|
|
152
152
|
const usableLimit = (value) => value !== undefined && Number.isFinite(value) && value > 0;
|
|
153
|
+
const usableTime = (value) => value !== undefined && value >= 0 && Number.isFinite(new Date(value).getTime());
|
|
153
154
|
// Matches the OpenCode 2.0.10 sidebar: the last assistant message with tokens that follows
|
|
154
155
|
// the last completed compaction carries the current context size. Running or failed
|
|
155
|
-
// compactions do not reset it
|
|
156
|
-
//
|
|
157
|
-
export function
|
|
158
|
-
if (!usableLimit(limit))
|
|
159
|
-
return undefined;
|
|
156
|
+
// compactions do not reset it. Missing token reports are skipped, while an explicit
|
|
157
|
+
// zero report is not replaced by older usage. Request details need no model limit.
|
|
158
|
+
export function requestDetails(messages, revertMessageID) {
|
|
160
159
|
// The staged revert excludes its boundary message and every later message.
|
|
161
160
|
// Session.message.list is ascending; an absent marker can occur during a
|
|
162
161
|
// concurrent commit, in which case the ordered ID still gives the cutoff.
|
|
@@ -178,12 +177,16 @@ export function contextDetails(messages, limit, revertMessageID) {
|
|
|
178
177
|
continue;
|
|
179
178
|
const tokens = normalize(message.tokens);
|
|
180
179
|
const used = total(tokens);
|
|
181
|
-
|
|
180
|
+
const time = [message.time?.streamed, message.time?.completed, message.time?.created].find(usableTime);
|
|
181
|
+
return used > 0 ? { tokens, total: used, time } : undefined;
|
|
182
182
|
}
|
|
183
183
|
return undefined;
|
|
184
184
|
}
|
|
185
185
|
export function contextUsage(messages, limit, revertMessageID) {
|
|
186
|
-
|
|
186
|
+
if (!usableLimit(limit))
|
|
187
|
+
return undefined;
|
|
188
|
+
const request = requestDetails(messages, revertMessageID);
|
|
189
|
+
return request ? { used: request.total, limit, percent: request.total / limit * 100 } : undefined;
|
|
187
190
|
}
|
|
188
191
|
export function formatTokens(value) {
|
|
189
192
|
return Math.round(safe(value)).toLocaleString("en-US");
|
|
@@ -251,7 +254,7 @@ export function requestRows(details, compact) {
|
|
|
251
254
|
return [
|
|
252
255
|
...rows.map(([label, count]) => [
|
|
253
256
|
label,
|
|
254
|
-
compact ? formatCompact(count) : `${formatTokens(count)} (${(count / details.
|
|
257
|
+
compact ? formatCompact(count) : `${formatTokens(count)} (${(count / details.total * 100).toFixed(1)}%)`,
|
|
255
258
|
]),
|
|
256
259
|
["Cache Rate", `${(incomingTokens ? t.cache.read / incomingTokens * 100 : 0).toFixed(1)}%`],
|
|
257
260
|
];
|
package/docs/verification.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# 验证记录
|
|
2
2
|
|
|
3
|
+
## 2026-10-09 · 统计钩子的 MCP 等待上限
|
|
4
|
+
|
|
5
|
+
环境:macOS、Node.js v22.23.2、npm 10.9.8,插件包仍为 0.4.6、SDK 2.0.24,真实宿主为 PATH 中的 OpenCode 2.0.25。未升级版本、提交、推送或发布。
|
|
6
|
+
|
|
7
|
+
MCP 元数据缓存缺失或失效时,共享读取默认最多等待 100 ms,构造参数可注入测试预算。独立定时器结束等待,同时通过 SDK 的 `AbortSignal` 请求中止读取;忽略取消的迟到成功/失败不会更新缓存或清除后续读取。超时或错误时复用旧元数据,不延长缓存 TTL,后续请求可重试;无缓存则跳过本次估算,保留已有估算与时间戳,不写入伪造零值。成功读取的空列表仍是有效缓存。持久化继续按会话顺序异步执行。
|
|
8
|
+
|
|
9
|
+
| 检查 | 结果 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| `npm run typecheck` | 通过 |
|
|
12
|
+
| `node --import tsx --test test/context-capture.test.ts` | 专项 14 项通过,新增 11 项回归测试 |
|
|
13
|
+
| `npm test` | 129 项通过 |
|
|
14
|
+
| `npm run build` | 通过 |
|
|
15
|
+
| `npm run test:smoke` | 完整通过,打包/隔离安装 + 真实 OpenCode 2.0.25 + 本地模拟提供商 |
|
|
16
|
+
|
|
17
|
+
新增测试覆盖永久挂起、100 ms 默认预算与可注入预算、并发共享读取、TTL/事件失效后的旧缓存回退、空缓存、超时后重试、迟到成功/失败与新读取的竞争、同步/异步错误、定时器清理,以及会话删除、flush 和服务端钩子的有界 MCP 等待。挂起与迟到竞态使用可控 Promise 和模拟时钟验证;真实 smoke 验证正常宿主链路,未在真实宿主中注入 MCP 挂起。Node 22 的 MockTimers 实验性警告和 OpenTUI 安装引擎警告未隐藏。
|
|
18
|
+
|
|
19
|
+
验证产物:`/private/var/folders/rw/bmx6c8hd737brl55m0_ff_b80000gn/T/opencode/token-usage-smoke-Syw70N`。
|
|
20
|
+
|
|
21
|
+
## 2026-10-09 · Last Request 与上下文上限解耦
|
|
22
|
+
|
|
23
|
+
环境:macOS、Node.js v22.23.2、npm 10.9.8,插件包 0.4.6、SDK 2.0.24,真实宿主为 PATH 中的 OpenCode 2.0.25。
|
|
24
|
+
|
|
25
|
+
`requestDetails` 独立提取所选 assistant 的五类 token、总量和消息时间,`contextUsage` 单独校验模型上限。Last Request 的分类占比以请求总量为分母;上限缺失、为零、负值或非有限值时,实测请求仍可显示。时间依次取同一条消息有效的 `streamed`、`completed`、`created`,缺失时隐藏;来源估算的 `capturedAt` 仅显示在 Context Breakdown。
|
|
26
|
+
|
|
27
|
+
| 检查 | 结果 |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| `npm run typecheck` | 通过 |
|
|
30
|
+
| `npm test` | 116 项通过,新增 3 项请求明细、时间与边界回归测试,并扩展控制器生命周期断言 |
|
|
31
|
+
| `npm run build` | 通过 |
|
|
32
|
+
| `npm run test:smoke` | 完整通过,打包/隔离安装 + 真实 OpenCode 2.0.25 + 本地模拟提供商 |
|
|
33
|
+
|
|
34
|
+
自动化测试覆盖有效/无效上限切换、请求分类与缓存率、时间优先级及无效日期、压缩与回退边界、子会话本地请求、刷新失败保留快照、跨会话清理,以及来源 RPC 缓慢/失败/返回另一轮请求的估算时间。
|
|
35
|
+
|
|
36
|
+
真实终端验证新增上限为 0 的活动模型:Context Window 显示不可用,Last Request 在紧凑/详细模式下仍显示 `Cache Read 1.0K`/`1,000 (78.7%)` 与 `Cache Rate 83.3%`,切回有效上限后恢复 Context。另核对两个标题各自显示实测 assistant 的流式结束时间和来源估算采样时间,并验证空请求不显示时间。完整 smoke 同时通过 100/48 列布局、子代理全树汇总、TPS/TTFT、定价、真实分叉及已完成压缩与消息排序检查。启动时按既有版本策略提示 2.0.25 未验证,随后实际完成全部检查。
|
|
37
|
+
|
|
38
|
+
验证产物:`/private/var/folders/rw/bmx6c8hd737brl55m0_ff_b80000gn/T/opencode/token-usage-smoke-12xAKR`,其中 `08-unknown-context-limit.txt` 保存未知上限下的详细弹窗。
|
|
39
|
+
|
|
40
|
+
## 2026-10-07 · npm 发布可见性校验“假失败”修复
|
|
41
|
+
|
|
42
|
+
环境:macOS、Node.js v22.23.2、npm 10.9.8,本地包版本为 0.4.5、SDK 为 2.0.24。本轮仅修改发布校验、工作流、测试与文档,未修改价格快照或插件运行时代码,未升级版本、移动标签、提交、推送、重跑 GitHub 工作流或实际发布 npm。
|
|
43
|
+
|
|
44
|
+
只读诊断确认已有真实自动发布链路:
|
|
45
|
+
|
|
46
|
+
- [价格更新运行 37555311596](https://github.com/chenlongapps/opencode-token-usage/actions/runs/37555311596) 由 `schedule` 触发,验证、patch 提交/原子推送、GitHub Release 和 dispatch 均成功。
|
|
47
|
+
- [发布运行 37555363814](https://github.com/chenlongapps/opencode-token-usage/actions/runs/37555363814) 固定在 `v0.4.5`(`66e0584e1f125f2e5012b081014e42d7d8d57d19`)。`npm publish` 成功,生成 provenance,并在 UTC 01:06:28 提示包仍在处理、可能需几分钟才能可用;失败的是 UTC 01:08:21 的 registry 可见性校验。旧实现只查询 12 次、间隔 10 秒,约 2 分钟即超时。
|
|
48
|
+
- 当前 registry 查询已返回目标版本与 `latest` 均为 `0.4.5`,`gitHead` 匹配上述提交,`dist.shasum` 为 `75f8490aa51a974a5d76942a4e1ffa2d9d13fd3c`,与真实发布日志一致。因此不能把该工作流的失败结论当作 npm 未收到版本,更不能为此另升 patch。
|
|
49
|
+
|
|
50
|
+
修复将按次数重试改为默认 10 分钟总时限,使用单调时钟计算剩余预算;每次版本/`latest` 请求最多 30 秒,且不得超过剩余预算,等待同样按剩余时间截短。`NPM_VERIFY_TIMEOUT_MS` 与 `NPM_VERIFY_INTERVAL_MS` 可配置并校验合法性。日志显示轮询进度、当前 `latest` 与具体查询错误;最终超时仍返回非零退出码,在 Actions 摘要中保留最后原因及原标签重试提示,不用 `continue-on-error` 掩盖故障。发布校验步骤上限为 11 分钟,整个任务仍为 20 分钟;已发布版本跳过发布与 `latest` 核验,原有禁止倒退 `latest`、registry 故障不等于版本不存在等保护保持不变。
|
|
51
|
+
|
|
52
|
+
| 检查 | 结果 |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `npm run typecheck` | 通过 |
|
|
55
|
+
| `node --test test/release.test.mjs` | 发布专项 23 项通过 |
|
|
56
|
+
| `npm test` | 113 项通过,比上轮净增 10 项发布校验回归测试 |
|
|
57
|
+
| `npm run build` | 通过 |
|
|
58
|
+
| `npm pack --dry-run` | 通过,执行 `prepack` 构建,包含 37 个发布文件 |
|
|
59
|
+
| `npm publish --dry-run` | 通过,执行类型检查、113 项测试与构建;未写入 registry |
|
|
60
|
+
| GitHub 工作流 | 三个工作流均通过官方 actionlint 1.7.12 检查,下载产物已校验 SHA-256 |
|
|
61
|
+
| npm 只读查询 | `publish-state` 返回 `published=true`;新版 `verify-published` 确认 `0.4.5` 与 `latest` |
|
|
62
|
+
|
|
63
|
+
回归测试用注入时钟模拟版本在 3 分钟后可见、`latest` 在 4 分钟后更新;同时覆盖 404、HTTP 401/403/429/500/503、网络及无效响应、最后原因保留、单次请求 30 秒上限、请求与等待共用预算、到期后不再发起 `latest` 查询或判定成功,以及真实 AbortSignal 中止挂起请求。CLI 测试预载本地 registry 模拟,不访问真实 npm,验证环境配置、超时非零退出码和失败/成功摘要。
|
|
64
|
+
|
|
65
|
+
以上真实 GitHub 运行属于修复前的已有标签;新校验逻辑尚未在新的真实发布工作流中验证。本次没有重跑 TUI smoke,之前的宿主验证记录保留。修复提交进入 `main` 后仅供后续新标签使用;旧标签不移动,重跑已可见的 `v0.4.5` 会由原实现跳过发布并核验现有版本。
|
|
66
|
+
|
|
3
67
|
## 2026-10-07 · SDK 2.0.24 升级
|
|
4
68
|
|
|
5
69
|
环境:macOS、Node.js v22.23.2、npm 10.9.8,插件包仍为 0.4.4。`@opencode/plugin`、`@opencode/client`、`@opencode/schema` 和 `@opencode/theme` 从 2.0.11 同步精确升级到 2.0.24;锁文件中的全部七个 OpenCode 包均为 2.0.24。OpenTUI 从 0.5.10 同步到 SDK 与 theme 配套的 0.5.14,没有追随独立发布的 0.5.15。未修改价格快照、未升插件包版本、未提交或推送、未实际发布 npm。
|