Back to home@boomboomxx

dsh-markstream

No description

Stars
0
Language
TypeScript
Created
Aug 26, 2026
Updated
Aug 26, 2026
GitHub repo

Introduction

dsh-markstream

A DeepSeek Harness (DSH) web plugin that renders assistant messages in the conversation stream with markstream-react — the low-jitter streaming Markdown renderer for AI chat (streamed code blocks, Mermaid, KaTeX, safe HTML).

English | 中文

What it does

  • Replaces the assistant-step renderer in the conversation.chat.node keyed slot with a markstream-based implementation, registered with priority: -10 (slot priority is ascending — lowest renders) to shadow the shipped renderer's same-key entry at default priority 0. Registering the same key at the same priority throws:
    • Text blocks render through <MarkdownRender content final fade> with low-jitter incremental streaming, including intermediate states of incomplete Markdown;
    • Streamed code blocks (via the optional stream-diffs package), Mermaid (optional mermaid), KaTeX (inlined with the dependency tree in the current build), and safe HTML;
    • Reasoning blocks keep the Think disclosure row, image groups keep the attachment gallery, unknown blocks keep the JSON fallback, and interrupted messages keep the "stopped" marker — matching the shipped renderer;
    • The turn-tail node (turn-tail, including IconActions / produced-file rows) and tool-row rendering remain the product's own.
  • The renderer's t locale seat is bound to the conversation namespace by the slot declaration, so it reuses ui-conversation's dictionary (row.running / message.stopped / message.unknownBlock / json.truncated) without registering a new namespace.

Floating config panel

The config surface is modeled on the demo site markstream-react.pages.dev (CODE THEME / DARK MODE etc.), the component docs, and the recommended code-block config, delivered as two layers:

  1. Composition layer: the plugin row's config (full template in cordis.patch.yml) — deployment defaults, validated by the Config schema and used as the settings namespace base.
  2. User layer: a floating gear button at the frame's bottom-right (shell.overlay entry dsh-markstream-config) opens a non-modal floating panel (demo-site style: side drawer, non-blocking, the rest of the page stays interactive); the header × closes it back to the gear. Fields override and persist to $DSH_HOME/settings.yaml; the "reset" affordance next to a field clears the user override and falls back to the composition value (a field's presence in the user layer marks it as overridden). Writes go through a revision-fenced settings scope and take effect immediately (the renderer re-installs itself on config changes).

Settings → Plugins → Config no longer shows a Markstream card; the gear panel is the single config entry point.

FieldDefaultMeaning
enabledtrueEnable switch: false restores the shipped Markdown renderer (the gear panel stays reachable)
themeautoauto follows the DSH theme (body[data-ds-dark-theme]) / light / dark (maps to MarkdownRender isDark)
fadetrueFade-in animation for streamed non-code nodes
typewriterfalseBlinking typewriter cursor while content streams

Code blocks (codeBlockProps header toggles + codeBlockOptions, following the stream-diffs surface recommendation; effective only with stream-diffs, falls back to a plain <pre> without it):

FieldDefaultMeaning
codeBlockShowHeadertrueShow the code-block header
codeBlockShowTooltipstrueShow tooltips
codeBlockShowFontSizeButtonstrueShow font-size buttons
codeBlockShowCollapseButtonfalseShow collapse button
codeBlockDiffStyleunifiedunified / split
codeBlockOverflowwrapwrap / scroll
codeBlockExpandUnchangedfalseCollapse unchanged diff regions
codeBlockEnableLineSelectiontrueEnable line selection
codeBlockDisableLineNumbersfalseHide line numbers
codeBlockFontSize13Code font size in px (13 recommended)
codeBlockTabSize2Tab width
codeBlockPadding12Symmetric code padding in px
codeBlockMaxHeight480Max height in px (0 = unlimited)
codeBlockLightThemevitesse-lightLight code theme (Shiki registration name)
codeBlockDarkThemevitesse-darkDark code theme

Diagrams (each needs its optional dependency; without it the source is shown as fallback):

FieldDefaultMeaning
mermaidEnabledfalseForward mermaidProps (needs mermaid)
mermaidIsStricttruemermaidProps.isStrict
mermaidMaxHeight480mermaidProps.maxHeight in px (0 = unlimited)
d2EnabledfalseForward d2Props (needs @terrastruct/d2)
d2MaxHeight480d2Props.maxHeight in px (0 = unlimited)

Directory layout

