dsh-plugin-effort-declare 0.1.0 → 0.1.1
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/CONTRIBUTING.en.md +3 -1
- package/CONTRIBUTING.md +3 -1
- package/INSTALL.en.md +35 -5
- package/INSTALL.md +35 -5
- package/README.en.md +17 -3
- package/README.md +17 -3
- package/lib/client.js +105 -68
- package/lib/client.js.map +1 -1
- package/lib/types/client/EffortDeclareSection.d.ts +1 -2
- package/lib/types/client/EffortDeclareSection.d.ts.map +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/load-drafts.d.ts +3 -0
- package/lib/types/client/load-drafts.d.ts.map +1 -1
- package/lib/types/client/schema-ops.d.ts +10 -0
- package/lib/types/client/schema-ops.d.ts.map +1 -1
- package/lib/types/core/catalog.d.ts +4 -4
- package/package.json +1 -1
- package/src/client/EffortDeclareSection.tsx +66 -32
- package/src/client/README.en.md +3 -3
- package/src/client/README.md +3 -3
- package/src/client/index.ts +7 -3
- package/src/client/load-drafts.ts +7 -5
- package/src/client/locales.ts +2 -2
- package/src/client/schema-ops.ts +33 -0
- package/src/core/README.en.md +1 -1
- package/src/core/README.md +1 -1
- package/src/core/catalog.ts +4 -4
|
@@ -31,7 +31,7 @@ import { buildSaveOps } from '../core/path-ops.ts'
|
|
|
31
31
|
import { cloneModels, cloneObject } from '../core/paths.ts'
|
|
32
32
|
import { modelEffortError } from '../core/validate.ts'
|
|
33
33
|
import { loadDrafts } from './load-drafts.ts'
|
|
34
|
-
import type
|
|
34
|
+
import { validateSaveDraft, type SchemaOps } from './schema-ops.ts'
|
|
35
35
|
import type { EffortDeclareKey } from './locales.ts'
|
|
36
36
|
import css from './effort-declare.module.css'
|
|
37
37
|
|
|
@@ -41,7 +41,6 @@ export interface EffortDeclareSectionInjected {
|
|
|
41
41
|
api: Pick<IApiClient, 'settings' | 'llm'>
|
|
42
42
|
describe: SettingsDescribeFace
|
|
43
43
|
schema: SchemaOps
|
|
44
|
-
t: (key: EffortDeclareKey) => string
|
|
45
44
|
subscribeInvalidate: (listener: (source: InvalidationSource) => void) => () => void
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -341,7 +340,7 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
|
|
|
341
340
|
void loadDrafts(api, describe, schema).then((result) => {
|
|
342
341
|
if (!generationIsCurrent(generationRef, generation)) return
|
|
343
342
|
setWritable(result.writable)
|
|
344
|
-
|
|
343
|
+
setFormats(result.formats)
|
|
345
344
|
if (result.error !== undefined) {
|
|
346
345
|
setStatus('error')
|
|
347
346
|
setError(result.error)
|
|
@@ -350,9 +349,13 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
|
|
|
350
349
|
const merged = mergeLoadedDrafts(draftsRef.current, result.drafts, { preserveDirty })
|
|
351
350
|
setDrafts(merged.drafts)
|
|
352
351
|
if (merged.conflicted.length > 0) {
|
|
353
|
-
setNotices(
|
|
354
|
-
|
|
355
|
-
|
|
352
|
+
setNotices(current => {
|
|
353
|
+
const next = { ...current }
|
|
354
|
+
for (const provider of merged.conflicted) {
|
|
355
|
+
next[provider] = { kind: 'conflict', text: t('dirtyConflict') }
|
|
356
|
+
}
|
|
357
|
+
return next
|
|
358
|
+
})
|
|
356
359
|
}
|
|
357
360
|
setStatus('ready')
|
|
358
361
|
}, (failure: unknown) => {
|
|
@@ -366,21 +369,32 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
|
|
|
366
369
|
|
|
367
370
|
useEffect(() => {
|
|
368
371
|
if (props.subscribeInvalidate === undefined) return undefined
|
|
369
|
-
return props.subscribeInvalidate(() => {
|
|
372
|
+
return props.subscribeInvalidate((source) => {
|
|
373
|
+
if (source === 'settings' || source === 'directory') reload(true)
|
|
374
|
+
})
|
|
370
375
|
}, [props.subscribeInvalidate, reload])
|
|
371
376
|
|
|
377
|
+
const patchNotice = (provider: string, notice: CardNotice | undefined): void => {
|
|
378
|
+
setNotices(current => {
|
|
379
|
+
const copy = { ...current }
|
|
380
|
+
if (notice === undefined) delete copy[provider]
|
|
381
|
+
else copy[provider] = notice
|
|
382
|
+
return copy
|
|
383
|
+
})
|
|
384
|
+
}
|
|
385
|
+
|
|
372
386
|
const save = async (draft: RouteDraft): Promise<void> => {
|
|
373
|
-
if (api === undefined || describe === undefined) return
|
|
387
|
+
if (api === undefined || describe === undefined || schema === undefined) return
|
|
374
388
|
if (status === 'loading' || busyRoute !== null) return
|
|
375
389
|
const blocking = draft.models
|
|
376
390
|
.map(row => errorText(modelEffortError(row), t))
|
|
377
391
|
.find(text => text !== undefined)
|
|
378
392
|
if (blocking !== undefined) {
|
|
379
|
-
|
|
393
|
+
patchNotice(draft.provider, { kind: 'error', text: blocking })
|
|
380
394
|
return
|
|
381
395
|
}
|
|
382
396
|
setBusyRoute(draft.provider)
|
|
383
|
-
|
|
397
|
+
patchNotice(draft.provider, undefined)
|
|
384
398
|
try {
|
|
385
399
|
const ops = buildSaveOps({
|
|
386
400
|
settingsPath: draft.settingsPath,
|
|
@@ -393,18 +407,44 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
|
|
|
393
407
|
setDrafts(current => current.map(row => row.provider === draft.provider ? alignDraft(row) : row))
|
|
394
408
|
return
|
|
395
409
|
}
|
|
410
|
+
const willWriteCompat = ops.some(op => (
|
|
411
|
+
op.path.length > draft.settingsPath.length && op.path[draft.settingsPath.length] === 'compat'
|
|
412
|
+
))
|
|
413
|
+
const pi = describe.getSnapshot().view?.namespaces.find(view => view.ns === LLM_PI_AI_NS)
|
|
414
|
+
if (pi !== undefined) {
|
|
415
|
+
let root: unknown
|
|
416
|
+
try {
|
|
417
|
+
root = schema.rehydrate(pi.schema)
|
|
418
|
+
} catch {
|
|
419
|
+
root = undefined
|
|
420
|
+
}
|
|
421
|
+
if (root !== undefined) {
|
|
422
|
+
const schemaError = validateSaveDraft(
|
|
423
|
+
schema,
|
|
424
|
+
root,
|
|
425
|
+
draft.settingsPath,
|
|
426
|
+
draft.models,
|
|
427
|
+
draft.compat,
|
|
428
|
+
willWriteCompat,
|
|
429
|
+
)
|
|
430
|
+
if (schemaError !== undefined) {
|
|
431
|
+
patchNotice(draft.provider, { kind: 'error', text: schemaError })
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
396
436
|
const response = await api.settings.mutate({
|
|
397
437
|
ns: LLM_PI_AI_NS,
|
|
398
438
|
ops,
|
|
399
439
|
expectedRevision: draft.revision,
|
|
400
440
|
})
|
|
401
441
|
if (!response.result.ok) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
},
|
|
442
|
+
const conflict = response.result.error.code === 'settings-conflict'
|
|
443
|
+
patchNotice(draft.provider, {
|
|
444
|
+
kind: conflict ? 'conflict' : 'error',
|
|
445
|
+
text: conflict ? t('conflict') : response.result.error.message,
|
|
407
446
|
})
|
|
447
|
+
if (conflict) reload(true)
|
|
408
448
|
return
|
|
409
449
|
}
|
|
410
450
|
const view = response.result.value
|
|
@@ -413,13 +453,11 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
|
|
|
413
453
|
user: view.user ?? {},
|
|
414
454
|
revision: view.revision,
|
|
415
455
|
}))
|
|
416
|
-
|
|
456
|
+
patchNotice(draft.provider, { kind: 'saved', text: t('saved') })
|
|
417
457
|
} catch (failure) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
text: failure instanceof Error ? failure.message : t('loadError'),
|
|
422
|
-
},
|
|
458
|
+
patchNotice(draft.provider, {
|
|
459
|
+
kind: 'error',
|
|
460
|
+
text: failure instanceof Error ? failure.message : t('loadError'),
|
|
423
461
|
})
|
|
424
462
|
} finally {
|
|
425
463
|
setBusyRoute(null)
|
|
@@ -429,6 +467,10 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
|
|
|
429
467
|
const showLoading = status === 'loading' && drafts.length === 0
|
|
430
468
|
const showEmpty = status === 'ready' && drafts.length === 0
|
|
431
469
|
const showList = drafts.length > 0
|
|
470
|
+
const hasCardFailure = Object.values(notices).some(
|
|
471
|
+
notice => notice.kind === 'conflict' || notice.kind === 'error',
|
|
472
|
+
)
|
|
473
|
+
const showReload = status === 'error' || hasCardFailure || showEmpty || !writable || status === 'loading' || showList
|
|
432
474
|
|
|
433
475
|
return (
|
|
434
476
|
<div className={css.section}>
|
|
@@ -437,7 +479,7 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
|
|
|
437
479
|
{!writable && status === 'ready' ? <p className={css.notice}>{t('readOnly')}</p> : null}
|
|
438
480
|
{showLoading ? <p className={css.intro}>{t('loading')}</p> : null}
|
|
439
481
|
{status === 'error' ? <p className={css.error}>{error}</p> : null}
|
|
440
|
-
{
|
|
482
|
+
{showReload
|
|
441
483
|
? (
|
|
442
484
|
<button type="button" className={css.secondaryButton} onClick={() => { reload(true) }}>{t('reload')}</button>
|
|
443
485
|
)
|
|
@@ -464,20 +506,12 @@ export function EffortDeclareSection(props: EffortDeclareSectionProps): ReactNod
|
|
|
464
506
|
notice={notices[draft.provider]}
|
|
465
507
|
t={t}
|
|
466
508
|
onChange={(next) => {
|
|
467
|
-
|
|
468
|
-
const copy = { ...current }
|
|
469
|
-
delete copy[next.provider]
|
|
470
|
-
return copy
|
|
471
|
-
})
|
|
509
|
+
patchNotice(next.provider, undefined)
|
|
472
510
|
setDrafts(current => current.map(row => row.provider === next.provider ? next : row))
|
|
473
511
|
}}
|
|
474
512
|
onSave={(next) => { void save(next) }}
|
|
475
513
|
onCancel={(next) => {
|
|
476
|
-
|
|
477
|
-
const copy = { ...current }
|
|
478
|
-
delete copy[next.provider]
|
|
479
|
-
return copy
|
|
480
|
-
})
|
|
514
|
+
patchNotice(next.provider, undefined)
|
|
481
515
|
setDrafts(current => current.map(row => row.provider === next.provider
|
|
482
516
|
? {
|
|
483
517
|
...row,
|
package/src/client/README.en.md
CHANGED
|
@@ -17,11 +17,11 @@ Cross-plugin work uses Cordis services only (`connection`, `settingsScope`, `set
|
|
|
17
17
|
|
|
18
18
|
| File | Role |
|
|
19
19
|
| --- | --- |
|
|
20
|
-
| [`index.ts`](./index.ts) | Registers zh/en copy, CSS (`ctx.effect` insert/remove), and `settings.section` (id `effort-declare`, order
|
|
20
|
+
| [`index.ts`](./index.ts) | Registers zh/en copy, CSS (`ctx.effect` insert/remove), and `settings.section` (id `effort-declare`, order 12). Subscribes in `apply` to the mirror `describe.subscribe`, `settings/document-updated` (`llm-pi-ai` only), `llm/adapters-updated`, and `connection/reset`. `locale: NS` lets the framework inject `t`; `inject` only returns `api` / `describe` / `schema` / `subscribeInvalidate`. |
|
|
21
21
|
| [`EffortDeclareSection.tsx`](./EffortDeclareSection.tsx) | Route cards, presets, Off tri-state, advanced protocol switches, save and cancel. |
|
|
22
|
-
| [`load-drafts.ts`](./load-drafts.ts) | Builds drafts from `llm.providers` + the settings mirror; drafts come from `user`, protocol classification may use `value`. |
|
|
22
|
+
| [`load-drafts.ts`](./load-drafts.ts) | Builds drafts from `llm.providers` + the settings mirror; drafts come from `user`, protocol classification may use `value`. `thinkingFormat` choices come only from the live schema union. |
|
|
23
23
|
| [`locales.ts`](./locales.ts) | Copy namespace `plugin-effort-declare`. |
|
|
24
24
|
| [`effort-declare.module.css`](./effort-declare.module.css) | `--dsw-alias-*` tokens only, so dark theme stays correct. |
|
|
25
|
-
| [`schema-ops.ts`](./schema-ops.ts) | Binds `settingsSchema` as plain callbacks so the service identity is not passed into React. |
|
|
25
|
+
| [`schema-ops.ts`](./schema-ops.ts) | Binds `settingsSchema` as plain callbacks (including `validate`) so the service identity is not passed into React. |
|
|
26
26
|
|
|
27
27
|
Visual language follows the official Models page, not `--ds-*` variables with light-mode fallbacks.
|
package/src/client/README.md
CHANGED
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
|
|
18
18
|
| 文件 | 说明 |
|
|
19
19
|
| --- | --- |
|
|
20
|
-
| [`index.ts`](./index.ts) | 注册 zh/en 文案、CSS(`ctx.effect` 插入/移除)与 `settings.section`(id `effort-declare`,order
|
|
20
|
+
| [`index.ts`](./index.ts) | 注册 zh/en 文案、CSS(`ctx.effect` 插入/移除)与 `settings.section`(id `effort-declare`,order 12)。在 `apply` 里订阅镜像 `describe.subscribe`、`settings/document-updated`(仅 `llm-pi-ai`)、`llm/adapters-updated`、`connection/reset`。`locale: NS` 由框架注入 `t`,inject 只传 `api` / `describe` / `schema` / `subscribeInvalidate`。 |
|
|
21
21
|
| [`EffortDeclareSection.tsx`](./EffortDeclareSection.tsx) | 路由卡片、预设、Off 三态、高级协议开关、保存与取消。 |
|
|
22
|
-
| [`load-drafts.ts`](./load-drafts.ts) | 从 `llm.providers` + 镜像组装草稿;草稿取自 `user`,协议分类可用 `value
|
|
22
|
+
| [`load-drafts.ts`](./load-drafts.ts) | 从 `llm.providers` + 镜像组装草稿;草稿取自 `user`,协议分类可用 `value`。`thinkingFormat` 可选项只来自现场 schema union。 |
|
|
23
23
|
| [`locales.ts`](./locales.ts) | 文案 namespace `plugin-effort-declare`。 |
|
|
24
24
|
| [`effort-declare.module.css`](./effort-declare.module.css) | 仅使用 `--dsw-alias-*` 设计令牌,保证暗色主题正确。 |
|
|
25
|
-
| [`schema-ops.ts`](./schema-ops.ts) | 将 `settingsSchema`
|
|
25
|
+
| [`schema-ops.ts`](./schema-ops.ts) | 将 `settingsSchema` 收成普通回调(含 `validate`),避免把服务身份带进 React。 |
|
|
26
26
|
|
|
27
27
|
样式与交互对齐官方模型页,而不是带浅色 fallback 的 `--ds-*` 变量。
|
package/src/client/index.ts
CHANGED
|
@@ -53,8 +53,12 @@ export function apply(ctx: ClientContext): void {
|
|
|
53
53
|
),
|
|
54
54
|
getPath: (value, path) => settingsSchema.getPath(value, path),
|
|
55
55
|
hasPath: (value, path) => settingsSchema.hasPath(value, path),
|
|
56
|
+
validate: (node, draft) => settingsSchema.validate(
|
|
57
|
+
node as Parameters<typeof settingsSchema.validate>[0],
|
|
58
|
+
draft,
|
|
59
|
+
),
|
|
56
60
|
})
|
|
57
|
-
const t = ctx.locale.bind(NS)
|
|
61
|
+
const t = ctx.locale.bind(NS)
|
|
58
62
|
const describe = ctx.settingsScope.describe()
|
|
59
63
|
const invalidation = new Set<(source: InvalidationSource) => void>()
|
|
60
64
|
|
|
@@ -63,6 +67,7 @@ export function apply(ctx: ClientContext): void {
|
|
|
63
67
|
for (const listener of invalidation) listener(source)
|
|
64
68
|
}
|
|
65
69
|
const disposers = [
|
|
70
|
+
describe.subscribe(() => { emit('settings') }),
|
|
66
71
|
ctx.remote.$on('settings/document-updated', (ns: string) => {
|
|
67
72
|
if (ns !== LLM_PI_AI_NS) return
|
|
68
73
|
emit('settings')
|
|
@@ -83,14 +88,13 @@ export function apply(ctx: ClientContext): void {
|
|
|
83
88
|
ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
84
89
|
name: 'settings.section',
|
|
85
90
|
id: 'effort-declare',
|
|
86
|
-
order:
|
|
91
|
+
order: 12,
|
|
87
92
|
label: () => t('nav'),
|
|
88
93
|
locale: NS,
|
|
89
94
|
inject: (): EffortDeclareSectionInjected => ({
|
|
90
95
|
api: connection.api,
|
|
91
96
|
describe,
|
|
92
97
|
schema,
|
|
93
|
-
t,
|
|
94
98
|
subscribeInvalidate,
|
|
95
99
|
}),
|
|
96
100
|
}, EffortDeclareSection))
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
import type { IApiClient, SettingsNamespaceView } from '@deepseek-ai/dsh-api-remotes/client'
|
|
6
6
|
import type { SettingsDescribeFace } from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
7
7
|
import {
|
|
8
|
-
FALLBACK_THINKING_FORMATS,
|
|
9
8
|
LLM_PI_AI_NS,
|
|
10
9
|
SCHEMA_PROBE_ROUTE,
|
|
11
10
|
} from '../core/catalog.ts'
|
|
@@ -29,6 +28,9 @@ export interface LoadDraftsResult {
|
|
|
29
28
|
/**
|
|
30
29
|
* First paint: `ensure()` (reads only from idle). Never treat ensure as refresh.
|
|
31
30
|
* Callers that must not apply a stale settlement compare generation themselves.
|
|
31
|
+
*
|
|
32
|
+
* `formats` is the live schema union only. Empty means the dropdown has no
|
|
33
|
+
* writable choices (stored values stay visible via `thinkingFormatChoices`).
|
|
32
34
|
*/
|
|
33
35
|
export async function loadDrafts(
|
|
34
36
|
api: Pick<IApiClient, 'llm'>,
|
|
@@ -38,20 +40,20 @@ export async function loadDrafts(
|
|
|
38
40
|
await describe.ensure()
|
|
39
41
|
const mirrored = describe.getSnapshot()
|
|
40
42
|
if (mirrored.view === undefined) {
|
|
41
|
-
return { writable: false, formats: [
|
|
43
|
+
return { writable: false, formats: [], drafts: [], error: mirrored.error ?? undefined }
|
|
42
44
|
}
|
|
43
45
|
const providersResponse = await api.llm.providers({})
|
|
44
46
|
if (!providersResponse.result.ok) {
|
|
45
47
|
return {
|
|
46
48
|
writable: mirrored.view.writable,
|
|
47
|
-
formats: [
|
|
49
|
+
formats: [],
|
|
48
50
|
drafts: [],
|
|
49
51
|
error: providersResponse.result.error.message,
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
const namespaces = new Map(mirrored.view.namespaces.map((view: SettingsNamespaceView) => [view.ns, view]))
|
|
53
55
|
const pi = namespaces.get(LLM_PI_AI_NS)
|
|
54
|
-
let formats: string[] = [
|
|
56
|
+
let formats: string[] = []
|
|
55
57
|
let schemaDefaultApi: string | undefined
|
|
56
58
|
if (pi !== undefined) {
|
|
57
59
|
try {
|
|
@@ -61,7 +63,7 @@ export async function loadDrafts(
|
|
|
61
63
|
if (fromSchema.length > 0) formats = fromSchema
|
|
62
64
|
schemaDefaultApi = schemaDefaultString(schema.nodeAtPath(root, ['providers', SCHEMA_PROBE_ROUTE, 'api']))
|
|
63
65
|
} catch {
|
|
64
|
-
// Live schema walk is best-effort;
|
|
66
|
+
// Live schema walk is best-effort; UI options stay empty rather than a handwritten list.
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
const drafts: RouteDraft[] = []
|
package/src/client/locales.ts
CHANGED
|
@@ -77,7 +77,7 @@ export const zh: Record<EffortDeclareKey, string> = {
|
|
|
77
77
|
clear: '清除本模型声明',
|
|
78
78
|
advanced: '高级:协议方言',
|
|
79
79
|
thinkingFormat: 'thinkingFormat',
|
|
80
|
-
thinkingFormatDefault: '
|
|
80
|
+
thinkingFormatDefault: '默认(省略该键)',
|
|
81
81
|
supportsDeveloperRole: '系统提示走 system 而不是 developer(supportsDeveloperRole: false)',
|
|
82
82
|
supportsReasoningEffort: '不发 reasoning_effort,只发开关(supportsReasoningEffort: false)',
|
|
83
83
|
developerTrueHint: '当前文档是 supportsDeveloperRole: true。v1 只能强制 false 或缺席;勾选会写成 false,取消勾选会删除该键。',
|
|
@@ -120,7 +120,7 @@ export const en: Record<EffortDeclareKey, string> = {
|
|
|
120
120
|
clear: 'Clear this model’s declaration',
|
|
121
121
|
advanced: 'Advanced: protocol dialect',
|
|
122
122
|
thinkingFormat: 'thinkingFormat',
|
|
123
|
-
thinkingFormatDefault: 'Default (
|
|
123
|
+
thinkingFormatDefault: 'Default (omit the key)',
|
|
124
124
|
supportsDeveloperRole: 'Send system prompts as system, not developer (supportsDeveloperRole: false)',
|
|
125
125
|
supportsReasoningEffort: 'Do not send reasoning_effort; switch only (supportsReasoningEffort: false)',
|
|
126
126
|
developerTrueHint: 'The document has supportsDeveloperRole: true. v1 can only force false or omit the key; checking writes false, unchecking deletes the key.',
|
package/src/client/schema-ops.ts
CHANGED
|
@@ -7,6 +7,11 @@ export interface SchemaOps {
|
|
|
7
7
|
nodeAtPath: (root: unknown, path: readonly string[]) => unknown
|
|
8
8
|
getPath: (value: unknown, path: readonly string[]) => unknown
|
|
9
9
|
hasPath: (value: unknown, path: readonly string[]) => boolean
|
|
10
|
+
/**
|
|
11
|
+
* Official `settingsSchema.validate`: failure text, or `undefined` when valid.
|
|
12
|
+
* Missing nodes are skipped by the caller — do not invent a second validator.
|
|
13
|
+
*/
|
|
14
|
+
validate: (node: unknown, draft: unknown) => string | undefined
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
/** Wrap a live settingsSchema service as plain callbacks. */
|
|
@@ -16,5 +21,33 @@ export function bindSchema(service: SchemaOps): SchemaOps {
|
|
|
16
21
|
nodeAtPath: (root, path) => service.nodeAtPath(root, path),
|
|
17
22
|
getPath: (value, path) => service.getPath(value, path),
|
|
18
23
|
hasPath: (value, path) => service.hasPath(value, path),
|
|
24
|
+
validate: (node, draft) => service.validate(node, draft),
|
|
19
25
|
}
|
|
20
26
|
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Pre-mutate schema check used by the settings page.
|
|
30
|
+
* A returned string means do not call `settings.mutate`.
|
|
31
|
+
*/
|
|
32
|
+
export function validateSaveDraft(
|
|
33
|
+
schema: SchemaOps,
|
|
34
|
+
root: unknown,
|
|
35
|
+
settingsPath: readonly string[],
|
|
36
|
+
afterModels: unknown,
|
|
37
|
+
afterCompat: unknown,
|
|
38
|
+
willWriteCompat: boolean,
|
|
39
|
+
): string | undefined {
|
|
40
|
+
const modelsNode = schema.nodeAtPath(root, [...settingsPath, 'models'])
|
|
41
|
+
if (modelsNode !== undefined) {
|
|
42
|
+
const error = schema.validate(modelsNode, afterModels)
|
|
43
|
+
if (error !== undefined) return error
|
|
44
|
+
}
|
|
45
|
+
if (willWriteCompat) {
|
|
46
|
+
const compatNode = schema.nodeAtPath(root, [...settingsPath, 'compat'])
|
|
47
|
+
if (compatNode !== undefined) {
|
|
48
|
+
const error = schema.validate(compatNode, afterCompat)
|
|
49
|
+
if (error !== undefined) return error
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return undefined
|
|
53
|
+
}
|
package/src/core/README.en.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
Domain logic for the plugin: no React, no Cordis service instances. The settings page and Vitest both depend on these pure functions, so effort semantics can regress without booting DSH.
|
|
11
11
|
|
|
12
|
-
Canonical level names and `thinkingFormat` values follow `@deepseek-ai/dsh-llm-pi-ai` (0.1.0-rc.8). A
|
|
12
|
+
Canonical level names and `thinkingFormat` values follow `@deepseek-ai/dsh-llm-pi-ai` (0.1.0-rc.8). A whitelist is pinned by tests; UI choices come only from the live schema union.
|
|
13
13
|
|
|
14
14
|
## What's in this directory
|
|
15
15
|
|
package/src/core/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
本插件的领域逻辑:无 React、无 Cordis 服务实例。设置页与 Vitest 都只依赖这些纯函数,因此档位语义可以在不启动 DSH 的情况下回归。
|
|
11
11
|
|
|
12
|
-
规范键名与 `thinkingFormat` 取值对齐 `@deepseek-ai/dsh-llm-pi-ai`(0.1.0-rc.8
|
|
12
|
+
规范键名与 `thinkingFormat` 取值对齐 `@deepseek-ai/dsh-llm-pi-ai`(0.1.0-rc.8)。仓库内有测试钉白名单;UI 可选项只来自现场 schema union。
|
|
13
13
|
|
|
14
14
|
## 这个目录里有什么
|
|
15
15
|
|
package/src/core/catalog.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Levels match `@deepseek-ai/dsh-llm-pi-ai` catalog.ts `THINKING_LEVELS`.
|
|
5
5
|
* Formats match `SUPPORTED_THINKING_FORMATS` in the same file (rc.8).
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Tests pin these lists against llm-pi-ai. The settings page never offers
|
|
7
|
+
* the handwritten thinkingFormat list as writable choices — only the live
|
|
8
|
+
* schema union, plus a stored value that the union omitted.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
/** Selectable reasoning levels, in pi-ai escalation order. */
|
|
@@ -28,7 +28,7 @@ export const THINKING_LEVELS_WITHOUT_OFF = THINKING_LEVELS.filter(
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* openai-completions thinkingFormat values from llm-pi-ai catalog.ts rc.8.
|
|
31
|
-
*
|
|
31
|
+
* Test pin only — not a writable UI fallback.
|
|
32
32
|
*/
|
|
33
33
|
export const FALLBACK_THINKING_FORMATS = [
|
|
34
34
|
'openai',
|