
这个插件的灵感,来自那个关于「有钱」的经典段子:
等咱有了钱,喝豆浆吃油条。豆浆买两碗,喝一碗,倒一碗!油条买两根,吃一根,扔一根!
我认为这个段子的笑点从来不在「有钱」,而在浪费被摆到明面上——买两碗就是为了倒一碗,倒掉的那碗才是主角。这个插件做的事情,就是把段子逐句翻译成 LLM 调用:
段子 | 插件 |
|---|---|
豆浆买两碗 | 每次模型请求真的发两份 |
喝一碗 | 第一份正常返回给 agent loop,是你真正在用的回复 |
倒一碗 | 第二份完整收完,然后直接扔掉 |
想蘸白糖蘸白糖 | discardedCopies 想扔几份扔几份 |
—— | 侧边栏状态条如实显示今日 / 本月 / 累计倒掉了多少 |
不过这里有两处刻意的坚持,我想特别说明一下,因为它们决定了这个项目到底是个玩具还是个作品:
usage,一个 token 都不编。一句话概括这个项目的设计纲领:行为是段子,账本是账本。 花两倍的钱是玩笑,把这两倍的钱如实记下来不是。
装好之后,你的侧边栏左下角、用户名正上方,会多出一行:
💰 今日浪费 2249万Token鼠标移上去,展开成三行:
💰 今日浪费 2249万Token
💰 本月浪费 2249万Token
💰 累计浪费 8400万Token
关于显示,有两个细节值得一提:
不想摆阔了?配置里一行 enabled: false,今天豆浆只买一碗:插件还在加载,状态条还在,但不再花钱。
同样是倒掉一碗,倒法有讲究。插件的 fortune 配置有两档,区别只有一个:第二份请求的提示词前缀一不一样——也就是它能不能蹭到 provider 的提示词缓存。
档位 | 第二份请求 | 缓存 | 计费 |
|---|---|---|---|
千万富翁 | 与原请求完全相同 | 通常命中 | 按缓存读取价(折扣价) |
亿万富翁 | system prompt 开头多一行当前时间戳 | 必然不命中 | 按全价(缓存写入) |
原理是这样的:provider 的提示词缓存按前缀匹配。两份请求前缀逐字相同时,第二份直接复用第一份写好的缓存条目,走折扣价;只要最前面那段文字不同,整条前缀就对不上,第二份按全价重新计费。时间戳天然满足「每次都不同」,所以两份重复请求之间也蹭不到彼此的缓存——这就是亿万富翁更烧钱的全部机制,实现上只是给副本的 system 消息头部加一行 [i-am-rich] <ISO 时间戳>。

