@goodandready/dsh-dsml-artifact-guard 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,54 +1,152 @@
1
- # dsh-dsml-artifact-guard
1
+ # 📦 @goodandready/dsh-dsml-artifact-guard
2
2
 
3
- Safe DeepSeek DSML artifact sanitizer for DeepSeek Harness.
3
+ <div align="center">
4
4
 
5
- ## What it fixes
5
+ <h3>Fail-Open Stream Sanitizer for Leaked Protocol DSML Closing Tags in DeepSeek Harness</h3>
6
6
 
7
- Some provider responses append protocol closing tags such as
8
- `</|DSML|parameter> </|DSML|invoke> </|DSML|tool_calls>` to the visible
9
- assistant text. The guard detects that terminal artifact and can remove it
10
- before the text reaches the chat UI.
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/@goodandready/dsh-dsml-artifact-guard"><img src="https://img.shields.io/npm/v/@goodandready/dsh-dsml-artifact-guard.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
9
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/GooDAnDReaDY/dsh-dsml-artifact-guard.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
10
+ <a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
11
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
12
+ </p>
11
13
 
12
- The `llm/stream` hook is deliberately synchronous. Cordis expects a stream (an
13
- `AsyncIterable`) immediately; making the hook `async` would return a `Promise`
14
- and break every turn with `stream is not async iterable`.
14
+ <p align="center">
15
+ <a href="https://goodandready.app/"><img src="https://img.shields.io/badge/All_Author_Projects-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="GoodAndReady Showcase"></a>
16
+ </p>
15
17
 
16
- ## Configuration
18
+ <p align="center">
19
+ <a href="README.md"><b>🇬🇧 English</b></a> •
20
+ <a href="docs/README.ru.md"><b>🇷🇺 Русский</b></a> •
21
+ <a href="docs/README.zh.md"><b>🇨🇳 中文说明</b></a>
22
+ </p>
17
23
 
18
- The bundle accepts:
24
+ </div>
19
25
 
20
- - `mode`: `audit` (default, log detections without changing text),
21
- `sanitize` (remove the terminal artifact), or `disabled`;
22
- - `providerId`: provider identifier to inspect (default `opencode-go`);
23
- - `modelId`: model identifier to inspect (default `deepseek-v4-flash`).
26
+ ---
24
27
 
25
- Only matching provider/model streams are changed. Other streams pass through
26
- unchanged. The sanitizer is fail-open: malformed or non-text chunks are
27
- forwarded, and only a terminal closing-tag sequence is considered an artifact.
28
+ ## Overview & The Problem
28
29
 
29
- ## Compatibility
30
+ When interacting with certain upstream model providers or API gateways, raw DSML (DeepSeek Markup Language) tool-invocation protocol tags can leak into the assistant's visible text stream. Users frequently see trailing protocol clutter like:
30
31
 
31
- Version 0.1.0 is compatible with DeepSeek Harness 0.1.2 alpha and rc web
32
- profiles and Cordis 4.x. The synchronous hook fix is required for the rc.1
33
- runtime and remains valid for later 0.1.2 releases.
32
+ ```text
33
+ Done. All tests have passed.
34
+ </|DSML|parameter> </|DSML|invoke> </|DSML|tool_calls>
35
+ ```
36
+
37
+ These leaked closing tags visually pollute the chat bubble, cause Markdown rendering glitches, and can confuse downstream agents or clipboard exports.
38
+
39
+ **`@goodandready/dsh-dsml-artifact-guard`** is a lightweight, host-only runtime stream interceptor for DeepSeek Harness that cleans up these terminal artifacts in real time before they reach the user interface:
40
+
41
+ 1. **Synchronous Stream Contract Preservation**: Cordis requires stream interceptors to return an `AsyncIterable` synchronously. Making interceptors `async` returns a `Promise` that crashes the harness turn with `stream is not async iterable`. This guard adheres strictly to the synchronous hook contract.
42
+ 2. **Split Chunk Buffer Pipeline**: Protocol tags often arrive split across multiple TCP or WebSocket text deltas. The guard maintains a small sliding buffer (`KEEP = 96` bytes) to reliably match and strip multi-chunk tails.
43
+ 3. **100% Fail-Open Safety**: Never drops legitimate user or assistant text. Legitimate discussions about DSML syntax or internal tool calls are preserved intact.
44
+ 4. **Targeted Provider & Model Scoping**: Restricts processing specifically to the provider and model configurations that exhibit tag leakage, passing other model traffic through with zero overhead.
45
+
46
+ ---
47
+
48
+ ## 🏗️ Architecture
49
+
50
+ ```mermaid
51
+ graph TD
52
+ subgraph DSH ["DeepSeek Harness Runtime"]
53
+ Turn["Agent Turn Execution<br/>(LLM Stream Request)"]
54
+ ChatUI["Chat UI Stream Consumer<br/>(Renders clean markdown text)"]
55
+ end
56
+
57
+ subgraph Guard ["@goodandready/dsh-dsml-artifact-guard"]
58
+ Hook["Synchronous llm/stream Hook<br/>(Returns AsyncIterable synchronously)"]
59
+ ScopeCheck{"Scope Match?<br/>(providerId & modelId)"}
60
+ PassThrough["Raw Stream Pass-Through<br/>(Zero overhead for other models)"]
61
+ Buffer["Sliding Tail Buffer<br/>(Preserves trailing 96 bytes across deltas)"]
62
+ Detector{"Terminal Artifact?<br/>(Matches leaked DSML tail at finish)"}
63
+ Sanitize["Sanitize Mode<br/>(Strips leaked closing tags)"]
64
+ Audit["Audit Mode<br/>(Emits ctx.logger warning only)"]
65
+ end
66
+
67
+ Turn -->|ctx.on('llm/stream')| Hook
68
+ Hook --> ScopeCheck
69
+ ScopeCheck -->|No| PassThrough
70
+ ScopeCheck -->|Yes| Buffer
71
+ PassThrough --> ChatUI
72
+ Buffer --> Detector
73
+ Detector -->|No Artifact| ChatUI
74
+ Detector -->|Artifact Found & sanitize| Sanitize --> ChatUI
75
+ Detector -->|Artifact Found & audit| Audit --> ChatUI
76
+ ```
77
+
78
+ ---
79
+
80
+ ## ✨ Features & Capabilities
81
+
82
+ ### 1. Synchronous Hook Guarantee
83
+ Under Cordis and DSH service lifecycles, event listeners on `llm/stream` must return the transformed stream synchronously. An asynchronous hook wrapper will return a `Promise<AsyncIterable>`, causing the runtime dispatcher to immediately throw `TypeError: stream is not async iterable`. `dsh-dsml-artifact-guard` wraps the stream generator in a pure synchronous registration.
84
+
85
+ ### 2. Multi-Chunk Tail Buffering
86
+ In real-world streaming, the artifact `</|DSML|parameter> </|DSML|invoke> </|DSML|tool_calls>` is frequently fractured into fragments:
87
+ * Chunk 1: `All tasks complete. </|DSML|pa`
88
+ * Chunk 2: `rameter> </|DSML|invoke> `
89
+ * Chunk 3: `</|DSML|tool_calls>`
34
90
 
35
- ## Development and tests
91
+ The guard retains a minimal 96-byte window until the next chunk or `finish` event arrives, ensuring fractured tags are seamlessly detected and sanitized as a single terminal artifact.
92
+
93
+ ### 3. Fail-Open Architecture
94
+ * If the text contains genuine prose about DSML (e.g. `<|DSML|tool_calls>example</|DSML|tool_calls>`), it is **never** removed.
95
+ * Non-text chunks (`tool-call-delta`, `usage`, `finish`) are forwarded immediately without delay.
96
+ * Any malformed chunk structure passes through transparently to preserve session stability.
97
+
98
+ ### 4. Flexible Operating Modes
99
+ * **`sanitize`** *(default)*: Strips terminal DSML closing tags and logs a warning with the count of removed artifacts.
100
+ * **`audit`**: Emits diagnostic logs with `ctx.logger.info(...)` without modifying the user-visible stream.
101
+ * **`disabled`**: Bypasses processing entirely.
102
+
103
+ ---
104
+
105
+ ## 📦 Installation
106
+
107
+ Install into your DeepSeek Harness web profile:
36
108
 
37
109
  ```bash
38
- npm test
39
- npm run check
40
- npm pack --dry-run --json
110
+ dsh plugin --profile web add @goodandready/dsh-dsml-artifact-guard
41
111
  ```
42
112
 
43
- Tests cover split stream chunks, audit versus sanitize behavior, scope
44
- matching, and the synchronous Cordis hook contract.
113
+ Restart your DeepSeek Harness instance.
45
114
 
46
- ## Related fixes
115
+ ---
116
+
117
+ ## ⚙️ Configuration (`settings.yaml`)
118
+
119
+ Configure provider and model targets in `settings.yaml` or through the Web UI:
120
+
121
+ ```yaml
122
+ # settings.yaml
123
+ dsh-dsml-artifact-guard:
124
+ mode: sanitize
125
+ providerId: "your-provider-id"
126
+ modelId: "your-model-id"
127
+ ```
128
+
129
+ ### Configuration Parameters
130
+
131
+ | Parameter | Type | Default | Description |
132
+ |:---|:---|:---|:---|
133
+ | `mode` | `string` | `"sanitize"` | Operation mode: `"sanitize"` (strip tags), `"audit"` (log only), or `"disabled"` |
134
+ | `providerId` | `string` | `"opencode-go"` | Target provider identifier exhibiting leaked tags |
135
+ | `modelId` | `string` | `"deepseek-v4-flash"` | Target model identifier exhibiting leaked tags |
136
+
137
+ ---
138
+
139
+ ## 🧪 Testing
140
+
141
+ Run the automated test suite covering split chunks, audit vs sanitize modes, scope matching, and synchronous hook contracts:
142
+
143
+ ```bash
144
+ npm test
145
+ npm run check
146
+ ```
47
147
 
48
- - Gitea issue #4: synchronous `llm/stream` hook (`stream is not async iterable`).
49
- - Gitea issue #6: package license, ignore rules, and deterministic package
50
- contents.
148
+ ---
51
149
 
52
- ## Release v0.1.1
150
+ ## 📄 License
53
151
 
