dsh-widget
Manifest validDSH Plugin: renders SVG/HTML snippets authored by the model into inline visualization cards within the conversation (charts, flowcharts, timelines, comparison tables) — sandboxed isolation, no network access, no dependencies.
dsh-widget
English | 中文
Inline visualization for DeepSeek Harness. The model calls one tool with a self-contained SVG or HTML fragment, and the fragment renders as a card in the conversation — diagrams, charts, timelines, comparison tables, annotated layouts — instead of being described in prose.
Install
From GitHub:
dsh plugin --profile web add github:anneqaq/dsh-widget
Or from a local checkout:
dsh plugin --profile web add /absolute/path/to/dsh-widget
Then restart dsh web. The plugin mounts at startup and has no hot reload.
Verify the row landed in the config tree:
dsh --profile web --dump-config | grep -A2 dsh-widget
If pnpm refuses with ERR_PNPM_ADDING_TO_ROOT — the shipped web profile is
a single-package workspace, so its root check fires — allow the root write:
npm_config_ignore_workspace_root_check=true dsh plugin --profile web add github:anneqaq/dsh-widget
Remove with:
dsh plugin --profile web remove dsh-widget
This package has no build step —
lib/is committed, so a git install needs nopreparescript and no pnpmallowBuildsgrant. Installing it runs no build code on your machine.
Use
The model does the work; there is nothing to configure. Ask for something
visual and it calls render_widget:
Draw me the request lifecycle for this service as a timeline.
render_widget takes four arguments:
| Argument | Required | Meaning |
|---|---|---|
html | yes | One self-contained SVG or HTML fragment. |
title | no | Caption above the card; also the frame title. |
height | no | Frame height in CSS pixels. Default 420, clamped to 80–2000. |
allowScripts | no | Enable scripts inside the fragment. Off by default. |
The tool returns only a short acknowledgement. The fragment is not echoed back into the model context — the card reads it from the logged call arguments, so a large diagram costs one copy in the transcript, not two.
How it works
Two halves, one name:
dsh-widget/
├── package.json dsh.bundle.patch + dsh.client, no dependencies
├── cordis.patch.yml inserts this plugin's node-half row
├── icon.svg plugin icon (display metadata)
├── locale/ display metadata: en.json / zh.json
├── lib/
│ ├── index.js node half → registers the tool + one prompt section
│ └── client.js browser half → renders the tool's calls
└── test/ 36 tests, no browser and no harness required
Node half registers a plain ToolDefinition and a system-prompt section
stating the authoring contract. It holds no state and draws nothing.
Browser half claims the wire name render_widget in the keyed
tool.call.toolview slot. Because the slot is keyed by wire tool name, this is
additive: it never touches the shipped tool cards.
No build step. The browser half is hand-authored in the
window.__ModuleLoader__.load({ id, factory }) lazy-CJS format the shell
expects, so the package ships lib/client.js directly — no tsdown, no
TypeScript, no bundler. react is the only module it requires, and react is
a member of the shell's frozen platform module table.
No dependencies at all. A bundle is loaded through its real path under the
profile, so a bare specifier such as @deepseek-ai/dsh-tools resolves only if
the package carries its own node_modules. This package imports nothing, which
is what lets it install from any directory — including a symlinked development
checkout. The input checks the defineTool helper would have generated are
written out in lib/index.js instead.
Isolation
The fragment is model-authored, so it is treated as untrusted content. It
renders through a sandboxed srcdoc frame, never through
dangerouslySetInnerHTML in the app's own DOM.
sandbox=""— no scripts, opaque origin.sandbox="allow-scripts"only when the caller asked withallowScripts: true, and never together withallow-same-origin(that pair would let the fragment reach the host document).- The frame's CSP is
default-src 'none'withimg-src data: blob:,style-src 'unsafe-inline', andconnect-src 'none'. A stray CDN import or afetchfails closed rather than silently exfiltrating. - Widgets carry no theme state of their own. The frame injects the palette the
app is currently painting with, tracked via
body[data-ds-dark-theme]so a live theme switch re-renders the frame.
Limits worth knowing
- The card appears when the fragment argument closes, not while it streams.
Reading a half-arrived fragment would render broken markup, so the view shows
a placeholder until
htmlis complete — which is usually before the tool result lands. - 512 KB per fragment. The markup is stored in the session log, so the ceiling bounds the transcript as well as the wire request.
- No network in the frame, by design. Inline everything.
- The card is a tool row, not prose. It renders where the call sits in the
turn. Inline-in-the-middle-of-a-paragraph rendering is not available through
a public extension point; the
tool.call.toolviewslot is the sanctioned seam.
Compatibility
package.json declares engines.dsh as
>=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-0 <0.2.0 || >=0.1.7-0 <0.2.0.
The multi-clause || form is required by semver's prerelease rule: a
prerelease version only satisfies a comparator set when a comparator in the
same [major, minor, patch] tuple carries a prerelease, so a bare
>=0.1.2-rc.1 <0.2.0 does not admit 0.1.5-rc.1. Every new upstream
prerelease tuple needs its own clause.
Developed and verified against 0.1.7-rc.2. This package deliberately declares
no peerDependencies on @deepseek-ai/dsh-*: it imports no host package,
and peerDependencies is an enforced install gate
(evaluatePluginCompatibility, fail-closed), so declaring one would add a gate
without adding truth. engines.dsh is declarative and is what the plugin market
reads for its compatibility display.
Development
npm test
36 tests, no browser and no harness required. They run the real projections: the streaming-JSON recovery that decides whether a card renders, the frame's CSP, and the tool contract.
Two checks are deliberate regression guards rather than behavior tests — the
module must import nothing, and output.schema must stay closed. Both encode
mistakes that fail loudly at harness boot rather than at unit-test time.
License
MIT
Comments
Loading…
Similar plugins
by lovezi0
DeepSeek Harness 的会话流「内联可视化」插件:模型产出 SVG / HTML 源码后,交给浏览器渲染成一张可交互的内联卡片,源码只进卡片、不回灌模型上下文。
★ 0
MIT
JavaScript
Sep 29, 2026
dsh plugin --profile web add dsh-visualizer-widgetby liceses
DSH 插件:把工作区里写好的 HTML 页展示在对话里,并让用户在页面上的表态快速回到 agent 手里。含四套预设样式。
★ 4
MIT
JavaScript
Sep 22, 2026
dsh plugin --profile web add dsh-showme-htmlby BiKing567
DSH 插件:把每个子代理渲染成主对话里可点击的卡片,点击直接进入子代理会话(与标题栏同一路径)。| DSH plugin: render every subagent as a clickable inline card that opens the child session — same navigation the header's subagent catalog uses.
★ 0
MIT
JavaScript
Aug 31, 2026
dsh plugin --profile web add dsh-subagent-panelby RyanZeeee
A visual, non-linear conversation canvas for DeepSeek Harness (DSH) — every turn is a node, every question grows a branch. DSH 插件:把线性聊天记录变成一张可以看、可以点的对话地图,每轮问答是一个节点,每次提问都是一条新分支。
★ 3
MIT
JavaScript
Sep 13, 2026
dsh plugin --profile web add dsh-chattreeby kbpoyo
DSH 插件:让纯文本模型也能看图。Web 端直接粘贴图片即可发送,无需指定图片路径;模型自主调用视觉技能查看,多模态模型原生直通,零skill绑定。
★ 5
↓ 34/wk
MIT
JavaScript
Aug 14, 2026
dsh plugin --profile web add @kbpoyo/dsh-image-bridgeby fengb3
DSH(DeepSeek Harness)Web 界面的会话标题图标插件:当模型为一次新会话生成标题时,宿主半用同一条路由发起一次辅助小请求,让模型按标题画一枚 24×24 单色 SVG 隐喻图标;浏览器半把它注入到左侧会话列表每行标题的左侧,跟随主题色。
★ 2
↓ 140/wk
MIT
TypeScript
Aug 22, 2026
dsh plugin --profile web add dsh-session-icons