@goodandready/dsh-dsml-artifact-guard 0.2.7 → 0.2.8
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 +12 -0
- package/README.md +15 -5
- package/README.ru.md +8 -0
- package/README.zh.md +8 -0
- package/lib/client.js +136 -35
- package/lib/index.js +35 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.2.8] - 2026-09-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **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).
|
|
14
|
+
- **Target Providers Filter**: added optional `providers` array filter (empty = all providers) (#48).
|
|
15
|
+
- **Russian Localization**: added native `ru` translations in the Web UI settings card alongside English and Chinese (#48).
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- **De-identification**: removed proprietary `opencode-go` default in public package configuration.
|
|
19
|
+
- **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.
|
|
20
|
+
- **Documentation**: added dedicated section explaining lack of DSML processing in core DSH 0.1.7-rc.2.
|
|
21
|
+
|
|
10
22
|
## [0.2.7] - 2026-09-25
|
|
11
23
|
|
|
12
24
|
### 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,45 @@ window.__ModuleLoader__.load({
|
|
|
77
91
|
saveError: '保存失败: {msg}',
|
|
78
92
|
};
|
|
79
93
|
|
|
94
|
+
const ru = {
|
|
95
|
+
title: 'Защита от артефактов DSML',
|
|
96
|
+
sub: 'Фильтрация утекших замыкающих тегов протокола DSML из потоков моделей DeepSeek',
|
|
97
|
+
version: 'Версия',
|
|
98
|
+
updateChecking: 'Проверка обновлений…',
|
|
99
|
+
updateAvailable: 'Доступно обновление: v{v}',
|
|
100
|
+
upToDate: 'Установлена последняя версия',
|
|
101
|
+
updateNow: 'Обновить сейчас',
|
|
102
|
+
updating: 'Обновление…',
|
|
103
|
+
updateSuccess: 'Обновлено до v{v}. Перезапустите службу DSH для применения изменений.',
|
|
104
|
+
updateFailed: 'Ошибка обновления: {msg}',
|
|
105
|
+
checkUpdateFailed: 'Не удалось проверить обновления',
|
|
106
|
+
mode: 'Режим работы',
|
|
107
|
+
modeSanitize: 'Санация (вырезать утекшие замыкающие теги)',
|
|
108
|
+
modeAudit: 'Аудит (только логировать предупреждения в журнал)',
|
|
109
|
+
modeDisabled: 'Отключено (пропускать все потоки без изменений)',
|
|
110
|
+
modelPattern: 'Шаблон модели (RegExp)',
|
|
111
|
+
modelPatternHint: 'Регулярное выражение без учёта регистра для ID модели (по умолчанию: deepseek).',
|
|
112
|
+
providers: 'Целевые провайдеры (опционально)',
|
|
113
|
+
providersPlaceholder: 'все провайдеры (через запятую, напр. commandcode, deepseek-official)',
|
|
114
|
+
providersHint: 'Список ID провайдеров через запятую. Оставьте пустым для защиты всех провайдеров.',
|
|
115
|
+
legacyDetected: 'Обнаружены устаревшие параметры providerId / modelId (действуют как дополнительное правило).',
|
|
116
|
+
legacyClear: 'Очистить устаревшие поля',
|
|
117
|
+
providerId: 'Целевой провайдер (устаревшее)',
|
|
118
|
+
modelId: 'Целевая модель (устаревшее)',
|
|
119
|
+
disabledNotice: '⚠️ Защита отключена. Замыкающие теги DSML будут попадать в интерфейс чата.',
|
|
120
|
+
statusUnavailable: 'Служба настроек недоступна.',
|
|
121
|
+
saving: 'Сохранение…',
|
|
122
|
+
ready: 'Сохранить настройки',
|
|
123
|
+
saved: 'Настройки сохранены',
|
|
124
|
+
saveError: 'Ошибка сохранения: {msg}',
|
|
125
|
+
};
|
|
126
|
+
|
|
80
127
|
const DEFAULTS = {
|
|
81
128
|
mode: 'sanitize',
|
|
82
|
-
|
|
83
|
-
|
|
129
|
+
modelPattern: 'deepseek',
|
|
130
|
+
providers: '',
|
|
131
|
+
providerId: '',
|
|
132
|
+
modelId: '',
|
|
84
133
|
};
|
|
85
134
|
|
|
86
135
|
function PluginCard({ ctx: _ctx, t, bare }) {
|
|
@@ -115,8 +164,9 @@ window.__ModuleLoader__.load({
|
|
|
115
164
|
}
|
|
116
165
|
const scope = scopeRef.current;
|
|
117
166
|
|
|
118
|
-
const
|
|
119
|
-
const
|
|
167
|
+
const rawLang = ((typeof _ctx?.locale?.getLocale === 'function' ? _ctx.locale.getLocale() : null) || (typeof navigator !== 'undefined' && navigator.language) || 'en');
|
|
168
|
+
const currentLang = rawLang.startsWith('ru') ? 'ru' : (rawLang.startsWith('zh') ? 'zh' : 'en');
|
|
169
|
+
const dict = currentLang === 'ru' ? ru : (currentLang === 'zh' ? zh : en);
|
|
120
170
|
const tt = (k, params) => {
|
|
121
171
|
let str = (typeof t === 'function' ? t(k) : null) || dict[k] || en[k] || k;
|
|
122
172
|
if (params && typeof params === 'object') {
|
|
@@ -137,22 +187,19 @@ window.__ModuleLoader__.load({
|
|
|
137
187
|
const syncSnapshot = (snap) => {
|
|
138
188
|
if (!snap || cancelled) return;
|
|
139
189
|
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') {
|
|
190
|
+
if (typeof snap === 'object' && 'status' in snap && snap.status !== 'ready') {
|
|
191
|
+
setStatus(snap.status);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (typeof view === 'object' && view !== null) {
|
|
152
195
|
setDraft({
|
|
153
196
|
mode: view.mode || DEFAULTS.mode,
|
|
154
|
-
|
|
155
|
-
|
|
197
|
+
modelPattern: view.modelPattern !== undefined ? String(view.modelPattern) : DEFAULTS.modelPattern,
|
|
198
|
+
providers: Array.isArray(view.providers)
|
|
199
|
+
? view.providers.join(', ')
|
|
200
|
+
: (typeof view.providers === 'string' ? view.providers : ''),
|
|
201
|
+
providerId: view.providerId ? String(view.providerId) : '',
|
|
202
|
+
modelId: view.modelId ? String(view.modelId) : '',
|
|
156
203
|
});
|
|
157
204
|
setStatus('ready');
|
|
158
205
|
} else {
|
|
@@ -253,9 +300,16 @@ window.__ModuleLoader__.load({
|
|
|
253
300
|
setMsg('');
|
|
254
301
|
try {
|
|
255
302
|
const failures = [];
|
|
256
|
-
|
|
303
|
+
const entries = [
|
|
304
|
+
['mode', draft.mode || 'sanitize'],
|
|
305
|
+
['modelPattern', (draft.modelPattern !== undefined && draft.modelPattern !== null) ? String(draft.modelPattern).trim() : 'deepseek'],
|
|
306
|
+
['providers', draft.providers ? draft.providers.split(',').map(s => s.trim()).filter(Boolean) : []],
|
|
307
|
+
['providerId', draft.providerId ? String(draft.providerId).trim() : ''],
|
|
308
|
+
['modelId', draft.modelId ? String(draft.modelId).trim() : ''],
|
|
309
|
+
];
|
|
310
|
+
for (const [key, val] of entries) {
|
|
257
311
|
try {
|
|
258
|
-
await scope.set(key,
|
|
312
|
+
await scope.set(key, val);
|
|
259
313
|
} catch (err) {
|
|
260
314
|
failures.push(key + ': ' + (err.message || String(err)));
|
|
261
315
|
}
|
|
@@ -452,15 +506,16 @@ window.__ModuleLoader__.load({
|
|
|
452
506
|
)
|
|
453
507
|
),
|
|
454
508
|
|
|
455
|
-
//
|
|
509
|
+
// Model Pattern Field
|
|
456
510
|
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
|
|
457
511
|
React.createElement('label', {
|
|
458
512
|
style: { fontSize: 13, fontWeight: 500, color: 'var(--dsw-alias-label-primary)' },
|
|
459
|
-
}, tt('
|
|
513
|
+
}, tt('modelPattern')),
|
|
460
514
|
React.createElement('input', {
|
|
461
515
|
type: 'text',
|
|
462
|
-
value: draft.
|
|
463
|
-
|
|
516
|
+
value: draft.modelPattern,
|
|
517
|
+
placeholder: 'deepseek',
|
|
518
|
+
onChange: (e) => setDraft(d => ({ ...d, modelPattern: e.target.value })),
|
|
464
519
|
style: {
|
|
465
520
|
height: 34,
|
|
466
521
|
borderRadius: 6,
|
|
@@ -470,18 +525,22 @@ window.__ModuleLoader__.load({
|
|
|
470
525
|
padding: '0 10px',
|
|
471
526
|
fontSize: 13,
|
|
472
527
|
},
|
|
473
|
-
})
|
|
528
|
+
}),
|
|
529
|
+
React.createElement('span', {
|
|
530
|
+
style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary)', lineHeight: '1.4' },
|
|
531
|
+
}, tt('modelPatternHint'))
|
|
474
532
|
),
|
|
475
533
|
|
|
476
|
-
//
|
|
534
|
+
// Target Providers Field
|
|
477
535
|
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
|
|
478
536
|
React.createElement('label', {
|
|
479
537
|
style: { fontSize: 13, fontWeight: 500, color: 'var(--dsw-alias-label-primary)' },
|
|
480
|
-
}, tt('
|
|
538
|
+
}, tt('providers')),
|
|
481
539
|
React.createElement('input', {
|
|
482
540
|
type: 'text',
|
|
483
|
-
value: draft.
|
|
484
|
-
|
|
541
|
+
value: draft.providers,
|
|
542
|
+
placeholder: tt('providersPlaceholder'),
|
|
543
|
+
onChange: (e) => setDraft(d => ({ ...d, providers: e.target.value })),
|
|
485
544
|
style: {
|
|
486
545
|
height: 34,
|
|
487
546
|
borderRadius: 6,
|
|
@@ -491,7 +550,49 @@ window.__ModuleLoader__.load({
|
|
|
491
550
|
padding: '0 10px',
|
|
492
551
|
fontSize: 13,
|
|
493
552
|
},
|
|
494
|
-
})
|
|
553
|
+
}),
|
|
554
|
+
React.createElement('span', {
|
|
555
|
+
style: { fontSize: 11, color: 'var(--dsw-alias-label-tertiary)', lineHeight: '1.4' },
|
|
556
|
+
}, tt('providersHint'))
|
|
557
|
+
),
|
|
558
|
+
|
|
559
|
+
// Legacy Provider/Model notice if detected
|
|
560
|
+
(draft.providerId || draft.modelId) && React.createElement('div', {
|
|
561
|
+
style: {
|
|
562
|
+
display: 'flex',
|
|
563
|
+
alignItems: 'flex-start',
|
|
564
|
+
justifyContent: 'space-between',
|
|
565
|
+
gap: 8,
|
|
566
|
+
padding: '8px 10px',
|
|
567
|
+
borderRadius: 6,
|
|
568
|
+
background: 'color-mix(in srgb, var(--dsw-alias-state-warning-primary) 10%, transparent)',
|
|
569
|
+
border: '1px solid color-mix(in srgb, var(--dsw-alias-state-warning-primary) 25%, transparent)',
|
|
570
|
+
fontSize: 12,
|
|
571
|
+
color: 'var(--dsw-alias-label-secondary)',
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 2 } },
|
|
575
|
+
React.createElement('span', null, tt('legacyDetected')),
|
|
576
|
+
React.createElement('span', { style: { fontFamily: 'monospace', fontSize: 11 } },
|
|
577
|
+
(draft.providerId ? 'provider: ' + draft.providerId : '') +
|
|
578
|
+
(draft.providerId && draft.modelId ? ' | ' : '') +
|
|
579
|
+
(draft.modelId ? 'model: ' + draft.modelId : '')
|
|
580
|
+
)
|
|
581
|
+
),
|
|
582
|
+
React.createElement('button', {
|
|
583
|
+
type: 'button',
|
|
584
|
+
onClick: () => setDraft(d => ({ ...d, providerId: '', modelId: '' })),
|
|
585
|
+
style: {
|
|
586
|
+
background: 'transparent',
|
|
587
|
+
border: 'none',
|
|
588
|
+
color: 'var(--dsw-alias-brand-primary, var(--dsw-alias-label-primary))',
|
|
589
|
+
fontSize: 11,
|
|
590
|
+
cursor: 'pointer',
|
|
591
|
+
textDecoration: 'underline',
|
|
592
|
+
whiteSpace: 'nowrap',
|
|
593
|
+
padding: '2px 4px',
|
|
594
|
+
},
|
|
595
|
+
}, tt('legacyClear'))
|
|
495
596
|
),
|
|
496
597
|
|
|
497
598
|
// Actions
|
|
@@ -574,7 +675,7 @@ window.__ModuleLoader__.load({
|
|
|
574
675
|
if (typeof ctx.locale?.register === 'function') {
|
|
575
676
|
const registerLocale = () => {
|
|
576
677
|
try {
|
|
577
|
-
return ctx.locale.register(NS, { en, zh });
|
|
678
|
+
return ctx.locale.register(NS, { en, zh, ru });
|
|
578
679
|
} catch (err) {
|
|
579
680
|
/* Locale namespace already registered or unavailable */
|
|
580
681
|
}
|
package/lib/index.js
CHANGED
|
@@ -11,8 +11,10 @@ export const inject = ['llm', 'settings', 'webServer'];
|
|
|
11
11
|
|
|
12
12
|
const rawConfig = Schema.object({
|
|
13
13
|
mode: Schema.string().default('sanitize'),
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
modelPattern: Schema.string().default('deepseek'),
|
|
15
|
+
providers: Schema.array(Schema.string()).default([]),
|
|
16
|
+
providerId: Schema.string(),
|
|
17
|
+
modelId: Schema.string(),
|
|
16
18
|
});
|
|
17
19
|
|
|
18
20
|
export const Config = typeof rawConfig.volatile === 'function'
|
|
@@ -20,9 +22,37 @@ export const Config = typeof rawConfig.volatile === 'function'
|
|
|
20
22
|
: rawConfig.extra('volatile', true);
|
|
21
23
|
|
|
22
24
|
export function matchesScope(o, c) {
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
25
|
+
if (!c || c.mode === 'disabled') return false;
|
|
26
|
+
const provider = String(o?.providerId ?? o?.provider?.id ?? o?.provider ?? '').trim();
|
|
27
|
+
const model = String(o?.modelId ?? o?.model?.id ?? o?.model?.name ?? o?.model ?? '').trim();
|
|
28
|
+
if (!model && !provider) return false;
|
|
29
|
+
|
|
30
|
+
// 1. Backward compatibility: if legacy exact providerId/modelId were specified and match
|
|
31
|
+
if (c.providerId && c.modelId && provider === c.providerId && model === c.modelId) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Provider check: if c.providers is non-empty, provider must be included
|
|
36
|
+
const providersList = Array.isArray(c.providers)
|
|
37
|
+
? c.providers.map(p => String(p).trim()).filter(Boolean)
|
|
38
|
+
: (typeof c.providers === 'string' && c.providers.trim() ? c.providers.split(',').map(p => p.trim()).filter(Boolean) : []);
|
|
39
|
+
|
|
40
|
+
if (providersList.length > 0) {
|
|
41
|
+
if (!providersList.includes(provider)) return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 3. Model pattern check (case-insensitive regex)
|
|
45
|
+
const pattern = (c.modelPattern !== undefined && c.modelPattern !== null && String(c.modelPattern).trim() !== '')
|
|
46
|
+
? String(c.modelPattern).trim()
|
|
47
|
+
: 'deepseek';
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const rx = new RegExp(pattern, 'i');
|
|
51
|
+
return rx.test(model);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
/* Fallback to substring matching if regex syntax is invalid */
|
|
54
|
+
return model.toLowerCase().includes(pattern.toLowerCase());
|
|
55
|
+
}
|
|
26
56
|
}
|
|
27
57
|
|
|
28
58
|
export async function* sanitizeDsmlArtifacts(s, { mode = 'sanitize', onArtifact } = {}) {
|