Skip to content

fix(parser): read tag attributes from raw text, not rich HTML - #227

Open
HuanyuMark wants to merge 3 commits into
EdgeTypE:mainfrom
HuanyuMark:fix/bds-args-katex-corruption
Open

HuanyuMark wants to merge 3 commits into
EdgeTypE:mainfrom
HuanyuMark:fix/bds-args-katex-corruption

Conversation

@HuanyuMark

@HuanyuMark HuanyuMark commented Nov 1, 2026 •

Copy link
Copy Markdown

Why: BDS tag attribute values are opaque, but the parser read richText, which serialises a KaTeX subtree as outerHTML. A payload containing two dollar signs was rendered as inline math, so MCP arguments arrived as span markup instead of the original text. A failed render could delete the payload outright: katexToMarkdown returned an empty string when a .katex element had neither an annotation nor a math child, and both callers replaced the subtree with that empty string.

Fix: parse tags and attributes from rawText, where KaTeX collapses back to dollar-delimited source, and keep richText only for the overlay visible text. marked has no KaTeX plugin and needs the renderer markup, so feeding it rawText would show literal delimiters instead of formulas. Also make katexToMarkdown fall back to MathML text and then to element text rather than returning empty.

Impact: attribute values now survive dollar signs. This does NOT fix backslash-slash collapsing, bare-URL autolinking, or ampersand entity round-trips. Those are rewritten by the Markdown renderer before the DOM exists and need pre-render stream capture.

Verified: build:chrome passes. Full unit suite 146 files and 2212 tests pass. The new test fails without the katexToMarkdown change, reporting an empty extraction, and passes with it. Manual check: an MCP start_process call whose args command wraps a letter in dollar signs now delivers them literally to the server instead of KaTeX span markup.

Refs: bug report on BDS tag attribute values destroyed by the Markdown render pipeline

Update: Preserve streamed text for \/ escapes

The original commit fixed $...$ payload deletion caused by KaTeX. However, rawText (reconstructed from DOM) still loses CommonMark escapes like \/ → /.

The new commit (4e948b2) intercepts the raw SSE stream in fetch-patch.js and uses extractReplyTextFromSse to rebuild the original text before the renderer touches it. This ensures tag attributes are parsed from the exact bytes the model wrote.

  • Add src/injected/reply-text.js and its 7 tests.
  • Cache the verbatim reply in bridge.js (bds:raw-reply event) keyed by session.
  • message-processor.svelte.js prioritizes the cached stream text; falls back to DOM-reconstructed rawText for history messages.
  • Verified: 147 files / 2219 tests pass. Build succeeds.

Note: This does NOT cover history messages loaded from the API (original markdown is not stored by DeepSeek).

Why: BDS tag attribute values are opaque, but the parser read richText,
which serialises a KaTeX subtree as outerHTML. A payload containing two
dollar signs was rendered as inline math, so MCP arguments arrived as
span markup instead of the original text. A failed render could delete
the payload outright: katexToMarkdown returned an empty string when a
.katex element had neither an annotation nor a math child, and both
callers replaced the subtree with that empty string.

Fix: parse tags and attributes from rawText, where KaTeX collapses back
to dollar-delimited source, and keep richText only for the overlay
visible text. marked has no KaTeX plugin and needs the renderer markup,
so feeding it rawText would show literal delimiters instead of formulas.
Also make katexToMarkdown fall back to MathML text and then to element
text rather than returning empty.

Impact: attribute values now survive dollar signs. This does NOT fix
backslash-slash collapsing, bare-URL autolinking, or ampersand entity
round-trips. Those are rewritten by the Markdown renderer before the DOM
exists and need pre-render stream capture.

Verified: build:chrome passes. Full unit suite 146 files and 2212 tests
pass. The new test fails without the katexToMarkdown change, reporting an
empty extraction, and passes with it. Manual check: an MCP
start_process call whose args command wraps a letter in dollar signs now
delivers them literally to the server instead of KaTeX span markup.

Refs: bug report on BDS tag attribute values destroyed by the Markdown
render pipeline
为什么:`<BDS:...>` 的属性值是模型写的原始 payload,但插件此前只能从渲染后的
DOM 里反向重建。marked 在渲染阶段就按 CommonMark 规则吃掉了 `\/` 这类转义
(`\/` -> `/`),DOM 里已无据可依,parseBdsMessage 拿到的是被改写的参数,
MCP 的 args 会静默截断。PR EdgeTypE#227 把解析源从 richText 换成 rawText,绕过了
KaTeX,但 rawText 同样是 DOM 派生物,绕不过 CommonMark。

修复:fetch-patch 在读取 completion 流的 token usage 时,本来就已经把完整
SSE 响应读进 buffer。现在从同一个 buffer 里按 APPEND 操作重建回复原文,
经 bds:raw-reply 事件交给 content script,按会话缓存。message-processor
在解析标签时优先使用这份原文;缓存未命中(历史消息、刷新后重放)则退回
原有 rawText 路径,行为不变。

影响/权衡:
- 只覆盖当前会话刚生成的回复。历史消息仍然走 DOM 重建,`\/` 依旧会丢,
  这是 API 不提供原始 markdown 的限制,不是本改动能修的。
- 缓存按会话保存最近 5 条,仅最新一条 assistant 消息会消费,避免多消息
  场景下错位。
- 缓存到达时机在流结束之后,因此它参与 stateData.hash 计算;否则已 settle
  的消息不会用更好的来源重新解析。
- 裸 URL -> autolink 与 `&` -> `&amp;` 不受影响,仍由 link-artifacts 和
  HTML 实体解码兜底。

验证:
- 新增 src/injected/reply-text.test.js(7 项),覆盖 APPEND 拼接、首帧
  fragment、反斜杠保留、message_id 提取、非目标字段忽略、非 JSON 容错、
  空输入。
- 红->绿:先跑出 Cannot find module,实现后 7/7 通过。
- 全量:147 文件 / 2219 测试通过(基线 2212 + 新增 7)。
- 构建:npm build.js 成功,dist-chrome 的 injected.js 与 content.js 均包含
  bds:raw-reply。
- 真实流取证:用 Playwright 拦截 chat.deepseek.com 的 completion 响应,
  确认服务端原样返回 `D:\/test`,反斜杠在 SSE 层完好。

Refs: EdgeTypE#227
Update @playwright/test to 1.64.1 to support Ubuntu 26.04 in GitHub Actions. Verified: 147 files / 2219 tests passed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant