ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

dsh-ads架构全解析:Node端与浏览器端分离、cordis补丁与双入口注入的完整设计

dsh-ads架构全解析:Node端与浏览器端分离、cordis补丁与双入口注入的完整设计

【免费下载链接】dsh-ads

把 DSH 变成 2005 年门户网站|Parody ads, fake games, and popups for the DSH Web UI

项目地址:https://gitcode.com/gh_mirrors/ds/dsh-ads
点击查看免费下载

dsh-ads是把 DSH(DeepSeek Harness)变成 2005 年门户网站的 DSH 广告插件。它的架构精髓在于Node 端与浏览器端彻底分离:一半跑在宿主服务器进程里、只负责"发现与缓存",另一半跑在浏览器里、只负责"渲染广告",两者之间只用两根 HTTP 路由传数据。看懂这套"双入口 + cordis 补丁"的设计,你就能明白一个 DSH 插件是如何被完整装进一个 profile 的。

先看懂整体:一个插件,两个"半身"

dsh-ads不是一坨代码,而是两个能独立加载的入口,各自只干自己擅长的事:

  • Node 端(服务器半身):运行在 DSH 的 Web 宿主进程里。它不画任何像素,只向宿主的 HTTP 服务器注册两条路由,并在后台搜索 GitHub 的dsh-plugintopic、做缓存与降级。
  • 浏览器端(浏览器半身):运行在用户的页面里。它注册三个 UI 插槽,把浮动广告、推理中途门、对话内广告"挂"进会话,并按语言热切换整套素材。

两半之间的"线"只有一根——src/protocol.ts 里定义的两条路由和它们的数据结构。

Node 端:只干两件事,注册两条路由 🪝

Node 端的入口是 src/index.ts,构建产物是 lib/index.js。它对宿主只声明了一个能力注入:

export const inject = ['webServer']

然后在apply()里,通过ctx.webServer.register(...)把两条"精确路由"挂到宿主 Web 服务器上:

路由路径提供什么
赞助清单/dsh-ads/registry.json带dsh-plugintopic 的社区插件列表(已过滤、已截断)
星检测/dsh-ads/star-check.json宿主自己那个 GitHub 账号有没有 star 本插件

它的几个设计点很值得学:

  1. 动态层才有 Node 端存在的意义。内置横幅是烤进浏览器包的,根本不需要宿主;只有"给真实社区插件打广告"这一层,才需要宿主去搜 GitHub。
  2. 多级降级链。清单发现走loadRegistry():先用认证过的ghCLI,再用环境变量 token,再退回 GitHub 匿名公共 API,最后落到一份内置快照——保证"离线、被限流、没凭据"都不断广告。
  3. 缓存 + 并发合并。清单默认缓存 30 分钟(cacheMinutes),同时用inflight把并发请求合并,避免多个会话同时打开时各自 shell out 一次gh;星检测则"一旦看到 star 就进程内记住,没看到不记",方便"先 star 再点重新检测"。

浏览器端:三个插槽,把广告"挂"进会话 🧩

浏览器端入口是 src/client/index.tsx,产物 lib/client.js。它注入['slots', 'locale'],在apply()里注册三种性质完全不同的挂载点:

插槽作用对应组件
conversation.input.dock挂载浮动广告层 + 推理中途门(零行内占位,全部走 Portal)AdDockEntry
settings.section设置面板里多一页"广告(非官方)"开关AdsSectionEntry
conversation.chat.turnTail把广告插进对话正文,而非浮在上面InlineAdEntry

推理中途门("流式回答时假装暂停 inference"的彩蛋)就挂在 dock 上:

广告开关则单独成页,而不是藏在会消失的海报里:

一个关键细节:这三个入口都从ctx.locale这个observable读语言,所以切换 DSH「设置 → 语言」时,当前页面立即换掉整套素材、文案和交互,不用刷新。

两者之间只走一根线:protocol 路由与数据流 📡

两半不共享任何运行内存,唯一的契约是 src/protocol.ts:

  • REGISTRY_ROUTE = '/dsh-ads/registry.json'、STAR_ROUTE = '/dsh-ads/star-check.json'
  • RegistryPayload(source标记来自gh-cli / github-token / github-public / snapshot)、StarCheckPayload(starred / absent / unavailable)

