feat: add searxng web-search provider, openrouter cost balance UI, offline scripts; update source-launch and docs
Some checks failed
CI / node 22.19 (push) Has been skipped
CI / node 26 (push) Has been skipped
CI / python 3.10 / keyless SDK (push) Has been skipped
CI / python runtime / release-shaped Linux x64 (push) Has been skipped
CI / windows node 24 / wine blocking (push) Has been skipped
CI / wine apt cache (push) Successful in 58s
CI / serial / linux (push) Has been skipped
Deploy documentation / build (push) Failing after 2m46s
Deploy documentation / deploy (push) Has been skipped
E2E (real DeepSeek API) / e2e (push) Failing after 1m18s
Sandbox / sandbox e2e (bwrap, ubuntu-latest) (push) Failing after 1m18s
Landlock Run / Matrix (push) Successful in 13s
Release (vendor) / Pack npm tarballs (push) Failing after 3m43s
Release (dsh) / Pack npm tarballs (push) Failing after 1m53s
Sandbox / sandbox e2e (landlock, ubuntu-24.04) (push) Failing after 1m51s
Release (vendor) / Publish to npm (push) Has been skipped
Release (dsh) / Publish to npm (push) Has been skipped
CI / node 24 / static (push) Has been cancelled
CI / node 24 / coverage (push) Has been cancelled
CI / node 24 / snapshots and artifacts (push) Has been cancelled
CI / windows node 24 / native complete (push) Has been cancelled
CI / serial / linux (self-hosted standby) (push) Has been cancelled
CI / serial / macos (push) Has been cancelled
CI / serial / windows (self-hosted standby) (push) Has been cancelled
CI / larger-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (16, windows, dsh-windows-2025-16core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (32, windows, dsh-windows-2025-32core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (4, windows, dsh-windows-2025-4core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (64, windows, dsh-windows-2025-64core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (8, windows, dsh-windows-2025-8core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (96, windows, dsh-windows-2025-96core, production-site) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, 16) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, windows, dsh-windows-2025-16core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, windows, dsh-windows-2025-32core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, 4) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, windows, dsh-windows-2025-4core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, windows, dsh-windows-2025-64core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, 8) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, windows, dsh-windows-2025-8core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, windows, dsh-windows-2025-96core, 2) (push) Has been cancelled
CI / all checks passed (push) Has been cancelled
Sandbox / sandbox e2e (seatbelt, macos-latest) (push) Has been cancelled
Sandbox / sandbox e2e (landlock, ubuntu-24.04-arm) (push) Has been cancelled
Landlock Run / ${{ matrix.platform }} (push) Has been cancelled
Landlock Run / darwin (no platform package — degradation proof) (push) Has been cancelled
Some checks failed
CI / node 22.19 (push) Has been skipped
CI / node 26 (push) Has been skipped
CI / python 3.10 / keyless SDK (push) Has been skipped
CI / python runtime / release-shaped Linux x64 (push) Has been skipped
CI / windows node 24 / wine blocking (push) Has been skipped
CI / wine apt cache (push) Successful in 58s
CI / serial / linux (push) Has been skipped
Deploy documentation / build (push) Failing after 2m46s
Deploy documentation / deploy (push) Has been skipped
E2E (real DeepSeek API) / e2e (push) Failing after 1m18s
Sandbox / sandbox e2e (bwrap, ubuntu-latest) (push) Failing after 1m18s
Landlock Run / Matrix (push) Successful in 13s
Release (vendor) / Pack npm tarballs (push) Failing after 3m43s
Release (dsh) / Pack npm tarballs (push) Failing after 1m53s
Sandbox / sandbox e2e (landlock, ubuntu-24.04) (push) Failing after 1m51s
Release (vendor) / Publish to npm (push) Has been skipped
Release (dsh) / Publish to npm (push) Has been skipped
CI / node 24 / static (push) Has been cancelled
CI / node 24 / coverage (push) Has been cancelled
CI / node 24 / snapshots and artifacts (push) Has been cancelled
CI / windows node 24 / native complete (push) Has been cancelled
CI / serial / linux (self-hosted standby) (push) Has been cancelled
CI / serial / macos (push) Has been cancelled
CI / serial / windows (self-hosted standby) (push) Has been cancelled
CI / larger-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (16, windows, dsh-windows-2025-16core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (32, windows, dsh-windows-2025-32core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (4, windows, dsh-windows-2025-4core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (64, windows, dsh-windows-2025-64core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (8, windows, dsh-windows-2025-8core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (96, windows, dsh-windows-2025-96core, production-site) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, 16) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, windows, dsh-windows-2025-16core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, windows, dsh-windows-2025-32core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, 4) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, windows, dsh-windows-2025-4core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, windows, dsh-windows-2025-64core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, 8) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, windows, dsh-windows-2025-8core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, windows, dsh-windows-2025-96core, 2) (push) Has been cancelled
CI / all checks passed (push) Has been cancelled
Sandbox / sandbox e2e (seatbelt, macos-latest) (push) Has been cancelled
Sandbox / sandbox e2e (landlock, ubuntu-24.04-arm) (push) Has been cancelled
Landlock Run / ${{ matrix.platform }} (push) Has been cancelled
Landlock Run / darwin (no platform package — degradation proof) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/feature/2026-08-20-openrouter-cost-balance-readouts.md
|
||||
2026-08-20-openrouter-cost-balance-readouts.md: 339acc4140eb6c34d1c3910a93babbeda9a28cac
|
||||
2026-08-20-openrouter-cost-balance-readouts.zh.md: 16606ae58b346a1eeb10f703eae748665f64ea83
|
||||
@@ -0,0 +1,90 @@
|
||||
# Agent Note: OpenRouter cost and balance readouts in the web GUI
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-08-20-openrouter-cost-balance-readouts.zh.md)
|
||||
|
||||
> Scope: two new packages — `@deepseek-ai/dsh-openrouter-usage` (host gateway +
|
||||
> projection) and `@deepseek-ai/dsh-client-ui-openrouter-usage` (web readouts) —
|
||||
> composed into the web profile. The gateway resolves the same
|
||||
> `OPENROUTER_API_KEY` credential that OpenRouter LLM routing uses.
|
||||
|
||||
## Problem
|
||||
|
||||
A user running the web GUI with LLM calls routed through OpenRouter has no
|
||||
visibility into what those calls cost or how much account credit remains.
|
||||
Token counts exist in the session log (`assistant/message.usage`, `assistant/chunk`
|
||||
usage chunks) and the token-meter projection, but there is no monetary figure
|
||||
anywhere in the repository, and no durable record of OpenRouter's per-model
|
||||
pricing or account balance. The harness deliberately discards the provider's
|
||||
own cost metadata (the pi-ai adapter zeroes `usage.cost`), so cost had to be
|
||||
reconstructed from token counts against a pricing table fetched from
|
||||
OpenRouter's public API.
|
||||
|
||||
## Decision
|
||||
|
||||
**Cost is a client-facing read-model projection, never a logged or model-visible
|
||||
fact.** The gateway fetches OpenRouter's model pricing table (`GET /models`,
|
||||
`pricing.prompt`/`completion` USD per token, flat `request` fee, disclosed
|
||||
cache rates) and folds each session's logged usage against it in the
|
||||
`openRouterCost` `SessionProjectionMap` entry. Balance comes from OpenRouter's
|
||||
canonical balance endpoint `GET /credits` (available credits = `total_credits`
|
||||
minus the spent `total_usage`, the figure the dashboard surfaces), merged with
|
||||
`GET /auth/key` for the key label and the monthly token budget
|
||||
(`usage`/`limit`); a standard `sk-or-v1` key returns **no** `credits` field on
|
||||
`/auth/key`, so that endpoint alone would always read a null balance. Both
|
||||
fetches go through plain `globalThis.fetch` with `redirect: 'error'`, the host
|
||||
standard for credential-bearing outbound calls.
|
||||
|
||||
**The web surface is two slot entries.** A per-session cost readout registers
|
||||
into `conversation.composer.dock` (the shipped stats line's dock), reading the
|
||||
`openRouterCost` projection through the `useProjection` framework seat; a live
|
||||
balance badge registers into `sidebar.footer.action`, polling the balance
|
||||
Remote on a 60s client-side interval. Both hide themselves when their data is
|
||||
absent: cost hides before any priced step, balance renders an empty dashes
|
||||
marker when the snapshot has no value yet.
|
||||
|
||||
**The API key is a credential reference, seeded identically to routing.**
|
||||
`apiKeyEnv` (default `OPENROUTER_API_KEY`) resolves through the credentials
|
||||
seam with an environment fallback exactly like `dsh-web-search-deepseek`,
|
||||
captured per refresh in a thunk so a credentials or settings change applies on
|
||||
the next tick without a rebuild.
|
||||
|
||||
**No key → no fetch, and the plugin stays inert.** No key produces an empty
|
||||
pricing table (every step stays unpriced), an all-`null` balance, and the
|
||||
client reads null-ish values it renders as absent — the same dormant posture
|
||||
as the LLM adapters, not a load-time failure.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Persisting per-request cost as a session event** — cost is derived from
|
||||
events already logged (`assistant/message` usage, `assistant/chunk` usage);
|
||||
an event would duplicate accounting, be priced at the wrong time (pricing
|
||||
changes with `pricingRefreshMs`), and leak a client-facing figure into the
|
||||
durable log. The projection stays the single read-model home.
|
||||
- **Balance via `/auth/key` alone** — the plan's original shape; the real
|
||||
endpoint response for a standard `sk-or-v1` key carries no `credits` field,
|
||||
verified against OpenRouter's live API, so the snapshot would always read
|
||||
null. `/credits` is the authoritative balance endpoint and `/auth/key` only
|
||||
supplements label + monthly budget.
|
||||
- **Reading `usage.cost` from pi-ai** — the harness deliberately discards
|
||||
provider cost metadata (the adapter pins `NO_COST`); reconstructing from
|
||||
tokens × table is the only available path and is documented as an estimate.
|
||||
- **A settings card and a dedicated credentials UI** — deferred: the readouts
|
||||
work with the existing credential the routing already uses; a card adds
|
||||
surface without changing behavior.
|
||||
|
||||
## Consequences
|
||||
|
||||
`test:gui` stays green (3800 tests) and both packages carry unit tests plus a
|
||||
real-composition loader test that boots the shipped YAML shape through the
|
||||
vendored Loader, resolves the key from a credentials document, and asserts
|
||||
the mocked `/models` and `/credits` fetches populate the projection and the
|
||||
snapshot. The numbers are an estimate, not itemized billing: the projection
|
||||
prices `tokens × pricing` per step, pricing is applied as of fold time
|
||||
(refreshing pricing affects only cells folded afterward), cache rates fall
|
||||
back to the prompt rate when undisclosed, and free-tier / promotional pricing
|
||||
may differ from the model table — all documented under Known Limitations.
|
||||
Nothing is model-visible: the agent loop, session log format, and SDKs are
|
||||
untouched. Deferred: a settings card, and any per-generation billing fidelity
|
||||
that would require logging OpenRouter generation ids.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Agent Note: OpenRouter cost and balance readouts in the web GUI
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-08-20-openrouter-cost-balance-readouts.md) | 中文
|
||||
|
||||
> 范围:两个新包 —— `@deepseek-ai/dsh-openrouter-usage`(主机端网关 + 投影)与 `@deepseek-ai/dsh-client-ui-openrouter-usage`(Web 读数)—— 组合进 web profile。网关解析与 OpenRouter LLM 路由相同的 `OPENROUTER_API_KEY` 凭证。
|
||||
|
||||
## Problem
|
||||
|
||||
使用 Web GUI 并通过 OpenRouter 路由 LLM 调用的用户,无法看到这些调用花费了多少、账户还剩多少额度。token 计数存在于会话日志(`assistant/message.usage`、`assistant/chunk` 用量块)和 token-meter 投影中,但仓库里没有任何货币数字,也没有 OpenRouter 按模型定价或账户余额的持久记录。harness 有意丢弃 provider 自身的成本元数据(pi-ai 适配器将 `usage.cost` 清零),因此成本必须根据 token 计数与从 OpenRouter 公共 API 拉取的定价表重建。
|
||||
|
||||
## Decision
|
||||
|
||||
**费用是面向客户端的只读模型投影,绝不是被记录或模型可见的事实。** 网关拉取 OpenRouter 的模型定价表(`GET /models`,`pricing.prompt`/`completion` USD 每 token、flat 的 `request` 费用、披露的缓存费率),并在 `openRouterCost` 的 `SessionProjectionMap` 条目中按各会话已记录的用量折叠它。余额来自 OpenRouter 权威余额端点 `GET /credits`(可用余额 = `total_credits` 减去已花费的 `total_usage`,即仪表盘展示的数字),并与 `GET /auth/key` 合并以获取 key 标签与月度 token 预算(`usage`/`limit`);标准 `sk-or-v1` key 在 `/auth/key` 上**不**返回 `credits` 字段,因此仅靠该端点总会读到空余额。两个请求都使用 `redirect: 'error'` 的 `global.fetch` —— 携带凭证的出站调用的主机标准。
|
||||
|
||||
**Web 表面是两个 slot 条目。**每会话费用读数注册进 `conversation.composer.dock`(随附统计行的 dock),通过 `useProjection` 框架座位读取 `openRouterCost` 投影;实时余额徽章注册进 `sidebar.footer.action`,以客户端 60s 间隔轮询余额 Remote。两者在数据缺失时自我隐藏:费用在任何已计价步骤前隐藏,余额在快照尚无值时渲染空的破折号标记。
|
||||
|
||||
**API key 是凭证引用,与路由的初始化方式一致。**`apiKeyEnv`(默认 `OPENROUTER_API_KEY`)与 `dsh-web-search-deepseek` 完全一样,通过凭证接缝加环境回退解析,按刷新在 thunk 中捕获,使凭证或设置变更无需重建即可在下一周期生效。
|
||||
|
||||
**无 key → 不发请求,插件保持惰性。**无 key 产生空定价表(每个 step 都保持未计价)、全 `null` 余额,客户端把 null 渲染为缺失 —— 与 LLM 适配器相同的休眠姿态,而非加载期失败。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **将会话事件持久化每次请求费用** —— 费用派生自已记录的事件(`assistant/message` 用量、`assistant/chunk` 用量);事件会重复记账、在错误时间计价(`pricingRefreshMs` 变更定价),并使客户端数字泄漏进持久日志。投影保持唯一的读数模型归属。
|
||||
- **仅用 `/auth/key` 读取余额** —— 计划的初始形态;真实端点响应中标准 `sk-or-v1` key 不携带 `credits` 字段(已对照 OpenRouter 线上 API 验证),因此快照总会是 null。`/credits` 是权威余额端点,`/auth/key` 只是补充标签与月度预算。
|
||||
- **读 pi-ai 的 `usage.cost`** —— harness 刻意丢弃 provider 成本元数据(适配器钉定 `NO_COST`);从 token × 定价表重建是唯一可行路径,并记录为估算。
|
||||
- **提供设置卡片与专用凭证 UI** —— 推迟:读数可以用路由已使用的既有凭证工作;卡片只是界面,不改变行为。
|
||||
|
||||
## Consequences
|
||||
|
||||
`test:gui` 保持全绿(3800 个测试),两个包都带单元测试加真实组合 loader 测试:把带变形的 YAML 形状(session + 投影注册表 + 凭证 + openrouter-usage)跑进 vendored Loader,从凭证文档解析 key,并断言被 mock 的 `/models` 与 `/credits` 请求填充投影与快照。数字是估算而非逐项账单:投影按 step 以 `tokens × pricing` 计价,计价格次折叠时生效(刷新定价只影响其后折叠的单元格),未披露时缓存费率回退到 prompt 费率,免费档位/促销价格可能与模型表不同 —— 全部记录在 Known Limitations。没有任何内容对模型可见:agent 循环、会话日志格式与 SDK 均未改动。推迟:设置卡片、以及需要记录 OpenRouter generation id 的逐代账单保真度。
|
||||
Reference in New Issue
Block a user