54
- This public hotfix release packages the synchronous stream-hook repair and the deterministic package hygiene changes from Gitea PR #7. It is compatible with DeepSeek Harness 0.1.2 alpha/rc web profiles and keeps the fail-open, provider/model-scoped sanitizer behavior described above.
152
+ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
@@ -0,0 +1,152 @@
1
+ # 📦 @goodandready/dsh-dsml-artifact-guard
2
+
3
+ <div align="center">
4
+
5
+ <h3>Предохранитель и потоковый очиститель от артефактов закрывающих тегов протокола DSML в DeepSeek Harness</h3>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/@goodandready/dsh-dsml-artifact-guard"><img src="https://img.shields.io/npm/v/@goodandready/dsh-dsml-artifact-guard.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
9
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/GooDAnDReaDY/dsh-dsml-artifact-guard.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
10
+ <a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
11
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://goodandready.app/"><img src="https://img.shields.io/badge/Все_проекты_автора-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="Все проекты автора"></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="../README.md"><b>🇬🇧 English</b></a> •
20
+ <a href="README.ru.md"><b>🇷🇺 Русский</b></a> •
21
+ <a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
22
+ </p>
23
+
24
+ </div>
25
+
26
+ ---
27
+
28
+ ## ⚡ Назначение и решаемая проблема
29
+
30
+ При взаимодействии с некоторыми сторонними провайдерами моделей или прокси-шлюзами служебные теги протокола вызова инструментов DSML (DeepSeek Markup Language) могут просачиваться в видимый текстовый поток ассистента. В конце ответов пользователи нередко наблюдают протокольный мусор вида:
31
+
32
+ ```text
33
+ Готово. Все тесты успешно пройдены.
34
+ </|DSML|parameter> </|DSML|invoke> </|DSML|tool_calls>
35
+ ```
36
+
37
+ Такие теги загрязняют чат, ломают рендеринг Markdown и сбивают парсинг при копировании или передаче ответов другим агентам.
38
+
39
+ **`@goodandready/dsh-dsml-artifact-guard`** — легковесный плагин времени выполнения (host-only) для DeepSeek Harness, перехватывающий и удаляющий эти паразитные терминальные артефакты в реальном времени до того, как текст попадёт в интерфейс:
40
+
41
+ 1. **Строго синхронный контракт хука**: в среде Cordis обработчик события `llm/stream` обязан синхронно возвращать итератор `AsyncIterable`. Сделать обработчик асинхронным (`async`) означает вернуть `Promise`, что неизбежно приводит к падению среды выполнения с ошибкой `stream is not async iterable`. Плагин строго соблюдает синхронный контракт.
42
+ 2. **Буферизация фрагментированного хвоста**: сетевые чанки часто разрезают закрывающие теги на части. Плагин использует скользящее окно размером 96 байт (`KEEP = 96`), гарантируя надёжный захват и вырезание артефакта, даже если он разделен между пакетами.
43
+ 3. **100% Fail-Open архитектура**: плагин никогда не удаляет полезный пользовательский текст или легитимные обсуждения синтаксиса DSML.
44
+ 4. **Адресный скоупинг (Provider & Model)**: фильтрация применяется только к целевым провайдерам и моделям, у которых наблюдается утечка протокольных тегов. Трафик остальных моделей проходит без задержек и оверхеда.
45
+
46
+ ---
47
+
48
+ ## 🏗️ Архитектура работы
49
+
50
+ ```mermaid
51
+ graph TD
52
+ subgraph DSH ["Среда DeepSeek Harness"]
53
+ Turn["Ход выполнения агента<br/>(Запрос к потоку модели)"]
54
+ ChatUI["Отображение в чате<br/>(Чистый текст без паразитных тегов)"]
55
+ end
56
+
57
+ subgraph Guard ["@goodandready/dsh-dsml-artifact-guard"]
58
+ Hook["Синхронный хук llm/stream<br/>(Возвращает AsyncIterable синхронно)"]
59
+ ScopeCheck{"Совпадает скоуп?<br/>(providerId и modelId)"}
60
+ PassThrough["Сквозной пропуск потока<br/>(Нулевой оверхед для других моделей)"]
61
+ Buffer["Буфер скользящего хвоста<br/>(Сохраняет последние 96 байт)"]
62
+ Detector{"Терминальный артефакт?<br/>(Поиск хвоста DSML при finish)"}
63
+ Sanitize["Режим Sanitize<br/>(Вырезание закрывающих тегов)"]
64
+ Audit["Режим Audit<br/>(Логирование через ctx.logger)"]
65
+ end
66
+
67
+ Turn -->|ctx.on('llm/stream')| Hook
68
+ Hook --> ScopeCheck
69
+ ScopeCheck -->|Нет| PassThrough
70
+ ScopeCheck -->|Да| Buffer
71
+ PassThrough --> ChatUI
72
+ Buffer --> Detector
73
+ Detector -->|Нет артефакта| ChatUI
74
+ Detector -->|Артефакт найден & sanitize| Sanitize --> ChatUI
75
+ Detector -->|Артефакт найден & audit| Audit --> ChatUI
76
+ ```
77
+
78
+ ---
79
+
80
+ ## ✨ Подробный разбор возможностей
81
+
82
+ ### 1. Защита синхронного контракта Cordis
83
+ В архитектуре Cordis вызов `ctx.on('llm/stream', (o, next) => ...)` ожидает немедленный возврат асинхронного генератора. Если функция хука объявлена как `async`, JavaScript оборачивает возвращаемое значение в `Promise`. В результате потребители стрима падают с фатальной ошибкой `TypeError: stream is not async iterable`. `dsh-dsml-artifact-guard` оборачивает поток синхронно, сохраняя целостность рантайма.
84
+
85
+ ### 2. Захват разорванных сетевых чанков
86
+ В реальном сетевом потоке завершающая последовательность тегов может приходить отдельными фрагментами:
87
+ * Чанк 1: `Задача выполнена. </|DSML|pa`
88
+ * Чанк 2: `rameter> </|DSML|invoke> `
89
+ * Чанк 3: `</|DSML|tool_calls>`
90
+
91
+ Буфер скользящего хвоста удерживает последние 96 байт до получения следующего текстового блока или сигнала завершения `finish`, точно сопоставляя полный шаблон и удаляя его как единое целое.
92
+
93
+ ### 3. Гарантия сохранности данных (Fail-Open)
94
+ * Если в ответе содержится обычный текст с упоминанием тегов (например, руководство по написанию `<|DSML|tool_calls>`), текст **никогда** не удаляется.
95
+ * Нетекстовые чанки (`tool-call-delta`, `usage`, `finish`) пробрасываются немедленно без задержек.
96
+ * При любых непредвиденных ошибках или нестандартных форматах чанков поток не прерывается, а беспрепятственно передаётся дальше.
97
+
98
+ ### 4. Режимы работы
99
+ * **`sanitize`** *(по умолчанию)*: вырезает паразитные закрывающие теги и фиксирует событие в журнале с указанием числа удалённых артефактов.
100
+ * **`audit`**: только логирует обнаружение артефактов через `ctx.logger.info(...)`, не модифицируя текст в чате.
101
+ * **`disabled`**: полностью отключает обработку.
102
+
103
+ ---
104
+
105
+ ## 📦 Установка
106
+
107
+ Установка через CLI DeepSeek Harness:
108
+
109
+ ```bash
110
+ dsh plugin --profile web add @goodandready/dsh-dsml-artifact-guard
111
+ ```
112
+
113
+ Перезапустите экземпляр DeepSeek Harness.
114
+
115
+ ---
116
+
117
+ ## ⚙️ Конфигурация (`settings.yaml`)
118
+
119
+ Настройка провайдера и модели в `settings.yaml` или через веб-панель управления:
120
+
121
+ ```yaml
122
+ # settings.yaml
123
+ dsh-dsml-artifact-guard:
124
+ mode: sanitize
125
+ providerId: "your-provider-id"
126
+ modelId: "your-model-id"
127
+ ```
128
+
129
+ ### Таблица параметров конфигурации
130
+
131
+ | Параметр | Тип | По умолчанию | Описание |
132
+ |:---|:---|:---|:---|
133
+ | `mode` | `string` | `"sanitize"` | Режим работы: `"sanitize"` (удалять теги), `"audit"` (только логировать) или `"disabled"` |
134
+ | `providerId` | `string` | `"opencode-go"` | Идентификатор целевого провайдера, отдающего паразитные теги |
135
+ | `modelId` | `string` | `"deepseek-v4-flash"` | Идентификатор целевой модели с артефактами |
136
+
137
+ ---
138
+
139
+ ## 🧪 Тестирование
140
+
141
+ Запуск автоматических тестов:
142
+
143
+ ```bash
144
+ npm test
145
+ npm run check
146
+ ```
147
+
148
+ ---
149
+
150
+ ## 📄 Лицензия
151
+
152
+ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
@@ -0,0 +1,152 @@
1
+ # 📦 @goodandready/dsh-dsml-artifact-guard
2
+
3
+ <div align="center">
4
+
5
+ <h3>面向 DeepSeek Harness 的残留 DSML 协议闭合标签流式熔断清洗引擎</h3>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/@goodandready/dsh-dsml-artifact-guard"><img src="https://img.shields.io/npm/v/@goodandready/dsh-dsml-artifact-guard.svg?style=for-the-badge&color=6366f1&labelColor=1e1b4b" alt="npm version"></a>
9
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/GooDAnDReaDY/dsh-dsml-artifact-guard.svg?style=for-the-badge&color=10b981&labelColor=064e3b" alt="license"></a>
10
+ <a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/DSH-Plugin-8b5cf6.svg?style=for-the-badge&labelColor=2e1065" alt="DSH Plugin"></a>
11
+ <a href="https://nodejs.org"><img src="https://img.shields.io/badge/Node-20%2B-f59e0b.svg?style=for-the-badge&labelColor=451a03" alt="Node version"></a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://goodandready.app/"><img src="https://img.shields.io/badge/作者全部项目-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="作者全部项目"></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="../README.md"><b>🇬🇧 English</b></a> •
20
+ <a href="README.ru.md"><b>🇷🇺 Русский</b></a> •
21
+ <a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
22
+ </p>
23
+
24
+ </div>
25
+
26
+ ---
27
+
28
+ ## ⚡ 核心定位与解决痛点
29
+
30
+ 在对接特定第三方大模型服务商或代理网关时,底层工具调用协议 DSML(DeepSeek Markup Language)的闭合标签有时会异常泄漏并附着在模型输出的正文末尾。用户在聊天界面中经常看到多余的协议残留:
31
+
32
+ ```text
33
+ 任务完成,所有测试均已通过。
34
+ </|DSML|parameter> </|DSML|invoke> </|DSML|tool_calls>
35
+ ```
36
+
37
+ 此类闭合标签不仅造成视觉污染、破坏 Markdown 排版渲染,还会干扰后续多智能体协作解析或文本复制导出。
38
+
39
+ **`@goodandready/dsh-dsml-artifact-guard`** 是专为 DeepSeek Harness 打造的原生宿主级(host-only)文本流拦截过滤插件,在文本到达前端界面之前实时侦测并净化残留闭合标签:
40
+
41
+ 1. **严格同步 Hook 契约保障**:Cordis 要求流拦截器必须同步返回 `AsyncIterable`。将拦截器写成 `async` 会返回 `Promise`,直接引发运行时崩溃:`stream is not async iterable`。本插件严格保持同步注册。
42
+ 2. **分片分包滑动缓冲流**:网络分包常将闭合标签切断并分散在不同 Text Delta 中。插件内置 96 字节(`KEEP = 96`)滑动窗口,精准捕获并合并跨分片的长尾标签。
43
+ 3. **100% 故障开放(Fail-Open)安全**:绝不误伤合法正文。对于讨论 DSML 语法的正常技术文本,插件保证原样放行。
44
+ 4. **定向模型与服务商匹配**:仅对发生协议泄漏的目标 Provider/Model 执行流式清洗,其他模型流量直通零开销。
45
+
46
+ ---
47
+
48
+ ## 🏗️ 架构设计
49
+
50
+ ```mermaid
51
+ graph TD
52
+ subgraph DSH ["DeepSeek Harness 核心运行时"]
53
+ Turn["智能体回合执行<br/>(发起大模型流式请求)"]
54
+ ChatUI["聊天界面流式消费端<br/>(渲染纯净 Markdown 文本)"]
55
+ end
56
+
57
+ subgraph Guard ["@goodandready/dsh-dsml-artifact-guard"]
58
+ Hook["同步 llm/stream 拦截钩子<br/>(同步返回 AsyncIterable)"]
59
+ ScopeCheck{"是否命中范围?<br/>(校验 providerId 与 modelId)"}
60
+ PassThrough["原始数据流直通<br/>(其他模型零延迟零开销)"]
61
+ Buffer["滑动尾部缓冲区<br/>(跨分片保留最后 96 字节)"]
62
+ Detector{"末端残留判定?<br/>(流结束 finish 时比对标签)"]
63
+ Sanitize["清洗模式 (Sanitize)<br/>(安全剥除协议残留标签)"]
64
+ Audit["审计模式 (Audit)<br/>(仅记录日志不改动文本)"]
65
+ end
66
+
67
+ Turn -->|ctx.on('llm/stream')| Hook
68
+ Hook --> ScopeCheck
69
+ ScopeCheck -->|未命中| PassThrough
70
+ ScopeCheck -->|命中| Buffer
71
+ PassThrough --> ChatUI
72
+ Buffer --> Detector
73
+ Detector -->|无异常| ChatUI
74
+ Detector -->|检出残留 & sanitize| Sanitize --> ChatUI
75
+ Detector -->|检出残留 & audit| Audit --> ChatUI
76
+ ```
77
+
78
+ ---
79
+
80
+ ## ✨ 核心特性深度解析
81
+
82
+ ### 1. 同步 Hook 契约保护
83
+ 在 Cordis 与 DSH 的底层事件总线中,`llm/stream` 监听器必须同步返回转换后的迭代器。若将回调函数声明为 `async`,返回值将被自动包裹为 `Promise<AsyncIterable>`,导致下层调度器抛出致命异常:`TypeError: stream is not async iterable`。`dsh-dsml-artifact-guard` 采用纯同步生成器封装,从根源规避调度崩溃。
84
+
85
+ ### 2. 跨分片长尾标签精准捕获
86
+ 在实际网络传输中,长尾标签 `</|DSML|parameter> </|DSML|invoke> </|DSML|tool_calls>` 常被拆分为多个 chunk:
87
+ * Chunk 1: `All tasks complete. </|DSML|pa`
88
+ * Chunk 2: `rameter> </|DSML|invoke> `
89
+ * Chunk 3: `</|DSML|tool_calls>`
90
+
91
+ 滑动窗口在接收到下一个文本分片或 `finish` 信号前保留尾部 96 字节,确保跨包切分的标签被作为一个完整实体精准匹配并剔除。
92
+
93
+ ### 3. 数据安全与故障开放保障
94
+ * 正文技术讨论中出现 DSML 示例代码时(如 `<|DSML|tool_calls>demo</|DSML|tool_calls>`),绝不误删。
95
+ * 非文本分片(`tool-call-delta`, `usage`, `finish`)即时透传,无任何额外排队延迟。
96
+ * 遇到畸形数据格式时自动降级放行,优先确保会话畅通不中断。
97
+
98
+ ### 4. 三种灵活运行模式
99
+ * **`sanitize`** *(默认)*:自动剥离末尾残留标签,并在日志中输出剥离计数。
100
+ * **`audit`**:仅通过 `ctx.logger.info(...)` 记录检出警告,保持用户界面原样输出。
101
+ * **`disabled`**:完全旁路,不进行任何处理。
102
+
103
+ ---
104
+
105
+ ## 📦 快速安装
106
+
107
+ 通过 DeepSeek Harness CLI 一键安装:
108
+
109
+ ```bash
110
+ dsh plugin --profile web add @goodandready/dsh-dsml-artifact-guard
111
+ ```
112
+
113
+ 重启 DeepSeek Harness 实例即可生效。
114
+
115
+ ---
116
+
117
+ ## ⚙️ 配置指南 (`settings.yaml`)
118
+
119
+ 在 `settings.yaml` 或 Web UI 设置面板中配置目标服务商与模型:
120
+
121
+ ```yaml
122
+ # settings.yaml
123
+ dsh-dsml-artifact-guard:
124
+ mode: sanitize
125
+ providerId: "your-provider-id"
126
+ modelId: "your-model-id"
127
+ ```
128
+
129
+ ### 配置参数参考表
130
+
131
+ | 参数名 | 类型 | 默认值 | 功能说明 |
132
+ |:---|:---|:---|:---|
133
+ | `mode` | `string` | `"sanitize"` | 运行模式:`"sanitize"`(清洗标签)、`"audit"`(仅审计)或 `"disabled"` |
134
+ | `providerId` | `string` | `"opencode-go"` | 发生协议标签泄漏的目标服务商标识符 |
135
+ | `modelId` | `string` | `"deepseek-v4-flash"` | 发生协议标签泄漏的目标模型标识符 |
136
+
137
+ ---
138
+
139
+ ## 🧪 测试与校验
140
+
141
+ 运行全部自动化测试与语法检查:
142
+
143
+ ```bash
144
+ npm test
145
+ npm run check
146
+ ```
147
+
148
+ ---
149
+
150
+ ## 📄 开源许可证
151
+
152
+ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
package/package.json CHANGED
@@ -1 +1,28 @@
1
- {"name":"@goodandready/dsh-dsml-artifact-guard","version":"0.1.1","description":"Sanitizes leaked DeepSeek DSML closing tags from model text streams","type":"module","main":"lib/index.js","license":"MIT","files":["lib","test","README.md","LICENSE","cordis.patch.yml"],"scripts":{"check":"node --check lib/index.js","test":"node --test"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/schemastery":"^3.18.1"}}
1
+ {
2
+ "name": "@goodandready/dsh-dsml-artifact-guard",
3
+ "version": "0.1.2",
4
+ "description": "Sanitizes leaked DeepSeek DSML closing tags from model text streams",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "license": "MIT",
8
+ "files": [
9
+ "lib",
10
+ "cordis.patch.yml",
11
+ "README.md",
12
+ "docs",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "check": "node --check lib/index.js",
17
+ "test": "node --test"
18
+ },
19
+ "dsh": {
20
+ "bundle": {
21
+ "patch": "./cordis.patch.yml"
22
+ }
23
+ },
24
+ "peerDependencies": {
25
+ "@deepseek-ai/cordis": "^4.0.1",
26
+ "@deepseek-ai/schemastery": "^3.18.1"
27
+ }
28
+ }
@@ -1 +0,0 @@
1
- import assert from'node:assert/strict';import test from'node:test';import{apply,matchesScope,sanitizeDsmlArtifacts}from'../lib/index.js';const tail='</|DSML|parameter> </|DSML|invoke> </|DSML|tool_calls>';async function collect(c,o){const r=[];async function*s(){yield*c}for await(const x of sanitizeDsmlArtifacts(s(),o))r.push(x);return r}test('removes terminal tail',async()=>{const c=[{type:'tool-call-delta',index:1},{type:'text-delta',index:2,text:'Done. '+tail},{type:'finish'}];assert.deepEqual(await collect(c,{mode:'sanitize'}),[c[0],{type:'text-delta',index:2,text:'Done. '},c[2]])});test('split tail',async()=>{const c=[{type:'text-delta',index:0,text:'A '+tail.slice(0,20)},{type:'text-delta',index:0,text:tail.slice(20)},{type:'finish'}];assert.deepEqual(await collect(c,{mode:'sanitize'}),[{type:'text-delta',index:0,text:'A '},c[2]])});test('audit unchanged',async()=>{const c=[{type:'text-delta',index:0,text:tail},{type:'finish'}];assert.deepEqual(await collect(c,{mode:'audit'}),c)});test('keeps prose',async()=>{const c=[{type:'text-delta',index:0,text:'<|DSML|tool_calls>x</|DSML|tool_calls>'},{type:'usage'},{type:'finish'}];assert.deepEqual(await collect(c,{mode:'sanitize'}),c)});test('scope',()=>assert.equal(matchesScope({providerId:'opencode-go',modelId:'deepseek-v4-flash'},{providerId:'opencode-go',modelId:'deepseek-v4-flash'}),true));test('llm stream hook returns an async iterable synchronously',()=>{let hook;const ctx={on(_event,fn){hook=fn},logger:{info(){}}};apply(ctx,{mode:'sanitize',providerId:'opencode-go',modelId:'deepseek-v4-flash'});assert.equal(typeof hook,'function');async function*stream(){yield{type:'finish'}}const source=stream();const result=hook({providerId:'opencode-go',modelId:'deepseek-v4-flash'},()=>source);assert.equal(typeof result?.[Symbol.asyncIterator],'function');assert.equal(typeof result?.then,'undefined')});test('out of scope stream passes through unchanged',()=>{let hook;const ctx={on(_event,fn){hook=fn},logger:{info(){}}};apply(ctx,{mode:'sanitize',providerId:'opencode-go',modelId:'deepseek-v4-flash'});async function*stream(){yield{type:'finish'}}const source=stream();assert.equal(hook({providerId:'other',modelId:'model'},()=>source),source)});