浏览器端在 src/client/registry.ts 里每次页面加载只 fetch 一次(模块级共享 Promise),拿到清单后"一份清单、两处投放":侧栏 gutter 取轮播子集(中文 8 个 / 英文 3 个),对话内 feed 拿完整列表做公平轮转(见 src/client/feed.ts,"最少被看到的先上")。星检测同理:浏览器先问 Node 路由,unavailable就退回匿名翻页 stargazers(src/client/star-check.ts)。

数据谁产生谁消费走哪条路由
社区插件清单Node 端catalog.ts浏览器端registry.ts/dsh-ads/registry.json
星状态Node 端star-host.ts浏览器端star-check.ts回退/dsh-ads/star-check.json

降级是优雅且正确的:浏览器端拿不到路由(比如 TUI、ACP、headless 这种没有 Web 宿主的场景),fetchPlugins()直接吞掉错误返回空列表——内置横幅照常,只是少了动态那一层。

cordis 补丁与双入口注入:插件如何被装进 DSH 🔌

这部分是"插件怎么被加载"的关键,答案在 package.json 与 cordis.patch.yml。

1)cordis 补丁(bundle 级注入)

cordis.patch.yml 只有一小段,作用是把本插件插入某个 profile 的layer 栈:

- insert: - id: dsh-ads name: '@nagi-ovo/dsh-ads'

它由package.json的dsh.bundle.patch指向。这就是"cordis 补丁"——在打包/装配阶段声明"把dsh-ads这一层加进去"。

2)双入口(exports 级注入)

package.json的exports同时暴露了两个入口,分别给两种运行环境:

入口指向跑在哪apply签名
.lib/index.jsNode 宿主apply(ctx: HostContext, config?)
./clientlib/client.js浏览器apply(ctx: ClientContext)

3)客户端额外注入(dsh.client)

dsh.client.platform: "web"标记这是 Web 客户端插件;dsh.client.inject: ['@deepseek-ai/dsh-client-locale']再把语言包一起注入进浏览器端——这正是上面"locale 热切换"能用的前提。

4)构建怎么产出这两个入口

tsdown.config.ts 定义了两个构建任务:

  • Node 半:src/index.ts→lib/index.js,ESM、platform: node、带.d.ts,框架包(cordis、schemastery)保持neverBundle外部。
  • 浏览器半:src/client/index.tsx→lib/client.js,CJS、platform: browser,把平台模块表保持外部、其余(含 base64 内联的素材)全部打进单文件,并用window.__ModuleLoader__.load({ id, factory })包一层,供 DSH Web 壳的模块加载器在/plugins/<id>/client.js处执行。

小结:一张表看懂"双半身 + 补丁 + 双入口"

维度Node 端浏览器端
入口文件src/index.ts → lib/index.jssrc/client/index.tsx → lib/client.js
导出格式ESMCJS(__ModuleLoader__包装)
注入能力webServerslots、locale
核心职责注册两条路由、发现与缓存三个插槽渲染广告、拉取路由
缺失时降级无动态层,内置广告照常无广告层(TUI/ACP/headless 正确降级)
装配方式dsh.bundle.patch+ exports.dsh.client.*+ exports./client

一句话记忆:Node 端管"从哪来"(发现 + 缓存),浏览器端管"长什么样"(渲染 + 热切换),protocol.ts是它们之间唯一的线,cordis.patch.yml负责把插件插进 profile,exports的两个入口则分别交给服务器和浏览器去加载。想动手看代码,建议从 src/protocol.ts 这条"线"入手,再分头读两半的apply(),最后对照 tsdown.config.ts 理解产物是怎么被分别生成的。

【免费下载链接】dsh-ads

把 DSH 变成 2005 年门户网站|Parody ads, fake games, and popups for the DSH Web UI

项目地址:https://gitcode.com/gh_mirrors/ds/dsh-ads
点击查看免费下载
上一篇:awesome-macOS:300+ 款 macOS 工具合集指南
下一篇:Fort Firewall 防火墙配置教程:5分钟配出第一条拦截规则,附排错清单

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进