【注意】档位在状态条展开面板顶部选择,写入 profile 配置(带文件锁、校验与失败回滚),重启后仍生效;写入失败时单选框停在实际生效的那一档——显示与真实行为不一致,正是这个插件要防的事。
账本和状态条是两件事,这一点最近被明确地分开了。
账本如实落盘四个桶:新输入、输出、缓存读取、缓存写入——provider 报什么记什么,一个不少。
状态条只统计两个数字:缓存未命中的输入 + 生成的输出。 原因很直白:缓存命中部分按折扣价计费,往往比全价低一个数量级——千万富翁档的副本每个 token 几乎不花钱;缓存写入则是输入前缀本身入仓,不是副本新「说」出来的东西。把这两类算进「浪费」,会虚捧数字而不代表真实的挥霍。
这里有一个关键点:倒豆浆的账要记全,但摆出来炫耀的,得是按全价买单的那部分。
如果这是个只会烧钱的玩具,博客到这里就该结束了。但我一直认为,这个插件真正值得看的,是它如何用做支付系统的态度做一个玩笑。它是一个「双面插件」,一个包编译出两个产物,由两个世界分别加载。接下来,我们来看看它的具体实现。
插件挂载在 DSH 的 llm/stream waterfall 上。每次请求经过时,它调用 next() 两次:一次是真实请求,照常返回给 agent loop;其余的每份交给 burn()——把流完整 drain 到最后一个 chunk,只留下 provider 报告的用量,内容全部丢弃。
亿万富翁档是个例外:副本必须带着被改过的前缀出发,next() 不接受参数(它只会重放闭包里的那一个请求),所以副本改走 ctx.llm.stream() 重新派发,并用一张 WeakSet 按对象身份让「自己派发的那份」直接穿透,否则会无限递归。
几个值得说的细节:
failed。诚实账本的原则是:钱花了就记,不管这碗豆浆倒没倒干净。usage 的调用单独计为「未报告用量的调用」,绝不冒充 Token 数。浪费记录写在 ~/.dsh/i-am-rich/waste-YYYY-MM.jsonl,每月一个文件,一行一次丢弃:
{"v":1,"wasteId":"…","provider":"deepseek","model":"…","outcome":"discarded","day":"2026-10-10","usage":{"inputTokens":100,"outputTokens":20}}刻意不写进 dsh 的会话日志——段子归段子,账本不该混进别人的日志里。
崩溃安全交给文件系统:单行 O_APPEND 在本地文件系统上实际原子,最坏留下末尾半行;读取端跳过解析失败的行,一个月里的一个坏行不会吞掉其余的账。账本按单实例设计,不做跨进程加锁——诚实地承认边界,而不是假装解决了不存在的问题。
src/waste.ts所有算术——按天折叠、三个口径求和、万/亿 vs K/M/B 的量级缩放——都在一个零依赖的纯函数模块里。不需要 Host,不需要 cordis,测试就是普通函数调用。
「今天」和「本月」取决于谁在什么时候问。如果把区间选择做在 Host 的持久化投影里,重放(replay)时算出的「今天」就会漂移。
所以架构是:Host 只发布按天的账本(无时钟的 fold),拥有时钟的客户端负责选取区间。 持久化的计算因此严格可复现,而日历区间依然正确。跨月、跨年、非法日期键的边界都有测试锁死——2025-12-31 不会计入本月,去年同月同日不算「今日」,格式非法的日期只计入「累计」。
lib/client.js 以 CJS 形式包裹在 window.__ModuleLoader__.load({ id, factory }) 里——浏览器内核物化工厂函数,不执行 ESM。状态条组件注册进 sidebar.footer.action 槽位(由 ui-sidebar 声明),落在账号按钮正上方,字号、内边距、配色逐条对齐用户名的样式——它是用户名的同级控件,不是注脚。
组件只用 inline styles 不用 CSS 文件,因为 tsc 不会把 .css 拷进产物。数据不经过会话 store:挂载即拉取一次账本,之后每 15 秒轮询,标签页重新可见时补一次。轮询失败时保留最后一次成功的数据并在 tooltip 里注明「账本暂不可用」——冻结的数字必须让人看出来是冻结的,绝不静默清零。
这里还有一个反直觉但很对的决策:状态条从挂载那一刻就显示,没有记录时显示 0。 因为一个什么都不渲染的侧边栏条目,和一个加载失败的插件在界面上完全一样——「没有记录就不渲染」会让你永远分不清「已正确挂载」和「没挂上」。
这个项目的注释和测试里,记录着四个真实踩过的坑,每一个都值得写进任何插件系统的文档。我把它们分享出来,希望能帮到同样在写 DSH 插件的朋友。
坑一:inject 没声明,状态条永远显示 0。 cordis 在已激活的插件上读取未声明 inject 的服务会抛异常而不是返回 undefined。当时的代码用 try/catch 把这个异常当成「宿主没有 Web 客户端」吞掉了——于是 /api 路由根本没注册,可账本文件却被正常写入。两种情况的界面表现完全一样,这个 bug 静默了很久。修复方式是把 connection 声明进 inject:读取合法,且 cordis 会等服务就绪才调用 apply,顺带消除了启动时序竞争。现在有一个测试用真实的 cordis fiber 复现那条异常。
坑二:槽位名写错,插件「装了但看不见」。 注册到一个没有任何 bundle 声明的槽位,slots.inject 既不报错也不渲染——和加载失败完全无法区分。槽位名最终是从 ui-sidebar 自己的声明表里逐字抄来的。
坑三:最关键的测试数的是 adapter 调用次数。 invokes the underlying adapter twice 这条测试数的是 adapter 被实际调用的次数——只有这个断言能证明真的发出了第二份请求,测下游监听器数量是证明不了的。这是整个测试套件的定海神针。
坑四:测试的绿灯是「结构性」的。 亿万富翁档在真实 harness 里崩溃,本地测试却全绿——因为所有用例都在裸 new Context() 上手工赋值 ctx.llm,而裸 context 不是代理,读取既不抛异常、也不进服务注册表,恰好把两条真实路径(fiber proxy 的抛错语义、注册表解析)都结构性错过了。修复后补的测试把 apply 跑进真实的 cordis fiber、让 llm 从注册表 provide——用例的形状必须贴着运行时的形状,绿了才算数。
诚实地说:没有人「需要」它。 它不省钱、不省时、不提升任何指标。
但如果你符合以下任何一条,它会让你微笑:
/api 路由、按月 JSONL 账本、纯函数内核 + 全边界测试,一整套插件开发的全部要素都在这一个包里,而且每个 why 都写在注释里;目前尚未发布到 npm,从源码安装:
git clone https://github.com/studyzy/dsh-i-am-rich.git
cd dsh-i-am-rich
pnpm install && pnpm run build
dsh plugin --profile <你的 profile> add link:/path/to/dsh-i-am-rich然后把插件行合进 profile:
- insert:
- id: i-am-rich
name: '@studyzy/dsh-i-am-rich'如果安装过程中遇到问题,欢迎到 GitHub Issues 反馈。
写这个插件的过程中,我一直在想一个问题:当一个功能的存在理由是荒谬的,工程上的正确还重要吗?
我的答案是:正因为行为是荒谬的,账本才必须是精确的。每一行 JSONL 都是真实发生的浪费,每一个数字都来自 provider 亲手报的用量,每一次故障都被测试复现并锁死。段子的部分交给段子,其余的部分,用做基础设施的态度做完。
毕竟,真正的有钱人,倒掉的每一碗豆浆都有账可查。
欢迎在评论区聊聊你的看法,或者去 GitHub 仓库 提 Issue、点 Star——如果你也喜欢这种「认真地做一件不正经的事」,我会很高兴。
参考资源:
温馨提示:discardedCopies 是自然数,没有上限。