diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2304e7b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,146 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + - feat/token-usage-high-value-insights + +permissions: + contents: write + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + DSH_REF: fb82698709c39f1860b0ab0ed147e1fa30c1d5d0 + DSH_PNPM_VERSION: 11.7.0 + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Check out plugin branch + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref || github.ref_name }} + fetch-depth: 0 + + - name: Use Node.js 22.19.0 + uses: actions/setup-node@v4 + with: + node-version: 22.19.0 + + - name: Build pinned DeepSeek Harness toolchain + shell: bash + run: | + set -euo pipefail + harness="$RUNNER_TEMP/deepseek-harness" + git init "$harness" + git -C "$harness" remote add origin https://github.com/deepseek-ai/deepseek-harness.git + git -C "$harness" fetch --depth 1 origin "$DSH_REF" + git -C "$harness" checkout --detach FETCH_HEAD + corepack enable + corepack prepare "pnpm@$DSH_PNPM_VERSION" --activate + pnpm --dir "$harness" install --frozen-lockfile --ignore-scripts + pnpm --dir "$harness" run build:lib + ln -sfn "$harness" "$GITHUB_WORKSPACE/../deepseek-harness" + node --check scripts/link-dsh-workspace.mjs + node scripts/link-dsh-workspace.mjs "$harness" "$GITHUB_WORKSPACE/node_modules" + echo "$GITHUB_WORKSPACE/node_modules/.bin" >> "$GITHUB_PATH" + + - name: Typecheck host and client + run: npm run typecheck + + - name: Run regression tests + run: npm test + + - name: Rebuild committed bundles + run: npm run build + + - name: Verify package contents + shell: bash + run: | + set -euo pipefail + rm -rf dist + mkdir -p dist + npm pack --dry-run --json --pack-destination dist > dist/pack.json + cat dist/pack.json + node --input-type=module <<'NODE' + import { readFileSync } from 'node:fs' + + const results = JSON.parse(readFileSync('dist/pack.json', 'utf8')) + if (!Array.isArray(results) || results.length !== 1) { + throw new Error(`Expected one npm pack result, received ${JSON.stringify(results)}`) + } + + const files = results[0]?.files?.map((entry) => entry.path).sort() + if (!Array.isArray(files)) { + throw new Error('npm pack result did not include a files array') + } + + const required = [ + 'LICENSE', + 'README.md', + 'lib/index.cjs', + 'lib/index.mjs', + 'package.json', + 'src/index.ts', + 'src/manifests.ts', + ] + for (const path of required) { + if (!files.includes(path)) { + throw new Error(`Missing required package file: ${path}`) + } + } + + const allowedExact = new Set(['LICENSE', 'README.md', 'package.json']) + const unexpected = files.filter((path) => ( + !allowedExact.has(path) + && !path.startsWith('lib/') + && !path.startsWith('src/') + )) + if (unexpected.length > 0) { + throw new Error(`Unexpected package files: ${unexpected.join(', ')}`) + } + + console.log(`Verified npm package contents (${files.length} files)`) + NODE + + - name: Persist generated bundles on feature branch + if: github.head_ref == 'feat/token-usage-high-value-insights' || github.ref == 'refs/heads/feat/token-usage-high-value-insights' + shell: bash + run: | + set -euo pipefail + git add -- lib/index.cjs lib/index.mjs + git diff --cached --check + mapfile -t staged < <(git diff --cached --name-only) + for file in "${staged[@]}"; do + case "$file" in + lib/index.cjs|lib/index.mjs) ;; + *) + echo "Unexpected staged file: $file" >&2 + exit 1 + ;; + esac + done + if git diff --cached --quiet; then + echo "Generated bundles are already current." + else + target_branch="${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit -m "build: refresh generated plugin bundles" + git push origin "HEAD:$target_branch" + fi + + - name: Verify generated artifacts + shell: bash + run: | + set -euo pipefail + git diff --check + git diff --exit-code -- lib diff --git a/.github/workflows/sync-generated-bundles-v2.yml b/.github/workflows/sync-generated-bundles-v2.yml new file mode 100644 index 0000000..82f169c --- /dev/null +++ b/.github/workflows/sync-generated-bundles-v2.yml @@ -0,0 +1,89 @@ +name: Sync generated bundles v2 (temporary) + +on: + push: + branches: + - feat/token-usage-high-value-insights + paths: + - .github/workflows/sync-generated-bundles-v2.yml + +permissions: + contents: write + +env: + DSH_REF: fb82698709c39f1860b0ab0ed147e1fa30c1d5d0 + DSH_PNPM_VERSION: 11.7.0 + +jobs: + sync: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Check out feature branch + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Use Node.js 22.19.0 + uses: actions/setup-node@v4 + with: + node-version: 22.19.0 + + - name: Build pinned DeepSeek Harness toolchain + shell: bash + run: | + set -euo pipefail + harness="$RUNNER_TEMP/deepseek-harness" + git init "$harness" + git -C "$harness" remote add origin https://github.com/deepseek-ai/deepseek-harness.git + git -C "$harness" fetch --depth 1 origin "$DSH_REF" + git -C "$harness" checkout --detach FETCH_HEAD + corepack enable + corepack prepare "pnpm@$DSH_PNPM_VERSION" --activate + pnpm --dir "$harness" install --frozen-lockfile --ignore-scripts + pnpm --dir "$harness" run build:lib + ln -sfn "$harness" "$GITHUB_WORKSPACE/../deepseek-harness" + node --check scripts/link-dsh-workspace.mjs + node scripts/link-dsh-workspace.mjs "$harness" "$GITHUB_WORKSPACE/node_modules" + echo "$GITHUB_WORKSPACE/node_modules/.bin" >> "$GITHUB_PATH" + + - name: Typecheck host and client + run: npm run typecheck + + - name: Run regression tests + run: npm test + + - name: Rebuild generated bundles + run: npm run build + + - name: Stage and verify generated bundles only + shell: bash + run: | + set -euo pipefail + git status --short + git add -- lib/index.cjs lib/index.mjs + git diff --cached --check + mapfile -t staged < <(git diff --cached --name-only) + for file in "${staged[@]}"; do + case "$file" in + lib/index.cjs|lib/index.mjs) ;; + *) + echo "Unexpected staged file: $file" >&2 + exit 1 + ;; + esac + done + + - name: Commit refreshed bundles + shell: bash + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + if git diff --cached --quiet; then + echo "Generated bundles are already current." + exit 0 + fi + git commit -m "build: refresh generated plugin bundles" + git push origin "HEAD:$GITHUB_REF_NAME" diff --git a/.github/workflows/sync-generated-bundles.yml b/.github/workflows/sync-generated-bundles.yml new file mode 100644 index 0000000..6d33d5e --- /dev/null +++ b/.github/workflows/sync-generated-bundles.yml @@ -0,0 +1,89 @@ +name: Sync generated bundles (temporary) + +on: + push: + branches: + - feat/token-usage-high-value-insights + paths: + - .github/workflows/sync-generated-bundles.yml + +permissions: + contents: write + +env: + DSH_REF: fb82698709c39f1860b0ab0ed147e1fa30c1d5d0 + DSH_PNPM_VERSION: 11.7.0 + +jobs: + sync: + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Check out feature branch + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Use Node.js 22.19.0 + uses: actions/setup-node@v4 + with: + node-version: 22.19.0 + + - name: Build pinned DeepSeek Harness toolchain + shell: bash + run: | + set -euo pipefail + harness="$RUNNER_TEMP/deepseek-harness" + git init "$harness" + git -C "$harness" remote add origin https://github.com/deepseek-ai/deepseek-harness.git + git -C "$harness" fetch --depth 1 origin "$DSH_REF" + git -C "$harness" checkout --detach FETCH_HEAD + corepack enable + corepack prepare "pnpm@$DSH_PNPM_VERSION" --activate + pnpm --dir "$harness" install --frozen-lockfile --ignore-scripts + pnpm --dir "$harness" run build:lib + ln -sfn "$harness" "$GITHUB_WORKSPACE/../deepseek-harness" + node --check scripts/link-dsh-workspace.mjs + node scripts/link-dsh-workspace.mjs "$harness" "$GITHUB_WORKSPACE/node_modules" + echo "$GITHUB_WORKSPACE/node_modules/.bin" >> "$GITHUB_PATH" + + - name: Typecheck host and client + run: npm run typecheck + + - name: Run regression tests + run: npm test + + - name: Rebuild generated bundles + run: npm run build + + - name: Stage and verify generated bundles only + shell: bash + run: | + set -euo pipefail + git status --short + git add -- lib/index.cjs lib/index.mjs + git diff --cached --check + mapfile -t staged < <(git diff --cached --name-only) + for file in "${staged[@]}"; do + case "$file" in + lib/index.cjs|lib/index.mjs) ;; + *) + echo "Unexpected staged file: $file" >&2 + exit 1 + ;; + esac + done + + - name: Commit refreshed bundles + shell: bash + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + if git diff --cached --quiet; then + echo "Generated bundles are already current." + exit 0 + fi + git commit -m "build: refresh generated plugin bundles" + git push origin "HEAD:$GITHUB_REF_NAME" diff --git a/.impeccable.md b/.impeccable.md new file mode 100644 index 0000000..fc4acb8 --- /dev/null +++ b/.impeccable.md @@ -0,0 +1,17 @@ +## Design Context + +### Users +DeepSeek Harness developers and operators use this local-first settings page while diagnosing model usage, budget pressure, route concentration, retries, and context-compaction overhead. Their main job is to decide where Token consumption is rising and which exact provider/model route needs attention without exposing conversation content or mistaking estimates for billing facts. + +### Brand Personality +Native, restrained, trustworthy. The interface should feel like a precise DSH operational surface: calm under normal conditions, explicit when evidence is incomplete, and direct when a budget crosses a threshold. + +### Aesthetic Direction +Follow the host DSH Web theme, typography, controls, spacing, and light/dark behavior rather than creating a separate visual brand. Prefer compact data-dense rows, quiet controls, semantic warning states, and progressive disclosure. Avoid decorative dashboards, gradients, glass effects, excessive cards, and color-only status communication. + +### Design Principles +1. Evidence before decoration: every trend, forecast, and alert states its data scope and becomes unavailable when coverage cannot support it. +2. Native over novel: reuse DSH controls and existing plugin patterns so the page feels built into Settings. +3. Actionable hierarchy: surface exceeded and forecast-to-exceed budgets first, then warnings, then healthy routes. +4. Progressive detail: summarize status at a glance while preserving exact values, route identity, and calculation notes nearby. +5. Accessible in every state: pair color with text, retain keyboard operation, support narrow layouts, and respect the host theme and reduced-motion preference. diff --git a/README.md b/README.md index 1ddb26c..5d85c0a 100644 --- a/README.md +++ b/README.md @@ -36,22 +36,22 @@ | 领域 | 已实现能力 | | --- | --- | | **精确记账** | 分别记录未缓存输入、输出、缓存读取与缓存写入;`reasoningTokens` 已包含在输出中,不重复计算。流式 usage 先作为临时值,最终消息在同一 attempt 内覆盖它;重试与上下文压缩独立计数。 | -| **多维聚合** | 以 provider / model、会话与 UTC 日期聚合普通对话、每次重试和上下文压缩;旧用量无法归因时单独披露,不破坏总量守恒。 | +| **多维聚合** | 以 provider / model、会话、UTC 日期和日期×模型聚合普通对话、每次重试和上下文压缩;旧用量无法归因时单独披露,不破坏总量守恒。 | | **概览与活跃度** | 八项概览指标覆盖总量、输入、输出、缓存结构、公开费用、缓存读取避免费用、费率覆盖和有用量会话;30 周热力图支持四类 bucket 悬停与按日会话下钻。 | -| **趋势与数据质量** | 支持 7/30/90 日环比、活跃天数和峰值日;运行率、预算预测、异常检测与 AI 日趋势只在逐日 bucket 完整可靠时启用,避免旧版合成日期造成低估。 | -| **预算与异常** | 本机持久化滚动 30 日 Token 预算;按最近 7 个完整 UTC 日预测 30 日运行率,以中位数/MAD 稳健基线识别近期突增,并支持异常日下钻。 | +| **趋势与数据质量** | 支持全部路由或单个 provider/model 的 7/30/90 日环比、活跃天数和峰值日;日期×模型 bucket 必须同时与会话总量、模型总量和逐日总量守恒,覆盖不完整或不一致时自动降级,避免旧数据造成低估。 | +| **预算与异常** | 本机持久化全局及精确 provider/model 的滚动 30 日 Token 预算;模型预算提供 80% 预警、100% 超额和 7 个完整 UTC 日运行率预测,并只在日期×模型覆盖完整且守恒时启用。全局异常仍以中位数/MAD 识别近期突增并支持按日下钻。 | | **Agent 效率** | 展示模型尝试数、每次尝试 Token、每百次尝试压缩数、精确压缩 Token 占比、缓存读取占输入、Top 1/Top 3 路由集中度和未归因比例。 | | **公开价格估算** | 用静态公开费率计算已覆盖路由的 USD 估算与缓存读取避免费用,明确显示费率基准日、Token/路由覆盖和未覆盖项;不冒充 provider 账单。 | | **模型与会话工作流** | 模型表支持按总 Token、公开费用、每次记录调用 Token 或缓存占比排序;会话表支持搜索、渐进展开、直接打开会话,轨迹分析操作固定在第一列。 | -| **聚合导出** | 导出不含会话正文和标题的 JSON v2、每日 CSV 与模型 CSV;CSV 单元格防公式注入,JSON/模型 CSV 带公开费率覆盖和已覆盖路由估算。 | +| **聚合导出** | 导出不含会话正文和标题的 JSON v3、每日 CSV、模型 CSV 与日期×模型 CSV;JSON v3 显式记录逐日/日期×模型覆盖状态,不完整或守恒失败时禁用日期×模型 CSV,避免生成看似完整的低估数据。CSV 单元格防公式注入。 | | **AI 用量优化** | 使用目录自动预选的默认/首个路由,或改选任一当前可列出的已接入 provider/model,对总量、压缩、缓存、路由贡献、可靠日趋势、峰值和波动生成证据化建议;目录可刷新,单个 provider 枚举失败不影响其他路由。 | | **会话轨迹分析** | 可从设置页会话列表或对话页标题操作区启动同一 Host 流程;同时支持 live 与冷会话,确定性分析调用节点、重试、压缩、工具可靠性、速率、生命周期和 Token 对账。 | | **合规控制审计** | 配对审批请求与决定,统计单次允许、拒绝、取消、不可用、未闭合和孤立决策;单次决定不存在“永久允许”结果;独立的会话级 `approval/policy` 事件不在当前报告白名单中,因此持久策略明确标为不可用证据。报告强制区分观测证据、风险假设与不可用证据,不声称法律或认证结论。 | -| **实时分析进度** | 两类模型分析均显示准备/生成/整理阶段、等待时间、流块、字符数和输出 Token;provider usage 到达前明确标注估算值,到达后切换为精确值。最多同时维护 8 个请求级进度记录,结束即清理。 | +| **实时分析进度** | 两类模型分析均显示准备/生成/整理阶段、等待时间、流块、字符数和输出 Token;provider usage 到达前明确标注估算值,到达后切换为精确值。缺少终态 finish 的截断流会失败;成功或取消后立即停止轮询且不发布迟到快照。最多同时维护 8 个请求级进度记录,结束即清理。 | | **报告、导出与历史** | 使用 DSH Markdown 组件展示标题、列表、表格与代码;远程图片降级为链接、原始 HTML 转义为文本。两类报告均可导出;轨迹报告在当前浏览器配置中全局最多保存 24 条,界面按当前会话过滤查看并支持删除。 | | **紧凑、响应式与可访问 UI** | 跟随 DSH 中/英文界面并适配窄屏;大数字使用 `K` / `M` / `B` 且悬停保留精确值,轨迹摘要分为四组;加载状态提供 `aria-live`、`aria-busy` 和 reduced-motion 兼容。 | | **历史预热与生命周期** | Host 启动后顺序预热可读历史并写入 projection cache,不阻塞插件启动;模型目录、调用与中止信号绑定当前 LLM service 生命周期。 | -| **隐私与边界校验** | 持久 projection 只保存统计数据;模型仅接收最小聚合 DTO 或白名单轨迹元数据。私有 RPC 只允许 loopback 页面,Client 会验证报告 schema、时间戳、bucket、节点、signed delta 与最大节点引用。 | +| **隐私与边界校验** | 持久 projection 只保存统计数据;模型仅接收最小聚合 DTO 或白名单轨迹元数据。私有 RPC 只允许 loopback 页面,非法 bucket、日期、路由、进度 ID 和超限数组会在读取会话或调用 Adapter 前拒绝;Client 会验证报告 schema、时间戳、bucket、节点、signed delta 与最大节点引用。 | ### 该插件如何工作,以及不做什么 @@ -112,7 +112,7 @@ dsh plugin --profile web add ./dsh-token-usage ### 兼容性、存储与卸载 - 需要挂载完整 Client 服务的 DSH **Web profile**,并依赖 DSH `0.1.0-rc.6` 系列的 session、LLM、settings、projection 和 Web UI 服务;CLI 或非 Web profile 不提供仪表盘。 -- 数据分为三层:Host 的会话 projection 聚合统计、DSH settings 中的滚动 30 日预算(`token-usage.rolling30DayBudget`),以及当前浏览器 `localStorage` 中最多 24 条的轨迹报告(`dsh-token-usage.trajectory-history.v1`)。聚合 AI 用量报告不会持久化。 +- 数据分为三层:Host 的会话 projection 聚合统计、DSH settings 中的全局与精确路由滚动 30 日预算(`token-usage.rolling30DayBudget` / `token-usage.routeBudgets`),以及当前浏览器 `localStorage` 中最多 24 条的轨迹报告(`dsh-token-usage.trajectory-history.v1`)。聚合 AI 用量报告不会持久化。 - 卸载是移除插件挂载,并不是数据重置流程。若要减少本地残留,请先在轨迹历史中删除报告、将预算清零,再按 DSH 自身的 session/cache 保留策略处理 projection 数据。 ```powershell @@ -125,14 +125,14 @@ dsh plugin --profile web remove dsh-token-usage - **概览卡片**:总 Token、输入 Token、输出 Token、缓存读取占输入、有用量会话数,以及已覆盖路由的公开 USD 估算、缓存读取避免费用和 Token 费率覆盖率。 - **Token 活跃度与下钻**:最近 30 周按 UTC 日汇总的热力图;悬停方格查看四类 bucket,点击查看当天总量和贡献会话。旧版内置 projection 缺少逐日数据时,仍会以会话最后活动日保留历史总量,但该日期会从运行率/异常口径排除。 -- **周期趋势**:切换 7/30/90 日窗口,查看当前周期总量、环比、活跃天数与峰值日。 +- **周期趋势**:切换 7/30/90 日窗口,查看当前周期总量、环比、活跃天数与峰值日;日期×模型 bucket 完整时可按精确 provider/model 筛选,覆盖不完整时选择器会停用并说明原因。 - **用量信号**:仅在全部纳入统计的会话都有真实逐日 bucket 时,按最近 7 个完整 UTC 日计算日均和预计 30 日运行率;昨天的完整日会与此前 28 日至少 5 个活跃日的中位数/MAD 稳健基线比较,异常日可直接下钻会话。覆盖不完整时会明确显示不可用,而不是低估。 -- **30 日预算**:预算写入本机 DSH settings;逐日覆盖完整时显示滚动消耗比例、当前运行率的 30 日预测和可能的预测超额,填 0 或清空可关闭。 +- **30 日预算**:全局预算与精确 provider/model 预算写入本机 DSH settings。全局预算在逐日覆盖完整时显示消耗和预测;模型预算达到 80% 时预警、达到 100% 时标记超额,并按最近 7 个完整 UTC 日预测超额。模型预算只有在日期×模型 bucket 与会话/路由/日期三组总量全部守恒时才计算,配置仍可保留。 - **Agent 效率与归因**:显示模型尝试数、每次尝试 Token、每 100 次尝试压缩数、精确压缩 Token 占比、缓存读取占输入、Top 1/Top 3 路由集中度及未归因比例。 - **价格统计**:显示已覆盖路由的估算 USD 成本、缓存读取避免费用、按 Token 计算的费率覆盖率,以及每条模型路由的估算成本;未覆盖路由明确显示 `—`。 -- **AI Token 用量分析**:使用目录自动预选的默认/首个模型,或改选另一个已接入模型,按需生成总量、精确压缩开销、输入/输出/缓存、路由集中度、可靠日趋势、峰值、波动和 Token 优化建议报告。报告按 Markdown 结构渲染并可导出;生成中显示准备/生成/整理阶段、等待时间,以及估算或 provider 上报的精确输出 Token。模型目录可手动刷新;某个提供方暂时无法列出模型时,其他可用模型不受影响。 +- **AI Token 用量分析**:使用目录自动预选的默认/首个模型,或改选另一个已接入模型,按需生成总量、精确压缩开销、输入/输出/缓存、路由集中度、可靠日趋势、峰值、波动和 Token 优化建议报告。报告按 Markdown 结构渲染并可导出;生成中显示准备/生成/整理阶段、等待时间,以及估算或 provider 上报的精确输出 Token。模型目录可手动刷新;provider 目录并发读取并以 5 秒隔离挂起项,同一 provider 的未决枚举会在重复刷新间复用;挂起 30 秒后最多补发一次并行恢复调用,永久挂起时最多保留两次底层调用,避免无限累积;整个 RPC 短暂失败后也可直接重试,不让单个提供方阻塞其他可用模型。 - **模型用量**:按 provider / model 汇总普通模型尝试、上下文压缩次数、总量、输入与输出;可按总 Token、已覆盖的估算费用、每次记录调用 Token 或缓存读取占输入排序。 -- **聚合导出**:导出不含会话标题和正文的 JSON v2(含压缩 bucket、公开费率覆盖和路由估算)、每日 CSV 或模型 CSV;模型 CSV 含已覆盖路由的费用/缓存避免费用,CSV 单元格防公式注入。 +- **聚合导出**:导出不含会话标题和正文的 JSON v3(含压缩 bucket、日期×模型 bucket、逐日/日期×模型覆盖状态、公开费率覆盖和路由估算)、每日 CSV、模型 CSV 或日期×模型 CSV;日期×模型覆盖不完整或守恒失败时该 CSV 会停用。模型 CSV 含已覆盖路由的费用/缓存避免费用,CSV 单元格防公式注入。 - **会话记录**:轨迹分析操作位于表格第一列;可搜索会话标题、会话 ID 或模型路由,初始最多显示 50 条并可渐进展开,也可直接打开会话。 ### 统计口径 @@ -151,7 +151,7 @@ dsh plugin --profile web remove dsh-token-usage ## 📈 运行率、异常与 Agent 效率 - **运行率**:仅在逐日覆盖完整时,使用真实逐日 bucket,并取最近 7 个完整 UTC 日(不包含尚未结束的今天)计算日均,再乘以 30 得到滚动 30 日预测;它是 Token 运行率,不是账单预测。 -- **预算预测**:仅在已开启 Token 预算时比较预测值和预算。当前已超额与“按当前运行率将超额”会分别提示,不会自动阻止模型调用。 +- **预算预测**:全局预算使用完整逐日 bucket;精确模型预算使用完整且守恒的日期×模型 bucket。两者都分别提示当前超额和“按当前运行率将超额”;模型预算另在消耗达到 80% 时预警。预算始终是被动提示,不会自动阻止模型调用。 - **异常检测**:将昨天这个完整 UTC 日与此前 28 日内的活跃完整日比较。至少需要 5 个活跃基线日;使用活跃日中位数和 MAD,MAD 为 0 时采用 3×中位数阈值。异常提示会显示绝对超量和倍数,并可下钻现有的按日会话贡献。 - **效率/集中度**:Top 1/Top 3 是全部 Token 的路由份额;未归因用量会单独披露。每次模型尝试 Token 从可归因总量扣除精确压缩 Token 后计算,重试属于独立尝试。 - **会话工作流**:会话名称可直接打开当前仍在列表内的会话;若会话在点击前消失,保持设置页面并显示失败原因。初始列表只渲染 50 行,聚合统计仍覆盖全部会话。 @@ -173,7 +173,7 @@ dsh plugin --profile web remove dsh-token-usage - 缓存读取避免费用仅对已匹配路由计算:`cacheReadTokens × max(inputRate - cacheReadRate, 0) / 1M`;它比较同一静态公开表中的未缓存输入价,不是账单返还。 - OpenAI 路由没有单独公开的 cache-write 费率时,cache-write 按普通输入费率估算;每条路由的悬停说明会显示所用四项费率与基准日。 - 历史聚合会按当前内置静态表重估,不按事件发生日的历史价格还原;因此暂不提供 USD 预算。 -- 价格计算、页面展示和 JSON v2/模型 CSV 只在本地浏览器中使用已持久化的聚合 bucket;价格匹配、覆盖率、估算 USD 和缓存读取避免费用都不会作为外部分析模型的证据,也不会新增会话正文、提示词或响应数据的收集。 +- 价格计算、页面展示和 JSON v3/模型 CSV 只在本地浏览器中使用已持久化的聚合 bucket;价格匹配、覆盖率、估算 USD 和缓存读取避免费用都不会作为外部分析模型的证据,也不会新增会话正文、提示词或响应数据的收集。 ## 🤖 AI Token 用量分析 @@ -262,7 +262,15 @@ flowchart LR 本插件吸收了主流 Agent 可观测性产品对 Token、成本、缓存和聚合趋势的做法,例如 [LangSmith cost tracking](https://docs.langchain.com/langsmith/cost-tracking)、[OpenAI Agents SDK usage](https://openai.github.io/openai-agents-python/usage/)、[Langfuse token/cost tracking](https://python-sdk-v2.docs-snapshot.langfuse.com/docs/observability/features/token-and-cost-tracking/) 和 [Phoenix LLM metrics](https://arize.com/docs/phoenix/tracing/llm-traces/metrics)。预算和异常部分参考 [FinOps Budgeting](https://www.finops.org/framework/capabilities/budgeting/)、[Anomaly Management](https://www.finops.org/framework/capabilities/anomaly-management/) 与 [MAD 的稳健统计定义](https://itl.nist.gov/div898//software/dataplot/refman2/auxillar/mad.htm)。 -取舍是有意的:Host projection 只持久化聚合 bucket、日期、计数和路由归因;聚合 AI 报告只在当前页面内存中,用户显式生成的轨迹报告才进入当前浏览器 `localStorage`。插件不保存请求正文、逐请求正文日志、日期×模型交叉明细或人员/组织归因。现有投影没有日期×模型维度,因此异常日只支持会话贡献下钻,不声称模型级日归因。 +取舍是有意的:Host projection 只持久化聚合 bucket、日期、计数、路由归因和日期×模型交叉 bucket;聚合 AI 报告只在当前页面内存中,用户显式生成的轨迹报告才进入当前浏览器 `localStorage`。插件不保存请求正文、逐请求正文日志或人员/组织归因。模型趋势使用精确日期×模型维度,但异常日当前仍只支持会话贡献下钻,不把全局 MAD 结论冒充为模型级异常。 + +## 🛣️ 后续优化路线 + +已完成日期×模型归因与三维守恒门槛、精确路由 Token 预算和 80%/100%/预测告警、覆盖状态导出、目录失败恢复、分析终态校验,以及非法 RPC 早拒绝、轮询清理和证据上限。后续按以下顺序推进: + +1. **P0 · 可信成本与预算策略**:把静态费率表深化为带生效日期、来源和版本的本地 `FeeCatalog`,支持用户精确路由覆盖;在现有 provider/model Token 预算之上增加可信 USD 预算、阈值穿越冷却和本地告警历史。成本仍区分 provider 上报、用户覆盖和公开估算,不冒充账单。参考 [LangSmith cost tracking](https://docs.langchain.com/langsmith/cost-tracking)、[OpenAI Agents SDK usage](https://openai.github.io/openai-agents-python/usage/) 与 [FinOps Anomaly Management](https://www.finops.org/framework/capabilities/anomaly-management/)。 +2. **P1 · 数据质量与查询性能**:集中展示逐日/费率/路由覆盖、未知模型、迟到与不一致记录;把超大的 Dashboard 聚合 Implementation 收进更深的聚合 Module,并为历史预热增加有界并发、增量进度和真实 Web profile E2E。模型级异常仍只在对应日期×模型覆盖完整时启用。 +3. **P2 · 互操作、可访问性与治理**:提供带 schema/时区/费率版本 manifest 的 JSONL 或 [OpenTelemetry GenAI semantic conventions](https://opentelemetry.io/docs/specs/semconv/gen-ai/) 兼容映射;为图形提供等价可排序表格,增强高对比度与键盘说明,并增加按范围删除、保留期限和导出前字段预览。远程 exporter 保持默认关闭和显式 opt-in。 ## 🔄 更新与热加载 @@ -277,7 +285,7 @@ flowchart LR | 现象 | 检查方式 | | --- | --- | | 没有可选分析模型 | 刷新模型目录;确认至少一个已接入 provider 能列出模型。 | -| 趋势、运行率或异常显示不可用 | 逐日 bucket 覆盖不足时会刻意停用这些结论;等待完整数据,或查看历史投影是否仍为未归因。 | +| 趋势、预算、运行率或异常显示不可用 | 逐日或日期×模型 bucket 覆盖不足/守恒失败时会刻意停用对应结论;等待完整数据,或查看历史投影是否仍为未归因。 | | 费用显示 `—` | 当前 provider/model 没有匹配内置静态公开费率;这不是账单或调用失败。 | | 轨迹历史无法保存 | 检查浏览器是否禁用/耗尽 `localStorage`;当前报告仍可查看和导出。 | @@ -297,7 +305,7 @@ npm run build ### 测试与费率维护边界 -自动化覆盖 projection、聚合、分析/RPC、报告安全、浏览器历史和组件行为,但不是完整的 DSH Web E2E。真实 profile 的安装激活、HMR、Slot/RPC/Schema 版本互操作、英文文案/CSS/reduced-motion 视觉,以及历史预热的全部 fail-soft 分支仍需要人工或浏览器 E2E 验证。费率测试覆盖代表性条目而非完整公开价格目录;修改 `src/pricing.ts` 的任一行时,应逐项复核公开来源、更新基准日/README,并在发布前完成真实 Web profile 验证。 +自动化覆盖 projection、聚合、全局/精确路由预算、覆盖状态导出、分析/RPC、报告安全、浏览器历史和组件行为,包括日期×模型三维守恒降级、80%/100%/预测预算状态、非法 RPC 早拒绝、目录超时后恢复、分析轮询清理,以及 48 路由/366 日证据上限;但不是完整的 DSH Web E2E。真实 profile 的安装激活、HMR、Slot/RPC/Schema 版本互操作、英文文案/CSS/reduced-motion 视觉,以及历史预热的全部 fail-soft 分支仍需要人工或浏览器 E2E 验证。费率测试覆盖代表性条目而非完整公开价格目录;修改 `src/pricing.ts` 的任一行时,应逐项复核公开来源、更新基准日/README,并在发布前完成真实 Web profile 验证。 ## ⚠️ 已知限制 @@ -305,11 +313,12 @@ npm run build - 历史预热依赖 DSH session projection cache。预热完成前,仅有内置 projection 的旧会话会被显示为“未归因用量”;刷新后可读取新的模型明细。 - 单个损坏或不可读取的历史会话只会记录警告,不会阻止插件启动。 - 热力图按持久事件的 UTC 日期统计;旧版内置 projection 缺少逐日数据时,会暂按会话最后活动日归档。 -- 运行率和异常检测会排除上述旧版合成日期,只使用真实逐日 bucket;覆盖缺失或仅部分可靠时都会显示不可用,而不是以不完整数据给出偏低结果。异常检测需要昨天有记录且此前 28 日至少 5 个活跃日,不足基线时不报告“正常”。预测和异常不会做模型级逐日归因。 +- 运行率和异常检测会排除上述旧版合成日期,只使用真实逐日 bucket;模型趋势和模型预算进一步要求日期×模型 bucket 与会话总量、模型总量和逐日总量全部守恒。覆盖缺失或不一致时都会显示不可用,而不是以不完整数据给出偏低结果。异常检测需要昨天有记录且此前 28 日至少 5 个活跃日,目前仍是全局口径;模型预算是实时派生状态,暂不保存阈值穿越历史或发送系统通知。 - 轨迹报告是模型辅助的资源效率解释,不是策略执行器或合规证明;确定性节点、provider bucket 和对账结果优先于模型推断。 - provider 当前不提供系统、用户、历史、检索、工具和子代理输入的独立 Token bucket,因此这些细分不会估算;超长元数据轨迹的中段会明确标为不可用。 - 插件不建设人员、团队、部门、组织、成本中心或行为画像维度;AI 用量报告刷新后消失,轨迹报告只保存在当前浏览器配置中、跨全部会话全局最多 24 条,不跨浏览器同步,也暂不生成历史趋势对比。 - 分析调用的 Token 在生成进度和完成报告中显示,不计入持久化仪表盘。provider usage 到达前的输出 Token 是基于字符的近似值,不可用于账单。 +- DSH 当前的 provider 模型目录 Interface 不接受 `AbortSignal`。挂起调用会在刷新间复用,30 秒冷却后允许一次并行恢复尝试;每个 provider 最多保留两个未决底层调用。若两次都永久挂起,该 provider 仍需等待其中一次结算或 LLM runtime 重建。 - AI 用量分析只可选择当前能由已接入 provider 列出的模型;每日趋势证据最多传递最新 366 天。内置 USD 费率表不是实时账单或汇率服务,仅按文档列出的 OpenAI 路由标签本地匹配;模型建议不接收价格证据,也不替代账单、延迟或质量观测。 ## 📄 License diff --git a/lib/client.js b/lib/client.js index b4d2d9e..f8836a1 100644 --- a/lib/client.js +++ b/lib/client.js @@ -27,7 +27,7 @@ window.__ModuleLoader__.load({ }; } /** Full request/response total without counting reasoning output twice. */ - function totalTokens$4(usage) { + function totalTokens$5(usage) { return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + usage.outputTokens; } /** Stable UTC day key shared with the durable projection. */ @@ -58,8 +58,8 @@ window.__ModuleLoader__.load({ const active = currentDates.map((date) => ({ date, usage: { ...byDate.get(date) ?? zeroBuckets$1() } - })).filter((record) => totalTokens$4(record.usage) > 0); - const peak = active.reduce((highest, record) => highest === void 0 || totalTokens$4(record.usage) > totalTokens$4(highest.usage) ? record : highest, void 0); + })).filter((record) => totalTokens$5(record.usage) > 0); + const peak = active.reduce((highest, record) => highest === void 0 || totalTokens$5(record.usage) > totalTokens$5(highest.usage) ? record : highest, void 0); return { days, usage: aggregateDates(byDate, currentDates), @@ -82,7 +82,7 @@ window.__ModuleLoader__.load({ function runRateInsight(records, now = Date.now()) { const byDate = new Map(records.map((record) => [record.date, record.usage])); const dates = completeDaysEndingBefore(now, 7); - const averageDailyTokens = dates.reduce((sum, date) => sum + totalTokens$4(byDate.get(date) ?? zeroBuckets$1()), 0) / dates.length; + const averageDailyTokens = dates.reduce((sum, date) => sum + totalTokens$5(byDate.get(date) ?? zeroBuckets$1()), 0) / dates.length; return { observedDays: dates.length, averageDailyTokens, @@ -94,8 +94,8 @@ window.__ModuleLoader__.load({ const byDate = new Map(records.map((record) => [record.date, record.usage])); const [date] = completeDaysEndingBefore(now, 1); if (date === void 0) return void 0; - const tokens = totalTokens$4(byDate.get(date) ?? zeroBuckets$1()); - const activeBaseline = datesEndingOn(now - 2 * 864e5, 28).map((baselineDate) => totalTokens$4(byDate.get(baselineDate) ?? zeroBuckets$1())).filter((value) => value > 0); + const tokens = totalTokens$5(byDate.get(date) ?? zeroBuckets$1()); + const activeBaseline = datesEndingOn(now - 2 * 864e5, 28).map((baselineDate) => totalTokens$5(byDate.get(baselineDate) ?? zeroBuckets$1())).filter((value) => value > 0); if (tokens === 0 || activeBaseline.length < 5) return void 0; const baselineMedianTokens = median(activeBaseline); const baselineMadTokens = median(activeBaseline.map((value) => Math.abs(value - baselineMedianTokens))); @@ -117,18 +117,18 @@ window.__ModuleLoader__.load({ function dailyContributors(sessions, date) { return sessions.flatMap((session) => { const record = session.days.find((day) => day.date === date); - if (record === void 0 || totalTokens$4(record.usage) === 0) return []; + if (record === void 0 || totalTokens$5(record.usage) === 0) return []; return [{ id: session.id, title: session.title, usage: { ...record.usage } }]; - }).sort((left, right) => totalTokens$4(right.usage) - totalTokens$4(left.usage) || left.title.localeCompare(right.title) || left.id.localeCompare(right.id)); + }).sort((left, right) => totalTokens$5(right.usage) - totalTokens$5(left.usage) || left.title.localeCompare(right.title) || left.id.localeCompare(right.id)); } //#endregion //#region src/client/efficiency.ts /** Return one full disjoint-bucket total. */ - function totalTokens$3(usage) { + function totalTokens$4(usage) { return usage.uncachedInputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens; } /** Return the prompt-side total across uncached and cache buckets. */ @@ -141,15 +141,15 @@ window.__ModuleLoader__.load({ } /** Derive request efficiency, compaction overhead, and stable top-route shares. */ function usageEfficiencyInsight(usage, compactionUsage, models, assistantAttempts, compactionAttempts) { - const total = totalTokens$3(usage); + const total = totalTokens$4(usage); const input = inputTokens$1(usage); const attributed = models.filter((model) => !isUnattributed$1(model)); - const unattributedTokens = models.filter(isUnattributed$1).reduce((sum, model) => sum + totalTokens$3(model.usage), 0); - const assistantTokens = Math.max(0, total - totalTokens$3(compactionUsage)); + const unattributedTokens = models.filter(isUnattributed$1).reduce((sum, model) => sum + totalTokens$4(model.usage), 0); + const assistantTokens = Math.max(0, total - totalTokens$4(compactionUsage)); const topRoutes = attributed.map((model) => ({ provider: model.provider, model: model.model, - tokens: totalTokens$3(model.usage) + tokens: totalTokens$4(model.usage) })).sort((left, right) => right.tokens - left.tokens || left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)).slice(0, 3).map((route) => ({ ...route, share: total === 0 ? 0 : route.tokens / total @@ -160,7 +160,7 @@ window.__ModuleLoader__.load({ assistantTokens, tokensPerAssistantAttempt: assistantAttempts === 0 || unattributedTokens > 0 ? void 0 : assistantTokens / assistantAttempts, compactionsPerHundredAssistantAttempts: assistantAttempts === 0 ? void 0 : compactionAttempts / assistantAttempts * 100, - compactionTokenShare: total === 0 ? void 0 : totalTokens$3(compactionUsage) / total, + compactionTokenShare: total === 0 ? void 0 : totalTokens$4(compactionUsage) / total, cacheReadInputShare: input === 0 ? void 0 : usage.cacheReadTokens / input, cacheWriteInputShare: input === 0 ? void 0 : usage.cacheWriteTokens / input, uncachedInputShare: input === 0 ? void 0 : usage.uncachedInputTokens / input, @@ -169,6 +169,38 @@ window.__ModuleLoader__.load({ topRoutes }; } + /** Sum all four disjoint provider Token buckets. */ + function totalTokens$3(usage) { + return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + usage.outputTokens; + } + /** Evaluate one route budget from exact date-by-model buckets. */ + function routeBudgetInsight(budget, records, now = Date.now()) { + const days = records.filter((record) => record.provider === budget.provider && record.model === budget.model).map((record) => ({ + date: record.date, + usage: { ...record.usage } + })); + const usedTokens = totalTokens$3(periodInsight(days, 30, now).usage); + const projectedThirtyDayTokens = runRateInsight(days, now).projectedThirtyDayTokens; + const ratio = usedTokens / budget.rolling30DayBudget; + const status = usedTokens >= budget.rolling30DayBudget ? "exceeded" : projectedThirtyDayTokens > budget.rolling30DayBudget ? "forecast-exceeded" : ratio >= .8 ? "warning" : "healthy"; + return { + ...budget, + usedTokens, + projectedThirtyDayTokens, + ratio, + status + }; + } + const STATUS_PRIORITY = { + exceeded: 3, + "forecast-exceeded": 2, + warning: 1, + healthy: 0 + }; + /** Evaluate and risk-sort every persisted exact-route budget without mutating settings. */ + function routeBudgetInsights(budgets, records, now = Date.now()) { + return budgets.map((budget) => routeBudgetInsight(budget, records, now)).sort((left, right) => STATUS_PRIORITY[right.status] - STATUS_PRIORITY[left.status] || right.ratio - left.ratio || left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)); + } //#endregion //#region src/pricing.ts const TOKENS_PER_MILLION = 1e6; @@ -411,18 +443,28 @@ window.__ModuleLoader__.load({ function tokenUsageExport(source, generatedAt) { const pricing = copiedPricing(source.models); return { - schema: "dsh-token-usage/export-v2", + schema: "dsh-token-usage/export-v3", generatedAt, timezone: "UTC", totals: copyBuckets(source.usage), compactionUsage: copyBuckets(source.compactionUsage), pricingCatalogAsOf: PUBLIC_PRICE_CATALOG_AS_OF, pricing, + coverage: { + daily: source.dailyCoverage, + modelDaily: source.modelDailyCoverage + }, models: pricing.models.slice().sort((left, right) => left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)), days: source.days.map((day) => ({ date: day.date, usage: copyBuckets(day.usage) - })).sort((left, right) => left.date.localeCompare(right.date)) + })).sort((left, right) => left.date.localeCompare(right.date)), + modelDays: source.modelDays.map((modelDay) => ({ + provider: modelDay.provider, + model: modelDay.model, + date: modelDay.date, + usage: copyBuckets(modelDay.usage) + })).sort((left, right) => left.date.localeCompare(right.date) || left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)) }; } /** Serialize the versioned aggregate-only export document. */ @@ -431,7 +473,7 @@ window.__ModuleLoader__.load({ } /** Prevent spreadsheet applications from interpreting an untrusted cell as a formula. */ function spreadsheetText(value) { - return /^[=+\-@]/.test(value) ? `'${value}` : value; + return /^[\u0000-\u0020]*[=+\-@]/.test(value) ? `'${value}` : value; } /** Escape one scalar value as a CSV cell. */ function csvCell(value) { @@ -462,6 +504,31 @@ window.__ModuleLoader__.load({ day.usage.uncachedInputTokens + day.usage.cacheReadTokens + day.usage.cacheWriteTokens + day.usage.outputTokens ])]); } + /** Export exact route-by-day buckets only when the source proves complete coverage. */ + function modelDailyUsageCsv(source) { + if (source.modelDailyCoverage !== "complete") throw new Error("Date-by-model CSV requires complete and conserved route-day coverage."); + return csv([[ + "date", + "provider", + "model", + "uncachedInputTokens", + "outputTokens", + "cacheReadTokens", + "cacheWriteTokens", + "inputTokens", + "totalTokens" + ], ...source.modelDays.slice().sort((left, right) => left.date.localeCompare(right.date) || left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)).map((modelDay) => [ + modelDay.date, + modelDay.provider, + modelDay.model, + modelDay.usage.uncachedInputTokens, + modelDay.usage.outputTokens, + modelDay.usage.cacheReadTokens, + modelDay.usage.cacheWriteTokens, + modelDay.usage.uncachedInputTokens + modelDay.usage.cacheReadTokens + modelDay.usage.cacheWriteTokens, + modelDay.usage.uncachedInputTokens + modelDay.usage.cacheReadTokens + modelDay.usage.cacheWriteTokens + modelDay.usage.outputTokens + ])]); + } /** Export model aggregate buckets without session identity or conversation content. */ function modelUsageCsv(source) { const models = tokenUsageCostSummary(source.models).models; @@ -584,7 +651,7 @@ window.__ModuleLoader__.load({ } //#endregion //#region \0dsh-css:C:\Users\zhanglimin202307\Desktop\dsh\dsh-token-usage\src\client\TokenUsageSection.module.css.mjs - const css = ".hGAOSW_section{width:100%;max-width:960px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:22px;display:flex}.hGAOSW_header{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.hGAOSW_header h2,.hGAOSW_header p,.hGAOSW_block h3,.hGAOSW_status{margin:0}.hGAOSW_header h2{font-size:18px;font-weight:600;line-height:26px}.hGAOSW_header p{max-width:720px;color:var(--dsw-alias-label-tertiary);margin-top:5px;font-size:13px;line-height:20px}.hGAOSW_metrics{grid-template-columns:repeat(5,minmax(0,1fr));gap:10px;display:grid}.hGAOSW_metric{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;gap:7px;min-width:0;padding:13px 14px;display:flex}.hGAOSW_metric span{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;line-height:16px;overflow:hidden}.hGAOSW_metric strong{font-variant-numeric:tabular-nums;text-overflow:ellipsis;font-size:20px;line-height:26px;overflow:hidden}.hGAOSW_activity{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;gap:10px;min-width:0;padding:14px;display:flex}.hGAOSW_activityHead{justify-content:space-between;align-items:flex-end;gap:14px;display:flex}.hGAOSW_activityHead h3,.hGAOSW_activityHead p{margin:0}.hGAOSW_activityHead h3{font-size:14px;font-weight:600;line-height:22px}.hGAOSW_activityHead p{color:var(--dsw-alias-label-tertiary);margin-top:2px;font-size:11px;line-height:17px}.hGAOSW_activityGrid{box-sizing:border-box;grid-template-rows:repeat(7,minmax(0,1fr));grid-template-columns:repeat(30,minmax(0,1fr));grid-auto-flow:column;gap:3px;width:100%;min-width:0;padding:2px;display:grid}.hGAOSW_activityCell,.hGAOSW_activityLegend i{background:var(--dsw-alias-bg-module-platform);border:0;border-radius:2px;flex:none;display:block}.hGAOSW_activityCell{aspect-ratio:1;cursor:pointer;width:100%;min-width:0;padding:0}.hGAOSW_activityCell:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.hGAOSW_activityCell[data-selected=true]{box-shadow:0 0 0 2px var(--dsw-alias-label-primary)}.hGAOSW_activityLegend i{width:10px;height:10px}.hGAOSW_activityCell[data-future=true]{cursor:default;background:0 0}.hGAOSW_activityCell[data-level=\"1\"],.hGAOSW_activityLegend i[data-level=\"1\"]{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 22%, var(--dsw-alias-bg-module-platform))}.hGAOSW_activityCell[data-level=\"2\"],.hGAOSW_activityLegend i[data-level=\"2\"]{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 42%, var(--dsw-alias-bg-module-platform))}.hGAOSW_activityCell[data-level=\"3\"],.hGAOSW_activityLegend i[data-level=\"3\"]{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 64%, var(--dsw-alias-bg-module-platform))}.hGAOSW_activityCell[data-level=\"4\"],.hGAOSW_activityLegend i[data-level=\"4\"]{background:var(--dsw-alias-state-business-primary)}.hGAOSW_activityLegend{color:var(--dsw-alias-label-tertiary);white-space:nowrap;align-items:center;gap:4px;font-size:10px;line-height:14px;display:flex}.hGAOSW_insights,.hGAOSW_budget,.hGAOSW_dayDrilldown{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;gap:12px;min-width:0;padding:14px;display:flex}.hGAOSW_insights h3,.hGAOSW_budget h3,.hGAOSW_dayDrilldown h3,.hGAOSW_insights p,.hGAOSW_budget p,.hGAOSW_dayDrilldown p{margin:0}.hGAOSW_insights h3,.hGAOSW_budget h3,.hGAOSW_dayDrilldown h3{font-size:14px;font-weight:600;line-height:22px}.hGAOSW_insights .hGAOSW_blockHead p,.hGAOSW_budget .hGAOSW_blockHead p,.hGAOSW_dayDrilldown .hGAOSW_blockHead p{color:var(--dsw-alias-label-tertiary);margin-top:2px;font-size:11px;line-height:17px}.hGAOSW_detailMetrics{grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;display:grid}.hGAOSW_rangeTabs,.hGAOSW_exportControls{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.hGAOSW_rangeTabs button,.hGAOSW_exportControls button,.hGAOSW_quietButton{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);min-height:30px;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;border-radius:7px;padding:0 9px;font-size:11px}.hGAOSW_rangeTabs button[aria-pressed=true]{border-color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 12%, var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-state-business-primary)}.hGAOSW_rangeTabs button:focus-visible,.hGAOSW_exportControls button:focus-visible,.hGAOSW_quietButton:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.hGAOSW_exportControls{justify-content:flex-end}.hGAOSW_exportControls>span{color:var(--dsw-alias-label-tertiary);font-size:11px}.hGAOSW_insightNote{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.hGAOSW_anomalyNotice{color:var(--dsw-alias-state-error-primary);justify-content:space-between;align-items:center;gap:10px;font-size:11px;line-height:17px;display:flex}.hGAOSW_anomalyNotice p{margin:0}.hGAOSW_budgetInput{color:var(--dsw-alias-label-tertiary);white-space:nowrap;align-items:center;gap:7px;font-size:11px;display:flex}.hGAOSW_budgetInput input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:128px;height:30px;color:var(--dsw-alias-label-primary);font:inherit;font-variant-numeric:tabular-nums;border-radius:7px;outline:none;padding:0 8px;font-size:12px}.hGAOSW_budgetInput input:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb, var(--dsw-alias-state-business-primary) 18%, transparent)}.hGAOSW_budgetProgress{align-items:center;gap:10px;display:flex}.hGAOSW_budgetProgress progress{width:min(320px,55%);height:8px;accent-color:var(--dsw-alias-state-business-primary)}.hGAOSW_budgetProgress strong{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;font-size:12px}.hGAOSW_budgetWarning{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}.hGAOSW_contributors{flex-direction:column;gap:7px;display:flex}.hGAOSW_contributors>strong{color:var(--dsw-alias-label-secondary);font-size:12px}.hGAOSW_contributors ol{gap:5px;margin:0;padding:0;list-style:none;display:grid}.hGAOSW_contributors li{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:7px;justify-content:space-between;align-items:center;gap:12px;padding:7px 9px;font-size:12px;display:flex}.hGAOSW_contributors li>span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.hGAOSW_block{flex-direction:column;gap:10px;min-width:0;display:flex}.hGAOSW_block h3{font-size:14px;font-weight:600;line-height:22px}.hGAOSW_blockHead{justify-content:space-between;align-items:center;gap:16px;display:flex}.hGAOSW_blockHead input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:min(280px,45%);height:34px;color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;outline:none;padding:0 11px;font-size:12px}.hGAOSW_blockHead input::placeholder{color:var(--dsw-alias-label-tertiary)}.hGAOSW_blockHead input:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb, var(--dsw-alias-state-business-primary) 18%, transparent)}.hGAOSW_tableWrap{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;min-width:0;overflow:auto}.hGAOSW_tableWrap table{border-collapse:collapse;width:100%;min-width:0;font-size:12px;line-height:18px}.hGAOSW_tableWrap .hGAOSW_modelTable{table-layout:fixed;min-width:580px}.hGAOSW_tableWrap .hGAOSW_sessionTable{min-width:780px}.hGAOSW_modelTable th:first-child,.hGAOSW_modelTable td:first-child{width:30%}.hGAOSW_modelTable th:nth-child(2),.hGAOSW_modelTable td:nth-child(2){width:18%}.hGAOSW_tableWrap th,.hGAOSW_tableWrap td{border-bottom:1px solid var(--dsw-alias-border-l1);text-align:right;vertical-align:middle;white-space:nowrap;padding:10px 12px}.hGAOSW_tableWrap th{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:500}.hGAOSW_tableWrap th:first-child,.hGAOSW_tableWrap td:first-child{text-align:left;max-width:270px}.hGAOSW_tableWrap tbody tr:last-child td{border-bottom:0}.hGAOSW_tableWrap tbody tr:hover td{background:var(--dsw-alias-interactive-bg-hover)}.hGAOSW_tableWrap td{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}.hGAOSW_tableWrap td strong,.hGAOSW_tableWrap td span{text-overflow:ellipsis;max-width:260px;display:block;overflow:hidden}.hGAOSW_tableWrap td strong{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600}.hGAOSW_tableWrap td span{color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:15px}.hGAOSW_tableWrap td .hGAOSW_tokenValue{max-width:none;color:inherit;font-size:inherit;line-height:inherit;display:inline}.hGAOSW_tableWrap td .hGAOSW_cacheDetail{margin-top:2px}.hGAOSW_analysisEmpty,.hGAOSW_analysisError,.hGAOSW_analysisPanel{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;gap:12px;min-width:0;padding:14px;display:flex}.hGAOSW_analysisEmpty{border-style:dashed}.hGAOSW_analysisError{border-color:var(--dsw-alias-state-error-primary)}.hGAOSW_analysisEmpty h3,.hGAOSW_analysisEmpty p,.hGAOSW_analysisError h3,.hGAOSW_analysisError p,.hGAOSW_analysisPanel h3,.hGAOSW_analysisPanel p{margin:0}.hGAOSW_analysisEmpty h3,.hGAOSW_analysisError h3,.hGAOSW_analysisPanel h3{font-size:14px;font-weight:600;line-height:22px}.hGAOSW_analysisEmpty p,.hGAOSW_analysisError p,.hGAOSW_analysisPanel .hGAOSW_blockHead p,.hGAOSW_analysisPrivacy{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.hGAOSW_analysisError p,.hGAOSW_analysisWarning{color:var(--dsw-alias-state-error-primary)}.hGAOSW_analysisCost{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;padding:5px 9px;font-size:11px}.hGAOSW_analysisReport{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-module-platform);max-height:640px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;border-radius:10px;margin:0;padding:18px 20px;font-size:13px;line-height:1.75;overflow:auto}.hGAOSW_analysisReport h1,.hGAOSW_analysisReport h2,.hGAOSW_analysisReport h3,.hGAOSW_analysisReport h4{color:var(--dsw-alias-label-primary);margin:1.25em 0 .55em;line-height:1.35}.hGAOSW_analysisReport h1:first-child,.hGAOSW_analysisReport h2:first-child,.hGAOSW_analysisReport h3:first-child,.hGAOSW_analysisReport p:first-child{margin-top:0}.hGAOSW_analysisReport p,.hGAOSW_analysisReport ul,.hGAOSW_analysisReport ol,.hGAOSW_analysisReport blockquote,.hGAOSW_analysisReport table{margin:.65em 0}.hGAOSW_analysisReport blockquote{border-left:3px solid var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-tertiary);padding-left:12px}.hGAOSW_analysisReport table{border-collapse:collapse;width:100%;font-size:12px}.hGAOSW_analysisReport th,.hGAOSW_analysisReport td{border:1px solid var(--dsw-alias-border-l2);text-align:left;vertical-align:top;padding:7px 9px}.hGAOSW_analysisReport th{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary)}.hGAOSW_analysisReport code{background:var(--dsw-alias-bg-layer-3);border-radius:4px;padding:1px 4px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.92em}.hGAOSW_analysisHeaderActions{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:8px;display:flex}.hGAOSW_analysisSummaryGrid{grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;display:grid}.hGAOSW_analysisSummaryGroup{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);border-radius:9px;min-width:0;padding:10px 12px}.hGAOSW_analysisSummaryGroup h4{color:var(--dsw-alias-label-primary);margin:0 0 6px;font-size:12px;line-height:18px}.hGAOSW_analysisSummaryGroup dl{gap:4px;margin:0;display:grid}.hGAOSW_analysisSummaryGroup dl>div{justify-content:space-between;align-items:baseline;gap:10px;display:flex}.hGAOSW_analysisSummaryGroup dt{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.hGAOSW_analysisSummaryGroup dd{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;margin:0;font-size:12px;font-weight:600}.hGAOSW_analysisLoading{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;align-items:flex-start;gap:12px;padding:14px;display:flex}.hGAOSW_analysisSpinner{border:2px solid var(--dsw-alias-border-l2);border-top-color:var(--dsw-alias-state-business-primary);border-radius:50%;flex:none;width:18px;height:18px;margin-top:2px;animation:.85s linear infinite hGAOSW_token-analysis-spin}.hGAOSW_analysisLoadingBody{flex:1;gap:6px;min-width:0;display:grid}.hGAOSW_analysisLoadingBody h3,.hGAOSW_analysisLoadingBody p{margin:0}.hGAOSW_analysisLoadingBody h3{color:var(--dsw-alias-label-primary);font-size:14px}.hGAOSW_analysisLoadingBody p,.hGAOSW_analysisProgressMeta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.hGAOSW_analysisProgressMeta{flex-wrap:wrap;gap:10px;display:flex}.hGAOSW_analysisProgressMeta strong{color:var(--dsw-alias-state-business-primary)}.hGAOSW_analysisLoadingBody progress{width:100%;height:7px;accent-color:var(--dsw-alias-state-business-primary)}@keyframes hGAOSW_token-analysis-spin{to{transform:rotate(360deg)}}.hGAOSW_analysisWarning{font-size:11px;line-height:18px}.hGAOSW_modelSort{color:var(--dsw-alias-label-tertiary);align-items:center;gap:6px;font-size:11px;display:flex}.hGAOSW_modelSort select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);min-width:116px;color:var(--dsw-alias-label-primary);font:inherit;border-radius:7px;padding:5px 7px;font-size:12px}.hGAOSW_modelSort select:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.hGAOSW_sessionLink{max-width:240px;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;background:0 0;border:0;padding:0;font-weight:600;text-decoration:underline #0000;display:block;overflow:hidden}.hGAOSW_sessionLink:hover{text-decoration-color:currentColor}.hGAOSW_sessionLink:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;border-radius:2px}.hGAOSW_analysisButton{border:1px solid var(--dsw-alias-state-business-primary);background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 10%, var(--dsw-alias-bg-layer-1));min-height:28px;color:var(--dsw-alias-state-business-primary);font:inherit;cursor:pointer;border-radius:7px;padding:0 9px;font-size:11px}.hGAOSW_analysisButton:disabled{cursor:wait;opacity:.65}.hGAOSW_analysisButton:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.hGAOSW_pricingNotice{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-tertiary);border-radius:10px;align-items:baseline;gap:8px;padding:10px 12px;font-size:11px;line-height:18px;display:flex}.hGAOSW_pricingNotice strong{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-weight:600}.hGAOSW_pricingNotice p{margin:0}.hGAOSW_priceUnknown{color:var(--dsw-alias-label-tertiary)}.hGAOSW_priceValue{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap}.hGAOSW_analysisModelSelect{min-width:180px;color:var(--dsw-alias-label-tertiary);gap:4px;font-size:11px;line-height:16px;display:grid}.hGAOSW_analysisModelSelect select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);min-width:0;color:var(--dsw-alias-label-primary);font:inherit;border-radius:7px;padding:5px 7px;font-size:12px}.hGAOSW_analysisModelSelect select:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.hGAOSW_analysisScope{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.hGAOSW_analysisErrorText{color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:18px}.hGAOSW_conversationAnalysisButton{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);height:28px;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;border-radius:7px;padding:0 9px;font-size:11px}.hGAOSW_conversationAnalysisButton:hover{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.hGAOSW_conversationAnalysisButton:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.hGAOSW_analysisDialog{width:min(820px,100%);max-height:min(84dvh,780px)}.hGAOSW_analysisDialogContent{overscroll-behavior:contain;min-height:0;overflow-y:auto}.hGAOSW_analysisDialog .hGAOSW_analysisReport{max-height:min(48dvh,460px)}.hGAOSW_conversationAnalysisControls{flex-wrap:wrap;align-items:end;gap:10px;margin-bottom:12px;display:flex}.hGAOSW_analysisHistory{border-top:1px solid var(--dsw-alias-border-l1);gap:9px;margin-top:16px;padding-top:14px;display:grid}.hGAOSW_analysisHistory h3,.hGAOSW_analysisHistory p{margin:0}.hGAOSW_analysisHistory .hGAOSW_blockHead>span{color:var(--dsw-alias-label-tertiary);font-size:11px}.hGAOSW_analysisHistory ul{gap:6px;max-height:210px;margin:0;padding:0;list-style:none;display:grid;overflow:auto}.hGAOSW_analysisHistory li{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);border-radius:8px;align-items:stretch;display:flex}.hGAOSW_analysisHistory li>button:first-child{min-width:0;color:var(--dsw-alias-label-secondary);text-align:left;cursor:pointer;background:0 0;border:0;flex:1;justify-content:space-between;align-items:center;gap:10px;padding:8px 10px;display:flex}.hGAOSW_analysisHistory li>button:first-child:hover{background:var(--dsw-alias-interactive-bg-hover)}.hGAOSW_analysisHistory li strong{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.hGAOSW_analysisHistory li span{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:10px}.hGAOSW_historyDeleteButton{border:0;border-left:1px solid var(--dsw-alias-border-l1);width:34px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;font-size:18px}.hGAOSW_historyDeleteButton:hover{color:var(--dsw-alias-state-error-primary)}.hGAOSW_status{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);border-radius:10px;padding:16px;font-size:13px;line-height:20px}@media (prefers-reduced-motion:reduce){.hGAOSW_analysisSpinner{animation:none}}@media (width<=860px){.hGAOSW_metrics{grid-template-columns:repeat(3,minmax(0,1fr))}.hGAOSW_detailMetrics,.hGAOSW_analysisSummaryGrid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (width<=580px){.hGAOSW_metrics,.hGAOSW_detailMetrics{grid-template-columns:repeat(2,minmax(0,1fr))}.hGAOSW_analysisSummaryGrid{grid-template-columns:minmax(0,1fr)}.hGAOSW_header,.hGAOSW_activityHead,.hGAOSW_blockHead{flex-direction:column;align-items:stretch;gap:8px}.hGAOSW_exportControls{justify-content:flex-start}.hGAOSW_budgetInput{justify-content:space-between}.hGAOSW_pricingNotice{flex-direction:column;align-items:flex-start;gap:2px}.hGAOSW_budgetProgress{flex-direction:column;align-items:flex-start}.hGAOSW_budgetProgress progress,.hGAOSW_blockHead input{width:100%}}"; + const css = ".hGAOSW_section{width:100%;max-width:960px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:22px;display:flex}.hGAOSW_header{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.hGAOSW_header h2,.hGAOSW_header p,.hGAOSW_block h3,.hGAOSW_status{margin:0}.hGAOSW_header h2{font-size:18px;font-weight:600;line-height:26px}.hGAOSW_header p{max-width:720px;color:var(--dsw-alias-label-tertiary);margin-top:5px;font-size:13px;line-height:20px}.hGAOSW_metrics{grid-template-columns:repeat(5,minmax(0,1fr));gap:10px;display:grid}.hGAOSW_metric{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;gap:7px;min-width:0;padding:13px 14px;display:flex}.hGAOSW_metric span{color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;line-height:16px;overflow:hidden}.hGAOSW_metric strong{font-variant-numeric:tabular-nums;text-overflow:ellipsis;font-size:20px;line-height:26px;overflow:hidden}.hGAOSW_activity{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;gap:10px;min-width:0;padding:14px;display:flex}.hGAOSW_activityHead{justify-content:space-between;align-items:flex-end;gap:14px;display:flex}.hGAOSW_activityHead h3,.hGAOSW_activityHead p{margin:0}.hGAOSW_activityHead h3{font-size:14px;font-weight:600;line-height:22px}.hGAOSW_activityHead p{color:var(--dsw-alias-label-tertiary);margin-top:2px;font-size:11px;line-height:17px}.hGAOSW_activityGrid{box-sizing:border-box;grid-template-rows:repeat(7,minmax(0,1fr));grid-template-columns:repeat(30,minmax(0,1fr));grid-auto-flow:column;gap:3px;width:100%;min-width:0;padding:2px;display:grid}.hGAOSW_activityCell,.hGAOSW_activityLegend i{background:var(--dsw-alias-bg-module-platform);border:0;border-radius:2px;flex:none;display:block}.hGAOSW_activityCell{aspect-ratio:1;cursor:pointer;width:100%;min-width:0;padding:0}.hGAOSW_activityCell:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.hGAOSW_activityCell[data-selected=true]{box-shadow:0 0 0 2px var(--dsw-alias-label-primary)}.hGAOSW_activityLegend i{width:10px;height:10px}.hGAOSW_activityCell[data-future=true]{cursor:default;background:0 0}.hGAOSW_activityCell[data-level=\"1\"],.hGAOSW_activityLegend i[data-level=\"1\"]{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 22%, var(--dsw-alias-bg-module-platform))}.hGAOSW_activityCell[data-level=\"2\"],.hGAOSW_activityLegend i[data-level=\"2\"]{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 42%, var(--dsw-alias-bg-module-platform))}.hGAOSW_activityCell[data-level=\"3\"],.hGAOSW_activityLegend i[data-level=\"3\"]{background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 64%, var(--dsw-alias-bg-module-platform))}.hGAOSW_activityCell[data-level=\"4\"],.hGAOSW_activityLegend i[data-level=\"4\"]{background:var(--dsw-alias-state-business-primary)}.hGAOSW_activityLegend{color:var(--dsw-alias-label-tertiary);white-space:nowrap;align-items:center;gap:4px;font-size:10px;line-height:14px;display:flex}.hGAOSW_insights,.hGAOSW_budget,.hGAOSW_dayDrilldown{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;gap:12px;min-width:0;padding:14px;display:flex}.hGAOSW_insights h3,.hGAOSW_budget h3,.hGAOSW_dayDrilldown h3,.hGAOSW_insights p,.hGAOSW_budget p,.hGAOSW_dayDrilldown p{margin:0}.hGAOSW_insights h3,.hGAOSW_budget h3,.hGAOSW_dayDrilldown h3{font-size:14px;font-weight:600;line-height:22px}.hGAOSW_insights .hGAOSW_blockHead p,.hGAOSW_budget .hGAOSW_blockHead p,.hGAOSW_dayDrilldown .hGAOSW_blockHead p{color:var(--dsw-alias-label-tertiary);margin-top:2px;font-size:11px;line-height:17px}.hGAOSW_detailMetrics{grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;display:grid}.hGAOSW_trendControls,.hGAOSW_rangeTabs,.hGAOSW_exportControls{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.hGAOSW_trendControls{justify-content:flex-end}.hGAOSW_rangeTabs button,.hGAOSW_exportControls button,.hGAOSW_quietButton{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);min-height:30px;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;border-radius:7px;padding:0 9px;font-size:11px}.hGAOSW_rangeTabs button[aria-pressed=true]{border-color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 12%, var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-state-business-primary)}.hGAOSW_rangeTabs button:focus-visible,.hGAOSW_exportControls button:focus-visible,.hGAOSW_quietButton:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.hGAOSW_exportControls{justify-content:flex-end}.hGAOSW_exportControls>span{color:var(--dsw-alias-label-tertiary);font-size:11px}.hGAOSW_exportControls>small{max-width:520px;color:var(--dsw-alias-label-tertiary);text-align:right;flex-basis:100%;font-size:10px;line-height:15px}.hGAOSW_exportControls button:disabled,.hGAOSW_quietButton:disabled{opacity:.5;cursor:not-allowed}.hGAOSW_insightNote{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.hGAOSW_anomalyNotice{color:var(--dsw-alias-state-error-primary);justify-content:space-between;align-items:center;gap:10px;font-size:11px;line-height:17px;display:flex}.hGAOSW_anomalyNotice p{margin:0}.hGAOSW_budgetInput{color:var(--dsw-alias-label-tertiary);white-space:nowrap;align-items:center;gap:7px;font-size:11px;display:flex}.hGAOSW_budgetInput input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:128px;height:30px;color:var(--dsw-alias-label-primary);font:inherit;font-variant-numeric:tabular-nums;border-radius:7px;outline:none;padding:0 8px;font-size:12px}.hGAOSW_budgetInput input:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb, var(--dsw-alias-state-business-primary) 18%, transparent)}.hGAOSW_budgetProgress{align-items:center;gap:10px;display:flex}.hGAOSW_budgetProgress progress{width:min(320px,55%);height:8px;accent-color:var(--dsw-alias-state-business-primary)}.hGAOSW_budgetProgress strong{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;font-size:12px}.hGAOSW_budgetWarning{color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}.hGAOSW_routeBudgets{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:10px;padding-top:12px;display:flex}.hGAOSW_routeBudgetHead{justify-content:space-between;align-items:flex-end;gap:14px;display:flex}.hGAOSW_routeBudgetHead h4,.hGAOSW_routeBudgetHead p{margin:0}.hGAOSW_routeBudgetHead h4{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}.hGAOSW_routeBudgetHead p{max-width:560px;color:var(--dsw-alias-label-tertiary);margin-top:2px;font-size:11px;line-height:17px}.hGAOSW_routeBudgetEditor{grid-template-columns:minmax(160px,1fr) 132px auto;align-items:end;gap:7px;display:grid}.hGAOSW_routeBudgetEditor label{min-width:0;color:var(--dsw-alias-label-tertiary);flex-direction:column;gap:3px;font-size:10px;display:flex}.hGAOSW_routeBudgetEditor select,.hGAOSW_routeBudgetEditor input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;height:30px;color:var(--dsw-alias-label-primary);font:inherit;border-radius:7px;padding:0 8px;font-size:11px}.hGAOSW_routeBudgetEditor select:focus-visible,.hGAOSW_routeBudgetEditor input:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.hGAOSW_routeBudgetList{flex-direction:column;display:flex}.hGAOSW_routeBudgetRow{border-top:1px solid var(--dsw-alias-border-l2);grid-template-columns:minmax(170px,.8fr) minmax(260px,1.6fr) auto;align-items:center;gap:12px;padding:10px 0;display:grid}.hGAOSW_routeBudgetRow:last-child{padding-bottom:0}.hGAOSW_routeBudgetIdentity{flex-direction:column;gap:2px;min-width:0;display:flex}.hGAOSW_routeBudgetIdentity>strong{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.hGAOSW_routeBudgetStatus{color:var(--dsw-alias-label-secondary);font-size:10px;font-weight:600;line-height:15px}.hGAOSW_routeBudgetStatus[data-status=warning],.hGAOSW_routeBudgetStatus[data-status=forecast-exceeded]{color:var(--dsw-alias-state-business-primary)}.hGAOSW_routeBudgetStatus[data-status=exceeded]{color:var(--dsw-alias-state-error-primary)}.hGAOSW_routeBudgetStatus[data-status=unavailable]{color:var(--dsw-alias-label-tertiary)}.hGAOSW_routeBudgetUsage{flex-direction:column;gap:3px;min-width:0;display:flex}.hGAOSW_routeBudgetRow>.hGAOSW_quietButton{justify-self:end}.hGAOSW_contributors{flex-direction:column;gap:7px;display:flex}.hGAOSW_contributors>strong{color:var(--dsw-alias-label-secondary);font-size:12px}.hGAOSW_contributors ol{gap:5px;margin:0;padding:0;list-style:none;display:grid}.hGAOSW_contributors li{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:7px;justify-content:space-between;align-items:center;gap:12px;padding:7px 9px;font-size:12px;display:flex}.hGAOSW_contributors li>span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.hGAOSW_block{flex-direction:column;gap:10px;min-width:0;display:flex}.hGAOSW_block h3{font-size:14px;font-weight:600;line-height:22px}.hGAOSW_blockHead{justify-content:space-between;align-items:center;gap:16px;display:flex}.hGAOSW_blockHead input{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:min(280px,45%);height:34px;color:var(--dsw-alias-label-primary);font:inherit;border-radius:8px;outline:none;padding:0 11px;font-size:12px}.hGAOSW_blockHead input::placeholder{color:var(--dsw-alias-label-tertiary)}.hGAOSW_blockHead input:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb, var(--dsw-alias-state-business-primary) 18%, transparent)}.hGAOSW_tableWrap{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;min-width:0;overflow:auto}.hGAOSW_tableWrap table{border-collapse:collapse;width:100%;min-width:0;font-size:12px;line-height:18px}.hGAOSW_tableWrap .hGAOSW_modelTable{table-layout:fixed;min-width:580px}.hGAOSW_tableWrap .hGAOSW_sessionTable{min-width:780px}.hGAOSW_modelTable th:first-child,.hGAOSW_modelTable td:first-child{width:30%}.hGAOSW_modelTable th:nth-child(2),.hGAOSW_modelTable td:nth-child(2){width:18%}.hGAOSW_tableWrap th,.hGAOSW_tableWrap td{border-bottom:1px solid var(--dsw-alias-border-l1);text-align:right;vertical-align:middle;white-space:nowrap;padding:10px 12px}.hGAOSW_tableWrap th{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:500}.hGAOSW_tableWrap th:first-child,.hGAOSW_tableWrap td:first-child{text-align:left;max-width:270px}.hGAOSW_tableWrap tbody tr:last-child td{border-bottom:0}.hGAOSW_tableWrap tbody tr:hover td{background:var(--dsw-alias-interactive-bg-hover)}.hGAOSW_tableWrap td{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums}.hGAOSW_tableWrap td strong,.hGAOSW_tableWrap td span{text-overflow:ellipsis;max-width:260px;display:block;overflow:hidden}.hGAOSW_tableWrap td strong{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600}.hGAOSW_tableWrap td span{color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:15px}.hGAOSW_tableWrap td .hGAOSW_tokenValue{max-width:none;color:inherit;font-size:inherit;line-height:inherit;display:inline}.hGAOSW_tableWrap td .hGAOSW_cacheDetail{margin-top:2px}.hGAOSW_analysisEmpty,.hGAOSW_analysisError,.hGAOSW_analysisPanel{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;flex-direction:column;gap:12px;min-width:0;padding:14px;display:flex}.hGAOSW_analysisEmpty{border-style:dashed}.hGAOSW_analysisError{border-color:var(--dsw-alias-state-error-primary)}.hGAOSW_analysisEmpty h3,.hGAOSW_analysisEmpty p,.hGAOSW_analysisError h3,.hGAOSW_analysisError p,.hGAOSW_analysisPanel h3,.hGAOSW_analysisPanel p{margin:0}.hGAOSW_analysisEmpty h3,.hGAOSW_analysisError h3,.hGAOSW_analysisPanel h3{font-size:14px;font-weight:600;line-height:22px}.hGAOSW_analysisEmpty p,.hGAOSW_analysisError p,.hGAOSW_analysisPanel .hGAOSW_blockHead p,.hGAOSW_analysisPrivacy{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.hGAOSW_analysisError p,.hGAOSW_analysisWarning{color:var(--dsw-alias-state-error-primary)}.hGAOSW_analysisCost{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:999px;padding:5px 9px;font-size:11px}.hGAOSW_analysisReport{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-module-platform);max-height:640px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;border-radius:10px;margin:0;padding:18px 20px;font-size:13px;line-height:1.75;overflow:auto}.hGAOSW_analysisReport h1,.hGAOSW_analysisReport h2,.hGAOSW_analysisReport h3,.hGAOSW_analysisReport h4{color:var(--dsw-alias-label-primary);margin:1.25em 0 .55em;line-height:1.35}.hGAOSW_analysisReport h1:first-child,.hGAOSW_analysisReport h2:first-child,.hGAOSW_analysisReport h3:first-child,.hGAOSW_analysisReport p:first-child{margin-top:0}.hGAOSW_analysisReport p,.hGAOSW_analysisReport ul,.hGAOSW_analysisReport ol,.hGAOSW_analysisReport blockquote,.hGAOSW_analysisReport table{margin:.65em 0}.hGAOSW_analysisReport blockquote{border-left:3px solid var(--dsw-alias-state-business-primary);color:var(--dsw-alias-label-tertiary);padding-left:12px}.hGAOSW_analysisReport table{border-collapse:collapse;width:100%;font-size:12px}.hGAOSW_analysisReport th,.hGAOSW_analysisReport td{border:1px solid var(--dsw-alias-border-l2);text-align:left;vertical-align:top;padding:7px 9px}.hGAOSW_analysisReport th{background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-primary)}.hGAOSW_analysisReport code{background:var(--dsw-alias-bg-layer-3);border-radius:4px;padding:1px 4px;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:.92em}.hGAOSW_analysisHeaderActions{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:8px;display:flex}.hGAOSW_analysisSummaryGrid{grid-template-columns:repeat(2,minmax(0,1fr));gap:9px;display:grid}.hGAOSW_analysisSummaryGroup{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);border-radius:9px;min-width:0;padding:10px 12px}.hGAOSW_analysisSummaryGroup h4{color:var(--dsw-alias-label-primary);margin:0 0 6px;font-size:12px;line-height:18px}.hGAOSW_analysisSummaryGroup dl{gap:4px;margin:0;display:grid}.hGAOSW_analysisSummaryGroup dl>div{justify-content:space-between;align-items:baseline;gap:10px;display:flex}.hGAOSW_analysisSummaryGroup dt{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.hGAOSW_analysisSummaryGroup dd{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;margin:0;font-size:12px;font-weight:600}.hGAOSW_analysisLoading{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;align-items:flex-start;gap:12px;padding:14px;display:flex}.hGAOSW_analysisSpinner{border:2px solid var(--dsw-alias-border-l2);border-top-color:var(--dsw-alias-state-business-primary);border-radius:50%;flex:none;width:18px;height:18px;margin-top:2px;animation:.85s linear infinite hGAOSW_token-analysis-spin}.hGAOSW_analysisLoadingBody{flex:1;gap:6px;min-width:0;display:grid}.hGAOSW_analysisLoadingBody h3,.hGAOSW_analysisLoadingBody p{margin:0}.hGAOSW_analysisLoadingBody h3{color:var(--dsw-alias-label-primary);font-size:14px}.hGAOSW_analysisLoadingBody p,.hGAOSW_analysisProgressMeta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.hGAOSW_analysisProgressMeta{flex-wrap:wrap;gap:10px;display:flex}.hGAOSW_analysisProgressMeta strong{color:var(--dsw-alias-state-business-primary)}.hGAOSW_analysisLoadingBody progress{width:100%;height:7px;accent-color:var(--dsw-alias-state-business-primary)}@keyframes hGAOSW_token-analysis-spin{to{transform:rotate(360deg)}}.hGAOSW_analysisWarning{font-size:11px;line-height:18px}.hGAOSW_modelSort{color:var(--dsw-alias-label-tertiary);align-items:center;gap:6px;font-size:11px;display:flex}.hGAOSW_modelSort select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);min-width:116px;color:var(--dsw-alias-label-primary);font:inherit;border-radius:7px;padding:5px 7px;font-size:12px}.hGAOSW_modelSort select:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.hGAOSW_sessionLink{max-width:240px;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;background:0 0;border:0;padding:0;font-weight:600;text-decoration:underline #0000;display:block;overflow:hidden}.hGAOSW_sessionLink:hover{text-decoration-color:currentColor}.hGAOSW_sessionLink:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;border-radius:2px}.hGAOSW_analysisButton{border:1px solid var(--dsw-alias-state-business-primary);background:color-mix(in srgb, var(--dsw-alias-state-business-primary) 10%, var(--dsw-alias-bg-layer-1));min-height:28px;color:var(--dsw-alias-state-business-primary);font:inherit;cursor:pointer;border-radius:7px;padding:0 9px;font-size:11px}.hGAOSW_analysisButton:disabled{cursor:wait;opacity:.65}.hGAOSW_analysisButton:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.hGAOSW_pricingNotice{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-tertiary);border-radius:10px;align-items:baseline;gap:8px;padding:10px 12px;font-size:11px;line-height:18px;display:flex}.hGAOSW_pricingNotice strong{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-weight:600}.hGAOSW_pricingNotice p{margin:0}.hGAOSW_priceUnknown{color:var(--dsw-alias-label-tertiary)}.hGAOSW_priceValue{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap}.hGAOSW_analysisModelSelect{min-width:180px;color:var(--dsw-alias-label-tertiary);gap:4px;font-size:11px;line-height:16px;display:grid}.hGAOSW_analysisModelSelect select{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);min-width:0;color:var(--dsw-alias-label-primary);font:inherit;border-radius:7px;padding:5px 7px;font-size:12px}.hGAOSW_analysisModelSelect select:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}.hGAOSW_analysisScope{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:18px}.hGAOSW_analysisErrorText{color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:18px}.hGAOSW_conversationAnalysisButton{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);height:28px;color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;border-radius:7px;padding:0 9px;font-size:11px}.hGAOSW_conversationAnalysisButton:hover{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.hGAOSW_conversationAnalysisButton:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}.hGAOSW_analysisDialog{width:min(820px,100%);max-height:min(84dvh,780px)}.hGAOSW_analysisDialogContent{overscroll-behavior:contain;min-height:0;overflow-y:auto}.hGAOSW_analysisDialog .hGAOSW_analysisReport{max-height:min(48dvh,460px)}.hGAOSW_conversationAnalysisControls{flex-wrap:wrap;align-items:end;gap:10px;margin-bottom:12px;display:flex}.hGAOSW_analysisHistory{border-top:1px solid var(--dsw-alias-border-l1);gap:9px;margin-top:16px;padding-top:14px;display:grid}.hGAOSW_analysisHistory h3,.hGAOSW_analysisHistory p{margin:0}.hGAOSW_analysisHistory .hGAOSW_blockHead>span{color:var(--dsw-alias-label-tertiary);font-size:11px}.hGAOSW_analysisHistory ul{gap:6px;max-height:210px;margin:0;padding:0;list-style:none;display:grid;overflow:auto}.hGAOSW_analysisHistory li{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);border-radius:8px;align-items:stretch;display:flex}.hGAOSW_analysisHistory li>button:first-child{min-width:0;color:var(--dsw-alias-label-secondary);text-align:left;cursor:pointer;background:0 0;border:0;flex:1;justify-content:space-between;align-items:center;gap:10px;padding:8px 10px;display:flex}.hGAOSW_analysisHistory li>button:first-child:hover{background:var(--dsw-alias-interactive-bg-hover)}.hGAOSW_analysisHistory li strong{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.hGAOSW_analysisHistory li span{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-size:10px}.hGAOSW_historyDeleteButton{border:0;border-left:1px solid var(--dsw-alias-border-l1);width:34px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;font-size:18px}.hGAOSW_historyDeleteButton:hover{color:var(--dsw-alias-state-error-primary)}.hGAOSW_status{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);border-radius:10px;padding:16px;font-size:13px;line-height:20px}@media (prefers-reduced-motion:reduce){.hGAOSW_analysisSpinner{animation:none}}@media (width<=860px){.hGAOSW_metrics{grid-template-columns:repeat(3,minmax(0,1fr))}.hGAOSW_detailMetrics,.hGAOSW_analysisSummaryGrid{grid-template-columns:repeat(2,minmax(0,1fr))}.hGAOSW_routeBudgetHead{flex-direction:column;align-items:stretch}.hGAOSW_routeBudgetRow{grid-template-columns:minmax(160px,.8fr) minmax(240px,1.4fr) auto}}@media (width<=580px){.hGAOSW_metrics,.hGAOSW_detailMetrics{grid-template-columns:repeat(2,minmax(0,1fr))}.hGAOSW_analysisSummaryGrid{grid-template-columns:minmax(0,1fr)}.hGAOSW_header,.hGAOSW_activityHead,.hGAOSW_blockHead{flex-direction:column;align-items:stretch;gap:8px}.hGAOSW_exportControls{justify-content:flex-start}.hGAOSW_exportControls>small{text-align:left}.hGAOSW_routeBudgetEditor,.hGAOSW_routeBudgetRow{grid-template-columns:minmax(0,1fr)}.hGAOSW_routeBudgetRow{gap:8px}.hGAOSW_routeBudgetRow>.hGAOSW_quietButton{justify-self:start}.hGAOSW_budgetInput{justify-content:space-between}.hGAOSW_pricingNotice{flex-direction:column;align-items:flex-start;gap:2px}.hGAOSW_budgetProgress{flex-direction:column;align-items:flex-start}.hGAOSW_budgetProgress progress,.hGAOSW_blockHead input{width:100%}}"; const tagId = "dsh-token-usage/TokenUsageSection.module.css"; if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) { const tag = document.createElement("style"); @@ -594,66 +661,75 @@ window.__ModuleLoader__.load({ document.head.appendChild(tag); } var TokenUsageSection_module_css_default = { + "analysisButton": "hGAOSW_analysisButton", "pricingNotice": "hGAOSW_pricingNotice", - "blockHead": "hGAOSW_blockHead", "activityGrid": "hGAOSW_activityGrid", - "budgetInput": "hGAOSW_budgetInput", - "cacheDetail": "hGAOSW_cacheDetail", - "modelSort": "hGAOSW_modelSort", - "historyDeleteButton": "hGAOSW_historyDeleteButton", - "analysisButton": "hGAOSW_analysisButton", - "analysisDialogContent": "hGAOSW_analysisDialogContent", - "exportControls": "hGAOSW_exportControls", + "routeBudgetRow": "hGAOSW_routeBudgetRow", + "activityCell": "hGAOSW_activityCell", "analysisLoading": "hGAOSW_analysisLoading", - "section": "hGAOSW_section", - "analysisPanel": "hGAOSW_analysisPanel", - "detailMetrics": "hGAOSW_detailMetrics", - "status": "hGAOSW_status", - "analysisLoadingBody": "hGAOSW_analysisLoadingBody", - "insights": "hGAOSW_insights", - "budgetProgress": "hGAOSW_budgetProgress", + "analysisErrorText": "hGAOSW_analysisErrorText", + "budgetWarning": "hGAOSW_budgetWarning", + "modelSort": "hGAOSW_modelSort", "block": "hGAOSW_block", - "anomalyNotice": "hGAOSW_anomalyNotice", + "activityLegend": "hGAOSW_activityLegend", + "historyDeleteButton": "hGAOSW_historyDeleteButton", "analysisPrivacy": "hGAOSW_analysisPrivacy", - "analysisReport": "hGAOSW_analysisReport", - "analysisSummaryGrid": "hGAOSW_analysisSummaryGrid", - "analysisScope": "hGAOSW_analysisScope", - "metric": "hGAOSW_metric", - "analysisCost": "hGAOSW_analysisCost", - "budget": "hGAOSW_budget", "metrics": "hGAOSW_metrics", - "sessionTable": "hGAOSW_sessionTable", - "analysisError": "hGAOSW_analysisError", - "analysisSpinner": "hGAOSW_analysisSpinner", + "budget": "hGAOSW_budget", "analysisSummaryGroup": "hGAOSW_analysisSummaryGroup", - "contributors": "hGAOSW_contributors", - "token-analysis-spin": "hGAOSW_token-analysis-spin", - "priceUnknown": "hGAOSW_priceUnknown", - "tableWrap": "hGAOSW_tableWrap", - "sessionLink": "hGAOSW_sessionLink", - "budgetWarning": "hGAOSW_budgetWarning", - "analysisHeaderActions": "hGAOSW_analysisHeaderActions", + "analysisScope": "hGAOSW_analysisScope", + "blockHead": "hGAOSW_blockHead", + "routeBudgetHead": "hGAOSW_routeBudgetHead", + "insightNote": "hGAOSW_insightNote", "conversationAnalysisButton": "hGAOSW_conversationAnalysisButton", - "quietButton": "hGAOSW_quietButton", - "analysisWarning": "hGAOSW_analysisWarning", "analysisProgressMeta": "hGAOSW_analysisProgressMeta", - "analysisErrorText": "hGAOSW_analysisErrorText", - "analysisDialog": "hGAOSW_analysisDialog", - "activityHead": "hGAOSW_activityHead", + "contributors": "hGAOSW_contributors", + "analysisSpinner": "hGAOSW_analysisSpinner", + "priceValue": "hGAOSW_priceValue", + "rangeTabs": "hGAOSW_rangeTabs", + "budgetProgress": "hGAOSW_budgetProgress", + "section": "hGAOSW_section", + "analysisSummaryGrid": "hGAOSW_analysisSummaryGrid", + "modelTable": "hGAOSW_modelTable", + "insights": "hGAOSW_insights", + "conversationAnalysisControls": "hGAOSW_conversationAnalysisControls", + "analysisHeaderActions": "hGAOSW_analysisHeaderActions", "activity": "hGAOSW_activity", "analysisEmpty": "hGAOSW_analysisEmpty", - "rangeTabs": "hGAOSW_rangeTabs", + "quietButton": "hGAOSW_quietButton", + "analysisLoadingBody": "hGAOSW_analysisLoadingBody", + "exportControls": "hGAOSW_exportControls", + "routeBudgetUsage": "hGAOSW_routeBudgetUsage", + "routeBudgetList": "hGAOSW_routeBudgetList", + "dayDrilldown": "hGAOSW_dayDrilldown", + "budgetInput": "hGAOSW_budgetInput", + "tableWrap": "hGAOSW_tableWrap", "analysisModelSelect": "hGAOSW_analysisModelSelect", - "conversationAnalysisControls": "hGAOSW_conversationAnalysisControls", - "activityLegend": "hGAOSW_activityLegend", - "activityCell": "hGAOSW_activityCell", - "priceValue": "hGAOSW_priceValue", - "modelTable": "hGAOSW_modelTable", + "routeBudgetIdentity": "hGAOSW_routeBudgetIdentity", + "sessionTable": "hGAOSW_sessionTable", + "analysisDialogContent": "hGAOSW_analysisDialogContent", + "priceUnknown": "hGAOSW_priceUnknown", + "analysisWarning": "hGAOSW_analysisWarning", + "status": "hGAOSW_status", + "routeBudgets": "hGAOSW_routeBudgets", + "routeBudgetStatus": "hGAOSW_routeBudgetStatus", "analysisHistory": "hGAOSW_analysisHistory", + "analysisError": "hGAOSW_analysisError", + "routeBudgetEditor": "hGAOSW_routeBudgetEditor", + "analysisDialog": "hGAOSW_analysisDialog", + "token-analysis-spin": "hGAOSW_token-analysis-spin", + "cacheDetail": "hGAOSW_cacheDetail", + "metric": "hGAOSW_metric", + "activityHead": "hGAOSW_activityHead", + "trendControls": "hGAOSW_trendControls", + "anomalyNotice": "hGAOSW_anomalyNotice", + "tokenValue": "hGAOSW_tokenValue", + "sessionLink": "hGAOSW_sessionLink", + "analysisPanel": "hGAOSW_analysisPanel", "header": "hGAOSW_header", - "insightNote": "hGAOSW_insightNote", - "dayDrilldown": "hGAOSW_dayDrilldown", - "tokenValue": "hGAOSW_tokenValue" + "detailMetrics": "hGAOSW_detailMetrics", + "analysisReport": "hGAOSW_analysisReport", + "analysisCost": "hGAOSW_analysisCost" }; //#endregion //#region src/client/TokenUsageSection.tsx @@ -676,6 +752,40 @@ window.__ModuleLoader__.load({ cacheWriteTokens: left.cacheWriteTokens + right.cacheWriteTokens }; } + /** Whether two detached provider bucket sets are exactly conserved. */ + function sameBuckets$1(left, right) { + return left.uncachedInputTokens === right.uncachedInputTokens && left.outputTokens === right.outputTokens && left.cacheReadTokens === right.cacheReadTokens && left.cacheWriteTokens === right.cacheWriteTokens; + } + /** Sum detached bucket records without retaining projection-owned objects. */ + function summedUsage(records) { + return records.reduce((sum, record) => addBuckets(sum, record.usage), zeroBuckets()); + } + /** Compare two aggregate maps without accepting missing or extra keys. */ + function sameUsageMap(actual, expected) { + return actual.size === expected.size && [...expected].every(([key, usage]) => { + const value = actual.get(key); + return value !== void 0 && sameBuckets$1(value, usage); + }); + } + /** Verify daily buckets conserve the session-level projection total. */ + function dailyUsageConserved(recorded) { + return sameBuckets$1(summedUsage(recorded.days), recorded.usage); + } + /** Verify date-by-model buckets conserve totals across session, route, and UTC day dimensions. */ + function modelDailyUsageConserved(recorded) { + if (!sameBuckets$1(summedUsage(recorded.modelDays), recorded.usage)) return false; + const routeTotals = /* @__PURE__ */ new Map(); + const dayTotals = /* @__PURE__ */ new Map(); + for (const record of recorded.modelDays) { + if (totalTokens$1(record.usage) === 0) continue; + const route = modelKey(record); + routeTotals.set(route, addBuckets(routeTotals.get(route) ?? zeroBuckets(), record.usage)); + dayTotals.set(record.date, addBuckets(dayTotals.get(record.date) ?? zeroBuckets(), record.usage)); + } + const expectedRoutes = new Map(recorded.models.filter((model) => totalTokens$1(model.usage) > 0).map((model) => [modelKey(model), model.usage])); + const expectedDays = new Map(recorded.days.filter((day) => totalTokens$1(day.usage) > 0).map((day) => [day.date, day.usage])); + return sameUsageMap(routeTotals, expectedRoutes) && sameUsageMap(dayTotals, expectedDays); + } /** Stable UTC day key used by durable Host records and legacy fallbacks. */ function dayKey(time) { return new Date(time).toISOString().slice(0, 10); @@ -745,6 +855,14 @@ window.__ModuleLoader__.load({ function modelKey(model) { return JSON.stringify([model.provider, model.model]); } + /** Stable provider/model/date identity for route-day aggregation. */ + function modelDayKey(model) { + return JSON.stringify([ + model.provider, + model.model, + model.date + ]); + } /** Compact route label retained in the session table. */ function routeLabel(model) { return `${model.provider}/${model.model}`; @@ -799,6 +917,8 @@ window.__ModuleLoader__.load({ const assistantRequests = recorded?.assistantRequests ?? 0; const compactionRequests = recorded?.compactionRequests ?? 0; if (usage === void 0 || totalTokens$1(usage) === 0 && assistantRequests === 0 && compactionRequests === 0) return null; + const dailyUsageReliable = recorded?.days !== void 0 && dailyUsageConserved(recorded); + const modelDailyUsageReliable = recorded?.modelDays !== void 0 && dailyUsageReliable && modelDailyUsageConserved(recorded); return { id: summary.id, title: summary.displayTitle, @@ -812,7 +932,9 @@ window.__ModuleLoader__.load({ date: dayKey(summary.updatedAt), usage }], - dailyUsageReliable: recorded?.days !== void 0 + modelDays: recorded?.modelDays ?? [], + dailyUsageReliable, + modelDailyUsageReliable }; } /** Aggregate session summaries into totals and provider/model records. */ @@ -820,12 +942,14 @@ window.__ModuleLoader__.load({ const sessions = []; const models = /* @__PURE__ */ new Map(); const days = /* @__PURE__ */ new Map(); + const modelDays = /* @__PURE__ */ new Map(); const operationalDays = /* @__PURE__ */ new Map(); let usage = zeroBuckets(); let assistantRequests = 0; let compactionRequests = 0; let compactionUsage = zeroBuckets(); let reliableDailySessions = 0; + let reliableModelDailySessions = 0; for (const summary of summaries) { const row = sessionRow(summary); if (row === null) continue; @@ -835,10 +959,22 @@ window.__ModuleLoader__.load({ compactionRequests += row.compactionRequests; compactionUsage = addBuckets(compactionUsage, row.compactionUsage); if (row.dailyUsageReliable) reliableDailySessions += 1; + if (row.modelDailyUsageReliable) reliableModelDailySessions += 1; for (const day of row.days) { days.set(day.date, addBuckets(days.get(day.date) ?? zeroBuckets(), day.usage)); if (row.dailyUsageReliable) operationalDays.set(day.date, addBuckets(operationalDays.get(day.date) ?? zeroBuckets(), day.usage)); } + for (const modelDay of row.modelDays) { + const key = modelDayKey(modelDay); + const current = modelDays.get(key); + modelDays.set(key, current === void 0 ? { + ...modelDay, + usage: { ...modelDay.usage } + } : { + ...current, + usage: addBuckets(current.usage, modelDay.usage) + }); + } for (const model of row.models) { const key = modelKey(model); const current = models.get(key); @@ -865,13 +1001,22 @@ window.__ModuleLoader__.load({ date, usage })).sort((left, right) => left.date.localeCompare(right.date)), + modelDays: [...modelDays.values()].sort((left, right) => left.date.localeCompare(right.date) || left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)), operationalDays: [...operationalDays.entries()].map(([date, usage]) => ({ date, usage })).sort((left, right) => left.date.localeCompare(right.date)), - dailyCoverage: reliableDailySessions === 0 ? "unavailable" : reliableDailySessions === sessions.length ? "complete" : "partial" + dailyCoverage: reliableDailySessions === 0 ? "unavailable" : reliableDailySessions === sessions.length ? "complete" : "partial", + modelDailyCoverage: reliableModelDailySessions === 0 ? "unavailable" : reliableModelDailySessions === sessions.length ? "complete" : "partial" }; } + /** Return one exact route's reliable daily buckets from the aggregate route-day table. */ + function modelTrendDays(modelDays, route) { + return modelDays.filter((modelDay) => modelKey(modelDay) === route).map((modelDay) => ({ + date: modelDay.date, + usage: { ...modelDay.usage } + })); + } /** Return only detached aggregate buckets, route records, and UTC dates for AI usage analysis. */ function usageAnalysisInput(data) { return { @@ -1048,32 +1193,62 @@ window.__ModuleLoader__.load({ }); } /** Render period-aware trend and activity summaries from daily records. */ - function PeriodInsights({ days, range, onRangeChange, t }) { + function PeriodInsights({ days, range, models, selectedModel, modelDailyCoverage, onRangeChange, onModelChange, t }) { const insight = (0, react.useMemo)(() => periodInsight(days, range), [days, range]); const current = totalTokens$1(insight.usage); const previous = totalTokens$1(insight.previousUsage); const delta = previous === 0 ? void 0 : Math.round((current - previous) / previous * 100); const peak = insight.peak; + const selectedRoute = models.find((model) => modelKey(model) === selectedModel); + let modelCoverageNote = null; + if (modelDailyCoverage !== "complete") modelCoverageNote = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { + className: TokenUsageSection_module_css_default.insightNote, + children: t(modelDailyCoverage === "partial" ? "modelDailyCoveragePartial" : "modelDailyCoverageUnavailable") + }); + else if (selectedRoute !== void 0) modelCoverageNote = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { + className: TokenUsageSection_module_css_default.insightNote, + children: t("trendModelScope", { route: routeLabel(selectedRoute) }) + }); return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: TokenUsageSection_module_css_default.insights, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: TokenUsageSection_module_css_default.blockHead, - children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("trend") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("trendIntro") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { - className: TokenUsageSection_module_css_default.rangeTabs, - "aria-label": t("trend"), - children: [ - 7, - 30, - 90 - ].map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { - type: "button", - "aria-pressed": range === value, - onClick: () => { - onRangeChange(value); - }, - children: t("rangeDays", { count: value }) - }, value)) + children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("trend") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("trendIntro") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { + className: TokenUsageSection_module_css_default.trendControls, + children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { + className: TokenUsageSection_module_css_default.modelSort, + children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("trendModel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", { + "aria-label": t("trendModel"), + value: selectedModel, + disabled: modelDailyCoverage !== "complete", + onChange: (event) => { + onModelChange(event.currentTarget.value); + }, + children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { + value: "", + children: t("allModels") + }), models.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { + value: modelKey(model), + children: routeLabel(model) + }, modelKey(model)))] + })] + }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { + className: TokenUsageSection_module_css_default.rangeTabs, + "aria-label": t("trend"), + children: [ + 7, + 30, + 90 + ].map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { + type: "button", + "aria-pressed": range === value, + onClick: () => { + onRangeChange(value); + }, + children: t("rangeDays", { count: value }) + }, value)) + })] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { @@ -1103,12 +1278,13 @@ window.__ModuleLoader__.load({ date: peak.date, total: formatTokens(totalTokens$1(peak.usage)) }) - }) + }), + modelCoverageNote ] }); } - /** Render the persistent trailing-30-day budget setting and progress. */ - function BudgetPanel({ operationalDays, dailyCoverage, snapshot, setBudget, t }) { + /** Render persistent global and exact-route rolling Token budgets. */ + function BudgetPanel({ operationalDays, dailyCoverage, models, modelDays, modelDailyCoverage, snapshot, setBudget, setRouteBudget, t }) { const insight = (0, react.useMemo)(() => periodInsight(operationalDays, 30), [operationalDays]); const runRate = (0, react.useMemo)(() => runRateInsight(operationalDays), [operationalDays]); const runRateAvailable = dailyCoverage === "complete"; @@ -1117,9 +1293,26 @@ window.__ModuleLoader__.load({ const enabled = budget > 0; const durableValue = enabled ? String(budget) : ""; const [draft, setDraft] = (0, react.useState)(durableValue); + const [routeSelection, setRouteSelection] = (0, react.useState)(""); + const [routeDraft, setRouteDraft] = (0, react.useState)(""); const editGeneration = (0, react.useRef)(0); const dirtyDraft = (0, react.useRef)(false); const ratio = enabled ? used / budget : 0; + const configurableModels = (0, react.useMemo)(() => models.filter((model) => !isUnattributed(model) && totalTokens$1(model.usage) > 0), [models]); + const persistedRouteBudgets = snapshot.routeBudgets ?? []; + const routeInsights = (0, react.useMemo)(() => modelDailyCoverage === "complete" ? routeBudgetInsights(persistedRouteBudgets, modelDays) : [], [ + modelDailyCoverage, + modelDays, + persistedRouteBudgets + ]); + const routeInsightByKey = (0, react.useMemo)(() => new Map(routeInsights.map((route) => [modelKey(route), route])), [routeInsights]); + const routeRows = persistedRouteBudgets.map((route) => ({ + route, + insight: routeInsightByKey.get(modelKey(route)) + })).sort((left, right) => { + const insightOrder = routeInsights.findIndex((insight) => modelKey(insight) === modelKey(left.route)) - routeInsights.findIndex((insight) => modelKey(insight) === modelKey(right.route)); + return modelDailyCoverage === "complete" && insightOrder !== 0 ? insightOrder : left.route.provider.localeCompare(right.route.provider) || left.route.model.localeCompare(right.route.model); + }); (0, react.useEffect)(() => { if (!dirtyDraft.current) setDraft(durableValue); }, [durableValue, snapshot.status]); @@ -1142,6 +1335,14 @@ window.__ModuleLoader__.load({ setDraft(durableValue); }); }; + const saveRoute = () => { + const selected = configurableModels.find((model) => modelKey(model) === routeSelection); + const next = Number(routeDraft); + if (selected === void 0 || !Number.isSafeInteger(next) || next <= 0) return; + setRouteBudget(selected.provider, selected.model, next).then(() => { + setRouteDraft(""); + }); + }; return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: TokenUsageSection_module_css_default.budget, children: [ @@ -1197,18 +1398,129 @@ window.__ModuleLoader__.load({ percent: Math.round(ratio * 100) }) })] }), - ratio > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { + ratio >= 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { className: TokenUsageSection_module_css_default.budgetWarning, - children: t("budgetExceeded", { excess: formatCompactTokens(used - budget) }) + children: t("budgetExceeded", { excess: formatCompactTokens(Math.max(0, used - budget)) }) }) : null, - runRateAvailable && ratio <= 1 && runRate.projectedThirtyDayTokens > budget ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { + runRateAvailable && ratio < 1 && runRate.projectedThirtyDayTokens > budget ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { className: TokenUsageSection_module_css_default.budgetWarning, children: t("budgetForecastExceeded", { projected: formatCompactTokens(runRate.projectedThirtyDayTokens), budget: formatCompactTokens(budget) }) }) : null - ] }) + ] }), + /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { + className: TokenUsageSection_module_css_default.routeBudgets, + "aria-labelledby": "token-usage-route-budgets", + children: [ + /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { + className: TokenUsageSection_module_css_default.routeBudgetHead, + children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", { + id: "token-usage-route-budgets", + children: t("routeBudgets") + }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("routeBudgetsIntro") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { + className: TokenUsageSection_module_css_default.routeBudgetEditor, + children: [ + /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("routeBudgetModel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", { + value: routeSelection, + disabled: snapshot.status !== "ready" || configurableModels.length === 0, + onChange: (event) => { + const value = event.currentTarget.value; + setRouteSelection(value); + const current = persistedRouteBudgets.find((route) => modelKey(route) === value); + setRouteDraft(current === void 0 ? "" : String(current.rolling30DayBudget)); + }, + children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { + value: "", + children: t("routeBudgetChooseModel") + }), configurableModels.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { + value: modelKey(model), + children: routeLabel(model) + }, modelKey(model)))] + })] }), + /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("routeBudgetInput") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { + type: "number", + inputMode: "numeric", + min: "1", + step: "1", + value: routeDraft, + placeholder: "0", + disabled: snapshot.status !== "ready", + onChange: (event) => { + setRouteDraft(event.currentTarget.value); + }, + onKeyDown: (event) => { + if (event.key === "Enter") saveRoute(); + } + })] }), + /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { + className: TokenUsageSection_module_css_default.quietButton, + type: "button", + disabled: routeSelection === "" || !Number.isSafeInteger(Number(routeDraft)) || Number(routeDraft) <= 0, + onClick: saveRoute, + children: t("routeBudgetSave") + }) + ] + })] + }), + modelDailyCoverage === "complete" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { + className: TokenUsageSection_module_css_default.insightNote, + children: t(modelDailyCoverage === "partial" ? "routeBudgetCoveragePartial" : "routeBudgetCoverageUnavailable") + }), + persistedRouteBudgets.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { + className: TokenUsageSection_module_css_default.insightNote, + children: t("routeBudgetsEmpty") + }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { + className: TokenUsageSection_module_css_default.routeBudgetList, + "aria-live": "polite", + children: routeRows.map(({ route, insight }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", { + className: TokenUsageSection_module_css_default.routeBudgetRow, + children: [ + /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { + className: TokenUsageSection_module_css_default.routeBudgetIdentity, + children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: routeLabel(route) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { + className: TokenUsageSection_module_css_default.routeBudgetStatus, + "data-status": insight?.status ?? "unavailable", + children: insight === void 0 ? t("routeBudgetUnavailable") : t(`routeBudgetStatus_${insight.status}`) + })] + }), + insight === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { + className: TokenUsageSection_module_css_default.routeBudgetUsage, + children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { + className: TokenUsageSection_module_css_default.budgetProgress, + children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("progress", { + value: Math.min(insight.usedTokens, insight.rolling30DayBudget), + max: insight.rolling30DayBudget, + "aria-label": t("routeBudgetProgress", { + used: formatCompactTokens(insight.usedTokens), + budget: formatCompactTokens(insight.rolling30DayBudget), + percent: Math.round(insight.ratio * 100) + }) + }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("routeBudgetProgress", { + used: formatCompactTokens(insight.usedTokens), + budget: formatCompactTokens(insight.rolling30DayBudget), + percent: Math.round(insight.ratio * 100) + }) })] + }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { + className: TokenUsageSection_module_css_default.insightNote, + children: t("routeBudgetForecast", { projected: formatCompactTokens(insight.projectedThirtyDayTokens) }) + })] + }), + /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { + className: TokenUsageSection_module_css_default.quietButton, + type: "button", + "aria-label": t("routeBudgetRemoveFor", { route: routeLabel(route) }), + onClick: () => { + setRouteBudget(route.provider, route.model, 0); + }, + children: t("routeBudgetRemove") + }) + ] + }, modelKey(route))) + }) + ] + }) ] }); } @@ -1356,7 +1668,10 @@ window.__ModuleLoader__.load({ case "daily": download.save(`dsh-token-usage-daily-${date}.csv`, "text/csv;charset=utf-8", dailyUsageCsv(data)); return; - case "models": download.save(`dsh-token-usage-models-${date}.csv`, "text/csv;charset=utf-8", modelUsageCsv(data)); + case "models": + download.save(`dsh-token-usage-models-${date}.csv`, "text/csv;charset=utf-8", modelUsageCsv(data)); + return; + case "modelDaily": download.save(`dsh-token-usage-model-daily-${date}.csv`, "text/csv;charset=utf-8", modelDailyUsageCsv(data)); } }; return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { @@ -1384,7 +1699,17 @@ window.__ModuleLoader__.load({ save("models"); }, children: t("exportModels") - }) + }), + /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { + type: "button", + disabled: data.modelDailyCoverage !== "complete", + title: data.modelDailyCoverage === "complete" ? void 0 : t("exportModelDailyUnavailable"), + onClick: () => { + save("modelDaily"); + }, + children: t("exportModelDaily") + }), + data.modelDailyCoverage === "complete" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("exportModelDailyUnavailable") }) ] }); } @@ -1444,7 +1769,16 @@ window.__ModuleLoader__.load({ }); if (catalog.status === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: TokenUsageSection_module_css_default.analysisError, - children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("usageAnalysis") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("analysisModelsFailed", { message: catalog.message }) })] + children: [ + /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("usageAnalysis") }), + /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("analysisModelsFailed", { message: catalog.message }) }), + /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { + className: TokenUsageSection_module_css_default.quietButton, + type: "button", + onClick: onRefreshCatalog, + children: t("refreshAnalysisModels") + }) + ] }); const catalogFailures = catalog.value.failures ?? []; if (catalog.value.models.length === 0 || selectedModel === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { @@ -1727,7 +2061,7 @@ window.__ModuleLoader__.load({ }); } /** Render durable Token usage across all listed sessions. */ - function TokenUsageSection({ close, useSessions, useBudget, setBudget, download, saveTrajectoryAnalysis, openSession, listAnalysisModels, analyzeTokenUsage, analyzeTrajectory, t }) { + function TokenUsageSection({ close, useSessions, useBudget, setBudget, setRouteBudget, download, saveTrajectoryAnalysis, openSession, listAnalysisModels, analyzeTokenUsage, analyzeTrajectory, t }) { const phase = useSessions((state) => state.phase); const ids = useSessions((state) => state.ids); const byId = useSessions((state) => state.byId); @@ -1736,6 +2070,7 @@ window.__ModuleLoader__.load({ const [modelSort, setModelSort] = (0, react.useState)("total"); const [sessionLimit, setSessionLimit] = (0, react.useState)(SESSION_PAGE_SIZE); const [range, setRange] = (0, react.useState)(30); + const [trendModel, setTrendModel] = (0, react.useState)(""); const [selectedDate, setSelectedDate] = (0, react.useState)(); const [operationalDrilldown, setOperationalDrilldown] = (0, react.useState)(false); const [sessionOpenError, setSessionOpenError] = (0, react.useState)(); @@ -1816,6 +2151,13 @@ window.__ModuleLoader__.load({ } }; const data = (0, react.useMemo)(() => aggregateUsage(ids.map((id) => byId[id]).filter((value) => value !== void 0)), [byId, ids]); + const trendModels = (0, react.useMemo)(() => data.models.filter((model) => !isUnattributed(model) && totalTokens$1(model.usage) > 0), [data.models]); + const selectedTrendModel = data.modelDailyCoverage === "complete" && trendModels.some((model) => modelKey(model) === trendModel) ? trendModel : ""; + const trendDays = (0, react.useMemo)(() => selectedTrendModel === "" ? data.days : modelTrendDays(data.modelDays, selectedTrendModel), [ + data.days, + data.modelDays, + selectedTrendModel + ]); const runUsageAnalysis = () => { if (selectedAnalysisModel === void 0) return; usageController.current?.abort(); @@ -1931,9 +2273,13 @@ window.__ModuleLoader__.load({ } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PeriodInsights, { - days: data.days, + days: trendDays, range, + models: trendModels, + selectedModel: selectedTrendModel, + modelDailyCoverage: data.modelDailyCoverage, onRangeChange: setRange, + onModelChange: setTrendModel, t }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EfficiencyPanel, { @@ -1956,8 +2302,12 @@ window.__ModuleLoader__.load({ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BudgetPanel, { operationalDays: data.operationalDays, dailyCoverage: data.dailyCoverage, + models: data.models, + modelDays: data.modelDays, + modelDailyCoverage: data.modelDailyCoverage, snapshot: budget, setBudget, + setRouteBudget, t }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { @@ -2207,8 +2557,12 @@ window.__ModuleLoader__.load({ setCatalog({ status: "idle" }); }; const closeModal = () => { + catalogController.current?.abort(); + catalogController.current = void 0; analysisController.current?.abort(); analysisController.current = void 0; + setSelectedModel(void 0); + setCatalog({ status: "idle" }); setAnalysis((current) => current.status === "loading" ? { status: "idle" } : current); setOpen(false); }; @@ -2377,8 +2731,21 @@ window.__ModuleLoader__.load({ //#region src/client/budget-controller.ts const INITIAL = { status: "loading", - budget: 0 + budget: 0, + routeBudgets: [] }; + const MAX_ROUTE_BUDGETS = 64; + /** Stable exact-route identity for settings comparisons and updates. */ + function routeKey(route) { + return JSON.stringify([route.provider, route.model]); + } + /** Whether two normalized route-budget lists contain the same settings. */ + function sameRouteBudgets(left, right) { + return left.length === right.length && left.every((route, index) => { + const other = right[index]; + return other !== void 0 && route.provider === other.provider && route.model === other.model && route.rolling30DayBudget === other.rolling30DayBudget; + }); + } /** Minimal stable observable source consumed by the Settings slot hook binder. */ var BudgetStore = class { snapshot = INITIAL; @@ -2393,17 +2760,41 @@ window.__ModuleLoader__.load({ }; } set(next) { - if (this.snapshot.status === next.status && this.snapshot.budget === next.budget) return; + if (this.snapshot.status === next.status && this.snapshot.budget === next.budget && sameRouteBudgets(this.snapshot.routeBudgets, next.routeBudgets)) return; this.snapshot = next; for (const listener of [...this.listeners]) listener(); } }; - /** Decode the one numeric setting returned by the private Host RPC. */ + /** Decode normalized budget settings returned by the private Host RPC. */ function settingsOf(value) { if (typeof value !== "object" || value === null) return void 0; const budget = value.rolling30DayBudget; if (typeof budget !== "number" || !Number.isSafeInteger(budget) || budget < 0) return void 0; - return { rolling30DayBudget: budget }; + const rawRoutes = value.routeBudgets ?? []; + if (!Array.isArray(rawRoutes) || rawRoutes.length > MAX_ROUTE_BUDGETS) return void 0; + const routeBudgets = []; + const seen = /* @__PURE__ */ new Set(); + for (const value of rawRoutes) { + if (typeof value !== "object" || value === null) return void 0; + const provider = value.provider; + const model = value.model; + const rolling30DayBudget = value.rolling30DayBudget; + if (typeof provider !== "string" || provider.length === 0 || provider.length > 256 || typeof model !== "string" || model.length === 0 || model.length > 256 || typeof rolling30DayBudget !== "number" || !Number.isSafeInteger(rolling30DayBudget) || rolling30DayBudget <= 0) return void 0; + const route = { + provider, + model, + rolling30DayBudget + }; + const key = routeKey(route); + if (seen.has(key)) return void 0; + seen.add(key); + routeBudgets.push(route); + } + routeBudgets.sort((left, right) => left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)); + return { + rolling30DayBudget: budget, + routeBudgets + }; } /** Mirror the private Host settings endpoint onto one HMR-safe observable source. */ var TokenUsageBudgetController = class { @@ -2417,13 +2808,14 @@ window.__ModuleLoader__.load({ constructor(connection) { this.connection = connection; } - /** Fetch the durable budget unless the current page cannot call loopback-only endpoints. */ + /** Fetch durable budgets unless the current page cannot call loopback-only endpoints. */ async load() { const generation = ++this.generation; if (!this.connection.isLoopback) { this.publish(generation, { status: "unavailable", - budget: 0 + budget: 0, + routeBudgets: [] }); return; } @@ -2432,50 +2824,77 @@ window.__ModuleLoader__.load({ const settings = result.ok ? settingsOf(result.value) : void 0; this.publish(generation, settings === void 0 ? { status: "unavailable", - budget: 0 + budget: 0, + routeBudgets: [] } : { status: "ready", - budget: settings.rolling30DayBudget + budget: settings.rolling30DayBudget, + routeBudgets: settings.routeBudgets }); } catch (_budgetReadFailure) { this.publish(generation, { status: "unavailable", - budget: 0 + budget: 0, + routeBudgets: [] }); } } - /** Persist one whole-token rolling budget and return the value that remains durable. */ + /** Persist one whole-token global rolling budget and return the durable value. */ setBudget(rolling30DayBudget) { if (!Number.isSafeInteger(rolling30DayBudget) || rolling30DayBudget < 0) return Promise.resolve(this.store.getSnapshot().budget); - const operation = this.writeQueue.then(() => this.writeBudget(rolling30DayBudget)); - this.writeQueue = operation.then(() => void 0, () => void 0); - return operation; + return this.enqueue(async () => (await this.writeSettings({ rolling30DayBudget })).budget); + } + /** Add, replace, or remove one exact-route rolling budget; zero removes it. */ + setRouteBudget(provider, model, rolling30DayBudget) { + if (provider.length === 0 || provider.length > 256 || model.length === 0 || model.length > 256 || !Number.isSafeInteger(rolling30DayBudget) || rolling30DayBudget < 0) return Promise.resolve(); + return this.enqueue(async () => { + const current = this.store.getSnapshot().routeBudgets; + const key = routeKey({ + provider, + model + }); + const routeBudgets = current.filter((route) => routeKey(route) !== key).concat(rolling30DayBudget === 0 ? [] : [{ + provider, + model, + rolling30DayBudget + }]).sort((left, right) => left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)); + if (routeBudgets.length > MAX_ROUTE_BUDGETS) return; + await this.writeSettings({ routeBudgets }); + }); + } + /** Serialize settings writes so each patch is based on the latest durable snapshot. */ + enqueue(operation) { + const queued = this.writeQueue.then(operation); + this.writeQueue = queued.then(() => void 0, () => void 0); + return queued; } - /** Execute one queued budget write against the latest published durable value. */ - async writeBudget(rolling30DayBudget) { - if (this.disposed) return this.store.getSnapshot().budget; + /** Execute one queued settings patch and publish the Host-returned durable value. */ + async writeSettings(payload) { + if (this.disposed) return this.store.getSnapshot(); const previous = this.store.getSnapshot(); const fallback = previous.status === "ready" ? previous : { status: "unavailable", - budget: 0 + budget: 0, + routeBudgets: [] }; const generation = ++this.generation; if (!this.connection.isLoopback) { this.publish(generation, fallback); - return fallback.budget; + return fallback; } try { - const result = await this.connection.rpc.call(TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT.budgetWrite, { rolling30DayBudget }); + const result = await this.connection.rpc.call(TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT.budgetWrite, payload); const settings = result.ok ? settingsOf(result.value) : void 0; const next = settings === void 0 ? fallback : { status: "ready", - budget: settings.rolling30DayBudget + budget: settings.rolling30DayBudget, + routeBudgets: settings.routeBudgets }; this.publish(generation, next); - return next.budget; + return next; } catch (_budgetWriteFailure) { this.publish(generation, fallback); - return fallback.budget; + return fallback; } } /** Stop all late asynchronous publications after the owning Client fiber disposes. */ @@ -2545,6 +2964,7 @@ window.__ModuleLoader__.load({ while (!pollingSignal.aborted) { try { const result = await connection.rpc.call(TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT.analysisProgress, { progressId }, pollingSignal); + if (pollingSignal.aborted) return; if (result.ok) { const progress = analysisProgressOf(result.value); if (progress !== void 0) onProgress?.(progress); @@ -3046,6 +3466,11 @@ window.__ModuleLoader__.load({ more: "多", trend: "用量趋势", trendIntro: "按 UTC 日期比较当前周期与前一等长周期。", + trendModel: "趋势模型", + allModels: "全部模型", + trendModelScope: "当前趋势仅统计 {route} 的可靠逐日 bucket。", + modelDailyCoverageUnavailable: "旧版回退记录没有日期×模型 bucket;完成历史重折叠后才能按模型筛选趋势。", + modelDailyCoveragePartial: "部分历史记录缺少日期×模型 bucket;为避免低估,模型趋势筛选暂不可用。", rangeDays: "{count} 天", periodTokens: "最近 {count} 天", periodChange: "较前一周期", @@ -3061,6 +3486,24 @@ window.__ModuleLoader__.load({ budgetExceeded: "已超出预算 {excess} Token。", budgetRunRate: "按最近 7 个完整 UTC 日:日均 {average} Token,预计滚动 30 日 {projected} Token。", budgetForecastExceeded: "按当前运行率预计 30 日 {projected} Token,将超过预算 {budget} Token。", + routeBudgets: "模型预算与预警", + routeBudgetsIntro: "为精确 provider/model 设置滚动 30 日 Token 预算;达到 80% 预警,达到 100% 或预测超额时优先提示。", + routeBudgetModel: "模型路由", + routeBudgetChooseModel: "选择有用量的模型", + routeBudgetInput: "模型 30 日预算(Token)", + routeBudgetSave: "保存模型预算", + routeBudgetRemove: "移除预算", + routeBudgetRemoveFor: "移除 {route} 的预算", + routeBudgetsEmpty: "尚未设置模型级预算。", + routeBudgetCoverageUnavailable: "没有完整且守恒的日期×模型数据;可配置预算,但暂不计算消耗和预测。", + routeBudgetCoveragePartial: "部分日期×模型数据缺失或无法与总量守恒;为避免低估,模型预算状态暂不可用。", + routeBudgetUnavailable: "等待可靠数据", + routeBudgetStatus_healthy: "健康", + routeBudgetStatus_warning: "已达 80% 预警", + "routeBudgetStatus_forecast-exceeded": "预测将超额", + routeBudgetStatus_exceeded: "已超额", + routeBudgetProgress: "已用 {used} / {budget} Token({percent}%)", + routeBudgetForecast: "按最近 7 个完整 UTC 日预测滚动 30 日 {projected} Token。", estimatedCost: "估算费用(USD)", cacheReadSavings: "缓存读取避免费用(USD)", priceCoverage: "费率覆盖", @@ -3081,7 +3524,7 @@ window.__ModuleLoader__.load({ routeConcentration: "Top 路由 {route} 占全部 Token 的 {topOne};Top 3 合计 {topThree}。", unattributedShare: "未归因用量占全部 Token 的 {share};其调用次数与路由效率未知。", usageSignals: "用量信号", - usageSignalsIntro: "运行率和突增检测只使用完整 UTC 日;不对每日用量归因模型。", + usageSignalsIntro: "运行率和突增检测只使用完整 UTC 日,当前仍是全局口径,不作为模型级告警。", dailyRunRate: "7 日日均 Token", projectedThirtyDayUsage: "预计 30 日 Token", anomalyRatio: "昨日相对基线", @@ -3128,6 +3571,8 @@ window.__ModuleLoader__.load({ exportJson: "JSON 汇总", exportDaily: "每日 CSV", exportModels: "模型 CSV", + exportModelDaily: "日期×模型 CSV", + exportModelDailyUnavailable: "日期×模型覆盖不完整或未通过守恒校验,暂不导出以避免生成低估数据。", recentSessions: "会话记录", openSessionFailed: "无法打开会话:{message}", showMoreSessions: "显示更多会话(已显示 {shown}/{total})", @@ -3222,6 +3667,11 @@ window.__ModuleLoader__.load({ more: "More", trend: "Usage trends", trendIntro: "Compares the current UTC period with the preceding period of equal length.", + trendModel: "Trend model", + allModels: "All models", + trendModelScope: "This trend includes reliable daily buckets for {route} only.", + modelDailyCoverageUnavailable: "Legacy fallback records lack date-by-model buckets. Model filtering becomes available after history is refolded.", + modelDailyCoveragePartial: "Some history lacks date-by-model buckets, so model filtering is unavailable to avoid undercounting.", rangeDays: "{count} days", periodTokens: "Last {count} days", periodChange: "Previous period", @@ -3237,6 +3687,24 @@ window.__ModuleLoader__.load({ budgetExceeded: "{excess} tokens over budget.", budgetRunRate: "Latest 7 complete UTC days: {average} tokens/day; projected rolling 30 days: {projected} tokens.", budgetForecastExceeded: "At the current run rate, the projected 30-day usage of {projected} tokens exceeds the {budget}-token budget.", + routeBudgets: "Model budgets and alerts", + routeBudgetsIntro: "Set a rolling 30-day Token budget for an exact provider/model route. Warning starts at 80%; actual and forecast overages are prioritized.", + routeBudgetModel: "Model route", + routeBudgetChooseModel: "Choose a model with usage", + routeBudgetInput: "Model 30-day budget (tokens)", + routeBudgetSave: "Save model budget", + routeBudgetRemove: "Remove budget", + routeBudgetRemoveFor: "Remove the budget for {route}", + routeBudgetsEmpty: "No model-level budget is configured.", + routeBudgetCoverageUnavailable: "No complete, conserved date-by-model data is available. Budgets can be configured, but usage and forecasts are not evaluated yet.", + routeBudgetCoveragePartial: "Some date-by-model data is missing or does not conserve totals. Model budget status is unavailable to avoid undercounting.", + routeBudgetUnavailable: "Waiting for reliable data", + routeBudgetStatus_healthy: "Healthy", + routeBudgetStatus_warning: "80% warning reached", + "routeBudgetStatus_forecast-exceeded": "Forecast over budget", + routeBudgetStatus_exceeded: "Over budget", + routeBudgetProgress: "{used} / {budget} tokens used ({percent}%)", + routeBudgetForecast: "Latest 7 complete UTC days project {projected} tokens over a rolling 30-day period.", estimatedCost: "Estimated cost (USD)", cacheReadSavings: "Cache-read avoided cost (USD)", priceCoverage: "Rate coverage", @@ -3257,7 +3725,7 @@ window.__ModuleLoader__.load({ routeConcentration: "Top route {route} accounts for {topOne} of all Tokens; Top 3 account for {topThree}.", unattributedShare: "Unattributed usage is {share} of all Tokens; its attempt count and route efficiency are unknown.", usageSignals: "Usage signals", - usageSignalsIntro: "Run rate and spike detection use complete UTC days only; daily usage is not attributed to a model.", + usageSignalsIntro: "Run rate and spike detection use complete UTC days and remain global signals, not model-level alerts.", dailyRunRate: "7-day daily Tokens", projectedThirtyDayUsage: "Projected 30-day Tokens", anomalyRatio: "Yesterday versus baseline", @@ -3304,6 +3772,8 @@ window.__ModuleLoader__.load({ exportJson: "JSON summary", exportDaily: "Daily CSV", exportModels: "Model CSV", + exportModelDaily: "Date × model CSV", + exportModelDailyUnavailable: "Date-by-model coverage is incomplete or failed conservation checks, so this export is disabled to avoid undercounting.", recentSessions: "Session records", openSessionFailed: "Unable to open session: {message}", showMoreSessions: "Show more sessions ({shown}/{total} shown)", @@ -3416,6 +3886,7 @@ window.__ModuleLoader__.load({ inject: () => ({ hooks: { budget: budget.store }, setBudget: (value) => budget.setBudget(value), + setRouteBudget: (provider, model, value) => budget.setRouteBudget(provider, model, value), download: browserDownload, saveTrajectoryAnalysis: (analysis) => { trajectoryHistory.save(analysis); diff --git a/lib/client.js.map b/lib/client.js.map index 4643e69..63f5a28 100644 --- a/lib/client.js.map +++ b/lib/client.js.map @@ -1 +1 @@ -{"version":3,"file":"client.js","names":["zeroBuckets","addBuckets","totalTokens","dayKey","totalTokens","inputTokens","isUnattributed","totalTokens","MarkdownText","totalTokens","css","css","Modal","Button","isRecord","isRecord","bucketsOf"],"sources":["../src/client/analytics.ts","../src/client/efficiency.ts","../src/pricing.ts","../src/client/report-safety.ts","../src/client/export.ts","../src/client/SafeMarkdownReport.tsx","../src/client/TokenUsageSection.tsx","../src/client/TrajectoryAnalysisAction.tsx","../src/rpc.ts","../src/client/budget-controller.ts","../src/client/analysis-progress-client.ts","../src/client/trajectory-analysis-client.ts","../src/client/trajectory-history.ts","../src/client/usage-analysis-client.ts","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["import type { DailyTokenUsageRecord, TokenUsageBuckets } from '../types.ts'\n\n/** A session contribution to one selected UTC day. */\nexport interface DailySessionContributor {\n id: string\n title: string\n usage: TokenUsageBuckets\n}\n\n/** Minimum session data required to find one day's contributors. */\nexport interface DailySessionSource {\n id: string\n title: string\n days: readonly DailyTokenUsageRecord[]\n}\n\n/** Aggregate comparison for one trailing UTC period. */\nexport interface PeriodInsight {\n days: number\n usage: TokenUsageBuckets\n previousUsage: TokenUsageBuckets\n activeDays: number\n peak: DailyTokenUsageRecord | undefined\n}\n\n/** Complete-UTC-day burn rate projected to a rolling 30-day period. */\nexport interface RunRateInsight {\n observedDays: number\n averageDailyTokens: number\n projectedThirtyDayTokens: number\n}\n\n/** A robust comparison of yesterday's complete usage with preceding active days. */\nexport interface DailyAnomalyInsight {\n date: string\n tokens: number\n baselineMedianTokens: number\n baselineMadTokens: number\n activeBaselineDays: number\n ratio: number\n excessTokens: number\n status: 'normal' | 'elevated'\n}\n\n/** Detached zero buckets for analytics calculations. */\nfunction zeroBuckets(): TokenUsageBuckets {\n return {\n uncachedInputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n }\n}\n\n/** Add two disjoint Token bucket sets. */\nfunction addBuckets(left: TokenUsageBuckets, right: TokenUsageBuckets): TokenUsageBuckets {\n return {\n uncachedInputTokens: left.uncachedInputTokens + right.uncachedInputTokens,\n outputTokens: left.outputTokens + right.outputTokens,\n cacheReadTokens: left.cacheReadTokens + right.cacheReadTokens,\n cacheWriteTokens: left.cacheWriteTokens + right.cacheWriteTokens,\n }\n}\n\n/** Full request/response total without counting reasoning output twice. */\nfunction totalTokens(usage: TokenUsageBuckets): number {\n return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + usage.outputTokens\n}\n\n/** Stable UTC day key shared with the durable projection. */\nfunction dayKey(time: number): string {\n return new Date(time).toISOString().slice(0, 10)\n}\n\n/** Build a newest-inclusive UTC date range. */\nfunction datesEndingOn(now: number, length: number): string[] {\n const end = new Date(`${dayKey(now)}T00:00:00.000Z`)\n end.setUTCDate(end.getUTCDate() - length + 1)\n const dates: string[] = []\n for (let offset = 0; offset < length; offset += 1) {\n const date = new Date(end)\n date.setUTCDate(date.getUTCDate() + offset)\n dates.push(dayKey(date.getTime()))\n }\n return dates\n}\n\n/** Aggregate a fixed sequence of UTC dates from a daily bucket lookup. */\nfunction aggregateDates(byDate: ReadonlyMap, dates: readonly string[]): TokenUsageBuckets {\n return dates.reduce((usage, date) => addBuckets(usage, byDate.get(date) ?? zeroBuckets()), zeroBuckets())\n}\n\n/** Derive period totals, comparison totals, activity, and the highest-use day. */\nexport function periodInsight(\n records: readonly DailyTokenUsageRecord[],\n days: number,\n now = Date.now(),\n): PeriodInsight {\n const byDate = new Map(records.map(record => [record.date, record.usage]))\n const currentDates = datesEndingOn(now, days)\n const previousDates = datesEndingOn(now - days * 86_400_000, days)\n const currentRecords = currentDates.map(date => ({\n date,\n usage: { ...(byDate.get(date) ?? zeroBuckets()) },\n }))\n const active = currentRecords.filter(record => totalTokens(record.usage) > 0)\n const peak = active.reduce((highest, record) =>\n highest === undefined || totalTokens(record.usage) > totalTokens(highest.usage) ? record : highest,\n undefined)\n return {\n days,\n usage: aggregateDates(byDate, currentDates),\n previousUsage: aggregateDates(byDate, previousDates),\n activeDays: active.length,\n peak,\n }\n}\n\n/** Return the median of a non-empty numeric list without retaining a source reference. */\nfunction median(values: readonly number[]): number {\n const sorted = values.slice().sort((left, right) => left - right)\n const middle = Math.floor(sorted.length / 2)\n return sorted.length % 2 === 0\n ? (sorted[middle - 1]! + sorted[middle]!) / 2\n : sorted[middle]!\n}\n\n/** Aggregate one complete UTC day range ending before the current partial day. */\nfunction completeDaysEndingBefore(now: number, length: number): string[] {\n return datesEndingOn(now - 86_400_000, length)\n}\n\n/** Project a 30-day run rate from the latest seven complete UTC calendar days. */\nexport function runRateInsight(records: readonly DailyTokenUsageRecord[], now = Date.now()): RunRateInsight {\n const byDate = new Map(records.map(record => [record.date, record.usage]))\n const dates = completeDaysEndingBefore(now, 7)\n const tokens = dates.reduce((sum, date) => sum + totalTokens(byDate.get(date) ?? zeroBuckets()), 0)\n const averageDailyTokens = tokens / dates.length\n return {\n observedDays: dates.length,\n averageDailyTokens,\n projectedThirtyDayTokens: Math.round(averageDailyTokens * 30),\n }\n}\n\n/** Detect an elevated latest complete UTC day using the preceding 28 days' active-day median and MAD. */\nexport function dailyAnomalyInsight(\n records: readonly DailyTokenUsageRecord[],\n now = Date.now(),\n): DailyAnomalyInsight | undefined {\n const byDate = new Map(records.map(record => [record.date, record.usage]))\n const [date] = completeDaysEndingBefore(now, 1)\n if (date === undefined) return undefined\n const tokens = totalTokens(byDate.get(date) ?? zeroBuckets())\n const baselineDates = datesEndingOn(now - 2 * 86_400_000, 28)\n const activeBaseline = baselineDates\n .map(baselineDate => totalTokens(byDate.get(baselineDate) ?? zeroBuckets()))\n .filter(value => value > 0)\n if (tokens === 0 || activeBaseline.length < 5) return undefined\n const baselineMedianTokens = median(activeBaseline)\n const baselineMadTokens = median(activeBaseline.map(value => Math.abs(value - baselineMedianTokens)))\n const robustThreshold = baselineMadTokens === 0\n ? baselineMedianTokens * 3\n : baselineMedianTokens + 3 * 1.4826 * baselineMadTokens\n const ratio = baselineMedianTokens === 0 ? 0 : tokens / baselineMedianTokens\n const excessTokens = Math.max(0, tokens - baselineMedianTokens)\n return {\n date,\n tokens,\n baselineMedianTokens,\n baselineMadTokens,\n activeBaselineDays: activeBaseline.length,\n ratio,\n excessTokens,\n status: tokens > robustThreshold ? 'elevated' : 'normal',\n }\n}\n\n/** List sessions contributing usage to one UTC day, highest usage first. */\nexport function dailyContributors(\n sessions: readonly DailySessionSource[],\n date: string,\n): DailySessionContributor[] {\n return sessions.flatMap((session) => {\n const record = session.days.find(day => day.date === date)\n if (record === undefined || totalTokens(record.usage) === 0) return []\n return [{\n id: session.id,\n title: session.title,\n usage: { ...record.usage },\n }]\n }).sort((left, right) => totalTokens(right.usage) - totalTokens(left.usage)\n || left.title.localeCompare(right.title)\n || left.id.localeCompare(right.id))\n}\n","/** Aggregate-only efficiency and attribution metrics for the Token dashboard. */\r\n\r\nimport type { ModelTokenUsageRecord, TokenUsageBuckets } from '../types.ts'\r\n\r\n/** One route's stable share of all dashboard Tokens. */\r\nexport interface RouteContribution {\r\n provider: string\r\n model: string\r\n tokens: number\r\n share: number\r\n}\r\n\r\n/** Derived efficiency signals that never inspect session content. */\r\nexport interface UsageEfficiencyInsight {\r\n assistantAttempts: number\r\n compactionAttempts: number\r\n assistantTokens: number\r\n tokensPerAssistantAttempt: number | undefined\r\n compactionsPerHundredAssistantAttempts: number | undefined\r\n compactionTokenShare: number | undefined\r\n cacheReadInputShare: number | undefined\r\n cacheWriteInputShare: number | undefined\r\n uncachedInputShare: number | undefined\r\n outputToInputRatio: number | undefined\r\n unattributedTokenShare: number\r\n topRoutes: readonly RouteContribution[]\r\n}\r\n\r\n/** Return one full disjoint-bucket total. */\r\nfunction totalTokens(usage: TokenUsageBuckets): number {\r\n return usage.uncachedInputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens\r\n}\r\n\r\n/** Return the prompt-side total across uncached and cache buckets. */\r\nfunction inputTokens(usage: TokenUsageBuckets): number {\r\n return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens\r\n}\r\n\r\n/** Identify legacy fallback rows with no source route attribution. */\r\nfunction isUnattributed(model: ModelTokenUsageRecord): boolean {\r\n return model.provider === '' && model.model === ''\r\n}\r\n\r\n/** Derive request efficiency, compaction overhead, and stable top-route shares. */\r\nexport function usageEfficiencyInsight(\r\n usage: TokenUsageBuckets,\r\n compactionUsage: TokenUsageBuckets,\r\n models: readonly ModelTokenUsageRecord[],\r\n assistantAttempts: number,\r\n compactionAttempts: number,\r\n): UsageEfficiencyInsight {\r\n const total = totalTokens(usage)\r\n const input = inputTokens(usage)\r\n const attributed = models.filter(model => !isUnattributed(model))\r\n const unattributedTokens = models\r\n .filter(isUnattributed)\r\n .reduce((sum, model) => sum + totalTokens(model.usage), 0)\r\n const assistantTokens = Math.max(0, total - totalTokens(compactionUsage))\r\n const topRoutes = attributed\r\n .map(model => ({\r\n provider: model.provider,\r\n model: model.model,\r\n tokens: totalTokens(model.usage),\r\n }))\r\n .sort((left, right) => right.tokens - left.tokens\r\n || left.provider.localeCompare(right.provider)\r\n || left.model.localeCompare(right.model))\r\n .slice(0, 3)\r\n .map(route => ({ ...route, share: total === 0 ? 0 : route.tokens / total }))\r\n return {\r\n assistantAttempts,\r\n compactionAttempts,\r\n assistantTokens,\r\n tokensPerAssistantAttempt: assistantAttempts === 0 || unattributedTokens > 0\r\n ? undefined\r\n : assistantTokens / assistantAttempts,\r\n compactionsPerHundredAssistantAttempts: assistantAttempts === 0\r\n ? undefined\r\n : compactionAttempts / assistantAttempts * 100,\r\n compactionTokenShare: total === 0 ? undefined : totalTokens(compactionUsage) / total,\r\n cacheReadInputShare: input === 0 ? undefined : usage.cacheReadTokens / input,\r\n cacheWriteInputShare: input === 0 ? undefined : usage.cacheWriteTokens / input,\r\n uncachedInputShare: input === 0 ? undefined : usage.uncachedInputTokens / input,\r\n outputToInputRatio: input === 0 ? undefined : usage.outputTokens / input,\r\n unattributedTokenShare: total === 0 ? 0 : unattributedTokens / total,\r\n topRoutes,\r\n }\r\n}\r\n","/** Built-in public API price estimates for exact model routes. */\r\n\r\nimport type {\r\n ModelTokenUsageRecord,\r\n PricedModelTokenUsageRecord,\r\n TokenUsageBuckets,\r\n TokenUsageCostSummary,\r\n TokenUsagePriceRate,\r\n} from './types.ts'\r\n\r\nconst TOKENS_PER_MILLION = 1_000_000\r\nexport const PUBLIC_PRICE_CATALOG_URL = 'https://developers.openai.com/api/docs/pricing'\r\n\r\ninterface CatalogRate extends TokenUsagePriceRate {\r\n provider: string\r\n model: string\r\n}\r\n\r\n/**\r\n * OpenAI public USD API rates per million Tokens, retrieved from the official price page on 2025-08-07.\r\n *\r\n * Cache writes use the ordinary input price because these routes publish no separate cache-write tariff.\r\n * The catalog intentionally requires an exact provider/model label match and never borrows a superficially\r\n * similar rate. A matching label does not verify the configured endpoint, reseller, contract, or invoice.\r\n */\r\nconst PUBLIC_USD_RATES: readonly CatalogRate[] = [\r\n { provider: 'openai', model: 'gpt-5', currency: 'USD', inputPerMillion: 1.25, cacheReadPerMillion: 0.125, cacheWritePerMillion: 1.25, outputPerMillion: 10, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-08-07' },\r\n { provider: 'openai', model: 'gpt-5-2025-08-07', currency: 'USD', inputPerMillion: 1.25, cacheReadPerMillion: 0.125, cacheWritePerMillion: 1.25, outputPerMillion: 10, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-08-07' },\r\n { provider: 'openai', model: 'gpt-5-mini', currency: 'USD', inputPerMillion: 0.25, cacheReadPerMillion: 0.025, cacheWritePerMillion: 0.25, outputPerMillion: 2, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-08-07' },\r\n { provider: 'openai', model: 'gpt-5-mini-2025-08-07', currency: 'USD', inputPerMillion: 0.25, cacheReadPerMillion: 0.025, cacheWritePerMillion: 0.25, outputPerMillion: 2, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-08-07' },\r\n { provider: 'openai', model: 'gpt-5-nano', currency: 'USD', inputPerMillion: 0.05, cacheReadPerMillion: 0.005, cacheWritePerMillion: 0.05, outputPerMillion: 0.4, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-08-07' },\r\n { provider: 'openai', model: 'gpt-5-nano-2025-08-07', currency: 'USD', inputPerMillion: 0.05, cacheReadPerMillion: 0.005, cacheWritePerMillion: 0.05, outputPerMillion: 0.4, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-08-07' },\r\n { provider: 'openai', model: 'gpt-4.1', currency: 'USD', inputPerMillion: 2, cacheReadPerMillion: 0.5, cacheWritePerMillion: 2, outputPerMillion: 8, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-04-14' },\r\n { provider: 'openai', model: 'gpt-4.1-mini', currency: 'USD', inputPerMillion: 0.4, cacheReadPerMillion: 0.1, cacheWritePerMillion: 0.4, outputPerMillion: 1.6, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-04-14' },\r\n { provider: 'openai', model: 'gpt-4.1-nano', currency: 'USD', inputPerMillion: 0.1, cacheReadPerMillion: 0.025, cacheWritePerMillion: 0.1, outputPerMillion: 0.4, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-04-14' },\r\n { provider: 'openai', model: 'gpt-4o', currency: 'USD', inputPerMillion: 2.5, cacheReadPerMillion: 1.25, cacheWritePerMillion: 2.5, outputPerMillion: 10, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-04-14' },\r\n { provider: 'openai', model: 'gpt-4o-mini', currency: 'USD', inputPerMillion: 0.15, cacheReadPerMillion: 0.075, cacheWritePerMillion: 0.15, outputPerMillion: 0.6, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-04-14' },\r\n]\r\n\r\n/** Version marker displayed with every estimate, not a live pricing feed. */\r\nexport const PUBLIC_PRICE_CATALOG_AS_OF = '2025-08-07'\r\n\r\n/** Return a detached public rate for one exact catalog label; this does not verify its billing endpoint. */\r\nexport function publicPriceFor(provider: string, model: string): TokenUsagePriceRate | undefined {\r\n const rate = PUBLIC_USD_RATES.find(entry => entry.provider === provider && entry.model === model)\r\n return rate === undefined ? undefined : {\r\n currency: rate.currency,\r\n inputPerMillion: rate.inputPerMillion,\r\n outputPerMillion: rate.outputPerMillion,\r\n cacheReadPerMillion: rate.cacheReadPerMillion,\r\n cacheWritePerMillion: rate.cacheWritePerMillion,\r\n sourceUrl: rate.sourceUrl,\r\n asOf: rate.asOf,\r\n }\r\n}\r\n\r\n/** Return complete Tokens across the four disjoint provider-reported buckets. */\r\nexport function totalTokens(usage: TokenUsageBuckets): number {\r\n return usage.uncachedInputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens\r\n}\r\n\r\n/** Calculate one exact-route USD estimate from a fully published four-bucket rate. */\r\nexport function estimateCostUSD(usage: TokenUsageBuckets, rate: TokenUsagePriceRate): number {\r\n return (\r\n usage.uncachedInputTokens * rate.inputPerMillion\r\n + usage.outputTokens * rate.outputPerMillion\r\n + usage.cacheReadTokens * rate.cacheReadPerMillion\r\n + usage.cacheWriteTokens * rate.cacheWritePerMillion\r\n ) / TOKENS_PER_MILLION\r\n}\r\n\r\n/** Add built-in public USD pricing to model aggregates without pricing unknown routes. */\r\nexport function tokenUsageCostSummary(models: readonly ModelTokenUsageRecord[]): TokenUsageCostSummary {\r\n let totalCostUSD = 0\r\n let cacheReadSavingsUSD = 0\r\n let coveredTokens = 0\r\n let totalTokensCount = 0\r\n let coveredModels = 0\r\n const pricedModels: PricedModelTokenUsageRecord[] = models.map(model => {\r\n const usage = { ...model.usage }\r\n const total = totalTokens(usage)\r\n totalTokensCount += total\r\n const rate = publicPriceFor(model.provider, model.model)\r\n if (rate === undefined) {\r\n return {\r\n provider: model.provider,\r\n model: model.model,\r\n assistantRequests: model.assistantRequests,\r\n compactionRequests: model.compactionRequests,\r\n usage,\r\n }\r\n }\r\n const totalCost = estimateCostUSD(usage, rate)\r\n const cacheReadSavings = usage.cacheReadTokens * Math.max(0, rate.inputPerMillion - rate.cacheReadPerMillion) / TOKENS_PER_MILLION\r\n totalCostUSD += totalCost\r\n cacheReadSavingsUSD += cacheReadSavings\r\n coveredTokens += total\r\n if (total > 0) coveredModels += 1\r\n return {\r\n provider: model.provider,\r\n model: model.model,\r\n assistantRequests: model.assistantRequests,\r\n compactionRequests: model.compactionRequests,\r\n usage,\r\n totalCostUSD: totalCost,\r\n cacheReadSavingsUSD: cacheReadSavings,\r\n rate,\r\n }\r\n })\r\n return {\r\n currency: 'USD',\r\n totalCostUSD,\r\n cacheReadSavingsUSD,\r\n coveredTokens,\r\n totalTokens: totalTokensCount,\r\n coveredModels,\r\n totalModels: models.filter(model => totalTokens(model.usage) > 0).length,\r\n models: pricedModels,\r\n }\r\n}\r\n","/** Disable model-supplied Markdown images and raw HTML while preserving readable text and links. */\nexport function safeModelMarkdown(markdown: string): string {\n return markdown\n .replace(/!\\[([^\\]]*)\\]\\(([^)\\n]+)\\)/g, (_match, alt: string, destination: string) =>\n `[${alt.length === 0 ? 'image' : alt}](${destination})`)\n .replace(/!\\[([^\\]]*)\\]\\[([^\\]]*)\\]/g, (_match, alt: string, reference: string) =>\n `[${alt.length === 0 ? 'image' : alt}][${reference}]`)\n .replace(/!\\[([^\\]]*)\\]/g, (_match, alt: string) => alt.length === 0 ? 'image' : alt)\n // A CommonMark image always starts with `![`; entity-encode any marker left by the readable rewrites above.\n .replace(/!\\[/g, '![')\n .replace(/<(?=[A-Za-z/!?])/g, '<')\n}\n","import type {\n DailyTokenUsageRecord,\n ModelTokenUsageRecord,\n PricedModelTokenUsageRecord,\n TokenUsageAnalysis,\n TokenUsageBuckets,\n TokenUsageCostSummary,\n TrajectoryAnalysis,\n} from '../types.ts'\nimport { PUBLIC_PRICE_CATALOG_AS_OF, tokenUsageCostSummary } from '../pricing.ts'\nimport { safeModelMarkdown } from './report-safety.ts'\n\n/** Narrow aggregate-only source accepted by the privacy-safe export Module. */\nexport interface UsageExportSource {\n usage: TokenUsageBuckets\n compactionUsage: TokenUsageBuckets\n models: readonly ModelTokenUsageRecord[]\n days: readonly DailyTokenUsageRecord[]\n}\n\n/** Versioned aggregate-only JSON document with exact compaction and partial public-rate pricing. */\nexport interface TokenUsageExportV2 {\n schema: 'dsh-token-usage/export-v2'\n generatedAt: string\n timezone: 'UTC'\n totals: TokenUsageBuckets\n compactionUsage: TokenUsageBuckets\n pricingCatalogAsOf: string\n pricing: TokenUsageCostSummary\n models: PricedModelTokenUsageRecord[]\n days: DailyTokenUsageRecord[]\n}\n\n/** Browser download Adapter owned by the Client plugin registration. */\nexport interface DownloadPort {\n save(name: string, mime: string, content: string): void\n}\n\n/** Detached copy of one bucket object. */\nfunction copyBuckets(usage: TokenUsageBuckets): TokenUsageBuckets {\n return {\n uncachedInputTokens: usage.uncachedInputTokens,\n outputTokens: usage.outputTokens,\n cacheReadTokens: usage.cacheReadTokens,\n cacheWriteTokens: usage.cacheWriteTokens,\n }\n}\n\n/** Return a detached public-price estimate that never retains source aggregates. */\nfunction copiedPricing(models: readonly ModelTokenUsageRecord[]): TokenUsageCostSummary {\n const summary = tokenUsageCostSummary(models)\n return {\n ...summary,\n models: summary.models.map(model => ({\n provider: model.provider,\n model: model.model,\n assistantRequests: model.assistantRequests,\n compactionRequests: model.compactionRequests,\n usage: copyBuckets(model.usage),\n ...model.totalCostUSD === undefined ? {} : { totalCostUSD: model.totalCostUSD },\n ...model.cacheReadSavingsUSD === undefined ? {} : { cacheReadSavingsUSD: model.cacheReadSavingsUSD },\n ...model.rate === undefined ? {} : { rate: { ...model.rate } },\n })),\n }\n}\n\n/** Stable aggregate-only document that never accepts session data. */\nexport function tokenUsageExport(source: UsageExportSource, generatedAt: string): TokenUsageExportV2 {\n const pricing = copiedPricing(source.models)\n return {\n schema: 'dsh-token-usage/export-v2',\n generatedAt,\n timezone: 'UTC',\n totals: copyBuckets(source.usage),\n compactionUsage: copyBuckets(source.compactionUsage),\n pricingCatalogAsOf: PUBLIC_PRICE_CATALOG_AS_OF,\n pricing,\n models: pricing.models\n .slice()\n .sort((left, right) => left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)),\n days: source.days\n .map(day => ({ date: day.date, usage: copyBuckets(day.usage) }))\n .sort((left, right) => left.date.localeCompare(right.date)),\n }\n}\n\n/** Serialize the versioned aggregate-only export document. */\nexport function tokenUsageJson(source: UsageExportSource, generatedAt: string): string {\n return `${JSON.stringify(tokenUsageExport(source, generatedAt), null, 2)}\\n`\n}\n\n/** Prevent spreadsheet applications from interpreting an untrusted cell as a formula. */\nfunction spreadsheetText(value: string): string {\n return /^[=+\\-@]/.test(value) ? `'${value}` : value\n}\n\n/** Escape one scalar value as a CSV cell. */\nfunction csvCell(value: number | string): string {\n const text = typeof value === 'string' ? spreadsheetText(value) : String(value)\n return /[\",\\r\\n]/.test(text) ? `\"${text.replaceAll('\"', '\"\"')}\"` : text\n}\n\n/** Encode a complete CSV table with a stable CRLF delimiter. */\nfunction csv(rows: ReadonlyArray>): string {\n return `${rows.map(row => row.map(csvCell).join(',')).join('\\r\\n')}\\r\\n`\n}\n\n/** Export the daily aggregate buckets without session identity or conversation content. */\nexport function dailyUsageCsv(source: Pick): string {\n return csv([\n ['date', 'uncachedInputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens', 'inputTokens', 'totalTokens'],\n ...source.days\n .slice()\n .sort((left, right) => left.date.localeCompare(right.date))\n .map(day => [\n day.date,\n day.usage.uncachedInputTokens,\n day.usage.outputTokens,\n day.usage.cacheReadTokens,\n day.usage.cacheWriteTokens,\n day.usage.uncachedInputTokens + day.usage.cacheReadTokens + day.usage.cacheWriteTokens,\n day.usage.uncachedInputTokens + day.usage.cacheReadTokens + day.usage.cacheWriteTokens + day.usage.outputTokens,\n ]),\n ])\n}\n\n/** Export model aggregate buckets without session identity or conversation content. */\nexport function modelUsageCsv(source: Pick): string {\n const models = tokenUsageCostSummary(source.models).models\n return csv([\n ['provider', 'model', 'assistantRequests', 'compactionRequests', 'uncachedInputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens', 'inputTokens', 'totalTokens', 'estimatedCostUSD', 'cacheReadSavingsUSD', 'pricingCatalogAsOf'],\n ...models\n .slice()\n .sort((left, right) => left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model))\n .map(model => [\n model.provider,\n model.model,\n model.assistantRequests,\n model.compactionRequests,\n model.usage.uncachedInputTokens,\n model.usage.outputTokens,\n model.usage.cacheReadTokens,\n model.usage.cacheWriteTokens,\n model.usage.uncachedInputTokens + model.usage.cacheReadTokens + model.usage.cacheWriteTokens,\n model.usage.uncachedInputTokens + model.usage.cacheReadTokens + model.usage.cacheWriteTokens + model.usage.outputTokens,\n model.totalCostUSD ?? '',\n model.cacheReadSavingsUSD ?? '',\n model.rate?.asOf ?? '',\n ]),\n ])\n}\n\n/** Create a filesystem-safe UTC suffix without embedding session identity. */\nexport function analysisReportFilename(kind: 'usage' | 'trajectory', generatedAt: string): string {\n const timestamp = generatedAt.replaceAll(/[^0-9A-Za-z]+/g, '-').replaceAll(/^-|-$/g, '')\n return `dsh-${kind}-analysis-${timestamp}.md`\n}\n\n/** Serialize one aggregate analysis as a portable Markdown report. */\nexport function tokenUsageAnalysisMarkdown(analysis: TokenUsageAnalysis): string {\n const auxiliary = analysis.analysisUsage === undefined\n ? 'Unavailable'\n : String(analysis.analysisUsage.uncachedInputTokens\n + analysis.analysisUsage.outputTokens\n + analysis.analysisUsage.cacheReadTokens\n + analysis.analysisUsage.cacheWriteTokens)\n const output = analysis.analysisUsage === undefined ? 'Unavailable' : String(analysis.analysisUsage.outputTokens)\n return [\n '# DSH Token Usage Analysis',\n '',\n `- Generated: ${analysis.generatedAt}`,\n `- Model: ${analysis.model.provider}/${analysis.model.model}`,\n `- Analysis tokens: ${auxiliary}`,\n `- Model output tokens: ${output}`,\n '',\n '## Model Report',\n '',\n safeModelMarkdown(analysis.report),\n '',\n ].join('\\n')\n}\n\n/** Serialize one trajectory analysis with deterministic technical-control evidence. */\nexport function trajectoryAnalysisMarkdown(analysis: TrajectoryAnalysis): string {\n const metrics = analysis.metrics\n const auxiliary = analysis.analysisUsage === undefined\n ? 'Unavailable'\n : String(analysis.analysisUsage.uncachedInputTokens\n + analysis.analysisUsage.outputTokens\n + analysis.analysisUsage.cacheReadTokens\n + analysis.analysisUsage.cacheWriteTokens)\n const output = analysis.analysisUsage === undefined ? 'Unavailable' : String(analysis.analysisUsage.outputTokens)\n const approvalAuditRows = metrics.completeComplianceEvidenceAvailable\n ? [\n `| Approval closure | ${metrics.approvalsResolved}/${metrics.approvalsAsked} |`,\n `| Rejected/cancelled/unavailable | ${metrics.approvalsRejected + metrics.approvalsCancelled + metrics.approvalsUnavailable} |`,\n `| Unresolved/orphan approval records | ${metrics.unresolvedApprovals}/${metrics.orphanApprovalDecisions} |`,\n '| Persistent approval decisions | Not defined by ApprovalOutcome; session policy events excluded |',\n ]\n : [\n `| Approval requests | ${metrics.approvalsAsked} |`,\n `| Rejected decisions | ${metrics.approvalsRejected} |`,\n '| v3 closure/categorized outcomes/audit gaps | Unavailable in this pre-v3 report |',\n ]\n return [\n '# DSH Session Trajectory Analysis',\n '',\n `- Generated: ${analysis.generatedAt}`,\n `- Model: ${analysis.model.provider}/${analysis.model.model}`,\n `- Analysis tokens: ${auxiliary}`,\n `- Model output tokens: ${output}`,\n `- Evidence truncated: ${analysis.truncated ? 'yes' : 'no'}`,\n '',\n '## Deterministic Audit Summary',\n '',\n '| Control | Evidence |',\n '| --- | ---: |',\n ...approvalAuditRows,\n `| Orphan tool calls/results | ${metrics.orphanToolCalls}/${metrics.orphanToolResults} |`,\n `| Open turns/steps | ${metrics.openTurns}/${metrics.openSteps} |`,\n `| Accounting reconciliation | ${metrics.reconciliation.status} |`,\n '',\n '> This is a metadata-based technical-control review, not legal advice or compliance certification.',\n '',\n '## Model Report',\n '',\n safeModelMarkdown(analysis.report),\n '',\n ].join('\\n')\n}\n\n/** Save text content through browser-native download primitives. */\nexport const browserDownload: DownloadPort = {\n save(name, mime, content) {\n const url = URL.createObjectURL(new Blob([content], { type: mime }))\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = name\n anchor.click()\n URL.revokeObjectURL(url)\n },\n}\n","import { useMemo, type ReactNode } from 'react'\nimport { MarkdownText } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { safeModelMarkdown } from './report-safety.ts'\n\n/** Render untrusted model Markdown semantically without activating remote images or raw HTML. */\nexport function SafeMarkdownReport({\n report,\n className,\n copyLabel,\n copiedLabel,\n}: {\n report: string\n className?: string | undefined\n copyLabel: string\n copiedLabel: string\n}): ReactNode {\n const safeReport = useMemo(() => safeModelMarkdown(report), [report])\n const codeLabels = useMemo(() => ({ copyLabel, copiedLabel }), [copiedLabel, copyLabel])\n return
\n}\n","import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\r\nimport type { ObservableSnapshot, SessionId, SessionSummary } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { TokenUsageProjection } from '@deepseek-ai/dsh-token-meter/client'\r\nimport type {\r\n DailyTokenUsageRecord,\r\n ModelTokenUsageRecord,\r\n PricedModelTokenUsageRecord,\r\n TokenUsageAnalysis,\r\n TokenUsageAnalysisInput,\r\n TokenUsageAnalysisModel,\r\n TokenUsageAnalysisModelSelection,\r\n TokenUsageAnalysisProgress,\r\n TokenUsageBuckets,\r\n TokenUsageRecorderProjection,\r\n TrajectoryAnalysis,\r\n} from '../types.ts'\r\nimport type { TokenUsageBudgetSnapshot } from './budget-controller.ts'\r\nimport { dailyAnomalyInsight, dailyContributors, periodInsight, runRateInsight } from './analytics.ts'\r\nimport { usageEfficiencyInsight } from './efficiency.ts'\r\nimport {\r\n analysisReportFilename,\r\n dailyUsageCsv,\r\n modelUsageCsv,\r\n tokenUsageAnalysisMarkdown,\r\n tokenUsageJson,\r\n trajectoryAnalysisMarkdown,\r\n type DownloadPort,\r\n} from './export.ts'\r\nimport { PUBLIC_PRICE_CATALOG_AS_OF, PUBLIC_PRICE_CATALOG_URL, tokenUsageCostSummary } from '../pricing.ts'\r\nimport type { TokenUsageAnalysisModelCatalog } from './usage-analysis-client.ts'\r\nimport { NS } from './locales.ts'\r\nimport { SafeMarkdownReport } from './SafeMarkdownReport.tsx'\r\nimport css from './TokenUsageSection.module.css'\r\n\r\ninterface TokenUsageSectionInjected {\r\n hooks: {\r\n budget: ObservableSnapshot\r\n }\r\n setBudget(value: number): Promise\r\n download: DownloadPort\r\n saveTrajectoryAnalysis(analysis: TrajectoryAnalysis): void\r\n openSession(sessionId: SessionId): void\r\n listAnalysisModels(signal: AbortSignal): Promise\r\n analyzeTokenUsage(\r\n input: TokenUsageAnalysisInput,\r\n model: TokenUsageAnalysisModelSelection,\r\n signal: AbortSignal,\r\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\r\n ): Promise\r\n analyzeTrajectory(\r\n sessionId: string,\r\n model: TokenUsageAnalysisModelSelection,\r\n signal: AbortSignal,\r\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\r\n ): Promise\r\n}\r\n\r\n/** Full props assembled by the root-scoped Settings section renderer. */\r\nexport type TokenUsageSectionProps = PropsRuntime<'settings.section'>\r\n & PropsLocale\r\n & InjectFace\r\n\r\ninterface SessionUsageRow {\r\n id: SessionId\r\n title: string\r\n updatedAt: number\r\n assistantRequests: number\r\n compactionRequests: number\r\n compactionUsage: TokenUsageBuckets\r\n usage: TokenUsageBuckets\r\n models: readonly ModelTokenUsageRecord[]\r\n days: readonly DailyTokenUsageRecord[]\r\n dailyUsageReliable: boolean\r\n}\r\n\r\ninterface DashboardData {\r\n usage: TokenUsageBuckets\r\n assistantRequests: number\r\n compactionRequests: number\r\n compactionUsage: TokenUsageBuckets\r\n sessions: SessionUsageRow[]\r\n models: ModelTokenUsageRecord[]\r\n days: DailyTokenUsageRecord[]\r\n operationalDays: DailyTokenUsageRecord[]\r\n dailyCoverage: 'complete' | 'partial' | 'unavailable'\r\n}\r\n\r\ntype InsightRange = 7 | 30 | 90\r\ntype ModelSort = 'total' | 'cost' | 'tokensPerAttempt' | 'cacheReadShare'\r\n\r\nconst SESSION_PAGE_SIZE = 50\r\n\r\nexport type TrajectoryAnalysisState =\r\n | { status: 'idle' }\r\n | { status: 'loading'; sessionId: string; title: string; progress?: TokenUsageAnalysisProgress }\r\n | { status: 'ready'; title: string; value: TrajectoryAnalysis }\r\n | { status: 'error'; sessionId: string; title: string; message: string }\r\n\r\ntype AnalysisCatalogState =\r\n | { status: 'loading' }\r\n | { status: 'ready'; value: TokenUsageAnalysisModelCatalog }\r\n | { status: 'error'; message: string }\r\n\r\ntype TokenUsageAnalysisState =\r\n | { status: 'idle' }\r\n | { status: 'loading'; progress?: TokenUsageAnalysisProgress }\r\n | { status: 'ready'; value: TokenUsageAnalysis }\r\n | { status: 'error'; message: string }\r\n\r\n/** Detached zero buckets for dashboard folds. */\r\nfunction zeroBuckets(): TokenUsageBuckets {\r\n return {\r\n uncachedInputTokens: 0,\r\n outputTokens: 0,\r\n cacheReadTokens: 0,\r\n cacheWriteTokens: 0,\r\n }\r\n}\r\n\r\n/** Add four disjoint token buckets. */\r\nfunction addBuckets(left: TokenUsageBuckets, right: TokenUsageBuckets): TokenUsageBuckets {\r\n return {\r\n uncachedInputTokens: left.uncachedInputTokens + right.uncachedInputTokens,\r\n outputTokens: left.outputTokens + right.outputTokens,\r\n cacheReadTokens: left.cacheReadTokens + right.cacheReadTokens,\r\n cacheWriteTokens: left.cacheWriteTokens + right.cacheWriteTokens,\r\n }\r\n}\r\n\r\n/** Stable UTC day key used by durable Host records and legacy fallbacks. */\r\nfunction dayKey(time: number): string {\r\n return new Date(time).toISOString().slice(0, 10)\r\n}\r\n\r\n/** Prompt-side total across uncached input and cache traffic. */\r\nexport function inputTokens(usage: TokenUsageBuckets): number {\r\n return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens\r\n}\r\n\r\n/** Complete request/response total without double-counting reasoning output. */\r\nexport function totalTokens(usage: TokenUsageBuckets): number {\r\n return inputTokens(usage) + usage.outputTokens\r\n}\r\n\r\n/** Locale-aware exact integer formatting. */\r\nfunction formatTokens(value: number): string {\r\n return new Intl.NumberFormat().format(value)\r\n}\r\n\r\n/** Format one public-rate estimate in USD without implying accounting precision. */\r\nfunction formatUSD(value: number): string {\r\n return new Intl.NumberFormat(undefined, {\r\n style: 'currency',\r\n currency: 'USD',\r\n minimumFractionDigits: value < 1 ? 4 : 2,\r\n maximumFractionDigits: value < 1 ? 4 : 2,\r\n }).format(value)\r\n}\r\n\r\n/** Format a ratio without implying fractional measurement precision. */\r\nfunction formatPercent(value: number): string {\r\n return `${Math.round(value * 100)}%`\r\n}\r\n\r\n/** Format partial price coverage without ever rounding an incomplete estimate to 100%. */\r\nfunction formatCoveragePercent(covered: number, total: number): string {\r\n if (total <= 0 || covered <= 0) return '0'\r\n if (covered >= total) return '100'\r\n const percent = covered / total * 100\r\n if (percent < 0.1) return '<0.1'\r\n return new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(Math.floor(percent * 10) / 10)\r\n}\r\n\r\n/** Compact a token count with a stable K/M/B suffix for dense dashboard cells. */\r\nfunction formatCompactTokens(value: number): string {\r\n const unit = [\r\n { divisor: 1_000_000_000, suffix: 'B' },\r\n { divisor: 1_000_000, suffix: 'M' },\r\n { divisor: 1_000, suffix: 'K' },\r\n ].find(candidate => value >= candidate.divisor)\r\n if (unit === undefined) return formatTokens(value)\r\n return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(value / unit.divisor)}${unit.suffix}`\r\n}\r\n\r\n/** Format deterministic tool latency for one compact metric card. */\r\nfunction formatLatency(value: number): string {\r\n if (value < 1_000) return `${Math.round(value)}ms`\r\n return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(value / 1_000)}s`\r\n}\r\n\r\n/** Whether a dashboard-only row contains usage whose model route is unavailable. */\r\nfunction isUnattributed(model: ModelTokenUsageRecord): boolean {\r\n return model.provider === '' && model.model === ''\r\n}\r\n\r\n/** Stable provider/model identity for React lists and aggregation. */\r\nfunction modelKey(model: Pick): string {\r\n return JSON.stringify([model.provider, model.model])\r\n}\r\n\r\n/** Compact route label retained in the session table. */\r\nfunction routeLabel(model: ModelTokenUsageRecord): string {\r\n return `${model.provider}/${model.model}`\r\n}\r\n\r\n/** Count every provider-recorded assistant or compaction attempt for one route. */\r\nfunction recordedAttempts(model: ModelTokenUsageRecord): number {\r\n return model.assistantRequests + model.compactionRequests\r\n}\r\n\r\n/** Return the selected stable sort value for one model hotspot row. */\r\nfunction modelSortValue(model: PricedModelTokenUsageRecord, sort: ModelSort): number {\r\n switch (sort) {\r\n case 'total': return totalTokens(model.usage)\r\n case 'cost': return model.totalCostUSD ?? -1\r\n case 'tokensPerAttempt': {\r\n const attempts = recordedAttempts(model)\r\n return attempts === 0 ? -1 : totalTokens(model.usage) / attempts\r\n }\r\n case 'cacheReadShare': {\r\n const input = inputTokens(model.usage)\r\n return input === 0 ? -1 : model.usage.cacheReadTokens / input\r\n }\r\n }\r\n}\r\n\r\n/** Sort model hotspot rows deterministically without mutating cost-summary data. */\r\nfunction sortedModelHotspots(models: readonly PricedModelTokenUsageRecord[], sort: ModelSort): PricedModelTokenUsageRecord[] {\r\n return models.slice().sort((left, right) => modelSortValue(right, sort) - modelSortValue(left, sort)\r\n || totalTokens(right.usage) - totalTokens(left.usage)\r\n || left.provider.localeCompare(right.provider)\r\n || left.model.localeCompare(right.model))\r\n}\r\n\r\n/** Attribute a built-in projection fallback to an explicit dashboard remainder row. */\r\nfunction unattributedModel(usage: TokenUsageBuckets): ModelTokenUsageRecord {\r\n return {\r\n provider: '',\r\n model: '',\r\n assistantRequests: 0,\r\n compactionRequests: 0,\r\n usage: { ...usage },\r\n }\r\n}\r\n\r\n/** Built-in projection fallback for a cache created before this plugin was installed. */\r\nfunction fallbackUsage(value: TokenUsageProjection): TokenUsageBuckets {\r\n return {\r\n uncachedInputTokens: value.uncachedInputTokens,\r\n outputTokens: value.outputTokens,\r\n cacheReadTokens: value.cacheReadTokens,\r\n cacheWriteTokens: value.cacheWriteTokens,\r\n }\r\n}\r\n\r\n/** One session summary projected into a usage row, or null when it has no usage. */\r\nfunction sessionRow(summary: SessionSummary): SessionUsageRow | null {\r\n const recorded: TokenUsageRecorderProjection | undefined = summary.projectionValues?.tokenUsageRecorder\r\n const builtIn = summary.projectionValues?.tokenUsage\r\n const usage = recorded?.usage ?? (builtIn === undefined ? undefined : fallbackUsage(builtIn))\r\n const assistantRequests = recorded?.assistantRequests ?? 0\r\n const compactionRequests = recorded?.compactionRequests ?? 0\r\n if (usage === undefined || (totalTokens(usage) === 0 && assistantRequests === 0 && compactionRequests === 0)) return null\r\n return {\r\n id: summary.id,\r\n title: summary.displayTitle,\r\n updatedAt: summary.updatedAt,\r\n assistantRequests,\r\n compactionRequests,\r\n compactionUsage: recorded?.compactionUsage === undefined ? zeroBuckets() : { ...recorded.compactionUsage },\r\n usage,\r\n models: recorded?.models ?? [unattributedModel(usage)],\r\n days: recorded?.days ?? [{ date: dayKey(summary.updatedAt), usage }],\r\n dailyUsageReliable: recorded?.days !== undefined,\r\n }\r\n}\r\n\r\n/** Aggregate session summaries into totals and provider/model records. */\r\nexport function aggregateUsage(summaries: readonly SessionSummary[]): DashboardData {\r\n const sessions: SessionUsageRow[] = []\r\n const models = new Map()\r\n const days = new Map()\r\n const operationalDays = new Map()\r\n let usage = zeroBuckets()\r\n let assistantRequests = 0\r\n let compactionRequests = 0\r\n let compactionUsage = zeroBuckets()\r\n let reliableDailySessions = 0\r\n\r\n for (const summary of summaries) {\r\n const row = sessionRow(summary)\r\n if (row === null) continue\r\n sessions.push(row)\r\n usage = addBuckets(usage, row.usage)\r\n assistantRequests += row.assistantRequests\r\n compactionRequests += row.compactionRequests\r\n compactionUsage = addBuckets(compactionUsage, row.compactionUsage)\r\n if (row.dailyUsageReliable) reliableDailySessions += 1\r\n for (const day of row.days) {\r\n days.set(day.date, addBuckets(days.get(day.date) ?? zeroBuckets(), day.usage))\r\n if (row.dailyUsageReliable) {\r\n operationalDays.set(day.date, addBuckets(operationalDays.get(day.date) ?? zeroBuckets(), day.usage))\r\n }\r\n }\r\n for (const model of row.models) {\r\n const key = modelKey(model)\r\n const current = models.get(key)\r\n models.set(key, current === undefined ? {\r\n ...model,\r\n usage: { ...model.usage },\r\n } : {\r\n ...current,\r\n assistantRequests: current.assistantRequests + model.assistantRequests,\r\n compactionRequests: current.compactionRequests + model.compactionRequests,\r\n usage: addBuckets(current.usage, model.usage),\r\n })\r\n }\r\n }\r\n\r\n sessions.sort((left, right) => right.updatedAt - left.updatedAt)\r\n return {\r\n usage,\r\n assistantRequests,\r\n compactionRequests,\r\n compactionUsage,\r\n sessions,\r\n models: [...models.values()].sort((left, right) =>\r\n totalTokens(right.usage) - totalTokens(left.usage)\r\n || left.provider.localeCompare(right.provider)\r\n || left.model.localeCompare(right.model)),\r\n days: [...days.entries()]\r\n .map(([date, usage]): DailyTokenUsageRecord => ({ date, usage }))\r\n .sort((left, right) => left.date.localeCompare(right.date)),\r\n operationalDays: [...operationalDays.entries()]\r\n .map(([date, usage]): DailyTokenUsageRecord => ({ date, usage }))\r\n .sort((left, right) => left.date.localeCompare(right.date)),\r\n dailyCoverage: reliableDailySessions === 0\r\n ? 'unavailable'\r\n : reliableDailySessions === sessions.length ? 'complete' : 'partial',\r\n }\r\n}\r\n\r\n/** Return only detached aggregate buckets, route records, and UTC dates for AI usage analysis. */\r\nexport function usageAnalysisInput(data: Pick): TokenUsageAnalysisInput {\r\n return {\r\n usage: { ...data.usage },\r\n assistantRequests: data.assistantRequests,\r\n compactionRequests: data.compactionRequests,\r\n compactionUsage: { ...data.compactionUsage },\r\n models: data.models.map(model => ({\r\n provider: model.provider,\r\n model: model.model,\r\n assistantRequests: model.assistantRequests,\r\n compactionRequests: model.compactionRequests,\r\n usage: { ...model.usage },\r\n })),\r\n days: data.dailyCoverage === 'complete'\r\n ? data.operationalDays.map(day => ({ date: day.date, usage: { ...day.usage } }))\r\n : [],\r\n }\r\n}\r\n\r\n/** Render a summary metric card with exact token counts available on hover. */\r\nfunction Metric({ label, value }: { label: string; value: number | string }): ReactNode {\r\n const display = typeof value === 'number' ? formatCompactTokens(value) : value\r\n const exact = typeof value === 'number' ? formatTokens(value) : undefined\r\n return (\r\n
\r\n {label}\r\n {display}\r\n
\r\n )\r\n}\r\n\r\n/** Group related trajectory facts without promoting every number to a large card. */\r\nfunction AnalysisSummaryGroup({ title, rows }: {\r\n title: string\r\n rows: readonly { label: string; value: string; title?: string | undefined }[]\r\n}): ReactNode {\r\n return
\r\n

{title}

\r\n
{rows.map(row =>
\r\n
{row.label}
\r\n
{row.value}
\r\n
)}
\r\n
\r\n}\r\n\r\n/** Render a compact table count with an exact-count tooltip. */\r\nfunction TokenValue({ value }: { value: number }): ReactNode {\r\n return {formatCompactTokens(value)}\r\n}\r\n\r\ninterface ActivityDay {\r\n date: string\r\n usage: TokenUsageBuckets\r\n tokens: number\r\n level: 0 | 1 | 2 | 3 | 4\r\n future: boolean\r\n}\r\n\r\n/** Build exactly 30 Monday-first calendar weeks, including blank future days this week. */\r\nfunction activityCalendar(days: readonly DailyTokenUsageRecord[], now = Date.now()): ActivityDay[] {\r\n const byDate = new Map(days.map(day => [day.date, day.usage]))\r\n const today = dayKey(now)\r\n const end = new Date(`${today}T00:00:00.000Z`)\r\n const start = new Date(end)\r\n start.setUTCDate(start.getUTCDate() - ((start.getUTCDay() + 6) % 7) - 29 * 7)\r\n\r\n const dates: string[] = []\r\n for (const cursor = new Date(start); dates.length < 30 * 7; cursor.setUTCDate(cursor.getUTCDate() + 1)) {\r\n dates.push(dayKey(cursor.getTime()))\r\n }\r\n const maximum = Math.max(0, ...dates\r\n .filter(date => date <= today)\r\n .map(date => totalTokens(byDate.get(date) ?? zeroBuckets())))\r\n return dates.map((date) => {\r\n const future = date > today\r\n const usage = byDate.get(date) ?? zeroBuckets()\r\n const tokens = future ? 0 : totalTokens(usage)\r\n const level = tokens === 0 || maximum === 0 ? 0 : Math.ceil(tokens / maximum * 4) as ActivityDay['level']\r\n return { date, usage, tokens, level, future }\r\n })\r\n}\r\n\r\n/** Render a GitHub-style calendar heatmap of daily Token activity. */\r\nfunction ActivityHeatmap({\r\n days,\r\n selectedDate,\r\n onSelectDate,\r\n t,\r\n}: {\r\n days: readonly DailyTokenUsageRecord[]\r\n selectedDate: string | undefined\r\n onSelectDate(date: string): void\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const calendar = useMemo(() => activityCalendar(days), [days])\r\n return (\r\n
\r\n
\r\n
\r\n

{t('activity')}

\r\n

{t('activityIntro')}

\r\n
\r\n
\r\n {t('less')}\r\n {[0, 1, 2, 3, 4].map(level => )}\r\n {t('more')}\r\n
\r\n
\r\n
\r\n {calendar.map((day) => {\r\n const details = day.future ? undefined : t('activityTooltip', {\r\n date: day.date,\r\n total: formatTokens(day.tokens),\r\n input: formatTokens(inputTokens(day.usage)),\r\n output: formatTokens(day.usage.outputTokens),\r\n cacheRead: formatTokens(day.usage.cacheReadTokens),\r\n cacheWrite: formatTokens(day.usage.cacheWriteTokens),\r\n })\r\n return (\r\n { onSelectDate(day.date) }}\r\n />\r\n )\r\n })}\r\n
\r\n
\r\n )\r\n}\r\n\r\n/** Render a selected day's exact totals and contributing sessions. */\r\nfunction DayDrilldown({\r\n day,\r\n sessions,\r\n t,\r\n onClose,\r\n}: {\r\n day: DailyTokenUsageRecord\r\n sessions: readonly SessionUsageRow[]\r\n t: TokenUsageSectionProps['t']\r\n onClose(): void\r\n}): ReactNode {\r\n const contributors = useMemo(() => dailyContributors(sessions, day.date), [day.date, sessions])\r\n return (\r\n
\r\n
\r\n
\r\n

{t('dayDetails', { date: day.date })}

\r\n

{t('dayDetailsIntro')}

\r\n
\r\n \r\n
\r\n
\r\n \r\n \r\n \r\n \r\n
\r\n
\r\n {t('contributors', { count: contributors.length })}\r\n {contributors.length === 0 ?

{t('noContributors')}

: (\r\n
    \r\n {contributors.slice(0, 5).map(contributor => (\r\n
  1. \r\n {contributor.title}\r\n \r\n
  2. \r\n ))}\r\n
\r\n )}\r\n
\r\n
\r\n )\r\n}\r\n\r\n/** Render period-aware trend and activity summaries from daily records. */\r\nfunction PeriodInsights({\r\n days,\r\n range,\r\n onRangeChange,\r\n t,\r\n}: {\r\n days: readonly DailyTokenUsageRecord[]\r\n range: InsightRange\r\n onRangeChange(range: InsightRange): void\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const insight = useMemo(() => periodInsight(days, range), [days, range])\r\n const current = totalTokens(insight.usage)\r\n const previous = totalTokens(insight.previousUsage)\r\n const delta = previous === 0 ? undefined : Math.round((current - previous) / previous * 100)\r\n const peak = insight.peak\r\n return (\r\n
\r\n
\r\n
\r\n

{t('trend')}

\r\n

{t('trendIntro')}

\r\n
\r\n
\r\n {([7, 30, 90] as const).map(value => (\r\n { onRangeChange(value) }}\r\n >{t('rangeDays', { count: value })}\r\n ))}\r\n
\r\n
\r\n
\r\n \r\n 0 ? '+' : ''}${delta}%`} />\r\n \r\n \r\n
\r\n {peak === undefined ? null :

{t('peakDayNote', { date: peak.date, total: formatTokens(totalTokens(peak.usage)) })}

}\r\n
\r\n )\r\n}\r\n\r\n/** Render the persistent trailing-30-day budget setting and progress. */\r\nfunction BudgetPanel({\r\n operationalDays,\r\n dailyCoverage,\r\n snapshot,\r\n setBudget,\r\n t,\r\n}: {\r\n operationalDays: readonly DailyTokenUsageRecord[]\r\n dailyCoverage: DashboardData['dailyCoverage']\r\n snapshot: TokenUsageBudgetSnapshot\r\n setBudget(value: number): Promise\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const insight = useMemo(() => periodInsight(operationalDays, 30), [operationalDays])\r\n const runRate = useMemo(() => runRateInsight(operationalDays), [operationalDays])\r\n const runRateAvailable = dailyCoverage === 'complete'\r\n const used = totalTokens(insight.usage)\r\n const budget = snapshot.budget\r\n const enabled = budget > 0\r\n const durableValue = enabled ? String(budget) : ''\r\n const [draft, setDraft] = useState(durableValue)\r\n const editGeneration = useRef(0)\r\n const dirtyDraft = useRef(false)\r\n const ratio = enabled ? used / budget : 0\r\n useEffect(() => {\r\n if (!dirtyDraft.current) setDraft(durableValue)\r\n }, [durableValue, snapshot.status])\r\n const save = (value: string): void => {\r\n const next = value.trim() === '' ? 0 : Number(value)\r\n if (!Number.isSafeInteger(next) || next < 0) {\r\n dirtyDraft.current = false\r\n setDraft(durableValue)\r\n return\r\n }\r\n const generation = editGeneration.current + 1\r\n editGeneration.current = generation\r\n void setBudget(next).then(\r\n (saved) => {\r\n if (editGeneration.current !== generation) return\r\n dirtyDraft.current = false\r\n setDraft(saved > 0 ? String(saved) : '')\r\n },\r\n () => {\r\n if (editGeneration.current !== generation) return\r\n dirtyDraft.current = false\r\n setDraft(durableValue)\r\n },\r\n )\r\n }\r\n return (\r\n
\r\n
\r\n
\r\n

{t('budget')}

\r\n

{t('budgetIntro')}

\r\n
\r\n \r\n
\r\n

{runRateAvailable ? t('budgetRunRate', {\r\n average: formatCompactTokens(Math.round(runRate.averageDailyTokens)),\r\n projected: formatCompactTokens(runRate.projectedThirtyDayTokens),\r\n }) : t(dailyCoverage === 'partial' ? 'dailyCoveragePartial' : 'dailyCoverageUnavailable')}

\r\n {snapshot.status !== 'ready' ?

{t('budgetUnavailable')}

\r\n : !enabled ?

{t('budgetDisabled')}

\r\n : !runRateAvailable ? null\r\n : <>\r\n
\r\n \r\n {t('budgetProgress', { used: formatCompactTokens(used), budget: formatCompactTokens(budget), percent: Math.round(ratio * 100) })}\r\n
\r\n {ratio > 1 ?

{t('budgetExceeded', { excess: formatCompactTokens(used - budget) })}

: null}\r\n {runRateAvailable && ratio <= 1 && runRate.projectedThirtyDayTokens > budget ?

{t('budgetForecastExceeded', {\r\n projected: formatCompactTokens(runRate.projectedThirtyDayTokens),\r\n budget: formatCompactTokens(budget),\r\n })}

: null}\r\n }\r\n
\r\n )\r\n}\r\n\r\n/** Render aggregate request efficiency, compaction overhead, and route concentration. */\r\nfunction EfficiencyPanel({\r\n usage,\r\n compactionUsage,\r\n models,\r\n assistantAttempts,\r\n compactionAttempts,\r\n t,\r\n}: {\r\n usage: TokenUsageBuckets\r\n compactionUsage: TokenUsageBuckets\r\n models: readonly ModelTokenUsageRecord[]\r\n assistantAttempts: number\r\n compactionAttempts: number\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const insight = useMemo(\r\n () => usageEfficiencyInsight(usage, compactionUsage, models, assistantAttempts, compactionAttempts),\r\n [usage, compactionUsage, models, assistantAttempts, compactionAttempts],\r\n )\r\n const top = insight.topRoutes[0]\r\n const topThreeShare = insight.topRoutes.reduce((sum, route) => sum + route.share, 0)\r\n return (\r\n
\r\n
\r\n
\r\n

{t('efficiency')}

\r\n

{t('efficiencyIntro')}

\r\n
\r\n
\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n {top === undefined ?

{t('noRouteAttribution')}

\r\n :

{t('routeConcentration', {\r\n route: `${top.provider}/${top.model}`,\r\n topOne: formatPercent(top.share),\r\n topThree: formatPercent(topThreeShare),\r\n })}

}\r\n {insight.unattributedTokenShare > 0 ?

{t('unattributedShare', { share: formatPercent(insight.unattributedTokenShare) })}

: null}\r\n
\r\n )\r\n}\r\n\r\n/** Render complete-day burn rate and robust recent spike signals. */\r\nfunction OperationsPanel({\r\n days,\r\n dailyCoverage,\r\n onSelectDate,\r\n t,\r\n}: {\r\n days: readonly DailyTokenUsageRecord[]\r\n dailyCoverage: DashboardData['dailyCoverage']\r\n onSelectDate(date: string): void\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const runRate = useMemo(() => runRateInsight(days), [days])\r\n const anomaly = useMemo(\r\n () => dailyCoverage === 'complete' ? dailyAnomalyInsight(days) : undefined,\r\n [days, dailyCoverage],\r\n )\r\n const dailyCoverageAvailable = dailyCoverage === 'complete'\r\n return (\r\n
\r\n
\r\n
\r\n

{t('usageSignals')}

\r\n

{t('usageSignalsIntro')}

\r\n
\r\n
\r\n
\r\n \r\n \r\n \r\n \r\n
\r\n {!dailyCoverageAvailable ?

{t(dailyCoverage === 'partial' ? 'dailyCoveragePartial' : 'dailyCoverageUnavailable')}

\r\n : anomaly === undefined ?

{t('anomalyInsufficient')}

\r\n : anomaly.status === 'normal' ?

{t('anomalyNormal', {\r\n date: anomaly.date,\r\n baseline: formatCompactTokens(anomaly.baselineMedianTokens),\r\n active: anomaly.activeBaselineDays,\r\n })}

\r\n :
\r\n

{t('anomalyElevated', {\r\n date: anomaly.date,\r\n tokens: formatCompactTokens(anomaly.tokens),\r\n baseline: formatCompactTokens(anomaly.baselineMedianTokens),\r\n ratio: new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(anomaly.ratio),\r\n excess: formatCompactTokens(anomaly.excessTokens),\r\n active: anomaly.activeBaselineDays,\r\n })}

\r\n \r\n
}\r\n
\r\n )\r\n}\r\n\r\n/** Render export controls that only receive aggregate, privacy-safe dashboard data. */\r\nfunction ExportControls({\r\n data,\r\n download,\r\n t,\r\n}: {\r\n data: DashboardData\r\n download: DownloadPort\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const save = (kind: 'json' | 'daily' | 'models'): void => {\r\n const generatedAt = new Date().toISOString()\r\n const date = generatedAt.slice(0, 10)\r\n switch (kind) {\r\n case 'json':\r\n download.save(`dsh-token-usage-${date}.json`, 'application/json;charset=utf-8', tokenUsageJson(data, generatedAt))\r\n return\r\n case 'daily':\r\n download.save(`dsh-token-usage-daily-${date}.csv`, 'text/csv;charset=utf-8', dailyUsageCsv(data))\r\n return\r\n case 'models':\r\n download.save(`dsh-token-usage-models-${date}.csv`, 'text/csv;charset=utf-8', modelUsageCsv(data))\r\n }\r\n }\r\n return (\r\n
\r\n {t('export')}\r\n \r\n \r\n \r\n
\r\n )\r\n}\r\n\r\n/** Encode one provider/model route for the native selector without displaying an opaque id. */\r\nfunction analysisModelKey(model: TokenUsageAnalysisModelSelection): string {\r\n return `${model.provider}\\u0000${model.model}`\r\n}\r\n\r\n/** Render one accessible indeterminate/stream-aware analysis progress surface. */\r\nfunction AnalysisLoading({\r\n title,\r\n message,\r\n progress,\r\n t,\r\n}: {\r\n title: string\r\n message: string\r\n progress?: TokenUsageAnalysisProgress | undefined\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const stage = progress === undefined\r\n ? t('analysisProgressPreparing')\r\n : t(progress.phase === 'preparing'\r\n ? 'analysisProgressPreparing'\r\n : progress.phase === 'generating' ? 'analysisProgressGenerating' : 'analysisProgressFinalizing')\r\n const output = progress === undefined || progress.maximumOutputTokens === 0\r\n ? t('analysisProgressWaiting')\r\n : progress.exactOutputTokens === undefined\r\n ? t('analysisProgressEstimated', {\r\n count: formatTokens(progress.estimatedOutputTokens),\r\n maximum: formatTokens(progress.maximumOutputTokens),\r\n })\r\n : t('analysisProgressExact', {\r\n count: formatTokens(progress.exactOutputTokens),\r\n maximum: formatTokens(progress.maximumOutputTokens),\r\n })\r\n const value = progress?.exactOutputTokens ?? progress?.estimatedOutputTokens\r\n const maximum = progress?.maximumOutputTokens ?? 0\r\n return
\r\n
\r\n
\r\n

{title}

\r\n

{message}

\r\n
\r\n {stage}\r\n {output}\r\n {progress === undefined ? null : <>\r\n {t('analysisProgressActivity', {\r\n chunks: formatTokens(progress.chunks),\r\n characters: formatTokens(progress.outputCharacters),\r\n })}\r\n {t('analysisProgressElapsed', { seconds: Math.max(1, Math.round(progress.elapsedMs / 1_000)) })}\r\n }\r\n
\r\n {maximum > 0 && value !== undefined\r\n ? \r\n : }\r\n
\r\n
\r\n}\r\n\r\n/** Render a manual integrated-model picker and one aggregate-only Token optimization report. */\r\nfunction UsageAnalysisPanel({\r\n catalog,\r\n selectedModel,\r\n state,\r\n onSelectModel,\r\n onRefreshCatalog,\r\n onAnalyze,\r\n download,\r\n t,\r\n}: {\r\n catalog: AnalysisCatalogState\r\n selectedModel: TokenUsageAnalysisModelSelection | undefined\r\n state: TokenUsageAnalysisState\r\n onSelectModel(model: TokenUsageAnalysisModelSelection): void\r\n onRefreshCatalog(): void\r\n onAnalyze(): void\r\n download: DownloadPort\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n if (catalog.status === 'loading') {\r\n return

{t('usageAnalysis')}

{t('analysisModelsLoading')}

\r\n }\r\n if (catalog.status === 'error') {\r\n return

{t('usageAnalysis')}

{t('analysisModelsFailed', { message: catalog.message })}

\r\n }\r\n const catalogFailures = catalog.value.failures ?? []\r\n if (catalog.value.models.length === 0 || selectedModel === undefined) {\r\n return
\r\n

{t('usageAnalysis')}

\r\n

{t('analysisModelsUnavailable')}

\r\n {catalogFailures.length === 0 ? null :

{t('analysisModelsAllFailed', {\r\n providers: catalogFailures.map(failure => failure.providerName).join(', '),\r\n })}

}\r\n \r\n
\r\n }\r\n const report = state.status === 'ready' ? state.value : undefined\r\n const analysisUsage = report?.analysisUsage\r\n return (\r\n
\r\n
\r\n
\r\n

{t('usageAnalysis')}

\r\n

{t('usageAnalysisIntro')}

\r\n
\r\n \r\n
\r\n \r\n

{t('usageAnalysisPrivacy')}

\r\n

{t('analysisModelScope')}

\r\n {catalogFailures.length === 0 ? null :

{t('analysisModelsPartial', {\r\n providers: catalogFailures.map(failure => failure.providerName).join(', '),\r\n })}

}\r\n {state.status === 'loading' ? t('usageAnalyzing') : t('analyzeUsage')}\r\n {state.status === 'loading' ? : null}\r\n {state.status === 'error' ?

{t('usageAnalysisFailed', { message: state.message })}

: null}\r\n {report === undefined ? null : <>\r\n
\r\n
\r\n

{t('usageAnalysisReport')}

\r\n

{t('analysisMeta', {\r\n provider: report.model.provider,\r\n model: report.model.model,\r\n time: new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(report.generatedAt)),\r\n })}

\r\n
\r\n
\r\n {analysisUsage === undefined ? null : {t('analysisCostDetailed', {\r\n total: formatTokens(totalTokens(analysisUsage)),\r\n output: formatTokens(analysisUsage.outputTokens),\r\n })}}\r\n \r\n
\r\n
\r\n \r\n }\r\n
\r\n )\r\n}\r\n\r\n/** Render one ephemeral model-generated review and its deterministic measurements. */\r\nexport function TrajectoryAnalysisPanel({ state, download, t }: {\r\n state: TrajectoryAnalysisState\r\n download: DownloadPort\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n if (state.status === 'idle') {\r\n return

{t('trajectoryAnalysis')}

{t('trajectoryAnalysisIntro')}

\r\n }\r\n if (state.status === 'loading') {\r\n return \r\n }\r\n if (state.status === 'error') {\r\n return

{t('trajectoryAnalysis')}

{t('analysisFailed', { message: state.message })}

\r\n }\r\n const analysis = state.value\r\n const metrics = analysis.metrics\r\n const analysisUsage = analysis.analysisUsage\r\n const largestSpan = metrics.largestSpanId === undefined\r\n ? undefined\r\n : metrics.spans.find(span => span.id === metrics.largestSpanId)\r\n const reconciliationDelta = Object.values(metrics.reconciliation.delta)\r\n .reduce((total, value) => total + Math.abs(value), 0)\r\n const deniedApprovals = metrics.approvalsRejected + metrics.approvalsCancelled + metrics.approvalsUnavailable\r\n const approvalGaps = metrics.unresolvedApprovals + metrics.orphanApprovalDecisions\r\n return (\r\n
\r\n
\r\n
\r\n

{t('analysisFor', { title: state.title })}

\r\n

{t('analysisMeta', {\r\n provider: analysis.model.provider,\r\n model: analysis.model.model,\r\n time: new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(analysis.generatedAt)),\r\n })}

\r\n
\r\n
\r\n {analysisUsage === undefined ? null : {t('analysisCostDetailed', {\r\n total: formatTokens(totalTokens(analysisUsage)),\r\n output: formatTokens(analysisUsage.outputTokens),\r\n })}}\r\n \r\n
\r\n
\r\n
\r\n \r\n \r\n \r\n \r\n
\r\n {analysis.truncated ?

{t('analysisTruncated')}

: null}\r\n \r\n

{t('analysisPrivacy')}

\r\n
\r\n )\r\n}\r\n\r\n/** Render durable Token usage across all listed sessions. */\r\nexport function TokenUsageSection({\r\n close,\r\n useSessions,\r\n useBudget,\r\n setBudget,\r\n download,\r\n saveTrajectoryAnalysis,\r\n openSession,\r\n listAnalysisModels,\r\n analyzeTokenUsage,\r\n analyzeTrajectory,\r\n t,\r\n}: TokenUsageSectionProps): ReactNode {\r\n const phase = useSessions(state => state.phase)\r\n const ids = useSessions(state => state.ids)\r\n const byId = useSessions(state => state.byId)\r\n const budget = useBudget(snapshot => snapshot)\r\n const [query, setQuery] = useState('')\r\n const [modelSort, setModelSort] = useState('total')\r\n const [sessionLimit, setSessionLimit] = useState(SESSION_PAGE_SIZE)\r\n const [range, setRange] = useState(30)\r\n const [selectedDate, setSelectedDate] = useState()\r\n const [operationalDrilldown, setOperationalDrilldown] = useState(false)\r\n const [sessionOpenError, setSessionOpenError] = useState()\r\n const [analysis, setAnalysis] = useState({ status: 'idle' })\r\n const [analysisCatalog, setAnalysisCatalog] = useState({ status: 'loading' })\r\n const [selectedAnalysisModel, setSelectedAnalysisModel] = useState()\r\n const [usageReport, setUsageReport] = useState({ status: 'idle' })\r\n const trajectoryController = useRef()\r\n const usageController = useRef()\r\n const catalogController = useRef()\r\n useEffect(() => () => {\r\n trajectoryController.current?.abort()\r\n usageController.current?.abort()\r\n catalogController.current?.abort()\r\n }, [])\r\n const refreshAnalysisModels = useCallback((): void => {\r\n catalogController.current?.abort()\r\n const controller = new AbortController()\r\n catalogController.current = controller\r\n setAnalysisCatalog({ status: 'loading' })\r\n void listAnalysisModels(controller.signal).then((catalog) => {\r\n if (catalogController.current !== controller || controller.signal.aborted) return\r\n setAnalysisCatalog({ status: 'ready', value: catalog })\r\n setSelectedAnalysisModel(current => current !== undefined\r\n && catalog.models.some(model => model.provider === current.provider && model.model === current.model)\r\n ? current\r\n : catalog.default ?? catalog.models[0])\r\n }, (error: unknown) => {\r\n if (catalogController.current === controller && !controller.signal.aborted) {\r\n setAnalysisCatalog({ status: 'error', message: error instanceof Error ? error.message : String(error) })\r\n }\r\n })\r\n }, [listAnalysisModels])\r\n useEffect(() => {\r\n refreshAnalysisModels()\r\n }, [refreshAnalysisModels])\r\n\r\n const runAnalysis = (row: SessionUsageRow): void => {\r\n if (selectedAnalysisModel === undefined) return\r\n trajectoryController.current?.abort()\r\n const controller = new AbortController()\r\n trajectoryController.current = controller\r\n setAnalysis({ status: 'loading', sessionId: row.id, title: row.title })\r\n void analyzeTrajectory(row.id, selectedAnalysisModel, controller.signal, (progress) => {\r\n if (trajectoryController.current !== controller || controller.signal.aborted) return\r\n setAnalysis(current => current.status === 'loading' && current.sessionId === row.id\r\n ? { ...current, progress }\r\n : current)\r\n }).then((value) => {\r\n if (trajectoryController.current === controller && !controller.signal.aborted) {\r\n saveTrajectoryAnalysis(value)\r\n setAnalysis({ status: 'ready', title: row.title, value })\r\n }\r\n }, (error: unknown) => {\r\n if (trajectoryController.current === controller && !controller.signal.aborted) {\r\n setAnalysis({\r\n status: 'error',\r\n sessionId: row.id,\r\n title: row.title,\r\n message: error instanceof Error ? error.message : String(error),\r\n })\r\n }\r\n })\r\n }\r\n\r\n const openUsageSession = (row: SessionUsageRow): void => {\r\n try {\r\n openSession(row.id)\r\n close()\r\n } catch (error) {\r\n setSessionOpenError(error instanceof Error ? error.message : String(error))\r\n }\r\n }\r\n\r\n const data = useMemo(\r\n () => aggregateUsage(ids.map(id => byId[id]).filter((value): value is SessionSummary => value !== undefined)),\r\n [byId, ids],\r\n )\r\n const runUsageAnalysis = (): void => {\r\n if (selectedAnalysisModel === undefined) return\r\n usageController.current?.abort()\r\n const controller = new AbortController()\r\n usageController.current = controller\r\n setUsageReport({ status: 'loading' })\r\n void analyzeTokenUsage(usageAnalysisInput(data), selectedAnalysisModel, controller.signal, (progress) => {\r\n if (usageController.current !== controller || controller.signal.aborted) return\r\n setUsageReport(current => current.status === 'loading' ? { ...current, progress } : current)\r\n }).then((value) => {\r\n if (usageController.current === controller && !controller.signal.aborted) {\r\n setUsageReport({ status: 'ready', value })\r\n }\r\n }, (error: unknown) => {\r\n if (usageController.current === controller && !controller.signal.aborted) {\r\n setUsageReport({ status: 'error', message: error instanceof Error ? error.message : String(error) })\r\n }\r\n })\r\n }\r\n\r\n const costSummary = useMemo(() => tokenUsageCostSummary(data.models), [data.models])\r\n const priceCoverage = formatCoveragePercent(costSummary.coveredTokens, costSummary.totalTokens)\r\n const sortedModels = useMemo(\r\n () => sortedModelHotspots(costSummary.models, modelSort),\r\n [costSummary.models, modelSort],\r\n )\r\n\r\n const normalizedQuery = query.trim().toLocaleLowerCase()\r\n const filteredSessions = useMemo(() => data.sessions.filter(row => {\r\n if (normalizedQuery.length === 0) return true\r\n return row.title.toLocaleLowerCase().includes(normalizedQuery)\r\n || row.id.toLocaleLowerCase().includes(normalizedQuery)\r\n || row.models.some(model => routeLabel(model).toLocaleLowerCase().includes(normalizedQuery))\r\n }), [data.sessions, normalizedQuery])\r\n const visibleSessions = useMemo(\r\n () => filteredSessions.slice(0, sessionLimit),\r\n [filteredSessions, sessionLimit],\r\n )\r\n const selectedDay = useMemo(\r\n () => selectedDate === undefined\r\n ? undefined\r\n : (operationalDrilldown ? data.operationalDays : data.days).find(day => day.date === selectedDate),\r\n [data.days, data.operationalDays, operationalDrilldown, selectedDate],\r\n )\r\n const selectedDaySessions = useMemo(\r\n () => operationalDrilldown ? data.sessions.filter(row => row.dailyUsageReliable) : data.sessions,\r\n [data.sessions, operationalDrilldown],\r\n )\r\n const billedInput = inputTokens(data.usage)\r\n const cacheHit = billedInput === 0 ? '—' : `${Math.round(data.usage.cacheReadTokens / billedInput * 100)}%`\r\n\r\n if (phase !== 'ready' && ids.length === 0) {\r\n return

{t('loading')}

\r\n }\r\n\r\n return (\r\n
\r\n
\r\n
\r\n

{t('title')}

\r\n

{t('intro')}

\r\n
\r\n \r\n
\r\n\r\n {data.sessions.length === 0 ?

{t('empty')}

: (\r\n <>\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n\r\n {\r\n setOperationalDrilldown(false)\r\n setSelectedDate(date)\r\n }} t={t} />\r\n {selectedDay === undefined ? null : {\r\n setSelectedDate(undefined)\r\n setOperationalDrilldown(false)\r\n }} />}\r\n \r\n \r\n {\r\n setOperationalDrilldown(true)\r\n setSelectedDate(date)\r\n }} t={t} />\r\n \r\n
\r\n {t('pricingTitle')}\r\n

{t('pricingIntro', {\r\n asOf: PUBLIC_PRICE_CATALOG_AS_OF,\r\n covered: formatTokens(costSummary.coveredTokens),\r\n total: formatTokens(costSummary.totalTokens),\r\n routes: costSummary.coveredModels,\r\n allRoutes: costSummary.totalModels,\r\n })}

\r\n {t('pricingSource')}\r\n
\r\n \r\n
\r\n
\r\n

{t('modelBreakdown')}

\r\n \r\n
\r\n {data.models.length === 0 ?

{t('unknownRoute')}

: (\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {sortedModels.map(model => (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n ))}\r\n \r\n
{t('providerModel')}{t('calls')}{t('total')}{t('input')}{t('output')}{t('estimatedCost')}
{isUnattributed(model)\r\n ? {t('unattributed')}\r\n : <>{model.model}{model.provider}}{isUnattributed(model) ? '—' : (\r\n <>\r\n {t('assistantCalls', { count: model.assistantRequests })}\r\n {model.compactionRequests > 0\r\n ? {t('compactionCalls', { count: model.compactionRequests })}\r\n : null}\r\n \r\n )}\r\n \r\n {(model.usage.cacheReadTokens > 0 || model.usage.cacheWriteTokens > 0) ? (\r\n {t('cacheDetail', {\r\n read: formatCompactTokens(model.usage.cacheReadTokens),\r\n write: formatCompactTokens(model.usage.cacheWriteTokens),\r\n })}\r\n ) : null}\r\n {model.totalCostUSD === undefined || model.rate === undefined\r\n ? \r\n : {formatUSD(model.totalCostUSD)}}
\r\n
\r\n )}\r\n
\r\n\r\n \r\n\r\n
\r\n
\r\n

{t('recentSessions')}

\r\n {\r\n setQuery(event.currentTarget.value)\r\n setSessionLimit(SESSION_PAGE_SIZE)\r\n }}\r\n />\r\n
\r\n

{t('analysisPrivacy')}

\r\n {sessionOpenError === undefined ? null :

{t('openSessionFailed', { message: sessionOpenError })}

}\r\n {filteredSessions.length === 0 ?

{t('emptySearch')}

: (\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {visibleSessions.map(row => (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n ))}\r\n \r\n
{t('analysis')}{t('session')}{t('updated')}{t('routes')}{t('total')}{t('input')}{t('output')}
\r\n { runAnalysis(row) }}\r\n >{analysis.status === 'loading' && analysis.sessionId === row.id ? t('analyzing') : t('analyze')}\r\n {row.id}{new Intl.DateTimeFormat(undefined, {\r\n dateStyle: 'medium',\r\n timeStyle: 'short',\r\n }).format(row.updatedAt)}{row.models.length === 0 || row.models.every(isUnattributed)\r\n ? {t('unknownRoute')}\r\n : row.models.filter(model => !isUnattributed(model))\r\n .map(model => {routeLabel(model)})}
\r\n
\r\n )}\r\n {filteredSessions.length > visibleSessions.length ? { setSessionLimit(current => current + SESSION_PAGE_SIZE) }}\r\n >{t('showMoreSessions', { shown: visibleSessions.length, total: filteredSessions.length })} : null}\r\n
\r\n \r\n )}\r\n
\r\n )\r\n}\r\n","import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport type { ObservableSnapshot } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport { Button, Modal } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type {\n TokenUsageAnalysisModelSelection,\n TokenUsageAnalysisProgress,\n TrajectoryAnalysis,\n} from '../types.ts'\nimport type { DownloadPort } from './export.ts'\nimport { NS } from './locales.ts'\nimport type { TokenUsageAnalysisModelCatalog } from './usage-analysis-client.ts'\nimport type { TrajectoryHistorySnapshot } from './trajectory-history.ts'\nimport {\n TrajectoryAnalysisPanel,\n type TrajectoryAnalysisState,\n} from './TokenUsageSection.tsx'\nimport css from './TokenUsageSection.module.css'\n\ntype CatalogState =\n | { status: 'idle' | 'loading' }\n | { status: 'ready'; value: TokenUsageAnalysisModelCatalog }\n | { status: 'error'; message: string }\n\ninterface TrajectoryAnalysisActionInjected {\n hooks: {\n trajectoryHistory: ObservableSnapshot\n }\n download: DownloadPort\n listAnalysisModels(signal: AbortSignal): Promise\n analyzeTrajectory(\n sessionId: string,\n model: TokenUsageAnalysisModelSelection,\n signal: AbortSignal,\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\n ): Promise\n saveTrajectoryAnalysis(analysis: TrajectoryAnalysis): void\n removeTrajectoryAnalysis(id: string): void\n}\n\ntype TrajectoryAnalysisActionProps = PropsRuntime<'conversation.session.header.actions'>\n & PropsLocale\n & InjectFace\n\n/** Conversation-header entry opening session analysis and browser-local history. */\nexport function TrajectoryAnalysisAction({\n sessionId,\n useTrajectoryHistory,\n download,\n listAnalysisModels,\n analyzeTrajectory,\n saveTrajectoryAnalysis,\n removeTrajectoryAnalysis,\n t,\n}: TrajectoryAnalysisActionProps): ReactNode {\n const [open, setOpen] = useState(false)\n const [catalog, setCatalog] = useState({ status: 'idle' })\n const [selectedModel, setSelectedModel] = useState()\n const [analysis, setAnalysis] = useState({ status: 'idle' })\n const catalogController = useRef()\n const analysisController = useRef()\n const history = useTrajectoryHistory(snapshot => snapshot)\n const sessionHistory = useMemo(() => history.entries\n .filter(entry => entry.analysis.sessionId === String(sessionId)), [history.entries, sessionId])\n const availableModels = catalog.status === 'ready' ? catalog.value.models : []\n\n useEffect(() => () => {\n catalogController.current?.abort()\n analysisController.current?.abort()\n }, [])\n\n useEffect(() => {\n if (!open || catalog.status !== 'idle') return\n const controller = new AbortController()\n catalogController.current = controller\n setCatalog({ status: 'loading' })\n void listAnalysisModels(controller.signal).then((value) => {\n if (catalogController.current !== controller || controller.signal.aborted) return\n setCatalog({ status: 'ready', value })\n setSelectedModel(value.default ?? value.models[0])\n }, (error: unknown) => {\n if (catalogController.current === controller && !controller.signal.aborted) {\n setCatalog({ status: 'error', message: error instanceof Error ? error.message : String(error) })\n }\n })\n }, [catalog.status, listAnalysisModels, open])\n\n const retryCatalog = (): void => {\n catalogController.current?.abort()\n catalogController.current = undefined\n setSelectedModel(undefined)\n setCatalog({ status: 'idle' })\n }\n\n const closeModal = (): void => {\n analysisController.current?.abort()\n analysisController.current = undefined\n setAnalysis(current => current.status === 'loading' ? { status: 'idle' } : current)\n setOpen(false)\n }\n\n const run = (): void => {\n if (selectedModel === undefined) return\n analysisController.current?.abort()\n const controller = new AbortController()\n analysisController.current = controller\n setAnalysis({ status: 'loading', sessionId: String(sessionId), title: t('currentSession') })\n void analyzeTrajectory(String(sessionId), selectedModel, controller.signal, (progress) => {\n if (analysisController.current !== controller || controller.signal.aborted) return\n setAnalysis(current => current.status === 'loading' ? { ...current, progress } : current)\n }).then((value) => {\n if (analysisController.current !== controller || controller.signal.aborted) return\n analysisController.current = undefined\n saveTrajectoryAnalysis(value)\n setAnalysis({ status: 'ready', title: t('currentSession'), value })\n }, (error: unknown) => {\n if (analysisController.current === controller && !controller.signal.aborted) {\n analysisController.current = undefined\n setAnalysis({\n status: 'error',\n sessionId: String(sessionId),\n title: t('currentSession'),\n message: error instanceof Error ? error.message : String(error),\n })\n }\n })\n }\n\n return <>\n \n {t('close')}}\n >\n
\n {catalog.status === 'loading' || catalog.status === 'idle'\n ? {t('analysisModelsLoading')}\n : catalog.status === 'error'\n ? <>\n {t('analysisModelsFailed', { message: catalog.message })}\n \n \n : availableModels.length === 0\n ? <>\n {t('analysisModelsUnavailable')}\n \n \n : <>\n \n {analysis.status === 'loading' ? t('analyzing') : t('analyze')}\n }\n
\n\n \n\n
\n
\n

{t('analysisHistory')}

{t('analysisHistoryLocal')}

\n {t('analysisHistoryCount', { count: sessionHistory.length })}\n
\n {history.status !== 'ready'\n ?

{t('analysisHistoryUnavailable')}

\n : null}\n {sessionHistory.length === 0\n ?

{t('analysisHistoryEmpty')}

\n :
    {sessionHistory.map(entry =>
  • \n \n { removeTrajectoryAnalysis(entry.id) }}\n >×\n
  • )}
}\n
\n \n \n}\n","/** Shared private RPC names used by the Host and browser halves. */\r\nexport const TOKEN_USAGE_RPC_CHANNEL = '/token-usage'\r\n\r\n/** Version-stable endpoint names for Token usage preferences and analysis. */\r\nexport const TOKEN_USAGE_RPC_ENDPOINT = {\r\n budgetRead: 'budget/read',\r\n budgetWrite: 'budget/write',\r\n analysisModels: 'analysis/models',\r\n analysisProgress: 'analysis/progress',\r\n usageAnalyze: 'usage/analyze',\r\n trajectoryAnalyze: 'trajectory/analyze',\r\n} as const\r\n","import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'\r\nimport type { TokenUsageBudgetSettings } from '../budget-settings.ts'\r\nimport { TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT } from '../rpc.ts'\r\n\r\n/** One browser-visible state of the persistent rolling budget. */\r\nexport interface TokenUsageBudgetSnapshot {\r\n status: 'loading' | 'ready' | 'unavailable'\r\n budget: number\r\n}\r\n\r\nconst INITIAL: TokenUsageBudgetSnapshot = { status: 'loading', budget: 0 }\r\n\r\n/** Minimal stable observable source consumed by the Settings slot hook binder. */\r\nclass BudgetStore {\r\n private snapshot: TokenUsageBudgetSnapshot = INITIAL\r\n private readonly listeners = new Set<() => void>()\r\n\r\n getSnapshot(): TokenUsageBudgetSnapshot {\r\n return this.snapshot\r\n }\r\n\r\n subscribe(listener: () => void): () => void {\r\n this.listeners.add(listener)\r\n return () => { this.listeners.delete(listener) }\r\n }\r\n\r\n set(next: TokenUsageBudgetSnapshot): void {\r\n if (this.snapshot.status === next.status && this.snapshot.budget === next.budget) return\r\n this.snapshot = next\r\n for (const listener of [...this.listeners]) listener()\r\n }\r\n}\r\n\r\n/** Decode the one numeric setting returned by the private Host RPC. */\r\nfunction settingsOf(value: unknown): TokenUsageBudgetSettings | undefined {\r\n if (typeof value !== 'object' || value === null) return undefined\r\n const budget = (value as { rolling30DayBudget?: unknown }).rolling30DayBudget\r\n if (typeof budget !== 'number' || !Number.isSafeInteger(budget) || budget < 0) return undefined\r\n return { rolling30DayBudget: budget }\r\n}\r\n\r\n/** Mirror the private Host settings endpoint onto one HMR-safe observable source. */\r\nexport class TokenUsageBudgetController {\r\n /** Observable snapshot supplied through the settings section's hooks compartment. */\r\n readonly store = new BudgetStore()\r\n\r\n private generation = 0\r\n private disposed = false\r\n private writeQueue: Promise = Promise.resolve()\r\n\r\n /** @param connection - client connection carrying the loopback RPC channel. */\r\n constructor(private readonly connection: ConnectionHandle) {}\r\n\r\n /** Fetch the durable budget unless the current page cannot call loopback-only endpoints. */\r\n async load(): Promise {\r\n const generation = ++this.generation\r\n if (!this.connection.isLoopback) {\r\n this.publish(generation, { status: 'unavailable', budget: 0 })\r\n return\r\n }\r\n try {\r\n const result = await this.connection.rpc.call(TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT.budgetRead, {})\r\n const settings = result.ok ? settingsOf(result.value) : undefined\r\n this.publish(generation, settings === undefined\r\n ? { status: 'unavailable', budget: 0 }\r\n : { status: 'ready', budget: settings.rolling30DayBudget })\r\n } catch (_budgetReadFailure) {\r\n this.publish(generation, { status: 'unavailable', budget: 0 })\r\n }\r\n }\r\n\r\n /** Persist one whole-token rolling budget and return the value that remains durable. */\r\n setBudget(rolling30DayBudget: number): Promise {\r\n if (!Number.isSafeInteger(rolling30DayBudget) || rolling30DayBudget < 0) {\r\n return Promise.resolve(this.store.getSnapshot().budget)\r\n }\r\n const operation = this.writeQueue.then(() => this.writeBudget(rolling30DayBudget))\r\n this.writeQueue = operation.then(() => undefined, () => undefined)\r\n return operation\r\n }\r\n\r\n /** Execute one queued budget write against the latest published durable value. */\r\n private async writeBudget(rolling30DayBudget: number): Promise {\r\n if (this.disposed) return this.store.getSnapshot().budget\r\n const previous = this.store.getSnapshot()\r\n const fallback = previous.status === 'ready' ? previous : { status: 'unavailable' as const, budget: 0 }\r\n const generation = ++this.generation\r\n if (!this.connection.isLoopback) {\r\n this.publish(generation, fallback)\r\n return fallback.budget\r\n }\r\n try {\r\n const result = await this.connection.rpc.call(TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT.budgetWrite, { rolling30DayBudget })\r\n const settings = result.ok ? settingsOf(result.value) : undefined\r\n const next = settings === undefined ? fallback : { status: 'ready' as const, budget: settings.rolling30DayBudget }\r\n this.publish(generation, next)\r\n return next.budget\r\n } catch (_budgetWriteFailure) {\r\n this.publish(generation, fallback)\r\n return fallback.budget\r\n }\r\n }\r\n\r\n /** Stop all late asynchronous publications after the owning Client fiber disposes. */\r\n dispose(): void {\r\n this.disposed = true\r\n this.generation += 1\r\n }\r\n\r\n /** Publish only the latest request result while this controller remains owned. */\r\n private publish(generation: number, next: TokenUsageBudgetSnapshot): void {\r\n if (this.disposed || generation !== this.generation) return\r\n this.store.set(next)\r\n }\r\n}\r\n","/** Poll request-bound auxiliary-analysis progress over the unary Connection RPC. */\n\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'\nimport type { TokenUsageAnalysisProgress } from '../types.ts'\nimport { TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT } from '../rpc.ts'\n\nconst POLL_INTERVAL_MS = 350\n\n/** Return whether one wire value is a JSON record. */\nfunction isRecord(value: unknown): value is Record {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** Generate one request-local opaque progress id without persisting browser identity. */\nfunction createProgressId(): string {\n const random = globalThis.crypto?.randomUUID?.()\n return random === undefined\n ? `${Date.now().toString(36)}.${Math.random().toString(36).slice(2)}.${Math.random().toString(36).slice(2)}`\n : random\n}\n\n/** Decode one live progress snapshot from the Host. */\nexport function analysisProgressOf(value: unknown): TokenUsageAnalysisProgress | undefined {\n if (!isRecord(value) || value.available !== true) return undefined\n const numericKeys = [\n 'elapsedMs', 'chunks', 'outputCharacters', 'estimatedOutputTokens', 'maximumOutputTokens',\n ] as const\n if (!numericKeys.every(key => typeof value[key] === 'number' && Number.isFinite(value[key]) && value[key] >= 0)\n || (value.phase !== 'preparing' && value.phase !== 'generating' && value.phase !== 'finalizing')\n || (value.exactOutputTokens !== undefined\n && (typeof value.exactOutputTokens !== 'number' || !Number.isFinite(value.exactOutputTokens) || value.exactOutputTokens < 0))) {\n return undefined\n }\n return {\n phase: value.phase,\n elapsedMs: value.elapsedMs as number,\n chunks: value.chunks as number,\n outputCharacters: value.outputCharacters as number,\n estimatedOutputTokens: value.estimatedOutputTokens as number,\n ...value.exactOutputTokens === undefined ? {} : { exactOutputTokens: value.exactOutputTokens },\n maximumOutputTokens: value.maximumOutputTokens as number,\n }\n}\n\n/** Wait for the next poll or reject promptly when the owning request ends. */\nfunction waitForPoll(signal: AbortSignal): Promise {\n return new Promise((resolve, reject) => {\n const timer = globalThis.setTimeout(() => {\n signal.removeEventListener('abort', aborted)\n resolve()\n }, POLL_INTERVAL_MS)\n const aborted = (): void => {\n globalThis.clearTimeout(timer)\n reject(signal.reason)\n }\n if (signal.aborted) aborted()\n else signal.addEventListener('abort', aborted, { once: true })\n })\n}\n\n/** Run one unary analysis call while polling its request-bound progress record. */\nexport async function requestAnalysisWithProgress(\n connection: ConnectionHandle,\n endpoint: string,\n payload: Record,\n signal: AbortSignal,\n decode: (value: unknown) => T | undefined,\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\n): Promise {\n const progressId = createProgressId()\n const polling = new AbortController()\n const pollingSignal = AbortSignal.any([signal, polling.signal])\n const monitor = (async (): Promise => {\n while (!pollingSignal.aborted) {\n try {\n const result = await connection.rpc.call(\n TOKEN_USAGE_RPC_CHANNEL,\n TOKEN_USAGE_RPC_ENDPOINT.analysisProgress,\n { progressId },\n pollingSignal,\n )\n if (result.ok) {\n const progress = analysisProgressOf(result.value)\n if (progress !== undefined) onProgress?.(progress)\n }\n } catch (_transientProgressFailure) {\n if (pollingSignal.aborted) return\n // Progress is advisory; a transient polling failure must not fail the model call.\n }\n try {\n await waitForPoll(pollingSignal)\n } catch (_analysisFinished) {\n return\n }\n }\n })()\n\n try {\n const result = await connection.rpc.call(\n TOKEN_USAGE_RPC_CHANNEL,\n endpoint,\n { ...payload, progressId },\n signal,\n )\n if (!result.ok) throw new Error(result.error.message)\n const decoded = decode(result.value)\n if (decoded === undefined) throw new Error('The Host returned an invalid analysis report.')\n return decoded\n } finally {\n polling.abort(new Error('analysis request settled'))\n await monitor\n }\n}\n","/** Client decoder and loopback request for configured-model trajectory analysis. */\r\n\r\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'\r\nimport type {\r\n SignedTokenUsageBuckets,\r\n TokenUsageAnalysisModelSelection,\r\n TokenUsageAnalysisProgress,\r\n TokenUsageBuckets,\r\n TrajectoryAnalysis,\r\n TrajectoryMetrics,\r\n TrajectoryReconciliation,\r\n TrajectoryUsageSpan,\r\n} from '../types.ts'\r\nimport { TOKEN_USAGE_RPC_ENDPOINT } from '../rpc.ts'\r\nimport { requestAnalysisWithProgress } from './analysis-progress-client.ts'\r\n\r\n/** Return whether a wire value is an object. */\r\nfunction isRecord(value: unknown): value is Record {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value)\r\n}\r\n\r\nconst TOKEN_BUCKET_KEYS = ['uncachedInputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens'] as const\r\n\r\n/** Decode the plugin's four disjoint buckets. */\r\nfunction bucketsOf(value: unknown): TokenUsageBuckets | undefined {\r\n if (!isRecord(value)) return undefined\r\n if (!TOKEN_BUCKET_KEYS.every(key => typeof value[key] === 'number' && Number.isFinite(value[key]) && value[key] >= 0)) return undefined\r\n return Object.fromEntries(TOKEN_BUCKET_KEYS.map(key => [key, value[key]])) as unknown as TokenUsageBuckets\r\n}\r\n\r\n/** Decode a signed bucket delta. */\r\nfunction signedBucketsOf(value: unknown): SignedTokenUsageBuckets | undefined {\r\n if (!isRecord(value)) return undefined\r\n if (!TOKEN_BUCKET_KEYS.every(key => typeof value[key] === 'number' && Number.isFinite(value[key]))) return undefined\r\n return Object.fromEntries(TOKEN_BUCKET_KEYS.map(key => [key, value[key]])) as unknown as SignedTokenUsageBuckets\r\n}\r\n\r\n/** Compare bucket sets without collapsing cache categories. */\r\nfunction sameBuckets(left: TokenUsageBuckets, right: TokenUsageBuckets): boolean {\r\n return TOKEN_BUCKET_KEYS.every(key => left[key] === right[key])\r\n}\r\n\r\n/** Sum the four provider buckets for largest-node validation. */\r\nfunction totalTokens(usage: TokenUsageBuckets): number {\r\n return TOKEN_BUCKET_KEYS.reduce((total, key) => total + usage[key], 0)\r\n}\r\n\r\n/** Decode one metadata-only provider usage span. */\r\nfunction spanOf(value: unknown): TrajectoryUsageSpan | undefined {\r\n if (!isRecord(value)\r\n || typeof value.id !== 'string'\r\n || (value.kind !== 'model' && value.kind !== 'compaction')\r\n || typeof value.seq !== 'number'\r\n || !Number.isSafeInteger(value.seq)\r\n || value.seq < 0\r\n || typeof value.provider !== 'string'\r\n || typeof value.model !== 'string'\r\n || !['open', 'completed', 'retried'].includes(String(value.status))\r\n || value.valueKind !== 'actual'\r\n || (value.finality !== 'provisional' && value.finality !== 'authoritative')) return undefined\r\n const usage = bucketsOf(value.usage)\r\n if (usage === undefined) return undefined\r\n const optionalNumber = (key: 'turn' | 'step' | 'attempt'): number | undefined => {\r\n const candidate = value[key]\r\n return typeof candidate === 'number' && Number.isSafeInteger(candidate) && candidate >= 0 ? candidate : undefined\r\n }\r\n if (['turn', 'step', 'attempt'].some(key => value[key] !== undefined && optionalNumber(key as 'turn' | 'step' | 'attempt') === undefined)) {\r\n return undefined\r\n }\r\n const turn = optionalNumber('turn')\r\n const step = optionalNumber('step')\r\n const attempt = optionalNumber('attempt')\r\n return {\r\n id: value.id,\r\n kind: value.kind,\r\n seq: value.seq,\r\n ...turn === undefined ? {} : { turn },\r\n ...step === undefined ? {} : { step },\r\n ...attempt === undefined ? {} : { attempt },\r\n provider: value.provider,\r\n model: value.model,\r\n status: value.status as TrajectoryUsageSpan['status'],\r\n valueKind: 'actual',\r\n finality: value.finality,\r\n usage,\r\n }\r\n}\r\n\r\n/** Decode the explicit provider-ledger reconciliation result. */\r\nfunction reconciliationOf(value: unknown): TrajectoryReconciliation | undefined {\r\n if (!isRecord(value) || (value.status !== 'matched' && value.status !== 'mismatch')) return undefined\r\n const providerUsage = bucketsOf(value.providerUsage)\r\n const attributedUsage = bucketsOf(value.attributedUsage)\r\n const delta = signedBucketsOf(value.delta)\r\n if (providerUsage === undefined || attributedUsage === undefined || delta === undefined) return undefined\r\n const expectedDelta = Object.fromEntries(TOKEN_BUCKET_KEYS.map(key => [key, providerUsage[key] - attributedUsage[key]])) as unknown as SignedTokenUsageBuckets\r\n const matched = TOKEN_BUCKET_KEYS.every(key => expectedDelta[key] === 0)\r\n if (!TOKEN_BUCKET_KEYS.every(key => delta[key] === expectedDelta[key]) || (value.status === 'matched') !== matched) return undefined\r\n return {\r\n status: value.status,\r\n providerUsage,\r\n attributedUsage,\r\n delta,\r\n }\r\n}\r\n\r\nconst BASE_METRIC_KEYS = [\r\n 'eventCount', 'includedEventCount', 'omittedChunkEvents', 'turnCount', 'completedTurns', 'failedTurns',\r\n 'stepCount', 'assistantRequests', 'toolCalls', 'toolErrors', 'retries', 'compactions', 'approvalsAsked',\r\n 'approvalsRejected', 'subagents', 'durationMs', 'eventsPerMinute', 'tokensPerMinute',\r\n] as const\r\nconst ADDITIVE_METRIC_KEYS = [\r\n 'omittedContentEvents', 'toolResults', 'orphanToolCalls', 'orphanToolResults', 'averageToolLatencyMs',\r\n 'maxToolLatencyMs', 'modelSwitches', 'openTurns', 'openSteps', 'activeDurationMs', 'activeTokensPerMinute',\r\n] as const\r\nconst COMPLIANCE_METRIC_KEYS = [\r\n 'approvalsResolved', 'approvalsAllowedOnce', 'approvalsCancelled', 'approvalsUnavailable',\r\n 'unresolvedApprovals', 'orphanApprovalDecisions',\r\n] as const\r\n\r\n/** Decode deterministic analysis metrics while tolerating older report schema fields. */\r\nfunction metricsOf(value: unknown, schema: TrajectoryAnalysis['schema']): TrajectoryMetrics | undefined {\r\n const legacy = schema === 'dsh-token-usage/trajectory-analysis-v1'\r\n if (!isRecord(value)) return undefined\r\n const validNumber = (candidate: unknown): candidate is number =>\r\n typeof candidate === 'number' && Number.isFinite(candidate) && candidate >= 0\r\n if (!BASE_METRIC_KEYS.every(key => validNumber(value[key]))) return undefined\r\n const additive: Record = {}\r\n for (const key of ADDITIVE_METRIC_KEYS) {\r\n if (value[key] === undefined && legacy) additive[key] = 0\r\n else if (validNumber(value[key])) additive[key] = value[key]\r\n else return undefined\r\n }\r\n const compliance: Record = {}\r\n for (const key of COMPLIANCE_METRIC_KEYS) {\r\n if (value[key] === undefined && schema !== 'dsh-token-usage/trajectory-analysis-v3') compliance[key] = 0\r\n else if (validNumber(value[key])) compliance[key] = value[key]\r\n else return undefined\r\n }\r\n\r\n const usage = bucketsOf(value.usage)\r\n if (usage === undefined) return undefined\r\n const retryUsage = value.retryUsage === undefined && legacy\r\n ? { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }\r\n : bucketsOf(value.retryUsage)\r\n if (retryUsage === undefined) return undefined\r\n const rawSpans = value.spans === undefined && legacy ? [] : value.spans\r\n if (!Array.isArray(rawSpans)) return undefined\r\n const spans = rawSpans.map(spanOf)\r\n if (spans.some(span => span === undefined)\r\n || (value.largestSpanId !== undefined && typeof value.largestSpanId !== 'string')) return undefined\r\n const decodedSpans = spans as TrajectoryUsageSpan[]\r\n if (decodedSpans.length > 0 && value.largestSpanId === undefined) return undefined\r\n if (value.largestSpanId !== undefined) {\r\n const largest = decodedSpans.find(span => span.id === value.largestSpanId)\r\n const maximumTokens = decodedSpans.reduce((maximum, span) => Math.max(maximum, totalTokens(span.usage)), 0)\r\n if (largest === undefined || totalTokens(largest.usage) !== maximumTokens) return undefined\r\n }\r\n\r\n const reconciliation = value.reconciliation === undefined && legacy\r\n ? {\r\n status: 'unavailable' as const,\r\n providerUsage: usage,\r\n attributedUsage: { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 },\r\n delta: { ...usage },\r\n }\r\n : reconciliationOf(value.reconciliation)\r\n if (reconciliation === undefined) return undefined\r\n if (reconciliation.status !== 'unavailable') {\r\n const attributedUsage = decodedSpans.reduce((total, span) => ({\r\n uncachedInputTokens: total.uncachedInputTokens + span.usage.uncachedInputTokens,\r\n outputTokens: total.outputTokens + span.usage.outputTokens,\r\n cacheReadTokens: total.cacheReadTokens + span.usage.cacheReadTokens,\r\n cacheWriteTokens: total.cacheWriteTokens + span.usage.cacheWriteTokens,\r\n }), { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 })\r\n if (!sameBuckets(usage, reconciliation.providerUsage)\r\n || !sameBuckets(attributedUsage, reconciliation.attributedUsage)) return undefined\r\n }\r\n return {\r\n ...Object.fromEntries(BASE_METRIC_KEYS.map(key => [key, value[key]])),\r\n ...additive,\r\n completeComplianceEvidenceAvailable: schema === 'dsh-token-usage/trajectory-analysis-v3',\r\n ...compliance,\r\n usage,\r\n retryUsage,\r\n spans: decodedSpans,\r\n ...value.largestSpanId === undefined ? {} : { largestSpanId: value.largestSpanId },\r\n reconciliation,\r\n } as unknown as TrajectoryMetrics\r\n}\r\n\r\n/** Decode one complete versioned trajectory report. */\r\nexport function trajectoryAnalysisOf(value: unknown): TrajectoryAnalysis | undefined {\r\n if (!isRecord(value)\r\n || (value.schema !== 'dsh-token-usage/trajectory-analysis-v1'\r\n && value.schema !== 'dsh-token-usage/trajectory-analysis-v2'\r\n && value.schema !== 'dsh-token-usage/trajectory-analysis-v3')\r\n || typeof value.sessionId !== 'string'\r\n || typeof value.generatedAt !== 'string'\r\n || !Number.isFinite(Date.parse(value.generatedAt))\r\n || typeof value.truncated !== 'boolean'\r\n || typeof value.report !== 'string'\r\n || !isRecord(value.model)\r\n || typeof value.model.provider !== 'string'\r\n || typeof value.model.model !== 'string') return undefined\r\n const metrics = metricsOf(value.metrics, value.schema)\r\n const auxiliary = value.analysisUsage === undefined ? undefined : bucketsOf(value.analysisUsage)\r\n if (metrics === undefined || (value.analysisUsage !== undefined && auxiliary === undefined)) return undefined\r\n return {\r\n schema: value.schema,\r\n sessionId: value.sessionId,\r\n generatedAt: value.generatedAt,\r\n model: { provider: value.model.provider, model: value.model.model },\r\n truncated: value.truncated,\r\n metrics,\r\n ...auxiliary === undefined ? {} : { analysisUsage: auxiliary },\r\n report: value.report,\r\n }\r\n}\r\n\r\n/** Request an ephemeral report from the Host through the loopback-only plugin channel. */\r\nexport async function requestTrajectoryAnalysis(\r\n connection: ConnectionHandle,\r\n sessionId: string,\r\n model: TokenUsageAnalysisModelSelection,\r\n language: string,\r\n signal: AbortSignal,\r\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\r\n): Promise {\r\n if (!connection.isLoopback) throw new Error('Trajectory analysis is available only from the local DSH page.')\r\n return requestAnalysisWithProgress(\r\n connection,\r\n TOKEN_USAGE_RPC_ENDPOINT.trajectoryAnalyze,\r\n { sessionId, model, language },\r\n signal,\r\n trajectoryAnalysisOf,\r\n onProgress,\r\n )\r\n}\r\n","/** Browser-local trajectory report history shared by Settings and conversation controls. */\n\nimport type { TrajectoryAnalysis } from '../types.ts'\nimport { trajectoryAnalysisOf } from './trajectory-analysis-client.ts'\n\nconst STORAGE_KEY = 'dsh-token-usage.trajectory-history.v1'\nconst MAX_ENTRIES = 24\nconst MAX_SERIALIZED_CHARS = 3_000_000\n\nexport interface TrajectoryHistoryEntry {\n id: string\n savedAt: string\n analysis: TrajectoryAnalysis\n}\n\nexport interface TrajectoryHistorySnapshot {\n status: 'ready' | 'error' | 'unavailable'\n entries: readonly TrajectoryHistoryEntry[]\n}\n\n/** Stable observable source consumed through the slot inject hooks compartment. */\nclass TrajectoryHistoryStore {\n private snapshot: TrajectoryHistorySnapshot = { status: 'ready', entries: [] }\n private readonly listeners = new Set<() => void>()\n\n getSnapshot(): TrajectoryHistorySnapshot {\n return this.snapshot\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n set(snapshot: TrajectoryHistorySnapshot): void {\n this.snapshot = snapshot\n for (const listener of [...this.listeners]) listener()\n }\n}\n\n/** Return whether a durable timestamp is finite and renderable. */\nfunction validTimestamp(value: unknown): value is string {\n return typeof value === 'string' && Number.isFinite(Date.parse(value))\n}\n\n/** Decode only reports that still satisfy the current client wire validation. */\nfunction storedEntriesOf(value: unknown): TrajectoryHistoryEntry[] {\n if (!Array.isArray(value)) return []\n return value.flatMap((candidate): TrajectoryHistoryEntry[] => {\n if (typeof candidate !== 'object' || candidate === null) return []\n const record = candidate as Record\n const analysis = trajectoryAnalysisOf(record.analysis)\n return typeof record.id === 'string' && record.id.length > 0 && record.id.length <= 512\n && validTimestamp(record.savedAt)\n && analysis !== undefined\n && validTimestamp(analysis.generatedAt)\n ? [{ id: record.id, savedAt: record.savedAt, analysis }]\n : []\n }).slice(0, MAX_ENTRIES)\n}\n\n/** Persist bounded report history in the current browser profile only. */\nexport class TrajectoryHistoryController {\n readonly store = new TrajectoryHistoryStore()\n\n load(): void {\n if (typeof localStorage === 'undefined') {\n this.store.set({ status: 'unavailable', entries: [] })\n return\n }\n try {\n const raw = localStorage.getItem(STORAGE_KEY)\n const entries = raw === null ? [] : storedEntriesOf(JSON.parse(raw) as unknown)\n this.store.set({ status: 'ready', entries })\n } catch (_invalidOrUnavailableStorage) {\n try {\n localStorage.removeItem(STORAGE_KEY)\n this.store.set({ status: 'ready', entries: [] })\n } catch (_storageUnavailable) {\n this.store.set({ status: 'unavailable', entries: [] })\n }\n }\n }\n\n save(analysis: TrajectoryAnalysis): void {\n const snapshot = this.store.getSnapshot()\n if (snapshot.status === 'unavailable' || typeof localStorage === 'undefined') return\n const entry: TrajectoryHistoryEntry = {\n id: `${analysis.sessionId}\\u0000${analysis.generatedAt}\\u0000${analysis.model.provider}\\u0000${analysis.model.model}`,\n savedAt: new Date().toISOString(),\n analysis,\n }\n const entries = [entry, ...snapshot.entries.filter(candidate => candidate.id !== entry.id)].slice(0, MAX_ENTRIES)\n while (entries.length > 1 && JSON.stringify(entries).length > MAX_SERIALIZED_CHARS) entries.pop()\n const serialized = JSON.stringify(entries)\n if (serialized.length > MAX_SERIALIZED_CHARS) {\n this.store.set({ status: 'error', entries: snapshot.entries })\n return\n }\n try {\n localStorage.setItem(STORAGE_KEY, serialized)\n this.store.set({ status: 'ready', entries })\n } catch (_storageQuotaOrPrivacyMode) {\n this.store.set({ status: 'error', entries: snapshot.entries })\n }\n }\n\n remove(id: string): void {\n const snapshot = this.store.getSnapshot()\n if (snapshot.status === 'unavailable' || typeof localStorage === 'undefined') return\n const entries = snapshot.entries.filter(entry => entry.id !== id)\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(entries))\n this.store.set({ status: 'ready', entries })\n } catch (_storageQuotaOrPrivacyMode) {\n this.store.set({ status: 'error', entries: snapshot.entries })\n }\n }\n}\n","/** Loopback client for selecting an integrated model and analyzing aggregate Token usage. */\r\n\r\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'\r\nimport type {\r\n TokenUsageAnalysis,\r\n TokenUsageAnalysisCatalogFailure,\r\n TokenUsageAnalysisInput,\r\n TokenUsageAnalysisModel,\r\n TokenUsageAnalysisModelSelection,\r\n TokenUsageAnalysisProgress,\r\n TokenUsageBuckets,\r\n} from '../types.ts'\r\nimport { TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT } from '../rpc.ts'\r\nimport { requestAnalysisWithProgress } from './analysis-progress-client.ts'\r\n\r\n/** One server-provided selectable-model catalog and its eligible default route. */\r\nexport interface TokenUsageAnalysisModelCatalog {\r\n models: readonly TokenUsageAnalysisModel[]\r\n failures: readonly TokenUsageAnalysisCatalogFailure[]\r\n default?: TokenUsageAnalysisModelSelection\r\n}\r\n\r\n/** Return whether a wire value is a JSON record. */\r\nfunction isRecord(value: unknown): value is Record {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value)\r\n}\r\n\r\n/** Decode the plugin's four disjoint buckets. */\r\nfunction bucketsOf(value: unknown): TokenUsageBuckets | undefined {\r\n if (!isRecord(value)) return undefined\r\n const keys = ['uncachedInputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens'] as const\r\n if (!keys.every(key => typeof value[key] === 'number' && Number.isFinite(value[key]) && value[key] >= 0)) return undefined\r\n return Object.fromEntries(keys.map(key => [key, value[key]])) as unknown as TokenUsageBuckets\r\n}\r\n\r\n/** Decode one provider/model selector row owned by the Host catalog. */\r\nfunction modelOf(value: unknown): TokenUsageAnalysisModel | undefined {\r\n if (!isRecord(value)\r\n || typeof value.provider !== 'string'\r\n || typeof value.providerName !== 'string'\r\n || typeof value.model !== 'string'\r\n || typeof value.modelName !== 'string'\r\n || value.provider.length === 0\r\n || value.model.length === 0) return undefined\r\n return {\r\n provider: value.provider,\r\n providerName: value.providerName,\r\n model: value.model,\r\n modelName: value.modelName,\r\n }\r\n}\r\n\r\n/** Decode one safe provider identifier whose model list was unavailable. */\r\nfunction failureOf(value: unknown): TokenUsageAnalysisCatalogFailure | undefined {\r\n if (!isRecord(value)\r\n || typeof value.provider !== 'string'\r\n || typeof value.providerName !== 'string'\r\n || value.provider.length === 0\r\n || value.providerName.length === 0) return undefined\r\n return { provider: value.provider, providerName: value.providerName }\r\n}\r\n\r\n/** Decode a server-selected default only when it belongs to the model catalog. */\r\nfunction selectionOf(value: unknown, models: readonly TokenUsageAnalysisModel[]): TokenUsageAnalysisModelSelection | undefined {\r\n if (!isRecord(value) || typeof value.provider !== 'string' || typeof value.model !== 'string') return undefined\r\n return models.some(entry => entry.provider === value.provider && entry.model === value.model)\r\n ? { provider: value.provider, model: value.model }\r\n : undefined\r\n}\r\n\r\n/** Decode the Host's selectable integrated-model catalog. */\r\nexport function analysisModelCatalogOf(value: unknown): TokenUsageAnalysisModelCatalog | undefined {\r\n if (!isRecord(value) || !Array.isArray(value.models)) return undefined\r\n const models = value.models.map(modelOf)\r\n if (models.some(model => model === undefined)) return undefined\r\n const available = models as TokenUsageAnalysisModel[]\r\n const rawFailures = value.failures === undefined ? [] : value.failures\r\n if (!Array.isArray(rawFailures)) return undefined\r\n const failures = rawFailures.map(failureOf)\r\n if (failures.some(failure => failure === undefined)) return undefined\r\n const defaultSelection = selectionOf(value.default, available)\r\n return defaultSelection === undefined\r\n ? { models: available, failures: failures as TokenUsageAnalysisCatalogFailure[] }\r\n : { models: available, failures: failures as TokenUsageAnalysisCatalogFailure[], default: defaultSelection }\r\n}\r\n\r\n/** Decode one complete versioned aggregate Token usage report. */\r\nexport function tokenUsageAnalysisOf(value: unknown): TokenUsageAnalysis | undefined {\r\n if (!isRecord(value)\r\n || value.schema !== 'dsh-token-usage/usage-analysis-v1'\r\n || typeof value.generatedAt !== 'string'\r\n || !Number.isFinite(Date.parse(value.generatedAt))\r\n || typeof value.report !== 'string'\r\n || !isRecord(value.model)\r\n || typeof value.model.provider !== 'string'\r\n || typeof value.model.model !== 'string') return undefined\r\n const auxiliary = value.analysisUsage === undefined ? undefined : bucketsOf(value.analysisUsage)\r\n if (value.analysisUsage !== undefined && auxiliary === undefined) return undefined\r\n return {\r\n schema: value.schema,\r\n generatedAt: value.generatedAt,\r\n model: { provider: value.model.provider, model: value.model.model },\r\n ...auxiliary === undefined ? {} : { analysisUsage: auxiliary },\r\n report: value.report,\r\n }\r\n}\r\n\r\n/** Read every currently registered model route eligible for a manual analysis selection. */\r\nexport async function requestAnalysisModels(\r\n connection: ConnectionHandle,\r\n signal: AbortSignal,\r\n): Promise {\r\n if (!connection.isLoopback) throw new Error('AI analysis is available only from the local DSH page.')\r\n const result = await connection.rpc.call(TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT.analysisModels, {}, signal)\r\n if (!result.ok) throw new Error(result.error.message)\r\n const catalog = analysisModelCatalogOf(result.value)\r\n if (catalog === undefined) throw new Error('The Host returned an invalid integrated-model catalog.')\r\n return catalog\r\n}\r\n\r\n/** Analyze aggregate-only Token usage through the manually selected integrated model. */\r\nexport async function requestTokenUsageAnalysis(\r\n connection: ConnectionHandle,\r\n input: TokenUsageAnalysisInput,\r\n model: TokenUsageAnalysisModelSelection,\r\n language: string,\r\n signal: AbortSignal,\r\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\r\n): Promise {\r\n if (!connection.isLoopback) throw new Error('AI analysis is available only from the local DSH page.')\r\n return requestAnalysisWithProgress(\r\n connection,\r\n TOKEN_USAGE_RPC_ENDPOINT.usageAnalyze,\r\n { input, model, language },\r\n signal,\r\n tokenUsageAnalysisOf,\r\n onProgress,\r\n )\r\n}\r\n","import type {} from '@deepseek-ai/dsh-client-locale/client'\r\n\r\n/** Dictionary namespace owned by the Token usage dashboard. */\r\nexport const NS = 'settings.tokenUsage'\r\n\r\n/** Simplified Chinese dictionary and key source of truth. */\r\nexport const zh = {\r\n nav: 'Token 用量',\r\n title: 'Token 使用记录',\r\n intro: '基于 DSH 持久会话日志统计模型请求与上下文压缩用量,不保存提示词或回复正文。',\r\n totalTokens: '总 Token',\r\n inputTokens: '输入 Token',\r\n outputTokens: '输出 Token',\r\n cacheHit: '缓存读取占输入',\r\n sessions: '有用量会话',\r\n modelBreakdown: '模型用量',\r\n activity: 'Token 活跃度',\r\n activityIntro: '最近 30 周,颜色越深表示当日 Token 使用量越高。悬停查看明细,点击查看当天会话。',\r\n activityTooltip: '{date}\\n总计 {total} Token\\n输入 {input} · 输出 {output}\\n缓存:读 {cacheRead} · 写 {cacheWrite}',\r\n less: '少',\r\n more: '多',\r\n trend: '用量趋势',\r\n trendIntro: '按 UTC 日期比较当前周期与前一等长周期。',\r\n rangeDays: '{count} 天',\r\n periodTokens: '最近 {count} 天',\r\n periodChange: '较前一周期',\r\n activeDays: '活跃天数',\r\n peakDay: '峰值日',\r\n peakDayNote: '峰值:{date} · {total} Token',\r\n budget: '30 日 Token 预算',\r\n budgetIntro: '预算保存在本机 DSH 设置中,填 0 或清空即可关闭。',\r\n budgetInput: '30 日预算(Token)',\r\n budgetDisabled: '尚未设置预算。',\r\n budgetUnavailable: '当前连接无法读取或保存预算。',\r\n budgetProgress: '已用 {used} / {budget} Token({percent}%)',\r\n budgetExceeded: '已超出预算 {excess} Token。',\r\n budgetRunRate: '按最近 7 个完整 UTC 日:日均 {average} Token,预计滚动 30 日 {projected} Token。',\r\n budgetForecastExceeded: '按当前运行率预计 30 日 {projected} Token,将超过预算 {budget} Token。',\r\n estimatedCost: '估算费用(USD)',\r\n cacheReadSavings: '缓存读取避免费用(USD)',\r\n priceCoverage: '费率覆盖',\r\n pricingTitle: '公开费率估算',\r\n pricingIntro: '内置 USD 费率表截至 {asOf},可能已经变化;覆盖 {covered}/{total} Token、{routes}/{allRoutes} 个有用量路由。仅按路由标签匹配,不能验证端点、合同或账单。',\r\n pricingSource: '查看官方费率来源',\r\n priceUnavailable: '该 route 不在内置公开 USD 费率表中。',\r\n priceRate: '每 1M Token:输入 ${input} · 输出 ${output} · 缓存读 ${cacheRead} · 缓存写 ${cacheWrite}(公开费率 {asOf})',\r\n efficiency: 'Agent 效率与归因',\r\n efficiencyIntro: '仅基于已记录的聚合 Token、调用次数与上下文压缩统计,不推断任务质量。',\r\n assistantAttempts: '模型尝试次数',\r\n tokensPerAssistantAttempt: '每次模型尝试 Token',\r\n compactionRate: '每 100 次尝试的压缩',\r\n compactionTokenShare: '压缩 Token 占比',\r\n cacheReadShare: '缓存读取占输入',\r\n topRouteShare: 'Top 路由占比',\r\n noRouteAttribution: '当前没有可归因的模型路由。',\r\n routeConcentration: 'Top 路由 {route} 占全部 Token 的 {topOne};Top 3 合计 {topThree}。',\r\n unattributedShare: '未归因用量占全部 Token 的 {share};其调用次数与路由效率未知。',\r\n usageSignals: '用量信号',\r\n usageSignalsIntro: '运行率和突增检测只使用完整 UTC 日;不对每日用量归因模型。',\r\n dailyRunRate: '7 日日均 Token',\r\n projectedThirtyDayUsage: '预计 30 日 Token',\r\n anomalyRatio: '昨日相对基线',\r\n anomalyExcess: '昨日超出中位数',\r\n anomalyInsufficient: '突增检测需要昨日有用量,以及此前 28 日至少 5 个活跃完整 UTC 日。',\r\n dailyCoverageUnavailable: '旧版回退记录没有真实逐日 bucket,因此不用于运行率、预测或异常信号。',\r\n dailyCoveragePartial: '部分历史记录缺少真实逐日 bucket;为避免低估,运行率、预测和异常信号暂不显示。',\r\n anomalyNormal: '{date} 未超过稳健基线(活跃日中位数 {baseline} Token,样本 {active} 日)。',\r\n anomalyElevated: '{date} 使用 {tokens} Token,是活跃日中位数 {baseline} Token 的 {ratio}×,超出 {excess} Token(样本 {active} 日)。',\r\n inspectAnomalyDay: '查看异常日会话',\r\n usageAnalysis: 'AI Token 用量分析',\r\n usageAnalysisIntro: '使用手动选择的已接入模型,基于聚合 Token 数据生成多维分析与优化建议。',\r\n analysisModel: '分析模型',\r\n analysisModelsLoading: '正在读取已接入模型…',\r\n analysisModelsFailed: '无法读取已接入模型:{message}',\r\n analysisModelsUnavailable: '没有可用于分析的已接入模型。请先在 DSH 的模型设置中接入模型。',\r\n analysisModelsPartial: '以下提供方暂时无法列出模型,但其他可用模型仍可分析:{providers}。',\r\n analysisModelsAllFailed: '暂时无法从以下提供方读取模型:{providers}。请稍后刷新目录。',\r\n refreshAnalysisModels: '刷新模型目录',\r\n usageAnalysisPrivacy: '隐私提示:仅发送总量、路由别名、请求次数和 UTC 每日 Token bucket;不会发送原始 provider/model、会话 ID、标题、提示词或回复。',\r\n analysisModelScope: '此处选择的模型同样用于下方按会话运行的轨迹分析;轨迹报告会保存在当前浏览器本地。',\r\n analyzeUsage: '生成用量分析',\r\n usageAnalyzing: '正在生成分析…',\r\n usageAnalysisRunning: '模型正在生成 Token 用量分析报告。',\r\n usageAnalysisFailed: '用量分析失败:{message}',\r\n usageAnalysisReport: 'AI 用量分析报告',\r\n exportAnalysisReport: '导出 Markdown 报告',\r\n copyCode: '复制代码',\r\n copiedCode: '已复制',\r\n analysisProgressPreparing: '准备分析证据',\r\n analysisProgressGenerating: '模型生成中',\r\n analysisProgressFinalizing: '整理报告',\r\n analysisProgressWaiting: '等待模型返回内容',\r\n analysisProgressEstimated: '已返回约 {count} / {maximum} Token',\r\n analysisProgressExact: '已返回 {count} / {maximum} Token',\r\n analysisProgressActivity: '{chunks} 个流块 · {characters} 个字符',\r\n analysisProgressElapsed: '已等待 {seconds} 秒',\r\n dayDetails: '{date} 用量明细',\r\n dayDetailsIntro: '仅显示该 UTC 日期内已记录的聚合用量与贡献会话。',\r\n closeDayDetails: '收起明细',\r\n contributors: '贡献会话({count})',\r\n noContributors: '当天没有可显示的会话贡献。',\r\n export: '导出',\r\n exportJson: 'JSON 汇总',\r\n exportDaily: '每日 CSV',\r\n exportModels: '模型 CSV',\r\n recentSessions: '会话记录',\r\n openSessionFailed: '无法打开会话:{message}',\r\n showMoreSessions: '显示更多会话(已显示 {shown}/{total})',\r\n providerModel: '提供方 / 模型',\r\n modelSort: '排序方式',\r\n modelSortTotal: '总 Token',\r\n modelSortCost: '估算费用',\r\n modelSortTokensPerAttempt: '每次记录调用 Token',\r\n modelSortCacheRead: '缓存读取占输入',\r\n calls: '调用',\r\n total: '总量',\r\n input: '输入',\r\n output: '输出',\r\n cacheRead: '缓存读取',\r\n cacheWrite: '缓存写入',\r\n cacheDetail: '缓存:读 {read} · 写 {write}',\r\n session: '会话',\r\n updated: '最近活动',\r\n routes: '模型',\r\n search: '搜索会话或模型',\r\n empty: '暂无 Token 使用记录。',\r\n emptySearch: '没有匹配的使用记录。',\r\n assistantCalls: '对话 {count}',\r\n compactionCalls: '压缩 {count}',\r\n unknownRoute: '模型信息不可用',\r\n unattributed: '未归因用量',\r\n trajectoryAnalysis: '会话 Token 轨迹分析',\r\n trajectoryAnalysisIntro: '选择“分析轨迹”后,仅将事件类别、相对时间、路由别名、工具名称、审批结果、状态和 provider Token bucket 发送给所选模型;提示词、回复、工具参数与结果始终省略。完成的报告保存在当前浏览器本地。',\r\n analysisRunning: '正在分析“{title}”的元数据轨迹…',\r\n analysisFailed: '分析失败:{message}',\r\n analysisFor: '轨迹分析 · {title}',\r\n analysisMeta: '{provider}/{model} · {time}',\r\n analysisCostDetailed: '本次分析 {total} Token · 模型输出 {output}',\r\n analysisTurns: '回合 / 未结束',\r\n analysisTools: '工具调用 / 结果 / 错误',\r\n analysisIntegrity: '孤立工具 / 未结束步骤',\r\n analysisToolLatency: '工具延迟(均值 / 最大)',\r\n analysisRetries: '模型重试',\r\n analysisRetryTokens: '重试 Token',\r\n analysisLargest: '最大用量节点',\r\n analysisReconciliation: 'Token 对账',\r\n analysisMatched: '一致',\r\n analysisUnavailable: '旧版报告不可用',\r\n analysisMismatch: '差异 {count}',\r\n analysisRate: '活跃时段 Token 速率',\r\n analysisApprovals: '审批 / 拒绝',\r\n analysisLifecycleGroup: '生命周期',\r\n analysisToolGroup: '工具可靠性',\r\n analysisComplianceGroup: '合规控制',\r\n analysisEfficiencyGroup: '资源效率',\r\n analysisSteps: '步骤',\r\n analysisOpenCount: '未闭合 {count}',\r\n analysisTokenCount: '{count} Token',\r\n analysisApprovalClosure: '审批闭环',\r\n analysisApprovalDenied: '拒绝 / 取消 / 不可用',\r\n analysisApprovalRequests: '审批请求',\r\n analysisApprovalRejectedOnly: '拒绝决定',\r\n analysisComplianceEvidence: 'v3 审计字段',\r\n analysisAuditGaps: '审计缺口',\r\n analysisTruncated: '元数据轨迹过长,模型仅收到首尾有界样本;报告会将中段标为不可用证据。',\r\n analysisPrivacy: '隐私:所选模型只接收白名单元数据、工具名称、审批结果和 provider 上报 Token;不发送提示词、回复、工具参数/结果、原始 provider/model、会话标题/ID 或个人与组织字段。报告历史仅保存在当前浏览器。',\r\n conversationTrajectoryAnalysis: '当前会话轨迹分析',\r\n conversationTrajectoryAnalysisIntro: '可在对话页直接运行与 Token 用量页一致的轨迹审计,并查看当前会话的本地历史。',\r\n currentSession: '当前会话',\r\n analysisHistory: '分析历史',\r\n analysisHistoryLocal: '仅保存在当前浏览器 localStorage,最多保留 24 条。',\r\n analysisHistoryCount: '{count} 条',\r\n analysisHistoryEmpty: '当前会话还没有已保存的分析报告。',\r\n analysisHistoryUnavailable: '浏览器本地存储不可用,分析报告不会保留。',\r\n deleteAnalysisHistory: '删除分析历史',\r\n close: '关闭',\r\n analysis: '轨迹分析',\r\n analyze: '分析轨迹',\r\n analyzing: '分析中…',\r\n loading: '正在读取会话统计…',\r\n} satisfies Record\r\n\r\n/** Token usage locale key union. */\r\nexport type TokenUsageLocaleKey = keyof typeof zh\r\n\r\n/** English dictionary checked against the Chinese key set. */\r\nexport const en = {\r\n nav: 'Token usage',\r\n title: 'Token usage records',\r\n intro: 'Counts model requests and context compactions from durable DSH session logs without storing prompt or response text.',\r\n totalTokens: 'Total tokens',\r\n inputTokens: 'Input tokens',\r\n outputTokens: 'Output tokens',\r\n cacheHit: 'Cache-read share of input',\r\n sessions: 'Sessions with usage',\r\n modelBreakdown: 'Usage by model',\r\n activity: 'Token activity',\r\n activityIntro: 'Last 30 weeks. Darker cells represent higher daily Token usage. Hover for details or select a day for its sessions.',\r\n activityTooltip: '{date}\\nTotal {total} tokens\\nInput {input} · Output {output}\\nCache: read {cacheRead} · write {cacheWrite}',\r\n less: 'Less',\r\n more: 'More',\r\n trend: 'Usage trends',\r\n trendIntro: 'Compares the current UTC period with the preceding period of equal length.',\r\n rangeDays: '{count} days',\r\n periodTokens: 'Last {count} days',\r\n periodChange: 'Previous period',\r\n activeDays: 'Active days',\r\n peakDay: 'Peak day',\r\n peakDayNote: 'Peak: {date} · {total} tokens',\r\n budget: '30-day Token budget',\r\n budgetIntro: 'The budget is stored in local DSH settings. Enter 0 or clear it to disable.',\r\n budgetInput: '30-day budget (tokens)',\r\n budgetDisabled: 'No budget is set.',\r\n budgetUnavailable: 'This connection cannot read or save the budget.',\r\n budgetProgress: '{used} / {budget} tokens used ({percent}%)',\r\n budgetExceeded: '{excess} tokens over budget.',\r\n budgetRunRate: 'Latest 7 complete UTC days: {average} tokens/day; projected rolling 30 days: {projected} tokens.',\r\n budgetForecastExceeded: 'At the current run rate, the projected 30-day usage of {projected} tokens exceeds the {budget}-token budget.',\r\n estimatedCost: 'Estimated cost (USD)',\r\n cacheReadSavings: 'Cache-read avoided cost (USD)',\r\n priceCoverage: 'Rate coverage',\r\n pricingTitle: 'Public-rate estimate',\r\n pricingIntro: 'Built-in USD rates as of {asOf} may have changed; coverage is {covered}/{total} tokens across {routes}/{allRoutes} active routes. Label matching cannot verify the endpoint, contract, or invoice.',\r\n pricingSource: 'View official pricing source',\r\n priceUnavailable: 'This route is not in the built-in public USD rate catalog.',\r\n priceRate: 'Per 1M tokens: input ${input} · output ${output} · cache read ${cacheRead} · cache write ${cacheWrite} (public rate {asOf})',\r\n efficiency: 'Agent efficiency and attribution',\r\n efficiencyIntro: 'Uses only recorded aggregate Tokens, attempt counts, and context-compaction statistics; it does not infer task quality.',\r\n assistantAttempts: 'Model attempts',\r\n tokensPerAssistantAttempt: 'Tokens per model attempt',\r\n compactionRate: 'Compactions per 100 attempts',\r\n compactionTokenShare: 'Compaction Token share',\r\n cacheReadShare: 'Cache-read share of input',\r\n topRouteShare: 'Top route share',\r\n noRouteAttribution: 'No attributable model route is available.',\r\n routeConcentration: 'Top route {route} accounts for {topOne} of all Tokens; Top 3 account for {topThree}.',\r\n unattributedShare: 'Unattributed usage is {share} of all Tokens; its attempt count and route efficiency are unknown.',\r\n usageSignals: 'Usage signals',\r\n usageSignalsIntro: 'Run rate and spike detection use complete UTC days only; daily usage is not attributed to a model.',\r\n dailyRunRate: '7-day daily Tokens',\r\n projectedThirtyDayUsage: 'Projected 30-day Tokens',\r\n anomalyRatio: 'Yesterday versus baseline',\r\n anomalyExcess: 'Yesterday above median',\r\n anomalyInsufficient: 'Spike detection needs usage yesterday and at least five active complete UTC days in the prior 28 days.',\r\n dailyCoverageUnavailable: 'Legacy fallback records lack true daily buckets, so they are excluded from run rate, forecast, and anomaly signals.',\r\n dailyCoveragePartial: 'Some historical records lack true daily buckets; run rate, forecast, and anomaly signals are hidden to avoid undercounting.',\r\n anomalyNormal: '{date} is within the robust baseline (active-day median {baseline} tokens; {active} days).',\r\n anomalyElevated: '{date} used {tokens} tokens, {ratio}× the active-day median of {baseline} tokens, exceeding it by {excess} tokens ({active} days).',\r\n inspectAnomalyDay: 'Inspect spike-day sessions',\r\n usageAnalysis: 'AI Token usage analysis',\r\n usageAnalysisIntro: 'Use a manually selected integrated model to generate a multi-dimensional usage review and optimization recommendations from aggregate Token data.',\r\n analysisModel: 'Analysis model',\r\n analysisModelsLoading: 'Reading integrated models…',\r\n analysisModelsFailed: 'Unable to read integrated models: {message}',\r\n analysisModelsUnavailable: 'No integrated model is available for analysis. Add one in DSH Model settings first.',\r\n analysisModelsPartial: 'Models could not be listed for {providers}; other available models can still run analysis.',\r\n analysisModelsAllFailed: 'Models could not be read for {providers}. Refresh the catalog later.',\r\n refreshAnalysisModels: 'Refresh model catalog',\r\n usageAnalysisPrivacy: 'Privacy: only totals, route aliases, request counts, and UTC daily Token buckets are sent. No raw provider/model ids, session IDs, titles, prompts, or responses are sent.',\r\n analysisModelScope: 'The selected model is also used by the per-session trajectory analysis below; trajectory reports are stored in this browser.',\r\n analyzeUsage: 'Generate usage analysis',\r\n usageAnalyzing: 'Generating analysis…',\r\n usageAnalysisRunning: 'The model is generating the Token usage analysis report.',\r\n usageAnalysisFailed: 'Usage analysis failed: {message}',\r\n usageAnalysisReport: 'AI usage analysis report',\r\n exportAnalysisReport: 'Export Markdown report',\r\n copyCode: 'Copy code',\r\n copiedCode: 'Copied',\r\n analysisProgressPreparing: 'Preparing evidence',\r\n analysisProgressGenerating: 'Model generating',\r\n analysisProgressFinalizing: 'Finalizing report',\r\n analysisProgressWaiting: 'Waiting for model output',\r\n analysisProgressEstimated: 'About {count} / {maximum} tokens returned',\r\n analysisProgressExact: '{count} / {maximum} tokens returned',\r\n analysisProgressActivity: '{chunks} stream chunks · {characters} characters',\r\n analysisProgressElapsed: 'Waiting for {seconds}s',\r\n dayDetails: '{date} usage details',\r\n dayDetailsIntro: 'Shows aggregate usage and contributing sessions recorded for this UTC date.',\r\n closeDayDetails: 'Hide details',\r\n contributors: 'Contributing sessions ({count})',\r\n noContributors: 'No session contribution is available for this day.',\r\n export: 'Export',\r\n exportJson: 'JSON summary',\r\n exportDaily: 'Daily CSV',\r\n exportModels: 'Model CSV',\r\n recentSessions: 'Session records',\r\n openSessionFailed: 'Unable to open session: {message}',\r\n showMoreSessions: 'Show more sessions ({shown}/{total} shown)',\r\n providerModel: 'Provider / model',\r\n modelSort: 'Sort by',\r\n modelSortTotal: 'Total Tokens',\r\n modelSortCost: 'Estimated cost',\r\n modelSortTokensPerAttempt: 'Tokens per recorded call',\r\n modelSortCacheRead: 'Cache-read share of input',\r\n calls: 'Calls',\r\n total: 'Total',\r\n input: 'Input',\r\n output: 'Output',\r\n cacheRead: 'Cache read',\r\n cacheWrite: 'Cache write',\r\n cacheDetail: 'Cache: read {read} · write {write}',\r\n session: 'Session',\r\n updated: 'Last activity',\r\n routes: 'Models',\r\n search: 'Search sessions or models',\r\n empty: 'No token usage has been recorded.',\r\n emptySearch: 'No matching usage records.',\r\n assistantCalls: 'Chat {count}',\r\n compactionCalls: 'Compaction {count}',\r\n unknownRoute: 'Model unavailable',\r\n unattributed: 'Unattributed usage',\r\n trajectoryAnalysis: 'Session Token trajectory analysis',\r\n trajectoryAnalysisIntro: 'Analyze trajectory sends only event categories, relative timing, route aliases, tool names, approval outcomes, statuses, and provider Token buckets to the selected model. Prompts, replies, tool arguments, and results are always omitted. Completed reports are stored in this browser.',\r\n analysisRunning: 'Analyzing the metadata trajectory for “{title}”…',\r\n analysisFailed: 'Analysis failed: {message}',\r\n analysisFor: 'Trajectory analysis · {title}',\r\n analysisMeta: '{provider}/{model} · {time}',\r\n analysisCostDetailed: '{total} analysis tokens · {output} model output',\r\n analysisTurns: 'Turns / open',\r\n analysisTools: 'Tool calls / results / errors',\r\n analysisIntegrity: 'Orphaned tools / open steps',\r\n analysisToolLatency: 'Tool latency (avg / max)',\r\n analysisRetries: 'Model retries',\r\n analysisRetryTokens: 'Retry tokens',\r\n analysisLargest: 'Largest usage node',\r\n analysisReconciliation: 'Token reconciliation',\r\n analysisMatched: 'Matched',\r\n analysisUnavailable: 'Unavailable in legacy report',\r\n analysisMismatch: '{count} difference',\r\n analysisRate: 'Active-window Token rate',\r\n analysisApprovals: 'Approvals / rejected',\r\n analysisLifecycleGroup: 'Lifecycle',\r\n analysisToolGroup: 'Tool reliability',\r\n analysisComplianceGroup: 'Compliance controls',\r\n analysisEfficiencyGroup: 'Resource efficiency',\r\n analysisSteps: 'Steps',\r\n analysisOpenCount: '{count} open',\r\n analysisTokenCount: '{count} tokens',\r\n analysisApprovalClosure: 'Approval closure',\r\n analysisApprovalDenied: 'Rejected / cancelled / unavailable',\r\n analysisApprovalRequests: 'Approval requests',\r\n analysisApprovalRejectedOnly: 'Rejected decisions',\r\n analysisComplianceEvidence: 'v3 audit fields',\r\n analysisAuditGaps: 'Audit gaps',\r\n analysisTruncated: 'The metadata trajectory was too long, so the model received a bounded head-and-tail sample and treats the middle as unavailable evidence.',\r\n analysisPrivacy: 'Privacy: the selected model receives only allowlisted metadata, tool names, approval outcomes, and provider-reported Token buckets—never prompts, replies, tool arguments/results, raw provider/model ids, session titles/IDs, or personal and organization fields. Report history stays in this browser.',\r\n conversationTrajectoryAnalysis: 'Current session trajectory analysis',\r\n conversationTrajectoryAnalysisIntro: 'Run the same trajectory audit from the conversation page and review local history for this session.',\r\n currentSession: 'Current session',\r\n analysisHistory: 'Analysis history',\r\n analysisHistoryLocal: 'Stored only in this browser localStorage, up to 24 reports.',\r\n analysisHistoryCount: '{count} reports',\r\n analysisHistoryEmpty: 'No saved analysis report exists for this session.',\r\n analysisHistoryUnavailable: 'Browser local storage is unavailable, so analysis reports will not persist.',\r\n deleteAnalysisHistory: 'Delete analysis history',\r\n close: 'Close',\r\n analysis: 'Trajectory analysis',\r\n analyze: 'Analyze trajectory',\r\n analyzing: 'Analyzing…',\r\n loading: 'Reading session usage…',\r\n} satisfies Record\r\n","/** Token usage dashboard registered into Web Settings. */\n\nimport type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {\n TokenUsageAnalysisInput,\n TokenUsageAnalysisModelSelection,\n TokenUsageAnalysisProgress,\n TrajectoryAnalysis,\n} from '../types.ts'\nimport { TokenUsageSection } from './TokenUsageSection.tsx'\nimport { TrajectoryAnalysisAction } from './TrajectoryAnalysisAction.tsx'\nimport { TokenUsageBudgetController } from './budget-controller.ts'\nimport { browserDownload } from './export.ts'\nimport { requestTrajectoryAnalysis } from './trajectory-analysis-client.ts'\nimport { TrajectoryHistoryController } from './trajectory-history.ts'\nimport { requestAnalysisModels, requestTokenUsageAnalysis } from './usage-analysis-client.ts'\nimport { en, NS, zh, type TokenUsageLocaleKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Token usage dashboard copy. */\n 'settings.tokenUsage': TokenUsageLocaleKey\n }\n}\n\n/** Client services required by the Settings contribution. */\nexport const inject = ['slots', 'locale', 'connection', 'sessions']\n\n/** Contribute a localized Token usage page to Settings. */\nexport function apply(ctx: ClientContext): void {\n const connection = ctx.get('connection') as ConnectionHandle | undefined\n if (connection === undefined) throw new Error('dsh-token-usage requires the Client connection service')\n const budget = new TokenUsageBudgetController(connection)\n const trajectoryHistory = new TrajectoryHistoryController()\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'token-usage: dictionaries')\n ctx.effect(() => {\n trajectoryHistory.load()\n void budget.load()\n return () => { budget.dispose() }\n }, 'token usage: load persistent budget')\n const t = ctx.locale.bind(NS)\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'token-usage',\n order: 30,\n label: () => t('nav'),\n locale: NS,\n inject: () => ({\n hooks: { budget: budget.store },\n setBudget: (value: number) => budget.setBudget(value),\n download: browserDownload,\n saveTrajectoryAnalysis: (analysis: TrajectoryAnalysis) => { trajectoryHistory.save(analysis) },\n openSession: (sessionId: SessionId) => { ctx.sessions.open(sessionId) },\n listAnalysisModels: (signal: AbortSignal) => requestAnalysisModels(connection, signal),\n analyzeTokenUsage: (\n input: TokenUsageAnalysisInput,\n model: TokenUsageAnalysisModelSelection,\n signal: AbortSignal,\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\n ) => requestTokenUsageAnalysis(\n connection,\n input,\n model,\n ctx.locale.getLocale().active,\n signal,\n onProgress,\n ),\n analyzeTrajectory: (\n sessionId: string,\n model: TokenUsageAnalysisModelSelection,\n signal: AbortSignal,\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\n ) => requestTrajectoryAnalysis(\n connection,\n sessionId,\n model,\n ctx.locale.getLocale().active,\n signal,\n onProgress,\n ),\n }),\n }, TokenUsageSection))\n ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({\n name: 'conversation.session.header.actions',\n id: 'token-usage-trajectory-analysis',\n order: 40,\n locale: NS,\n inject: () => ({\n hooks: { trajectoryHistory: trajectoryHistory.store },\n download: browserDownload,\n listAnalysisModels: (signal: AbortSignal) => requestAnalysisModels(connection, signal),\n analyzeTrajectory: (\n sessionId: string,\n model: TokenUsageAnalysisModelSelection,\n signal: AbortSignal,\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\n ) => requestTrajectoryAnalysis(\n connection,\n sessionId,\n model,\n ctx.locale.getLocale().active,\n signal,\n onProgress,\n ),\n saveTrajectoryAnalysis: (analysis: TrajectoryAnalysis) => { trajectoryHistory.save(analysis) },\n removeTrajectoryAnalysis: (id: string) => { trajectoryHistory.remove(id) },\n }),\n }, TrajectoryAnalysisAction))\n}\n"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"client.js","names":["zeroBuckets","addBuckets","totalTokens","dayKey","totalTokens","inputTokens","isUnattributed","totalTokens","totalTokens","MarkdownText","sameBuckets","totalTokens","css","css","Modal","Button","isRecord","isRecord","bucketsOf"],"sources":["../src/client/analytics.ts","../src/client/efficiency.ts","../src/client/route-budget.ts","../src/pricing.ts","../src/client/report-safety.ts","../src/client/export.ts","../src/client/SafeMarkdownReport.tsx","../src/client/TokenUsageSection.tsx","../src/client/TrajectoryAnalysisAction.tsx","../src/rpc.ts","../src/client/budget-controller.ts","../src/client/analysis-progress-client.ts","../src/client/trajectory-analysis-client.ts","../src/client/trajectory-history.ts","../src/client/usage-analysis-client.ts","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["import type { DailyTokenUsageRecord, TokenUsageBuckets } from '../types.ts'\n\n/** A session contribution to one selected UTC day. */\nexport interface DailySessionContributor {\n id: string\n title: string\n usage: TokenUsageBuckets\n}\n\n/** Minimum session data required to find one day's contributors. */\nexport interface DailySessionSource {\n id: string\n title: string\n days: readonly DailyTokenUsageRecord[]\n}\n\n/** Aggregate comparison for one trailing UTC period. */\nexport interface PeriodInsight {\n days: number\n usage: TokenUsageBuckets\n previousUsage: TokenUsageBuckets\n activeDays: number\n peak: DailyTokenUsageRecord | undefined\n}\n\n/** Complete-UTC-day burn rate projected to a rolling 30-day period. */\nexport interface RunRateInsight {\n observedDays: number\n averageDailyTokens: number\n projectedThirtyDayTokens: number\n}\n\n/** A robust comparison of yesterday's complete usage with preceding active days. */\nexport interface DailyAnomalyInsight {\n date: string\n tokens: number\n baselineMedianTokens: number\n baselineMadTokens: number\n activeBaselineDays: number\n ratio: number\n excessTokens: number\n status: 'normal' | 'elevated'\n}\n\n/** Detached zero buckets for analytics calculations. */\nfunction zeroBuckets(): TokenUsageBuckets {\n return {\n uncachedInputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n }\n}\n\n/** Add two disjoint Token bucket sets. */\nfunction addBuckets(left: TokenUsageBuckets, right: TokenUsageBuckets): TokenUsageBuckets {\n return {\n uncachedInputTokens: left.uncachedInputTokens + right.uncachedInputTokens,\n outputTokens: left.outputTokens + right.outputTokens,\n cacheReadTokens: left.cacheReadTokens + right.cacheReadTokens,\n cacheWriteTokens: left.cacheWriteTokens + right.cacheWriteTokens,\n }\n}\n\n/** Full request/response total without counting reasoning output twice. */\nfunction totalTokens(usage: TokenUsageBuckets): number {\n return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + usage.outputTokens\n}\n\n/** Stable UTC day key shared with the durable projection. */\nfunction dayKey(time: number): string {\n return new Date(time).toISOString().slice(0, 10)\n}\n\n/** Build a newest-inclusive UTC date range. */\nfunction datesEndingOn(now: number, length: number): string[] {\n const end = new Date(`${dayKey(now)}T00:00:00.000Z`)\n end.setUTCDate(end.getUTCDate() - length + 1)\n const dates: string[] = []\n for (let offset = 0; offset < length; offset += 1) {\n const date = new Date(end)\n date.setUTCDate(date.getUTCDate() + offset)\n dates.push(dayKey(date.getTime()))\n }\n return dates\n}\n\n/** Aggregate a fixed sequence of UTC dates from a daily bucket lookup. */\nfunction aggregateDates(byDate: ReadonlyMap, dates: readonly string[]): TokenUsageBuckets {\n return dates.reduce((usage, date) => addBuckets(usage, byDate.get(date) ?? zeroBuckets()), zeroBuckets())\n}\n\n/** Derive period totals, comparison totals, activity, and the highest-use day. */\nexport function periodInsight(\n records: readonly DailyTokenUsageRecord[],\n days: number,\n now = Date.now(),\n): PeriodInsight {\n const byDate = new Map(records.map(record => [record.date, record.usage]))\n const currentDates = datesEndingOn(now, days)\n const previousDates = datesEndingOn(now - days * 86_400_000, days)\n const currentRecords = currentDates.map(date => ({\n date,\n usage: { ...(byDate.get(date) ?? zeroBuckets()) },\n }))\n const active = currentRecords.filter(record => totalTokens(record.usage) > 0)\n const peak = active.reduce((highest, record) =>\n highest === undefined || totalTokens(record.usage) > totalTokens(highest.usage) ? record : highest,\n undefined)\n return {\n days,\n usage: aggregateDates(byDate, currentDates),\n previousUsage: aggregateDates(byDate, previousDates),\n activeDays: active.length,\n peak,\n }\n}\n\n/** Return the median of a non-empty numeric list without retaining a source reference. */\nfunction median(values: readonly number[]): number {\n const sorted = values.slice().sort((left, right) => left - right)\n const middle = Math.floor(sorted.length / 2)\n return sorted.length % 2 === 0\n ? (sorted[middle - 1]! + sorted[middle]!) / 2\n : sorted[middle]!\n}\n\n/** Aggregate one complete UTC day range ending before the current partial day. */\nfunction completeDaysEndingBefore(now: number, length: number): string[] {\n return datesEndingOn(now - 86_400_000, length)\n}\n\n/** Project a 30-day run rate from the latest seven complete UTC calendar days. */\nexport function runRateInsight(records: readonly DailyTokenUsageRecord[], now = Date.now()): RunRateInsight {\n const byDate = new Map(records.map(record => [record.date, record.usage]))\n const dates = completeDaysEndingBefore(now, 7)\n const tokens = dates.reduce((sum, date) => sum + totalTokens(byDate.get(date) ?? zeroBuckets()), 0)\n const averageDailyTokens = tokens / dates.length\n return {\n observedDays: dates.length,\n averageDailyTokens,\n projectedThirtyDayTokens: Math.round(averageDailyTokens * 30),\n }\n}\n\n/** Detect an elevated latest complete UTC day using the preceding 28 days' active-day median and MAD. */\nexport function dailyAnomalyInsight(\n records: readonly DailyTokenUsageRecord[],\n now = Date.now(),\n): DailyAnomalyInsight | undefined {\n const byDate = new Map(records.map(record => [record.date, record.usage]))\n const [date] = completeDaysEndingBefore(now, 1)\n if (date === undefined) return undefined\n const tokens = totalTokens(byDate.get(date) ?? zeroBuckets())\n const baselineDates = datesEndingOn(now - 2 * 86_400_000, 28)\n const activeBaseline = baselineDates\n .map(baselineDate => totalTokens(byDate.get(baselineDate) ?? zeroBuckets()))\n .filter(value => value > 0)\n if (tokens === 0 || activeBaseline.length < 5) return undefined\n const baselineMedianTokens = median(activeBaseline)\n const baselineMadTokens = median(activeBaseline.map(value => Math.abs(value - baselineMedianTokens)))\n const robustThreshold = baselineMadTokens === 0\n ? baselineMedianTokens * 3\n : baselineMedianTokens + 3 * 1.4826 * baselineMadTokens\n const ratio = baselineMedianTokens === 0 ? 0 : tokens / baselineMedianTokens\n const excessTokens = Math.max(0, tokens - baselineMedianTokens)\n return {\n date,\n tokens,\n baselineMedianTokens,\n baselineMadTokens,\n activeBaselineDays: activeBaseline.length,\n ratio,\n excessTokens,\n status: tokens > robustThreshold ? 'elevated' : 'normal',\n }\n}\n\n/** List sessions contributing usage to one UTC day, highest usage first. */\nexport function dailyContributors(\n sessions: readonly DailySessionSource[],\n date: string,\n): DailySessionContributor[] {\n return sessions.flatMap((session) => {\n const record = session.days.find(day => day.date === date)\n if (record === undefined || totalTokens(record.usage) === 0) return []\n return [{\n id: session.id,\n title: session.title,\n usage: { ...record.usage },\n }]\n }).sort((left, right) => totalTokens(right.usage) - totalTokens(left.usage)\n || left.title.localeCompare(right.title)\n || left.id.localeCompare(right.id))\n}\n","/** Aggregate-only efficiency and attribution metrics for the Token dashboard. */\r\n\r\nimport type { ModelTokenUsageRecord, TokenUsageBuckets } from '../types.ts'\r\n\r\n/** One route's stable share of all dashboard Tokens. */\r\nexport interface RouteContribution {\r\n provider: string\r\n model: string\r\n tokens: number\r\n share: number\r\n}\r\n\r\n/** Derived efficiency signals that never inspect session content. */\r\nexport interface UsageEfficiencyInsight {\r\n assistantAttempts: number\r\n compactionAttempts: number\r\n assistantTokens: number\r\n tokensPerAssistantAttempt: number | undefined\r\n compactionsPerHundredAssistantAttempts: number | undefined\r\n compactionTokenShare: number | undefined\r\n cacheReadInputShare: number | undefined\r\n cacheWriteInputShare: number | undefined\r\n uncachedInputShare: number | undefined\r\n outputToInputRatio: number | undefined\r\n unattributedTokenShare: number\r\n topRoutes: readonly RouteContribution[]\r\n}\r\n\r\n/** Return one full disjoint-bucket total. */\r\nfunction totalTokens(usage: TokenUsageBuckets): number {\r\n return usage.uncachedInputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens\r\n}\r\n\r\n/** Return the prompt-side total across uncached and cache buckets. */\r\nfunction inputTokens(usage: TokenUsageBuckets): number {\r\n return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens\r\n}\r\n\r\n/** Identify legacy fallback rows with no source route attribution. */\r\nfunction isUnattributed(model: ModelTokenUsageRecord): boolean {\r\n return model.provider === '' && model.model === ''\r\n}\r\n\r\n/** Derive request efficiency, compaction overhead, and stable top-route shares. */\r\nexport function usageEfficiencyInsight(\r\n usage: TokenUsageBuckets,\r\n compactionUsage: TokenUsageBuckets,\r\n models: readonly ModelTokenUsageRecord[],\r\n assistantAttempts: number,\r\n compactionAttempts: number,\r\n): UsageEfficiencyInsight {\r\n const total = totalTokens(usage)\r\n const input = inputTokens(usage)\r\n const attributed = models.filter(model => !isUnattributed(model))\r\n const unattributedTokens = models\r\n .filter(isUnattributed)\r\n .reduce((sum, model) => sum + totalTokens(model.usage), 0)\r\n const assistantTokens = Math.max(0, total - totalTokens(compactionUsage))\r\n const topRoutes = attributed\r\n .map(model => ({\r\n provider: model.provider,\r\n model: model.model,\r\n tokens: totalTokens(model.usage),\r\n }))\r\n .sort((left, right) => right.tokens - left.tokens\r\n || left.provider.localeCompare(right.provider)\r\n || left.model.localeCompare(right.model))\r\n .slice(0, 3)\r\n .map(route => ({ ...route, share: total === 0 ? 0 : route.tokens / total }))\r\n return {\r\n assistantAttempts,\r\n compactionAttempts,\r\n assistantTokens,\r\n tokensPerAssistantAttempt: assistantAttempts === 0 || unattributedTokens > 0\r\n ? undefined\r\n : assistantTokens / assistantAttempts,\r\n compactionsPerHundredAssistantAttempts: assistantAttempts === 0\r\n ? undefined\r\n : compactionAttempts / assistantAttempts * 100,\r\n compactionTokenShare: total === 0 ? undefined : totalTokens(compactionUsage) / total,\r\n cacheReadInputShare: input === 0 ? undefined : usage.cacheReadTokens / input,\r\n cacheWriteInputShare: input === 0 ? undefined : usage.cacheWriteTokens / input,\r\n uncachedInputShare: input === 0 ? undefined : usage.uncachedInputTokens / input,\r\n outputToInputRatio: input === 0 ? undefined : usage.outputTokens / input,\r\n unattributedTokenShare: total === 0 ? 0 : unattributedTokens / total,\r\n topRoutes,\r\n }\r\n}\r\n","import type { TokenUsageRouteBudget } from '../budget-settings.ts'\nimport type { DailyTokenUsageRecord, ModelDailyTokenUsageRecord, TokenUsageBuckets } from '../types.ts'\nimport { periodInsight, runRateInsight } from './analytics.ts'\n\n/** Fixed warning threshold before an exact route reaches its hard Token budget. */\nexport const ROUTE_BUDGET_WARNING_RATIO = 0.8\n\n/** Actionable state for one exact provider/model rolling Token budget. */\nexport interface RouteBudgetInsight extends TokenUsageRouteBudget {\n usedTokens: number\n projectedThirtyDayTokens: number\n ratio: number\n status: 'healthy' | 'warning' | 'forecast-exceeded' | 'exceeded'\n}\n\n/** Sum all four disjoint provider Token buckets. */\nfunction totalTokens(usage: TokenUsageBuckets): number {\n return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + usage.outputTokens\n}\n\n/** Evaluate one route budget from exact date-by-model buckets. */\nexport function routeBudgetInsight(\n budget: TokenUsageRouteBudget,\n records: readonly ModelDailyTokenUsageRecord[],\n now = Date.now(),\n): RouteBudgetInsight {\n const days: DailyTokenUsageRecord[] = records\n .filter(record => record.provider === budget.provider && record.model === budget.model)\n .map(record => ({ date: record.date, usage: { ...record.usage } }))\n const usedTokens = totalTokens(periodInsight(days, 30, now).usage)\n const projectedThirtyDayTokens = runRateInsight(days, now).projectedThirtyDayTokens\n const ratio = usedTokens / budget.rolling30DayBudget\n const status = usedTokens >= budget.rolling30DayBudget\n ? 'exceeded'\n : projectedThirtyDayTokens > budget.rolling30DayBudget\n ? 'forecast-exceeded'\n : ratio >= ROUTE_BUDGET_WARNING_RATIO ? 'warning' : 'healthy'\n return {\n ...budget,\n usedTokens,\n projectedThirtyDayTokens,\n ratio,\n status,\n }\n}\n\nconst STATUS_PRIORITY: Record = {\n exceeded: 3,\n 'forecast-exceeded': 2,\n warning: 1,\n healthy: 0,\n}\n\n/** Evaluate and risk-sort every persisted exact-route budget without mutating settings. */\nexport function routeBudgetInsights(\n budgets: readonly TokenUsageRouteBudget[],\n records: readonly ModelDailyTokenUsageRecord[],\n now = Date.now(),\n): RouteBudgetInsight[] {\n return budgets\n .map(budget => routeBudgetInsight(budget, records, now))\n .sort((left, right) => STATUS_PRIORITY[right.status] - STATUS_PRIORITY[left.status]\n || right.ratio - left.ratio\n || left.provider.localeCompare(right.provider)\n || left.model.localeCompare(right.model))\n}\n","/** Built-in public API price estimates for exact model routes. */\r\n\r\nimport type {\r\n ModelTokenUsageRecord,\r\n PricedModelTokenUsageRecord,\r\n TokenUsageBuckets,\r\n TokenUsageCostSummary,\r\n TokenUsagePriceRate,\r\n} from './types.ts'\r\n\r\nconst TOKENS_PER_MILLION = 1_000_000\r\nexport const PUBLIC_PRICE_CATALOG_URL = 'https://developers.openai.com/api/docs/pricing'\r\n\r\ninterface CatalogRate extends TokenUsagePriceRate {\r\n provider: string\r\n model: string\r\n}\r\n\r\n/**\r\n * OpenAI public USD API rates per million Tokens, retrieved from the official price page on 2025-08-07.\r\n *\r\n * Cache writes use the ordinary input price because these routes publish no separate cache-write tariff.\r\n * The catalog intentionally requires an exact provider/model label match and never borrows a superficially\r\n * similar rate. A matching label does not verify the configured endpoint, reseller, contract, or invoice.\r\n */\r\nconst PUBLIC_USD_RATES: readonly CatalogRate[] = [\r\n { provider: 'openai', model: 'gpt-5', currency: 'USD', inputPerMillion: 1.25, cacheReadPerMillion: 0.125, cacheWritePerMillion: 1.25, outputPerMillion: 10, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-08-07' },\r\n { provider: 'openai', model: 'gpt-5-2025-08-07', currency: 'USD', inputPerMillion: 1.25, cacheReadPerMillion: 0.125, cacheWritePerMillion: 1.25, outputPerMillion: 10, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-08-07' },\r\n { provider: 'openai', model: 'gpt-5-mini', currency: 'USD', inputPerMillion: 0.25, cacheReadPerMillion: 0.025, cacheWritePerMillion: 0.25, outputPerMillion: 2, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-08-07' },\r\n { provider: 'openai', model: 'gpt-5-mini-2025-08-07', currency: 'USD', inputPerMillion: 0.25, cacheReadPerMillion: 0.025, cacheWritePerMillion: 0.25, outputPerMillion: 2, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-08-07' },\r\n { provider: 'openai', model: 'gpt-5-nano', currency: 'USD', inputPerMillion: 0.05, cacheReadPerMillion: 0.005, cacheWritePerMillion: 0.05, outputPerMillion: 0.4, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-08-07' },\r\n { provider: 'openai', model: 'gpt-5-nano-2025-08-07', currency: 'USD', inputPerMillion: 0.05, cacheReadPerMillion: 0.005, cacheWritePerMillion: 0.05, outputPerMillion: 0.4, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-08-07' },\r\n { provider: 'openai', model: 'gpt-4.1', currency: 'USD', inputPerMillion: 2, cacheReadPerMillion: 0.5, cacheWritePerMillion: 2, outputPerMillion: 8, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-04-14' },\r\n { provider: 'openai', model: 'gpt-4.1-mini', currency: 'USD', inputPerMillion: 0.4, cacheReadPerMillion: 0.1, cacheWritePerMillion: 0.4, outputPerMillion: 1.6, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-04-14' },\r\n { provider: 'openai', model: 'gpt-4.1-nano', currency: 'USD', inputPerMillion: 0.1, cacheReadPerMillion: 0.025, cacheWritePerMillion: 0.1, outputPerMillion: 0.4, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-04-14' },\r\n { provider: 'openai', model: 'gpt-4o', currency: 'USD', inputPerMillion: 2.5, cacheReadPerMillion: 1.25, cacheWritePerMillion: 2.5, outputPerMillion: 10, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-04-14' },\r\n { provider: 'openai', model: 'gpt-4o-mini', currency: 'USD', inputPerMillion: 0.15, cacheReadPerMillion: 0.075, cacheWritePerMillion: 0.15, outputPerMillion: 0.6, sourceUrl: PUBLIC_PRICE_CATALOG_URL, asOf: '2025-04-14' },\r\n]\r\n\r\n/** Version marker displayed with every estimate, not a live pricing feed. */\r\nexport const PUBLIC_PRICE_CATALOG_AS_OF = '2025-08-07'\r\n\r\n/** Return a detached public rate for one exact catalog label; this does not verify its billing endpoint. */\r\nexport function publicPriceFor(provider: string, model: string): TokenUsagePriceRate | undefined {\r\n const rate = PUBLIC_USD_RATES.find(entry => entry.provider === provider && entry.model === model)\r\n return rate === undefined ? undefined : {\r\n currency: rate.currency,\r\n inputPerMillion: rate.inputPerMillion,\r\n outputPerMillion: rate.outputPerMillion,\r\n cacheReadPerMillion: rate.cacheReadPerMillion,\r\n cacheWritePerMillion: rate.cacheWritePerMillion,\r\n sourceUrl: rate.sourceUrl,\r\n asOf: rate.asOf,\r\n }\r\n}\r\n\r\n/** Return complete Tokens across the four disjoint provider-reported buckets. */\r\nexport function totalTokens(usage: TokenUsageBuckets): number {\r\n return usage.uncachedInputTokens + usage.outputTokens + usage.cacheReadTokens + usage.cacheWriteTokens\r\n}\r\n\r\n/** Calculate one exact-route USD estimate from a fully published four-bucket rate. */\r\nexport function estimateCostUSD(usage: TokenUsageBuckets, rate: TokenUsagePriceRate): number {\r\n return (\r\n usage.uncachedInputTokens * rate.inputPerMillion\r\n + usage.outputTokens * rate.outputPerMillion\r\n + usage.cacheReadTokens * rate.cacheReadPerMillion\r\n + usage.cacheWriteTokens * rate.cacheWritePerMillion\r\n ) / TOKENS_PER_MILLION\r\n}\r\n\r\n/** Add built-in public USD pricing to model aggregates without pricing unknown routes. */\r\nexport function tokenUsageCostSummary(models: readonly ModelTokenUsageRecord[]): TokenUsageCostSummary {\r\n let totalCostUSD = 0\r\n let cacheReadSavingsUSD = 0\r\n let coveredTokens = 0\r\n let totalTokensCount = 0\r\n let coveredModels = 0\r\n const pricedModels: PricedModelTokenUsageRecord[] = models.map(model => {\r\n const usage = { ...model.usage }\r\n const total = totalTokens(usage)\r\n totalTokensCount += total\r\n const rate = publicPriceFor(model.provider, model.model)\r\n if (rate === undefined) {\r\n return {\r\n provider: model.provider,\r\n model: model.model,\r\n assistantRequests: model.assistantRequests,\r\n compactionRequests: model.compactionRequests,\r\n usage,\r\n }\r\n }\r\n const totalCost = estimateCostUSD(usage, rate)\r\n const cacheReadSavings = usage.cacheReadTokens * Math.max(0, rate.inputPerMillion - rate.cacheReadPerMillion) / TOKENS_PER_MILLION\r\n totalCostUSD += totalCost\r\n cacheReadSavingsUSD += cacheReadSavings\r\n coveredTokens += total\r\n if (total > 0) coveredModels += 1\r\n return {\r\n provider: model.provider,\r\n model: model.model,\r\n assistantRequests: model.assistantRequests,\r\n compactionRequests: model.compactionRequests,\r\n usage,\r\n totalCostUSD: totalCost,\r\n cacheReadSavingsUSD: cacheReadSavings,\r\n rate,\r\n }\r\n })\r\n return {\r\n currency: 'USD',\r\n totalCostUSD,\r\n cacheReadSavingsUSD,\r\n coveredTokens,\r\n totalTokens: totalTokensCount,\r\n coveredModels,\r\n totalModels: models.filter(model => totalTokens(model.usage) > 0).length,\r\n models: pricedModels,\r\n }\r\n}\r\n","/** Disable model-supplied Markdown images and raw HTML while preserving readable text and links. */\nexport function safeModelMarkdown(markdown: string): string {\n return markdown\n .replace(/!\\[([^\\]]*)\\]\\(([^)\\n]+)\\)/g, (_match, alt: string, destination: string) =>\n `[${alt.length === 0 ? 'image' : alt}](${destination})`)\n .replace(/!\\[([^\\]]*)\\]\\[([^\\]]*)\\]/g, (_match, alt: string, reference: string) =>\n `[${alt.length === 0 ? 'image' : alt}][${reference}]`)\n .replace(/!\\[([^\\]]*)\\]/g, (_match, alt: string) => alt.length === 0 ? 'image' : alt)\n // A CommonMark image always starts with `![`; entity-encode any marker left by the readable rewrites above.\n .replace(/!\\[/g, '![')\n .replace(/<(?=[A-Za-z/!?])/g, '<')\n}\n","import type {\n DailyTokenUsageRecord,\n ModelDailyTokenUsageRecord,\n ModelTokenUsageRecord,\n PricedModelTokenUsageRecord,\n TokenUsageAnalysis,\n TokenUsageBuckets,\n TokenUsageCostSummary,\n TrajectoryAnalysis,\n} from '../types.ts'\nimport { PUBLIC_PRICE_CATALOG_AS_OF, tokenUsageCostSummary } from '../pricing.ts'\nimport { safeModelMarkdown } from './report-safety.ts'\n\n/** Narrow aggregate-only source accepted by the privacy-safe export Module. */\nexport interface UsageExportSource {\n usage: TokenUsageBuckets\n compactionUsage: TokenUsageBuckets\n models: readonly ModelTokenUsageRecord[]\n days: readonly DailyTokenUsageRecord[]\n modelDays: readonly ModelDailyTokenUsageRecord[]\n dailyCoverage: 'complete' | 'partial' | 'unavailable'\n modelDailyCoverage: 'complete' | 'partial' | 'unavailable'\n}\n\n/** Versioned aggregate-only JSON document with route-by-day usage and partial public-rate pricing. */\nexport interface TokenUsageExportV3 {\n schema: 'dsh-token-usage/export-v3'\n generatedAt: string\n timezone: 'UTC'\n totals: TokenUsageBuckets\n compactionUsage: TokenUsageBuckets\n pricingCatalogAsOf: string\n pricing: TokenUsageCostSummary\n coverage: {\n daily: UsageExportSource['dailyCoverage']\n modelDaily: UsageExportSource['modelDailyCoverage']\n }\n models: PricedModelTokenUsageRecord[]\n days: DailyTokenUsageRecord[]\n modelDays: ModelDailyTokenUsageRecord[]\n}\n\n/** Browser download Adapter owned by the Client plugin registration. */\nexport interface DownloadPort {\n save(name: string, mime: string, content: string): void\n}\n\n/** Detached copy of one bucket object. */\nfunction copyBuckets(usage: TokenUsageBuckets): TokenUsageBuckets {\n return {\n uncachedInputTokens: usage.uncachedInputTokens,\n outputTokens: usage.outputTokens,\n cacheReadTokens: usage.cacheReadTokens,\n cacheWriteTokens: usage.cacheWriteTokens,\n }\n}\n\n/** Return a detached public-price estimate that never retains source aggregates. */\nfunction copiedPricing(models: readonly ModelTokenUsageRecord[]): TokenUsageCostSummary {\n const summary = tokenUsageCostSummary(models)\n return {\n ...summary,\n models: summary.models.map(model => ({\n provider: model.provider,\n model: model.model,\n assistantRequests: model.assistantRequests,\n compactionRequests: model.compactionRequests,\n usage: copyBuckets(model.usage),\n ...model.totalCostUSD === undefined ? {} : { totalCostUSD: model.totalCostUSD },\n ...model.cacheReadSavingsUSD === undefined ? {} : { cacheReadSavingsUSD: model.cacheReadSavingsUSD },\n ...model.rate === undefined ? {} : { rate: { ...model.rate } },\n })),\n }\n}\n\n/** Stable aggregate-only document that never accepts session data. */\nexport function tokenUsageExport(source: UsageExportSource, generatedAt: string): TokenUsageExportV3 {\n const pricing = copiedPricing(source.models)\n return {\n schema: 'dsh-token-usage/export-v3',\n generatedAt,\n timezone: 'UTC',\n totals: copyBuckets(source.usage),\n compactionUsage: copyBuckets(source.compactionUsage),\n pricingCatalogAsOf: PUBLIC_PRICE_CATALOG_AS_OF,\n pricing,\n coverage: {\n daily: source.dailyCoverage,\n modelDaily: source.modelDailyCoverage,\n },\n models: pricing.models\n .slice()\n .sort((left, right) => left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)),\n days: source.days\n .map(day => ({ date: day.date, usage: copyBuckets(day.usage) }))\n .sort((left, right) => left.date.localeCompare(right.date)),\n modelDays: source.modelDays\n .map(modelDay => ({\n provider: modelDay.provider,\n model: modelDay.model,\n date: modelDay.date,\n usage: copyBuckets(modelDay.usage),\n }))\n .sort((left, right) => left.date.localeCompare(right.date)\n || left.provider.localeCompare(right.provider)\n || left.model.localeCompare(right.model)),\n }\n}\n\n/** Serialize the versioned aggregate-only export document. */\nexport function tokenUsageJson(source: UsageExportSource, generatedAt: string): string {\n return `${JSON.stringify(tokenUsageExport(source, generatedAt), null, 2)}\\n`\n}\n\n/** Prevent spreadsheet applications from interpreting an untrusted cell as a formula. */\nfunction spreadsheetText(value: string): string {\n return /^[\\u0000-\\u0020]*[=+\\-@]/.test(value) ? `'${value}` : value\n}\n\n/** Escape one scalar value as a CSV cell. */\nfunction csvCell(value: number | string): string {\n const text = typeof value === 'string' ? spreadsheetText(value) : String(value)\n return /[\",\\r\\n]/.test(text) ? `\"${text.replaceAll('\"', '\"\"')}\"` : text\n}\n\n/** Encode a complete CSV table with a stable CRLF delimiter. */\nfunction csv(rows: ReadonlyArray>): string {\n return `${rows.map(row => row.map(csvCell).join(',')).join('\\r\\n')}\\r\\n`\n}\n\n/** Export the daily aggregate buckets without session identity or conversation content. */\nexport function dailyUsageCsv(source: Pick): string {\n return csv([\n ['date', 'uncachedInputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens', 'inputTokens', 'totalTokens'],\n ...source.days\n .slice()\n .sort((left, right) => left.date.localeCompare(right.date))\n .map(day => [\n day.date,\n day.usage.uncachedInputTokens,\n day.usage.outputTokens,\n day.usage.cacheReadTokens,\n day.usage.cacheWriteTokens,\n day.usage.uncachedInputTokens + day.usage.cacheReadTokens + day.usage.cacheWriteTokens,\n day.usage.uncachedInputTokens + day.usage.cacheReadTokens + day.usage.cacheWriteTokens + day.usage.outputTokens,\n ]),\n ])\n}\n\n/** Export exact route-by-day buckets only when the source proves complete coverage. */\nexport function modelDailyUsageCsv(source: Pick): string {\n if (source.modelDailyCoverage !== 'complete') {\n throw new Error('Date-by-model CSV requires complete and conserved route-day coverage.')\n }\n return csv([\n ['date', 'provider', 'model', 'uncachedInputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens', 'inputTokens', 'totalTokens'],\n ...source.modelDays\n .slice()\n .sort((left, right) => left.date.localeCompare(right.date)\n || left.provider.localeCompare(right.provider)\n || left.model.localeCompare(right.model))\n .map(modelDay => [\n modelDay.date,\n modelDay.provider,\n modelDay.model,\n modelDay.usage.uncachedInputTokens,\n modelDay.usage.outputTokens,\n modelDay.usage.cacheReadTokens,\n modelDay.usage.cacheWriteTokens,\n modelDay.usage.uncachedInputTokens + modelDay.usage.cacheReadTokens + modelDay.usage.cacheWriteTokens,\n modelDay.usage.uncachedInputTokens + modelDay.usage.cacheReadTokens + modelDay.usage.cacheWriteTokens + modelDay.usage.outputTokens,\n ]),\n ])\n}\n\n/** Export model aggregate buckets without session identity or conversation content. */\nexport function modelUsageCsv(source: Pick): string {\n const models = tokenUsageCostSummary(source.models).models\n return csv([\n ['provider', 'model', 'assistantRequests', 'compactionRequests', 'uncachedInputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens', 'inputTokens', 'totalTokens', 'estimatedCostUSD', 'cacheReadSavingsUSD', 'pricingCatalogAsOf'],\n ...models\n .slice()\n .sort((left, right) => left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model))\n .map(model => [\n model.provider,\n model.model,\n model.assistantRequests,\n model.compactionRequests,\n model.usage.uncachedInputTokens,\n model.usage.outputTokens,\n model.usage.cacheReadTokens,\n model.usage.cacheWriteTokens,\n model.usage.uncachedInputTokens + model.usage.cacheReadTokens + model.usage.cacheWriteTokens,\n model.usage.uncachedInputTokens + model.usage.cacheReadTokens + model.usage.cacheWriteTokens + model.usage.outputTokens,\n model.totalCostUSD ?? '',\n model.cacheReadSavingsUSD ?? '',\n model.rate?.asOf ?? '',\n ]),\n ])\n}\n\n/** Create a filesystem-safe UTC suffix without embedding session identity. */\nexport function analysisReportFilename(kind: 'usage' | 'trajectory', generatedAt: string): string {\n const timestamp = generatedAt.replaceAll(/[^0-9A-Za-z]+/g, '-').replaceAll(/^-|-$/g, '')\n return `dsh-${kind}-analysis-${timestamp}.md`\n}\n\n/** Serialize one aggregate analysis as a portable Markdown report. */\nexport function tokenUsageAnalysisMarkdown(analysis: TokenUsageAnalysis): string {\n const auxiliary = analysis.analysisUsage === undefined\n ? 'Unavailable'\n : String(analysis.analysisUsage.uncachedInputTokens\n + analysis.analysisUsage.outputTokens\n + analysis.analysisUsage.cacheReadTokens\n + analysis.analysisUsage.cacheWriteTokens)\n const output = analysis.analysisUsage === undefined ? 'Unavailable' : String(analysis.analysisUsage.outputTokens)\n return [\n '# DSH Token Usage Analysis',\n '',\n `- Generated: ${analysis.generatedAt}`,\n `- Model: ${analysis.model.provider}/${analysis.model.model}`,\n `- Analysis tokens: ${auxiliary}`,\n `- Model output tokens: ${output}`,\n '',\n '## Model Report',\n '',\n safeModelMarkdown(analysis.report),\n '',\n ].join('\\n')\n}\n\n/** Serialize one trajectory analysis with deterministic technical-control evidence. */\nexport function trajectoryAnalysisMarkdown(analysis: TrajectoryAnalysis): string {\n const metrics = analysis.metrics\n const auxiliary = analysis.analysisUsage === undefined\n ? 'Unavailable'\n : String(analysis.analysisUsage.uncachedInputTokens\n + analysis.analysisUsage.outputTokens\n + analysis.analysisUsage.cacheReadTokens\n + analysis.analysisUsage.cacheWriteTokens)\n const output = analysis.analysisUsage === undefined ? 'Unavailable' : String(analysis.analysisUsage.outputTokens)\n const approvalAuditRows = metrics.completeComplianceEvidenceAvailable\n ? [\n `| Approval closure | ${metrics.approvalsResolved}/${metrics.approvalsAsked} |`,\n `| Rejected/cancelled/unavailable | ${metrics.approvalsRejected + metrics.approvalsCancelled + metrics.approvalsUnavailable} |`,\n `| Unresolved/orphan approval records | ${metrics.unresolvedApprovals}/${metrics.orphanApprovalDecisions} |`,\n '| Persistent approval decisions | Not defined by ApprovalOutcome; session policy events excluded |',\n ]\n : [\n `| Approval requests | ${metrics.approvalsAsked} |`,\n `| Rejected decisions | ${metrics.approvalsRejected} |`,\n '| v3 closure/categorized outcomes/audit gaps | Unavailable in this pre-v3 report |',\n ]\n return [\n '# DSH Session Trajectory Analysis',\n '',\n `- Generated: ${analysis.generatedAt}`,\n `- Model: ${analysis.model.provider}/${analysis.model.model}`,\n `- Analysis tokens: ${auxiliary}`,\n `- Model output tokens: ${output}`,\n `- Evidence truncated: ${analysis.truncated ? 'yes' : 'no'}`,\n '',\n '## Deterministic Audit Summary',\n '',\n '| Control | Evidence |',\n '| --- | ---: |',\n ...approvalAuditRows,\n `| Orphan tool calls/results | ${metrics.orphanToolCalls}/${metrics.orphanToolResults} |`,\n `| Open turns/steps | ${metrics.openTurns}/${metrics.openSteps} |`,\n `| Accounting reconciliation | ${metrics.reconciliation.status} |`,\n '',\n '> This is a metadata-based technical-control review, not legal advice or compliance certification.',\n '',\n '## Model Report',\n '',\n safeModelMarkdown(analysis.report),\n '',\n ].join('\\n')\n}\n\n/** Save text content through browser-native download primitives. */\nexport const browserDownload: DownloadPort = {\n save(name, mime, content) {\n const url = URL.createObjectURL(new Blob([content], { type: mime }))\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = name\n anchor.click()\n URL.revokeObjectURL(url)\n },\n}\n","import { useMemo, type ReactNode } from 'react'\nimport { MarkdownText } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { safeModelMarkdown } from './report-safety.ts'\n\n/** Render untrusted model Markdown semantically without activating remote images or raw HTML. */\nexport function SafeMarkdownReport({\n report,\n className,\n copyLabel,\n copiedLabel,\n}: {\n report: string\n className?: string | undefined\n copyLabel: string\n copiedLabel: string\n}): ReactNode {\n const safeReport = useMemo(() => safeModelMarkdown(report), [report])\n const codeLabels = useMemo(() => ({ copyLabel, copiedLabel }), [copiedLabel, copyLabel])\n return
\n}\n","import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\r\nimport type { ObservableSnapshot, SessionId, SessionSummary } from '@deepseek-ai/dsh-client-runtime/client'\r\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\r\nimport type { TokenUsageProjection } from '@deepseek-ai/dsh-token-meter/client'\r\nimport type {\r\n DailyTokenUsageRecord,\r\n ModelDailyTokenUsageRecord,\r\n ModelTokenUsageRecord,\r\n PricedModelTokenUsageRecord,\r\n TokenUsageAnalysis,\r\n TokenUsageAnalysisInput,\r\n TokenUsageAnalysisModel,\r\n TokenUsageAnalysisModelSelection,\r\n TokenUsageAnalysisProgress,\r\n TokenUsageBuckets,\r\n TokenUsageRecorderProjection,\r\n TrajectoryAnalysis,\r\n} from '../types.ts'\r\nimport type { TokenUsageBudgetSnapshot } from './budget-controller.ts'\r\nimport { dailyAnomalyInsight, dailyContributors, periodInsight, runRateInsight } from './analytics.ts'\r\nimport { usageEfficiencyInsight } from './efficiency.ts'\r\nimport { routeBudgetInsights, type RouteBudgetInsight } from './route-budget.ts'\r\nimport {\r\n analysisReportFilename,\r\n dailyUsageCsv,\r\n modelDailyUsageCsv,\r\n modelUsageCsv,\r\n tokenUsageAnalysisMarkdown,\r\n tokenUsageJson,\r\n trajectoryAnalysisMarkdown,\r\n type DownloadPort,\r\n} from './export.ts'\r\nimport { PUBLIC_PRICE_CATALOG_AS_OF, PUBLIC_PRICE_CATALOG_URL, tokenUsageCostSummary } from '../pricing.ts'\r\nimport type { TokenUsageAnalysisModelCatalog } from './usage-analysis-client.ts'\r\nimport { NS } from './locales.ts'\r\nimport { SafeMarkdownReport } from './SafeMarkdownReport.tsx'\r\nimport css from './TokenUsageSection.module.css'\r\n\r\ninterface TokenUsageSectionInjected {\r\n hooks: {\r\n budget: ObservableSnapshot\r\n }\r\n setBudget(value: number): Promise\r\n setRouteBudget(provider: string, model: string, value: number): Promise\r\n download: DownloadPort\r\n saveTrajectoryAnalysis(analysis: TrajectoryAnalysis): void\r\n openSession(sessionId: SessionId): void\r\n listAnalysisModels(signal: AbortSignal): Promise\r\n analyzeTokenUsage(\r\n input: TokenUsageAnalysisInput,\r\n model: TokenUsageAnalysisModelSelection,\r\n signal: AbortSignal,\r\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\r\n ): Promise\r\n analyzeTrajectory(\r\n sessionId: string,\r\n model: TokenUsageAnalysisModelSelection,\r\n signal: AbortSignal,\r\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\r\n ): Promise\r\n}\r\n\r\n/** Full props assembled by the root-scoped Settings section renderer. */\r\nexport type TokenUsageSectionProps = PropsRuntime<'settings.section'>\r\n & PropsLocale\r\n & InjectFace\r\n\r\ninterface SessionUsageRow {\r\n id: SessionId\r\n title: string\r\n updatedAt: number\r\n assistantRequests: number\r\n compactionRequests: number\r\n compactionUsage: TokenUsageBuckets\r\n usage: TokenUsageBuckets\r\n models: readonly ModelTokenUsageRecord[]\r\n days: readonly DailyTokenUsageRecord[]\r\n modelDays: readonly ModelDailyTokenUsageRecord[]\r\n dailyUsageReliable: boolean\r\n modelDailyUsageReliable: boolean\r\n}\r\n\r\ninterface DashboardData {\r\n usage: TokenUsageBuckets\r\n assistantRequests: number\r\n compactionRequests: number\r\n compactionUsage: TokenUsageBuckets\r\n sessions: SessionUsageRow[]\r\n models: ModelTokenUsageRecord[]\r\n days: DailyTokenUsageRecord[]\r\n modelDays: ModelDailyTokenUsageRecord[]\r\n operationalDays: DailyTokenUsageRecord[]\r\n dailyCoverage: 'complete' | 'partial' | 'unavailable'\r\n modelDailyCoverage: 'complete' | 'partial' | 'unavailable'\r\n}\r\n\r\ntype InsightRange = 7 | 30 | 90\r\ntype ModelSort = 'total' | 'cost' | 'tokensPerAttempt' | 'cacheReadShare'\r\n\r\nconst SESSION_PAGE_SIZE = 50\r\n\r\nexport type TrajectoryAnalysisState =\r\n | { status: 'idle' }\r\n | { status: 'loading'; sessionId: string; title: string; progress?: TokenUsageAnalysisProgress }\r\n | { status: 'ready'; title: string; value: TrajectoryAnalysis }\r\n | { status: 'error'; sessionId: string; title: string; message: string }\r\n\r\ntype AnalysisCatalogState =\r\n | { status: 'loading' }\r\n | { status: 'ready'; value: TokenUsageAnalysisModelCatalog }\r\n | { status: 'error'; message: string }\r\n\r\ntype TokenUsageAnalysisState =\r\n | { status: 'idle' }\r\n | { status: 'loading'; progress?: TokenUsageAnalysisProgress }\r\n | { status: 'ready'; value: TokenUsageAnalysis }\r\n | { status: 'error'; message: string }\r\n\r\n/** Detached zero buckets for dashboard folds. */\r\nfunction zeroBuckets(): TokenUsageBuckets {\r\n return {\r\n uncachedInputTokens: 0,\r\n outputTokens: 0,\r\n cacheReadTokens: 0,\r\n cacheWriteTokens: 0,\r\n }\r\n}\r\n\r\n/** Add four disjoint token buckets. */\r\nfunction addBuckets(left: TokenUsageBuckets, right: TokenUsageBuckets): TokenUsageBuckets {\r\n return {\r\n uncachedInputTokens: left.uncachedInputTokens + right.uncachedInputTokens,\r\n outputTokens: left.outputTokens + right.outputTokens,\r\n cacheReadTokens: left.cacheReadTokens + right.cacheReadTokens,\r\n cacheWriteTokens: left.cacheWriteTokens + right.cacheWriteTokens,\r\n }\r\n}\r\n\r\n/** Whether two detached provider bucket sets are exactly conserved. */\r\nfunction sameBuckets(left: TokenUsageBuckets, right: TokenUsageBuckets): boolean {\r\n return left.uncachedInputTokens === right.uncachedInputTokens\r\n && left.outputTokens === right.outputTokens\r\n && left.cacheReadTokens === right.cacheReadTokens\r\n && left.cacheWriteTokens === right.cacheWriteTokens\r\n}\r\n\r\n/** Sum detached bucket records without retaining projection-owned objects. */\r\nfunction summedUsage(records: readonly { usage: TokenUsageBuckets }[]): TokenUsageBuckets {\r\n return records.reduce((sum, record) => addBuckets(sum, record.usage), zeroBuckets())\r\n}\r\n\r\n/** Compare two aggregate maps without accepting missing or extra keys. */\r\nfunction sameUsageMap(\r\n actual: ReadonlyMap,\r\n expected: ReadonlyMap,\r\n): boolean {\r\n return actual.size === expected.size\r\n && [...expected].every(([key, usage]) => {\r\n const value = actual.get(key)\r\n return value !== undefined && sameBuckets(value, usage)\r\n })\r\n}\r\n\r\n/** Verify daily buckets conserve the session-level projection total. */\r\nfunction dailyUsageConserved(recorded: TokenUsageRecorderProjection): boolean {\r\n return sameBuckets(summedUsage(recorded.days), recorded.usage)\r\n}\r\n\r\n/** Verify date-by-model buckets conserve totals across session, route, and UTC day dimensions. */\r\nfunction modelDailyUsageConserved(recorded: TokenUsageRecorderProjection): boolean {\r\n if (!sameBuckets(summedUsage(recorded.modelDays), recorded.usage)) return false\r\n const routeTotals = new Map()\r\n const dayTotals = new Map()\r\n for (const record of recorded.modelDays) {\r\n if (totalTokens(record.usage) === 0) continue\r\n const route = modelKey(record)\r\n routeTotals.set(route, addBuckets(routeTotals.get(route) ?? zeroBuckets(), record.usage))\r\n dayTotals.set(record.date, addBuckets(dayTotals.get(record.date) ?? zeroBuckets(), record.usage))\r\n }\r\n const expectedRoutes = new Map(recorded.models\r\n .filter(model => totalTokens(model.usage) > 0)\r\n .map(model => [modelKey(model), model.usage]))\r\n const expectedDays = new Map(recorded.days\r\n .filter(day => totalTokens(day.usage) > 0)\r\n .map(day => [day.date, day.usage]))\r\n return sameUsageMap(routeTotals, expectedRoutes) && sameUsageMap(dayTotals, expectedDays)\r\n}\r\n\r\n/** Stable UTC day key used by durable Host records and legacy fallbacks. */\r\nfunction dayKey(time: number): string {\r\n return new Date(time).toISOString().slice(0, 10)\r\n}\r\n\r\n/** Prompt-side total across uncached input and cache traffic. */\r\nexport function inputTokens(usage: TokenUsageBuckets): number {\r\n return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens\r\n}\r\n\r\n/** Complete request/response total without double-counting reasoning output. */\r\nexport function totalTokens(usage: TokenUsageBuckets): number {\r\n return inputTokens(usage) + usage.outputTokens\r\n}\r\n\r\n/** Locale-aware exact integer formatting. */\r\nfunction formatTokens(value: number): string {\r\n return new Intl.NumberFormat().format(value)\r\n}\r\n\r\n/** Format one public-rate estimate in USD without implying accounting precision. */\r\nfunction formatUSD(value: number): string {\r\n return new Intl.NumberFormat(undefined, {\r\n style: 'currency',\r\n currency: 'USD',\r\n minimumFractionDigits: value < 1 ? 4 : 2,\r\n maximumFractionDigits: value < 1 ? 4 : 2,\r\n }).format(value)\r\n}\r\n\r\n/** Format a ratio without implying fractional measurement precision. */\r\nfunction formatPercent(value: number): string {\r\n return `${Math.round(value * 100)}%`\r\n}\r\n\r\n/** Format partial price coverage without ever rounding an incomplete estimate to 100%. */\r\nfunction formatCoveragePercent(covered: number, total: number): string {\r\n if (total <= 0 || covered <= 0) return '0'\r\n if (covered >= total) return '100'\r\n const percent = covered / total * 100\r\n if (percent < 0.1) return '<0.1'\r\n return new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(Math.floor(percent * 10) / 10)\r\n}\r\n\r\n/** Compact a token count with a stable K/M/B suffix for dense dashboard cells. */\r\nfunction formatCompactTokens(value: number): string {\r\n const unit = [\r\n { divisor: 1_000_000_000, suffix: 'B' },\r\n { divisor: 1_000_000, suffix: 'M' },\r\n { divisor: 1_000, suffix: 'K' },\r\n ].find(candidate => value >= candidate.divisor)\r\n if (unit === undefined) return formatTokens(value)\r\n return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(value / unit.divisor)}${unit.suffix}`\r\n}\r\n\r\n/** Format deterministic tool latency for one compact metric card. */\r\nfunction formatLatency(value: number): string {\r\n if (value < 1_000) return `${Math.round(value)}ms`\r\n return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(value / 1_000)}s`\r\n}\r\n\r\n/** Whether a dashboard-only row contains usage whose model route is unavailable. */\r\nfunction isUnattributed(model: ModelTokenUsageRecord): boolean {\r\n return model.provider === '' && model.model === ''\r\n}\r\n\r\n/** Stable provider/model identity for React lists and aggregation. */\r\nfunction modelKey(model: Pick): string {\r\n return JSON.stringify([model.provider, model.model])\r\n}\r\n\r\n/** Stable provider/model/date identity for route-day aggregation. */\r\nfunction modelDayKey(model: Pick): string {\r\n return JSON.stringify([model.provider, model.model, model.date])\r\n}\r\n\r\n/** Compact route label retained in the session table. */\r\nfunction routeLabel(model: Pick): string {\r\n return `${model.provider}/${model.model}`\r\n}\r\n\r\n/** Count every provider-recorded assistant or compaction attempt for one route. */\r\nfunction recordedAttempts(model: ModelTokenUsageRecord): number {\r\n return model.assistantRequests + model.compactionRequests\r\n}\r\n\r\n/** Return the selected stable sort value for one model hotspot row. */\r\nfunction modelSortValue(model: PricedModelTokenUsageRecord, sort: ModelSort): number {\r\n switch (sort) {\r\n case 'total': return totalTokens(model.usage)\r\n case 'cost': return model.totalCostUSD ?? -1\r\n case 'tokensPerAttempt': {\r\n const attempts = recordedAttempts(model)\r\n return attempts === 0 ? -1 : totalTokens(model.usage) / attempts\r\n }\r\n case 'cacheReadShare': {\r\n const input = inputTokens(model.usage)\r\n return input === 0 ? -1 : model.usage.cacheReadTokens / input\r\n }\r\n }\r\n}\r\n\r\n/** Sort model hotspot rows deterministically without mutating cost-summary data. */\r\nfunction sortedModelHotspots(models: readonly PricedModelTokenUsageRecord[], sort: ModelSort): PricedModelTokenUsageRecord[] {\r\n return models.slice().sort((left, right) => modelSortValue(right, sort) - modelSortValue(left, sort)\r\n || totalTokens(right.usage) - totalTokens(left.usage)\r\n || left.provider.localeCompare(right.provider)\r\n || left.model.localeCompare(right.model))\r\n}\r\n\r\n/** Attribute a built-in projection fallback to an explicit dashboard remainder row. */\r\nfunction unattributedModel(usage: TokenUsageBuckets): ModelTokenUsageRecord {\r\n return {\r\n provider: '',\r\n model: '',\r\n assistantRequests: 0,\r\n compactionRequests: 0,\r\n usage: { ...usage },\r\n }\r\n}\r\n\r\n/** Built-in projection fallback for a cache created before this plugin was installed. */\r\nfunction fallbackUsage(value: TokenUsageProjection): TokenUsageBuckets {\r\n return {\r\n uncachedInputTokens: value.uncachedInputTokens,\r\n outputTokens: value.outputTokens,\r\n cacheReadTokens: value.cacheReadTokens,\r\n cacheWriteTokens: value.cacheWriteTokens,\r\n }\r\n}\r\n\r\n/** One session summary projected into a usage row, or null when it has no usage. */\r\nfunction sessionRow(summary: SessionSummary): SessionUsageRow | null {\r\n const recorded: TokenUsageRecorderProjection | undefined = summary.projectionValues?.tokenUsageRecorder\r\n const builtIn = summary.projectionValues?.tokenUsage\r\n const usage = recorded?.usage ?? (builtIn === undefined ? undefined : fallbackUsage(builtIn))\r\n const assistantRequests = recorded?.assistantRequests ?? 0\r\n const compactionRequests = recorded?.compactionRequests ?? 0\r\n if (usage === undefined || (totalTokens(usage) === 0 && assistantRequests === 0 && compactionRequests === 0)) return null\r\n const dailyUsageReliable = recorded?.days !== undefined && dailyUsageConserved(recorded)\r\n const modelDailyUsageReliable = recorded?.modelDays !== undefined\r\n && dailyUsageReliable\r\n && modelDailyUsageConserved(recorded)\r\n return {\r\n id: summary.id,\r\n title: summary.displayTitle,\r\n updatedAt: summary.updatedAt,\r\n assistantRequests,\r\n compactionRequests,\r\n compactionUsage: recorded?.compactionUsage === undefined ? zeroBuckets() : { ...recorded.compactionUsage },\r\n usage,\r\n models: recorded?.models ?? [unattributedModel(usage)],\r\n days: recorded?.days ?? [{ date: dayKey(summary.updatedAt), usage }],\r\n modelDays: recorded?.modelDays ?? [],\r\n dailyUsageReliable,\r\n modelDailyUsageReliable,\r\n }\r\n}\r\n\r\n/** Aggregate session summaries into totals and provider/model records. */\r\nexport function aggregateUsage(summaries: readonly SessionSummary[]): DashboardData {\r\n const sessions: SessionUsageRow[] = []\r\n const models = new Map()\r\n const days = new Map()\r\n const modelDays = new Map()\r\n const operationalDays = new Map()\r\n let usage = zeroBuckets()\r\n let assistantRequests = 0\r\n let compactionRequests = 0\r\n let compactionUsage = zeroBuckets()\r\n let reliableDailySessions = 0\r\n let reliableModelDailySessions = 0\r\n\r\n for (const summary of summaries) {\r\n const row = sessionRow(summary)\r\n if (row === null) continue\r\n sessions.push(row)\r\n usage = addBuckets(usage, row.usage)\r\n assistantRequests += row.assistantRequests\r\n compactionRequests += row.compactionRequests\r\n compactionUsage = addBuckets(compactionUsage, row.compactionUsage)\r\n if (row.dailyUsageReliable) reliableDailySessions += 1\r\n if (row.modelDailyUsageReliable) reliableModelDailySessions += 1\r\n for (const day of row.days) {\r\n days.set(day.date, addBuckets(days.get(day.date) ?? zeroBuckets(), day.usage))\r\n if (row.dailyUsageReliable) {\r\n operationalDays.set(day.date, addBuckets(operationalDays.get(day.date) ?? zeroBuckets(), day.usage))\r\n }\r\n }\r\n for (const modelDay of row.modelDays) {\r\n const key = modelDayKey(modelDay)\r\n const current = modelDays.get(key)\r\n modelDays.set(key, current === undefined ? {\r\n ...modelDay,\r\n usage: { ...modelDay.usage },\r\n } : {\r\n ...current,\r\n usage: addBuckets(current.usage, modelDay.usage),\r\n })\r\n }\r\n for (const model of row.models) {\r\n const key = modelKey(model)\r\n const current = models.get(key)\r\n models.set(key, current === undefined ? {\r\n ...model,\r\n usage: { ...model.usage },\r\n } : {\r\n ...current,\r\n assistantRequests: current.assistantRequests + model.assistantRequests,\r\n compactionRequests: current.compactionRequests + model.compactionRequests,\r\n usage: addBuckets(current.usage, model.usage),\r\n })\r\n }\r\n }\r\n\r\n sessions.sort((left, right) => right.updatedAt - left.updatedAt)\r\n return {\r\n usage,\r\n assistantRequests,\r\n compactionRequests,\r\n compactionUsage,\r\n sessions,\r\n models: [...models.values()].sort((left, right) =>\r\n totalTokens(right.usage) - totalTokens(left.usage)\r\n || left.provider.localeCompare(right.provider)\r\n || left.model.localeCompare(right.model)),\r\n days: [...days.entries()]\r\n .map(([date, usage]): DailyTokenUsageRecord => ({ date, usage }))\r\n .sort((left, right) => left.date.localeCompare(right.date)),\r\n modelDays: [...modelDays.values()].sort((left, right) =>\r\n left.date.localeCompare(right.date)\r\n || left.provider.localeCompare(right.provider)\r\n || left.model.localeCompare(right.model)),\r\n operationalDays: [...operationalDays.entries()]\r\n .map(([date, usage]): DailyTokenUsageRecord => ({ date, usage }))\r\n .sort((left, right) => left.date.localeCompare(right.date)),\r\n dailyCoverage: reliableDailySessions === 0\r\n ? 'unavailable'\r\n : reliableDailySessions === sessions.length ? 'complete' : 'partial',\r\n modelDailyCoverage: reliableModelDailySessions === 0\r\n ? 'unavailable'\r\n : reliableModelDailySessions === sessions.length ? 'complete' : 'partial',\r\n }\r\n}\r\n\r\n/** Return one exact route's reliable daily buckets from the aggregate route-day table. */\r\nfunction modelTrendDays(modelDays: readonly ModelDailyTokenUsageRecord[], route: string): DailyTokenUsageRecord[] {\r\n return modelDays\r\n .filter(modelDay => modelKey(modelDay) === route)\r\n .map(modelDay => ({ date: modelDay.date, usage: { ...modelDay.usage } }))\r\n}\r\n\r\n/** Return only detached aggregate buckets, route records, and UTC dates for AI usage analysis. */\r\nexport function usageAnalysisInput(data: Pick): TokenUsageAnalysisInput {\r\n return {\r\n usage: { ...data.usage },\r\n assistantRequests: data.assistantRequests,\r\n compactionRequests: data.compactionRequests,\r\n compactionUsage: { ...data.compactionUsage },\r\n models: data.models.map(model => ({\r\n provider: model.provider,\r\n model: model.model,\r\n assistantRequests: model.assistantRequests,\r\n compactionRequests: model.compactionRequests,\r\n usage: { ...model.usage },\r\n })),\r\n days: data.dailyCoverage === 'complete'\r\n ? data.operationalDays.map(day => ({ date: day.date, usage: { ...day.usage } }))\r\n : [],\r\n }\r\n}\r\n\r\n/** Render a summary metric card with exact token counts available on hover. */\r\nfunction Metric({ label, value }: { label: string; value: number | string }): ReactNode {\r\n const display = typeof value === 'number' ? formatCompactTokens(value) : value\r\n const exact = typeof value === 'number' ? formatTokens(value) : undefined\r\n return (\r\n
\r\n {label}\r\n {display}\r\n
\r\n )\r\n}\r\n\r\n/** Group related trajectory facts without promoting every number to a large card. */\r\nfunction AnalysisSummaryGroup({ title, rows }: {\r\n title: string\r\n rows: readonly { label: string; value: string; title?: string | undefined }[]\r\n}): ReactNode {\r\n return
\r\n

{title}

\r\n
{rows.map(row =>
\r\n
{row.label}
\r\n
{row.value}
\r\n
)}
\r\n
\r\n}\r\n\r\n/** Render a compact table count with an exact-count tooltip. */\r\nfunction TokenValue({ value }: { value: number }): ReactNode {\r\n return {formatCompactTokens(value)}\r\n}\r\n\r\ninterface ActivityDay {\r\n date: string\r\n usage: TokenUsageBuckets\r\n tokens: number\r\n level: 0 | 1 | 2 | 3 | 4\r\n future: boolean\r\n}\r\n\r\n/** Build exactly 30 Monday-first calendar weeks, including blank future days this week. */\r\nfunction activityCalendar(days: readonly DailyTokenUsageRecord[], now = Date.now()): ActivityDay[] {\r\n const byDate = new Map(days.map(day => [day.date, day.usage]))\r\n const today = dayKey(now)\r\n const end = new Date(`${today}T00:00:00.000Z`)\r\n const start = new Date(end)\r\n start.setUTCDate(start.getUTCDate() - ((start.getUTCDay() + 6) % 7) - 29 * 7)\r\n\r\n const dates: string[] = []\r\n for (const cursor = new Date(start); dates.length < 30 * 7; cursor.setUTCDate(cursor.getUTCDate() + 1)) {\r\n dates.push(dayKey(cursor.getTime()))\r\n }\r\n const maximum = Math.max(0, ...dates\r\n .filter(date => date <= today)\r\n .map(date => totalTokens(byDate.get(date) ?? zeroBuckets())))\r\n return dates.map((date) => {\r\n const future = date > today\r\n const usage = byDate.get(date) ?? zeroBuckets()\r\n const tokens = future ? 0 : totalTokens(usage)\r\n const level = tokens === 0 || maximum === 0 ? 0 : Math.ceil(tokens / maximum * 4) as ActivityDay['level']\r\n return { date, usage, tokens, level, future }\r\n })\r\n}\r\n\r\n/** Render a GitHub-style calendar heatmap of daily Token activity. */\r\nfunction ActivityHeatmap({\r\n days,\r\n selectedDate,\r\n onSelectDate,\r\n t,\r\n}: {\r\n days: readonly DailyTokenUsageRecord[]\r\n selectedDate: string | undefined\r\n onSelectDate(date: string): void\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const calendar = useMemo(() => activityCalendar(days), [days])\r\n return (\r\n
\r\n
\r\n
\r\n

{t('activity')}

\r\n

{t('activityIntro')}

\r\n
\r\n
\r\n {t('less')}\r\n {[0, 1, 2, 3, 4].map(level => )}\r\n {t('more')}\r\n
\r\n
\r\n
\r\n {calendar.map((day) => {\r\n const details = day.future ? undefined : t('activityTooltip', {\r\n date: day.date,\r\n total: formatTokens(day.tokens),\r\n input: formatTokens(inputTokens(day.usage)),\r\n output: formatTokens(day.usage.outputTokens),\r\n cacheRead: formatTokens(day.usage.cacheReadTokens),\r\n cacheWrite: formatTokens(day.usage.cacheWriteTokens),\r\n })\r\n return (\r\n { onSelectDate(day.date) }}\r\n />\r\n )\r\n })}\r\n
\r\n
\r\n )\r\n}\r\n\r\n/** Render a selected day's exact totals and contributing sessions. */\r\nfunction DayDrilldown({\r\n day,\r\n sessions,\r\n t,\r\n onClose,\r\n}: {\r\n day: DailyTokenUsageRecord\r\n sessions: readonly SessionUsageRow[]\r\n t: TokenUsageSectionProps['t']\r\n onClose(): void\r\n}): ReactNode {\r\n const contributors = useMemo(() => dailyContributors(sessions, day.date), [day.date, sessions])\r\n return (\r\n
\r\n
\r\n
\r\n

{t('dayDetails', { date: day.date })}

\r\n

{t('dayDetailsIntro')}

\r\n
\r\n \r\n
\r\n
\r\n \r\n \r\n \r\n \r\n
\r\n
\r\n {t('contributors', { count: contributors.length })}\r\n {contributors.length === 0 ?

{t('noContributors')}

: (\r\n
    \r\n {contributors.slice(0, 5).map(contributor => (\r\n
  1. \r\n {contributor.title}\r\n \r\n
  2. \r\n ))}\r\n
\r\n )}\r\n
\r\n
\r\n )\r\n}\r\n\r\n/** Render period-aware trend and activity summaries from daily records. */\r\nfunction PeriodInsights({\r\n days,\r\n range,\r\n models,\r\n selectedModel,\r\n modelDailyCoverage,\r\n onRangeChange,\r\n onModelChange,\r\n t,\r\n}: {\r\n days: readonly DailyTokenUsageRecord[]\r\n range: InsightRange\r\n models: readonly ModelTokenUsageRecord[]\r\n selectedModel: string\r\n modelDailyCoverage: DashboardData['modelDailyCoverage']\r\n onRangeChange(range: InsightRange): void\r\n onModelChange(model: string): void\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const insight = useMemo(() => periodInsight(days, range), [days, range])\r\n const current = totalTokens(insight.usage)\r\n const previous = totalTokens(insight.previousUsage)\r\n const delta = previous === 0 ? undefined : Math.round((current - previous) / previous * 100)\r\n const peak = insight.peak\r\n const selectedRoute = models.find(model => modelKey(model) === selectedModel)\r\n let modelCoverageNote: ReactNode = null\r\n if (modelDailyCoverage !== 'complete') {\r\n modelCoverageNote =

{t(modelDailyCoverage === 'partial'\r\n ? 'modelDailyCoveragePartial'\r\n : 'modelDailyCoverageUnavailable')}

\r\n } else if (selectedRoute !== undefined) {\r\n modelCoverageNote =

{t('trendModelScope', { route: routeLabel(selectedRoute) })}

\r\n }\r\n return (\r\n
\r\n
\r\n
\r\n

{t('trend')}

\r\n

{t('trendIntro')}

\r\n
\r\n
\r\n \r\n
\r\n {([7, 30, 90] as const).map(value => (\r\n { onRangeChange(value) }}\r\n >{t('rangeDays', { count: value })}\r\n ))}\r\n
\r\n
\r\n
\r\n
\r\n \r\n 0 ? '+' : ''}${delta}%`} />\r\n \r\n \r\n
\r\n {peak === undefined ? null :

{t('peakDayNote', { date: peak.date, total: formatTokens(totalTokens(peak.usage)) })}

}\r\n {modelCoverageNote}\r\n
\r\n )\r\n}\r\n\r\n/** Render persistent global and exact-route rolling Token budgets. */\r\nfunction BudgetPanel({\r\n operationalDays,\r\n dailyCoverage,\r\n models,\r\n modelDays,\r\n modelDailyCoverage,\r\n snapshot,\r\n setBudget,\r\n setRouteBudget,\r\n t,\r\n}: {\r\n operationalDays: readonly DailyTokenUsageRecord[]\r\n dailyCoverage: DashboardData['dailyCoverage']\r\n models: readonly ModelTokenUsageRecord[]\r\n modelDays: readonly ModelDailyTokenUsageRecord[]\r\n modelDailyCoverage: DashboardData['modelDailyCoverage']\r\n snapshot: TokenUsageBudgetSnapshot\r\n setBudget(value: number): Promise\r\n setRouteBudget(provider: string, model: string, value: number): Promise\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const insight = useMemo(() => periodInsight(operationalDays, 30), [operationalDays])\r\n const runRate = useMemo(() => runRateInsight(operationalDays), [operationalDays])\r\n const runRateAvailable = dailyCoverage === 'complete'\r\n const used = totalTokens(insight.usage)\r\n const budget = snapshot.budget\r\n const enabled = budget > 0\r\n const durableValue = enabled ? String(budget) : ''\r\n const [draft, setDraft] = useState(durableValue)\r\n const [routeSelection, setRouteSelection] = useState('')\r\n const [routeDraft, setRouteDraft] = useState('')\r\n const editGeneration = useRef(0)\r\n const dirtyDraft = useRef(false)\r\n const ratio = enabled ? used / budget : 0\r\n const configurableModels = useMemo(\r\n () => models.filter(model => !isUnattributed(model) && totalTokens(model.usage) > 0),\r\n [models],\r\n )\r\n const persistedRouteBudgets = snapshot.routeBudgets ?? []\r\n const routeInsights = useMemo(\r\n () => modelDailyCoverage === 'complete' ? routeBudgetInsights(persistedRouteBudgets, modelDays) : [],\r\n [modelDailyCoverage, modelDays, persistedRouteBudgets],\r\n )\r\n const routeInsightByKey = useMemo(\r\n () => new Map(routeInsights.map(route => [modelKey(route), route])),\r\n [routeInsights],\r\n )\r\n const routeRows = persistedRouteBudgets\r\n .map(route => ({ route, insight: routeInsightByKey.get(modelKey(route)) }))\r\n .sort((left, right) => {\r\n const insightOrder = routeInsights.findIndex(insight => modelKey(insight) === modelKey(left.route))\r\n - routeInsights.findIndex(insight => modelKey(insight) === modelKey(right.route))\r\n return modelDailyCoverage === 'complete' && insightOrder !== 0\r\n ? insightOrder\r\n : left.route.provider.localeCompare(right.route.provider) || left.route.model.localeCompare(right.route.model)\r\n })\r\n useEffect(() => {\r\n if (!dirtyDraft.current) setDraft(durableValue)\r\n }, [durableValue, snapshot.status])\r\n const save = (value: string): void => {\r\n const next = value.trim() === '' ? 0 : Number(value)\r\n if (!Number.isSafeInteger(next) || next < 0) {\r\n dirtyDraft.current = false\r\n setDraft(durableValue)\r\n return\r\n }\r\n const generation = editGeneration.current + 1\r\n editGeneration.current = generation\r\n void setBudget(next).then(\r\n (saved) => {\r\n if (editGeneration.current !== generation) return\r\n dirtyDraft.current = false\r\n setDraft(saved > 0 ? String(saved) : '')\r\n },\r\n () => {\r\n if (editGeneration.current !== generation) return\r\n dirtyDraft.current = false\r\n setDraft(durableValue)\r\n },\r\n )\r\n }\r\n const saveRoute = (): void => {\r\n const selected = configurableModels.find(model => modelKey(model) === routeSelection)\r\n const next = Number(routeDraft)\r\n if (selected === undefined || !Number.isSafeInteger(next) || next <= 0) return\r\n void setRouteBudget(selected.provider, selected.model, next).then(() => {\r\n setRouteDraft('')\r\n })\r\n }\r\n return (\r\n
\r\n
\r\n
\r\n

{t('budget')}

\r\n

{t('budgetIntro')}

\r\n
\r\n \r\n
\r\n

{runRateAvailable ? t('budgetRunRate', {\r\n average: formatCompactTokens(Math.round(runRate.averageDailyTokens)),\r\n projected: formatCompactTokens(runRate.projectedThirtyDayTokens),\r\n }) : t(dailyCoverage === 'partial' ? 'dailyCoveragePartial' : 'dailyCoverageUnavailable')}

\r\n {snapshot.status !== 'ready' ?

{t('budgetUnavailable')}

\r\n : !enabled ?

{t('budgetDisabled')}

\r\n : !runRateAvailable ? null\r\n : <>\r\n
\r\n \r\n {t('budgetProgress', { used: formatCompactTokens(used), budget: formatCompactTokens(budget), percent: Math.round(ratio * 100) })}\r\n
\r\n {ratio >= 1 ?

{t('budgetExceeded', { excess: formatCompactTokens(Math.max(0, used - budget)) })}

: null}\r\n {runRateAvailable && ratio < 1 && runRate.projectedThirtyDayTokens > budget ?

{t('budgetForecastExceeded', {\r\n projected: formatCompactTokens(runRate.projectedThirtyDayTokens),\r\n budget: formatCompactTokens(budget),\r\n })}

: null}\r\n }\r\n
\r\n
\r\n
\r\n

{t('routeBudgets')}

\r\n

{t('routeBudgetsIntro')}

\r\n
\r\n
\r\n \r\n \r\n {t('routeBudgetSave')}\r\n
\r\n
\r\n {modelDailyCoverage === 'complete' ? null :

{t(modelDailyCoverage === 'partial'\r\n ? 'routeBudgetCoveragePartial'\r\n : 'routeBudgetCoverageUnavailable')}

}\r\n {persistedRouteBudgets.length === 0 ?

{t('routeBudgetsEmpty')}

:
\r\n {routeRows.map(({ route, insight }) =>
\r\n
\r\n {routeLabel(route)}\r\n {insight === undefined ? t('routeBudgetUnavailable') : t(`routeBudgetStatus_${insight.status}`)}\r\n
\r\n {insight === undefined ?
:
\r\n
\r\n \r\n {t('routeBudgetProgress', {\r\n used: formatCompactTokens(insight.usedTokens),\r\n budget: formatCompactTokens(insight.rolling30DayBudget),\r\n percent: Math.round(insight.ratio * 100),\r\n })}\r\n
\r\n

{t('routeBudgetForecast', { projected: formatCompactTokens(insight.projectedThirtyDayTokens) })}

\r\n
}\r\n { void setRouteBudget(route.provider, route.model, 0) }}\r\n >{t('routeBudgetRemove')}\r\n
)}\r\n
}\r\n
\r\n
\r\n )\r\n}\r\n\r\n/** Render aggregate request efficiency, compaction overhead, and route concentration. */\r\nfunction EfficiencyPanel({\r\n usage,\r\n compactionUsage,\r\n models,\r\n assistantAttempts,\r\n compactionAttempts,\r\n t,\r\n}: {\r\n usage: TokenUsageBuckets\r\n compactionUsage: TokenUsageBuckets\r\n models: readonly ModelTokenUsageRecord[]\r\n assistantAttempts: number\r\n compactionAttempts: number\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const insight = useMemo(\r\n () => usageEfficiencyInsight(usage, compactionUsage, models, assistantAttempts, compactionAttempts),\r\n [usage, compactionUsage, models, assistantAttempts, compactionAttempts],\r\n )\r\n const top = insight.topRoutes[0]\r\n const topThreeShare = insight.topRoutes.reduce((sum, route) => sum + route.share, 0)\r\n return (\r\n
\r\n
\r\n
\r\n

{t('efficiency')}

\r\n

{t('efficiencyIntro')}

\r\n
\r\n
\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n {top === undefined ?

{t('noRouteAttribution')}

\r\n :

{t('routeConcentration', {\r\n route: `${top.provider}/${top.model}`,\r\n topOne: formatPercent(top.share),\r\n topThree: formatPercent(topThreeShare),\r\n })}

}\r\n {insight.unattributedTokenShare > 0 ?

{t('unattributedShare', { share: formatPercent(insight.unattributedTokenShare) })}

: null}\r\n
\r\n )\r\n}\r\n\r\n/** Render complete-day burn rate and robust recent spike signals. */\r\nfunction OperationsPanel({\r\n days,\r\n dailyCoverage,\r\n onSelectDate,\r\n t,\r\n}: {\r\n days: readonly DailyTokenUsageRecord[]\r\n dailyCoverage: DashboardData['dailyCoverage']\r\n onSelectDate(date: string): void\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const runRate = useMemo(() => runRateInsight(days), [days])\r\n const anomaly = useMemo(\r\n () => dailyCoverage === 'complete' ? dailyAnomalyInsight(days) : undefined,\r\n [days, dailyCoverage],\r\n )\r\n const dailyCoverageAvailable = dailyCoverage === 'complete'\r\n return (\r\n
\r\n
\r\n
\r\n

{t('usageSignals')}

\r\n

{t('usageSignalsIntro')}

\r\n
\r\n
\r\n
\r\n \r\n \r\n \r\n \r\n
\r\n {!dailyCoverageAvailable ?

{t(dailyCoverage === 'partial' ? 'dailyCoveragePartial' : 'dailyCoverageUnavailable')}

\r\n : anomaly === undefined ?

{t('anomalyInsufficient')}

\r\n : anomaly.status === 'normal' ?

{t('anomalyNormal', {\r\n date: anomaly.date,\r\n baseline: formatCompactTokens(anomaly.baselineMedianTokens),\r\n active: anomaly.activeBaselineDays,\r\n })}

\r\n :
\r\n

{t('anomalyElevated', {\r\n date: anomaly.date,\r\n tokens: formatCompactTokens(anomaly.tokens),\r\n baseline: formatCompactTokens(anomaly.baselineMedianTokens),\r\n ratio: new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(anomaly.ratio),\r\n excess: formatCompactTokens(anomaly.excessTokens),\r\n active: anomaly.activeBaselineDays,\r\n })}

\r\n \r\n
}\r\n
\r\n )\r\n}\r\n\r\n/** Render export controls that only receive aggregate, privacy-safe dashboard data. */\r\nfunction ExportControls({\r\n data,\r\n download,\r\n t,\r\n}: {\r\n data: DashboardData\r\n download: DownloadPort\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const save = (kind: 'json' | 'daily' | 'models' | 'modelDaily'): void => {\r\n const generatedAt = new Date().toISOString()\r\n const date = generatedAt.slice(0, 10)\r\n switch (kind) {\r\n case 'json':\r\n download.save(`dsh-token-usage-${date}.json`, 'application/json;charset=utf-8', tokenUsageJson(data, generatedAt))\r\n return\r\n case 'daily':\r\n download.save(`dsh-token-usage-daily-${date}.csv`, 'text/csv;charset=utf-8', dailyUsageCsv(data))\r\n return\r\n case 'models':\r\n download.save(`dsh-token-usage-models-${date}.csv`, 'text/csv;charset=utf-8', modelUsageCsv(data))\r\n return\r\n case 'modelDaily':\r\n download.save(`dsh-token-usage-model-daily-${date}.csv`, 'text/csv;charset=utf-8', modelDailyUsageCsv(data))\r\n }\r\n }\r\n return (\r\n
\r\n {t('export')}\r\n \r\n \r\n \r\n { save('modelDaily') }}\r\n >{t('exportModelDaily')}\r\n {data.modelDailyCoverage === 'complete' ? null : {t('exportModelDailyUnavailable')}}\r\n
\r\n )\r\n}\r\n\r\n/** Encode one provider/model route for the native selector without displaying an opaque id. */\r\nfunction analysisModelKey(model: TokenUsageAnalysisModelSelection): string {\r\n return `${model.provider}\\u0000${model.model}`\r\n}\r\n\r\n/** Render one accessible indeterminate/stream-aware analysis progress surface. */\r\nfunction AnalysisLoading({\r\n title,\r\n message,\r\n progress,\r\n t,\r\n}: {\r\n title: string\r\n message: string\r\n progress?: TokenUsageAnalysisProgress | undefined\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n const stage = progress === undefined\r\n ? t('analysisProgressPreparing')\r\n : t(progress.phase === 'preparing'\r\n ? 'analysisProgressPreparing'\r\n : progress.phase === 'generating' ? 'analysisProgressGenerating' : 'analysisProgressFinalizing')\r\n const output = progress === undefined || progress.maximumOutputTokens === 0\r\n ? t('analysisProgressWaiting')\r\n : progress.exactOutputTokens === undefined\r\n ? t('analysisProgressEstimated', {\r\n count: formatTokens(progress.estimatedOutputTokens),\r\n maximum: formatTokens(progress.maximumOutputTokens),\r\n })\r\n : t('analysisProgressExact', {\r\n count: formatTokens(progress.exactOutputTokens),\r\n maximum: formatTokens(progress.maximumOutputTokens),\r\n })\r\n const value = progress?.exactOutputTokens ?? progress?.estimatedOutputTokens\r\n const maximum = progress?.maximumOutputTokens ?? 0\r\n return
\r\n
\r\n
\r\n

{title}

\r\n

{message}

\r\n
\r\n {stage}\r\n {output}\r\n {progress === undefined ? null : <>\r\n {t('analysisProgressActivity', {\r\n chunks: formatTokens(progress.chunks),\r\n characters: formatTokens(progress.outputCharacters),\r\n })}\r\n {t('analysisProgressElapsed', { seconds: Math.max(1, Math.round(progress.elapsedMs / 1_000)) })}\r\n }\r\n
\r\n {maximum > 0 && value !== undefined\r\n ? \r\n : }\r\n
\r\n
\r\n}\r\n\r\n/** Render a manual integrated-model picker and one aggregate-only Token optimization report. */\r\nfunction UsageAnalysisPanel({\r\n catalog,\r\n selectedModel,\r\n state,\r\n onSelectModel,\r\n onRefreshCatalog,\r\n onAnalyze,\r\n download,\r\n t,\r\n}: {\r\n catalog: AnalysisCatalogState\r\n selectedModel: TokenUsageAnalysisModelSelection | undefined\r\n state: TokenUsageAnalysisState\r\n onSelectModel(model: TokenUsageAnalysisModelSelection): void\r\n onRefreshCatalog(): void\r\n onAnalyze(): void\r\n download: DownloadPort\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n if (catalog.status === 'loading') {\r\n return

{t('usageAnalysis')}

{t('analysisModelsLoading')}

\r\n }\r\n if (catalog.status === 'error') {\r\n return
\r\n

{t('usageAnalysis')}

\r\n

{t('analysisModelsFailed', { message: catalog.message })}

\r\n \r\n
\r\n }\r\n const catalogFailures = catalog.value.failures ?? []\r\n if (catalog.value.models.length === 0 || selectedModel === undefined) {\r\n return
\r\n

{t('usageAnalysis')}

\r\n

{t('analysisModelsUnavailable')}

\r\n {catalogFailures.length === 0 ? null :

{t('analysisModelsAllFailed', {\r\n providers: catalogFailures.map(failure => failure.providerName).join(', '),\r\n })}

}\r\n \r\n
\r\n }\r\n const report = state.status === 'ready' ? state.value : undefined\r\n const analysisUsage = report?.analysisUsage\r\n return (\r\n
\r\n
\r\n
\r\n

{t('usageAnalysis')}

\r\n

{t('usageAnalysisIntro')}

\r\n
\r\n \r\n
\r\n \r\n

{t('usageAnalysisPrivacy')}

\r\n

{t('analysisModelScope')}

\r\n {catalogFailures.length === 0 ? null :

{t('analysisModelsPartial', {\r\n providers: catalogFailures.map(failure => failure.providerName).join(', '),\r\n })}

}\r\n {state.status === 'loading' ? t('usageAnalyzing') : t('analyzeUsage')}\r\n {state.status === 'loading' ? : null}\r\n {state.status === 'error' ?

{t('usageAnalysisFailed', { message: state.message })}

: null}\r\n {report === undefined ? null : <>\r\n
\r\n
\r\n

{t('usageAnalysisReport')}

\r\n

{t('analysisMeta', {\r\n provider: report.model.provider,\r\n model: report.model.model,\r\n time: new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(report.generatedAt)),\r\n })}

\r\n
\r\n
\r\n {analysisUsage === undefined ? null : {t('analysisCostDetailed', {\r\n total: formatTokens(totalTokens(analysisUsage)),\r\n output: formatTokens(analysisUsage.outputTokens),\r\n })}}\r\n \r\n
\r\n
\r\n \r\n }\r\n
\r\n )\r\n}\r\n\r\n/** Render one ephemeral model-generated review and its deterministic measurements. */\r\nexport function TrajectoryAnalysisPanel({ state, download, t }: {\r\n state: TrajectoryAnalysisState\r\n download: DownloadPort\r\n t: TokenUsageSectionProps['t']\r\n}): ReactNode {\r\n if (state.status === 'idle') {\r\n return

{t('trajectoryAnalysis')}

{t('trajectoryAnalysisIntro')}

\r\n }\r\n if (state.status === 'loading') {\r\n return \r\n }\r\n if (state.status === 'error') {\r\n return

{t('trajectoryAnalysis')}

{t('analysisFailed', { message: state.message })}

\r\n }\r\n const analysis = state.value\r\n const metrics = analysis.metrics\r\n const analysisUsage = analysis.analysisUsage\r\n const largestSpan = metrics.largestSpanId === undefined\r\n ? undefined\r\n : metrics.spans.find(span => span.id === metrics.largestSpanId)\r\n const reconciliationDelta = Object.values(metrics.reconciliation.delta)\r\n .reduce((total, value) => total + Math.abs(value), 0)\r\n const deniedApprovals = metrics.approvalsRejected + metrics.approvalsCancelled + metrics.approvalsUnavailable\r\n const approvalGaps = metrics.unresolvedApprovals + metrics.orphanApprovalDecisions\r\n return (\r\n
\r\n
\r\n
\r\n

{t('analysisFor', { title: state.title })}

\r\n

{t('analysisMeta', {\r\n provider: analysis.model.provider,\r\n model: analysis.model.model,\r\n time: new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(analysis.generatedAt)),\r\n })}

\r\n
\r\n
\r\n {analysisUsage === undefined ? null : {t('analysisCostDetailed', {\r\n total: formatTokens(totalTokens(analysisUsage)),\r\n output: formatTokens(analysisUsage.outputTokens),\r\n })}}\r\n \r\n
\r\n
\r\n
\r\n \r\n \r\n \r\n \r\n
\r\n {analysis.truncated ?

{t('analysisTruncated')}

: null}\r\n \r\n

{t('analysisPrivacy')}

\r\n
\r\n )\r\n}\r\n\r\n/** Render durable Token usage across all listed sessions. */\r\nexport function TokenUsageSection({\r\n close,\r\n useSessions,\r\n useBudget,\r\n setBudget,\r\n setRouteBudget,\r\n download,\r\n saveTrajectoryAnalysis,\r\n openSession,\r\n listAnalysisModels,\r\n analyzeTokenUsage,\r\n analyzeTrajectory,\r\n t,\r\n}: TokenUsageSectionProps): ReactNode {\r\n const phase = useSessions(state => state.phase)\r\n const ids = useSessions(state => state.ids)\r\n const byId = useSessions(state => state.byId)\r\n const budget = useBudget(snapshot => snapshot)\r\n const [query, setQuery] = useState('')\r\n const [modelSort, setModelSort] = useState('total')\r\n const [sessionLimit, setSessionLimit] = useState(SESSION_PAGE_SIZE)\r\n const [range, setRange] = useState(30)\r\n const [trendModel, setTrendModel] = useState('')\r\n const [selectedDate, setSelectedDate] = useState()\r\n const [operationalDrilldown, setOperationalDrilldown] = useState(false)\r\n const [sessionOpenError, setSessionOpenError] = useState()\r\n const [analysis, setAnalysis] = useState({ status: 'idle' })\r\n const [analysisCatalog, setAnalysisCatalog] = useState({ status: 'loading' })\r\n const [selectedAnalysisModel, setSelectedAnalysisModel] = useState()\r\n const [usageReport, setUsageReport] = useState({ status: 'idle' })\r\n const trajectoryController = useRef()\r\n const usageController = useRef()\r\n const catalogController = useRef()\r\n useEffect(() => () => {\r\n trajectoryController.current?.abort()\r\n usageController.current?.abort()\r\n catalogController.current?.abort()\r\n }, [])\r\n const refreshAnalysisModels = useCallback((): void => {\r\n catalogController.current?.abort()\r\n const controller = new AbortController()\r\n catalogController.current = controller\r\n setAnalysisCatalog({ status: 'loading' })\r\n void listAnalysisModels(controller.signal).then((catalog) => {\r\n if (catalogController.current !== controller || controller.signal.aborted) return\r\n setAnalysisCatalog({ status: 'ready', value: catalog })\r\n setSelectedAnalysisModel(current => current !== undefined\r\n && catalog.models.some(model => model.provider === current.provider && model.model === current.model)\r\n ? current\r\n : catalog.default ?? catalog.models[0])\r\n }, (error: unknown) => {\r\n if (catalogController.current === controller && !controller.signal.aborted) {\r\n setAnalysisCatalog({ status: 'error', message: error instanceof Error ? error.message : String(error) })\r\n }\r\n })\r\n }, [listAnalysisModels])\r\n useEffect(() => {\r\n refreshAnalysisModels()\r\n }, [refreshAnalysisModels])\r\n\r\n const runAnalysis = (row: SessionUsageRow): void => {\r\n if (selectedAnalysisModel === undefined) return\r\n trajectoryController.current?.abort()\r\n const controller = new AbortController()\r\n trajectoryController.current = controller\r\n setAnalysis({ status: 'loading', sessionId: row.id, title: row.title })\r\n void analyzeTrajectory(row.id, selectedAnalysisModel, controller.signal, (progress) => {\r\n if (trajectoryController.current !== controller || controller.signal.aborted) return\r\n setAnalysis(current => current.status === 'loading' && current.sessionId === row.id\r\n ? { ...current, progress }\r\n : current)\r\n }).then((value) => {\r\n if (trajectoryController.current === controller && !controller.signal.aborted) {\r\n saveTrajectoryAnalysis(value)\r\n setAnalysis({ status: 'ready', title: row.title, value })\r\n }\r\n }, (error: unknown) => {\r\n if (trajectoryController.current === controller && !controller.signal.aborted) {\r\n setAnalysis({\r\n status: 'error',\r\n sessionId: row.id,\r\n title: row.title,\r\n message: error instanceof Error ? error.message : String(error),\r\n })\r\n }\r\n })\r\n }\r\n\r\n const openUsageSession = (row: SessionUsageRow): void => {\r\n try {\r\n openSession(row.id)\r\n close()\r\n } catch (error) {\r\n setSessionOpenError(error instanceof Error ? error.message : String(error))\r\n }\r\n }\r\n\r\n const data = useMemo(\r\n () => aggregateUsage(ids.map(id => byId[id]).filter((value): value is SessionSummary => value !== undefined)),\r\n [byId, ids],\r\n )\r\n const trendModels = useMemo(\r\n () => data.models.filter(model => !isUnattributed(model) && totalTokens(model.usage) > 0),\r\n [data.models],\r\n )\r\n const selectedTrendModel = data.modelDailyCoverage === 'complete'\r\n && trendModels.some(model => modelKey(model) === trendModel)\r\n ? trendModel\r\n : ''\r\n const trendDays = useMemo(\r\n () => selectedTrendModel === '' ? data.days : modelTrendDays(data.modelDays, selectedTrendModel),\r\n [data.days, data.modelDays, selectedTrendModel],\r\n )\r\n const runUsageAnalysis = (): void => {\r\n if (selectedAnalysisModel === undefined) return\r\n usageController.current?.abort()\r\n const controller = new AbortController()\r\n usageController.current = controller\r\n setUsageReport({ status: 'loading' })\r\n void analyzeTokenUsage(usageAnalysisInput(data), selectedAnalysisModel, controller.signal, (progress) => {\r\n if (usageController.current !== controller || controller.signal.aborted) return\r\n setUsageReport(current => current.status === 'loading' ? { ...current, progress } : current)\r\n }).then((value) => {\r\n if (usageController.current === controller && !controller.signal.aborted) {\r\n setUsageReport({ status: 'ready', value })\r\n }\r\n }, (error: unknown) => {\r\n if (usageController.current === controller && !controller.signal.aborted) {\r\n setUsageReport({ status: 'error', message: error instanceof Error ? error.message : String(error) })\r\n }\r\n })\r\n }\r\n\r\n const costSummary = useMemo(() => tokenUsageCostSummary(data.models), [data.models])\r\n const priceCoverage = formatCoveragePercent(costSummary.coveredTokens, costSummary.totalTokens)\r\n const sortedModels = useMemo(\r\n () => sortedModelHotspots(costSummary.models, modelSort),\r\n [costSummary.models, modelSort],\r\n )\r\n\r\n const normalizedQuery = query.trim().toLocaleLowerCase()\r\n const filteredSessions = useMemo(() => data.sessions.filter(row => {\r\n if (normalizedQuery.length === 0) return true\r\n return row.title.toLocaleLowerCase().includes(normalizedQuery)\r\n || row.id.toLocaleLowerCase().includes(normalizedQuery)\r\n || row.models.some(model => routeLabel(model).toLocaleLowerCase().includes(normalizedQuery))\r\n }), [data.sessions, normalizedQuery])\r\n const visibleSessions = useMemo(\r\n () => filteredSessions.slice(0, sessionLimit),\r\n [filteredSessions, sessionLimit],\r\n )\r\n const selectedDay = useMemo(\r\n () => selectedDate === undefined\r\n ? undefined\r\n : (operationalDrilldown ? data.operationalDays : data.days).find(day => day.date === selectedDate),\r\n [data.days, data.operationalDays, operationalDrilldown, selectedDate],\r\n )\r\n const selectedDaySessions = useMemo(\r\n () => operationalDrilldown ? data.sessions.filter(row => row.dailyUsageReliable) : data.sessions,\r\n [data.sessions, operationalDrilldown],\r\n )\r\n const billedInput = inputTokens(data.usage)\r\n const cacheHit = billedInput === 0 ? '—' : `${Math.round(data.usage.cacheReadTokens / billedInput * 100)}%`\r\n\r\n if (phase !== 'ready' && ids.length === 0) {\r\n return

{t('loading')}

\r\n }\r\n\r\n return (\r\n
\r\n
\r\n
\r\n

{t('title')}

\r\n

{t('intro')}

\r\n
\r\n \r\n
\r\n\r\n {data.sessions.length === 0 ?

{t('empty')}

: (\r\n <>\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n\r\n {\r\n setOperationalDrilldown(false)\r\n setSelectedDate(date)\r\n }} t={t} />\r\n {selectedDay === undefined ? null : {\r\n setSelectedDate(undefined)\r\n setOperationalDrilldown(false)\r\n }} />}\r\n \r\n \r\n {\r\n setOperationalDrilldown(true)\r\n setSelectedDate(date)\r\n }} t={t} />\r\n \r\n
\r\n {t('pricingTitle')}\r\n

{t('pricingIntro', {\r\n asOf: PUBLIC_PRICE_CATALOG_AS_OF,\r\n covered: formatTokens(costSummary.coveredTokens),\r\n total: formatTokens(costSummary.totalTokens),\r\n routes: costSummary.coveredModels,\r\n allRoutes: costSummary.totalModels,\r\n })}

\r\n {t('pricingSource')}\r\n
\r\n \r\n
\r\n
\r\n

{t('modelBreakdown')}

\r\n \r\n
\r\n {data.models.length === 0 ?

{t('unknownRoute')}

: (\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {sortedModels.map(model => (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n ))}\r\n \r\n
{t('providerModel')}{t('calls')}{t('total')}{t('input')}{t('output')}{t('estimatedCost')}
{isUnattributed(model)\r\n ? {t('unattributed')}\r\n : <>{model.model}{model.provider}}{isUnattributed(model) ? '—' : (\r\n <>\r\n {t('assistantCalls', { count: model.assistantRequests })}\r\n {model.compactionRequests > 0\r\n ? {t('compactionCalls', { count: model.compactionRequests })}\r\n : null}\r\n \r\n )}\r\n \r\n {(model.usage.cacheReadTokens > 0 || model.usage.cacheWriteTokens > 0) ? (\r\n {t('cacheDetail', {\r\n read: formatCompactTokens(model.usage.cacheReadTokens),\r\n write: formatCompactTokens(model.usage.cacheWriteTokens),\r\n })}\r\n ) : null}\r\n {model.totalCostUSD === undefined || model.rate === undefined\r\n ? \r\n : {formatUSD(model.totalCostUSD)}}
\r\n
\r\n )}\r\n
\r\n\r\n \r\n\r\n
\r\n
\r\n

{t('recentSessions')}

\r\n {\r\n setQuery(event.currentTarget.value)\r\n setSessionLimit(SESSION_PAGE_SIZE)\r\n }}\r\n />\r\n
\r\n

{t('analysisPrivacy')}

\r\n {sessionOpenError === undefined ? null :

{t('openSessionFailed', { message: sessionOpenError })}

}\r\n {filteredSessions.length === 0 ?

{t('emptySearch')}

: (\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n {visibleSessions.map(row => (\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n ))}\r\n \r\n
{t('analysis')}{t('session')}{t('updated')}{t('routes')}{t('total')}{t('input')}{t('output')}
\r\n { runAnalysis(row) }}\r\n >{analysis.status === 'loading' && analysis.sessionId === row.id ? t('analyzing') : t('analyze')}\r\n {row.id}{new Intl.DateTimeFormat(undefined, {\r\n dateStyle: 'medium',\r\n timeStyle: 'short',\r\n }).format(row.updatedAt)}{row.models.length === 0 || row.models.every(isUnattributed)\r\n ? {t('unknownRoute')}\r\n : row.models.filter(model => !isUnattributed(model))\r\n .map(model => {routeLabel(model)})}
\r\n
\r\n )}\r\n {filteredSessions.length > visibleSessions.length ? { setSessionLimit(current => current + SESSION_PAGE_SIZE) }}\r\n >{t('showMoreSessions', { shown: visibleSessions.length, total: filteredSessions.length })} : null}\r\n
\r\n \r\n )}\r\n
\r\n )\r\n}\r\n","import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport type { ObservableSnapshot } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\nimport { Button, Modal } from '@deepseek-ai/dsh-client-ui-primitives'\nimport type {\n TokenUsageAnalysisModelSelection,\n TokenUsageAnalysisProgress,\n TrajectoryAnalysis,\n} from '../types.ts'\nimport type { DownloadPort } from './export.ts'\nimport { NS } from './locales.ts'\nimport type { TokenUsageAnalysisModelCatalog } from './usage-analysis-client.ts'\nimport type { TrajectoryHistorySnapshot } from './trajectory-history.ts'\nimport {\n TrajectoryAnalysisPanel,\n type TrajectoryAnalysisState,\n} from './TokenUsageSection.tsx'\nimport css from './TokenUsageSection.module.css'\n\ntype CatalogState =\n | { status: 'idle' | 'loading' }\n | { status: 'ready'; value: TokenUsageAnalysisModelCatalog }\n | { status: 'error'; message: string }\n\ninterface TrajectoryAnalysisActionInjected {\n hooks: {\n trajectoryHistory: ObservableSnapshot\n }\n download: DownloadPort\n listAnalysisModels(signal: AbortSignal): Promise\n analyzeTrajectory(\n sessionId: string,\n model: TokenUsageAnalysisModelSelection,\n signal: AbortSignal,\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\n ): Promise\n saveTrajectoryAnalysis(analysis: TrajectoryAnalysis): void\n removeTrajectoryAnalysis(id: string): void\n}\n\ntype TrajectoryAnalysisActionProps = PropsRuntime<'conversation.session.header.actions'>\n & PropsLocale\n & InjectFace\n\n/** Conversation-header entry opening session analysis and browser-local history. */\nexport function TrajectoryAnalysisAction({\n sessionId,\n useTrajectoryHistory,\n download,\n listAnalysisModels,\n analyzeTrajectory,\n saveTrajectoryAnalysis,\n removeTrajectoryAnalysis,\n t,\n}: TrajectoryAnalysisActionProps): ReactNode {\n const [open, setOpen] = useState(false)\n const [catalog, setCatalog] = useState({ status: 'idle' })\n const [selectedModel, setSelectedModel] = useState()\n const [analysis, setAnalysis] = useState({ status: 'idle' })\n const catalogController = useRef()\n const analysisController = useRef()\n const history = useTrajectoryHistory(snapshot => snapshot)\n const sessionHistory = useMemo(() => history.entries\n .filter(entry => entry.analysis.sessionId === String(sessionId)), [history.entries, sessionId])\n const availableModels = catalog.status === 'ready' ? catalog.value.models : []\n\n useEffect(() => () => {\n catalogController.current?.abort()\n analysisController.current?.abort()\n }, [])\n\n useEffect(() => {\n if (!open || catalog.status !== 'idle') return\n const controller = new AbortController()\n catalogController.current = controller\n setCatalog({ status: 'loading' })\n void listAnalysisModels(controller.signal).then((value) => {\n if (catalogController.current !== controller || controller.signal.aborted) return\n setCatalog({ status: 'ready', value })\n setSelectedModel(value.default ?? value.models[0])\n }, (error: unknown) => {\n if (catalogController.current === controller && !controller.signal.aborted) {\n setCatalog({ status: 'error', message: error instanceof Error ? error.message : String(error) })\n }\n })\n }, [catalog.status, listAnalysisModels, open])\n\n const retryCatalog = (): void => {\n catalogController.current?.abort()\n catalogController.current = undefined\n setSelectedModel(undefined)\n setCatalog({ status: 'idle' })\n }\n\n const closeModal = (): void => {\n catalogController.current?.abort()\n catalogController.current = undefined\n analysisController.current?.abort()\n analysisController.current = undefined\n setSelectedModel(undefined)\n setCatalog({ status: 'idle' })\n setAnalysis(current => current.status === 'loading' ? { status: 'idle' } : current)\n setOpen(false)\n }\n\n const run = (): void => {\n if (selectedModel === undefined) return\n analysisController.current?.abort()\n const controller = new AbortController()\n analysisController.current = controller\n setAnalysis({ status: 'loading', sessionId: String(sessionId), title: t('currentSession') })\n void analyzeTrajectory(String(sessionId), selectedModel, controller.signal, (progress) => {\n if (analysisController.current !== controller || controller.signal.aborted) return\n setAnalysis(current => current.status === 'loading' ? { ...current, progress } : current)\n }).then((value) => {\n if (analysisController.current !== controller || controller.signal.aborted) return\n analysisController.current = undefined\n saveTrajectoryAnalysis(value)\n setAnalysis({ status: 'ready', title: t('currentSession'), value })\n }, (error: unknown) => {\n if (analysisController.current === controller && !controller.signal.aborted) {\n analysisController.current = undefined\n setAnalysis({\n status: 'error',\n sessionId: String(sessionId),\n title: t('currentSession'),\n message: error instanceof Error ? error.message : String(error),\n })\n }\n })\n }\n\n return <>\n \n {t('close')}}\n >\n
\n {catalog.status === 'loading' || catalog.status === 'idle'\n ? {t('analysisModelsLoading')}\n : catalog.status === 'error'\n ? <>\n {t('analysisModelsFailed', { message: catalog.message })}\n \n \n : availableModels.length === 0\n ? <>\n {t('analysisModelsUnavailable')}\n \n \n : <>\n \n {analysis.status === 'loading' ? t('analyzing') : t('analyze')}\n }\n
\n\n \n\n
\n
\n

{t('analysisHistory')}

{t('analysisHistoryLocal')}

\n {t('analysisHistoryCount', { count: sessionHistory.length })}\n
\n {history.status !== 'ready'\n ?

{t('analysisHistoryUnavailable')}

\n : null}\n {sessionHistory.length === 0\n ?

{t('analysisHistoryEmpty')}

\n :
    {sessionHistory.map(entry =>
  • \n \n { removeTrajectoryAnalysis(entry.id) }}\n >×\n
  • )}
}\n
\n \n \n}\n","/** Shared private RPC names used by the Host and browser halves. */\r\nexport const TOKEN_USAGE_RPC_CHANNEL = '/token-usage'\r\n\r\n/** Version-stable endpoint names for Token usage preferences and analysis. */\r\nexport const TOKEN_USAGE_RPC_ENDPOINT = {\r\n budgetRead: 'budget/read',\r\n budgetWrite: 'budget/write',\r\n analysisModels: 'analysis/models',\r\n analysisProgress: 'analysis/progress',\r\n usageAnalyze: 'usage/analyze',\r\n trajectoryAnalyze: 'trajectory/analyze',\r\n} as const\r\n","import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'\nimport type { TokenUsageBudgetSettings, TokenUsageRouteBudget } from '../budget-settings.ts'\nimport { TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT } from '../rpc.ts'\n\n/** One browser-visible state of the persistent global and exact-route budgets. */\nexport interface TokenUsageBudgetSnapshot {\n status: 'loading' | 'ready' | 'unavailable'\n budget: number\n routeBudgets: readonly TokenUsageRouteBudget[]\n}\n\nconst INITIAL: TokenUsageBudgetSnapshot = { status: 'loading', budget: 0, routeBudgets: [] }\nconst MAX_ROUTE_BUDGETS = 64\n\n/** Stable exact-route identity for settings comparisons and updates. */\nfunction routeKey(route: Pick): string {\n return JSON.stringify([route.provider, route.model])\n}\n\n/** Whether two normalized route-budget lists contain the same settings. */\nfunction sameRouteBudgets(left: readonly TokenUsageRouteBudget[], right: readonly TokenUsageRouteBudget[]): boolean {\n return left.length === right.length && left.every((route, index) => {\n const other = right[index]\n return other !== undefined\n && route.provider === other.provider\n && route.model === other.model\n && route.rolling30DayBudget === other.rolling30DayBudget\n })\n}\n\n/** Minimal stable observable source consumed by the Settings slot hook binder. */\nclass BudgetStore {\n private snapshot: TokenUsageBudgetSnapshot = INITIAL\n private readonly listeners = new Set<() => void>()\n\n getSnapshot(): TokenUsageBudgetSnapshot {\n return this.snapshot\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n set(next: TokenUsageBudgetSnapshot): void {\n if (this.snapshot.status === next.status\n && this.snapshot.budget === next.budget\n && sameRouteBudgets(this.snapshot.routeBudgets, next.routeBudgets)) return\n this.snapshot = next\n for (const listener of [...this.listeners]) listener()\n }\n}\n\n/** Decode normalized budget settings returned by the private Host RPC. */\nfunction settingsOf(value: unknown): TokenUsageBudgetSettings | undefined {\n if (typeof value !== 'object' || value === null) return undefined\n const budget = (value as { rolling30DayBudget?: unknown }).rolling30DayBudget\n if (typeof budget !== 'number' || !Number.isSafeInteger(budget) || budget < 0) return undefined\n const rawRoutes = (value as { routeBudgets?: unknown }).routeBudgets ?? []\n if (!Array.isArray(rawRoutes) || rawRoutes.length > MAX_ROUTE_BUDGETS) return undefined\n const routeBudgets: TokenUsageRouteBudget[] = []\n const seen = new Set()\n for (const value of rawRoutes) {\n if (typeof value !== 'object' || value === null) return undefined\n const provider = (value as { provider?: unknown }).provider\n const model = (value as { model?: unknown }).model\n const rolling30DayBudget = (value as { rolling30DayBudget?: unknown }).rolling30DayBudget\n if (typeof provider !== 'string' || provider.length === 0 || provider.length > 256\n || typeof model !== 'string' || model.length === 0 || model.length > 256\n || typeof rolling30DayBudget !== 'number' || !Number.isSafeInteger(rolling30DayBudget) || rolling30DayBudget <= 0) return undefined\n const route = { provider, model, rolling30DayBudget }\n const key = routeKey(route)\n if (seen.has(key)) return undefined\n seen.add(key)\n routeBudgets.push(route)\n }\n routeBudgets.sort((left, right) => left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model))\n return { rolling30DayBudget: budget, routeBudgets }\n}\n\n/** Mirror the private Host settings endpoint onto one HMR-safe observable source. */\nexport class TokenUsageBudgetController {\n /** Observable snapshot supplied through the settings section's hooks compartment. */\n readonly store = new BudgetStore()\n\n private generation = 0\n private disposed = false\n private writeQueue: Promise = Promise.resolve()\n\n /** @param connection - client connection carrying the loopback RPC channel. */\n constructor(private readonly connection: ConnectionHandle) {}\n\n /** Fetch durable budgets unless the current page cannot call loopback-only endpoints. */\n async load(): Promise {\n const generation = ++this.generation\n if (!this.connection.isLoopback) {\n this.publish(generation, { status: 'unavailable', budget: 0, routeBudgets: [] })\n return\n }\n try {\n const result = await this.connection.rpc.call(TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT.budgetRead, {})\n const settings = result.ok ? settingsOf(result.value) : undefined\n this.publish(generation, settings === undefined\n ? { status: 'unavailable', budget: 0, routeBudgets: [] }\n : { status: 'ready', budget: settings.rolling30DayBudget, routeBudgets: settings.routeBudgets })\n } catch (_budgetReadFailure) {\n this.publish(generation, { status: 'unavailable', budget: 0, routeBudgets: [] })\n }\n }\n\n /** Persist one whole-token global rolling budget and return the durable value. */\n setBudget(rolling30DayBudget: number): Promise {\n if (!Number.isSafeInteger(rolling30DayBudget) || rolling30DayBudget < 0) {\n return Promise.resolve(this.store.getSnapshot().budget)\n }\n return this.enqueue(async () => (await this.writeSettings({ rolling30DayBudget })).budget)\n }\n\n /** Add, replace, or remove one exact-route rolling budget; zero removes it. */\n setRouteBudget(provider: string, model: string, rolling30DayBudget: number): Promise {\n if (provider.length === 0 || provider.length > 256 || model.length === 0 || model.length > 256\n || !Number.isSafeInteger(rolling30DayBudget) || rolling30DayBudget < 0) return Promise.resolve()\n return this.enqueue(async () => {\n const current = this.store.getSnapshot().routeBudgets\n const key = routeKey({ provider, model })\n const routeBudgets = current\n .filter(route => routeKey(route) !== key)\n .concat(rolling30DayBudget === 0 ? [] : [{ provider, model, rolling30DayBudget }])\n .sort((left, right) => left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model))\n if (routeBudgets.length > MAX_ROUTE_BUDGETS) return\n await this.writeSettings({ routeBudgets })\n })\n }\n\n /** Serialize settings writes so each patch is based on the latest durable snapshot. */\n private enqueue(operation: () => Promise): Promise {\n const queued = this.writeQueue.then(operation)\n this.writeQueue = queued.then(() => undefined, () => undefined)\n return queued\n }\n\n /** Execute one queued settings patch and publish the Host-returned durable value. */\n private async writeSettings(payload: Record): Promise {\n if (this.disposed) return this.store.getSnapshot()\n const previous = this.store.getSnapshot()\n const fallback = previous.status === 'ready'\n ? previous\n : { status: 'unavailable' as const, budget: 0, routeBudgets: [] }\n const generation = ++this.generation\n if (!this.connection.isLoopback) {\n this.publish(generation, fallback)\n return fallback\n }\n try {\n const result = await this.connection.rpc.call(TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT.budgetWrite, payload)\n const settings = result.ok ? settingsOf(result.value) : undefined\n const next = settings === undefined\n ? fallback\n : { status: 'ready' as const, budget: settings.rolling30DayBudget, routeBudgets: settings.routeBudgets }\n this.publish(generation, next)\n return next\n } catch (_budgetWriteFailure) {\n this.publish(generation, fallback)\n return fallback\n }\n }\n\n /** Stop all late asynchronous publications after the owning Client fiber disposes. */\n dispose(): void {\n this.disposed = true\n this.generation += 1\n }\n\n /** Publish only the latest request result while this controller remains owned. */\n private publish(generation: number, next: TokenUsageBudgetSnapshot): void {\n if (this.disposed || generation !== this.generation) return\n this.store.set(next)\n }\n}\n","/** Poll request-bound auxiliary-analysis progress over the unary Connection RPC. */\n\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'\nimport type { TokenUsageAnalysisProgress } from '../types.ts'\nimport { TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT } from '../rpc.ts'\n\nconst POLL_INTERVAL_MS = 350\n\n/** Return whether one wire value is a JSON record. */\nfunction isRecord(value: unknown): value is Record {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** Generate one request-local opaque progress id without persisting browser identity. */\nfunction createProgressId(): string {\n const random = globalThis.crypto?.randomUUID?.()\n return random === undefined\n ? `${Date.now().toString(36)}.${Math.random().toString(36).slice(2)}.${Math.random().toString(36).slice(2)}`\n : random\n}\n\n/** Decode one live progress snapshot from the Host. */\nexport function analysisProgressOf(value: unknown): TokenUsageAnalysisProgress | undefined {\n if (!isRecord(value) || value.available !== true) return undefined\n const numericKeys = [\n 'elapsedMs', 'chunks', 'outputCharacters', 'estimatedOutputTokens', 'maximumOutputTokens',\n ] as const\n if (!numericKeys.every(key => typeof value[key] === 'number' && Number.isFinite(value[key]) && value[key] >= 0)\n || (value.phase !== 'preparing' && value.phase !== 'generating' && value.phase !== 'finalizing')\n || (value.exactOutputTokens !== undefined\n && (typeof value.exactOutputTokens !== 'number' || !Number.isFinite(value.exactOutputTokens) || value.exactOutputTokens < 0))) {\n return undefined\n }\n return {\n phase: value.phase,\n elapsedMs: value.elapsedMs as number,\n chunks: value.chunks as number,\n outputCharacters: value.outputCharacters as number,\n estimatedOutputTokens: value.estimatedOutputTokens as number,\n ...value.exactOutputTokens === undefined ? {} : { exactOutputTokens: value.exactOutputTokens },\n maximumOutputTokens: value.maximumOutputTokens as number,\n }\n}\n\n/** Wait for the next poll or reject promptly when the owning request ends. */\nfunction waitForPoll(signal: AbortSignal): Promise {\n return new Promise((resolve, reject) => {\n const timer = globalThis.setTimeout(() => {\n signal.removeEventListener('abort', aborted)\n resolve()\n }, POLL_INTERVAL_MS)\n const aborted = (): void => {\n globalThis.clearTimeout(timer)\n reject(signal.reason)\n }\n if (signal.aborted) aborted()\n else signal.addEventListener('abort', aborted, { once: true })\n })\n}\n\n/** Run one unary analysis call while polling its request-bound progress record. */\nexport async function requestAnalysisWithProgress(\n connection: ConnectionHandle,\n endpoint: string,\n payload: Record,\n signal: AbortSignal,\n decode: (value: unknown) => T | undefined,\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\n): Promise {\n const progressId = createProgressId()\n const polling = new AbortController()\n const pollingSignal = AbortSignal.any([signal, polling.signal])\n const monitor = (async (): Promise => {\n while (!pollingSignal.aborted) {\n try {\n const result = await connection.rpc.call(\n TOKEN_USAGE_RPC_CHANNEL,\n TOKEN_USAGE_RPC_ENDPOINT.analysisProgress,\n { progressId },\n pollingSignal,\n )\n if (pollingSignal.aborted) return\n if (result.ok) {\n const progress = analysisProgressOf(result.value)\n if (progress !== undefined) onProgress?.(progress)\n }\n } catch (_transientProgressFailure) {\n if (pollingSignal.aborted) return\n // Progress is advisory; a transient polling failure must not fail the model call.\n }\n try {\n await waitForPoll(pollingSignal)\n } catch (_analysisFinished) {\n return\n }\n }\n })()\n\n try {\n const result = await connection.rpc.call(\n TOKEN_USAGE_RPC_CHANNEL,\n endpoint,\n { ...payload, progressId },\n signal,\n )\n if (!result.ok) throw new Error(result.error.message)\n const decoded = decode(result.value)\n if (decoded === undefined) throw new Error('The Host returned an invalid analysis report.')\n return decoded\n } finally {\n polling.abort(new Error('analysis request settled'))\n await monitor\n }\n}\n","/** Client decoder and loopback request for configured-model trajectory analysis. */\r\n\r\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'\r\nimport type {\r\n SignedTokenUsageBuckets,\r\n TokenUsageAnalysisModelSelection,\r\n TokenUsageAnalysisProgress,\r\n TokenUsageBuckets,\r\n TrajectoryAnalysis,\r\n TrajectoryMetrics,\r\n TrajectoryReconciliation,\r\n TrajectoryUsageSpan,\r\n} from '../types.ts'\r\nimport { TOKEN_USAGE_RPC_ENDPOINT } from '../rpc.ts'\r\nimport { requestAnalysisWithProgress } from './analysis-progress-client.ts'\r\n\r\n/** Return whether a wire value is an object. */\r\nfunction isRecord(value: unknown): value is Record {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value)\r\n}\r\n\r\nconst TOKEN_BUCKET_KEYS = ['uncachedInputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens'] as const\r\n\r\n/** Decode the plugin's four disjoint buckets. */\r\nfunction bucketsOf(value: unknown): TokenUsageBuckets | undefined {\r\n if (!isRecord(value)) return undefined\r\n if (!TOKEN_BUCKET_KEYS.every(key => typeof value[key] === 'number' && Number.isFinite(value[key]) && value[key] >= 0)) return undefined\r\n return Object.fromEntries(TOKEN_BUCKET_KEYS.map(key => [key, value[key]])) as unknown as TokenUsageBuckets\r\n}\r\n\r\n/** Decode a signed bucket delta. */\r\nfunction signedBucketsOf(value: unknown): SignedTokenUsageBuckets | undefined {\r\n if (!isRecord(value)) return undefined\r\n if (!TOKEN_BUCKET_KEYS.every(key => typeof value[key] === 'number' && Number.isFinite(value[key]))) return undefined\r\n return Object.fromEntries(TOKEN_BUCKET_KEYS.map(key => [key, value[key]])) as unknown as SignedTokenUsageBuckets\r\n}\r\n\r\n/** Compare bucket sets without collapsing cache categories. */\r\nfunction sameBuckets(left: TokenUsageBuckets, right: TokenUsageBuckets): boolean {\r\n return TOKEN_BUCKET_KEYS.every(key => left[key] === right[key])\r\n}\r\n\r\n/** Sum the four provider buckets for largest-node validation. */\r\nfunction totalTokens(usage: TokenUsageBuckets): number {\r\n return TOKEN_BUCKET_KEYS.reduce((total, key) => total + usage[key], 0)\r\n}\r\n\r\n/** Decode one metadata-only provider usage span. */\r\nfunction spanOf(value: unknown): TrajectoryUsageSpan | undefined {\r\n if (!isRecord(value)\r\n || typeof value.id !== 'string'\r\n || (value.kind !== 'model' && value.kind !== 'compaction')\r\n || typeof value.seq !== 'number'\r\n || !Number.isSafeInteger(value.seq)\r\n || value.seq < 0\r\n || typeof value.provider !== 'string'\r\n || typeof value.model !== 'string'\r\n || !['open', 'completed', 'retried'].includes(String(value.status))\r\n || value.valueKind !== 'actual'\r\n || (value.finality !== 'provisional' && value.finality !== 'authoritative')) return undefined\r\n const usage = bucketsOf(value.usage)\r\n if (usage === undefined) return undefined\r\n const optionalNumber = (key: 'turn' | 'step' | 'attempt'): number | undefined => {\r\n const candidate = value[key]\r\n return typeof candidate === 'number' && Number.isSafeInteger(candidate) && candidate >= 0 ? candidate : undefined\r\n }\r\n if (['turn', 'step', 'attempt'].some(key => value[key] !== undefined && optionalNumber(key as 'turn' | 'step' | 'attempt') === undefined)) {\r\n return undefined\r\n }\r\n const turn = optionalNumber('turn')\r\n const step = optionalNumber('step')\r\n const attempt = optionalNumber('attempt')\r\n return {\r\n id: value.id,\r\n kind: value.kind,\r\n seq: value.seq,\r\n ...turn === undefined ? {} : { turn },\r\n ...step === undefined ? {} : { step },\r\n ...attempt === undefined ? {} : { attempt },\r\n provider: value.provider,\r\n model: value.model,\r\n status: value.status as TrajectoryUsageSpan['status'],\r\n valueKind: 'actual',\r\n finality: value.finality,\r\n usage,\r\n }\r\n}\r\n\r\n/** Decode the explicit provider-ledger reconciliation result. */\r\nfunction reconciliationOf(value: unknown): TrajectoryReconciliation | undefined {\r\n if (!isRecord(value) || (value.status !== 'matched' && value.status !== 'mismatch')) return undefined\r\n const providerUsage = bucketsOf(value.providerUsage)\r\n const attributedUsage = bucketsOf(value.attributedUsage)\r\n const delta = signedBucketsOf(value.delta)\r\n if (providerUsage === undefined || attributedUsage === undefined || delta === undefined) return undefined\r\n const expectedDelta = Object.fromEntries(TOKEN_BUCKET_KEYS.map(key => [key, providerUsage[key] - attributedUsage[key]])) as unknown as SignedTokenUsageBuckets\r\n const matched = TOKEN_BUCKET_KEYS.every(key => expectedDelta[key] === 0)\r\n if (!TOKEN_BUCKET_KEYS.every(key => delta[key] === expectedDelta[key]) || (value.status === 'matched') !== matched) return undefined\r\n return {\r\n status: value.status,\r\n providerUsage,\r\n attributedUsage,\r\n delta,\r\n }\r\n}\r\n\r\nconst BASE_METRIC_KEYS = [\r\n 'eventCount', 'includedEventCount', 'omittedChunkEvents', 'turnCount', 'completedTurns', 'failedTurns',\r\n 'stepCount', 'assistantRequests', 'toolCalls', 'toolErrors', 'retries', 'compactions', 'approvalsAsked',\r\n 'approvalsRejected', 'subagents', 'durationMs', 'eventsPerMinute', 'tokensPerMinute',\r\n] as const\r\nconst ADDITIVE_METRIC_KEYS = [\r\n 'omittedContentEvents', 'toolResults', 'orphanToolCalls', 'orphanToolResults', 'averageToolLatencyMs',\r\n 'maxToolLatencyMs', 'modelSwitches', 'openTurns', 'openSteps', 'activeDurationMs', 'activeTokensPerMinute',\r\n] as const\r\nconst COMPLIANCE_METRIC_KEYS = [\r\n 'approvalsResolved', 'approvalsAllowedOnce', 'approvalsCancelled', 'approvalsUnavailable',\r\n 'unresolvedApprovals', 'orphanApprovalDecisions',\r\n] as const\r\n\r\n/** Decode deterministic analysis metrics while tolerating older report schema fields. */\r\nfunction metricsOf(value: unknown, schema: TrajectoryAnalysis['schema']): TrajectoryMetrics | undefined {\r\n const legacy = schema === 'dsh-token-usage/trajectory-analysis-v1'\r\n if (!isRecord(value)) return undefined\r\n const validNumber = (candidate: unknown): candidate is number =>\r\n typeof candidate === 'number' && Number.isFinite(candidate) && candidate >= 0\r\n if (!BASE_METRIC_KEYS.every(key => validNumber(value[key]))) return undefined\r\n const additive: Record = {}\r\n for (const key of ADDITIVE_METRIC_KEYS) {\r\n if (value[key] === undefined && legacy) additive[key] = 0\r\n else if (validNumber(value[key])) additive[key] = value[key]\r\n else return undefined\r\n }\r\n const compliance: Record = {}\r\n for (const key of COMPLIANCE_METRIC_KEYS) {\r\n if (value[key] === undefined && schema !== 'dsh-token-usage/trajectory-analysis-v3') compliance[key] = 0\r\n else if (validNumber(value[key])) compliance[key] = value[key]\r\n else return undefined\r\n }\r\n\r\n const usage = bucketsOf(value.usage)\r\n if (usage === undefined) return undefined\r\n const retryUsage = value.retryUsage === undefined && legacy\r\n ? { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }\r\n : bucketsOf(value.retryUsage)\r\n if (retryUsage === undefined) return undefined\r\n const rawSpans = value.spans === undefined && legacy ? [] : value.spans\r\n if (!Array.isArray(rawSpans)) return undefined\r\n const spans = rawSpans.map(spanOf)\r\n if (spans.some(span => span === undefined)\r\n || (value.largestSpanId !== undefined && typeof value.largestSpanId !== 'string')) return undefined\r\n const decodedSpans = spans as TrajectoryUsageSpan[]\r\n if (decodedSpans.length > 0 && value.largestSpanId === undefined) return undefined\r\n if (value.largestSpanId !== undefined) {\r\n const largest = decodedSpans.find(span => span.id === value.largestSpanId)\r\n const maximumTokens = decodedSpans.reduce((maximum, span) => Math.max(maximum, totalTokens(span.usage)), 0)\r\n if (largest === undefined || totalTokens(largest.usage) !== maximumTokens) return undefined\r\n }\r\n\r\n const reconciliation = value.reconciliation === undefined && legacy\r\n ? {\r\n status: 'unavailable' as const,\r\n providerUsage: usage,\r\n attributedUsage: { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 },\r\n delta: { ...usage },\r\n }\r\n : reconciliationOf(value.reconciliation)\r\n if (reconciliation === undefined) return undefined\r\n if (reconciliation.status !== 'unavailable') {\r\n const attributedUsage = decodedSpans.reduce((total, span) => ({\r\n uncachedInputTokens: total.uncachedInputTokens + span.usage.uncachedInputTokens,\r\n outputTokens: total.outputTokens + span.usage.outputTokens,\r\n cacheReadTokens: total.cacheReadTokens + span.usage.cacheReadTokens,\r\n cacheWriteTokens: total.cacheWriteTokens + span.usage.cacheWriteTokens,\r\n }), { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 })\r\n if (!sameBuckets(usage, reconciliation.providerUsage)\r\n || !sameBuckets(attributedUsage, reconciliation.attributedUsage)) return undefined\r\n }\r\n return {\r\n ...Object.fromEntries(BASE_METRIC_KEYS.map(key => [key, value[key]])),\r\n ...additive,\r\n completeComplianceEvidenceAvailable: schema === 'dsh-token-usage/trajectory-analysis-v3',\r\n ...compliance,\r\n usage,\r\n retryUsage,\r\n spans: decodedSpans,\r\n ...value.largestSpanId === undefined ? {} : { largestSpanId: value.largestSpanId },\r\n reconciliation,\r\n } as unknown as TrajectoryMetrics\r\n}\r\n\r\n/** Decode one complete versioned trajectory report. */\r\nexport function trajectoryAnalysisOf(value: unknown): TrajectoryAnalysis | undefined {\r\n if (!isRecord(value)\r\n || (value.schema !== 'dsh-token-usage/trajectory-analysis-v1'\r\n && value.schema !== 'dsh-token-usage/trajectory-analysis-v2'\r\n && value.schema !== 'dsh-token-usage/trajectory-analysis-v3')\r\n || typeof value.sessionId !== 'string'\r\n || typeof value.generatedAt !== 'string'\r\n || !Number.isFinite(Date.parse(value.generatedAt))\r\n || typeof value.truncated !== 'boolean'\r\n || typeof value.report !== 'string'\r\n || !isRecord(value.model)\r\n || typeof value.model.provider !== 'string'\r\n || typeof value.model.model !== 'string') return undefined\r\n const metrics = metricsOf(value.metrics, value.schema)\r\n const auxiliary = value.analysisUsage === undefined ? undefined : bucketsOf(value.analysisUsage)\r\n if (metrics === undefined || (value.analysisUsage !== undefined && auxiliary === undefined)) return undefined\r\n return {\r\n schema: value.schema,\r\n sessionId: value.sessionId,\r\n generatedAt: value.generatedAt,\r\n model: { provider: value.model.provider, model: value.model.model },\r\n truncated: value.truncated,\r\n metrics,\r\n ...auxiliary === undefined ? {} : { analysisUsage: auxiliary },\r\n report: value.report,\r\n }\r\n}\r\n\r\n/** Request an ephemeral report from the Host through the loopback-only plugin channel. */\r\nexport async function requestTrajectoryAnalysis(\r\n connection: ConnectionHandle,\r\n sessionId: string,\r\n model: TokenUsageAnalysisModelSelection,\r\n language: string,\r\n signal: AbortSignal,\r\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\r\n): Promise {\r\n if (!connection.isLoopback) throw new Error('Trajectory analysis is available only from the local DSH page.')\r\n return requestAnalysisWithProgress(\r\n connection,\r\n TOKEN_USAGE_RPC_ENDPOINT.trajectoryAnalyze,\r\n { sessionId, model, language },\r\n signal,\r\n trajectoryAnalysisOf,\r\n onProgress,\r\n )\r\n}\r\n","/** Browser-local trajectory report history shared by Settings and conversation controls. */\n\nimport type { TrajectoryAnalysis } from '../types.ts'\nimport { trajectoryAnalysisOf } from './trajectory-analysis-client.ts'\n\nconst STORAGE_KEY = 'dsh-token-usage.trajectory-history.v1'\nconst MAX_ENTRIES = 24\nconst MAX_SERIALIZED_CHARS = 3_000_000\n\nexport interface TrajectoryHistoryEntry {\n id: string\n savedAt: string\n analysis: TrajectoryAnalysis\n}\n\nexport interface TrajectoryHistorySnapshot {\n status: 'ready' | 'error' | 'unavailable'\n entries: readonly TrajectoryHistoryEntry[]\n}\n\n/** Stable observable source consumed through the slot inject hooks compartment. */\nclass TrajectoryHistoryStore {\n private snapshot: TrajectoryHistorySnapshot = { status: 'ready', entries: [] }\n private readonly listeners = new Set<() => void>()\n\n getSnapshot(): TrajectoryHistorySnapshot {\n return this.snapshot\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener)\n return () => { this.listeners.delete(listener) }\n }\n\n set(snapshot: TrajectoryHistorySnapshot): void {\n this.snapshot = snapshot\n for (const listener of [...this.listeners]) listener()\n }\n}\n\n/** Return whether a durable timestamp is finite and renderable. */\nfunction validTimestamp(value: unknown): value is string {\n return typeof value === 'string' && Number.isFinite(Date.parse(value))\n}\n\n/** Decode only reports that still satisfy the current client wire validation. */\nfunction storedEntriesOf(value: unknown): TrajectoryHistoryEntry[] {\n if (!Array.isArray(value)) return []\n return value.flatMap((candidate): TrajectoryHistoryEntry[] => {\n if (typeof candidate !== 'object' || candidate === null) return []\n const record = candidate as Record\n const analysis = trajectoryAnalysisOf(record.analysis)\n return typeof record.id === 'string' && record.id.length > 0 && record.id.length <= 512\n && validTimestamp(record.savedAt)\n && analysis !== undefined\n && validTimestamp(analysis.generatedAt)\n ? [{ id: record.id, savedAt: record.savedAt, analysis }]\n : []\n }).slice(0, MAX_ENTRIES)\n}\n\n/** Persist bounded report history in the current browser profile only. */\nexport class TrajectoryHistoryController {\n readonly store = new TrajectoryHistoryStore()\n\n load(): void {\n if (typeof localStorage === 'undefined') {\n this.store.set({ status: 'unavailable', entries: [] })\n return\n }\n try {\n const raw = localStorage.getItem(STORAGE_KEY)\n const entries = raw === null ? [] : storedEntriesOf(JSON.parse(raw) as unknown)\n this.store.set({ status: 'ready', entries })\n } catch (_invalidOrUnavailableStorage) {\n try {\n localStorage.removeItem(STORAGE_KEY)\n this.store.set({ status: 'ready', entries: [] })\n } catch (_storageUnavailable) {\n this.store.set({ status: 'unavailable', entries: [] })\n }\n }\n }\n\n save(analysis: TrajectoryAnalysis): void {\n const snapshot = this.store.getSnapshot()\n if (snapshot.status === 'unavailable' || typeof localStorage === 'undefined') return\n const entry: TrajectoryHistoryEntry = {\n id: `${analysis.sessionId}\\u0000${analysis.generatedAt}\\u0000${analysis.model.provider}\\u0000${analysis.model.model}`,\n savedAt: new Date().toISOString(),\n analysis,\n }\n const entries = [entry, ...snapshot.entries.filter(candidate => candidate.id !== entry.id)].slice(0, MAX_ENTRIES)\n while (entries.length > 1 && JSON.stringify(entries).length > MAX_SERIALIZED_CHARS) entries.pop()\n const serialized = JSON.stringify(entries)\n if (serialized.length > MAX_SERIALIZED_CHARS) {\n this.store.set({ status: 'error', entries: snapshot.entries })\n return\n }\n try {\n localStorage.setItem(STORAGE_KEY, serialized)\n this.store.set({ status: 'ready', entries })\n } catch (_storageQuotaOrPrivacyMode) {\n this.store.set({ status: 'error', entries: snapshot.entries })\n }\n }\n\n remove(id: string): void {\n const snapshot = this.store.getSnapshot()\n if (snapshot.status === 'unavailable' || typeof localStorage === 'undefined') return\n const entries = snapshot.entries.filter(entry => entry.id !== id)\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(entries))\n this.store.set({ status: 'ready', entries })\n } catch (_storageQuotaOrPrivacyMode) {\n this.store.set({ status: 'error', entries: snapshot.entries })\n }\n }\n}\n","/** Loopback client for selecting an integrated model and analyzing aggregate Token usage. */\r\n\r\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'\r\nimport type {\r\n TokenUsageAnalysis,\r\n TokenUsageAnalysisCatalogFailure,\r\n TokenUsageAnalysisInput,\r\n TokenUsageAnalysisModel,\r\n TokenUsageAnalysisModelSelection,\r\n TokenUsageAnalysisProgress,\r\n TokenUsageBuckets,\r\n} from '../types.ts'\r\nimport { TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT } from '../rpc.ts'\r\nimport { requestAnalysisWithProgress } from './analysis-progress-client.ts'\r\n\r\n/** One server-provided selectable-model catalog and its eligible default route. */\r\nexport interface TokenUsageAnalysisModelCatalog {\r\n models: readonly TokenUsageAnalysisModel[]\r\n failures: readonly TokenUsageAnalysisCatalogFailure[]\r\n default?: TokenUsageAnalysisModelSelection\r\n}\r\n\r\n/** Return whether a wire value is a JSON record. */\r\nfunction isRecord(value: unknown): value is Record {\r\n return typeof value === 'object' && value !== null && !Array.isArray(value)\r\n}\r\n\r\n/** Decode the plugin's four disjoint buckets. */\r\nfunction bucketsOf(value: unknown): TokenUsageBuckets | undefined {\r\n if (!isRecord(value)) return undefined\r\n const keys = ['uncachedInputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens'] as const\r\n if (!keys.every(key => typeof value[key] === 'number' && Number.isFinite(value[key]) && value[key] >= 0)) return undefined\r\n return Object.fromEntries(keys.map(key => [key, value[key]])) as unknown as TokenUsageBuckets\r\n}\r\n\r\n/** Decode one provider/model selector row owned by the Host catalog. */\r\nfunction modelOf(value: unknown): TokenUsageAnalysisModel | undefined {\r\n if (!isRecord(value)\r\n || typeof value.provider !== 'string'\r\n || typeof value.providerName !== 'string'\r\n || typeof value.model !== 'string'\r\n || typeof value.modelName !== 'string'\r\n || value.provider.length === 0\r\n || value.model.length === 0) return undefined\r\n return {\r\n provider: value.provider,\r\n providerName: value.providerName,\r\n model: value.model,\r\n modelName: value.modelName,\r\n }\r\n}\r\n\r\n/** Decode one safe provider identifier whose model list was unavailable. */\r\nfunction failureOf(value: unknown): TokenUsageAnalysisCatalogFailure | undefined {\r\n if (!isRecord(value)\r\n || typeof value.provider !== 'string'\r\n || typeof value.providerName !== 'string'\r\n || value.provider.length === 0\r\n || value.providerName.length === 0) return undefined\r\n return { provider: value.provider, providerName: value.providerName }\r\n}\r\n\r\n/** Decode a server-selected default only when it belongs to the model catalog. */\r\nfunction selectionOf(value: unknown, models: readonly TokenUsageAnalysisModel[]): TokenUsageAnalysisModelSelection | undefined {\r\n if (!isRecord(value) || typeof value.provider !== 'string' || typeof value.model !== 'string') return undefined\r\n return models.some(entry => entry.provider === value.provider && entry.model === value.model)\r\n ? { provider: value.provider, model: value.model }\r\n : undefined\r\n}\r\n\r\n/** Decode the Host's selectable integrated-model catalog. */\r\nexport function analysisModelCatalogOf(value: unknown): TokenUsageAnalysisModelCatalog | undefined {\r\n if (!isRecord(value) || !Array.isArray(value.models)) return undefined\r\n const models = value.models.map(modelOf)\r\n if (models.some(model => model === undefined)) return undefined\r\n const available = models as TokenUsageAnalysisModel[]\r\n const rawFailures = value.failures === undefined ? [] : value.failures\r\n if (!Array.isArray(rawFailures)) return undefined\r\n const failures = rawFailures.map(failureOf)\r\n if (failures.some(failure => failure === undefined)) return undefined\r\n const defaultSelection = selectionOf(value.default, available)\r\n return defaultSelection === undefined\r\n ? { models: available, failures: failures as TokenUsageAnalysisCatalogFailure[] }\r\n : { models: available, failures: failures as TokenUsageAnalysisCatalogFailure[], default: defaultSelection }\r\n}\r\n\r\n/** Decode one complete versioned aggregate Token usage report. */\r\nexport function tokenUsageAnalysisOf(value: unknown): TokenUsageAnalysis | undefined {\r\n if (!isRecord(value)\r\n || value.schema !== 'dsh-token-usage/usage-analysis-v1'\r\n || typeof value.generatedAt !== 'string'\r\n || !Number.isFinite(Date.parse(value.generatedAt))\r\n || typeof value.report !== 'string'\r\n || !isRecord(value.model)\r\n || typeof value.model.provider !== 'string'\r\n || typeof value.model.model !== 'string') return undefined\r\n const auxiliary = value.analysisUsage === undefined ? undefined : bucketsOf(value.analysisUsage)\r\n if (value.analysisUsage !== undefined && auxiliary === undefined) return undefined\r\n return {\r\n schema: value.schema,\r\n generatedAt: value.generatedAt,\r\n model: { provider: value.model.provider, model: value.model.model },\r\n ...auxiliary === undefined ? {} : { analysisUsage: auxiliary },\r\n report: value.report,\r\n }\r\n}\r\n\r\n/** Read every currently registered model route eligible for a manual analysis selection. */\r\nexport async function requestAnalysisModels(\r\n connection: ConnectionHandle,\r\n signal: AbortSignal,\r\n): Promise {\r\n if (!connection.isLoopback) throw new Error('AI analysis is available only from the local DSH page.')\r\n const result = await connection.rpc.call(TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT.analysisModels, {}, signal)\r\n if (!result.ok) throw new Error(result.error.message)\r\n const catalog = analysisModelCatalogOf(result.value)\r\n if (catalog === undefined) throw new Error('The Host returned an invalid integrated-model catalog.')\r\n return catalog\r\n}\r\n\r\n/** Analyze aggregate-only Token usage through the manually selected integrated model. */\r\nexport async function requestTokenUsageAnalysis(\r\n connection: ConnectionHandle,\r\n input: TokenUsageAnalysisInput,\r\n model: TokenUsageAnalysisModelSelection,\r\n language: string,\r\n signal: AbortSignal,\r\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\r\n): Promise {\r\n if (!connection.isLoopback) throw new Error('AI analysis is available only from the local DSH page.')\r\n return requestAnalysisWithProgress(\r\n connection,\r\n TOKEN_USAGE_RPC_ENDPOINT.usageAnalyze,\r\n { input, model, language },\r\n signal,\r\n tokenUsageAnalysisOf,\r\n onProgress,\r\n )\r\n}\r\n","import type {} from '@deepseek-ai/dsh-client-locale/client'\r\n\r\n/** Dictionary namespace owned by the Token usage dashboard. */\r\nexport const NS = 'settings.tokenUsage'\r\n\r\n/** Simplified Chinese dictionary and key source of truth. */\r\nexport const zh = {\r\n nav: 'Token 用量',\r\n title: 'Token 使用记录',\r\n intro: '基于 DSH 持久会话日志统计模型请求与上下文压缩用量,不保存提示词或回复正文。',\r\n totalTokens: '总 Token',\r\n inputTokens: '输入 Token',\r\n outputTokens: '输出 Token',\r\n cacheHit: '缓存读取占输入',\r\n sessions: '有用量会话',\r\n modelBreakdown: '模型用量',\r\n activity: 'Token 活跃度',\r\n activityIntro: '最近 30 周,颜色越深表示当日 Token 使用量越高。悬停查看明细,点击查看当天会话。',\r\n activityTooltip: '{date}\\n总计 {total} Token\\n输入 {input} · 输出 {output}\\n缓存:读 {cacheRead} · 写 {cacheWrite}',\r\n less: '少',\r\n more: '多',\r\n trend: '用量趋势',\r\n trendIntro: '按 UTC 日期比较当前周期与前一等长周期。',\r\n trendModel: '趋势模型',\r\n allModels: '全部模型',\r\n trendModelScope: '当前趋势仅统计 {route} 的可靠逐日 bucket。',\r\n modelDailyCoverageUnavailable: '旧版回退记录没有日期×模型 bucket;完成历史重折叠后才能按模型筛选趋势。',\r\n modelDailyCoveragePartial: '部分历史记录缺少日期×模型 bucket;为避免低估,模型趋势筛选暂不可用。',\r\n rangeDays: '{count} 天',\r\n periodTokens: '最近 {count} 天',\r\n periodChange: '较前一周期',\r\n activeDays: '活跃天数',\r\n peakDay: '峰值日',\r\n peakDayNote: '峰值:{date} · {total} Token',\r\n budget: '30 日 Token 预算',\r\n budgetIntro: '预算保存在本机 DSH 设置中,填 0 或清空即可关闭。',\r\n budgetInput: '30 日预算(Token)',\r\n budgetDisabled: '尚未设置预算。',\r\n budgetUnavailable: '当前连接无法读取或保存预算。',\r\n budgetProgress: '已用 {used} / {budget} Token({percent}%)',\r\n budgetExceeded: '已超出预算 {excess} Token。',\r\n budgetRunRate: '按最近 7 个完整 UTC 日:日均 {average} Token,预计滚动 30 日 {projected} Token。',\r\n budgetForecastExceeded: '按当前运行率预计 30 日 {projected} Token,将超过预算 {budget} Token。',\r\n routeBudgets: '模型预算与预警',\r\n routeBudgetsIntro: '为精确 provider/model 设置滚动 30 日 Token 预算;达到 80% 预警,达到 100% 或预测超额时优先提示。',\r\n routeBudgetModel: '模型路由',\r\n routeBudgetChooseModel: '选择有用量的模型',\r\n routeBudgetInput: '模型 30 日预算(Token)',\r\n routeBudgetSave: '保存模型预算',\r\n routeBudgetRemove: '移除预算',\r\n routeBudgetRemoveFor: '移除 {route} 的预算',\r\n routeBudgetsEmpty: '尚未设置模型级预算。',\r\n routeBudgetCoverageUnavailable: '没有完整且守恒的日期×模型数据;可配置预算,但暂不计算消耗和预测。',\r\n routeBudgetCoveragePartial: '部分日期×模型数据缺失或无法与总量守恒;为避免低估,模型预算状态暂不可用。',\r\n routeBudgetUnavailable: '等待可靠数据',\r\n routeBudgetStatus_healthy: '健康',\r\n routeBudgetStatus_warning: '已达 80% 预警',\r\n 'routeBudgetStatus_forecast-exceeded': '预测将超额',\r\n routeBudgetStatus_exceeded: '已超额',\r\n routeBudgetProgress: '已用 {used} / {budget} Token({percent}%)',\r\n routeBudgetForecast: '按最近 7 个完整 UTC 日预测滚动 30 日 {projected} Token。',\r\n estimatedCost: '估算费用(USD)',\r\n cacheReadSavings: '缓存读取避免费用(USD)',\r\n priceCoverage: '费率覆盖',\r\n pricingTitle: '公开费率估算',\r\n pricingIntro: '内置 USD 费率表截至 {asOf},可能已经变化;覆盖 {covered}/{total} Token、{routes}/{allRoutes} 个有用量路由。仅按路由标签匹配,不能验证端点、合同或账单。',\r\n pricingSource: '查看官方费率来源',\r\n priceUnavailable: '该 route 不在内置公开 USD 费率表中。',\r\n priceRate: '每 1M Token:输入 ${input} · 输出 ${output} · 缓存读 ${cacheRead} · 缓存写 ${cacheWrite}(公开费率 {asOf})',\r\n efficiency: 'Agent 效率与归因',\r\n efficiencyIntro: '仅基于已记录的聚合 Token、调用次数与上下文压缩统计,不推断任务质量。',\r\n assistantAttempts: '模型尝试次数',\r\n tokensPerAssistantAttempt: '每次模型尝试 Token',\r\n compactionRate: '每 100 次尝试的压缩',\r\n compactionTokenShare: '压缩 Token 占比',\r\n cacheReadShare: '缓存读取占输入',\r\n topRouteShare: 'Top 路由占比',\r\n noRouteAttribution: '当前没有可归因的模型路由。',\r\n routeConcentration: 'Top 路由 {route} 占全部 Token 的 {topOne};Top 3 合计 {topThree}。',\r\n unattributedShare: '未归因用量占全部 Token 的 {share};其调用次数与路由效率未知。',\r\n usageSignals: '用量信号',\r\n usageSignalsIntro: '运行率和突增检测只使用完整 UTC 日,当前仍是全局口径,不作为模型级告警。',\r\n dailyRunRate: '7 日日均 Token',\r\n projectedThirtyDayUsage: '预计 30 日 Token',\r\n anomalyRatio: '昨日相对基线',\r\n anomalyExcess: '昨日超出中位数',\r\n anomalyInsufficient: '突增检测需要昨日有用量,以及此前 28 日至少 5 个活跃完整 UTC 日。',\r\n dailyCoverageUnavailable: '旧版回退记录没有真实逐日 bucket,因此不用于运行率、预测或异常信号。',\r\n dailyCoveragePartial: '部分历史记录缺少真实逐日 bucket;为避免低估,运行率、预测和异常信号暂不显示。',\r\n anomalyNormal: '{date} 未超过稳健基线(活跃日中位数 {baseline} Token,样本 {active} 日)。',\r\n anomalyElevated: '{date} 使用 {tokens} Token,是活跃日中位数 {baseline} Token 的 {ratio}×,超出 {excess} Token(样本 {active} 日)。',\r\n inspectAnomalyDay: '查看异常日会话',\r\n usageAnalysis: 'AI Token 用量分析',\r\n usageAnalysisIntro: '使用手动选择的已接入模型,基于聚合 Token 数据生成多维分析与优化建议。',\r\n analysisModel: '分析模型',\r\n analysisModelsLoading: '正在读取已接入模型…',\r\n analysisModelsFailed: '无法读取已接入模型:{message}',\r\n analysisModelsUnavailable: '没有可用于分析的已接入模型。请先在 DSH 的模型设置中接入模型。',\r\n analysisModelsPartial: '以下提供方暂时无法列出模型,但其他可用模型仍可分析:{providers}。',\r\n analysisModelsAllFailed: '暂时无法从以下提供方读取模型:{providers}。请稍后刷新目录。',\r\n refreshAnalysisModels: '刷新模型目录',\r\n usageAnalysisPrivacy: '隐私提示:仅发送总量、路由别名、请求次数和 UTC 每日 Token bucket;不会发送原始 provider/model、会话 ID、标题、提示词或回复。',\r\n analysisModelScope: '此处选择的模型同样用于下方按会话运行的轨迹分析;轨迹报告会保存在当前浏览器本地。',\r\n analyzeUsage: '生成用量分析',\r\n usageAnalyzing: '正在生成分析…',\r\n usageAnalysisRunning: '模型正在生成 Token 用量分析报告。',\r\n usageAnalysisFailed: '用量分析失败:{message}',\r\n usageAnalysisReport: 'AI 用量分析报告',\r\n exportAnalysisReport: '导出 Markdown 报告',\r\n copyCode: '复制代码',\r\n copiedCode: '已复制',\r\n analysisProgressPreparing: '准备分析证据',\r\n analysisProgressGenerating: '模型生成中',\r\n analysisProgressFinalizing: '整理报告',\r\n analysisProgressWaiting: '等待模型返回内容',\r\n analysisProgressEstimated: '已返回约 {count} / {maximum} Token',\r\n analysisProgressExact: '已返回 {count} / {maximum} Token',\r\n analysisProgressActivity: '{chunks} 个流块 · {characters} 个字符',\r\n analysisProgressElapsed: '已等待 {seconds} 秒',\r\n dayDetails: '{date} 用量明细',\r\n dayDetailsIntro: '仅显示该 UTC 日期内已记录的聚合用量与贡献会话。',\r\n closeDayDetails: '收起明细',\r\n contributors: '贡献会话({count})',\r\n noContributors: '当天没有可显示的会话贡献。',\r\n export: '导出',\r\n exportJson: 'JSON 汇总',\r\n exportDaily: '每日 CSV',\r\n exportModels: '模型 CSV',\r\n exportModelDaily: '日期×模型 CSV',\r\n exportModelDailyUnavailable: '日期×模型覆盖不完整或未通过守恒校验,暂不导出以避免生成低估数据。',\r\n recentSessions: '会话记录',\r\n openSessionFailed: '无法打开会话:{message}',\r\n showMoreSessions: '显示更多会话(已显示 {shown}/{total})',\r\n providerModel: '提供方 / 模型',\r\n modelSort: '排序方式',\r\n modelSortTotal: '总 Token',\r\n modelSortCost: '估算费用',\r\n modelSortTokensPerAttempt: '每次记录调用 Token',\r\n modelSortCacheRead: '缓存读取占输入',\r\n calls: '调用',\r\n total: '总量',\r\n input: '输入',\r\n output: '输出',\r\n cacheRead: '缓存读取',\r\n cacheWrite: '缓存写入',\r\n cacheDetail: '缓存:读 {read} · 写 {write}',\r\n session: '会话',\r\n updated: '最近活动',\r\n routes: '模型',\r\n search: '搜索会话或模型',\r\n empty: '暂无 Token 使用记录。',\r\n emptySearch: '没有匹配的使用记录。',\r\n assistantCalls: '对话 {count}',\r\n compactionCalls: '压缩 {count}',\r\n unknownRoute: '模型信息不可用',\r\n unattributed: '未归因用量',\r\n trajectoryAnalysis: '会话 Token 轨迹分析',\r\n trajectoryAnalysisIntro: '选择“分析轨迹”后,仅将事件类别、相对时间、路由别名、工具名称、审批结果、状态和 provider Token bucket 发送给所选模型;提示词、回复、工具参数与结果始终省略。完成的报告保存在当前浏览器本地。',\r\n analysisRunning: '正在分析“{title}”的元数据轨迹…',\r\n analysisFailed: '分析失败:{message}',\r\n analysisFor: '轨迹分析 · {title}',\r\n analysisMeta: '{provider}/{model} · {time}',\r\n analysisCostDetailed: '本次分析 {total} Token · 模型输出 {output}',\r\n analysisTurns: '回合 / 未结束',\r\n analysisTools: '工具调用 / 结果 / 错误',\r\n analysisIntegrity: '孤立工具 / 未结束步骤',\r\n analysisToolLatency: '工具延迟(均值 / 最大)',\r\n analysisRetries: '模型重试',\r\n analysisRetryTokens: '重试 Token',\r\n analysisLargest: '最大用量节点',\r\n analysisReconciliation: 'Token 对账',\r\n analysisMatched: '一致',\r\n analysisUnavailable: '旧版报告不可用',\r\n analysisMismatch: '差异 {count}',\r\n analysisRate: '活跃时段 Token 速率',\r\n analysisApprovals: '审批 / 拒绝',\r\n analysisLifecycleGroup: '生命周期',\r\n analysisToolGroup: '工具可靠性',\r\n analysisComplianceGroup: '合规控制',\r\n analysisEfficiencyGroup: '资源效率',\r\n analysisSteps: '步骤',\r\n analysisOpenCount: '未闭合 {count}',\r\n analysisTokenCount: '{count} Token',\r\n analysisApprovalClosure: '审批闭环',\r\n analysisApprovalDenied: '拒绝 / 取消 / 不可用',\r\n analysisApprovalRequests: '审批请求',\r\n analysisApprovalRejectedOnly: '拒绝决定',\r\n analysisComplianceEvidence: 'v3 审计字段',\r\n analysisAuditGaps: '审计缺口',\r\n analysisTruncated: '元数据轨迹过长,模型仅收到首尾有界样本;报告会将中段标为不可用证据。',\r\n analysisPrivacy: '隐私:所选模型只接收白名单元数据、工具名称、审批结果和 provider 上报 Token;不发送提示词、回复、工具参数/结果、原始 provider/model、会话标题/ID 或个人与组织字段。报告历史仅保存在当前浏览器。',\r\n conversationTrajectoryAnalysis: '当前会话轨迹分析',\r\n conversationTrajectoryAnalysisIntro: '可在对话页直接运行与 Token 用量页一致的轨迹审计,并查看当前会话的本地历史。',\r\n currentSession: '当前会话',\r\n analysisHistory: '分析历史',\r\n analysisHistoryLocal: '仅保存在当前浏览器 localStorage,最多保留 24 条。',\r\n analysisHistoryCount: '{count} 条',\r\n analysisHistoryEmpty: '当前会话还没有已保存的分析报告。',\r\n analysisHistoryUnavailable: '浏览器本地存储不可用,分析报告不会保留。',\r\n deleteAnalysisHistory: '删除分析历史',\r\n close: '关闭',\r\n analysis: '轨迹分析',\r\n analyze: '分析轨迹',\r\n analyzing: '分析中…',\r\n loading: '正在读取会话统计…',\r\n} satisfies Record\r\n\r\n/** Token usage locale key union. */\r\nexport type TokenUsageLocaleKey = keyof typeof zh\r\n\r\n/** English dictionary checked against the Chinese key set. */\r\nexport const en = {\r\n nav: 'Token usage',\r\n title: 'Token usage records',\r\n intro: 'Counts model requests and context compactions from durable DSH session logs without storing prompt or response text.',\r\n totalTokens: 'Total tokens',\r\n inputTokens: 'Input tokens',\r\n outputTokens: 'Output tokens',\r\n cacheHit: 'Cache-read share of input',\r\n sessions: 'Sessions with usage',\r\n modelBreakdown: 'Usage by model',\r\n activity: 'Token activity',\r\n activityIntro: 'Last 30 weeks. Darker cells represent higher daily Token usage. Hover for details or select a day for its sessions.',\r\n activityTooltip: '{date}\\nTotal {total} tokens\\nInput {input} · Output {output}\\nCache: read {cacheRead} · write {cacheWrite}',\r\n less: 'Less',\r\n more: 'More',\r\n trend: 'Usage trends',\r\n trendIntro: 'Compares the current UTC period with the preceding period of equal length.',\r\n trendModel: 'Trend model',\r\n allModels: 'All models',\r\n trendModelScope: 'This trend includes reliable daily buckets for {route} only.',\r\n modelDailyCoverageUnavailable: 'Legacy fallback records lack date-by-model buckets. Model filtering becomes available after history is refolded.',\r\n modelDailyCoveragePartial: 'Some history lacks date-by-model buckets, so model filtering is unavailable to avoid undercounting.',\r\n rangeDays: '{count} days',\r\n periodTokens: 'Last {count} days',\r\n periodChange: 'Previous period',\r\n activeDays: 'Active days',\r\n peakDay: 'Peak day',\r\n peakDayNote: 'Peak: {date} · {total} tokens',\r\n budget: '30-day Token budget',\r\n budgetIntro: 'The budget is stored in local DSH settings. Enter 0 or clear it to disable.',\r\n budgetInput: '30-day budget (tokens)',\r\n budgetDisabled: 'No budget is set.',\r\n budgetUnavailable: 'This connection cannot read or save the budget.',\r\n budgetProgress: '{used} / {budget} tokens used ({percent}%)',\r\n budgetExceeded: '{excess} tokens over budget.',\r\n budgetRunRate: 'Latest 7 complete UTC days: {average} tokens/day; projected rolling 30 days: {projected} tokens.',\r\n budgetForecastExceeded: 'At the current run rate, the projected 30-day usage of {projected} tokens exceeds the {budget}-token budget.',\r\n routeBudgets: 'Model budgets and alerts',\r\n routeBudgetsIntro: 'Set a rolling 30-day Token budget for an exact provider/model route. Warning starts at 80%; actual and forecast overages are prioritized.',\r\n routeBudgetModel: 'Model route',\r\n routeBudgetChooseModel: 'Choose a model with usage',\r\n routeBudgetInput: 'Model 30-day budget (tokens)',\r\n routeBudgetSave: 'Save model budget',\r\n routeBudgetRemove: 'Remove budget',\r\n routeBudgetRemoveFor: 'Remove the budget for {route}',\r\n routeBudgetsEmpty: 'No model-level budget is configured.',\r\n routeBudgetCoverageUnavailable: 'No complete, conserved date-by-model data is available. Budgets can be configured, but usage and forecasts are not evaluated yet.',\r\n routeBudgetCoveragePartial: 'Some date-by-model data is missing or does not conserve totals. Model budget status is unavailable to avoid undercounting.',\r\n routeBudgetUnavailable: 'Waiting for reliable data',\r\n routeBudgetStatus_healthy: 'Healthy',\r\n routeBudgetStatus_warning: '80% warning reached',\r\n 'routeBudgetStatus_forecast-exceeded': 'Forecast over budget',\r\n routeBudgetStatus_exceeded: 'Over budget',\r\n routeBudgetProgress: '{used} / {budget} tokens used ({percent}%)',\r\n routeBudgetForecast: 'Latest 7 complete UTC days project {projected} tokens over a rolling 30-day period.',\r\n estimatedCost: 'Estimated cost (USD)',\r\n cacheReadSavings: 'Cache-read avoided cost (USD)',\r\n priceCoverage: 'Rate coverage',\r\n pricingTitle: 'Public-rate estimate',\r\n pricingIntro: 'Built-in USD rates as of {asOf} may have changed; coverage is {covered}/{total} tokens across {routes}/{allRoutes} active routes. Label matching cannot verify the endpoint, contract, or invoice.',\r\n pricingSource: 'View official pricing source',\r\n priceUnavailable: 'This route is not in the built-in public USD rate catalog.',\r\n priceRate: 'Per 1M tokens: input ${input} · output ${output} · cache read ${cacheRead} · cache write ${cacheWrite} (public rate {asOf})',\r\n efficiency: 'Agent efficiency and attribution',\r\n efficiencyIntro: 'Uses only recorded aggregate Tokens, attempt counts, and context-compaction statistics; it does not infer task quality.',\r\n assistantAttempts: 'Model attempts',\r\n tokensPerAssistantAttempt: 'Tokens per model attempt',\r\n compactionRate: 'Compactions per 100 attempts',\r\n compactionTokenShare: 'Compaction Token share',\r\n cacheReadShare: 'Cache-read share of input',\r\n topRouteShare: 'Top route share',\r\n noRouteAttribution: 'No attributable model route is available.',\r\n routeConcentration: 'Top route {route} accounts for {topOne} of all Tokens; Top 3 account for {topThree}.',\r\n unattributedShare: 'Unattributed usage is {share} of all Tokens; its attempt count and route efficiency are unknown.',\r\n usageSignals: 'Usage signals',\r\n usageSignalsIntro: 'Run rate and spike detection use complete UTC days and remain global signals, not model-level alerts.',\r\n dailyRunRate: '7-day daily Tokens',\r\n projectedThirtyDayUsage: 'Projected 30-day Tokens',\r\n anomalyRatio: 'Yesterday versus baseline',\r\n anomalyExcess: 'Yesterday above median',\r\n anomalyInsufficient: 'Spike detection needs usage yesterday and at least five active complete UTC days in the prior 28 days.',\r\n dailyCoverageUnavailable: 'Legacy fallback records lack true daily buckets, so they are excluded from run rate, forecast, and anomaly signals.',\r\n dailyCoveragePartial: 'Some historical records lack true daily buckets; run rate, forecast, and anomaly signals are hidden to avoid undercounting.',\r\n anomalyNormal: '{date} is within the robust baseline (active-day median {baseline} tokens; {active} days).',\r\n anomalyElevated: '{date} used {tokens} tokens, {ratio}× the active-day median of {baseline} tokens, exceeding it by {excess} tokens ({active} days).',\r\n inspectAnomalyDay: 'Inspect spike-day sessions',\r\n usageAnalysis: 'AI Token usage analysis',\r\n usageAnalysisIntro: 'Use a manually selected integrated model to generate a multi-dimensional usage review and optimization recommendations from aggregate Token data.',\r\n analysisModel: 'Analysis model',\r\n analysisModelsLoading: 'Reading integrated models…',\r\n analysisModelsFailed: 'Unable to read integrated models: {message}',\r\n analysisModelsUnavailable: 'No integrated model is available for analysis. Add one in DSH Model settings first.',\r\n analysisModelsPartial: 'Models could not be listed for {providers}; other available models can still run analysis.',\r\n analysisModelsAllFailed: 'Models could not be read for {providers}. Refresh the catalog later.',\r\n refreshAnalysisModels: 'Refresh model catalog',\r\n usageAnalysisPrivacy: 'Privacy: only totals, route aliases, request counts, and UTC daily Token buckets are sent. No raw provider/model ids, session IDs, titles, prompts, or responses are sent.',\r\n analysisModelScope: 'The selected model is also used by the per-session trajectory analysis below; trajectory reports are stored in this browser.',\r\n analyzeUsage: 'Generate usage analysis',\r\n usageAnalyzing: 'Generating analysis…',\r\n usageAnalysisRunning: 'The model is generating the Token usage analysis report.',\r\n usageAnalysisFailed: 'Usage analysis failed: {message}',\r\n usageAnalysisReport: 'AI usage analysis report',\r\n exportAnalysisReport: 'Export Markdown report',\r\n copyCode: 'Copy code',\r\n copiedCode: 'Copied',\r\n analysisProgressPreparing: 'Preparing evidence',\r\n analysisProgressGenerating: 'Model generating',\r\n analysisProgressFinalizing: 'Finalizing report',\r\n analysisProgressWaiting: 'Waiting for model output',\r\n analysisProgressEstimated: 'About {count} / {maximum} tokens returned',\r\n analysisProgressExact: '{count} / {maximum} tokens returned',\r\n analysisProgressActivity: '{chunks} stream chunks · {characters} characters',\r\n analysisProgressElapsed: 'Waiting for {seconds}s',\r\n dayDetails: '{date} usage details',\r\n dayDetailsIntro: 'Shows aggregate usage and contributing sessions recorded for this UTC date.',\r\n closeDayDetails: 'Hide details',\r\n contributors: 'Contributing sessions ({count})',\r\n noContributors: 'No session contribution is available for this day.',\r\n export: 'Export',\r\n exportJson: 'JSON summary',\r\n exportDaily: 'Daily CSV',\r\n exportModels: 'Model CSV',\r\n exportModelDaily: 'Date × model CSV',\r\n exportModelDailyUnavailable: 'Date-by-model coverage is incomplete or failed conservation checks, so this export is disabled to avoid undercounting.',\r\n recentSessions: 'Session records',\r\n openSessionFailed: 'Unable to open session: {message}',\r\n showMoreSessions: 'Show more sessions ({shown}/{total} shown)',\r\n providerModel: 'Provider / model',\r\n modelSort: 'Sort by',\r\n modelSortTotal: 'Total Tokens',\r\n modelSortCost: 'Estimated cost',\r\n modelSortTokensPerAttempt: 'Tokens per recorded call',\r\n modelSortCacheRead: 'Cache-read share of input',\r\n calls: 'Calls',\r\n total: 'Total',\r\n input: 'Input',\r\n output: 'Output',\r\n cacheRead: 'Cache read',\r\n cacheWrite: 'Cache write',\r\n cacheDetail: 'Cache: read {read} · write {write}',\r\n session: 'Session',\r\n updated: 'Last activity',\r\n routes: 'Models',\r\n search: 'Search sessions or models',\r\n empty: 'No token usage has been recorded.',\r\n emptySearch: 'No matching usage records.',\r\n assistantCalls: 'Chat {count}',\r\n compactionCalls: 'Compaction {count}',\r\n unknownRoute: 'Model unavailable',\r\n unattributed: 'Unattributed usage',\r\n trajectoryAnalysis: 'Session Token trajectory analysis',\r\n trajectoryAnalysisIntro: 'Analyze trajectory sends only event categories, relative timing, route aliases, tool names, approval outcomes, statuses, and provider Token buckets to the selected model. Prompts, replies, tool arguments, and results are always omitted. Completed reports are stored in this browser.',\r\n analysisRunning: 'Analyzing the metadata trajectory for “{title}”…',\r\n analysisFailed: 'Analysis failed: {message}',\r\n analysisFor: 'Trajectory analysis · {title}',\r\n analysisMeta: '{provider}/{model} · {time}',\r\n analysisCostDetailed: '{total} analysis tokens · {output} model output',\r\n analysisTurns: 'Turns / open',\r\n analysisTools: 'Tool calls / results / errors',\r\n analysisIntegrity: 'Orphaned tools / open steps',\r\n analysisToolLatency: 'Tool latency (avg / max)',\r\n analysisRetries: 'Model retries',\r\n analysisRetryTokens: 'Retry tokens',\r\n analysisLargest: 'Largest usage node',\r\n analysisReconciliation: 'Token reconciliation',\r\n analysisMatched: 'Matched',\r\n analysisUnavailable: 'Unavailable in legacy report',\r\n analysisMismatch: '{count} difference',\r\n analysisRate: 'Active-window Token rate',\r\n analysisApprovals: 'Approvals / rejected',\r\n analysisLifecycleGroup: 'Lifecycle',\r\n analysisToolGroup: 'Tool reliability',\r\n analysisComplianceGroup: 'Compliance controls',\r\n analysisEfficiencyGroup: 'Resource efficiency',\r\n analysisSteps: 'Steps',\r\n analysisOpenCount: '{count} open',\r\n analysisTokenCount: '{count} tokens',\r\n analysisApprovalClosure: 'Approval closure',\r\n analysisApprovalDenied: 'Rejected / cancelled / unavailable',\r\n analysisApprovalRequests: 'Approval requests',\r\n analysisApprovalRejectedOnly: 'Rejected decisions',\r\n analysisComplianceEvidence: 'v3 audit fields',\r\n analysisAuditGaps: 'Audit gaps',\r\n analysisTruncated: 'The metadata trajectory was too long, so the model received a bounded head-and-tail sample and treats the middle as unavailable evidence.',\r\n analysisPrivacy: 'Privacy: the selected model receives only allowlisted metadata, tool names, approval outcomes, and provider-reported Token buckets—never prompts, replies, tool arguments/results, raw provider/model ids, session titles/IDs, or personal and organization fields. Report history stays in this browser.',\r\n conversationTrajectoryAnalysis: 'Current session trajectory analysis',\r\n conversationTrajectoryAnalysisIntro: 'Run the same trajectory audit from the conversation page and review local history for this session.',\r\n currentSession: 'Current session',\r\n analysisHistory: 'Analysis history',\r\n analysisHistoryLocal: 'Stored only in this browser localStorage, up to 24 reports.',\r\n analysisHistoryCount: '{count} reports',\r\n analysisHistoryEmpty: 'No saved analysis report exists for this session.',\r\n analysisHistoryUnavailable: 'Browser local storage is unavailable, so analysis reports will not persist.',\r\n deleteAnalysisHistory: 'Delete analysis history',\r\n close: 'Close',\r\n analysis: 'Trajectory analysis',\r\n analyze: 'Analyze trajectory',\r\n analyzing: 'Analyzing…',\r\n loading: 'Reading session usage…',\r\n} satisfies Record\r\n","/** Token usage dashboard registered into Web Settings. */\n\nimport type { ClientContext, SessionId } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-conversation/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {\n TokenUsageAnalysisInput,\n TokenUsageAnalysisModelSelection,\n TokenUsageAnalysisProgress,\n TrajectoryAnalysis,\n} from '../types.ts'\nimport { TokenUsageSection } from './TokenUsageSection.tsx'\nimport { TrajectoryAnalysisAction } from './TrajectoryAnalysisAction.tsx'\nimport { TokenUsageBudgetController } from './budget-controller.ts'\nimport { browserDownload } from './export.ts'\nimport { requestTrajectoryAnalysis } from './trajectory-analysis-client.ts'\nimport { TrajectoryHistoryController } from './trajectory-history.ts'\nimport { requestAnalysisModels, requestTokenUsageAnalysis } from './usage-analysis-client.ts'\nimport { en, NS, zh, type TokenUsageLocaleKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Token usage dashboard copy. */\n 'settings.tokenUsage': TokenUsageLocaleKey\n }\n}\n\n/** Client services required by the Settings contribution. */\nexport const inject = ['slots', 'locale', 'connection', 'sessions']\n\n/** Contribute a localized Token usage page to Settings. */\nexport function apply(ctx: ClientContext): void {\n const connection = ctx.get('connection') as ConnectionHandle | undefined\n if (connection === undefined) throw new Error('dsh-token-usage requires the Client connection service')\n const budget = new TokenUsageBudgetController(connection)\n const trajectoryHistory = new TrajectoryHistoryController()\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'token-usage: dictionaries')\n ctx.effect(() => {\n trajectoryHistory.load()\n void budget.load()\n return () => { budget.dispose() }\n }, 'token usage: load persistent budget')\n const t = ctx.locale.bind(NS)\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'token-usage',\n order: 30,\n label: () => t('nav'),\n locale: NS,\n inject: () => ({\n hooks: { budget: budget.store },\n setBudget: (value: number) => budget.setBudget(value),\n setRouteBudget: (provider: string, model: string, value: number) => budget.setRouteBudget(provider, model, value),\n download: browserDownload,\n saveTrajectoryAnalysis: (analysis: TrajectoryAnalysis) => { trajectoryHistory.save(analysis) },\n openSession: (sessionId: SessionId) => { ctx.sessions.open(sessionId) },\n listAnalysisModels: (signal: AbortSignal) => requestAnalysisModels(connection, signal),\n analyzeTokenUsage: (\n input: TokenUsageAnalysisInput,\n model: TokenUsageAnalysisModelSelection,\n signal: AbortSignal,\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\n ) => requestTokenUsageAnalysis(\n connection,\n input,\n model,\n ctx.locale.getLocale().active,\n signal,\n onProgress,\n ),\n analyzeTrajectory: (\n sessionId: string,\n model: TokenUsageAnalysisModelSelection,\n signal: AbortSignal,\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\n ) => requestTrajectoryAnalysis(\n connection,\n sessionId,\n model,\n ctx.locale.getLocale().active,\n signal,\n onProgress,\n ),\n }),\n }, TokenUsageSection))\n ctx.slots.inject('conversation.session.header.actions', () => ctx.slots.register({\n name: 'conversation.session.header.actions',\n id: 'token-usage-trajectory-analysis',\n order: 40,\n locale: NS,\n inject: () => ({\n hooks: { trajectoryHistory: trajectoryHistory.store },\n download: browserDownload,\n listAnalysisModels: (signal: AbortSignal) => requestAnalysisModels(connection, signal),\n analyzeTrajectory: (\n sessionId: string,\n model: TokenUsageAnalysisModelSelection,\n signal: AbortSignal,\n onProgress?: (progress: TokenUsageAnalysisProgress) => void,\n ) => requestTrajectoryAnalysis(\n connection,\n sessionId,\n model,\n ctx.locale.getLocale().active,\n signal,\n onProgress,\n ),\n saveTrajectoryAnalysis: (analysis: TrajectoryAnalysis) => { trajectoryHistory.save(analysis) },\n removeTrajectoryAnalysis: (id: string) => { trajectoryHistory.remove(id) },\n }),\n }, TrajectoryAnalysisAction))\n}\n"],"mappings":"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"} \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index 1b3395c..3882250 100644 --- a/lib/index.js +++ b/lib/index.js @@ -29,6 +29,12 @@ const projectionSchema = z$1.object({ days: z$1.array(z$1.object({ date: z$1.string(), usage: bucketsSchema + }).strict()), + modelDays: z$1.array(z$1.object({ + provider: z$1.string(), + model: z$1.string(), + date: z$1.string(), + usage: bucketsSchema }).strict()) }).strict(); /** Create detached zero buckets for projection state. */ @@ -76,6 +82,25 @@ function adjustDay(days, day, usage, direction) { function routeKey(route) { return JSON.stringify([route.provider, route.model]); } +/** Stable collision-free object key for one route on one UTC date. */ +function modelDayKey(route, day) { + return JSON.stringify([ + route.provider, + route.model, + day + ]); +} +/** Add or remove one usage sample from the route-by-day aggregation table. */ +function adjustModelDay(modelDays, route, day, usage, direction) { + const key = modelDayKey(route, day); + const next = addBuckets$1(modelDays[key]?.usage ?? zeroBuckets$1(), usage, direction); + if (bucketsEqual(next, zeroBuckets$1())) delete modelDays[key]; + else modelDays[key] = { + ...route, + date: day, + usage: next + }; +} /** Whether a route record became empty after replacing its only sample. */ function recordEmpty(record) { return record.assistantRequests === 0 && record.compactionRequests === 0 && bucketsEqual(record.usage, zeroBuckets$1()); @@ -125,6 +150,7 @@ const tokenUsageRecorderProjectionDefinition = { usage: zeroBuckets$1(), models: {}, days: {}, + modelDays: {}, lastAssistant: null }), apply: (state, event) => { @@ -186,15 +212,19 @@ const tokenUsageRecorderProjectionDefinition = { }; const models = { ...state.models }; const days = { ...state.days }; + const modelDays = { ...state.modelDays }; + const day = dayKey(event.time); adjustModel(models, route, usage, 1, "compaction"); - adjustDay(days, dayKey(event.time), usage, 1); + adjustDay(days, day, usage, 1); + adjustModelDay(modelDays, route, day, usage, 1); return { ...state, compactionRequests, compactionUsage: addBuckets$1(state.compactionUsage, usage, 1), usage: addBuckets$1(state.usage, usage, 1), models, - days + days, + modelDays }; } let turn; @@ -216,21 +246,25 @@ const tokenUsageRecorderProjectionDefinition = { if (previous !== null && previous.route.provider === route.provider && previous.route.model === route.model && bucketsEqual(previous.usage, usage)) return state; const models = { ...state.models }; const days = { ...state.days }; + const modelDays = { ...state.modelDays }; let total = state.usage; if (previous !== null) { total = addBuckets$1(total, previous.usage, -1); adjustModel(models, previous.route, previous.usage, -1, "assistant"); adjustDay(days, previous.day, previous.usage, -1); + adjustModelDay(modelDays, previous.route, previous.day, previous.usage, -1); } total = addBuckets$1(total, usage, 1); adjustModel(models, route, usage, 1, "assistant"); adjustDay(days, day, usage, 1); + adjustModelDay(modelDays, route, day, usage, 1); return { ...state, assistantRequests: state.assistantRequests + (previous === null ? 1 : 0), usage: total, models, days, + modelDays, lastAssistant: { turn, step, @@ -249,9 +283,10 @@ const tokenUsageRecorderProjectionDefinition = { days: Object.entries(state.days).map(([date, usage]) => ({ date, usage - })).sort((left, right) => left.date.localeCompare(right.date)) + })).sort((left, right) => left.date.localeCompare(right.date)), + modelDays: Object.values(state.modelDays).sort((left, right) => left.date.localeCompare(right.date) || left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)) }), - stateVersion: 6 + stateVersion: 7 }; /** Fold one complete event sequence through the canonical persistent projection reducer. */ function projectTokenUsage(events) { @@ -971,6 +1006,7 @@ function modelEvidence$1(prepared) { } /** Translate an unsuccessful terminal model finish into one user-visible analysis error. */ function finishError$1(finish) { + if (finish === void 0) return /* @__PURE__ */ new Error("Trajectory analysis model stream ended without a terminal finish reason."); switch (finish.kind) { case "stop": return; case "error": @@ -1017,6 +1053,7 @@ async function analyzeTrajectory(ctx, sessionId, events, selection, language, si }, signal); signal.throwIfAborted(); const assembler = new BlockAssembler(); + let finishReason; progress.generating(); for await (const chunk of preparedCall.stream({ ...preparedCall.config, @@ -1025,12 +1062,14 @@ async function analyzeTrajectory(ctx, sessionId, events, selection, language, si signal })) { signal.throwIfAborted(); + if (finishReason !== void 0) throw new Error("Trajectory analysis model emitted data after its terminal finish reason."); assembler.push(chunk); + if (chunk.type === "finish") finishReason = chunk.reason; progress.push(chunk); } signal.throwIfAborted(); progress.finalizing(assembler.usage); - const terminalError = finishError$1(assembler.finish); + const terminalError = finishError$1(finishReason); if (terminalError !== void 0) throw terminalError; const blocks = assembler.blocks(); if (blocks.some((block) => block.type === "tool-call")) throw new Error("Trajectory analysis must return text only."); @@ -1120,8 +1159,15 @@ function analysisUsage(value) { } /** Return a terminal model error when the stream did not finish normally. */ function finishError(reason) { - if (reason === void 0 || reason.kind === "stop") return void 0; - return /* @__PURE__ */ new Error(`Usage analysis model finished with ${reason.kind}.`); + if (reason === void 0) return /* @__PURE__ */ new Error("Usage analysis model stream ended without a terminal finish reason."); + switch (reason.kind) { + case "stop": return; + case "error": + case "aborted": return Object.assign(new Error(reason.failure.message), { code: reason.failure.code }); + case "max-tokens": return /* @__PURE__ */ new Error("Usage analysis reached its output limit. Please retry with a smaller date or model scope."); + case "tool-calls": return /* @__PURE__ */ new Error("Usage analysis model unexpectedly requested a tool."); + default: return /* @__PURE__ */ new Error(`Unsupported model finish reason: ${String(reason.kind)}`); + } } /** Create a constrained, evidence-first Token efficiency review instruction. */ function systemPrompt(language) { @@ -1150,6 +1196,7 @@ async function analyzeTokenUsage(ctx, input, selection, language, signal, onProg }, signal); signal.throwIfAborted(); const assembler = new BlockAssembler(); + let finishReason; progress.generating(); for await (const chunk of preparedCall.stream({ ...preparedCall.config, @@ -1158,12 +1205,14 @@ async function analyzeTokenUsage(ctx, input, selection, language, signal, onProg signal })) { signal.throwIfAborted(); + if (finishReason !== void 0) throw new Error("Usage analysis model emitted data after its terminal finish reason."); assembler.push(chunk); + if (chunk.type === "finish") finishReason = chunk.reason; progress.push(chunk); } signal.throwIfAborted(); progress.finalizing(assembler.usage); - const terminalError = finishError(assembler.finish); + const terminalError = finishError(finishReason); if (terminalError !== void 0) throw terminalError; const blocks = assembler.blocks(); if (blocks.some((block) => block.type === "tool-call")) throw new Error("Token usage analysis must return text only."); @@ -1199,12 +1248,52 @@ const auxiliaryPlugin = { apply: installRpc }; const BUDGET_NAMESPACE = settingsNamespace(TOKEN_USAGE_SETTINGS_NAMESPACE); -const BudgetSettingsSchema = z.object({ rolling30DayBudget: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(0) }); -/** Read one safe whole-token budget from a client RPC payload. */ -function budgetFrom(payload) { - if (typeof payload !== "object" || payload === null) return void 0; - const budget = payload.rolling30DayBudget; - return typeof budget === "number" && Number.isSafeInteger(budget) && budget >= 0 ? budget : void 0; +const MAX_ROUTE_BUDGETS = 64; +const RouteBudgetSchema = z.object({ + provider: z.string().min(1).max(256), + model: z.string().min(1).max(256), + rolling30DayBudget: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER) +}); +const BudgetSettingsSchema = z.object({ + rolling30DayBudget: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(0), + routeBudgets: z.array(RouteBudgetSchema).max(MAX_ROUTE_BUDGETS).default([]) +}); +/** Read one safe whole-token count from a settings payload. */ +function budgetCount(value, allowZero) { + return typeof value === "number" && Number.isSafeInteger(value) && (allowZero ? value >= 0 : value > 0) ? value : void 0; +} +/** Read one exact-route budget from the private client wire. */ +function routeBudgetFrom(value) { + if (!isRecord(value)) return void 0; + const provider = text(value.provider, 256); + const model = text(value.model, 256); + const rolling30DayBudget = budgetCount(value.rolling30DayBudget, false); + return provider === void 0 || model === void 0 || rolling30DayBudget === void 0 ? void 0 : { + provider, + model, + rolling30DayBudget + }; +} +/** Read a bounded global or route-budget settings patch from the private client wire. */ +function budgetPatchFrom(payload) { + if (!isRecord(payload)) return void 0; + const hasGlobal = payload.rolling30DayBudget !== void 0; + const hasRoutes = payload.routeBudgets !== void 0; + if (!hasGlobal && !hasRoutes) return void 0; + const rolling30DayBudget = hasGlobal ? budgetCount(payload.rolling30DayBudget, true) : void 0; + if (hasGlobal && rolling30DayBudget === void 0) return void 0; + let routeBudgets; + if (hasRoutes) { + if (!Array.isArray(payload.routeBudgets) || payload.routeBudgets.length > MAX_ROUTE_BUDGETS) return void 0; + const parsed = payload.routeBudgets.map(routeBudgetFrom); + if (parsed.some((route) => route === void 0)) return void 0; + routeBudgets = parsed.sort((left, right) => left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)); + if (new Set(routeBudgets.map((route) => JSON.stringify([route.provider, route.model]))).size !== routeBudgets.length) return void 0; + } + return { + ...rolling30DayBudget === void 0 ? {} : { rolling30DayBudget }, + ...routeBudgets === void 0 ? {} : { routeBudgets } + }; } /** Build one standard internal error response for the private loopback channel. */ function rpcError(message) { @@ -1386,13 +1475,70 @@ function awaitWithAbort(start, signal) { }, rejectOnce); }); } +const ANALYSIS_MODEL_PROVIDER_TIMEOUT_MS = 5e3; +const ANALYSIS_MODEL_PROVIDER_RETRY_COOLDOWN_MS = 3e4; +const MAX_UNSETTLED_PROVIDER_CATALOG_CALLS = 2; +/** Reuse recent enumeration while permitting one cooldown retry without unbounded hung calls. */ +function listAnalysisProvider(runtime, provider) { + let state = runtime.catalogInFlight.get(provider); + const now = Date.now(); + if (state?.current !== void 0 && (state.active.size >= MAX_UNSETTLED_PROVIDER_CATALOG_CALLS || now - state.lastStartedAt < ANALYSIS_MODEL_PROVIDER_RETRY_COOLDOWN_MS)) return state.current; + if (state === void 0) { + state = { + active: /* @__PURE__ */ new Set(), + current: void 0, + lastStartedAt: 0 + }; + runtime.catalogInFlight.set(provider, state); + } + if (state.active.size >= MAX_UNSETTLED_PROVIDER_CATALOG_CALLS) { + const reusable = state.current ?? [...state.active][state.active.size - 1]; + if (reusable === void 0) throw new Error(`No reusable model catalog call exists for provider "${provider}".`); + return reusable; + } + const operation = Promise.resolve().then(() => runtime.llm.listModels(provider)); + state.active.add(operation); + state.current = operation; + state.lastStartedAt = now; + const settled = () => { + if (runtime.catalogInFlight.get(provider) !== state) return; + state.active.delete(operation); + if (state.current === operation) state.current = void 0; + if (state.active.size === 0) runtime.catalogInFlight.delete(provider); + }; + operation.then(settled, settled); + return operation; +} /** List selectable routes without one unavailable provider blocking healthy providers. */ async function analysisModels(ctx, signal) { + const providers = ctx.llm.listProviders(); + const providerResults = await Promise.all(providers.map(async (provider) => { + try { + return { + provider, + listed: await awaitWithAbort(() => listAnalysisProvider(ctx, provider.id), AbortSignal.any([signal, AbortSignal.timeout(ANALYSIS_MODEL_PROVIDER_TIMEOUT_MS)])) + }; + } catch (error) { + signal.throwIfAborted(); + ctx.logger.warn(`token usage: failed to list analysis models for "${provider.id}": ${String(error)}`); + return { + provider, + listed: void 0 + }; + } + })); + signal.throwIfAborted(); const models = []; const failures = []; const seen = /* @__PURE__ */ new Set(); - for (const provider of ctx.llm.listProviders()) try { - const listed = await awaitWithAbort(() => ctx.llm.listModels(provider.id), signal); + for (const { provider, listed } of providerResults) { + if (listed === void 0) { + failures.push({ + provider: provider.id, + providerName: provider.name + }); + continue; + } for (const model of listed) { const key = `${provider.id}\u0000${model.id}`; if (seen.has(key)) continue; @@ -1404,13 +1550,6 @@ async function analysisModels(ctx, signal) { modelName: model.name }); } - } catch (error) { - signal.throwIfAborted(); - ctx.logger.warn(`token usage: failed to list analysis models for "${provider.id}": ${String(error)}`); - failures.push({ - provider: provider.id, - providerName: provider.name - }); } return { models, @@ -1457,11 +1596,13 @@ function installRpc(ctx) { const lifecycle = new AbortController(); const current = { llm: analysisCtx.llm, - signal: lifecycle.signal + signal: lifecycle.signal, + catalogInFlight: /* @__PURE__ */ new Map() }; analysisRuntime = current; analysisCtx.effect(() => () => { lifecycle.abort(/* @__PURE__ */ new Error("token usage analysis service disposed")); + current.catalogInFlight.clear(); if (analysisRuntime === current) analysisRuntime = void 0; }, "token usage: analysis runtime"); } @@ -1476,10 +1617,10 @@ function installRpc(ctx) { value: budget.get() }; case TOKEN_USAGE_RPC_ENDPOINT.budgetWrite: { - const rolling30DayBudget = budgetFrom(payload); - if (rolling30DayBudget === void 0) return budgetError("Budget must be a non-negative whole Token count."); + const patch = budgetPatchFrom(payload); + if (patch === void 0) return budgetError("Budget settings must contain a valid whole-Token global budget or unique exact-route budgets."); try { - await budget.update({ rolling30DayBudget }); + await budget.update(patch); } catch (error) { return budgetError(error instanceof Error ? error.message : String(error)); } @@ -1494,7 +1635,8 @@ function installRpc(ctx) { const analysisSignal = AbortSignal.any([operationSignal, runtime.signal]); const catalog = await analysisModels({ llm: runtime.llm, - logger: ctx.logger + logger: ctx.logger, + catalogInFlight: runtime.catalogInFlight }, analysisSignal); analysisSignal.throwIfAborted(); const defaultSelection = ctx.get("agentDefaultModel")?.currentSelection(); diff --git a/package.json b/package.json index 0b5f090..bba1184 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dsh-token-usage", - "version": "0.1.0", + "version": "0.2.0", "private": true, "description": "Persistent token usage records and dashboard for DeepSeek Harness", "keywords": [ diff --git a/scripts/link-dsh-workspace.mjs b/scripts/link-dsh-workspace.mjs new file mode 100644 index 0000000..4384ecc --- /dev/null +++ b/scripts/link-dsh-workspace.mjs @@ -0,0 +1,78 @@ +import { + existsSync, + mkdirSync, + readdirSync, + readFileSync, + realpathSync, + rmSync, + symlinkSync, +} from 'node:fs' +import { dirname, join, resolve } from 'node:path' + +const harnessRoot = resolve(process.argv[2] ?? '../deepseek-harness') +const targetRoot = resolve(process.argv[3] ?? 'node_modules') +const linkType = process.platform === 'win32' ? 'junction' : 'dir' + +if (!existsSync(join(harnessRoot, 'package.json'))) { + throw new Error(`DeepSeek Harness checkout not found at ${harnessRoot}`) +} + +rmSync(targetRoot, { force: true, recursive: true }) +mkdirSync(targetRoot, { recursive: true }) + +function replaceLink(target, source) { + mkdirSync(dirname(target), { recursive: true }) + rmSync(target, { force: true, recursive: true }) + symlinkSync(realpathSync(source), target, linkType) +} + +function linkPackage(name, source) { + replaceLink(join(targetRoot, ...name.split('/')), source) +} + +function mergeInstalledPackages(sourceRoot) { + if (!existsSync(sourceRoot)) return + for (const entry of readdirSync(sourceRoot, { withFileTypes: true })) { + if (entry.name === '.bin' || entry.name === '.pnpm') continue + const source = join(sourceRoot, entry.name) + if (entry.name.startsWith('@') && entry.isDirectory()) { + for (const scoped of readdirSync(source, { withFileTypes: true })) { + if (!scoped.isDirectory() && !scoped.isSymbolicLink()) continue + linkPackage(`${entry.name}/${scoped.name}`, join(source, scoped.name)) + } + continue + } + if (entry.isDirectory() || entry.isSymbolicLink()) linkPackage(entry.name, source) + } +} + +const workspacePackages = new Map() +function discoverWorkspacePackages(directory) { + if (!existsSync(directory)) return + for (const entry of readdirSync(directory, { withFileTypes: true })) { + if (!entry.isDirectory() || entry.name === 'node_modules' || entry.name === 'lib') continue + const child = join(directory, entry.name) + const manifestPath = join(child, 'package.json') + if (existsSync(manifestPath)) { + const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) + if (typeof manifest.name === 'string') workspacePackages.set(manifest.name, child) + continue + } + discoverWorkspacePackages(child) + } +} + +const virtualStore = join(harnessRoot, 'node_modules', '.pnpm') +if (existsSync(virtualStore)) replaceLink(join(targetRoot, '.pnpm'), virtualStore) +mergeInstalledPackages(join(virtualStore, 'node_modules')) +mergeInstalledPackages(join(harnessRoot, 'node_modules')) +discoverWorkspacePackages(join(harnessRoot, 'vendor')) +discoverWorkspacePackages(join(harnessRoot, 'packages')) +discoverWorkspacePackages(join(harnessRoot, 'native')) + +for (const [name, source] of workspacePackages) linkPackage(name, source) + +const binaries = join(harnessRoot, 'node_modules', '.bin') +if (existsSync(binaries)) replaceLink(join(targetRoot, '.bin'), binaries) + +console.log(`Linked ${workspacePackages.size} DeepSeek Harness workspace packages into ${targetRoot}.`) diff --git a/src/budget-settings.ts b/src/budget-settings.ts index bbfb3ec..9badf02 100644 --- a/src/budget-settings.ts +++ b/src/budget-settings.ts @@ -1,7 +1,18 @@ /** User-owned settings namespace for the Token usage dashboard. */ export const TOKEN_USAGE_SETTINGS_NAMESPACE = 'token-usage' -/** Persisted rolling-window Token budget; zero disables the budget. */ +/** One exact provider/model rolling-window Token budget. */ +export interface TokenUsageRouteBudget { + provider: string + model: string + /** Zero is never persisted; removing a rule disables its budget. */ + rolling30DayBudget: number +} + +/** Persisted global and route-scoped rolling-window Token budgets. */ export interface TokenUsageBudgetSettings { + /** Zero disables the global budget. */ rolling30DayBudget: number + /** Exact-route budgets evaluated only when date-by-model coverage is complete. */ + routeBudgets: TokenUsageRouteBudget[] } diff --git a/src/client/TokenUsageSection.module.css b/src/client/TokenUsageSection.module.css index 1e8c8f5..10ff5c1 100644 --- a/src/client/TokenUsageSection.module.css +++ b/src/client/TokenUsageSection.module.css @@ -229,6 +229,7 @@ gap: 10px; } +.trendControls, .rangeTabs, .exportControls { display: flex; @@ -237,6 +238,10 @@ gap: 6px; } +.trendControls { + justify-content: flex-end; +} + .rangeTabs button, .exportControls button, .quietButton { @@ -273,6 +278,21 @@ font-size: 11px; } +.exportControls > small { + flex-basis: 100%; + max-width: 520px; + color: var(--dsw-alias-label-tertiary); + font-size: 10px; + line-height: 15px; + text-align: right; +} + +.exportControls button:disabled, +.quietButton:disabled { + opacity: 0.5; + cursor: not-allowed; +} + .insightNote { color: var(--dsw-alias-label-tertiary); font-size: 11px; @@ -346,6 +366,140 @@ line-height: 18px; } +.routeBudgets { + display: flex; + flex-direction: column; + gap: 10px; + border-top: 1px solid var(--dsw-alias-border-l2); + padding-top: 12px; +} + +.routeBudgetHead { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 14px; +} + +.routeBudgetHead h4, +.routeBudgetHead p { + margin: 0; +} + +.routeBudgetHead h4 { + color: var(--dsw-alias-label-primary); + font-size: 13px; + line-height: 20px; +} + +.routeBudgetHead p { + max-width: 560px; + margin-top: 2px; + color: var(--dsw-alias-label-tertiary); + font-size: 11px; + line-height: 17px; +} + +.routeBudgetEditor { + display: grid; + grid-template-columns: minmax(160px, 1fr) 132px auto; + align-items: end; + gap: 7px; +} + +.routeBudgetEditor label { + display: flex; + min-width: 0; + flex-direction: column; + gap: 3px; + color: var(--dsw-alias-label-tertiary); + font-size: 10px; +} + +.routeBudgetEditor select, +.routeBudgetEditor input { + box-sizing: border-box; + width: 100%; + height: 30px; + border: 1px solid var(--dsw-alias-border-l2); + border-radius: 7px; + padding: 0 8px; + background: var(--dsw-alias-bg-layer-1); + color: var(--dsw-alias-label-primary); + font: inherit; + font-size: 11px; +} + +.routeBudgetEditor select:focus-visible, +.routeBudgetEditor input:focus-visible { + outline: 2px solid var(--dsw-alias-state-business-primary); + outline-offset: 1px; +} + +.routeBudgetList { + display: flex; + flex-direction: column; +} + +.routeBudgetRow { + display: grid; + grid-template-columns: minmax(170px, 0.8fr) minmax(260px, 1.6fr) auto; + align-items: center; + gap: 12px; + border-top: 1px solid var(--dsw-alias-border-l2); + padding: 10px 0; +} + +.routeBudgetRow:last-child { + padding-bottom: 0; +} + +.routeBudgetIdentity { + display: flex; + min-width: 0; + flex-direction: column; + gap: 2px; +} + +.routeBudgetIdentity > strong { + overflow: hidden; + color: var(--dsw-alias-label-primary); + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.routeBudgetStatus { + color: var(--dsw-alias-label-secondary); + font-size: 10px; + font-weight: 600; + line-height: 15px; +} + +.routeBudgetStatus[data-status='warning'], +.routeBudgetStatus[data-status='forecast-exceeded'] { + color: var(--dsw-alias-state-business-primary); +} + +.routeBudgetStatus[data-status='exceeded'] { + color: var(--dsw-alias-state-error-primary); +} + +.routeBudgetStatus[data-status='unavailable'] { + color: var(--dsw-alias-label-tertiary); +} + +.routeBudgetUsage { + display: flex; + min-width: 0; + flex-direction: column; + gap: 3px; +} + +.routeBudgetRow > .quietButton { + justify-self: end; +} + .contributors { display: flex; flex-direction: column; @@ -1087,6 +1241,15 @@ .analysisSummaryGrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + + .routeBudgetHead { + align-items: stretch; + flex-direction: column; + } + + .routeBudgetRow { + grid-template-columns: minmax(160px, 0.8fr) minmax(240px, 1.4fr) auto; + } } @media (max-width: 580px) { @@ -1111,6 +1274,23 @@ justify-content: flex-start; } + .exportControls > small { + text-align: left; + } + + .routeBudgetEditor, + .routeBudgetRow { + grid-template-columns: minmax(0, 1fr); + } + + .routeBudgetRow { + gap: 8px; + } + + .routeBudgetRow > .quietButton { + justify-self: start; + } + .budgetInput { justify-content: space-between; } diff --git a/src/client/TokenUsageSection.tsx b/src/client/TokenUsageSection.tsx index ef42121..383b51b 100644 --- a/src/client/TokenUsageSection.tsx +++ b/src/client/TokenUsageSection.tsx @@ -4,6 +4,7 @@ import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-cli import type { TokenUsageProjection } from '@deepseek-ai/dsh-token-meter/client' import type { DailyTokenUsageRecord, + ModelDailyTokenUsageRecord, ModelTokenUsageRecord, PricedModelTokenUsageRecord, TokenUsageAnalysis, @@ -18,9 +19,11 @@ import type { import type { TokenUsageBudgetSnapshot } from './budget-controller.ts' import { dailyAnomalyInsight, dailyContributors, periodInsight, runRateInsight } from './analytics.ts' import { usageEfficiencyInsight } from './efficiency.ts' +import { routeBudgetInsights, type RouteBudgetInsight } from './route-budget.ts' import { analysisReportFilename, dailyUsageCsv, + modelDailyUsageCsv, modelUsageCsv, tokenUsageAnalysisMarkdown, tokenUsageJson, @@ -38,6 +41,7 @@ interface TokenUsageSectionInjected { budget: ObservableSnapshot } setBudget(value: number): Promise + setRouteBudget(provider: string, model: string, value: number): Promise download: DownloadPort saveTrajectoryAnalysis(analysis: TrajectoryAnalysis): void openSession(sessionId: SessionId): void @@ -71,7 +75,9 @@ interface SessionUsageRow { usage: TokenUsageBuckets models: readonly ModelTokenUsageRecord[] days: readonly DailyTokenUsageRecord[] + modelDays: readonly ModelDailyTokenUsageRecord[] dailyUsageReliable: boolean + modelDailyUsageReliable: boolean } interface DashboardData { @@ -82,8 +88,10 @@ interface DashboardData { sessions: SessionUsageRow[] models: ModelTokenUsageRecord[] days: DailyTokenUsageRecord[] + modelDays: ModelDailyTokenUsageRecord[] operationalDays: DailyTokenUsageRecord[] dailyCoverage: 'complete' | 'partial' | 'unavailable' + modelDailyCoverage: 'complete' | 'partial' | 'unavailable' } type InsightRange = 7 | 30 | 90 @@ -128,6 +136,56 @@ function addBuckets(left: TokenUsageBuckets, right: TokenUsageBuckets): TokenUsa } } +/** Whether two detached provider bucket sets are exactly conserved. */ +function sameBuckets(left: TokenUsageBuckets, right: TokenUsageBuckets): boolean { + return left.uncachedInputTokens === right.uncachedInputTokens + && left.outputTokens === right.outputTokens + && left.cacheReadTokens === right.cacheReadTokens + && left.cacheWriteTokens === right.cacheWriteTokens +} + +/** Sum detached bucket records without retaining projection-owned objects. */ +function summedUsage(records: readonly { usage: TokenUsageBuckets }[]): TokenUsageBuckets { + return records.reduce((sum, record) => addBuckets(sum, record.usage), zeroBuckets()) +} + +/** Compare two aggregate maps without accepting missing or extra keys. */ +function sameUsageMap( + actual: ReadonlyMap, + expected: ReadonlyMap, +): boolean { + return actual.size === expected.size + && [...expected].every(([key, usage]) => { + const value = actual.get(key) + return value !== undefined && sameBuckets(value, usage) + }) +} + +/** Verify daily buckets conserve the session-level projection total. */ +function dailyUsageConserved(recorded: TokenUsageRecorderProjection): boolean { + return sameBuckets(summedUsage(recorded.days), recorded.usage) +} + +/** Verify date-by-model buckets conserve totals across session, route, and UTC day dimensions. */ +function modelDailyUsageConserved(recorded: TokenUsageRecorderProjection): boolean { + if (!sameBuckets(summedUsage(recorded.modelDays), recorded.usage)) return false + const routeTotals = new Map() + const dayTotals = new Map() + for (const record of recorded.modelDays) { + if (totalTokens(record.usage) === 0) continue + const route = modelKey(record) + routeTotals.set(route, addBuckets(routeTotals.get(route) ?? zeroBuckets(), record.usage)) + dayTotals.set(record.date, addBuckets(dayTotals.get(record.date) ?? zeroBuckets(), record.usage)) + } + const expectedRoutes = new Map(recorded.models + .filter(model => totalTokens(model.usage) > 0) + .map(model => [modelKey(model), model.usage])) + const expectedDays = new Map(recorded.days + .filter(day => totalTokens(day.usage) > 0) + .map(day => [day.date, day.usage])) + return sameUsageMap(routeTotals, expectedRoutes) && sameUsageMap(dayTotals, expectedDays) +} + /** Stable UTC day key used by durable Host records and legacy fallbacks. */ function dayKey(time: number): string { return new Date(time).toISOString().slice(0, 10) @@ -199,8 +257,13 @@ function modelKey(model: Pick): str return JSON.stringify([model.provider, model.model]) } +/** Stable provider/model/date identity for route-day aggregation. */ +function modelDayKey(model: Pick): string { + return JSON.stringify([model.provider, model.model, model.date]) +} + /** Compact route label retained in the session table. */ -function routeLabel(model: ModelTokenUsageRecord): string { +function routeLabel(model: Pick): string { return `${model.provider}/${model.model}` } @@ -262,6 +325,10 @@ function sessionRow(summary: SessionSummary): SessionUsageRow | null { const assistantRequests = recorded?.assistantRequests ?? 0 const compactionRequests = recorded?.compactionRequests ?? 0 if (usage === undefined || (totalTokens(usage) === 0 && assistantRequests === 0 && compactionRequests === 0)) return null + const dailyUsageReliable = recorded?.days !== undefined && dailyUsageConserved(recorded) + const modelDailyUsageReliable = recorded?.modelDays !== undefined + && dailyUsageReliable + && modelDailyUsageConserved(recorded) return { id: summary.id, title: summary.displayTitle, @@ -272,7 +339,9 @@ function sessionRow(summary: SessionSummary): SessionUsageRow | null { usage, models: recorded?.models ?? [unattributedModel(usage)], days: recorded?.days ?? [{ date: dayKey(summary.updatedAt), usage }], - dailyUsageReliable: recorded?.days !== undefined, + modelDays: recorded?.modelDays ?? [], + dailyUsageReliable, + modelDailyUsageReliable, } } @@ -281,12 +350,14 @@ export function aggregateUsage(summaries: readonly SessionSummary[]): DashboardD const sessions: SessionUsageRow[] = [] const models = new Map() const days = new Map() + const modelDays = new Map() const operationalDays = new Map() let usage = zeroBuckets() let assistantRequests = 0 let compactionRequests = 0 let compactionUsage = zeroBuckets() let reliableDailySessions = 0 + let reliableModelDailySessions = 0 for (const summary of summaries) { const row = sessionRow(summary) @@ -297,12 +368,24 @@ export function aggregateUsage(summaries: readonly SessionSummary[]): DashboardD compactionRequests += row.compactionRequests compactionUsage = addBuckets(compactionUsage, row.compactionUsage) if (row.dailyUsageReliable) reliableDailySessions += 1 + if (row.modelDailyUsageReliable) reliableModelDailySessions += 1 for (const day of row.days) { days.set(day.date, addBuckets(days.get(day.date) ?? zeroBuckets(), day.usage)) if (row.dailyUsageReliable) { operationalDays.set(day.date, addBuckets(operationalDays.get(day.date) ?? zeroBuckets(), day.usage)) } } + for (const modelDay of row.modelDays) { + const key = modelDayKey(modelDay) + const current = modelDays.get(key) + modelDays.set(key, current === undefined ? { + ...modelDay, + usage: { ...modelDay.usage }, + } : { + ...current, + usage: addBuckets(current.usage, modelDay.usage), + }) + } for (const model of row.models) { const key = modelKey(model) const current = models.get(key) @@ -332,15 +415,29 @@ export function aggregateUsage(summaries: readonly SessionSummary[]): DashboardD days: [...days.entries()] .map(([date, usage]): DailyTokenUsageRecord => ({ date, usage })) .sort((left, right) => left.date.localeCompare(right.date)), + modelDays: [...modelDays.values()].sort((left, right) => + left.date.localeCompare(right.date) + || left.provider.localeCompare(right.provider) + || left.model.localeCompare(right.model)), operationalDays: [...operationalDays.entries()] .map(([date, usage]): DailyTokenUsageRecord => ({ date, usage })) .sort((left, right) => left.date.localeCompare(right.date)), dailyCoverage: reliableDailySessions === 0 ? 'unavailable' : reliableDailySessions === sessions.length ? 'complete' : 'partial', + modelDailyCoverage: reliableModelDailySessions === 0 + ? 'unavailable' + : reliableModelDailySessions === sessions.length ? 'complete' : 'partial', } } +/** Return one exact route's reliable daily buckets from the aggregate route-day table. */ +function modelTrendDays(modelDays: readonly ModelDailyTokenUsageRecord[], route: string): DailyTokenUsageRecord[] { + return modelDays + .filter(modelDay => modelKey(modelDay) === route) + .map(modelDay => ({ date: modelDay.date, usage: { ...modelDay.usage } })) +} + /** Return only detached aggregate buckets, route records, and UTC dates for AI usage analysis. */ export function usageAnalysisInput(data: Pick): TokenUsageAnalysisInput { return { @@ -530,12 +627,20 @@ function DayDrilldown({ function PeriodInsights({ days, range, + models, + selectedModel, + modelDailyCoverage, onRangeChange, + onModelChange, t, }: { days: readonly DailyTokenUsageRecord[] range: InsightRange + models: readonly ModelTokenUsageRecord[] + selectedModel: string + modelDailyCoverage: DashboardData['modelDailyCoverage'] onRangeChange(range: InsightRange): void + onModelChange(model: string): void t: TokenUsageSectionProps['t'] }): ReactNode { const insight = useMemo(() => periodInsight(days, range), [days, range]) @@ -543,6 +648,15 @@ function PeriodInsights({ const previous = totalTokens(insight.previousUsage) const delta = previous === 0 ? undefined : Math.round((current - previous) / previous * 100) const peak = insight.peak + const selectedRoute = models.find(model => modelKey(model) === selectedModel) + let modelCoverageNote: ReactNode = null + if (modelDailyCoverage !== 'complete') { + modelCoverageNote =

{t(modelDailyCoverage === 'partial' + ? 'modelDailyCoveragePartial' + : 'modelDailyCoverageUnavailable')}

+ } else if (selectedRoute !== undefined) { + modelCoverageNote =

{t('trendModelScope', { route: routeLabel(selectedRoute) })}

+ } return (
@@ -550,15 +664,29 @@ function PeriodInsights({

{t('trend')}

{t('trendIntro')}

-
- {([7, 30, 90] as const).map(value => ( - - ))} +
+ +
+ {([7, 30, 90] as const).map(value => ( + + ))} +
@@ -568,22 +696,31 @@ function PeriodInsights({
{peak === undefined ? null :

{t('peakDayNote', { date: peak.date, total: formatTokens(totalTokens(peak.usage)) })}

} + {modelCoverageNote}
) } -/** Render the persistent trailing-30-day budget setting and progress. */ +/** Render persistent global and exact-route rolling Token budgets. */ function BudgetPanel({ operationalDays, dailyCoverage, + models, + modelDays, + modelDailyCoverage, snapshot, setBudget, + setRouteBudget, t, }: { operationalDays: readonly DailyTokenUsageRecord[] dailyCoverage: DashboardData['dailyCoverage'] + models: readonly ModelTokenUsageRecord[] + modelDays: readonly ModelDailyTokenUsageRecord[] + modelDailyCoverage: DashboardData['modelDailyCoverage'] snapshot: TokenUsageBudgetSnapshot setBudget(value: number): Promise + setRouteBudget(provider: string, model: string, value: number): Promise t: TokenUsageSectionProps['t'] }): ReactNode { const insight = useMemo(() => periodInsight(operationalDays, 30), [operationalDays]) @@ -594,9 +731,33 @@ function BudgetPanel({ const enabled = budget > 0 const durableValue = enabled ? String(budget) : '' const [draft, setDraft] = useState(durableValue) + const [routeSelection, setRouteSelection] = useState('') + const [routeDraft, setRouteDraft] = useState('') const editGeneration = useRef(0) const dirtyDraft = useRef(false) const ratio = enabled ? used / budget : 0 + const configurableModels = useMemo( + () => models.filter(model => !isUnattributed(model) && totalTokens(model.usage) > 0), + [models], + ) + const persistedRouteBudgets = snapshot.routeBudgets ?? [] + const routeInsights = useMemo( + () => modelDailyCoverage === 'complete' ? routeBudgetInsights(persistedRouteBudgets, modelDays) : [], + [modelDailyCoverage, modelDays, persistedRouteBudgets], + ) + const routeInsightByKey = useMemo( + () => new Map(routeInsights.map(route => [modelKey(route), route])), + [routeInsights], + ) + const routeRows = persistedRouteBudgets + .map(route => ({ route, insight: routeInsightByKey.get(modelKey(route)) })) + .sort((left, right) => { + const insightOrder = routeInsights.findIndex(insight => modelKey(insight) === modelKey(left.route)) + - routeInsights.findIndex(insight => modelKey(insight) === modelKey(right.route)) + return modelDailyCoverage === 'complete' && insightOrder !== 0 + ? insightOrder + : left.route.provider.localeCompare(right.route.provider) || left.route.model.localeCompare(right.route.model) + }) useEffect(() => { if (!dirtyDraft.current) setDraft(durableValue) }, [durableValue, snapshot.status]) @@ -622,6 +783,14 @@ function BudgetPanel({ }, ) } + const saveRoute = (): void => { + const selected = configurableModels.find(model => modelKey(model) === routeSelection) + const next = Number(routeDraft) + if (selected === undefined || !Number.isSafeInteger(next) || next <= 0) return + void setRouteBudget(selected.provider, selected.model, next).then(() => { + setRouteDraft('') + }) + } return (
@@ -664,12 +833,97 @@ function BudgetPanel({ {t('budgetProgress', { used: formatCompactTokens(used), budget: formatCompactTokens(budget), percent: Math.round(ratio * 100) })}
- {ratio > 1 ?

{t('budgetExceeded', { excess: formatCompactTokens(used - budget) })}

: null} - {runRateAvailable && ratio <= 1 && runRate.projectedThirtyDayTokens > budget ?

{t('budgetForecastExceeded', { + {ratio >= 1 ?

{t('budgetExceeded', { excess: formatCompactTokens(Math.max(0, used - budget)) })}

: null} + {runRateAvailable && ratio < 1 && runRate.projectedThirtyDayTokens > budget ?

{t('budgetForecastExceeded', { projected: formatCompactTokens(runRate.projectedThirtyDayTokens), budget: formatCompactTokens(budget), })}

: null} } +
+
+
+

{t('routeBudgets')}

+

{t('routeBudgetsIntro')}

+
+
+ + + +
+
+ {modelDailyCoverage === 'complete' ? null :

{t(modelDailyCoverage === 'partial' + ? 'routeBudgetCoveragePartial' + : 'routeBudgetCoverageUnavailable')}

} + {persistedRouteBudgets.length === 0 ?

{t('routeBudgetsEmpty')}

:
+ {routeRows.map(({ route, insight }) =>
+
+ {routeLabel(route)} + {insight === undefined ? t('routeBudgetUnavailable') : t(`routeBudgetStatus_${insight.status}`)} +
+ {insight === undefined ?
:
+
+ + {t('routeBudgetProgress', { + used: formatCompactTokens(insight.usedTokens), + budget: formatCompactTokens(insight.rolling30DayBudget), + percent: Math.round(insight.ratio * 100), + })} +
+

{t('routeBudgetForecast', { projected: formatCompactTokens(insight.projectedThirtyDayTokens) })}

+
} + +
)} +
} +
) } @@ -787,7 +1041,7 @@ function ExportControls({ download: DownloadPort t: TokenUsageSectionProps['t'] }): ReactNode { - const save = (kind: 'json' | 'daily' | 'models'): void => { + const save = (kind: 'json' | 'daily' | 'models' | 'modelDaily'): void => { const generatedAt = new Date().toISOString() const date = generatedAt.slice(0, 10) switch (kind) { @@ -799,6 +1053,9 @@ function ExportControls({ return case 'models': download.save(`dsh-token-usage-models-${date}.csv`, 'text/csv;charset=utf-8', modelUsageCsv(data)) + return + case 'modelDaily': + download.save(`dsh-token-usage-model-daily-${date}.csv`, 'text/csv;charset=utf-8', modelDailyUsageCsv(data)) } } return ( @@ -807,6 +1064,13 @@ function ExportControls({ + + {data.modelDailyCoverage === 'complete' ? null : {t('exportModelDailyUnavailable')}}
) } @@ -893,7 +1157,11 @@ function UsageAnalysisPanel({ return

{t('usageAnalysis')}

{t('analysisModelsLoading')}

} if (catalog.status === 'error') { - return

{t('usageAnalysis')}

{t('analysisModelsFailed', { message: catalog.message })}

+ return
+

{t('usageAnalysis')}

+

{t('analysisModelsFailed', { message: catalog.message })}

+ +
} const catalogFailures = catalog.value.failures ?? [] if (catalog.value.models.length === 0 || selectedModel === undefined) { @@ -1093,6 +1361,7 @@ export function TokenUsageSection({ useSessions, useBudget, setBudget, + setRouteBudget, download, saveTrajectoryAnalysis, openSession, @@ -1109,6 +1378,7 @@ export function TokenUsageSection({ const [modelSort, setModelSort] = useState('total') const [sessionLimit, setSessionLimit] = useState(SESSION_PAGE_SIZE) const [range, setRange] = useState(30) + const [trendModel, setTrendModel] = useState('') const [selectedDate, setSelectedDate] = useState() const [operationalDrilldown, setOperationalDrilldown] = useState(false) const [sessionOpenError, setSessionOpenError] = useState() @@ -1187,6 +1457,18 @@ export function TokenUsageSection({ () => aggregateUsage(ids.map(id => byId[id]).filter((value): value is SessionSummary => value !== undefined)), [byId, ids], ) + const trendModels = useMemo( + () => data.models.filter(model => !isUnattributed(model) && totalTokens(model.usage) > 0), + [data.models], + ) + const selectedTrendModel = data.modelDailyCoverage === 'complete' + && trendModels.some(model => modelKey(model) === trendModel) + ? trendModel + : '' + const trendDays = useMemo( + () => selectedTrendModel === '' ? data.days : modelTrendDays(data.modelDays, selectedTrendModel), + [data.days, data.modelDays, selectedTrendModel], + ) const runUsageAnalysis = (): void => { if (selectedAnalysisModel === undefined) return usageController.current?.abort() @@ -1273,7 +1555,16 @@ export function TokenUsageSection({ setSelectedDate(undefined) setOperationalDrilldown(false) }} />} - + - +
{t('pricingTitle')}

{t('pricingIntro', { diff --git a/src/client/TrajectoryAnalysisAction.tsx b/src/client/TrajectoryAnalysisAction.tsx index 0e0a71c..ae70143 100644 --- a/src/client/TrajectoryAnalysisAction.tsx +++ b/src/client/TrajectoryAnalysisAction.tsx @@ -93,8 +93,12 @@ export function TrajectoryAnalysisAction({ } const closeModal = (): void => { + catalogController.current?.abort() + catalogController.current = undefined analysisController.current?.abort() analysisController.current = undefined + setSelectedModel(undefined) + setCatalog({ status: 'idle' }) setAnalysis(current => current.status === 'loading' ? { status: 'idle' } : current) setOpen(false) } diff --git a/src/client/analysis-progress-client.ts b/src/client/analysis-progress-client.ts index f69280b..a76b2f9 100644 --- a/src/client/analysis-progress-client.ts +++ b/src/client/analysis-progress-client.ts @@ -79,6 +79,7 @@ export async function requestAnalysisWithProgress( { progressId }, pollingSignal, ) + if (pollingSignal.aborted) return if (result.ok) { const progress = analysisProgressOf(result.value) if (progress !== undefined) onProgress?.(progress) diff --git a/src/client/budget-controller.ts b/src/client/budget-controller.ts index 87d6bad..a0ace68 100644 --- a/src/client/budget-controller.ts +++ b/src/client/budget-controller.ts @@ -1,14 +1,32 @@ import type { ConnectionHandle } from '@deepseek-ai/dsh-client-connection/client' -import type { TokenUsageBudgetSettings } from '../budget-settings.ts' +import type { TokenUsageBudgetSettings, TokenUsageRouteBudget } from '../budget-settings.ts' import { TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT } from '../rpc.ts' -/** One browser-visible state of the persistent rolling budget. */ +/** One browser-visible state of the persistent global and exact-route budgets. */ export interface TokenUsageBudgetSnapshot { status: 'loading' | 'ready' | 'unavailable' budget: number + routeBudgets: readonly TokenUsageRouteBudget[] } -const INITIAL: TokenUsageBudgetSnapshot = { status: 'loading', budget: 0 } +const INITIAL: TokenUsageBudgetSnapshot = { status: 'loading', budget: 0, routeBudgets: [] } +const MAX_ROUTE_BUDGETS = 64 + +/** Stable exact-route identity for settings comparisons and updates. */ +function routeKey(route: Pick): string { + return JSON.stringify([route.provider, route.model]) +} + +/** Whether two normalized route-budget lists contain the same settings. */ +function sameRouteBudgets(left: readonly TokenUsageRouteBudget[], right: readonly TokenUsageRouteBudget[]): boolean { + return left.length === right.length && left.every((route, index) => { + const other = right[index] + return other !== undefined + && route.provider === other.provider + && route.model === other.model + && route.rolling30DayBudget === other.rolling30DayBudget + }) +} /** Minimal stable observable source consumed by the Settings slot hook binder. */ class BudgetStore { @@ -25,18 +43,39 @@ class BudgetStore { } set(next: TokenUsageBudgetSnapshot): void { - if (this.snapshot.status === next.status && this.snapshot.budget === next.budget) return + if (this.snapshot.status === next.status + && this.snapshot.budget === next.budget + && sameRouteBudgets(this.snapshot.routeBudgets, next.routeBudgets)) return this.snapshot = next for (const listener of [...this.listeners]) listener() } } -/** Decode the one numeric setting returned by the private Host RPC. */ +/** Decode normalized budget settings returned by the private Host RPC. */ function settingsOf(value: unknown): TokenUsageBudgetSettings | undefined { if (typeof value !== 'object' || value === null) return undefined const budget = (value as { rolling30DayBudget?: unknown }).rolling30DayBudget if (typeof budget !== 'number' || !Number.isSafeInteger(budget) || budget < 0) return undefined - return { rolling30DayBudget: budget } + const rawRoutes = (value as { routeBudgets?: unknown }).routeBudgets ?? [] + if (!Array.isArray(rawRoutes) || rawRoutes.length > MAX_ROUTE_BUDGETS) return undefined + const routeBudgets: TokenUsageRouteBudget[] = [] + const seen = new Set() + for (const value of rawRoutes) { + if (typeof value !== 'object' || value === null) return undefined + const provider = (value as { provider?: unknown }).provider + const model = (value as { model?: unknown }).model + const rolling30DayBudget = (value as { rolling30DayBudget?: unknown }).rolling30DayBudget + if (typeof provider !== 'string' || provider.length === 0 || provider.length > 256 + || typeof model !== 'string' || model.length === 0 || model.length > 256 + || typeof rolling30DayBudget !== 'number' || !Number.isSafeInteger(rolling30DayBudget) || rolling30DayBudget <= 0) return undefined + const route = { provider, model, rolling30DayBudget } + const key = routeKey(route) + if (seen.has(key)) return undefined + seen.add(key) + routeBudgets.push(route) + } + routeBudgets.sort((left, right) => left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)) + return { rolling30DayBudget: budget, routeBudgets } } /** Mirror the private Host settings endpoint onto one HMR-safe observable source. */ @@ -51,53 +90,78 @@ export class TokenUsageBudgetController { /** @param connection - client connection carrying the loopback RPC channel. */ constructor(private readonly connection: ConnectionHandle) {} - /** Fetch the durable budget unless the current page cannot call loopback-only endpoints. */ + /** Fetch durable budgets unless the current page cannot call loopback-only endpoints. */ async load(): Promise { const generation = ++this.generation if (!this.connection.isLoopback) { - this.publish(generation, { status: 'unavailable', budget: 0 }) + this.publish(generation, { status: 'unavailable', budget: 0, routeBudgets: [] }) return } try { const result = await this.connection.rpc.call(TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT.budgetRead, {}) const settings = result.ok ? settingsOf(result.value) : undefined this.publish(generation, settings === undefined - ? { status: 'unavailable', budget: 0 } - : { status: 'ready', budget: settings.rolling30DayBudget }) + ? { status: 'unavailable', budget: 0, routeBudgets: [] } + : { status: 'ready', budget: settings.rolling30DayBudget, routeBudgets: settings.routeBudgets }) } catch (_budgetReadFailure) { - this.publish(generation, { status: 'unavailable', budget: 0 }) + this.publish(generation, { status: 'unavailable', budget: 0, routeBudgets: [] }) } } - /** Persist one whole-token rolling budget and return the value that remains durable. */ + /** Persist one whole-token global rolling budget and return the durable value. */ setBudget(rolling30DayBudget: number): Promise { if (!Number.isSafeInteger(rolling30DayBudget) || rolling30DayBudget < 0) { return Promise.resolve(this.store.getSnapshot().budget) } - const operation = this.writeQueue.then(() => this.writeBudget(rolling30DayBudget)) - this.writeQueue = operation.then(() => undefined, () => undefined) - return operation + return this.enqueue(async () => (await this.writeSettings({ rolling30DayBudget })).budget) + } + + /** Add, replace, or remove one exact-route rolling budget; zero removes it. */ + setRouteBudget(provider: string, model: string, rolling30DayBudget: number): Promise { + if (provider.length === 0 || provider.length > 256 || model.length === 0 || model.length > 256 + || !Number.isSafeInteger(rolling30DayBudget) || rolling30DayBudget < 0) return Promise.resolve() + return this.enqueue(async () => { + const current = this.store.getSnapshot().routeBudgets + const key = routeKey({ provider, model }) + const routeBudgets = current + .filter(route => routeKey(route) !== key) + .concat(rolling30DayBudget === 0 ? [] : [{ provider, model, rolling30DayBudget }]) + .sort((left, right) => left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)) + if (routeBudgets.length > MAX_ROUTE_BUDGETS) return + await this.writeSettings({ routeBudgets }) + }) + } + + /** Serialize settings writes so each patch is based on the latest durable snapshot. */ + private enqueue(operation: () => Promise): Promise { + const queued = this.writeQueue.then(operation) + this.writeQueue = queued.then(() => undefined, () => undefined) + return queued } - /** Execute one queued budget write against the latest published durable value. */ - private async writeBudget(rolling30DayBudget: number): Promise { - if (this.disposed) return this.store.getSnapshot().budget + /** Execute one queued settings patch and publish the Host-returned durable value. */ + private async writeSettings(payload: Record): Promise { + if (this.disposed) return this.store.getSnapshot() const previous = this.store.getSnapshot() - const fallback = previous.status === 'ready' ? previous : { status: 'unavailable' as const, budget: 0 } + const fallback = previous.status === 'ready' + ? previous + : { status: 'unavailable' as const, budget: 0, routeBudgets: [] } const generation = ++this.generation if (!this.connection.isLoopback) { this.publish(generation, fallback) - return fallback.budget + return fallback } try { - const result = await this.connection.rpc.call(TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT.budgetWrite, { rolling30DayBudget }) + const result = await this.connection.rpc.call(TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT.budgetWrite, payload) const settings = result.ok ? settingsOf(result.value) : undefined - const next = settings === undefined ? fallback : { status: 'ready' as const, budget: settings.rolling30DayBudget } + const next = settings === undefined + ? fallback + : { status: 'ready' as const, budget: settings.rolling30DayBudget, routeBudgets: settings.routeBudgets } this.publish(generation, next) - return next.budget + return next } catch (_budgetWriteFailure) { this.publish(generation, fallback) - return fallback.budget + return fallback } } diff --git a/src/client/export.ts b/src/client/export.ts index e1f93c5..9594bbd 100644 --- a/src/client/export.ts +++ b/src/client/export.ts @@ -1,5 +1,6 @@ import type { DailyTokenUsageRecord, + ModelDailyTokenUsageRecord, ModelTokenUsageRecord, PricedModelTokenUsageRecord, TokenUsageAnalysis, @@ -16,19 +17,27 @@ export interface UsageExportSource { compactionUsage: TokenUsageBuckets models: readonly ModelTokenUsageRecord[] days: readonly DailyTokenUsageRecord[] + modelDays: readonly ModelDailyTokenUsageRecord[] + dailyCoverage: 'complete' | 'partial' | 'unavailable' + modelDailyCoverage: 'complete' | 'partial' | 'unavailable' } -/** Versioned aggregate-only JSON document with exact compaction and partial public-rate pricing. */ -export interface TokenUsageExportV2 { - schema: 'dsh-token-usage/export-v2' +/** Versioned aggregate-only JSON document with route-by-day usage and partial public-rate pricing. */ +export interface TokenUsageExportV3 { + schema: 'dsh-token-usage/export-v3' generatedAt: string timezone: 'UTC' totals: TokenUsageBuckets compactionUsage: TokenUsageBuckets pricingCatalogAsOf: string pricing: TokenUsageCostSummary + coverage: { + daily: UsageExportSource['dailyCoverage'] + modelDaily: UsageExportSource['modelDailyCoverage'] + } models: PricedModelTokenUsageRecord[] days: DailyTokenUsageRecord[] + modelDays: ModelDailyTokenUsageRecord[] } /** Browser download Adapter owned by the Client plugin registration. */ @@ -65,22 +74,36 @@ function copiedPricing(models: readonly ModelTokenUsageRecord[]): TokenUsageCost } /** Stable aggregate-only document that never accepts session data. */ -export function tokenUsageExport(source: UsageExportSource, generatedAt: string): TokenUsageExportV2 { +export function tokenUsageExport(source: UsageExportSource, generatedAt: string): TokenUsageExportV3 { const pricing = copiedPricing(source.models) return { - schema: 'dsh-token-usage/export-v2', + schema: 'dsh-token-usage/export-v3', generatedAt, timezone: 'UTC', totals: copyBuckets(source.usage), compactionUsage: copyBuckets(source.compactionUsage), pricingCatalogAsOf: PUBLIC_PRICE_CATALOG_AS_OF, pricing, + coverage: { + daily: source.dailyCoverage, + modelDaily: source.modelDailyCoverage, + }, models: pricing.models .slice() .sort((left, right) => left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)), days: source.days .map(day => ({ date: day.date, usage: copyBuckets(day.usage) })) .sort((left, right) => left.date.localeCompare(right.date)), + modelDays: source.modelDays + .map(modelDay => ({ + provider: modelDay.provider, + model: modelDay.model, + date: modelDay.date, + usage: copyBuckets(modelDay.usage), + })) + .sort((left, right) => left.date.localeCompare(right.date) + || left.provider.localeCompare(right.provider) + || left.model.localeCompare(right.model)), } } @@ -91,7 +114,7 @@ export function tokenUsageJson(source: UsageExportSource, generatedAt: string): /** Prevent spreadsheet applications from interpreting an untrusted cell as a formula. */ function spreadsheetText(value: string): string { - return /^[=+\-@]/.test(value) ? `'${value}` : value + return /^[\u0000-\u0020]*[=+\-@]/.test(value) ? `'${value}` : value } /** Escape one scalar value as a CSV cell. */ @@ -124,6 +147,32 @@ export function dailyUsageCsv(source: Pick): string { ]) } +/** Export exact route-by-day buckets only when the source proves complete coverage. */ +export function modelDailyUsageCsv(source: Pick): string { + if (source.modelDailyCoverage !== 'complete') { + throw new Error('Date-by-model CSV requires complete and conserved route-day coverage.') + } + return csv([ + ['date', 'provider', 'model', 'uncachedInputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens', 'inputTokens', 'totalTokens'], + ...source.modelDays + .slice() + .sort((left, right) => left.date.localeCompare(right.date) + || left.provider.localeCompare(right.provider) + || left.model.localeCompare(right.model)) + .map(modelDay => [ + modelDay.date, + modelDay.provider, + modelDay.model, + modelDay.usage.uncachedInputTokens, + modelDay.usage.outputTokens, + modelDay.usage.cacheReadTokens, + modelDay.usage.cacheWriteTokens, + modelDay.usage.uncachedInputTokens + modelDay.usage.cacheReadTokens + modelDay.usage.cacheWriteTokens, + modelDay.usage.uncachedInputTokens + modelDay.usage.cacheReadTokens + modelDay.usage.cacheWriteTokens + modelDay.usage.outputTokens, + ]), + ]) +} + /** Export model aggregate buckets without session identity or conversation content. */ export function modelUsageCsv(source: Pick): string { const models = tokenUsageCostSummary(source.models).models diff --git a/src/client/index.ts b/src/client/index.ts index 7969ceb..ee9196c 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -52,6 +52,7 @@ export function apply(ctx: ClientContext): void { inject: () => ({ hooks: { budget: budget.store }, setBudget: (value: number) => budget.setBudget(value), + setRouteBudget: (provider: string, model: string, value: number) => budget.setRouteBudget(provider, model, value), download: browserDownload, saveTrajectoryAnalysis: (analysis: TrajectoryAnalysis) => { trajectoryHistory.save(analysis) }, openSession: (sessionId: SessionId) => { ctx.sessions.open(sessionId) }, diff --git a/src/client/locales.ts b/src/client/locales.ts index 289a8b3..4854a02 100644 --- a/src/client/locales.ts +++ b/src/client/locales.ts @@ -21,6 +21,11 @@ export const zh = { more: '多', trend: '用量趋势', trendIntro: '按 UTC 日期比较当前周期与前一等长周期。', + trendModel: '趋势模型', + allModels: '全部模型', + trendModelScope: '当前趋势仅统计 {route} 的可靠逐日 bucket。', + modelDailyCoverageUnavailable: '旧版回退记录没有日期×模型 bucket;完成历史重折叠后才能按模型筛选趋势。', + modelDailyCoveragePartial: '部分历史记录缺少日期×模型 bucket;为避免低估,模型趋势筛选暂不可用。', rangeDays: '{count} 天', periodTokens: '最近 {count} 天', periodChange: '较前一周期', @@ -36,6 +41,24 @@ export const zh = { budgetExceeded: '已超出预算 {excess} Token。', budgetRunRate: '按最近 7 个完整 UTC 日:日均 {average} Token,预计滚动 30 日 {projected} Token。', budgetForecastExceeded: '按当前运行率预计 30 日 {projected} Token,将超过预算 {budget} Token。', + routeBudgets: '模型预算与预警', + routeBudgetsIntro: '为精确 provider/model 设置滚动 30 日 Token 预算;达到 80% 预警,达到 100% 或预测超额时优先提示。', + routeBudgetModel: '模型路由', + routeBudgetChooseModel: '选择有用量的模型', + routeBudgetInput: '模型 30 日预算(Token)', + routeBudgetSave: '保存模型预算', + routeBudgetRemove: '移除预算', + routeBudgetRemoveFor: '移除 {route} 的预算', + routeBudgetsEmpty: '尚未设置模型级预算。', + routeBudgetCoverageUnavailable: '没有完整且守恒的日期×模型数据;可配置预算,但暂不计算消耗和预测。', + routeBudgetCoveragePartial: '部分日期×模型数据缺失或无法与总量守恒;为避免低估,模型预算状态暂不可用。', + routeBudgetUnavailable: '等待可靠数据', + routeBudgetStatus_healthy: '健康', + routeBudgetStatus_warning: '已达 80% 预警', + 'routeBudgetStatus_forecast-exceeded': '预测将超额', + routeBudgetStatus_exceeded: '已超额', + routeBudgetProgress: '已用 {used} / {budget} Token({percent}%)', + routeBudgetForecast: '按最近 7 个完整 UTC 日预测滚动 30 日 {projected} Token。', estimatedCost: '估算费用(USD)', cacheReadSavings: '缓存读取避免费用(USD)', priceCoverage: '费率覆盖', @@ -56,7 +79,7 @@ export const zh = { routeConcentration: 'Top 路由 {route} 占全部 Token 的 {topOne};Top 3 合计 {topThree}。', unattributedShare: '未归因用量占全部 Token 的 {share};其调用次数与路由效率未知。', usageSignals: '用量信号', - usageSignalsIntro: '运行率和突增检测只使用完整 UTC 日;不对每日用量归因模型。', + usageSignalsIntro: '运行率和突增检测只使用完整 UTC 日,当前仍是全局口径,不作为模型级告警。', dailyRunRate: '7 日日均 Token', projectedThirtyDayUsage: '预计 30 日 Token', anomalyRatio: '昨日相对基线', @@ -103,6 +126,8 @@ export const zh = { exportJson: 'JSON 汇总', exportDaily: '每日 CSV', exportModels: '模型 CSV', + exportModelDaily: '日期×模型 CSV', + exportModelDailyUnavailable: '日期×模型覆盖不完整或未通过守恒校验,暂不导出以避免生成低估数据。', recentSessions: '会话记录', openSessionFailed: '无法打开会话:{message}', showMoreSessions: '显示更多会话(已显示 {shown}/{total})', @@ -201,6 +226,11 @@ export const en = { more: 'More', trend: 'Usage trends', trendIntro: 'Compares the current UTC period with the preceding period of equal length.', + trendModel: 'Trend model', + allModels: 'All models', + trendModelScope: 'This trend includes reliable daily buckets for {route} only.', + modelDailyCoverageUnavailable: 'Legacy fallback records lack date-by-model buckets. Model filtering becomes available after history is refolded.', + modelDailyCoveragePartial: 'Some history lacks date-by-model buckets, so model filtering is unavailable to avoid undercounting.', rangeDays: '{count} days', periodTokens: 'Last {count} days', periodChange: 'Previous period', @@ -216,6 +246,24 @@ export const en = { budgetExceeded: '{excess} tokens over budget.', budgetRunRate: 'Latest 7 complete UTC days: {average} tokens/day; projected rolling 30 days: {projected} tokens.', budgetForecastExceeded: 'At the current run rate, the projected 30-day usage of {projected} tokens exceeds the {budget}-token budget.', + routeBudgets: 'Model budgets and alerts', + routeBudgetsIntro: 'Set a rolling 30-day Token budget for an exact provider/model route. Warning starts at 80%; actual and forecast overages are prioritized.', + routeBudgetModel: 'Model route', + routeBudgetChooseModel: 'Choose a model with usage', + routeBudgetInput: 'Model 30-day budget (tokens)', + routeBudgetSave: 'Save model budget', + routeBudgetRemove: 'Remove budget', + routeBudgetRemoveFor: 'Remove the budget for {route}', + routeBudgetsEmpty: 'No model-level budget is configured.', + routeBudgetCoverageUnavailable: 'No complete, conserved date-by-model data is available. Budgets can be configured, but usage and forecasts are not evaluated yet.', + routeBudgetCoveragePartial: 'Some date-by-model data is missing or does not conserve totals. Model budget status is unavailable to avoid undercounting.', + routeBudgetUnavailable: 'Waiting for reliable data', + routeBudgetStatus_healthy: 'Healthy', + routeBudgetStatus_warning: '80% warning reached', + 'routeBudgetStatus_forecast-exceeded': 'Forecast over budget', + routeBudgetStatus_exceeded: 'Over budget', + routeBudgetProgress: '{used} / {budget} tokens used ({percent}%)', + routeBudgetForecast: 'Latest 7 complete UTC days project {projected} tokens over a rolling 30-day period.', estimatedCost: 'Estimated cost (USD)', cacheReadSavings: 'Cache-read avoided cost (USD)', priceCoverage: 'Rate coverage', @@ -236,7 +284,7 @@ export const en = { routeConcentration: 'Top route {route} accounts for {topOne} of all Tokens; Top 3 account for {topThree}.', unattributedShare: 'Unattributed usage is {share} of all Tokens; its attempt count and route efficiency are unknown.', usageSignals: 'Usage signals', - usageSignalsIntro: 'Run rate and spike detection use complete UTC days only; daily usage is not attributed to a model.', + usageSignalsIntro: 'Run rate and spike detection use complete UTC days and remain global signals, not model-level alerts.', dailyRunRate: '7-day daily Tokens', projectedThirtyDayUsage: 'Projected 30-day Tokens', anomalyRatio: 'Yesterday versus baseline', @@ -283,6 +331,8 @@ export const en = { exportJson: 'JSON summary', exportDaily: 'Daily CSV', exportModels: 'Model CSV', + exportModelDaily: 'Date × model CSV', + exportModelDailyUnavailable: 'Date-by-model coverage is incomplete or failed conservation checks, so this export is disabled to avoid undercounting.', recentSessions: 'Session records', openSessionFailed: 'Unable to open session: {message}', showMoreSessions: 'Show more sessions ({shown}/{total} shown)', diff --git a/src/client/route-budget.ts b/src/client/route-budget.ts new file mode 100644 index 0000000..b5e14e4 --- /dev/null +++ b/src/client/route-budget.ts @@ -0,0 +1,66 @@ +import type { TokenUsageRouteBudget } from '../budget-settings.ts' +import type { DailyTokenUsageRecord, ModelDailyTokenUsageRecord, TokenUsageBuckets } from '../types.ts' +import { periodInsight, runRateInsight } from './analytics.ts' + +/** Fixed warning threshold before an exact route reaches its hard Token budget. */ +export const ROUTE_BUDGET_WARNING_RATIO = 0.8 + +/** Actionable state for one exact provider/model rolling Token budget. */ +export interface RouteBudgetInsight extends TokenUsageRouteBudget { + usedTokens: number + projectedThirtyDayTokens: number + ratio: number + status: 'healthy' | 'warning' | 'forecast-exceeded' | 'exceeded' +} + +/** Sum all four disjoint provider Token buckets. */ +function totalTokens(usage: TokenUsageBuckets): number { + return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens + usage.outputTokens +} + +/** Evaluate one route budget from exact date-by-model buckets. */ +export function routeBudgetInsight( + budget: TokenUsageRouteBudget, + records: readonly ModelDailyTokenUsageRecord[], + now = Date.now(), +): RouteBudgetInsight { + const days: DailyTokenUsageRecord[] = records + .filter(record => record.provider === budget.provider && record.model === budget.model) + .map(record => ({ date: record.date, usage: { ...record.usage } })) + const usedTokens = totalTokens(periodInsight(days, 30, now).usage) + const projectedThirtyDayTokens = runRateInsight(days, now).projectedThirtyDayTokens + const ratio = usedTokens / budget.rolling30DayBudget + const status = usedTokens >= budget.rolling30DayBudget + ? 'exceeded' + : projectedThirtyDayTokens > budget.rolling30DayBudget + ? 'forecast-exceeded' + : ratio >= ROUTE_BUDGET_WARNING_RATIO ? 'warning' : 'healthy' + return { + ...budget, + usedTokens, + projectedThirtyDayTokens, + ratio, + status, + } +} + +const STATUS_PRIORITY: Record = { + exceeded: 3, + 'forecast-exceeded': 2, + warning: 1, + healthy: 0, +} + +/** Evaluate and risk-sort every persisted exact-route budget without mutating settings. */ +export function routeBudgetInsights( + budgets: readonly TokenUsageRouteBudget[], + records: readonly ModelDailyTokenUsageRecord[], + now = Date.now(), +): RouteBudgetInsight[] { + return budgets + .map(budget => routeBudgetInsight(budget, records, now)) + .sort((left, right) => STATUS_PRIORITY[right.status] - STATUS_PRIORITY[left.status] + || right.ratio - left.ratio + || left.provider.localeCompare(right.provider) + || left.model.localeCompare(right.model)) +} diff --git a/src/index.ts b/src/index.ts index 4c17ed6..b2b0562 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,11 @@ import type {} from '@deepseek-ai/dsh-client-connection' import type {} from '@deepseek-ai/dsh-llm' import type {} from '@deepseek-ai/dsh-session-persistence' import type {} from '@deepseek-ai/dsh-session-projection-cache' -import { TOKEN_USAGE_SETTINGS_NAMESPACE, type TokenUsageBudgetSettings } from './budget-settings.ts' +import { + TOKEN_USAGE_SETTINGS_NAMESPACE, + type TokenUsageBudgetSettings, + type TokenUsageRouteBudget, +} from './budget-settings.ts' import { tokenUsageRecorderProjectionDefinition } from './projection.ts' import { TOKEN_USAGE_RPC_CHANNEL, TOKEN_USAGE_RPC_ENDPOINT } from './rpc.ts' import type { AnalysisProgressReporter, AnalysisProgressUpdate } from './analysis-progress.ts' @@ -45,15 +49,62 @@ const auxiliaryPlugin = { } const BUDGET_NAMESPACE = settingsNamespace(TOKEN_USAGE_SETTINGS_NAMESPACE) +const MAX_ROUTE_BUDGETS = 64 +const RouteBudgetSchema: z = z.object({ + provider: z.string().min(1).max(256), + model: z.string().min(1).max(256), + rolling30DayBudget: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER), +}) const BudgetSettingsSchema: z = z.object({ rolling30DayBudget: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(0), + routeBudgets: z.array(RouteBudgetSchema).max(MAX_ROUTE_BUDGETS).default([]), }) -/** Read one safe whole-token budget from a client RPC payload. */ -function budgetFrom(payload: unknown): number | undefined { - if (typeof payload !== 'object' || payload === null) return undefined - const budget = (payload as { rolling30DayBudget?: unknown }).rolling30DayBudget - return typeof budget === 'number' && Number.isSafeInteger(budget) && budget >= 0 ? budget : undefined +interface BudgetSettingsPatch { + rolling30DayBudget?: number + routeBudgets?: TokenUsageRouteBudget[] +} + +/** Read one safe whole-token count from a settings payload. */ +function budgetCount(value: unknown, allowZero: boolean): number | undefined { + return typeof value === 'number' && Number.isSafeInteger(value) && (allowZero ? value >= 0 : value > 0) + ? value + : undefined +} + +/** Read one exact-route budget from the private client wire. */ +function routeBudgetFrom(value: unknown): TokenUsageRouteBudget | undefined { + if (!isRecord(value)) return undefined + const provider = text(value.provider, 256) + const model = text(value.model, 256) + const rolling30DayBudget = budgetCount(value.rolling30DayBudget, false) + return provider === undefined || model === undefined || rolling30DayBudget === undefined + ? undefined + : { provider, model, rolling30DayBudget } +} + +/** Read a bounded global or route-budget settings patch from the private client wire. */ +function budgetPatchFrom(payload: unknown): BudgetSettingsPatch | undefined { + if (!isRecord(payload)) return undefined + const hasGlobal = payload.rolling30DayBudget !== undefined + const hasRoutes = payload.routeBudgets !== undefined + if (!hasGlobal && !hasRoutes) return undefined + const rolling30DayBudget = hasGlobal ? budgetCount(payload.rolling30DayBudget, true) : undefined + if (hasGlobal && rolling30DayBudget === undefined) return undefined + let routeBudgets: TokenUsageRouteBudget[] | undefined + if (hasRoutes) { + if (!Array.isArray(payload.routeBudgets) || payload.routeBudgets.length > MAX_ROUTE_BUDGETS) return undefined + const parsed = payload.routeBudgets.map(routeBudgetFrom) + if (parsed.some(route => route === undefined)) return undefined + routeBudgets = (parsed as TokenUsageRouteBudget[]).sort((left, right) => + left.provider.localeCompare(right.provider) || left.model.localeCompare(right.model)) + const seen = new Set(routeBudgets.map(route => JSON.stringify([route.provider, route.model]))) + if (seen.size !== routeBudgets.length) return undefined + } + return { + ...rolling30DayBudget === undefined ? {} : { rolling30DayBudget }, + ...routeBudgets === undefined ? {} : { routeBudgets }, + } } /** Build one standard internal error response for the private loopback channel. */ @@ -206,6 +257,20 @@ interface AnalysisModelCatalog { failures: TokenUsageAnalysisCatalogFailure[] } +type AnalysisListedModels = Awaited> + +interface AnalysisCatalogProviderState { + active: Set> + current: Promise | undefined + lastStartedAt: number +} + +interface AnalysisCatalogRuntime { + llm: Context['llm'] + logger: Context['logger'] + catalogInFlight: Map +} + /** Stop awaiting an API that cannot consume AbortSignal when its owning lifecycle ends. */ function awaitWithAbort(start: () => Promise, signal: AbortSignal): Promise { return new Promise((resolve, reject) => { @@ -241,29 +306,74 @@ function awaitWithAbort(start: () => Promise, signal: AbortSignal): Promis }) } +const ANALYSIS_MODEL_PROVIDER_TIMEOUT_MS = 5_000 +const ANALYSIS_MODEL_PROVIDER_RETRY_COOLDOWN_MS = 30_000 +const MAX_UNSETTLED_PROVIDER_CATALOG_CALLS = 2 + +/** Reuse recent enumeration while permitting one cooldown retry without unbounded hung calls. */ +function listAnalysisProvider(runtime: AnalysisCatalogRuntime, provider: string): Promise { + let state = runtime.catalogInFlight.get(provider) + const now = Date.now() + if (state?.current !== undefined + && (state.active.size >= MAX_UNSETTLED_PROVIDER_CATALOG_CALLS + || now - state.lastStartedAt < ANALYSIS_MODEL_PROVIDER_RETRY_COOLDOWN_MS)) return state.current + if (state === undefined) { + state = { active: new Set(), current: undefined, lastStartedAt: 0 } + runtime.catalogInFlight.set(provider, state) + } + if (state.active.size >= MAX_UNSETTLED_PROVIDER_CATALOG_CALLS) { + const reusable = state.current ?? [...state.active][state.active.size - 1] + if (reusable === undefined) throw new Error(`No reusable model catalog call exists for provider "${provider}".`) + return reusable + } + const operation = Promise.resolve().then(() => runtime.llm.listModels(provider)) + state.active.add(operation) + state.current = operation + state.lastStartedAt = now + const settled = (): void => { + if (runtime.catalogInFlight.get(provider) !== state) return + state.active.delete(operation) + if (state.current === operation) state.current = undefined + if (state.active.size === 0) runtime.catalogInFlight.delete(provider) + } + void operation.then(settled, settled) + return operation +} + /** List selectable routes without one unavailable provider blocking healthy providers. */ -async function analysisModels(ctx: Pick, signal: AbortSignal): Promise { - const models: TokenUsageAnalysisModel[] = [] - const failures: TokenUsageAnalysisCatalogFailure[] = [] - const seen = new Set() - for (const provider of ctx.llm.listProviders()) { +async function analysisModels(ctx: AnalysisCatalogRuntime, signal: AbortSignal): Promise { + const providers = ctx.llm.listProviders() + const providerResults = await Promise.all(providers.map(async (provider) => { try { - const listed = await awaitWithAbort(() => ctx.llm.listModels(provider.id), signal) - for (const model of listed) { - const key = `${provider.id}\u0000${model.id}` - if (seen.has(key)) continue - seen.add(key) - models.push({ - provider: provider.id, - providerName: provider.name, - model: model.id, - modelName: model.name, - }) - } + const providerSignal = AbortSignal.any([signal, AbortSignal.timeout(ANALYSIS_MODEL_PROVIDER_TIMEOUT_MS)]) + const listed = await awaitWithAbort(() => listAnalysisProvider(ctx, provider.id), providerSignal) + return { provider, listed } } catch (error) { signal.throwIfAborted() ctx.logger.warn(`token usage: failed to list analysis models for "${provider.id}": ${String(error)}`) + return { provider, listed: undefined } + } + })) + signal.throwIfAborted() + + const models: TokenUsageAnalysisModel[] = [] + const failures: TokenUsageAnalysisCatalogFailure[] = [] + const seen = new Set() + for (const { provider, listed } of providerResults) { + if (listed === undefined) { failures.push({ provider: provider.id, providerName: provider.name }) + continue + } + for (const model of listed) { + const key = `${provider.id}\u0000${model.id}` + if (seen.has(key)) continue + seen.add(key) + models.push({ + provider: provider.id, + providerName: provider.name, + model: model.id, + modelName: model.name, + }) } } return { models, failures } @@ -309,16 +419,25 @@ function installRpc(ctx: Context): void { activeProgress.delete(progressId) } } - let analysisRuntime: { llm: Context['llm']; signal: AbortSignal } | undefined + let analysisRuntime: { + llm: Context['llm'] + signal: AbortSignal + catalogInFlight: Map + } | undefined ctx.plugin({ name: 'token-usage-analysis-runtime', inject: ['llm'], apply(analysisCtx: Context) { const lifecycle = new AbortController() - const current = { llm: analysisCtx.llm, signal: lifecycle.signal } + const current = { + llm: analysisCtx.llm, + signal: lifecycle.signal, + catalogInFlight: new Map(), + } analysisRuntime = current analysisCtx.effect(() => () => { lifecycle.abort(new Error('token usage analysis service disposed')) + current.catalogInFlight.clear() if (analysisRuntime === current) analysisRuntime = undefined }, 'token usage: analysis runtime') }, @@ -331,12 +450,12 @@ function installRpc(ctx: Context): void { case TOKEN_USAGE_RPC_ENDPOINT.budgetRead: return { ok: true, value: budget.get() } case TOKEN_USAGE_RPC_ENDPOINT.budgetWrite: { - const rolling30DayBudget = budgetFrom(payload) - if (rolling30DayBudget === undefined) { - return budgetError('Budget must be a non-negative whole Token count.') + const patch = budgetPatchFrom(payload) + if (patch === undefined) { + return budgetError('Budget settings must contain a valid whole-Token global budget or unique exact-route budgets.') } try { - await budget.update({ rolling30DayBudget }) + await budget.update(patch) } catch (error) { return budgetError(error instanceof Error ? error.message : String(error)) } @@ -346,7 +465,7 @@ function installRpc(ctx: Context): void { const runtime = analysisRuntime if (runtime?.llm === undefined) return rpcError('Analysis requires an available model service.') const analysisSignal = AbortSignal.any([operationSignal, runtime.signal]) - const catalog = await analysisModels({ llm: runtime.llm, logger: ctx.logger }, analysisSignal) + const catalog = await analysisModels({ llm: runtime.llm, logger: ctx.logger, catalogInFlight: runtime.catalogInFlight }, analysisSignal) analysisSignal.throwIfAborted() const defaultSelection = ctx.get('agentDefaultModel')?.currentSelection() return { diff --git a/src/projection.ts b/src/projection.ts index 215c6aa..ed93a1a 100644 --- a/src/projection.ts +++ b/src/projection.ts @@ -6,6 +6,7 @@ import type {} from '@deepseek-ai/dsh-compaction' import type {} from '@deepseek-ai/dsh-llm-retry' import type { DailyTokenUsageRecord, + ModelDailyTokenUsageRecord, ModelTokenUsageRecord, TokenUsageBuckets, TokenUsageRecorderProjection, @@ -32,6 +33,7 @@ interface RecorderState { usage: TokenUsageBuckets models: Record days: Record + modelDays: Record lastAssistant: AssistantSample | null } @@ -58,6 +60,12 @@ const projectionSchema: z.ZodType = z.object({ date: z.string(), usage: bucketsSchema, }).strict()), + modelDays: z.array(z.object({ + provider: z.string(), + model: z.string(), + date: z.string(), + usage: bucketsSchema, + }).strict()), }).strict() /** Create detached zero buckets for projection state. */ @@ -124,6 +132,25 @@ function routeKey(route: Route): string { return JSON.stringify([route.provider, route.model]) } +/** Stable collision-free object key for one route on one UTC date. */ +function modelDayKey(route: Route, day: string): string { + return JSON.stringify([route.provider, route.model, day]) +} + +/** Add or remove one usage sample from the route-by-day aggregation table. */ +function adjustModelDay( + modelDays: Record, + route: Route, + day: string, + usage: TokenUsageBuckets, + direction: 1 | -1, +): void { + const key = modelDayKey(route, day) + const next = addBuckets(modelDays[key]?.usage ?? zeroBuckets(), usage, direction) + if (bucketsEqual(next, zeroBuckets())) delete modelDays[key] + else modelDays[key] = { ...route, date: day, usage: next } +} + /** Whether a route record became empty after replacing its only sample. */ function recordEmpty(record: ModelTokenUsageRecord): boolean { return record.assistantRequests === 0 @@ -188,6 +215,7 @@ ProjectionDefinition<'tokenUsageRecorder', RecorderState> = { usage: zeroBuckets(), models: {}, days: {}, + modelDays: {}, lastAssistant: null, }), apply: (state, event) => { @@ -225,8 +253,11 @@ ProjectionDefinition<'tokenUsageRecorder', RecorderState> = { const route = { provider: event.data.provider, model: event.data.model } const models = { ...state.models } const days = { ...state.days } + const modelDays = { ...state.modelDays } + const day = dayKey(event.time) adjustModel(models, route, usage, 1, 'compaction') - adjustDay(days, dayKey(event.time), usage, 1) + adjustDay(days, day, usage, 1) + adjustModelDay(modelDays, route, day, usage, 1) return { ...state, compactionRequests, @@ -234,6 +265,7 @@ ProjectionDefinition<'tokenUsageRecorder', RecorderState> = { usage: addBuckets(state.usage, usage, 1), models, days, + modelDays, } } @@ -267,21 +299,25 @@ ProjectionDefinition<'tokenUsageRecorder', RecorderState> = { const models = { ...state.models } const days = { ...state.days } + const modelDays = { ...state.modelDays } let total = state.usage if (previous !== null) { total = addBuckets(total, previous.usage, -1) adjustModel(models, previous.route, previous.usage, -1, 'assistant') adjustDay(days, previous.day, previous.usage, -1) + adjustModelDay(modelDays, previous.route, previous.day, previous.usage, -1) } total = addBuckets(total, usage, 1) adjustModel(models, route, usage, 1, 'assistant') adjustDay(days, day, usage, 1) + adjustModelDay(modelDays, route, day, usage, 1) return { ...state, assistantRequests: state.assistantRequests + (previous === null ? 1 : 0), usage: total, models, days, + modelDays, lastAssistant: { turn, step, route, day, usage }, } }, @@ -297,8 +333,12 @@ ProjectionDefinition<'tokenUsageRecorder', RecorderState> = { days: Object.entries(state.days) .map(([date, usage]): DailyTokenUsageRecord => ({ date, usage })) .sort((left, right) => left.date.localeCompare(right.date)), + modelDays: Object.values(state.modelDays).sort((left, right) => + left.date.localeCompare(right.date) + || left.provider.localeCompare(right.provider) + || left.model.localeCompare(right.model)), }), - stateVersion: 6, + stateVersion: 7, } /** Fold one complete event sequence through the canonical persistent projection reducer. */ diff --git a/src/trajectory-analysis.ts b/src/trajectory-analysis.ts index 25dc707..09fbdb5 100644 --- a/src/trajectory-analysis.ts +++ b/src/trajectory-analysis.ts @@ -638,7 +638,8 @@ function modelEvidence(prepared: PreparedTrajectory): { text: string; truncated: } /** Translate an unsuccessful terminal model finish into one user-visible analysis error. */ -function finishError(finish: FinishReason): Error | undefined { +function finishError(finish: FinishReason | undefined): Error | undefined { + if (finish === undefined) return new Error('Trajectory analysis model stream ended without a terminal finish reason.') switch (finish.kind) { case 'stop': return undefined case 'error': @@ -697,6 +698,7 @@ export async function analyzeTrajectory( }, signal) signal.throwIfAborted() const assembler = new BlockAssembler() + let finishReason: FinishReason | undefined progress.generating() for await (const chunk of preparedCall.stream({ ...preparedCall.config, @@ -705,12 +707,14 @@ export async function analyzeTrajectory( signal, })) { signal.throwIfAborted() + if (finishReason !== undefined) throw new Error('Trajectory analysis model emitted data after its terminal finish reason.') assembler.push(chunk) + if (chunk.type === 'finish') finishReason = chunk.reason progress.push(chunk) } signal.throwIfAborted() progress.finalizing(assembler.usage) - const terminalError = finishError(assembler.finish) + const terminalError = finishError(finishReason) if (terminalError !== undefined) throw terminalError const blocks = assembler.blocks() if (blocks.some(block => block.type === 'tool-call')) throw new Error('Trajectory analysis must return text only.') diff --git a/src/types.ts b/src/types.ts index 5eb67d4..b4df2f0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,6 +22,12 @@ export interface DailyTokenUsageRecord { usage: TokenUsageBuckets } +/** Token usage attributed to one exact provider/model route and UTC date. */ +export interface ModelDailyTokenUsageRecord extends DailyTokenUsageRecord { + provider: string + model: string +} + /** Durable per-session usage record served to Host and Web projection consumers. */ export interface TokenUsageRecorderProjection { assistantRequests: number @@ -31,6 +37,8 @@ export interface TokenUsageRecorderProjection { usage: TokenUsageBuckets models: readonly ModelTokenUsageRecord[] days: readonly DailyTokenUsageRecord[] + /** Exact route-by-day buckets used for scoped trends and aggregate-only exports. */ + modelDays: readonly ModelDailyTokenUsageRecord[] } /** Signed bucket delta used to expose accounting mismatches without normalization. */ diff --git a/src/usage-analysis.ts b/src/usage-analysis.ts index 3ad0c0f..a9d4558 100644 --- a/src/usage-analysis.ts +++ b/src/usage-analysis.ts @@ -102,8 +102,15 @@ function analysisUsage(value: TokenUsage | undefined): TokenUsageBuckets | undef /** Return a terminal model error when the stream did not finish normally. */ function finishError(reason: FinishReason | undefined): Error | undefined { - if (reason === undefined || reason.kind === 'stop') return undefined - return new Error(`Usage analysis model finished with ${reason.kind}.`) + if (reason === undefined) return new Error('Usage analysis model stream ended without a terminal finish reason.') + switch (reason.kind) { + case 'stop': return undefined + case 'error': + case 'aborted': return Object.assign(new Error(reason.failure.message), { code: reason.failure.code }) + case 'max-tokens': return new Error('Usage analysis reached its output limit. Please retry with a smaller date or model scope.') + case 'tool-calls': return new Error('Usage analysis model unexpectedly requested a tool.') + default: return new Error(`Unsupported model finish reason: ${String((reason as { kind?: unknown }).kind)}`) + } } /** Create a constrained, evidence-first Token efficiency review instruction. */ @@ -142,6 +149,7 @@ export async function analyzeTokenUsage( }, signal) signal.throwIfAborted() const assembler = new BlockAssembler() + let finishReason: FinishReason | undefined progress.generating() for await (const chunk of preparedCall.stream({ ...preparedCall.config, @@ -150,12 +158,14 @@ export async function analyzeTokenUsage( signal, })) { signal.throwIfAborted() + if (finishReason !== undefined) throw new Error('Usage analysis model emitted data after its terminal finish reason.') assembler.push(chunk) + if (chunk.type === 'finish') finishReason = chunk.reason progress.push(chunk) } signal.throwIfAborted() progress.finalizing(assembler.usage) - const terminalError = finishError(assembler.finish) + const terminalError = finishError(finishReason) if (terminalError !== undefined) throw terminalError const blocks = assembler.blocks() if (blocks.some(block => block.type === 'tool-call')) { diff --git a/tests/analysis-progress.spec.ts b/tests/analysis-progress.spec.ts index 691c31b..ac1ee7b 100644 --- a/tests/analysis-progress.spec.ts +++ b/tests/analysis-progress.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest' import type { StreamChunk } from '@deepseek-ai/dsh-llm' import { AnalysisProgressTracker } from '../src/analysis-progress.ts' -import { analysisProgressOf } from '../src/client/analysis-progress-client.ts' +import { analysisProgressOf, requestAnalysisWithProgress } from '../src/client/analysis-progress-client.ts' describe('analysis progress', () => { it('publishes estimated text progress and provider-reported exact output usage', () => { @@ -47,4 +47,81 @@ describe('analysis progress', () => { expect(analysisProgressOf({ available: true, phase: 'generating', elapsedMs: -1 })).toBeUndefined() expect(analysisProgressOf({ available: false })).toBeUndefined() }) + + it('suppresses a late progress response after the analysis has settled', async () => { + let resolveProgress: ((value: { ok: true; value: Record }) => void) | undefined + const call = vi.fn((_channel: string, endpoint: string) => { + if (endpoint === 'analysis/progress') { + return new Promise<{ ok: true; value: Record }>((resolve) => { resolveProgress = resolve }) + } + return Promise.resolve({ ok: true as const, value: { report: 'done' } }) + }) + const onProgress = vi.fn() + const request = requestAnalysisWithProgress( + { rpc: { call } } as never, + 'usage/analyze', + {}, + new AbortController().signal, + value => typeof (value as { report?: unknown })?.report === 'string' ? (value as { report: string }).report : undefined, + onProgress, + ) + await vi.waitFor(() => { expect(resolveProgress).toBeTypeOf('function') }) + await Promise.resolve() + resolveProgress?.({ + ok: true, + value: { + available: true, + phase: 'finalizing', + elapsedMs: 1, + chunks: 1, + outputCharacters: 4, + estimatedOutputTokens: 1, + maximumOutputTokens: 10, + }, + }) + + await expect(request).resolves.toBe('done') + expect(onProgress).not.toHaveBeenCalled() + expect(call.mock.calls.filter(([, endpoint]) => endpoint === 'analysis/progress')).toHaveLength(1) + }) + + it('stops polling and publishing after the owning analysis is cancelled', async () => { + vi.useFakeTimers() + try { + const controller = new AbortController() + const progress = { + available: true, + phase: 'generating', + elapsedMs: 1, + chunks: 1, + outputCharacters: 4, + estimatedOutputTokens: 1, + maximumOutputTokens: 10, + } + const call = vi.fn((_channel: string, endpoint: string, _payload: unknown, signal: AbortSignal) => { + if (endpoint === 'analysis/progress') return Promise.resolve({ ok: true as const, value: progress }) + return new Promise((_resolve, reject) => { + signal.addEventListener('abort', () => { reject(signal.reason) }, { once: true }) + }) + }) + const onProgress = vi.fn() + const request = requestAnalysisWithProgress( + { rpc: { call } } as never, + 'usage/analyze', + {}, + controller.signal, + () => undefined, + onProgress, + ) + await vi.waitFor(() => { expect(onProgress).toHaveBeenCalledTimes(1) }) + controller.abort(new Error('cancelled')) + + await expect(request).rejects.toThrow('cancelled') + await vi.advanceTimersByTimeAsync(1_000) + expect(onProgress).toHaveBeenCalledTimes(1) + expect(call.mock.calls.filter(([, endpoint]) => endpoint === 'analysis/progress')).toHaveLength(1) + } finally { + vi.useRealTimers() + } + }) }) diff --git a/tests/budget-controller.client.spec.ts b/tests/budget-controller.client.spec.ts index bdcf9bc..3d7b959 100644 --- a/tests/budget-controller.client.spec.ts +++ b/tests/budget-controller.client.spec.ts @@ -14,10 +14,10 @@ describe('TokenUsageBudgetController', () => { } as never) await controller.load() - expect(controller.store.getSnapshot()).toEqual({ status: 'ready', budget: 100 }) + expect(controller.store.getSnapshot()).toEqual({ status: 'ready', budget: 100, routeBudgets: [] }) await controller.setBudget(250) - expect(controller.store.getSnapshot()).toEqual({ status: 'ready', budget: 250 }) + expect(controller.store.getSnapshot()).toEqual({ status: 'ready', budget: 250, routeBudgets: [] }) expect(call).toHaveBeenNthCalledWith(1, '/token-usage', 'budget/read', {}) expect(call).toHaveBeenNthCalledWith(2, '/token-usage', 'budget/write', { rolling30DayBudget: 250 }) }) @@ -34,10 +34,10 @@ describe('TokenUsageBudgetController', () => { await controller.load() expect(await controller.setBudget(200)).toBe(100) - expect(controller.store.getSnapshot()).toEqual({ status: 'ready', budget: 100 }) + expect(controller.store.getSnapshot()).toEqual({ status: 'ready', budget: 100, routeBudgets: [] }) expect(await controller.setBudget(300)).toBe(300) - expect(controller.store.getSnapshot()).toEqual({ status: 'ready', budget: 300 }) + expect(controller.store.getSnapshot()).toEqual({ status: 'ready', budget: 300, routeBudgets: [] }) }) it('serializes overlapping writes so a later failure keeps the last durable success', async () => { @@ -60,10 +60,36 @@ describe('TokenUsageBudgetController', () => { resolveFirst?.({ ok: true, value: { rolling30DayBudget: 200 } }) await expect(Promise.all([first, second])).resolves.toEqual([200, 200]) - expect(controller.store.getSnapshot()).toEqual({ status: 'ready', budget: 200 }) + expect(controller.store.getSnapshot()).toEqual({ status: 'ready', budget: 200, routeBudgets: [] }) expect(call).toHaveBeenCalledTimes(3) }) + it('adds, replaces, and removes exact-route budgets through serialized settings writes', async () => { + let settings = { rolling30DayBudget: 100, routeBudgets: [] as Array<{ provider: string; model: string; rolling30DayBudget: number }> } + const call = vi.fn(async (_channel: string, endpoint: string, payload: unknown) => { + if (endpoint === 'budget/read') return { ok: true as const, value: settings } + settings = { ...settings, ...(payload as Partial) } + return { ok: true as const, value: settings } + }) + const controller = new TokenUsageBudgetController({ isLoopback: true, rpc: { call } } as never) + await controller.load() + + await controller.setRouteBudget('deepseek', 'chat', 1_000) + expect(controller.store.getSnapshot()).toEqual({ + status: 'ready', + budget: 100, + routeBudgets: [{ provider: 'deepseek', model: 'chat', rolling30DayBudget: 1_000 }], + }) + expect(call).toHaveBeenNthCalledWith(2, '/token-usage', 'budget/write', { + routeBudgets: [{ provider: 'deepseek', model: 'chat', rolling30DayBudget: 1_000 }], + }) + + await controller.setRouteBudget('deepseek', 'chat', 2_000) + await controller.setRouteBudget('deepseek', 'chat', 0) + expect(controller.store.getSnapshot().routeBudgets).toEqual([]) + expect(call).toHaveBeenLastCalledWith('/token-usage', 'budget/write', { routeBudgets: [] }) + }) + it('does not call the Host for an invalid local budget', async () => { const call = vi.fn() const controller = new TokenUsageBudgetController({ isLoopback: true, rpc: { call } } as never) diff --git a/tests/client-apply.spec.ts b/tests/client-apply.spec.ts index 79c4ad4..79f5631 100644 --- a/tests/client-apply.spec.ts +++ b/tests/client-apply.spec.ts @@ -13,7 +13,7 @@ describe('client apply', () => { const registerSlot = vi.fn(() => () => {}) const injectSlot = vi.fn((_name: string, install: () => unknown) => install()) const effect = vi.fn((install: () => unknown) => install()) - const rpcCall = vi.fn(async () => ({ ok: true as const, value: { rolling30DayBudget: 0 } })) + const rpcCall = vi.fn(async () => ({ ok: true as const, value: { rolling30DayBudget: 0, routeBudgets: [] } })) const connection = { isLoopback: true, rpc: { call: rpcCall }, @@ -56,6 +56,7 @@ describe('client apply', () => { listAnalysisModels: unknown openSession: unknown setBudget: unknown + setRouteBudget: unknown download: unknown } } @@ -66,6 +67,7 @@ describe('client apply', () => { listAnalysisModels: expect.any(Function), openSession: expect.any(Function), setBudget: expect.any(Function), + setRouteBudget: expect.any(Function), download: expect.any(Object), })) const actionOptions = registerSlot.mock.calls[1]?.[0] as { inject: () => Record } diff --git a/tests/component.client.spec.tsx b/tests/component.client.spec.tsx index 578ff3f..415e885 100644 --- a/tests/component.client.spec.tsx +++ b/tests/component.client.spec.tsx @@ -56,6 +56,17 @@ const first = summary({ cacheWriteTokens: 10, }, }], + modelDays: [{ + provider: 'deepseek', + model: 'deepseek-chat', + date: '1970-01-01', + usage: { + uncachedInputTokens: 100, + outputTokens: 30, + cacheReadTokens: 50, + cacheWriteTokens: 10, + }, + }], }, }, }) @@ -94,6 +105,12 @@ const activity = summary({ date: '2026-08-12', usage: { uncachedInputTokens: 1_000, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, }], + modelDays: [{ + provider: 'deepseek', + model: 'deepseek-chat', + date: '2026-08-12', + usage: { uncachedInputTokens: 1_000, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + }], }, }, }) @@ -207,8 +224,9 @@ function props( close: () => {}, useSessions, useWorkspaces: (() => { throw new Error('unused') }) as TokenUsageSectionProps['useWorkspaces'], - useBudget: selector => selector({ status: 'ready', budget: 0 }), + useBudget: selector => selector({ status: 'ready', budget: 0, routeBudgets: [] }), setBudget: async value => value, + setRouteBudget: async () => {}, download: { save: () => {} }, saveTrajectoryAnalysis: () => {}, openSession: () => {}, @@ -274,6 +292,7 @@ describe('TokenUsageSection', () => { }, }]) expect(data.dailyCoverage).toBe('partial') + expect(data.modelDailyCoverage).toBe('partial') expect(usageAnalysisInput(data)).toMatchObject({ assistantRequests: 2, compactionRequests: 1, @@ -281,11 +300,61 @@ describe('TokenUsageSection', () => { }) }) + it('keeps model-day coverage complete for count-only zero-token route records', () => { + const recorded = first.projectionValues?.tokenUsageRecorder + expect(recorded).toBeDefined() + const countOnly = summary({ + ...first, + id: 'session-count-only-route' as SessionSummary['id'], + projectionValues: { + tokenUsageRecorder: { + ...recorded!, + assistantRequests: recorded!.assistantRequests + 1, + models: recorded!.models.concat({ + provider: 'deepseek', + model: 'count-only', + assistantRequests: 1, + compactionRequests: 0, + usage: { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + }), + }, + }, + }) + + expect(aggregateUsage([countOnly]).modelDailyCoverage).toBe('complete') + }) + + it('downgrades model-day coverage when route-day buckets do not conserve session totals', () => { + const recorded = first.projectionValues?.tokenUsageRecorder + expect(recorded).toBeDefined() + const invalid = summary({ + ...first, + id: 'session-invalid-model-days' as SessionSummary['id'], + projectionValues: { + tokenUsageRecorder: { + ...recorded!, + modelDays: [{ + ...recorded!.modelDays[0]!, + usage: { ...recorded!.modelDays[0]!.usage, uncachedInputTokens: 99 }, + }], + }, + }, + }) + + const partial = aggregateUsage([first, invalid]) + const unavailable = aggregateUsage([invalid]) + + expect(partial.modelDailyCoverage).toBe('partial') + expect(unavailable.modelDailyCoverage).toBe('unavailable') + expect(partial.modelDays).toHaveLength(1) + }) + it('does not turn synthetic legacy dates into run-rate or anomaly signals', () => { render() expect(screen.getAllByText('旧版回退记录没有真实逐日 bucket,因此不用于运行率、预测或异常信号。')).toHaveLength(2) expect(screen.getByText('7 日日均 Token').parentElement?.querySelector('strong')?.textContent).toBe('—') + expect(screen.getByRole('button', { name: '日期×模型 CSV' }).hasAttribute('disabled')).toBe(true) }) it('suppresses operational and anomaly signals when daily coverage is only partial', () => { @@ -446,6 +515,49 @@ describe('TokenUsageSection', () => { expect(screen.getAllByText('热力图会话').length).toBeGreaterThan(1) }) + it('filters reliable UTC trends by exact provider/model route', () => { + vi.spyOn(Date, 'now').mockReturnValue(Date.UTC(2026, 7, 14, 12)) + const routed = summary({ + id: 'session-routed' as SessionSummary['id'], + displayTitle: '多模型会话', + updatedAt: Date.UTC(2026, 7, 12), + projectionValues: { + tokenUsageRecorder: { + assistantRequests: 2, + compactionRequests: 0, + compactionUsage: { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + usage: { uncachedInputTokens: 1_000, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + models: [{ + provider: 'deepseek', model: 'small', assistantRequests: 1, compactionRequests: 0, + usage: { uncachedInputTokens: 100, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + }, { + provider: 'deepseek', model: 'large', assistantRequests: 1, compactionRequests: 0, + usage: { uncachedInputTokens: 900, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + }], + days: [{ + date: '2026-08-12', + usage: { uncachedInputTokens: 1_000, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + }], + modelDays: [{ + provider: 'deepseek', model: 'small', date: '2026-08-12', + usage: { uncachedInputTokens: 100, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + }, { + provider: 'deepseek', model: 'large', date: '2026-08-12', + usage: { uncachedInputTokens: 900, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + }], + }, + }, + }) + render() + + const selector = screen.getByLabelText('趋势模型') as HTMLSelectElement + expect(selector.disabled).toBe(false) + expect(screen.getByText('最近 30 天').parentElement?.querySelector('strong')?.textContent).toBe('1K') + fireEvent.change(selector, { target: { value: JSON.stringify(['deepseek', 'small']) } }) + expect(screen.getByText('最近 30 天').parentElement?.querySelector('strong')?.textContent).toBe('100') + expect(screen.getByText('当前趋势仅统计 deepseek/small 的可靠逐日 bucket。')).toBeTruthy() + }) + it('switches trend periods, persists a budget, and exports aggregate-only data', () => { vi.spyOn(Date, 'now').mockReturnValue(Date.UTC(2026, 7, 14, 12)) const setBudget = vi.fn(async (value: number) => value) @@ -460,11 +572,73 @@ describe('TokenUsageSection', () => { fireEvent.click(screen.getByRole('button', { name: 'JSON 汇总' })) const exported = save.mock.calls[0]?.[2] as string - expect(exported).toContain('dsh-token-usage/export-v2') + expect(exported).toContain('dsh-token-usage/export-v3') expect(exported).not.toContain('热力图会话') expect(exported).not.toContain('session-activity') }) + it('evaluates and edits an exact-route budget only from conserved model-day data', async () => { + vi.spyOn(Date, 'now').mockReturnValue(Date.UTC(2026, 7, 14, 12)) + const routeUsage = summary({ + id: 'session-route-budget' as SessionSummary['id'], + displayTitle: '模型预算会话', + updatedAt: Date.UTC(2026, 6, 20), + projectionValues: { + tokenUsageRecorder: { + assistantRequests: 1, + compactionRequests: 0, + compactionUsage: { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + usage: { uncachedInputTokens: 80, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + models: [{ + provider: 'deepseek', model: 'deepseek-chat', assistantRequests: 1, compactionRequests: 0, + usage: { uncachedInputTokens: 80, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + }], + days: [{ + date: '2026-07-20', + usage: { uncachedInputTokens: 80, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + }], + modelDays: [{ + provider: 'deepseek', model: 'deepseek-chat', date: '2026-07-20', + usage: { uncachedInputTokens: 80, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + }], + }, + }, + }) + const setRouteBudget = vi.fn(async () => {}) + render( selector({ + status: 'ready', + budget: 0, + routeBudgets: [{ provider: 'deepseek', model: 'deepseek-chat', rolling30DayBudget: 100 }], + })} + setRouteBudget={setRouteBudget} + />) + + expect(screen.getByText('已达 80% 预警')).toBeTruthy() + expect(screen.getByText('已用 80 / 100 Token(80%)')).toBeTruthy() + fireEvent.change(screen.getByLabelText('模型路由'), { target: { value: JSON.stringify(['deepseek', 'deepseek-chat']) } }) + fireEvent.change(screen.getByLabelText('模型 30 日预算(Token)'), { target: { value: '120' } }) + fireEvent.click(screen.getByRole('button', { name: '保存模型预算' })) + + await waitFor(() => { expect(setRouteBudget).toHaveBeenCalledWith('deepseek', 'deepseek-chat', 120) }) + }) + + it('recovers the settings model catalog after a transient RPC failure', async () => { + const listAnalysisModels = vi.fn() + .mockRejectedValueOnce(new Error('offline')) + .mockResolvedValue({ + models: [{ provider: 'healthy', providerName: 'Healthy', model: 'model', modelName: 'Model' }], + failures: [], + }) + render() + + expect(await screen.findByText('无法读取已接入模型:offline')).toBeTruthy() + fireEvent.click(screen.getByRole('button', { name: '刷新模型目录' })) + expect(await screen.findByLabelText('分析模型')).toBeTruthy() + expect(listAnalysisModels).toHaveBeenCalledTimes(2) + }) + it('keeps available models usable while disclosing an unavailable provider catalog', async () => { render( ({ models: [{ provider: 'healthy', providerName: 'Healthy', model: 'model', modelName: 'Model' }], @@ -500,7 +674,7 @@ describe('TokenUsageSection', () => { it('rolls a rejected budget edit back to the last durable value', async () => { render( selector({ status: 'ready', budget: 100 })} + useBudget={selector => selector({ status: 'ready', budget: 100, routeBudgets: [] })} setBudget={async () => 100} />) const input = screen.getByLabelText('30 日预算(Token)') as HTMLInputElement @@ -516,7 +690,7 @@ describe('TokenUsageSection', () => { const setBudget = vi.fn(() => new Promise((resolve) => { resolveSave = resolve })) render( selector({ status: 'ready', budget: 100 })} + useBudget={selector => selector({ status: 'ready', budget: 100, routeBudgets: [] })} setBudget={setBudget} />) const input = screen.getByLabelText('30 日预算(Token)') as HTMLInputElement diff --git a/tests/export.client.spec.ts b/tests/export.client.spec.ts index b5886e0..43d1770 100644 --- a/tests/export.client.spec.ts +++ b/tests/export.client.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { dailyUsageCsv, modelUsageCsv, tokenUsageExport, tokenUsageJson, type UsageExportSource } from '../src/client/export.ts' +import { dailyUsageCsv, modelDailyUsageCsv, modelUsageCsv, tokenUsageExport, tokenUsageJson, type UsageExportSource } from '../src/client/export.ts' const source: UsageExportSource = { usage: { uncachedInputTokens: 10, outputTokens: 2, cacheReadTokens: 5, cacheWriteTokens: 1 }, @@ -12,6 +12,14 @@ const source: UsageExportSource = { usage: { uncachedInputTokens: 10, outputTokens: 2, cacheReadTokens: 5, cacheWriteTokens: 1 }, }], days: [{ date: '2026-08-14', usage: { uncachedInputTokens: 10, outputTokens: 2, cacheReadTokens: 5, cacheWriteTokens: 1 } }], + modelDays: [{ + date: '2026-08-14', + provider: '=formula-provider', + model: 'model, "quoted"', + usage: { uncachedInputTokens: 10, outputTokens: 2, cacheReadTokens: 5, cacheWriteTokens: 1 }, + }], + dailyCoverage: 'complete', + modelDailyCoverage: 'complete', } describe('privacy-safe usage export', () => { @@ -24,12 +32,13 @@ describe('privacy-safe usage export', () => { const json = tokenUsageJson(extended, '2026-08-14T12:00:00.000Z') expect(exported).toMatchObject({ - schema: 'dsh-token-usage/export-v2', + schema: 'dsh-token-usage/export-v3', generatedAt: '2026-08-14T12:00:00.000Z', timezone: 'UTC', totals: source.usage, compactionUsage: source.compactionUsage, pricingCatalogAsOf: '2025-08-07', + coverage: { daily: 'complete', modelDaily: 'complete' }, pricing: { totalCostUSD: 0, cacheReadSavingsUSD: 0, @@ -38,6 +47,7 @@ describe('privacy-safe usage export', () => { }, models: source.models, days: source.days, + modelDays: source.modelDays, }) expect(json).not.toContain('session-private') expect(json).not.toContain('私密会话标题') @@ -55,6 +65,9 @@ describe('privacy-safe usage export', () => { usage: { uncachedInputTokens: 1_000_000, outputTokens: 1_000_000, cacheReadTokens: 1_000_000, cacheWriteTokens: 1_000_000 }, }], days: [], + modelDays: [], + dailyCoverage: 'complete', + modelDailyCoverage: 'complete', } expect(tokenUsageExport(priced, '2026-08-14T12:00:00.000Z').pricing).toMatchObject({ @@ -72,5 +85,20 @@ describe('privacy-safe usage export', () => { '', ].join('\r\n')) expect(modelUsageCsv(source)).toContain("'=formula-provider,\"model, \"\"quoted\"\"\",2,1,10,2,5,1,16,18,,,") + expect(modelDailyUsageCsv(source)).toContain("2026-08-14,'=formula-provider,\"model, \"\"quoted\"\"\",10,2,5,1,16,18") + expect(modelDailyUsageCsv({ + modelDays: [{ ...source.modelDays[0]!, provider: '\t=1+1' }], + modelDailyCoverage: 'complete', + })).toContain(",'\t=1+1,") + }) + + it('discloses incomplete coverage in JSON and refuses misleading route-day CSV', () => { + const partial = { ...source, dailyCoverage: 'partial' as const, modelDailyCoverage: 'partial' as const } + + expect(tokenUsageExport(partial, '2026-08-14T12:00:00.000Z').coverage).toEqual({ + daily: 'partial', + modelDaily: 'partial', + }) + expect(() => modelDailyUsageCsv(partial)).toThrow('requires complete and conserved route-day coverage') }) }) diff --git a/tests/host-apply.spec.ts b/tests/host-apply.spec.ts index abf09d2..8863fc5 100644 --- a/tests/host-apply.spec.ts +++ b/tests/host-apply.spec.ts @@ -3,12 +3,20 @@ import type { Context } from '@deepseek-ai/cordis' import { apply, inject } from '../src/index.ts' function rpcServices() { - const settings = { rolling30DayBudget: 0 } + const settings = { + rolling30DayBudget: 0, + routeBudgets: [] as Array<{ provider: string; model: string; rolling30DayBudget: number }>, + } + const budgetUpdate = vi.fn(async (next: Partial) => { + if (next.rolling30DayBudget !== undefined) settings.rolling30DayBudget = next.rolling30DayBudget + if (next.routeBudgets !== undefined) settings.routeBudgets = next.routeBudgets + }) return { + budgetUpdate, settings: { register: vi.fn(() => ({ get: () => settings, - update: vi.fn(async (next: typeof settings) => { settings.rolling30DayBudget = next.rolling30DayBudget }), + update: budgetUpdate, })), }, connection: { rpc: { handle: vi.fn(() => async () => {}) } }, @@ -25,7 +33,7 @@ describe('host apply', () => { expect(inject).toEqual(['sessionProjections', 'sessionProjectionCache', 'sessionQuery', 'sessions']) }) - it('persists valid rolling budgets and rejects invalid values through the loopback channel', async () => { + it('persists global and exact-route budgets while rejecting malformed settings', async () => { const rpc = rpcServices() const ctx = { ...rpc, @@ -43,12 +51,84 @@ describe('host apply', () => { endpoint: string, payload: unknown, signal: AbortSignal, - ) => Promise<{ ok: boolean; value?: { rolling30DayBudget: number }; error?: { code: string } }> - const written = await handler('budget/write', { rolling30DayBudget: 250 }, new AbortController().signal) - const rejected = await handler('budget/write', { rolling30DayBudget: -1 }, new AbortController().signal) - - expect(written).toEqual({ ok: true, value: { rolling30DayBudget: 250 } }) + ) => Promise<{ + ok: boolean + value?: { rolling30DayBudget: number; routeBudgets: Array<{ provider: string; model: string; rolling30DayBudget: number }> } + error?: { code: string } + }> + const signal = new AbortController().signal + const written = await handler('budget/write', { rolling30DayBudget: 250 }, signal) + expect(written).toEqual({ ok: true, value: { rolling30DayBudget: 250, routeBudgets: [] } }) + const routeWritten = await handler('budget/write', { + routeBudgets: [{ provider: 'deepseek', model: 'chat', rolling30DayBudget: 1_000 }], + }, signal) + const rejected = await handler('budget/write', { rolling30DayBudget: -1 }, signal) + const duplicate = await handler('budget/write', { + routeBudgets: [ + { provider: 'deepseek', model: 'chat', rolling30DayBudget: 1_000 }, + { provider: 'deepseek', model: 'chat', rolling30DayBudget: 2_000 }, + ], + }, signal) + + expect(routeWritten).toEqual({ + ok: true, + value: { + rolling30DayBudget: 250, + routeBudgets: [{ provider: 'deepseek', model: 'chat', rolling30DayBudget: 1_000 }], + }, + }) + expect(rpc.budgetUpdate).toHaveBeenNthCalledWith(1, { rolling30DayBudget: 250 }) + expect(rpc.budgetUpdate).toHaveBeenNthCalledWith(2, { + routeBudgets: [{ provider: 'deepseek', model: 'chat', rolling30DayBudget: 1_000 }], + }) expect(rejected).toMatchObject({ ok: false, error: { code: 'settings-rejected' } }) + expect(duplicate).toMatchObject({ ok: false, error: { code: 'settings-rejected' } }) + }) + + it('rejects malformed analysis payloads before session reads or model Adapter calls', async () => { + let handler: ((endpoint: string, payload: unknown, signal: AbortSignal) => Promise<{ ok: boolean }>) | undefined + const rpc = rpcServices() + rpc.connection.rpc.handle = vi.fn((_channel, next) => { + handler = next as typeof handler + return async () => {} + }) as typeof rpc.connection.rpc.handle + const prepareCall = vi.fn() + const getSession = vi.fn() + const ctx = { + ...rpc, + sessionProjections: { register: vi.fn() }, + sessionQuery: { listSessions: vi.fn(async () => []) }, + sessionProjectionCache: { write: vi.fn(), coldSnapshot: vi.fn() }, + sessions: { get: getSession }, + sessionPersistence: { inspect: vi.fn() }, + llm: { + listProviders: () => [], + listModels: async () => [], + prepareCall, + }, + logger: { warn: vi.fn() }, + effect: vi.fn((install: () => unknown) => install()), + } as unknown as Context + apply(ctx) + const signal = new AbortController().signal + const model = { provider: 'provider', model: 'model' } + const buckets = { uncachedInputTokens: 1, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 } + const validInput = { usage: buckets, models: [], days: [] } + const modelRow = { provider: 'provider', model: 'model', assistantRequests: 1, compactionRequests: 0, usage: buckets } + const requests: Array<[string, unknown]> = [ + ['usage/analyze', { model, language: 'en', input: { ...validInput, usage: { ...buckets, outputTokens: -1 } } }], + ['usage/analyze', { model, language: 'en', input: { ...validInput, models: Array.from({ length: 513 }, () => modelRow) } }], + ['usage/analyze', { model, language: 'en', input: { ...validInput, days: [{ date: '2026-02-30', usage: buckets }] } }], + ['usage/analyze', { model, language: 'en', progressId: 'short', input: validInput }], + ['trajectory/analyze', { sessionId: '', model, language: 'en' }], + ['trajectory/analyze', { sessionId: 'session', model: { provider: '', model: 'model' }, language: 'en' }], + ] + + for (const [endpoint, payload] of requests) { + await expect(handler?.(endpoint, payload, signal)).resolves.toMatchObject({ ok: false }) + } + expect(getSession).not.toHaveBeenCalled() + expect(prepareCall).not.toHaveBeenCalled() }) it('registers before warming live and persisted sessions', async () => { @@ -253,6 +333,126 @@ describe('host apply', () => { expect(prepareCall).toHaveBeenCalledTimes(2) }) + it('isolates provider catalog timeouts and retries one hung call after cooldown', async () => { + vi.useFakeTimers() + try { + let handler: ((endpoint: string, payload: unknown, signal: AbortSignal) => Promise) | undefined + const rpc = rpcServices() + rpc.connection.rpc.handle = vi.fn((_channel, next) => { + handler = next as typeof handler + return async () => {} + }) as typeof rpc.connection.rpc.handle + let pendingCalls = 0 + const listModels = vi.fn((provider: string) => { + if (provider !== 'pending') return Promise.resolve([{ provider, id: 'model', name: 'Healthy model' }]) + pendingCalls += 1 + return pendingCalls > 1 + ? Promise.resolve([{ provider, id: 'recovered', name: 'Recovered model' }]) + : new Promise>(() => {}) + }) + const ctx = { + ...rpc, + sessionProjections: { register: vi.fn() }, + sessionQuery: { listSessions: vi.fn(async () => []) }, + sessionProjectionCache: { write: vi.fn(), coldSnapshot: vi.fn() }, + sessions: { get: vi.fn() }, + llm: { + listProviders: () => [{ id: 'pending', name: 'Pending provider' }, { id: 'healthy', name: 'Healthy provider' }], + listModels, + }, + logger: { warn: vi.fn() }, + effect: vi.fn((install: () => unknown) => install()), + } as unknown as Context + apply(ctx) + + const result = handler?.('analysis/models', {}, new AbortController().signal) as Promise<{ + ok: boolean + value?: { models: Array<{ provider: string; model: string }>; failures: Array<{ provider: string }> } + }> + await vi.advanceTimersByTimeAsync(5_000) + + await expect(result).resolves.toMatchObject({ + ok: true, + value: { + models: [{ provider: 'healthy', model: 'model' }], + failures: [{ provider: 'pending' }], + }, + }) + const repeated = handler?.('analysis/models', {}, new AbortController().signal) as typeof result + await vi.advanceTimersByTimeAsync(5_000) + await expect(repeated).resolves.toMatchObject({ + ok: true, + value: { + models: [{ provider: 'healthy', model: 'model' }], + failures: [{ provider: 'pending' }], + }, + }) + expect(listModels.mock.calls.filter(([provider]) => provider === 'pending')).toHaveLength(1) + expect(listModels.mock.calls.filter(([provider]) => provider === 'healthy')).toHaveLength(2) + + await vi.advanceTimersByTimeAsync(20_000) + const recovered = handler?.('analysis/models', {}, new AbortController().signal) as typeof result + await expect(recovered).resolves.toMatchObject({ + ok: true, + value: { + models: [ + { provider: 'pending', model: 'recovered' }, + { provider: 'healthy', model: 'model' }, + ], + }, + }) + expect(listModels.mock.calls.filter(([provider]) => provider === 'pending')).toHaveLength(2) + } finally { + vi.useRealTimers() + } + }) + + it('caps cooldown retries for a permanently hung provider catalog', async () => { + vi.useFakeTimers() + try { + let handler: ((endpoint: string, payload: unknown, signal: AbortSignal) => Promise) | undefined + const rpc = rpcServices() + rpc.connection.rpc.handle = vi.fn((_channel, next) => { + handler = next as typeof handler + return async () => {} + }) as typeof rpc.connection.rpc.handle + const listModels = vi.fn(() => new Promise(() => {})) + const ctx = { + ...rpc, + sessionProjections: { register: vi.fn() }, + sessionQuery: { listSessions: vi.fn(async () => []) }, + sessionProjectionCache: { write: vi.fn(), coldSnapshot: vi.fn() }, + sessions: { get: vi.fn() }, + llm: { + listProviders: () => [{ id: 'stuck', name: 'Stuck provider' }], + listModels, + }, + logger: { warn: vi.fn() }, + effect: vi.fn((install: () => unknown) => install()), + } as unknown as Context + apply(ctx) + + const first = handler?.('analysis/models', {}, new AbortController().signal) + await vi.advanceTimersByTimeAsync(5_000) + await expect(first).resolves.toMatchObject({ ok: true, value: { failures: [{ provider: 'stuck' }] } }) + await vi.advanceTimersByTimeAsync(25_000) + + const retry = handler?.('analysis/models', {}, new AbortController().signal) + await vi.advanceTimersByTimeAsync(5_000) + await expect(retry).resolves.toMatchObject({ ok: true, value: { failures: [{ provider: 'stuck' }] } }) + await vi.advanceTimersByTimeAsync(30_000) + + const controller = new AbortController() + const capped = handler?.('analysis/models', {}, controller.signal) + await Promise.resolve() + controller.abort(new Error('test cancellation')) + await expect(capped).rejects.toThrow('test cancellation') + expect(listModels).toHaveBeenCalledTimes(2) + } finally { + vi.useRealTimers() + } + }) + it('stops waiting for model enumeration when its injected LLM runtime is disposed', async () => { let handler: ((endpoint: string, payload: unknown, signal: AbortSignal) => Promise) | undefined let runtimeCleanup: (() => void) | undefined @@ -325,11 +525,10 @@ describe('host apply', () => { expect(listModels).toHaveBeenCalledTimes(1) }) - it('does not start the next provider after disposal between catalog entries', async () => { + it('aborts all concurrently enumerated providers when the model runtime is disposed', async () => { let handler: ((endpoint: string, payload: unknown, signal: AbortSignal) => Promise) | undefined let runtimeCleanup: (() => void) | undefined - let resolveFirst: ((models: Array<{ provider: string; id: string; name: string }>) => void) | undefined - const first = new Promise>(resolve => { resolveFirst = resolve }) + const first = new Promise>(() => {}) const listModels = vi.fn((provider: string) => provider === 'first' ? first : Promise.resolve([])) const rpc = rpcServices() rpc.connection.rpc.handle = vi.fn((_channel, next) => { @@ -356,13 +555,13 @@ describe('host apply', () => { apply(ctx) const pending = handler?.('analysis/models', {}, new AbortController().signal) - await vi.waitFor(() => { expect(listModels).toHaveBeenCalledWith('first') }) - resolveFirst?.([]) - await Promise.resolve() + await vi.waitFor(() => { + expect(listModels).toHaveBeenCalledWith('first') + expect(listModels).toHaveBeenCalledWith('second') + }) runtimeCleanup?.() await expect(pending).rejects.toThrow('token usage analysis service disposed') - expect(listModels).not.toHaveBeenCalledWith('second') }) it('aborts an in-flight analysis when its injected LLM runtime is disposed', async () => { diff --git a/tests/projection.spec.ts b/tests/projection.spec.ts index ad39c96..07e513e 100644 --- a/tests/projection.spec.ts +++ b/tests/projection.spec.ts @@ -64,7 +64,7 @@ describe('tokenUsageRecorder projection', () => { } try { - expect(definition.stateVersion).toBe(6) + expect(definition.stateVersion).toBe(7) expect(ctx.sessionProjections.restoreFloor(legacyCheckpoint)).toBe(0) const restored = ctx.sessionProjections.restore(legacyCheckpoint, events, 0) expect(restored.snapshot.values.tokenUsageRecorder).toMatchObject({ @@ -83,7 +83,7 @@ describe('tokenUsageRecorder projection', () => { cacheWriteTokens: 0, }, }) - expect(restored.checkpoint.tokenUsageRecorder).toMatchObject({ ver: 6, seq: 3 }) + expect(restored.checkpoint.tokenUsageRecorder).toMatchObject({ ver: 7, seq: 3 }) } finally { unregister() await ctx.fiber.dispose() @@ -200,11 +200,32 @@ describe('tokenUsageRecorder projection', () => { cacheWriteTokens: 1, }, }], + modelDays: [{ + provider: 'deepseek', + model: 'deepseek-chat', + date: '1970-01-01', + usage: { + uncachedInputTokens: 12, + outputTokens: 4, + cacheReadTokens: 8, + cacheWriteTokens: 1, + }, + }, { + provider: 'deepseek', + model: 'deepseek-reasoner', + date: '1970-01-01', + usage: { + uncachedInputTokens: 20, + outputTokens: 5, + cacheReadTokens: 2, + cacheWriteTokens: 0, + }, + }], }) expect(JSON.parse(JSON.stringify(state))).toEqual(state) }) - it('moves a finalized replacement sample to its reporting day', () => { + it('moves a finalized replacement sample to its authoritative route and reporting day', () => { let state = definition.init() state = definition.apply(state, event({ seq: 0, @@ -233,13 +254,25 @@ describe('tokenUsageRecorder projection', () => { id: 'message-1', role: 'assistant', content: [], - source: { kind: 'model', provider: 'deepseek', model: 'deepseek-chat' }, + source: { kind: 'model', provider: 'deepseek', model: 'deepseek-reasoner' }, }, usage: { inputTokens: 12, outputTokens: 2 }, }, })) - expect(definition.view(state).days).toEqual([{ + const view = definition.view(state) + expect(view.days).toEqual([{ + date: '2026-08-13', + usage: { + uncachedInputTokens: 12, + outputTokens: 2, + cacheReadTokens: 0, + cacheWriteTokens: 0, + }, + }]) + expect(view.modelDays).toEqual([{ + provider: 'deepseek', + model: 'deepseek-reasoner', date: '2026-08-13', usage: { uncachedInputTokens: 12, @@ -354,9 +387,107 @@ describe('tokenUsageRecorder projection', () => { cacheWriteTokens: 0, }, }], + modelDays: [{ + provider: 'deepseek', + model: 'deepseek-chat', + date: '1970-01-01', + usage: { + uncachedInputTokens: 18, + outputTokens: 4, + cacheReadTokens: 0, + cacheWriteTokens: 0, + }, + }], }) }) + it('conserves route-day and daily totals across a retry, route/day replacement, and compaction', () => { + let state = definition.init() + const events = [ + { + seq: 0, + time: Date.parse('2026-08-12T23:59:00.000Z'), + type: 'request/context', + data: { provider: 'provider-a', model: 'model-a' }, + }, + { + seq: 1, + time: Date.parse('2026-08-12T23:59:30.000Z'), + type: 'assistant/chunk', + data: { turn: 1, step: 1, chunk: { type: 'usage', usage: { inputTokens: 10, outputTokens: 2 } } }, + }, + { + seq: 2, + time: Date.parse('2026-08-12T23:59:40.000Z'), + type: 'llm/retry', + data: { retryId: 'retry-1', turn: 1, step: 1, retry: 1, failure: { code: 'SERVER', message: 'private' } }, + }, + { + seq: 3, + time: Date.parse('2026-08-13T00:00:00.000Z'), + type: 'request/context', + data: { provider: 'provider-b', model: 'model-b' }, + }, + { + seq: 4, + time: Date.parse('2026-08-13T00:00:10.000Z'), + type: 'assistant/chunk', + data: { turn: 1, step: 1, chunk: { type: 'usage', usage: { inputTokens: 20, outputTokens: 4 } } }, + }, + { + seq: 5, + time: Date.parse('2026-08-13T00:00:20.000Z'), + type: 'assistant/message', + data: { + turn: 1, + step: 1, + message: { + id: 'message-1', + role: 'assistant', + content: [], + source: { kind: 'model', provider: 'provider-b', model: 'model-b' }, + }, + usage: { inputTokens: 22, outputTokens: 5 }, + }, + }, + { + seq: 6, + time: Date.parse('2026-08-13T00:01:00.000Z'), + type: 'compaction/summary', + data: { + compactionId: 'compaction-1', + summary: [], + shadowedRange: { start: 0, end: 1 }, + shadowedSeqs: [0, 1], + shadowedTokenCount: 20, + provider: 'provider-c', + model: 'model-c', + usage: { inputTokens: 7, outputTokens: 3 }, + }, + }, + ] + for (const item of events) state = definition.apply(state, event(item)) + const view = definition.view(state) + const sum = (records: ReadonlyArray<{ usage: typeof view.usage }>) => records.reduce((total, record) => ({ + uncachedInputTokens: total.uncachedInputTokens + record.usage.uncachedInputTokens, + outputTokens: total.outputTokens + record.usage.outputTokens, + cacheReadTokens: total.cacheReadTokens + record.usage.cacheReadTokens, + cacheWriteTokens: total.cacheWriteTokens + record.usage.cacheWriteTokens, + }), { uncachedInputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }) + + expect(view.assistantRequests).toBe(2) + expect(view.compactionRequests).toBe(1) + expect(sum(view.modelDays)).toEqual(view.usage) + for (const day of view.days) { + expect(sum(view.modelDays.filter(modelDay => modelDay.date === day.date))).toEqual(day.usage) + } + expect(view.modelDays.map(modelDay => [modelDay.date, modelDay.provider, modelDay.model])).toEqual([ + ['2026-08-12', 'provider-a', 'model-a'], + ['2026-08-13', 'provider-b', 'model-b'], + ['2026-08-13', 'provider-c', 'model-c'], + ]) + }) + it('counts a failed pre-usage retry as a zero-Token model attempt', () => { let state = definition.init() state = definition.apply(state, event({ diff --git a/tests/route-budget.client.spec.ts b/tests/route-budget.client.spec.ts new file mode 100644 index 0000000..fce3310 --- /dev/null +++ b/tests/route-budget.client.spec.ts @@ -0,0 +1,62 @@ +import { describe, expect, it } from 'vitest' +import { routeBudgetInsight, routeBudgetInsights } from '../src/client/route-budget.ts' +import type { ModelDailyTokenUsageRecord } from '../src/types.ts' + +const NOW = Date.UTC(2026, 7, 15, 12) + +function record(provider: string, model: string, date: string, tokens: number): ModelDailyTokenUsageRecord { + return { + provider, + model, + date, + usage: { uncachedInputTokens: tokens, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 }, + } +} + +describe('route budget insight', () => { + it('distinguishes healthy, 80% warning, forecast overage, and actual overage states', () => { + expect(routeBudgetInsight( + { provider: 'p', model: 'healthy', rolling30DayBudget: 100 }, + [record('p', 'healthy', '2026-08-14', 10)], + NOW, + )).toMatchObject({ usedTokens: 10, projectedThirtyDayTokens: 43, ratio: 0.1, status: 'healthy' }) + + expect(routeBudgetInsight( + { provider: 'p', model: 'warning', rolling30DayBudget: 100 }, + [record('p', 'warning', '2026-07-20', 80)], + NOW, + )).toMatchObject({ usedTokens: 80, projectedThirtyDayTokens: 0, ratio: 0.8, status: 'warning' }) + + const recent = [8, 9, 10, 11, 12, 13, 14].map(day => record('p', 'forecast', `2026-08-${String(day).padStart(2, '0')}`, 10)) + expect(routeBudgetInsight( + { provider: 'p', model: 'forecast', rolling30DayBudget: 200 }, + recent, + NOW, + )).toMatchObject({ usedTokens: 70, projectedThirtyDayTokens: 300, ratio: 0.35, status: 'forecast-exceeded' }) + + expect(routeBudgetInsight( + { provider: 'p', model: 'exceeded', rolling30DayBudget: 100 }, + [record('p', 'exceeded', '2026-08-14', 100)], + NOW, + )).toMatchObject({ usedTokens: 100, projectedThirtyDayTokens: 429, ratio: 1, status: 'exceeded' }) + }) + + it('uses exact provider/model identity and sorts the highest-risk routes first', () => { + const budgets = [ + { provider: 'p', model: 'healthy', rolling30DayBudget: 1_000 }, + { provider: 'p', model: 'exceeded', rolling30DayBudget: 50 }, + { provider: 'other', model: 'exceeded', rolling30DayBudget: 50 }, + ] + const records = [ + record('p', 'exceeded', '2026-08-14', 60), + record('other', 'exceeded', '2026-08-14', 20), + record('p', 'healthy', '2026-08-14', 10), + ] + + expect(routeBudgetInsights(budgets, records, NOW).map(insight => [insight.provider, insight.model, insight.status])).toEqual([ + ['p', 'exceeded', 'exceeded'], + ['other', 'exceeded', 'forecast-exceeded'], + ['p', 'healthy', 'healthy'], + ]) + }) +}) diff --git a/tests/trajectory-action.client.spec.tsx b/tests/trajectory-action.client.spec.tsx index 6823deb..cc889c1 100644 --- a/tests/trajectory-action.client.spec.tsx +++ b/tests/trajectory-action.client.spec.tsx @@ -122,6 +122,43 @@ describe('conversation trajectory analysis action', () => { expect(screen.queryByRole('dialog', { name: '当前会话轨迹分析' })).toBeNull() }) + it('aborts a pending catalog request on close and refreshes it on reopen', async () => { + let firstSignal: AbortSignal | undefined + const listAnalysisModels = vi.fn((signal: AbortSignal) => { + if (listAnalysisModels.mock.calls.length === 1) { + firstSignal = signal + return new Promise(() => {}) + } + return Promise.resolve({ + models: [{ provider: 'deepseek', providerName: 'DeepSeek', model: 'chat', modelName: 'Chat' }], + failures: [], + default: { provider: 'deepseek', model: 'chat' }, + }) + }) + const props = { + sessionId: 'session-current', + useTrajectoryHistory: (selector: (snapshot: unknown) => unknown) => selector({ status: 'ready', entries: [] }), + download: { save: vi.fn() }, + listAnalysisModels, + analyzeTrajectory: vi.fn(), + saveTrajectoryAnalysis: vi.fn(), + removeTrajectoryAnalysis: vi.fn(), + t, + } as unknown as ComponentProps + + render() + fireEvent.click(screen.getByRole('button', { name: '会话 Token 轨迹分析' })) + await waitFor(() => { expect(listAnalysisModels).toHaveBeenCalledTimes(1) }) + const footerClose = screen.getAllByRole('button', { name: '关闭' }) + .find(button => button.textContent?.trim() === '关闭') + fireEvent.click(footerClose!) + expect(firstSignal?.aborted).toBe(true) + + fireEvent.click(screen.getByRole('button', { name: '会话 Token 轨迹分析' })) + expect(await screen.findByLabelText('分析模型')).toBeTruthy() + expect(listAnalysisModels).toHaveBeenCalledTimes(2) + }) + it('can retry a transient conversation model-catalog failure', async () => { const listAnalysisModels = vi.fn() .mockRejectedValueOnce(new Error('offline')) diff --git a/tests/trajectory-analysis.spec.ts b/tests/trajectory-analysis.spec.ts index ad25f77..e6f9033 100644 --- a/tests/trajectory-analysis.spec.ts +++ b/tests/trajectory-analysis.spec.ts @@ -366,6 +366,50 @@ describe('trajectory analysis', () => { }) }) + it('rejects a trajectory stream that ends without a terminal finish reason', async () => { + const stream = vi.fn(async function* () { + yield { type: 'text-delta' as const, index: 0, text: '# Partial report' } + }) + const ctx = { llm: { prepareCall: async (config: { provider: string; model: string; maxTokens: number }) => ({ + config, + retryPolicy: {}, + adapterDefaults: {}, + stream, + }) } } as unknown as Context + + await expect(analyzeTrajectory( + ctx, + SessionId('private-session-id'), + events, + { provider: 'configured', model: 'audit-model' }, + 'en', + new AbortController().signal, + )).rejects.toThrow('without a terminal finish reason') + }) + + it('rejects trajectory data emitted after the terminal finish reason', async () => { + const stream = vi.fn(async function* () { + yield { type: 'text-delta' as const, index: 0, text: '# Report' } + yield { type: 'finish' as const, reason: { kind: 'stop' as const } } + yield { type: 'text-delta' as const, index: 0, text: '\nlate data' } + }) + const ctx = { llm: { prepareCall: async (config: { provider: string; model: string; maxTokens: number }) => ({ + config, + retryPolicy: {}, + adapterDefaults: {}, + stream, + }) } } as unknown as Context + + await expect(analyzeTrajectory( + ctx, + SessionId('private-session-id'), + events, + { provider: 'configured', model: 'audit-model' }, + 'en', + new AbortController().signal, + )).rejects.toThrow('after its terminal finish reason') + }) + it('uses the configured model with metadata-only evidence and returns its usage', async () => { const stream = vi.fn(async function* () { yield { type: 'text-delta' as const, index: 0, text: '# Resource summary\nEvidence: seq 0-10.' } diff --git a/tests/usage-analysis.spec.ts b/tests/usage-analysis.spec.ts index 2f7f69b..60c6e53 100644 --- a/tests/usage-analysis.spec.ts +++ b/tests/usage-analysis.spec.ts @@ -49,6 +49,47 @@ describe('aggregate Token usage analysis', () => { expect(JSON.stringify(evidence)).not.toContain('session') }) + it('stably bounds anonymous model evidence and keeps the latest 366 UTC days', () => { + const models = Array.from({ length: 60 }, (_, index) => ({ + provider: `private-provider-${index}`, + model: `private-model-${index}`, + assistantRequests: 1, + compactionRequests: 0, + usage: usage(10, 2), + })) + const days = Array.from({ length: 400 }, (_, index) => ({ + date: new Date(Date.UTC(2025, 0, index + 1)).toISOString().slice(0, 10), + usage: usage(index + 1), + })).reverse() + const input = { + usage: usage(1_000, 200), + assistantRequests: 60, + compactionRequests: 0, + compactionUsage: usage(0), + models, + days, + } + + const evidence = usageAnalysisEvidence(input) + const renamed = usageAnalysisEvidence({ + ...input, + models: models.map((model, index) => ({ + ...model, + provider: `renamed-provider-${index}`, + model: `renamed-model-${index}`, + })), + }) + + const sortedDays = days.slice().sort((left, right) => left.date.localeCompare(right.date)) + expect(evidence.models).toHaveLength(48) + expect(evidence.models.map(model => model.model)).toEqual(Array.from({ length: 48 }, (_, index) => `route-${index + 1}`)) + expect(renamed).toEqual(evidence) + expect(JSON.stringify(evidence.models)).not.toContain('private-') + expect(evidence.days).toHaveLength(366) + expect(evidence.days[0]?.date).toBe(sortedDays[34]?.date) + expect(evidence.days.at(-1)?.date).toBe(sortedDays.at(-1)?.date) + }) + it('does not let local price matches fingerprint raw routes in model evidence', () => { const input = { usage: usage(1_000_000, 1_000_000), @@ -73,6 +114,52 @@ describe('aggregate Token usage analysis', () => { expect(JSON.stringify(pricedRoute)).not.toContain('gpt-5-mini') }) + it('rejects a stream that ends without a terminal finish reason', async () => { + const stream = vi.fn(async function* () { + yield { type: 'text-delta' as const, index: 0, text: '# Partial report' } + }) + const ctx = { llm: { prepareCall: async (config: { provider: string; model: string; maxTokens: number }) => ({ + config, + retryPolicy: {}, + adapterDefaults: {}, + stream, + }) } } as unknown as Context + + await expect(analyzeTokenUsage(ctx, { + usage: usage(100, 20), + assistantRequests: 1, + compactionRequests: 0, + compactionUsage: usage(0), + models: [], + days: [], + }, { provider: 'chosen', model: 'finops-model' }, 'en', new AbortController().signal)) + .rejects.toThrow('without a terminal finish reason') + }) + + it('rejects model data emitted after the terminal finish reason', async () => { + const stream = vi.fn(async function* () { + yield { type: 'text-delta' as const, index: 0, text: '# Report' } + yield { type: 'finish' as const, reason: { kind: 'stop' as const } } + yield { type: 'text-delta' as const, index: 0, text: '\nlate data' } + }) + const ctx = { llm: { prepareCall: async (config: { provider: string; model: string; maxTokens: number }) => ({ + config, + retryPolicy: {}, + adapterDefaults: {}, + stream, + }) } } as unknown as Context + + await expect(analyzeTokenUsage(ctx, { + usage: usage(100, 20), + assistantRequests: 1, + compactionRequests: 0, + compactionUsage: usage(0), + models: [], + days: [], + }, { provider: 'chosen', model: 'finops-model' }, 'en', new AbortController().signal)) + .rejects.toThrow('after its terminal finish reason') + }) + it('dispatches one selected model call with aggregate-only evidence and returns its Token usage', async () => { const stream = vi.fn(async function* () { yield { type: 'text-delta' as const, index: 0, text: '# Usage overview\n\nCache efficiency is high.' }