@goodandready/dsh-dsml-artifact-guard 0.2.7 → 0.2.9
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/CHANGELOG.md +19 -0
- package/README.md +15 -5
- package/README.ru.md +8 -0
- package/README.zh.md +8 -0
- package/lib/client.js +130 -34
- package/lib/index.js +58 -25
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.2.9] - 2026-10-07
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Settings Card Locale Snapshot Extraction**: safely extract active locale string from `LocaleSnapshot` object (`snap?.active`) returned by `ctx.locale.getLocale()` / `getSnapshot()`, eliminating fatal `TypeError: rawLang.startsWith is not a function` during settings card mount (#50, #57).
|
|
14
|
+
- **Russian Language Registration & Ownership**: removed embedded `ru` dictionary from the plugin bundle to prevent `(namespace, 'ru')` duplicate registration collisions in core; delegate Russian translations to `dsh-russian-lang` via `props.t` and `ctx.locale.bind(NS)` (#58).
|
|
15
|
+
- **Volatile Per-Field Settings Schema**: marked configuration fields volatile individually, enabling DSH to discover and generate settings form controls (#52).
|
|
16
|
+
|
|
17
|
+
## [0.2.8] - 2026-09-26
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- **Universal DeepSeek Model Matching**: replaced strict single `providerId`/`modelId` pair with case-insensitive `modelPattern` RegExp (default: `deepseek`), automatically protecting all DeepSeek models regardless of provider gateway or vendor prefix (#48).
|
|
21
|
+
- **Target Providers Filter**: added optional `providers` array filter (empty = all providers) (#48).
|
|
22
|
+
- **Russian Localization**: added native `ru` translations in the Web UI settings card alongside English and Chinese (#48).
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- **De-identification**: removed proprietary `opencode-go` default in public package configuration.
|
|
26
|
+
- **Backward Compatibility**: user configs with legacy `providerId` and `modelId` continue to work as an additional exact rule, with a deprecation notice and one-click clear button in the Web UI settings card.
|
|
27
|
+
- **Documentation**: added dedicated section explaining lack of DSML processing in core DSH 0.1.7-rc.2.
|
|
28
|
+
|
|
10
29
|
## [0.2.7] - 2026-09-25
|
|
11
30
|
|
|
12
31
|
### Fixed
|
package/README.md
CHANGED
|
@@ -55,6 +55,14 @@ These leaked closing tags visually pollute the chat bubble, cause Markdown rende
|
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
58
|
+
## 💡 Why Not in DSH Core?
|
|
59
|
+
|
|
60
|
+
As of DeepSeek Harness **0.1.7-rc.2**, the core upstream stream pipeline (`dsh-llm-pi-ai`, `dsh-llm-deepseek*`, etc.) does not perform terminal DSML protocol cleanup — there is zero mention or handling of `|DSML|` anywhere in the core engine. When upstream gateways (e.g. `commandcode`, `deepseek-official`, or custom proxies) leak protocol closing tags at the conclusion of an assistant turn, DSH forwards them directly to the frontend.
|
|
61
|
+
|
|
62
|
+
`dsh-dsml-artifact-guard` serves as the universal runtime guard across all DeepSeek models regardless of provider gateway or routing configuration.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
58
66
|
## 🏗️ Architecture
|
|
59
67
|
|
|
60
68
|
```mermaid
|
|
@@ -141,14 +149,14 @@ Restart your DeepSeek Harness instance.
|
|
|
141
149
|
|
|
142
150
|
## ⚙️ Configuration (`settings.yaml`)
|
|
143
151
|
|
|
144
|
-
Configure
|
|
152
|
+
Configure model matching and provider targets in `settings.yaml` or through the Web UI:
|
|
145
153
|
|
|
146
154
|
```yaml
|
|
147
155
|
# settings.yaml
|
|
148
156
|
dsh-dsml-artifact-guard:
|
|
149
157
|
mode: sanitize
|
|
150
|
-
|
|
151
|
-
|
|
158
|
+
modelPattern: "deepseek" # Case-insensitive RegExp matching all DeepSeek models
|
|
159
|
+
providers: [] # Empty = guard all providers; or e.g. ["commandcode", "deepseek-official"]
|
|
152
160
|
```
|
|
153
161
|
|
|
154
162
|
### Configuration Parameters
|
|
@@ -156,8 +164,10 @@ dsh-dsml-artifact-guard:
|
|
|
156
164
|
| Parameter | Type | Default | Description |
|
|
157
165
|
|:---|:---|:---|:---|
|
|
158
166
|
| `mode` | `string` | `"sanitize"` | Operation mode: `"sanitize"` (strip tags), `"audit"` (log only), or `"disabled"` |
|
|
159
|
-
| `
|
|
160
|
-
| `
|
|
167
|
+
| `modelPattern` | `string` | `"deepseek"` | Case-insensitive RegExp matched against model IDs (e.g. `deepseek`, `deepseek-v4.*`, `deepseek/.*`) |
|
|
168
|
+
| `providers` | `string[]` | `[]` | Optional list of provider IDs. When empty, guards all providers |
|
|
169
|
+
| `providerId` | `string` | `undefined` | *(Deprecated)* Legacy exact provider identifier; maintained for backward compatibility |
|
|
170
|
+
| `modelId` | `string` | `undefined` | *(Deprecated)* Legacy exact model identifier; maintained for backward compatibility |
|
|
161
171
|
|
|
162
172
|
### HTTP Management Endpoints
|
|
163
173
|
|
package/README.ru.md
CHANGED
|
@@ -55,6 +55,14 @@
|
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
58
|
+
## 💡 Почему этого нет в ядре DSH?
|
|
59
|
+
|
|
60
|
+
По состоянию на DeepSeek Harness **0.1.7-rc.2**, ядро системы (`dsh-llm-pi-ai`, `dsh-llm-deepseek*` и др.) вообще не выполняет очистку завершающих тегов DSML — в ядровом коде нет ни единого упоминания `|DSML|`. Когда вышестоящие провайдеры (например, `commandcode`, `deepseek-official` или внешние шлюзы) отдают в потоке закрывающие теги протокола, DSH пропускает их прямо в интерфейс чата.
|
|
61
|
+
|
|
62
|
+
Плагин `dsh-dsml-artifact-guard` обеспечивает универсальную защиту для всех моделей DeepSeek независимо от конкретного шлюза или маршрутизатора.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
58
66
|
## 🏗️ Архитектура работы
|
|
59
67
|
|
|
60
68
|
```mermaid
|
package/README.zh.md
CHANGED
|
@@ -55,6 +55,14 @@
|
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
58
|
+
## 💡 为什么核心层未包含此功能?
|
|
59
|
+
|
|
60
|
+
截至 DeepSeek Harness **0.1.7-rc.2**,核心上游流管道(`dsh-llm-pi-ai`、`dsh-llm-deepseek*` 等)均未对末端 DSML 协议标签进行任何清洗——核心引擎中完全不存在对 `|DSML|` 的处理。当上游网关(例如 `commandcode`、`deepseek-official` 或其他代理)在回复结尾泄漏协议闭合标签时,DSH 会将其直接传输到聊天前端。
|
|
61
|
+
|
|
62
|
+
`dsh-dsml-artifact-guard` 为所有 DeepSeek 模型提供通用的运行时防护,不受特定服务商网关或路由配置的限制。
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
58
66
|
## 🏗️ 架构设计
|
|
59
67
|
|
|
60
68
|
```mermaid
|
package/lib/client.js
CHANGED
|
@@ -41,8 +41,15 @@ window.__ModuleLoader__.load({
|
|
|
41
41
|
modeSanitize: 'Sanitize (strip leaked closing tags)',
|
|
42
42
|
modeAudit: 'Audit (log warnings only, preserve output)',
|
|
43
43
|
modeDisabled: 'Disabled (bypass all streams)',
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
modelPattern: 'Model Pattern (RegExp)',
|
|
45
|
+
modelPatternHint: 'Case-insensitive RegExp matched against model IDs (e.g. deepseek, deepseek-v4.*). Default: deepseek.',
|
|
46
|
+
providers: 'Target Providers (optional)',
|
|
47
|
+
providersPlaceholder: 'all providers (comma-separated, e.g. commandcode, deepseek-official)',
|
|
48
|
+
providersHint: 'Comma-separated list of provider IDs. Leave empty to guard all providers.',
|
|
49
|
+
legacyDetected: 'Legacy providerId / modelId detected: active as an additional rule.',
|
|
50
|
+
legacyClear: 'Clear legacy fields',
|
|
51
|
+
providerId: 'Target Provider ID (legacy)',
|
|
52
|
+
modelId: 'Target Model ID (legacy)',
|
|
46
53
|
disabledNotice: '⚠️ Guard is currently disabled. DSML closing tags will reach the chat interface.',
|
|
47
54
|
statusUnavailable: 'Settings service unavailable.',
|
|
48
55
|
saving: 'Saving…',
|
|
@@ -67,8 +74,15 @@ window.__ModuleLoader__.load({
|
|
|
67
74
|
modeSanitize: 'Sanitize 清洗(自动剥除残留闭合标签)',
|
|
68
75
|
modeAudit: 'Audit 审计(仅在日志记录警告,不修改正文)',
|
|
69
76
|
modeDisabled: 'Disabled 禁用(直通所有数据流)',
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
modelPattern: '模型正则匹配式 (RegExp)',
|
|
78
|
+
modelPatternHint: '对模型 ID 进行不区分大小写的正则表达式匹配(默认:deepseek)。支持任意服务商前缀。',
|
|
79
|
+
providers: '目标服务商(可选)',
|
|
80
|
+
providersPlaceholder: '所有服务商(以逗号分隔,例如 commandcode, deepseek-official)',
|
|
81
|
+
providersHint: '以逗号分隔的服务商 ID 列表。留空则保护所有服务商。',
|
|
82
|
+
legacyDetected: '检测到旧版 providerId / modelId 配置,并作为附加精确规则生效。',
|
|
83
|
+
legacyClear: '清除旧版配置',
|
|
84
|
+
providerId: '目标服务商标识 (Provider ID, 旧版)',
|
|
85
|
+
modelId: '目标模型标识 (Model ID, 旧版)',
|
|
72
86
|
disabledNotice: '⚠️ 守卫当前已禁用,残留的 DSML 闭合标签将直接显示在聊天界面。',
|
|
73
87
|
statusUnavailable: '配置服务不可用。',
|
|
74
88
|
saving: '正在保存…',
|
|
@@ -77,10 +91,13 @@ window.__ModuleLoader__.load({
|
|
|
77
91
|
saveError: '保存失败: {msg}',
|
|
78
92
|
};
|
|
79
93
|
|
|
94
|
+
|
|
80
95
|
const DEFAULTS = {
|
|
81
96
|
mode: 'sanitize',
|
|
82
|
-
|
|
83
|
-
|
|
97
|
+
modelPattern: 'deepseek',
|
|
98
|
+
providers: '',
|
|
99
|
+
providerId: '',
|
|
100
|
+
modelId: '',
|
|
84
101
|
};
|
|
85
102
|
|
|
86
103
|
function PluginCard({ ctx: _ctx, t, bare }) {
|
|
@@ -115,10 +132,38 @@ window.__ModuleLoader__.load({
|
|
|
115
132
|
}
|
|
116
133
|
const scope = scopeRef.current;
|
|
117
134
|
|
|
118
|
-
const
|
|
135
|
+
const rawLang = (() => {
|
|
136
|
+
try {
|
|
137
|
+
const snap = typeof _ctx?.locale?.getLocale === 'function'
|
|
138
|
+
? _ctx.locale.getLocale()
|
|
139
|
+
: (typeof _ctx?.locale?.getSnapshot === 'function' ? _ctx.locale.getSnapshot() : null);
|
|
140
|
+
if (typeof snap?.active === 'string' && snap.active) return snap.active;
|
|
141
|
+
if (typeof snap === 'string' && snap) return snap;
|
|
142
|
+
} catch (err) {
|
|
143
|
+
/* Fallback if locale service getter fails */
|
|
144
|
+
}
|
|
145
|
+
if (typeof navigator !== 'undefined' && typeof navigator.language === 'string' && navigator.language) {
|
|
146
|
+
return navigator.language;
|
|
147
|
+
}
|
|
148
|
+
return 'en';
|
|
149
|
+
})();
|
|
150
|
+
const currentLang = rawLang.startsWith('zh') ? 'zh' : 'en';
|
|
119
151
|
const dict = currentLang === 'zh' ? zh : en;
|
|
152
|
+
|
|
153
|
+
const tBound = React.useMemo(() => {
|
|
154
|
+
if (typeof t === 'function') return t;
|
|
155
|
+
if (typeof _ctx?.locale?.bind === 'function') {
|
|
156
|
+
try {
|
|
157
|
+
return _ctx.locale.bind(NS);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
/* Locale bind fallback */
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}, [t, _ctx]);
|
|
164
|
+
|
|
120
165
|
const tt = (k, params) => {
|
|
121
|
-
let str = (typeof
|
|
166
|
+
let str = (typeof tBound === 'function' ? tBound(k) : null) || dict[k] || en[k] || k;
|
|
122
167
|
if (params && typeof params === 'object') {
|
|
123
168
|
for (const [pk, pv] of Object.entries(params)) {
|
|
124
169
|
str = str.replace(new RegExp('\\{' + pk + '\\}', 'g'), String(pv));
|
|
@@ -137,22 +182,19 @@ window.__ModuleLoader__.load({
|
|
|
137
182
|
const syncSnapshot = (snap) => {
|
|
138
183
|
if (!snap || cancelled) return;
|
|
139
184
|
const view = snap.view || snap.value || snap;
|
|
140
|
-
if (typeof snap === 'object' && 'status' in snap) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
modelId: view.modelId || DEFAULTS.modelId,
|
|
146
|
-
});
|
|
147
|
-
setStatus('ready');
|
|
148
|
-
} else {
|
|
149
|
-
setStatus(snap.status);
|
|
150
|
-
}
|
|
151
|
-
} else if (typeof view === 'object') {
|
|
185
|
+
if (typeof snap === 'object' && 'status' in snap && snap.status !== 'ready') {
|
|
186
|
+
setStatus(snap.status);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (typeof view === 'object' && view !== null) {
|
|
152
190
|
setDraft({
|
|
153
191
|
mode: view.mode || DEFAULTS.mode,
|
|
154
|
-
|
|
155
|
-
|
|
192
|
+
modelPattern: view.modelPattern !== undefined ? String(view.modelPattern) : DEFAULTS.modelPattern,
|
|
193
|
+
providers: Array.isArray(view.providers)
|
|
194
|
+
? view.providers.join(', ')
|
|
195
|
+
: (typeof view.providers === 'string' ? view.providers : ''),
|
|
196
|
+
providerId: view.providerId ? String(view.providerId) : '',
|
|
197
|
+
modelId: view.modelId ? String(view.modelId) : '',
|
|
156
198
|
});
|
|
157
199
|
setStatus('ready');
|
|
158
200
|
} else {
|
|
@@ -253,9 +295,16 @@ window.__ModuleLoader__.load({
|
|
|
253
295
|
setMsg('');
|
|
254
296
|
try {
|
|
255
297
|
const failures = [];
|
|
256
|
-
|
|
298
|
+
const entries = [
|
|
299
|
+
['mode', draft.mode || 'sanitize'],
|
|
300
|
+
['modelPattern', (draft.modelPattern !== undefined && draft.modelPattern !== null) ? String(draft.modelPattern).trim() : 'deepseek'],
|
|
301
|
+
['providers', draft.providers ? draft.providers.split(',').map(s => s.trim()).filter(Boolean) : []],
|
|
302
|
+
['providerId', draft.providerId ? String(draft.providerId).trim() : ''],
|
|
303
|
+
['modelId', draft.modelId ? String(draft.modelId).trim() : ''],
|
|
304
|
+
];
|
|
305
|
+
for (const [key, val] of entries) {
|
|
257
306
|
try {
|
|
258
|
-
await scope.set(key,
|
|
307
|
+
await scope.set(key, val);
|
|
259
308
|
} catch (err) {
|
|
260
309
|
failures.push(key + ': ' + (err.message || String(err)));
|
|
261
310
|
}
|
|
@@ -452,15 +501,16 @@ window.__ModuleLoader__.load({
|
|
|
452
501
|
)
|
|
453
502
|
),
|
|
454
503
|
|
|
455
|
-
//
|
|
504
|
+
// Model Pattern Field
|
|
456
505
|
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
|
|
457
506
|
React.createElement('label', {
|
|
458
507
|
style: { fontSize: 13, fontWeight: 500, color: 'var(--dsw-alias-label-primary)' },
|
|
459
|
-
}, tt('
|
|
508
|
+
}, tt('modelPattern')),
|
|
460
509
|
React.createElement('input', {
|
|
461
510
|
type: 'text',
|
|
462
|
-
value: draft.
|
|
463
|
-
|
|
511
|
+
value: draft.modelPattern,
|
|
512
|
+
placeholder: 'deepseek',
|
|
513
|
+
onChange: (e) => setDraft(d => ({ ...d, modelPattern: e.target.value })),
|
|
464
514
|
style: {
|
|
465
515
|
height: 34,
|
|
466
516
|
borderRadius: 6,
|
|
@@ -470,18 +520,22 @@ window.__ModuleLoader__.load({
|
|
|
470
520
|
padding: '0 10px',
|
|
471
521
|
fontSize: 13,
|
|
472
522
|
},
|
|
473
|
-
})
|
|
523
|
+
}),
|
|
524
|
+
React.createElement('span', {
|
|
525
|
+
style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary)', lineHeight: '1.4' },
|
|
526
|
+
}, tt('modelPatternHint'))
|
|
474
527
|
),
|
|
475
528
|
|
|
476
|
-
//
|
|
529
|
+
// Target Providers Field
|
|
477
530
|
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
|
|
478
531
|
React.createElement('label', {
|
|
479
532
|
style: { fontSize: 13, fontWeight: 500, color: 'var(--dsw-alias-label-primary)' },
|
|
480
|
-
}, tt('
|
|
533
|
+
}, tt('providers')),
|
|
481
534
|
React.createElement('input', {
|
|
482
535
|
type: 'text',
|
|
483
|
-
value: draft.
|
|
484
|
-
|
|
536
|
+
value: draft.providers,
|
|
537
|
+
placeholder: tt('providersPlaceholder'),
|
|
538
|
+
onChange: (e) => setDraft(d => ({ ...d, providers: e.target.value })),
|
|
485
539
|
style: {
|
|
486
540
|
height: 34,
|
|
487
541
|
borderRadius: 6,
|
|
@@ -491,7 +545,49 @@ window.__ModuleLoader__.load({
|
|
|
491
545
|
padding: '0 10px',
|
|
492
546
|
fontSize: 13,
|
|
493
547
|
},
|
|
494
|
-
})
|
|
548
|
+
}),
|
|
549
|
+
React.createElement('span', {
|
|
550
|
+
style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary)', lineHeight: '1.4' },
|
|
551
|
+
}, tt('providersHint'))
|
|
552
|
+
),
|
|
553
|
+
|
|
554
|
+
// Legacy Provider/Model notice if detected
|
|
555
|
+
(draft.providerId || draft.modelId) && React.createElement('div', {
|
|
556
|
+
style: {
|
|
557
|
+
display: 'flex',
|
|
558
|
+
alignItems: 'flex-start',
|
|
559
|
+
justifyContent: 'space-between',
|
|
560
|
+
gap: 8,
|
|
561
|
+
padding: '8px 10px',
|
|
562
|
+
borderRadius: 6,
|
|
563
|
+
background: 'color-mix(in srgb, var(--dsw-alias-state-warning-primary) 10%, transparent)',
|
|
564
|
+
border: '1px solid color-mix(in srgb, var(--dsw-alias-state-warning-primary) 25%, transparent)',
|
|
565
|
+
fontSize: 12,
|
|
566
|
+
color: 'var(--dsw-alias-label-secondary)',
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 2 } },
|
|
570
|
+
React.createElement('span', null, tt('legacyDetected')),
|
|
571
|
+
React.createElement('span', { style: { fontFamily: 'monospace', fontSize: 11 } },
|
|
572
|
+
(draft.providerId ? 'provider: ' + draft.providerId : '') +
|
|
573
|
+
(draft.providerId && draft.modelId ? ' | ' : '') +
|
|
574
|
+
(draft.modelId ? 'model: ' + draft.modelId : '')
|
|
575
|
+
)
|
|
576
|
+
),
|
|
577
|
+
React.createElement('button', {
|
|
578
|
+
type: 'button',
|
|
579
|
+
onClick: () => setDraft(d => ({ ...d, providerId: '', modelId: '' })),
|
|
580
|
+
style: {
|
|
581
|
+
background: 'transparent',
|
|
582
|
+
border: 'none',
|
|
583
|
+
color: 'var(--dsw-alias-brand-primary, var(--dsw-alias-label-primary))',
|
|
584
|
+
fontSize: 11,
|
|
585
|
+
cursor: 'pointer',
|
|
586
|
+
textDecoration: 'underline',
|
|
587
|
+
whiteSpace: 'nowrap',
|
|
588
|
+
padding: '2px 4px',
|
|
589
|
+
},
|
|
590
|
+
}, tt('legacyClear'))
|
|
495
591
|
),
|
|
496
592
|
|
|
497
593
|
// Actions
|
package/lib/index.js
CHANGED
|
@@ -9,20 +9,57 @@ const KEEP = 96;
|
|
|
9
9
|
export const name = '@goodandready/dsh-dsml-artifact-guard';
|
|
10
10
|
export const inject = ['llm', 'settings', 'webServer'];
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
// DSH builds a namespace's settings form from the PER-FIELD volatile markers of the
|
|
13
|
+
// entry schema. Wrapping the object in .volatile() produced none of them, so no form was
|
|
14
|
+
// ever generated. Mark each user-editable field instead.
|
|
15
|
+
export const Config = Schema.object({
|
|
16
|
+
mode: Schema.string().default('sanitize').volatile().description('How to handle detected artifact markup'),
|
|
17
|
+
modelPattern: Schema.string().default('deepseek').volatile().description('Model name substring this plugin guards'),
|
|
18
|
+
providers: Schema.array(Schema.string()).default([]).volatile().description('Restrict guarding to these providers; empty means all'),
|
|
19
|
+
providerId: Schema.string().volatile().description('Legacy exact provider id match'),
|
|
20
|
+
modelId: Schema.string().volatile().description('Legacy exact model id match'),
|
|
16
21
|
});
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
// Unwrap Volatile boxes before any caller reads a value.
|
|
24
|
+
export function plainConfig(value) {
|
|
25
|
+
if (value === null || typeof value !== 'object') return value;
|
|
26
|
+
if (Array.isArray(value)) return value.map(plainConfig);
|
|
27
|
+
if (typeof value.get === 'function') return plainConfig(value.get());
|
|
28
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, plainConfig(v)]));
|
|
29
|
+
}
|
|
21
30
|
|
|
22
31
|
export function matchesScope(o, c) {
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
32
|
+
if (!c || c.mode === 'disabled') return false;
|
|
33
|
+
const provider = String(o?.providerId ?? o?.provider?.id ?? o?.provider ?? '').trim();
|
|
34
|
+
const model = String(o?.modelId ?? o?.model?.id ?? o?.model?.name ?? o?.model ?? '').trim();
|
|
35
|
+
if (!model && !provider) return false;
|
|
36
|
+
|
|
37
|
+
// 1. Backward compatibility: if legacy exact providerId/modelId were specified and match
|
|
38
|
+
if (c.providerId && c.modelId && provider === c.providerId && model === c.modelId) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 2. Provider check: if c.providers is non-empty, provider must be included
|
|
43
|
+
const providersList = Array.isArray(c.providers)
|
|
44
|
+
? c.providers.map(p => String(p).trim()).filter(Boolean)
|
|
45
|
+
: (typeof c.providers === 'string' && c.providers.trim() ? c.providers.split(',').map(p => p.trim()).filter(Boolean) : []);
|
|
46
|
+
|
|
47
|
+
if (providersList.length > 0) {
|
|
48
|
+
if (!providersList.includes(provider)) return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 3. Model pattern check (case-insensitive regex)
|
|
52
|
+
const pattern = (c.modelPattern !== undefined && c.modelPattern !== null && String(c.modelPattern).trim() !== '')
|
|
53
|
+
? String(c.modelPattern).trim()
|
|
54
|
+
: 'deepseek';
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const rx = new RegExp(pattern, 'i');
|
|
58
|
+
return rx.test(model);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
/* Fallback to substring matching if regex syntax is invalid */
|
|
61
|
+
return model.toLowerCase().includes(pattern.toLowerCase());
|
|
62
|
+
}
|
|
26
63
|
}
|
|
27
64
|
|
|
28
65
|
export async function* sanitizeDsmlArtifacts(s, { mode = 'sanitize', onArtifact } = {}) {
|
|
@@ -109,25 +146,21 @@ export async function* sanitizeDsmlArtifacts(s, { mode = 'sanitize', onArtifact
|
|
|
109
146
|
|
|
110
147
|
export function unwrapConfig(conf) {
|
|
111
148
|
if (!conf) return {};
|
|
112
|
-
|
|
149
|
+
// A Volatile reference (any field is now volatile) resolves through .get().
|
|
150
|
+
const readRef = (val) => {
|
|
113
151
|
try {
|
|
114
|
-
const
|
|
115
|
-
return
|
|
116
|
-
} catch
|
|
117
|
-
/* Volatile getter failed */
|
|
152
|
+
const inner = typeof val?.get === 'function' ? val.get() : val;
|
|
153
|
+
return inner && typeof inner === 'object' ? inner : {};
|
|
154
|
+
} catch {
|
|
118
155
|
return {};
|
|
119
156
|
}
|
|
120
|
-
}
|
|
157
|
+
};
|
|
158
|
+
if (typeof conf.get === 'function') return readRef(conf);
|
|
121
159
|
try {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
return (res && typeof res === 'object') ? res : {};
|
|
128
|
-
} catch (err) {
|
|
129
|
-
/* Config validation fallback to object */
|
|
130
|
-
return (conf && typeof conf === 'object') ? conf : {};
|
|
160
|
+
// Config() returns volatile boxes now, so unwrap them; a plain input stays plain.
|
|
161
|
+
return plainConfig(Config(conf));
|
|
162
|
+
} catch {
|
|
163
|
+
return plainConfig(conf) && typeof plainConfig(conf) === 'object' ? plainConfig(conf) : {};
|
|
131
164
|
}
|
|
132
165
|
}
|
|
133
166
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-dsml-artifact-guard",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "Sanitizes leaked DeepSeek DSML closing tags from model text streams",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -33,12 +33,15 @@
|
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
36
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
36
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
37
37
|
},
|
|
38
38
|
"exports": {
|
|
39
39
|
".": "./lib/index.js",
|
|
40
40
|
"./client": "./lib/client.js",
|
|
41
41
|
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
42
42
|
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
43
46
|
}
|
|
44
47
|
}
|