@songsid/agend 2.3.2-beta.4 → 2.3.2-beta.5
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/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/antigravity-usage-scan.d.ts +58 -0
- package/dist/antigravity-usage-scan.js +257 -0
- package/dist/antigravity-usage-scan.js.map +1 -0
- package/dist/antigravity-usage-worker.d.ts +1 -0
- package/dist/antigravity-usage-worker.js +10 -0
- package/dist/antigravity-usage-worker.js.map +1 -0
- package/dist/backend/kiro-identity.d.ts +45 -0
- package/dist/backend/kiro-identity.js +74 -0
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro.d.ts +15 -0
- package/dist/backend/kiro.js +42 -5
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/types.d.ts +6 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-signin-guide.d.ts +1 -0
- package/dist/backend-signin-guide.js +9 -0
- package/dist/backend-signin-guide.js.map +1 -0
- package/dist/backend-signin-pending.d.ts +10 -0
- package/dist/backend-signin-pending.js +44 -0
- package/dist/backend-signin-pending.js.map +1 -0
- package/dist/browser-backend-signins.d.ts +56 -0
- package/dist/browser-backend-signins.js +189 -0
- package/dist/browser-backend-signins.js.map +1 -0
- package/dist/cache-ledger.d.ts +38 -2
- package/dist/cache-ledger.js +133 -8
- package/dist/cache-ledger.js.map +1 -1
- package/dist/cache-prices.d.ts +2 -1
- package/dist/cache-prices.js +5 -2
- package/dist/cache-prices.js.map +1 -1
- package/dist/cache-service.d.ts +68 -0
- package/dist/cache-service.js +355 -13
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/mcp-tools.js +3 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +21 -2
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.js +25 -0
- package/dist/config.js.map +1 -1
- package/dist/cpu-quota.d.ts +23 -0
- package/dist/cpu-quota.js +86 -0
- package/dist/cpu-quota.js.map +1 -0
- package/dist/daemon.js +6 -3
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-manager.d.ts +35 -0
- package/dist/fleet-manager.js +233 -11
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +5 -2
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +14 -1
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +6 -3
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +6 -3
- package/dist/instance-lifecycle.d.ts +9 -0
- package/dist/instance-lifecycle.js +27 -1
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/kiro-db-reader.d.ts +5 -0
- package/dist/kiro-db-reader.js +18 -0
- package/dist/kiro-db-reader.js.map +1 -1
- package/dist/kiro-engine-status.d.ts +2 -0
- package/dist/kiro-engine-status.js +9 -0
- package/dist/kiro-engine-status.js.map +1 -1
- package/dist/kiro-migrate.d.ts +49 -0
- package/dist/kiro-migrate.js +105 -0
- package/dist/kiro-migrate.js.map +1 -0
- package/dist/kiro-transcript-lane.d.ts +2 -0
- package/dist/kiro-transcript-lane.js.map +1 -1
- package/dist/kiro-transcript-worker.js +1 -0
- package/dist/kiro-transcript-worker.js.map +1 -1
- package/dist/kiro-usage-run.d.ts +7 -0
- package/dist/kiro-usage-run.js +39 -0
- package/dist/kiro-usage-run.js.map +1 -0
- package/dist/kiro-usage-scan.d.ts +49 -0
- package/dist/kiro-usage-scan.js +223 -0
- package/dist/kiro-usage-scan.js.map +1 -0
- package/dist/kiro-usage-worker.d.ts +1 -0
- package/dist/kiro-usage-worker.js +10 -0
- package/dist/kiro-usage-worker.js.map +1 -0
- package/dist/kiro-usage.d.ts +58 -0
- package/dist/kiro-usage.js +310 -0
- package/dist/kiro-usage.js.map +1 -0
- package/dist/locale.js +70 -2
- package/dist/locale.js.map +1 -1
- package/dist/log-service.js +1 -1
- package/dist/log-service.js.map +1 -1
- package/dist/login-url.d.ts +4 -0
- package/dist/login-url.js +22 -0
- package/dist/login-url.js.map +1 -0
- package/dist/needs-you-hub.d.ts +2 -1
- package/dist/needs-you-hub.js +1 -1
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you.d.ts +8 -2
- package/dist/needs-you.js +6 -0
- package/dist/needs-you.js.map +1 -1
- package/dist/opencode-usage-scan.d.ts +28 -0
- package/dist/opencode-usage-scan.js +92 -0
- package/dist/opencode-usage-scan.js.map +1 -0
- package/dist/opencode-usage-worker.d.ts +1 -0
- package/dist/opencode-usage-worker.js +10 -0
- package/dist/opencode-usage-worker.js.map +1 -0
- package/dist/outbound-handlers.js +22 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +4 -0
- package/dist/outbound-schemas.js +4 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/quickstart-api.js +13 -2
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.js +2 -1
- package/dist/quickstart.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +7 -1
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-host.d.ts +9 -0
- package/dist/setup-host.js +124 -5
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-signin-assets.d.ts +2 -0
- package/dist/setup-signin-assets.js +6 -0
- package/dist/setup-signin-assets.js.map +1 -0
- package/dist/setup-wizard.js +2 -0
- package/dist/setup-wizard.js.map +1 -1
- package/dist/structured-signin-transport.d.ts +8 -0
- package/dist/structured-signin-transport.js +79 -0
- package/dist/structured-signin-transport.js.map +1 -0
- package/dist/structured-signin.d.ts +131 -0
- package/dist/structured-signin.js +388 -0
- package/dist/structured-signin.js.map +1 -0
- package/dist/tool-permissions.js +2 -0
- package/dist/tool-permissions.js.map +1 -1
- package/dist/topic-commands.js +4 -1
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-sources.d.ts +26 -2
- package/dist/transcript-sources.js +87 -40
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +3 -2
- package/dist/ui/backend-signin.js +63 -10
- package/dist/ui/fleet-usage.js +19 -15
- package/dist/ui/panel-log.js +2 -2
- package/dist/ui/panel-needs.js +4 -4
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app.css +1 -0
- package/dist/ui/structured-signin.js +115 -0
- package/dist/usage-report.d.ts +20 -0
- package/dist/usage-report.js +54 -17
- package/dist/usage-report.js.map +1 -1
- package/dist/usage-rollup.d.ts +9 -1
- package/dist/usage-rollup.js +20 -4
- package/dist/usage-rollup.js.map +1 -1
- package/dist/web-api.d.ts +1 -0
- package/dist/web-api.js +99 -0
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +1 -1
- package/dist/web-auth.js.map +1 -1
- package/dist/web-backend-login.d.ts +1 -7
- package/dist/web-backend-login.js +2 -24
- package/dist/web-backend-login.js.map +1 -1
- package/package.json +2 -1
- package/templates/windows-shortcuts.ps1 +7 -5
package/dist/ui/fleet-usage.js
CHANGED
|
@@ -15,14 +15,15 @@ register("fusage", {
|
|
|
15
15
|
range: "Range", today: "Today", r7d: "7 days", month: "This month", all: "All", custom: "Custom", from: "From", to: "To", apply: "Apply",
|
|
16
16
|
group: "Group by", g_instance: "Agent", g_backend: "Backend", g_model: "Model", g_profile: "Credential profile", g_team: "Team (as configured now)",
|
|
17
17
|
csv: "Download CSV", basis: "API-equivalent at list price (checked {0}) — not money spent. Subscriptions are not billed per token.",
|
|
18
|
-
amount: "API-equivalent", input: "Input", output: "Output", read: "Cache read", write: "Cache write", total: "Total tokens", requests: "Requests", messages: "Messages",
|
|
18
|
+
amount: "API-equivalent", input: "Input", output: "Output", reasoning: "Reasoning", read: "Cache read", write: "Cache write", total: "Total tokens", requests: "Requests", messages: "Messages",
|
|
19
19
|
notPriced: "not priced", partial: "partial", noTeam: "(no team)", unknown: "unknown",
|
|
20
|
-
f_cache_write_not_reported: "cache writes not reported (Codex) — an estimate", f_not_priced: "not priced: {0}", f_source_unknown: "read before backend and profile were recorded",
|
|
20
|
+
f_cache_write_not_reported: "cache writes not reported (Codex, Muse) — an estimate", f_not_priced: "not priced: {0}", f_source_unknown: "read before backend and profile were recorded",
|
|
21
|
+
f_estimated: "estimated from text size — Kiro records no token counts", f_includes_estimates: "includes estimates (Kiro)", f_cost_reported: "amount as the CLI reported it (no list price for this model)", estimatedPart: "of which estimated: {0}",
|
|
21
22
|
scanning: "Scanning — totals may still grow.", updated: "Updated {0} · read every 10 min", since: "Collected since {0}", notYet: "Not collected yet",
|
|
22
23
|
refresh: "Refresh",
|
|
23
24
|
notMeasured: "Not measured", nm_credit_billed: "Kiro is credit-billed and records no tokens", nm_unsupported: "AgEnD can't read this CLI's usage yet", nm_no_data: "nothing read yet",
|
|
24
25
|
shared: "shares its working directory with {0}; their transcripts can't be told apart",
|
|
25
|
-
empty: "Nothing recorded yet", emptyHint: "Usage is read from Claude Code and
|
|
26
|
+
empty: "Nothing recorded yet", emptyHint: "Usage is read from Claude Code, Codex, Grok, Muse, OpenCode and Antigravity, and estimated for Kiro, from when the fleet started.",
|
|
26
27
|
blocked: "Not offered over the public link", blockedHint: "Usage is offered over the public link only when \"Usage report over the public link\" is on in Settings → Web dashboard — on the dashboard reached directly, on the computer running AgEnD.",
|
|
27
28
|
openSettings: "Open Settings → Web dashboard on that computer",
|
|
28
29
|
partialDays: "Daily detail from {0}: the earlier days in this range are too many rows to send. The rows and totals above cover the whole range; the trend, the heatmap and the CSV start at {0}.",
|
|
@@ -42,14 +43,15 @@ register("fusage", {
|
|
|
42
43
|
range: "期間", today: "今天", r7d: "7 天", month: "本月", all: "全部", custom: "自訂", from: "從", to: "到", apply: "套用",
|
|
43
44
|
group: "分組", g_instance: "Agent", g_backend: "Backend", g_model: "模型", g_profile: "憑證 profile", g_team: "Team(依目前設定)",
|
|
44
45
|
csv: "下載 CSV", basis: "依牌價換算的 API 等值(查證日期 {0})——不是實際花費。訂閱方案不按 token 計費。",
|
|
45
|
-
amount: "API 等值", input: "輸入", output: "輸出", read: "快取讀取", write: "快取寫入", total: "token 總計", requests: "請求數", messages: "訊息數",
|
|
46
|
+
amount: "API 等值", input: "輸入", output: "輸出", reasoning: "推理", read: "快取讀取", write: "快取寫入", total: "token 總計", requests: "請求數", messages: "訊息數",
|
|
46
47
|
notPriced: "未定價", partial: "部分", noTeam: "(不屬於任何 team)", unknown: "未知",
|
|
47
|
-
f_cache_write_not_reported: "未回報快取寫入(Codex)——是估算", f_not_priced: "未定價:{0}", f_source_unknown: "讀取時尚未記錄 backend 與 profile",
|
|
48
|
+
f_cache_write_not_reported: "未回報快取寫入(Codex、Muse)——是估算", f_not_priced: "未定價:{0}", f_source_unknown: "讀取時尚未記錄 backend 與 profile",
|
|
49
|
+
f_estimated: "依文字長度估算——Kiro 不記錄 token 數", f_includes_estimates: "含估算值(Kiro)", f_cost_reported: "金額為 CLI 自行回報(價目表沒有此模型)", estimatedPart: "其中估算:{0}",
|
|
48
50
|
scanning: "掃描中——總數可能還會增加。", updated: "更新於 {0} · 每 10 分鐘讀取一次", since: "自 {0} 起收集", notYet: "尚未收集",
|
|
49
51
|
refresh: "重新整理",
|
|
50
52
|
notMeasured: "未量測", nm_credit_billed: "Kiro 以 credit 計費,不記錄 token", nm_unsupported: "AgEnD 目前還讀不到這個 CLI 的用量", nm_no_data: "尚未讀到任何資料",
|
|
51
53
|
shared: "與 {0} 共用工作目錄,無法分辨各自的 transcript",
|
|
52
|
-
empty: "還沒有任何紀錄", emptyHint: "用量從 Claude Code 與
|
|
54
|
+
empty: "還沒有任何紀錄", emptyHint: "用量從 Claude Code、Codex、Grok、Muse、OpenCode 與 Antigravity 讀取,Kiro 則為估算,自 fleet 啟動起計。",
|
|
53
55
|
blocked: "公開連結上不提供", blockedHint: "只有在設定 → 網頁儀表板裡開啟「透過公開連結提供用量報告」時,公開連結才會提供用量;這個設定要在執行 AgEnD 的那台電腦上、直接開啟的儀表板裡變更。",
|
|
54
56
|
openSettings: "在那台電腦上開啟設定 → 網頁儀表板",
|
|
55
57
|
partialDays: "每日明細從 {0} 起:這段期間更早的日子資料列太多,無法一次送出。上方的各列與總計涵蓋整段期間;趨勢、熱度圖與 CSV 則從 {0} 開始。",
|
|
@@ -95,11 +97,11 @@ export function keyText(group, key) {
|
|
|
95
97
|
const writes = (r) => (r.cacheWrite5m || 0) + (r.cacheWrite1h || 0) + (r.cacheWrite || 0);
|
|
96
98
|
function flagText(f, r) {
|
|
97
99
|
if (f === "not_priced") return tu("f_not_priced", (r.unpriced || []).join(", "));
|
|
98
|
-
return ["cache_write_not_reported", "source_unknown"].includes(f) ? tu(`f_${f}`) : f;
|
|
100
|
+
return ["cache_write_not_reported", "source_unknown", "estimated", "includes_estimates", "cost_reported"].includes(f) ? tu(`f_${f}`) : f;
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
// ── CSV (design §4): the daily rows of this same answer; messages on their own rows (they are per agent, not per model).
|
|
102
|
-
const CSV_COLUMNS = ["date", "zone", "instance", "backend", "model", "tier", "profile", "input", "output", "cache_read", "cache_write_5m", "cache_write_1h", "cache_write", "requests", "messages", "amount", "basis", "flags"];
|
|
104
|
+
const CSV_COLUMNS = ["date", "zone", "instance", "backend", "model", "tier", "profile", "input", "output", "reasoning", "cache_read", "cache_write_5m", "cache_write_1h", "cache_write", "requests", "messages", "amount", "basis", "flags", "estimated"];
|
|
103
105
|
/** One CSV field: quoted when it must be, and never read as a formula by a spreadsheet (=, +, -, @, tab, CR first). */
|
|
104
106
|
export function csvField(v) {
|
|
105
107
|
let s = v === null || v === undefined ? "" : String(v);
|
|
@@ -109,11 +111,11 @@ export function csvField(v) {
|
|
|
109
111
|
export function usageCsv(report) {
|
|
110
112
|
const lines = [CSV_COLUMNS.join(",")];
|
|
111
113
|
for (const d of report.daily || []) {
|
|
112
|
-
lines.push([d.date, d.zone, d.instance, d.backend, d.model, d.tier, d.profile, d.input, d.output, d.cacheRead, d.cacheWrite5m, d.cacheWrite1h, d.cacheWrite,
|
|
113
|
-
d.requests, "", d.amount === null ? "" : d.amount.toFixed(6), report.basis, (d.flags || []).join(" ")].map(csvField).join(","));
|
|
114
|
+
lines.push([d.date, d.zone, d.instance, d.backend, d.model, d.tier, d.profile, d.input, d.output, d.reasoning, d.cacheRead, d.cacheWrite5m, d.cacheWrite1h, d.cacheWrite,
|
|
115
|
+
d.requests, "", d.amount === null ? "" : d.amount.toFixed(6), report.basis, (d.flags || []).join(" "), d.estimated ? "yes" : "no"].map(csvField).join(","));
|
|
114
116
|
}
|
|
115
117
|
for (const m of report.messagesDaily || []) {
|
|
116
|
-
lines.push([m.date, report.zone, m.instance, "", "", "", "", "", "", "", "", "", "", "", m.count, "", report.basis, ""].map(csvField).join(","));
|
|
118
|
+
lines.push([m.date, report.zone, m.instance, "", "", "", "", "", "", "", "", "", "", "", "", m.count, "", report.basis, "", ""].map(csvField).join(","));
|
|
117
119
|
}
|
|
118
120
|
return `${lines.join("\r\n")}\r\n`;
|
|
119
121
|
}
|
|
@@ -151,7 +153,7 @@ export function levels(values) {
|
|
|
151
153
|
return (v) => (!(v > 0) ? 0 : v <= cut[0] ? 1 : v <= cut[1] ? 2 : v <= cut[2] ? 3 : 4);
|
|
152
154
|
}
|
|
153
155
|
|
|
154
|
-
const tokensOf = (d) => d.input + d.output + d.cacheRead + d.cacheWrite5m + d.cacheWrite1h + d.cacheWrite;
|
|
156
|
+
const tokensOf = (d) => d.input + d.output + (d.reasoning || 0) + d.cacheRead + d.cacheWrite5m + d.cacheWrite1h + d.cacheWrite;
|
|
155
157
|
/**
|
|
156
158
|
* Each day of the answer: its tokens (whether it was used at all), the priced part of its amount, whether part of it is
|
|
157
159
|
* not priced, and its parts by zone (a day folded before the fleet's timezone changed keeps its own zone, §5.3).
|
|
@@ -218,7 +220,7 @@ export function thinXLabels(n, slotPx, labelWidthPx) {
|
|
|
218
220
|
/** The trend's series: the top `TOP` agents by the measure over the range, then "others"; one value per date. */
|
|
219
221
|
export function trendSeries(report, dates) {
|
|
220
222
|
const priced = report.totals && report.totals.amount !== null && report.totals.amount > 0;
|
|
221
|
-
const measure = (d) => (priced ? d.amount || 0 : d
|
|
223
|
+
const measure = (d) => (priced ? d.amount || 0 : tokensOf(d));
|
|
222
224
|
const byAgent = new Map();
|
|
223
225
|
for (const d of report.daily || []) byAgent.set(d.instance, (byAgent.get(d.instance) || 0) + measure(d));
|
|
224
226
|
const ranked = [...byAgent.entries()].filter(([, v]) => v > 0).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
@@ -327,6 +329,7 @@ function Row({ r, group, max, measure }) {
|
|
|
327
329
|
<td data-label=${tu("amount")} class=${`num${r.amount === null ? " muted" : ""}`}>${amountText(r)}</td>
|
|
328
330
|
<td data-label=${tu("input")} class="num">${tokens(r.input)}</td>
|
|
329
331
|
<td data-label=${tu("output")} class="num">${tokens(r.output)}</td>
|
|
332
|
+
<td data-label=${tu("reasoning")} class="num">${r.reasoningReported === false ? "—" : tokens(r.reasoning || 0)}</td>
|
|
330
333
|
<td data-label=${tu("read")} class="num">${tokens(r.cacheRead)}</td>
|
|
331
334
|
<td data-label=${tu("write")} class="num">${r.flags.includes("cache_write_not_reported") && writes(r) === 0 ? "—" : tokens(writes(r))}</td>
|
|
332
335
|
<td data-label=${tu("total")} class="num">${tokens(r.total)}</td>
|
|
@@ -379,13 +382,14 @@ export function UsageReport({ report, choice, setChoice, refresh, fetchedAt }) {
|
|
|
379
382
|
${report.scanning.active && (!report.scanning.caughtUp || report.scanning.pendingBytes > 0) ? html` <span class="us-scan" role="status">${tu("scanning")} <button type="button" class="btn btn-sm" onClick=${refresh}>${tu("refresh")}</button></span>` : null}</p>
|
|
380
383
|
${empty ? html`<${Empty} icon="chart" title=${tu("empty")} hint=${tu("emptyHint")} />` : html`
|
|
381
384
|
<dl class="us-totals card">
|
|
382
|
-
<div class="ch-metric"><dt>${tu("amount")}</dt><dd>${amountText(report.totals)}
|
|
385
|
+
<div class="ch-metric"><dt>${tu("amount")}</dt><dd>${amountText(report.totals)}${report.totals.estimated
|
|
386
|
+
? html`<span class="us-est">${report.totals.amountEstimated !== null && report.totals.amountEstimated !== undefined ? tu("estimatedPart", money(report.totals.amountEstimated)) : tu("f_includes_estimates")}</span>` : null}</dd></div>
|
|
383
387
|
<div class="ch-metric"><dt>${tu("total")}</dt><dd>${tokens(report.totals.total)}</dd></div>
|
|
384
388
|
<div class="ch-metric"><dt>${tu("requests")}</dt><dd>${n(report.totals.requests)}</dd></div>
|
|
385
389
|
<div class="ch-metric"><dt>${tu("messages")}</dt><dd>${report.totals.messages === null ? "—" : n(report.totals.messages)}</dd></div>
|
|
386
390
|
</dl>
|
|
387
391
|
${rows.length ? html`<table class="us-table"><thead><tr><th scope="col">${tu(`g_${group}`)}</th><th scope="col" class="num">${tu("amount")}</th>
|
|
388
|
-
<th scope="col" class="num">${tu("input")}</th><th scope="col" class="num">${tu("output")}</th><th scope="col" class="num">${tu("read")}</th><th scope="col" class="num">${tu("write")}</th>
|
|
392
|
+
<th scope="col" class="num">${tu("input")}</th><th scope="col" class="num">${tu("output")}</th><th scope="col" class="num">${tu("reasoning")}</th><th scope="col" class="num">${tu("read")}</th><th scope="col" class="num">${tu("write")}</th>
|
|
389
393
|
<th scope="col" class="num">${tu("total")}</th><th scope="col" class="num">${tu("requests")}</th><th scope="col" class="num">${tu("messages")}</th></tr></thead>
|
|
390
394
|
<tbody>${rows.map((r) => html`<${Row} key=${r.key} r=${r} group=${group} max=${max} measure=${measure} />`)}</tbody></table>`
|
|
391
395
|
: html`<p class="note">${tu("noRows")}</p>`}
|
package/dist/ui/panel-log.js
CHANGED
|
@@ -62,7 +62,7 @@ register("log", {
|
|
|
62
62
|
ev_backend_unreachable: "CLI unreachable", ev_startup_backend_unreachable: "CLI unreachable at start", ev_backend_launch_warning: "Launch warning",
|
|
63
63
|
ev_clear_action: "Conversation cleared", ev_normal_exit_action: "Exited", ev_reply_drop_detected: "A reply went missing",
|
|
64
64
|
ev_reply_drop_recovered: "Missing reply recovered", ev_reply_drop_unrecovered: "Missing reply not recovered",
|
|
65
|
-
ev_supervision_ended: "Supervision ended", ev_tmux_server_crash: "tmux crashed", ev_malformed_tool_call: "Malformed tool call", ev_health_check_error: "Health check error", ev_working_state: "Working-state journal",
|
|
65
|
+
ev_supervision_ended: "Supervision ended", ev_tmux_server_crash: "tmux crashed", ev_malformed_tool_call: "Malformed tool call", ev_health_check_error: "Health check error", ev_context_lost: "Started fresh: context lost", ev_working_state: "Working-state journal",
|
|
66
66
|
},
|
|
67
67
|
"zh-TW": {
|
|
68
68
|
offTitle: "紀錄未開啟",
|
|
@@ -100,7 +100,7 @@ register("log", {
|
|
|
100
100
|
ev_backend_unreachable: "連不到 CLI", ev_startup_backend_unreachable: "啟動時連不到 CLI", ev_backend_launch_warning: "啟動警告",
|
|
101
101
|
ev_clear_action: "對話已清除", ev_normal_exit_action: "已結束", ev_reply_drop_detected: "有回覆遺失",
|
|
102
102
|
ev_reply_drop_recovered: "遺失的回覆已補回", ev_reply_drop_unrecovered: "遺失的回覆沒有補回",
|
|
103
|
-
ev_supervision_ended: "監督結束", ev_tmux_server_crash: "tmux 當掉", ev_malformed_tool_call: "格式錯誤的工具呼叫", ev_health_check_error: "健康檢查錯誤", ev_working_state: "工作狀態紀錄",
|
|
103
|
+
ev_supervision_ended: "監督結束", ev_tmux_server_crash: "tmux 當掉", ev_malformed_tool_call: "格式錯誤的工具呼叫", ev_health_check_error: "健康檢查錯誤", ev_context_lost: "改用全新對話:context 遺失", ev_working_state: "工作狀態紀錄",
|
|
104
104
|
},
|
|
105
105
|
});
|
|
106
106
|
const tl = (key, ...a) => t(`log.${key}`, ...a);
|
package/dist/ui/panel-needs.js
CHANGED
|
@@ -25,7 +25,7 @@ import { openSignIn, signInOffered } from "./backend-signin.js";
|
|
|
25
25
|
register("needs", {
|
|
26
26
|
en: {
|
|
27
27
|
title: "Needs you", empty: "Nothing needs you right now", emptyHint: "When an agent waits for you, it shows here — and in your chat app's General.", emptyHintWeb: "When an agent waits for you, it shows here.",
|
|
28
|
-
open: "Open chat", openCard: "Open the card", noAssignee: "Task board (nobody assigned)", signIn: "Sign in",
|
|
28
|
+
open: "Open chat", openCard: "Open the card", noAssignee: "Task board (nobody assigned)", fleetSignIns: "Fleet sign-ins", signIn: "Sign in",
|
|
29
29
|
gap: "not wrapped up: {0}", gapUnchecked: "{0} checklist item(s) unchecked", gapNoResult: "no result", gapJoin: ", ", acknowledge: "Acknowledge", acknowledging: "Acknowledging…", answering: "Answering…",
|
|
30
30
|
ageNow: "just now", ageMin: "{0} min", ageHour: "{0} h", ageDay: "{0} d",
|
|
31
31
|
notifyTitle: "Notify me on this device", notifyOn: "Notifications are on for this browser while a tab is open.",
|
|
@@ -38,7 +38,7 @@ register("needs", {
|
|
|
38
38
|
},
|
|
39
39
|
"zh-TW": {
|
|
40
40
|
title: "等你處理", empty: "目前沒有需要你處理的事", emptyHint: "agent 在等你時,會出現在這裡——也會出現在聊天 app 的 General。", emptyHintWeb: "agent 在等你時,會出現在這裡。",
|
|
41
|
-
open: "開啟聊天", openCard: "打開卡片", noAssignee: "任務看板(沒有負責人)", signIn: "登入",
|
|
41
|
+
open: "開啟聊天", openCard: "打開卡片", noAssignee: "任務看板(沒有負責人)", fleetSignIns: "Fleet 登入", signIn: "登入",
|
|
42
42
|
gap: "還沒補齊:{0}", gapUnchecked: "還有 {0} 項沒勾", gapNoResult: "沒有產出總結", gapJoin: "、", acknowledge: "確認", acknowledging: "確認中…", answering: "回覆中…",
|
|
43
43
|
ageNow: "剛剛", ageMin: "{0} 分鐘", ageHour: "{0} 小時", ageDay: "{0} 天",
|
|
44
44
|
notifyTitle: "在這台裝置通知我", notifyOn: "這個瀏覽器的通知已開啟(需要開著分頁)。",
|
|
@@ -143,7 +143,7 @@ export function NeedsPanel({ navKey }) {
|
|
|
143
143
|
<div data-trust="panel-content" class="panel-body"><div class="col">
|
|
144
144
|
${!items.length ? html`<${Empty} icon="check" title=${tn("empty")} hint=${tn(chatApp ? "emptyHint" : "emptyHintWeb")} />`
|
|
145
145
|
: groups.map((g) => html`<section key=${g.instance} class="n-group" aria-label=${g.instance}>
|
|
146
|
-
<h2 class="n-group-head">${g.instance ? html`<a href=${chatPath(g.instance)} title=${g.instance}>${nameFor(g.instance)}</a>` : tn("noAssignee")}</h2>
|
|
146
|
+
<h2 class="n-group-head">${g.instance ? html`<a href=${chatPath(g.instance)} title=${g.instance}>${nameFor(g.instance)}</a>` : tn(g.items.some(i => i.type === "backend") ? "fleetSignIns" : "noAssignee")}</h2>
|
|
147
147
|
${g.items.map((item) => html`<${NeedsItem} key=${item.id} item=${item} nowMs=${nowMs} owner=${owner} />`)}
|
|
148
148
|
</section>`)}
|
|
149
149
|
<${NotifyToggle} />
|
|
@@ -200,7 +200,7 @@ function NeedsItem({ item, nowMs, owner }) {
|
|
|
200
200
|
disabled=${promptBusy} onClick=${() => answer(a.id)}>${(p ? p.busy : fallback === "busy") ? tn("answering") : a.label}</button>`) : null}
|
|
201
201
|
${item.type === "delivery" ? html`<button type="button" class="btn btn-sm" disabled=${!!ack} onClick=${acknowledge}>
|
|
202
202
|
<${Icon} name="check" size=${14} />${ack === "busy" ? tn("acknowledging") : tn("acknowledge")}</button>` : null}
|
|
203
|
-
${item.reason === "auth_paused" && item.signIn && signInOffered() ? html`<button type="button" class="btn btn-sm btn-primary" onClick=${() => openSignIn(item.signIn)}>${tn("signIn")}</button>` : null}
|
|
203
|
+
${(item.reason === "auth_paused" || item.reason === "backend_signin") && item.signIn && signInOffered() ? html`<button type="button" class="btn btn-sm btn-primary" onClick=${() => openSignIn(item.signIn)}>${tn("signIn")}</button>` : null}
|
|
204
204
|
${item.type === "task" && item.card ? html`<a class="btn btn-sm" href=${`/ui/fleet?card=${encodeURIComponent(item.card.code)}`}>${tn("openCard")}</a>` : null}
|
|
205
205
|
${item.instance ? html`<a class="btn btn-sm btn-ghost" href=${chatPath(item.instance)}>${tn("open")}</a>` : null}
|
|
206
206
|
</div>
|
|
@@ -105,7 +105,7 @@ register("app", {
|
|
|
105
105
|
confirmTitle: "Please confirm", confirmOk: "Continue", cancel: "Cancel",
|
|
106
106
|
sidePanelLeave: "Leave {0} without saving? Your changes there will be lost.", sidePanelDiscard: "Discard changes",
|
|
107
107
|
// The server's own wording (src/locale.ts needs.reason.*), so the web says what Discord says.
|
|
108
|
-
needs_reason_hang: "Not responding", needs_reason_exited: "Exited", needs_reason_assist: "Waiting at its terminal", needs_reason_permission: "Permission needed", needs_reason_dangerous_command: "Dangerous command waiting", needs_reason_login: "Sign-in needed", needs_reason_dialog: "Waiting at a dialog", needs_reason_terminal_input: "May be waiting for input", needs_reason_auth_paused: "Paused: sign-in needed", needs_reason_crashed: "Crashed", needs_reason_delivery_uncertain: "Could not confirm delivery", needs_reason_delivery_failed: "Delivery failed", needs_reason_task_review: "Waiting for your confirmation", needs_reason_task_blocked: "Blocked: asks you",
|
|
108
|
+
needs_reason_hang: "Not responding", needs_reason_exited: "Exited", needs_reason_assist: "Waiting at its terminal", needs_reason_permission: "Permission needed", needs_reason_dangerous_command: "Dangerous command waiting", needs_reason_login: "Sign-in needed", needs_reason_dialog: "Waiting at a dialog", needs_reason_terminal_input: "May be waiting for input", needs_reason_auth_paused: "Paused: sign-in needed", needs_reason_backend_signin: "Backend sign-in needed", needs_reason_crashed: "Crashed", needs_reason_delivery_uncertain: "Could not confirm delivery", needs_reason_delivery_failed: "Delivery failed", needs_reason_task_review: "Waiting for your confirmation", needs_reason_task_blocked: "Blocked: asks you",
|
|
109
109
|
},
|
|
110
110
|
"zh-TW": {
|
|
111
111
|
brand: "AgEnD", fleet: "Fleet", fleetOverview: "Fleet 概覽", view: "檢視", settings: "設定", instances: "Instances", newInstance: "新增 instance", newAgent: "新增 Agent",
|
|
@@ -145,6 +145,6 @@ register("app", {
|
|
|
145
145
|
search: "搜尋", searchTitle: "搜尋聊天、agent 之間的訊息與任務({0})",
|
|
146
146
|
confirmTitle: "請確認", confirmOk: "繼續", cancel: "取消",
|
|
147
147
|
sidePanelLeave: "不儲存就離開「{0}」?你在那裡的修改會遺失。", sidePanelDiscard: "捨棄修改",
|
|
148
|
-
needs_reason_hang: "沒有回應", needs_reason_exited: "已結束", needs_reason_assist: "在終端機上等待", needs_reason_permission: "需要權限", needs_reason_dangerous_command: "危險指令待確認", needs_reason_login: "需要登入", needs_reason_dialog: "停在對話框", needs_reason_terminal_input: "可能在等輸入", needs_reason_auth_paused: "已暫停:需要登入", needs_reason_crashed: "已崩潰", needs_reason_delivery_uncertain: "無法確認是否送達", needs_reason_delivery_failed: "傳送失敗", needs_reason_task_review: "等你確認", needs_reason_task_blocked: "卡住,在問你",
|
|
148
|
+
needs_reason_hang: "沒有回應", needs_reason_exited: "已結束", needs_reason_assist: "在終端機上等待", needs_reason_permission: "需要權限", needs_reason_dangerous_command: "危險指令待確認", needs_reason_login: "需要登入", needs_reason_dialog: "停在對話框", needs_reason_terminal_input: "可能在等輸入", needs_reason_auth_paused: "已暫停:需要登入", needs_reason_backend_signin: "後端需要登入", needs_reason_crashed: "已崩潰", needs_reason_delivery_uncertain: "無法確認是否送達", needs_reason_delivery_failed: "傳送失敗", needs_reason_task_review: "等你確認", needs_reason_task_blocked: "卡住,在問你",
|
|
149
149
|
},
|
|
150
150
|
});
|
package/dist/ui/shared/app.css
CHANGED
|
@@ -1936,6 +1936,7 @@ html.oo-immersive .p-fleet > .panel-body { z-index: 50; }
|
|
|
1936
1936
|
.us-bar { display: block; width: 100%; height: 6px; margin: 4px 0; }
|
|
1937
1937
|
.us-bar-bg { fill: var(--surface-2); } .us-bar-fg { fill: var(--us-0); }
|
|
1938
1938
|
.us-meta { display: block; font-size: var(--fs-xs); color: var(--text-3); overflow-wrap: anywhere; }
|
|
1939
|
+
.us-est { display: block; font-size: var(--fs-xs); font-weight: var(--fw-regular); color: var(--text-3); }
|
|
1939
1940
|
.us-flags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
|
|
1940
1941
|
.us-trend, .us-heat { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }
|
|
1941
1942
|
.us-trend h3, .us-heat h3, .us-rest h3 { font-size: var(--fs-sm); font-weight: var(--fw-semibold); }
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/* Shared browser controls. No terminal relay, provider output or secrets in diagnostics. */
|
|
2
|
+
const NAMES = { "kiro-cli": "Kiro", "claude-code": "Claude Code", codex: "Codex", antigravity: "Antigravity (console fallback)" };
|
|
3
|
+
const CHOICES = { builder_id: "Builder ID", google: "Google", github: "GitHub", organization: "Your organization", subscription: "Claude subscription", console: "Anthropic Console" };
|
|
4
|
+
const ERRORS = {
|
|
5
|
+
unsupported_prompt: "This CLI screen is not supported. Cancel and use the console fallback.",
|
|
6
|
+
cleanup_failed: "The login process could not be confirmed closed. Close it on the host and restart setup before retrying.",
|
|
7
|
+
signin_cleanup_failed: "The login process could not be confirmed closed. Close it on the host and restart setup before retrying.",
|
|
8
|
+
signin_action_stale: "That prompt changed or was already answered. Refresh its status; do not submit it again.",
|
|
9
|
+
signin_console_only: "This backend uses the explicit console fallback.",
|
|
10
|
+
backend_signin_local_only: "Backend sign-in is available only on this computer. Use the local setup page, or sign in later.",
|
|
11
|
+
signin_session_expired: "This setup session has ended. Reopen setup on the host.",
|
|
12
|
+
expired: "This login attempt expired. Start a new attempt.",
|
|
13
|
+
auth_unconfirmed: "The CLI exited, but credentials could not be verified. Retry or sign in later.",
|
|
14
|
+
provider_rejected: "The provider rejected this sign-in. Retry or sign in later.",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function mountStructuredSignIns(root, { endpoint, fetcher = fetch, changed = () => {} } = {}) {
|
|
18
|
+
const doc = root.ownerDocument;
|
|
19
|
+
let view = null, busy = false, disposed = false, timer = null, generation = 0, message = "", rendered = "";
|
|
20
|
+
const el = (tag, text, parent = root) => { const node = doc.createElement(tag); if (text) node.textContent = text; parent.append(node); return node; };
|
|
21
|
+
const errorText = code => ERRORS[code] || "Could not complete sign-in. Check status, retry, or sign in later.";
|
|
22
|
+
const button = (parent, label, action) => { const b = el("button", label, parent); b.type = "button"; b.disabled = busy; b.onclick = action; return b; };
|
|
23
|
+
const request = async data => {
|
|
24
|
+
const response = await fetcher(endpoint, { method: data ? "POST" : "GET", cache: "no-store", headers: { "Content-Type": "application/json" }, ...(data ? { body: JSON.stringify(data) } : {}) });
|
|
25
|
+
const body = await response.json().catch(() => null);
|
|
26
|
+
if (!response.ok) throw new Error(body?.error || "signin_failed");
|
|
27
|
+
return body;
|
|
28
|
+
};
|
|
29
|
+
const accept = next => { view = next; root.dataset.ready = String(view.ready); changed(view); };
|
|
30
|
+
async function perform(data) {
|
|
31
|
+
if (busy || disposed) return;
|
|
32
|
+
busy = true; generation++; message = ""; render();
|
|
33
|
+
try { const next = await request(data); if (!disposed) accept(next); }
|
|
34
|
+
catch (error) { if (!disposed) message = errorText(error?.message); }
|
|
35
|
+
finally { busy = false; if (!disposed) render(); }
|
|
36
|
+
}
|
|
37
|
+
function render() {
|
|
38
|
+
const signature = JSON.stringify([view, busy, message]);
|
|
39
|
+
if (disposed || rendered === signature) return;
|
|
40
|
+
rendered = signature; root.replaceChildren();
|
|
41
|
+
el("h2", "Sign in your AI backends");
|
|
42
|
+
el("p", "Use the controls here; no terminal typing is needed for Kiro, Claude Code or Codex. You can sign in later from the dashboard.");
|
|
43
|
+
if (message) { const alert = el("p", message); alert.setAttribute("role", "alert"); }
|
|
44
|
+
if (!view) { button(root, "Check sign-ins", () => perform({ operation: "refresh" })); return; }
|
|
45
|
+
for (const backend of view.selected) {
|
|
46
|
+
const card = el("section"); card.dataset.backend = backend;
|
|
47
|
+
el("h3", NAMES[backend] || backend, card);
|
|
48
|
+
const state = view.states[backend], pending = view.pending.includes(backend), skipped = view.skipped.includes(backend);
|
|
49
|
+
el("p", skipped ? "Sign in later · remains in Needs you" : state === "valid" ? "Signed in ✓" : state === "not_installed" ? "Not installed on this host" : state === "console_only" ? "Use the explicit console fallback, or sign in later." : "Sign-in needed", card);
|
|
50
|
+
const login = view.login?.backend === backend ? view.login : null;
|
|
51
|
+
const active = login && login.phase !== "done" && !(login.phase === "failed" && login.reason);
|
|
52
|
+
if (active) {
|
|
53
|
+
el("p", login.phase === "waiting" || login.phase === "starting" ? "Waiting for the CLI…" : "Login: " + login.phase.replaceAll("_", " "), card);
|
|
54
|
+
if (login.phase === "method" && login.canAct) for (const choice of login.choices || []) {
|
|
55
|
+
if (CHOICES[choice]) button(card, CHOICES[choice], () => perform({ operation: "input", id: login.id, revision: login.revision, action: { kind: "method", choice } }));
|
|
56
|
+
}
|
|
57
|
+
if (login.url) {
|
|
58
|
+
// The host already allowlists these; still never turn a non-HTTPS URL into a link.
|
|
59
|
+
let url; try { url = new URL(login.url); } catch {}
|
|
60
|
+
if (url?.protocol === "https:" && !url.username && !url.password) {
|
|
61
|
+
const link = el("a", "Open provider sign-in", card); link.href = url.href; link.target = "_blank"; link.rel = "noopener noreferrer";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (login.code) { el("p", "Device code:", card); el("code", login.code, card); }
|
|
65
|
+
if (login.canAct && ["code", "start_url", "region"].includes(login.phase)) {
|
|
66
|
+
const label = el("label", { code: "Paste the returned code", start_url: "Organization Start URL", region: "AWS region" }[login.phase], card);
|
|
67
|
+
const input = el("input", null, label); input.type = login.phase === "code" ? "password" : "text"; input.autocomplete = "off"; input.spellcheck = false;
|
|
68
|
+
input.setAttribute("aria-label", label.textContent); input.disabled = busy;
|
|
69
|
+
button(card, "Submit " + login.phase.replaceAll("_", " "), () => { const value = input.value; input.value = ""; void perform({ operation: "input", id: login.id, revision: login.revision, action: { kind: login.phase, value } }); });
|
|
70
|
+
}
|
|
71
|
+
if (["method", "code", "start_url", "region"].includes(login.phase) && !login.canAct) el("p", "Answer sent. Waiting for the next screen; it will not be sent twice.", card);
|
|
72
|
+
button(card, "Cancel sign-in", () => perform({ operation: "cancel" }));
|
|
73
|
+
} else {
|
|
74
|
+
if (login?.reason && login.reason !== "cancelled") el("p", errorText(login.reason), card);
|
|
75
|
+
const usable = !["antigravity"].includes(backend) && state !== "not_installed";
|
|
76
|
+
if (usable) button(card, state === "valid" ? "Sign in again" : "Sign in", () => {
|
|
77
|
+
const relogin = state === "valid";
|
|
78
|
+
if (relogin && !globalThis.confirm("Sign in again? Kiro will sign out its existing account first.")) return;
|
|
79
|
+
void perform({ operation: "start", backend, relogin });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (pending && !skipped) button(card, "Sign in later", () => perform({ operation: "skip", backend }));
|
|
83
|
+
}
|
|
84
|
+
button(root, "Check sign-ins", () => perform({ operation: "refresh" }));
|
|
85
|
+
if (view.ready) el("p", "You can continue setup.");
|
|
86
|
+
}
|
|
87
|
+
async function poll() {
|
|
88
|
+
if (disposed) return;
|
|
89
|
+
const before = generation;
|
|
90
|
+
if (!busy) try { const next = await request(); if (!disposed && before === generation) { accept(next); render(); } }
|
|
91
|
+
catch (error) { if (!disposed && before === generation) { message = errorText(error?.message); render(); } }
|
|
92
|
+
if (!disposed) timer = setTimeout(poll, 1000);
|
|
93
|
+
}
|
|
94
|
+
render(); void poll();
|
|
95
|
+
return {
|
|
96
|
+
addBackends(backends) {
|
|
97
|
+
if (view && Array.isArray(backends) && backends.every(b => NAMES[b]) && backends.some(b => !view.selected.includes(b)))
|
|
98
|
+
return perform({ operation: "select", backends: [...new Set([...view.selected, ...backends])] });
|
|
99
|
+
},
|
|
100
|
+
addBackend(backend) { if (view && NAMES[backend] && !view.selected.includes(backend)) return perform({ operation: "select", backends: [...view.selected, backend] }); },
|
|
101
|
+
dispose() { disposed = true; generation++; clearTimeout(timer); root.replaceChildren(); },
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const setupRoot = typeof document === "undefined" ? null : document.getElementById("setupSignins");
|
|
106
|
+
if (setupRoot) {
|
|
107
|
+
const cards = mountStructuredSignIns(setupRoot, { endpoint: "setup/signins" });
|
|
108
|
+
const backend = document.getElementById("backend");
|
|
109
|
+
const select = () => cards.addBackend(backend?.value);
|
|
110
|
+
backend?.addEventListener("change", select);
|
|
111
|
+
const observer = new MutationObserver(select); if (backend) observer.observe(backend, { childList: true });
|
|
112
|
+
// Initial GET may finish after the environment list, so also observe our readiness result.
|
|
113
|
+
const ready = new MutationObserver(select); ready.observe(setupRoot, { attributes: true, attributeFilter: ["data-ready"] });
|
|
114
|
+
addEventListener("pagehide", () => { observer.disconnect(); ready.disconnect(); cards.dispose(); }, { once: true });
|
|
115
|
+
}
|
package/dist/usage-report.d.ts
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* Honest by construction: an amount is "API-equivalent at list price (checked <date>) — not money spent"; an unpriced
|
|
8
8
|
* model's tokens are shown and its amount is not; Codex's cache writes are "not reported" (every CLI seen records 0),
|
|
9
9
|
* never priced as uncached; an instance whose backend AgEnD cannot measure is listed with its reason, never as $0.
|
|
10
|
+
* Kiro records no token counts: its rows are ESTIMATED from text size (kiro-usage.ts, design §10.2) and say so — on the
|
|
11
|
+
* row (`estimated`), on any total that includes one (`includes_estimates`, with the estimated part of the amount), in
|
|
12
|
+
* the API, on the page and in the CSV. An estimate never mixes silently into a measured number.
|
|
10
13
|
*/
|
|
11
14
|
import type { Ledger } from "./cache-ledger.js";
|
|
12
15
|
import { type UsageDays } from "./usage-rollup.js";
|
|
@@ -27,6 +30,12 @@ export declare const usageBasis: (checked?: string) => string;
|
|
|
27
30
|
* latest whole days and says from which date (`dailyFrom`); the rows and totals always cover the whole range.
|
|
28
31
|
*/
|
|
29
32
|
export declare const USAGE_DAILY_MAX = 20000;
|
|
33
|
+
/** The backends whose usage is estimated, not measured (#2122 §10.2: Kiro records no token counts). */
|
|
34
|
+
export declare const ESTIMATED_BACKENDS: ReadonlySet<string>;
|
|
35
|
+
/** The backends whose transcripts report reasoning tokens (#2122 §10.3); elsewhere a 0 means not reported. */
|
|
36
|
+
export declare const REPORTS_REASONING: ReadonlySet<string>;
|
|
37
|
+
/** The backends whose transcripts carry a cache-write field that stays 0: "not reported" until a real write is seen. */
|
|
38
|
+
export declare const WRITES_UNREPORTED: ReadonlySet<string>;
|
|
30
39
|
export interface UsageInstanceInput {
|
|
31
40
|
name: string;
|
|
32
41
|
/** Its backend now: what it is collected with from here on (each slice of its history carries its own). */
|
|
@@ -63,7 +72,10 @@ export interface UsageInput {
|
|
|
63
72
|
export interface UsageTotals {
|
|
64
73
|
/** Uncached input. */
|
|
65
74
|
input: number;
|
|
75
|
+
/** Output without its reasoning part — the units of tokscale's daily rows (design §10.3). */
|
|
66
76
|
output: number;
|
|
77
|
+
/** The part of the output a CLI reports as reasoning (Codex); 0 where none is reported (Claude). */
|
|
78
|
+
reasoning: number;
|
|
67
79
|
cacheRead: number;
|
|
68
80
|
cacheWrite5m: number;
|
|
69
81
|
cacheWrite1h: number;
|
|
@@ -73,6 +85,12 @@ export interface UsageTotals {
|
|
|
73
85
|
}
|
|
74
86
|
export interface UsageRow extends UsageTotals {
|
|
75
87
|
key: string;
|
|
88
|
+
/** Whether any of it comes from a CLI that reports reasoning (REPORTS_REASONING): false → its 0 is "not reported". */
|
|
89
|
+
reasoningReported: boolean;
|
|
90
|
+
/** Some of it is estimated (ESTIMATED_BACKENDS); flagged "estimated" (a row) or "includes_estimates" (the totals). */
|
|
91
|
+
estimated: boolean;
|
|
92
|
+
/** The estimated part of `amount` (null: nothing estimated is priced). */
|
|
93
|
+
amountEstimated: number | null;
|
|
76
94
|
total: number;
|
|
77
95
|
/** Delivered messages; null where a message has no such key (by model, by profile). */
|
|
78
96
|
messages: number | null;
|
|
@@ -92,6 +110,8 @@ export interface UsageRow extends UsageTotals {
|
|
|
92
110
|
}
|
|
93
111
|
export interface UsageDaily extends UsageTotals {
|
|
94
112
|
date: string;
|
|
113
|
+
reasoningReported: boolean;
|
|
114
|
+
estimated: boolean;
|
|
95
115
|
zone: string;
|
|
96
116
|
instance: string;
|
|
97
117
|
backend: string;
|
package/dist/usage-report.js
CHANGED
|
@@ -10,6 +10,12 @@ export const usageBasis = (checked = PRICES_CHECKED) => `API-equivalent at list
|
|
|
10
10
|
*/
|
|
11
11
|
export const USAGE_DAILY_MAX = 20_000;
|
|
12
12
|
const M = 1_000_000;
|
|
13
|
+
/** The backends whose usage is estimated, not measured (#2122 §10.2: Kiro records no token counts). */
|
|
14
|
+
export const ESTIMATED_BACKENDS = new Set(["kiro-cli"]);
|
|
15
|
+
/** The backends whose transcripts report reasoning tokens (#2122 §10.3); elsewhere a 0 means not reported. */
|
|
16
|
+
export const REPORTS_REASONING = new Set(["codex", "grok", "muse", "opencode", "antigravity"]);
|
|
17
|
+
/** The backends whose transcripts carry a cache-write field that stays 0: "not reported" until a real write is seen. */
|
|
18
|
+
export const WRITES_UNREPORTED = new Set(["codex", "muse"]);
|
|
13
19
|
const DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
14
20
|
/** A range as asked (query string or tool argument); anything else is the last 7 days. */
|
|
15
21
|
export function usageRange(kind, from, to) {
|
|
@@ -31,19 +37,23 @@ export function rangeDates(range, now, zone) {
|
|
|
31
37
|
case "custom": return { from: range.from, to: range.to };
|
|
32
38
|
}
|
|
33
39
|
}
|
|
34
|
-
const zero = () => ({ input: 0, output: 0, cacheRead: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheWrite: 0, requests: 0 });
|
|
35
|
-
|
|
40
|
+
const zero = () => ({ input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite5m: 0, cacheWrite1h: 0, cacheWrite: 0, requests: 0 });
|
|
41
|
+
/** One slice into a row: `reasoning` is the part of the slice's output reported as reasoning (shown beside the rest). */
|
|
42
|
+
const addTotals = (into, [n, uncached, read, w5, w1h, w, out], reasoning) => {
|
|
43
|
+
const r = Math.min(Math.max(0, reasoning), out);
|
|
36
44
|
into.requests += n;
|
|
37
45
|
into.input += uncached;
|
|
38
46
|
into.cacheRead += read;
|
|
39
47
|
into.cacheWrite5m += w5;
|
|
40
48
|
into.cacheWrite1h += w1h;
|
|
41
49
|
into.cacheWrite += w;
|
|
42
|
-
into.output += out;
|
|
50
|
+
into.output += out - r;
|
|
51
|
+
into.reasoning += r;
|
|
43
52
|
};
|
|
44
53
|
const splitKey = (mk) => { const bar = mk.lastIndexOf("|"); return { model: mk.slice(0, bar), long: mk.slice(bar + 1) === "1" }; };
|
|
45
54
|
/**
|
|
46
|
-
* The list-price amount of one slice, or null when its model is not in the table.
|
|
55
|
+
* The list-price amount of one slice, or null when its model is not in the table. Reasoning is a part of the slice's
|
|
56
|
+
* output, so the whole output is priced at the output rate once. Codex writes not reported are 0 in the
|
|
47
57
|
* ledger (it marks writesSeen on the first one above 0), so they add nothing and are never priced as uncached input.
|
|
48
58
|
*/
|
|
49
59
|
function amountOf(model, long, [, uncached, read, w5, w1h, w, out]) {
|
|
@@ -52,6 +62,7 @@ function amountOf(model, long, [, uncached, read, w5, w1h, w, out]) {
|
|
|
52
62
|
return null;
|
|
53
63
|
return (uncached * r.input + read * r.read + w5 * r.write5m + w1h * r.write1h + w * r.write + out * r.output) / M;
|
|
54
64
|
}
|
|
65
|
+
const estimatedSlice = (s) => ESTIMATED_BACKENDS.has(s.backend);
|
|
55
66
|
const unknownSource = (s) => s.backend === "unknown" || s.profile === "unknown";
|
|
56
67
|
export function buildUsageReport(input) {
|
|
57
68
|
const { from, to } = rangeDates(input.range, input.now, input.zone);
|
|
@@ -70,14 +81,14 @@ export function buildUsageReport(input) {
|
|
|
70
81
|
if (!i.ledger || !i.days)
|
|
71
82
|
continue;
|
|
72
83
|
coverage.push({ name: i.name, intervals: i.days.coverage.map(([a, b]) => [a, b]) });
|
|
73
|
-
// Codex: a write is reported only once a real one has been seen (every CLI so far records 0).
|
|
84
|
+
// Codex and Muse: a write is reported only once a real one has been seen (every CLI so far records 0).
|
|
74
85
|
const writesSeen = i.ledger.writesSeen === true;
|
|
75
86
|
const base = { instance: i.name, team: i.team };
|
|
76
87
|
for (const row of i.days.rows) {
|
|
77
88
|
if (!inRange(row.date))
|
|
78
89
|
continue;
|
|
79
90
|
for (const [mk, tot] of Object.entries(row.m))
|
|
80
|
-
slices.push({ ...base, backend: row.be, writesReported: row.be
|
|
91
|
+
slices.push({ ...base, backend: row.be, writesReported: !WRITES_UNREPORTED.has(row.be) || writesSeen, date: row.date, zone: row.zone, profile: row.pf, ...splitKey(mk), tot, reasoning: row.r?.[mk] ?? 0, cost: row.c?.[mk] ?? 0 });
|
|
81
92
|
}
|
|
82
93
|
for (const [hk, hour] of Object.entries(i.ledger.hours)) {
|
|
83
94
|
const h = Number(hk);
|
|
@@ -88,7 +99,7 @@ export function buildUsageReport(input) {
|
|
|
88
99
|
continue;
|
|
89
100
|
for (const src of hourSources(hour)) {
|
|
90
101
|
for (const [mk, tot] of Object.entries(src.m))
|
|
91
|
-
slices.push({ ...base, backend: src.backend, writesReported: src.backend
|
|
102
|
+
slices.push({ ...base, backend: src.backend, writesReported: !WRITES_UNREPORTED.has(src.backend) || writesSeen, date, zone: input.zone, profile: src.profile, ...splitKey(mk), tot, reasoning: src.r[mk] ?? 0, cost: src.c[mk] ?? 0 });
|
|
92
103
|
}
|
|
93
104
|
}
|
|
94
105
|
}
|
|
@@ -102,7 +113,7 @@ export function buildUsageReport(input) {
|
|
|
102
113
|
}
|
|
103
114
|
};
|
|
104
115
|
const rows = new Map();
|
|
105
|
-
const newRow = (key) => ({ key, ...zero(), total: 0, messages: null, amount: null, priced: "none", flags: [], unpriced: [], instances: [], backends: [], models: [], profiles: [], _amount: 0, _pricedAny: false, _unpricedAny: false });
|
|
116
|
+
const newRow = (key) => ({ key, ...zero(), reasoningReported: false, estimated: false, amountEstimated: null, _estAmount: 0, _estPriced: false, total: 0, messages: null, amount: null, priced: "none", flags: [], unpriced: [], instances: [], backends: [], models: [], profiles: [], _amount: 0, _pricedAny: false, _unpricedAny: false });
|
|
106
117
|
const uniq = (list, v) => { if (!list.includes(v))
|
|
107
118
|
list.push(v); };
|
|
108
119
|
// Daily rows: one per day, zone, agent, backend, model, tier and profile (an hour's slices summed into their day).
|
|
@@ -110,12 +121,18 @@ export function buildUsageReport(input) {
|
|
|
110
121
|
for (const s of slices) {
|
|
111
122
|
const key = keyOf(s);
|
|
112
123
|
const row = rows.get(key) ?? (rows.set(key, newRow(key)), rows.get(key));
|
|
113
|
-
addTotals(row, s.tot);
|
|
124
|
+
addTotals(row, s.tot, s.reasoning);
|
|
125
|
+
if (REPORTS_REASONING.has(s.backend))
|
|
126
|
+
row.reasoningReported = true;
|
|
114
127
|
uniq(row.instances, s.instance);
|
|
115
128
|
uniq(row.backends, s.backend);
|
|
116
129
|
uniq(row.models, s.model);
|
|
117
130
|
uniq(row.profiles, s.profile);
|
|
118
|
-
|
|
131
|
+
// List price first; a model the table lacks takes the cost its CLI reported (Grok), said so (design §10.2).
|
|
132
|
+
const listed = amountOf(s.model, s.long, s.tot), reported = listed === null && s.cost > 0;
|
|
133
|
+
const amount = listed ?? (reported ? s.cost : null);
|
|
134
|
+
if (reported)
|
|
135
|
+
uniq(row.flags, "cost_reported");
|
|
119
136
|
if (amount === null) {
|
|
120
137
|
row._unpricedAny = true;
|
|
121
138
|
uniq(row.unpriced, s.model);
|
|
@@ -124,6 +141,14 @@ export function buildUsageReport(input) {
|
|
|
124
141
|
row._pricedAny = true;
|
|
125
142
|
row._amount += amount;
|
|
126
143
|
}
|
|
144
|
+
if (estimatedSlice(s)) {
|
|
145
|
+
row.estimated = true;
|
|
146
|
+
uniq(row.flags, "estimated");
|
|
147
|
+
if (amount !== null) {
|
|
148
|
+
row._estPriced = true;
|
|
149
|
+
row._estAmount += amount;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
127
152
|
if (!s.writesReported)
|
|
128
153
|
uniq(row.flags, "cache_write_not_reported");
|
|
129
154
|
if (unknownSource(s))
|
|
@@ -132,11 +157,13 @@ export function buildUsageReport(input) {
|
|
|
132
157
|
const dk = [s.date, s.zone, s.instance, s.backend, s.model, tier, s.profile].join("\u0000");
|
|
133
158
|
let d = dailyBy.get(dk);
|
|
134
159
|
if (!d) {
|
|
135
|
-
d = { date: s.date, zone: s.zone, instance: s.instance, backend: s.backend, model: s.model, tier, profile: s.profile, ...zero(), amount: amount === null ? null : 0,
|
|
136
|
-
flags: [...(s.writesReported ? [] : ["cache_write_not_reported"]), ...(amount === null ? ["not_priced"] : []), ...(unknownSource(s) ? ["source_unknown"] : [])] };
|
|
160
|
+
d = { date: s.date, zone: s.zone, instance: s.instance, backend: s.backend, model: s.model, tier, profile: s.profile, ...zero(), reasoningReported: REPORTS_REASONING.has(s.backend), estimated: estimatedSlice(s), amount: amount === null ? null : 0,
|
|
161
|
+
flags: [...(estimatedSlice(s) ? ["estimated"] : []), ...(s.writesReported ? [] : ["cache_write_not_reported"]), ...(amount === null ? ["not_priced"] : []), ...(unknownSource(s) ? ["source_unknown"] : [])] };
|
|
137
162
|
dailyBy.set(dk, d);
|
|
138
163
|
}
|
|
139
|
-
addTotals(d, s.tot);
|
|
164
|
+
addTotals(d, s.tot, s.reasoning);
|
|
165
|
+
if (reported && !d.flags.includes("cost_reported"))
|
|
166
|
+
d.flags.push("cost_reported");
|
|
140
167
|
if (amount !== null && d.amount !== null)
|
|
141
168
|
d.amount += amount;
|
|
142
169
|
}
|
|
@@ -188,8 +215,9 @@ export function buildUsageReport(input) {
|
|
|
188
215
|
row.messages ??= 0;
|
|
189
216
|
}
|
|
190
217
|
const finish = (row) => {
|
|
191
|
-
const { _amount, _pricedAny, _unpricedAny, ...rest } = row;
|
|
192
|
-
rest.
|
|
218
|
+
const { _amount, _pricedAny, _unpricedAny, _estAmount, _estPriced, ...rest } = row;
|
|
219
|
+
rest.amountEstimated = _estPriced ? _estAmount : null;
|
|
220
|
+
rest.total = rest.input + rest.output + rest.reasoning + rest.cacheRead + rest.cacheWrite5m + rest.cacheWrite1h + rest.cacheWrite;
|
|
193
221
|
rest.amount = _pricedAny ? _amount : null;
|
|
194
222
|
rest.priced = _pricedAny ? (_unpricedAny ? "partial" : "all") : "none";
|
|
195
223
|
if (_unpricedAny)
|
|
@@ -200,15 +228,24 @@ export function buildUsageReport(input) {
|
|
|
200
228
|
.sort((a, b) => (b.amount ?? -1) - (a.amount ?? -1) || b.total - a.total || a.key.localeCompare(b.key));
|
|
201
229
|
const totals = finish(newRow("all"));
|
|
202
230
|
for (const r of out) {
|
|
203
|
-
for (const k of ["input", "output", "cacheRead", "cacheWrite5m", "cacheWrite1h", "cacheWrite", "requests"])
|
|
231
|
+
for (const k of ["input", "output", "reasoning", "cacheRead", "cacheWrite5m", "cacheWrite1h", "cacheWrite", "requests"])
|
|
204
232
|
totals[k] += r[k];
|
|
205
233
|
totals.total += r.total;
|
|
206
234
|
if (r.messages !== null)
|
|
207
235
|
totals.messages = (totals.messages ?? 0) + r.messages;
|
|
208
236
|
if (r.amount !== null)
|
|
209
237
|
totals.amount = (totals.amount ?? 0) + r.amount;
|
|
238
|
+
if (r.reasoningReported)
|
|
239
|
+
totals.reasoningReported = true;
|
|
240
|
+
if (r.estimated) {
|
|
241
|
+
totals.estimated = true;
|
|
242
|
+
uniq(totals.flags, "includes_estimates");
|
|
243
|
+
}
|
|
244
|
+
if (r.amountEstimated !== null)
|
|
245
|
+
totals.amountEstimated = (totals.amountEstimated ?? 0) + r.amountEstimated;
|
|
210
246
|
for (const f of r.flags)
|
|
211
|
-
|
|
247
|
+
if (f !== "estimated")
|
|
248
|
+
uniq(totals.flags, f);
|
|
212
249
|
for (const m of r.unpriced)
|
|
213
250
|
uniq(totals.unpriced, m);
|
|
214
251
|
}
|