dsh-markstream/
├── package.json                  # dsh.client manifest + dsh.bundle.patch declaration
├── cordis.patch.yml              # plugin-row insert + composition config template (bundle channel)
├── tsconfig.json                 # typecheck (@deepseek-ai/* types from npm peers 0.1.0-rc.8)
├── tsconfig.build.json           # tsc full emit to lib/types (JS + d.ts)
├── tsdown.config.ts              # emits lib/client.js (browser half)
├── scripts/stage-node.mjs        # stages the Node-half files from lib/types to lib/
└── src/
    ├── index.ts                  # Node half: registers the dsh-markstream settings namespace (base = row config)
    ├── schema.ts                 # flat schema (schemastery z; row Config = namespace schema)
    ├── config.ts                 # shared field definitions / defaults / option tables
    ├── invariant.ts              # invariant companion
    └── client/
        ├── index.ts              # browser half: shell.overlay floating panel + assistant-step renderer
        ├── card-store.ts         # snapshot store (settings scope binding + set/unset)
        ├── ConfigSurface.tsx     # bottom-right gear + non-modal floating panel
        ├── ConfigSurface.module.css
        ├── ConfigFields.tsx      # shared field rows (demo-site style: section titles + hairline dividers)
        ├── ConfigFields.module.css
        ├── AssistantMarkstream.tsx   # config-driven assistant-step renderer (markstream-react)
        ├── AssistantMarkstream.module.css
        ├── locales.ts            # dsh-markstream dictionaries (zh/en)
        └── css-modules.d.ts

Build

pnpm install
pnpm build        # tsc -p tsconfig.build.json && node scripts/stage-node.mjs && tsdown

Artifacts:

  • lib/client.js — the browser half in the DSH client-bundle format (window.__ModuleLoader__.load({ id, factory })): markstream-react / markstream-core / stream-markdown-parser / @floating-ui/* / katex are all inlined into one file (inlineDynamicImports; the module loader serves only /plugins/<id>/client.js); react / react-dom / react/jsx-runtime / @deepseek-ai/dsh-client-ui-primitives stay as module-table externals; markstream-react/index.css is compiled and injected as a tagged data-plugin-css style.
  • lib/index.js / lib/invariant.js — the Node halves (plus config.js / schema.js staged by stage-node; Node resolves the plain .js relative imports; @deepseek-ai/dsh-settings / @deepseek-ai/schemastery stay external).
  • lib/types/**/*.d.ts — declarations.

@deepseek-ai/* types come from the auto-installed npm peers (0.1.0-rc.8, the same release the deployed GUI runs); at runtime the DSH client module table provides them.

Install into a DSH profile (web)

This package declares dsh.bundle.patch, so it installs through the official CLI channel:

dsh plugin --profile web add dsh-markstream

For an unpublished local package, wire it manually (local ~/.dsh/profiles/web):

  1. Add the package to the profile dependencies and install (heal node_modules). For local development use link:: file: makes pnpm pack and copy the package, so every plugin edit needs a reinstall; link: is a symlink, so after pnpm build rebuilds lib/client.js the server serves the new file directly (client-hmr picks up the change and hot-reloads without a restart):

    # edit ~/.dsh/profiles/web/package.json
    #   "dependencies": { "dsh-markstream": "link:D:/path/to/dsh-markstream" }
    #   "dsh": { "profile": { "bundles": [ ..., "dsh-markstream" ] } }
    cd ~/.dsh/profiles/web && pnpm install
    
  2. Restart dsh web and refresh the page. The plugin row mounts through the insert in cordis.patch.yml; you can also append it manually to the profile's cordis.patch.yml:

    - insert:
        - id: markstream
          name: 'dsh-markstream'
    

Optional features (markstream optional peers)

FeaturePackageNotes
Enhanced code blocksstream-diffs (bundled dependency)File/FileDiff surface + syntax highlighting; registered theme names resolve exact colors, unregistered names fall back through the documented seam (--markstream-code-fallback-*) to the selected theme's bg/fg
Mermaid diagramsmermaidMermaid blocks degrade when absent
KaTeX mathkatexInlined with the @deepseek-ai/dsh-client-ui-primitives dependency tree in the current build
D2 diagrams@terrastruct/d2Unresolved at build time, treated as external (only needed when rendering d2 blocks)
Infographics@antv/infographicSame, treated as external

Known limitations

  • Per-block rendering: each text block is handed to MarkdownRender independently (matching the shipped per-block renderer); Markdown structures split across text blocks (e.g. a code fence spanning two blocks) are not merged.
  • No file-mention enhancement: the shipped MarkdownText's fileMentions inline-code highlighting is not replicated in v1 (markstream renders them as plain inline code).
  • No tail-follow scrolling for Think rows: the collapsed summary follows the latest line, but without the shipped renderer's automatic horizontal follow scroll.
  • Replaces the shipped renderer: assistant-step is a keyed slot, so registering the same key replaces the shipped implementation wholesale; this plugin replicates text / reasoning / image / unknown-block / stopped-marker presentation, but later product changes are not synced automatically.
  • Panel renders settings only: the bottom-right gear panel edits and persists rendering fields (enabled / theme / …); writes persist to $DSH_HOME/settings.yaml.

License

MIT