@fonlan/dsh-model-parameters 0.1.5 → 0.1.7
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.en.md +26 -22
- package/README.md +20 -19
- package/lib/client.js +2 -1
- package/lib/client.js.map +1 -1
- package/lib/index.js +92 -39
- package/lib/types/index.d.ts +4 -4
- package/lib/types/server/service.d.ts +27 -11
- package/lib/types/shared/config.d.ts +90 -8
- package/package.json +16 -16
- package/src/client/settings-section.tsx +2 -1
- package/src/index.ts +5 -5
- package/src/server/service.ts +58 -32
- package/src/shared/config.ts +140 -17
package/README.en.md
CHANGED
|
@@ -14,18 +14,21 @@ could not match.
|
|
|
14
14
|
|
|
15
15
|
## How it works
|
|
16
16
|
|
|
17
|
-
1. The plugin listens to the `llm-pi-ai` settings namespace
|
|
18
|
-
Any committed change — a freshly synced
|
|
19
|
-
manual
|
|
17
|
+
1. The plugin listens to the `llm-pi-ai` settings namespace
|
|
18
|
+
(`settings/document-updated`). Any committed change — a freshly synced
|
|
19
|
+
provider, an edited provider card, a manual profile-patch edit — triggers a
|
|
20
|
+
reconcile.
|
|
20
21
|
2. On plugin start it also runs one backfill over the existing configuration.
|
|
21
22
|
3. For every model entry missing any of `name` / `contextWindow` / `maxTokens` /
|
|
22
23
|
`reasoningEfforts` / `input`, it resolves the best catalog entry and fills
|
|
23
24
|
only the missing fields (fill-only: present values are never overwritten).
|
|
24
25
|
4. The write goes through the settings seam (`ctx.settings.mutate`), i.e. it
|
|
25
|
-
lands in
|
|
26
|
+
lands in the active profile's patch file
|
|
27
|
+
(`~/.dsh/profiles/<profile>/cordis.patch.yml`) — the same file you would
|
|
28
|
+
edit by hand.
|
|
26
29
|
|
|
27
30
|
The reconcile is fill-only by construction, so its own writes cannot loop: the
|
|
28
|
-
next `settings/updated` sees the fields present and produces no ops.
|
|
31
|
+
next `settings/document-updated` sees the fields present and produces no ops.
|
|
29
32
|
|
|
30
33
|
## Matching
|
|
31
34
|
|
|
@@ -87,25 +90,26 @@ cards, collapsed by default):
|
|
|
87
90
|
- the last fill report: fields filled, providers touched, and the list of
|
|
88
91
|
unmatched model ids.
|
|
89
92
|
|
|
90
|
-
Plugin configuration
|
|
91
|
-
|
|
93
|
+
Plugin configuration IS the plugin's own loader entry config: the
|
|
94
|
+
`model-parameters` entry in the active profile's patch file, e.g.:
|
|
92
95
|
|
|
93
96
|
```yaml
|
|
94
|
-
model-parameters
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
97
|
+
- id: model-parameters
|
|
98
|
+
config:
|
|
99
|
+
enabled: true
|
|
100
|
+
ttlDays: 7
|
|
101
|
+
fillName: true
|
|
102
|
+
fillContext: true
|
|
103
|
+
fillMaxTokens: true
|
|
104
|
+
fillReasoning: true
|
|
105
|
+
fillInput: true
|
|
106
|
+
providerMap: {}
|
|
107
|
+
officialProviders:
|
|
108
|
+
- deepseek
|
|
109
|
+
- openai
|
|
110
|
+
- anthropic
|
|
111
|
+
- google
|
|
112
|
+
- xai
|
|
109
113
|
```
|
|
110
114
|
|
|
111
115
|
## Installation
|
package/README.md
CHANGED
|
@@ -8,12 +8,12 @@ DSH 插件:当 provider 的模型同步(或任何设置写入)留下缺失
|
|
|
8
8
|
|
|
9
9
|
## 工作原理
|
|
10
10
|
|
|
11
|
-
1. 插件监听 `llm-pi-ai` 设置命名空间(`settings/updated`)。任何已提交的变更——新同步的 provider、编辑过的 provider 卡片、手动编辑的
|
|
11
|
+
1. 插件监听 `llm-pi-ai` 设置命名空间(`settings/document-updated`)。任何已提交的变更——新同步的 provider、编辑过的 provider 卡片、手动编辑的 profile 补丁——都会触发一次 reconcile。
|
|
12
12
|
2. 插件启动时还会对现有配置执行一次全量回填。
|
|
13
13
|
3. 对每个缺少 `name` / `contextWindow` / `maxTokens` / `reasoningEfforts` / `input` 任一字段的模型条目,解析出最佳目录条目,只补全缺失字段(fill-only:已有值绝不覆盖)。
|
|
14
|
-
4. 写入走设置接缝(`ctx.settings.mutate
|
|
14
|
+
4. 写入走设置接缝(`ctx.settings.mutate`),即落到当前 profile 的补丁文件(`~/.dsh/profiles/<profile>/cordis.patch.yml`)——与你手动编辑的是同一个文件。
|
|
15
15
|
|
|
16
|
-
reconcile 天然是 fill-only 的,因此它自己的写入不会造成循环:下一次 `settings/updated` 看到字段已存在,不会产生任何操作。
|
|
16
|
+
reconcile 天然是 fill-only 的,因此它自己的写入不会造成循环:下一次 `settings/document-updated` 看到字段已存在,不会产生任何操作。
|
|
17
17
|
|
|
18
18
|
## 匹配逻辑
|
|
19
19
|
|
|
@@ -58,24 +58,25 @@ models.dev 把同一模型挂在许多 provider 名下(`deepseek-v4-flash` 出
|
|
|
58
58
|
- 目录新鲜度(条目数、provider 数、上次更新);
|
|
59
59
|
- 上次补全报告:补全的字段、涉及的 provider、未匹配的模型 id 列表。
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
插件配置就是它自己的 loader entry config——当前 profile 补丁文件里的 `model-parameters` 条目,例如:
|
|
62
62
|
|
|
63
63
|
```yaml
|
|
64
|
-
model-parameters
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
64
|
+
- id: model-parameters
|
|
65
|
+
config:
|
|
66
|
+
enabled: true
|
|
67
|
+
ttlDays: 7
|
|
68
|
+
fillName: true
|
|
69
|
+
fillContext: true
|
|
70
|
+
fillMaxTokens: true
|
|
71
|
+
fillReasoning: true
|
|
72
|
+
fillInput: true
|
|
73
|
+
providerMap: {}
|
|
74
|
+
officialProviders:
|
|
75
|
+
- deepseek
|
|
76
|
+
- openai
|
|
77
|
+
- anthropic
|
|
78
|
+
- google
|
|
79
|
+
- xai
|
|
79
80
|
```
|
|
80
81
|
|
|
81
82
|
## 安装
|
package/lib/client.js
CHANGED
|
@@ -153,7 +153,8 @@ window.__ModuleLoader__.load({
|
|
|
153
153
|
* and the last fill report (filled / touched / unmatched).
|
|
154
154
|
*
|
|
155
155
|
* All reads/mutations go through the plugin's fenced API; config persists into
|
|
156
|
-
* the `model-parameters`
|
|
156
|
+
* the `model-parameters` profile entry — the plugin's own loader entry config,
|
|
157
|
+
* written through the host settings service.
|
|
157
158
|
*/
|
|
158
159
|
function useLocaleRevision(ctx) {
|
|
159
160
|
const subscribe = (0, react.useCallback)((onChange) => {
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["useCallback","useSyncExternalStore","useState"],"sources":["../../../src/client/locales.ts","../../../src/client/api.ts","../../../src/client/settings-section.tsx","../../../src/client/index.tsx"],"sourcesContent":["/**\n * Locale dictionaries for @fonlan/dsh-model-parameters settings page.\n * zh is the key source of truth; en must carry the exact same key set.\n */\nexport const LOCALE_NS = 'model-parameters'\n\nexport const zh = {\n settingsTitle: '模型参数补全',\n sectionSub: '补全字段、目录刷新、Provider 映射与补全报告。',\n master: '启用自动补全',\n masterDesc: '关闭后不再自动补全任何字段(已补全的值保留)。',\n fields: '补全字段',\n fillName: '显示名称',\n fillNameDesc: '模型条目缺 name 时写入目录中的显示名称。',\n fillContext: '最大上下文',\n fillContextDesc: '缺 contextWindow 时写入目录的上下文上限。',\n fillMaxTokens: '最大输出',\n fillMaxTokensDesc: '缺 maxTokens 时写入目录的输出上限。',\n fillReasoning: '推理强度',\n fillReasoningDesc: '缺 reasoningEfforts 时按目录 effort 档位写入 identity 字典(如 {high:\"high\", max:\"max\"})。',\n fillInput: '输入模态',\n fillInputDesc: '缺 input 时写入目录中的 text/image 模态。',\n ttl: '目录更新周期(天)',\n ttlDesc: '缓存的 models.dev 目录超过该天数后,在下次补全时自动刷新。',\n providerMap: 'Provider 映射',\n providerMapDesc: 'dsh provider → models.dev provider 的匹配覆盖(可选)。',\n addMapping: '添加映射',\n removeMapping: '移除',\n catalog: '模型目录',\n catalogEntries: '目录条目',\n catalogProviders: '覆盖 Provider',\n catalogFetched: '上次更新',\n catalogFresh: '新鲜',\n catalogStale: '已过期',\n catalogNever: '尚未获取',\n refresh: '立即更新目录并补全',\n refreshBusy: '更新中…',\n reconcileNow: '立即补全一次',\n report: '最近一次补全',\n reportNever: '尚未执行',\n reportFilled: '补全字段',\n reportTouched: '涉及 Provider',\n reportUnmatched: '未匹配模型',\n reportEmpty: '全部匹配,无未匹配项',\n reportAt: '时间',\n error: '加载失败',\n saving: '保存中…',\n officialProviders: '官方厂商优先级',\n officialProvidersDesc: '同一模型 id 在多处出现时,优先采用这些 models.dev provider 的条目。',\n} as const\n\nexport const en: Record<keyof typeof zh, string> = {\n settingsTitle: 'Model Parameters',\n sectionSub: 'Field toggles, catalog refresh, provider mapping and the fill report.',\n master: 'Enable auto-fill',\n masterDesc: 'When off, no fields are filled (already-filled values are kept).',\n fields: 'Fields to fill',\n fillName: 'Display name',\n fillNameDesc: 'Write the catalog display name when a model entry lacks name.',\n fillContext: 'Context window',\n fillContextDesc: 'Write the catalog context limit when contextWindow is missing.',\n fillMaxTokens: 'Max output',\n fillMaxTokensDesc: 'Write the catalog output limit when maxTokens is missing.',\n fillReasoning: 'Reasoning efforts',\n fillReasoningDesc: 'Write an identity reasoningEfforts dict (e.g. {high:\"high\", max:\"max\"}) from catalog effort levels when missing.',\n fillInput: 'Input modalities',\n fillInputDesc: 'Write text/image modalities from the catalog when input is missing.',\n ttl: 'Catalog refresh interval (days)',\n ttlDesc: 'The cached models.dev catalog is refreshed on the next fill once older than this many days.',\n providerMap: 'Provider mapping',\n providerMapDesc: 'Optional dsh provider → models.dev provider matching overrides.',\n addMapping: 'Add mapping',\n removeMapping: 'Remove',\n catalog: 'Model catalog',\n catalogEntries: 'Catalog entries',\n catalogProviders: 'Providers covered',\n catalogFetched: 'Last updated',\n catalogFresh: 'fresh',\n catalogStale: 'stale',\n catalogNever: 'not fetched yet',\n refresh: 'Update catalog & fill now',\n refreshBusy: 'Updating…',\n reconcileNow: 'Fill now',\n report: 'Last fill',\n reportNever: 'never run',\n reportFilled: 'fields filled',\n reportTouched: 'providers touched',\n reportUnmatched: 'unmatched models',\n reportEmpty: 'all matched, nothing unmatched',\n reportAt: 'time',\n error: 'Failed to load',\n saving: 'Saving…',\n officialProviders: 'Official provider priority',\n officialProvidersDesc: 'When one model id appears under several providers, prefer entries from these models.dev providers.',\n}\n","/** Client transport for the model-parameters settings API. */\nimport type { ModelParametersConfig } from '../shared/config.js'\n\nexport interface CatalogView {\n entries: number\n providers: number\n fetchedAt: number\n fresh: boolean\n ttlDays: number\n}\n\nexport interface FillReportView {\n at: number\n filled: number\n touched: number\n unmatched: string[]\n}\n\nexport interface PluginState {\n config: ModelParametersConfig\n catalog: CatalogView\n report: FillReportView | null\n}\n\nexport class ModelParametersApiError extends Error {\n constructor(readonly code: string, message: string) {\n super(message)\n }\n}\n\nasync function call<T>(method: string, payload: Record<string, unknown> = {}): Promise<T> {\n let response: Response\n try {\n response = await fetch(`/plugins/@fonlan/dsh-model-parameters/api/${method}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n } catch (error) {\n throw new ModelParametersApiError('network', error instanceof Error ? error.message : String(error))\n }\n const parsed: { ok?: boolean; value?: unknown; error?: { code?: string; message?: string } } | null\n = await response.json().catch(() => null)\n if (!response.ok || parsed === null || parsed.ok !== true || parsed.value === undefined) {\n throw new ModelParametersApiError(\n parsed?.error?.code ?? 'http',\n parsed?.error?.message ?? `HTTP ${response.status}`,\n )\n }\n return parsed.value as T\n}\n\nexport const api = {\n state: () => call<PluginState>('state'),\n refresh: () => call<PluginState>('refresh'),\n reconcile: () => call<PluginState>('reconcile'),\n setConfig: (patch: Partial<ModelParametersConfig>) =>\n call<PluginState>('set-config', { patch }),\n}\n","/**\n * The Model Parameters settings page (设置 → 侧栏「模型参数补全」).\n *\n * Registers into the `settings.section` slot, so the page owns one entry in\n * the settings sidebar and renders its content in the panel's content column.\n * Content: master + per-field toggles, catalog TTL, provider→models.dev\n * mapping overrides, a manual refresh/fill action, the catalog freshness line,\n * and the last fill report (filled / touched / unmatched).\n *\n * All reads/mutations go through the plugin's fenced API; config persists into\n * the `model-parameters` settings namespace (settings.yaml).\n */\nimport { useCallback, useEffect, useState, useSyncExternalStore } from 'react'\nimport type { Context } from '@deepseek-ai/cordis'\ntype ClientContext = Context\nimport type { Translate } from '@deepseek-ai/dsh-client-ui-slots'\nimport { LOCALE_NS } from './locales'\nimport { api, type PluginState } from './api'\nimport type { ModelParametersConfig } from '../shared/config.js'\nimport './settings-section.css'\n\nfunction useLocaleRevision(ctx: ClientContext): number {\n const subscribe = useCallback(\n (onChange: () => void) => {\n try {\n return ctx.locale.subscribe(onChange)\n } catch {\n return () => {}\n }\n },\n [ctx],\n )\n const getSnapshot = useCallback(() => {\n try {\n return ctx.locale.getLocale().revision\n } catch {\n return 0\n }\n }, [ctx])\n return useSyncExternalStore(subscribe, getSnapshot)\n}\n\nfunction formatTime(epochMs: number): string {\n try {\n return new Date(epochMs).toLocaleString()\n } catch {\n return String(epochMs)\n }\n}\n\nexport function makeSettingsSection(ctx: ClientContext): () => JSX.Element {\n const t: Translate = (() => {\n try {\n return ctx.locale.bind(LOCALE_NS) as unknown as Translate\n } catch {\n return (key: string) => key\n }\n })()\n\n return function ModelParametersSettingsSection(): JSX.Element {\n useLocaleRevision(ctx)\n const [state, setState] = useState<PluginState | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [loading, setLoading] = useState(true)\n const [busy, setBusy] = useState(false)\n const [saving, setSaving] = useState(false)\n const [mappingRows, setMappingRows] = useState<Array<{ from: string; to: string }>>([])\n const [newFrom, setNewFrom] = useState('')\n const [newTo, setNewTo] = useState('')\n\n const load = useCallback(async () => {\n setLoading(true)\n try {\n const next = await api.state()\n setState(next)\n setMappingRows(Object.entries(next.config.providerMap).map(([from, to]) => ({ from, to })))\n setError(null)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setLoading(false)\n }\n }, [])\n\n useEffect(() => {\n void load()\n }, [load])\n\n const patchConfig = useCallback(async (patch: Partial<ModelParametersConfig>) => {\n setSaving(true)\n try {\n const next = await api.setConfig(patch)\n setState(next)\n setMappingRows(Object.entries(next.config.providerMap).map(([from, to]) => ({ from, to })))\n setError(null)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setSaving(false)\n }\n }, [])\n\n const refresh = useCallback(async () => {\n setBusy(true)\n try {\n setState(await api.refresh())\n setError(null)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setBusy(false)\n }\n }, [])\n\n const reconcileNow = useCallback(async () => {\n setBusy(true)\n try {\n setState(await api.reconcile())\n setError(null)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setBusy(false)\n }\n }, [])\n\n const applyMappings = useCallback(async () => {\n const providerMap: Record<string, string> = {}\n for (const row of mappingRows) {\n const from = row.from.trim()\n const to = row.to.trim()\n if (from.length > 0 && to.length > 0) providerMap[from] = to\n }\n await patchConfig({ providerMap })\n }, [mappingRows, patchConfig])\n\n return (\n <div className=\"mp-page\">\n <header className=\"mp-page-head\">\n <h3 className=\"mp-page-title\">{t('settingsTitle')}</h3>\n <p className=\"mp-page-sub\">{t('sectionSub')}</p>\n </header>\n {error !== null && state === null ? (\n <p className=\"mp-error\">{t('error')}: {error}</p>\n ) : loading || state === null ? (\n <p className=\"mp-muted\">{t('saving')}</p>\n ) : (\n <ModelParametersBody\n t={t}\n state={state}\n error={error}\n saving={saving}\n busy={busy}\n mappingRows={mappingRows}\n newFrom={newFrom}\n newTo={newTo}\n setNewFrom={setNewFrom}\n setNewTo={setNewTo}\n setMappingRows={setMappingRows}\n onPatchConfig={patchConfig}\n onRefresh={refresh}\n onReconcile={reconcileNow}\n onApplyMappings={applyMappings}\n />\n )}\n </div>\n )\n }\n}\n\n/** The page body: divider-separated control groups. */\nfunction ModelParametersBody(props: {\n t: Translate\n state: PluginState\n error: string | null\n saving: boolean\n busy: boolean\n mappingRows: Array<{ from: string; to: string }>\n newFrom: string\n newTo: string\n setNewFrom: (value: string) => void\n setNewTo: (value: string) => void\n setMappingRows: (rows: Array<{ from: string; to: string }>) => void\n onPatchConfig: (patch: Partial<ModelParametersConfig>) => Promise<void>\n onRefresh: () => Promise<void>\n onReconcile: () => Promise<void>\n onApplyMappings: () => Promise<void>\n}): JSX.Element {\n const {\n t, state, error, saving, busy,\n mappingRows, newFrom, newTo,\n setNewFrom, setNewTo, setMappingRows,\n onPatchConfig, onRefresh, onReconcile, onApplyMappings,\n } = props\n\n const config = state.config\n const catalog = state.catalog\n const report = state.report\n\n return (\n <div className=\"mp-body\">\n <section className=\"mp-group\">\n <h4 className=\"mp-group-title\">{t('fields')}</h4>\n <label className=\"mp-toggle\">\n <input\n type=\"checkbox\"\n checked={config.enabled}\n disabled={saving}\n onChange={(event) => void onPatchConfig({ enabled: event.target.checked })}\n />\n <span className=\"mp-toggle-body\">\n <span className=\"mp-toggle-label\">{t('master')}</span>\n <span className=\"mp-toggle-desc\">{t('masterDesc')}</span>\n </span>\n </label>\n {([\n ['fillName', t('fillName'), t('fillNameDesc')],\n ['fillContext', t('fillContext'), t('fillContextDesc')],\n ['fillMaxTokens', t('fillMaxTokens'), t('fillMaxTokensDesc')],\n ['fillReasoning', t('fillReasoning'), t('fillReasoningDesc')],\n ['fillInput', t('fillInput'), t('fillInputDesc')],\n ] as const).map(([key, label, desc]) => (\n <label className=\"mp-toggle mp-toggle-sub\" key={key}>\n <input\n type=\"checkbox\"\n checked={config[key]}\n disabled={saving || !config.enabled}\n onChange={(event) => void onPatchConfig({ [key]: event.target.checked })}\n />\n <span className=\"mp-toggle-body\">\n <span className=\"mp-toggle-label\">{label}</span>\n <span className=\"mp-toggle-desc\">{desc}</span>\n </span>\n </label>\n ))}\n </section>\n\n <hr className=\"mp-divider\" />\n\n <section className=\"mp-group\">\n <h4 className=\"mp-group-title\">{t('catalog')}</h4>\n <div className=\"mp-meta\">\n <span>{t('catalogEntries')}: <strong>{catalog.entries.toLocaleString()}</strong></span>\n <span>{t('catalogProviders')}: <strong>{catalog.providers.toLocaleString()}</strong></span>\n <span>\n {t('catalogFetched')}:{' '}\n <strong>{catalog.fetchedAt > 0 ? formatTime(catalog.fetchedAt) : t('catalogNever')}</strong>{' '}\n {catalog.fetchedAt > 0 && (\n <span className={catalog.fresh ? 'mp-badge mp-badge-ok' : 'mp-badge mp-badge-warn'}>\n {catalog.fresh ? t('catalogFresh') : t('catalogStale')}\n </span>\n )}\n </span>\n <label className=\"mp-ttl\">\n {t('ttl')}\n <input\n className=\"mp-input mp-input-narrow\"\n type=\"number\"\n min={1}\n max={90}\n value={config.ttlDays}\n disabled={saving}\n onChange={(event) => {\n const days = Number(event.target.value)\n if (Number.isFinite(days) && days >= 1 && days <= 90) void onPatchConfig({ ttlDays: Math.round(days) })\n }}\n />\n </label>\n </div>\n <div className=\"mp-actions\">\n <button className=\"mp-button\" disabled={busy} onClick={() => void onRefresh()}>\n {busy ? t('refreshBusy') : t('refresh')}\n </button>\n <button className=\"mp-button mp-button-ghost\" disabled={busy} onClick={() => void onReconcile()}>\n {t('reconcileNow')}\n </button>\n </div>\n </section>\n\n <hr className=\"mp-divider\" />\n\n <section className=\"mp-group\">\n <h4 className=\"mp-group-title\">{t('providerMap')}</h4>\n <p className=\"mp-desc\">{t('providerMapDesc')}</p>\n {mappingRows.map((row, index) => (\n <div className=\"mp-map-row\" key={index}>\n <input\n className=\"mp-input\"\n value={row.from}\n disabled={saving}\n onChange={(event) => {\n const next = [...mappingRows]\n next[index] = { ...row, from: event.target.value }\n setMappingRows(next)\n }}\n />\n <span className=\"mp-map-arrow\">→</span>\n <input\n className=\"mp-input\"\n value={row.to}\n disabled={saving}\n onChange={(event) => {\n const next = [...mappingRows]\n next[index] = { ...row, to: event.target.value }\n setMappingRows(next)\n }}\n />\n <button\n className=\"mp-button mp-button-ghost\"\n disabled={saving}\n onClick={() => setMappingRows(mappingRows.filter((_, i) => i !== index))}\n >\n {t('removeMapping')}\n </button>\n </div>\n ))}\n <div className=\"mp-map-row\">\n <input\n className=\"mp-input\"\n placeholder=\"dsh provider\"\n value={newFrom}\n onChange={(event) => setNewFrom(event.target.value)}\n />\n <span className=\"mp-map-arrow\">→</span>\n <input\n className=\"mp-input\"\n placeholder=\"models.dev provider\"\n value={newTo}\n onChange={(event) => setNewTo(event.target.value)}\n />\n <button\n className=\"mp-button mp-button-ghost\"\n disabled={newFrom.trim().length === 0 || newTo.trim().length === 0}\n onClick={() => {\n setMappingRows([...mappingRows, { from: newFrom.trim(), to: newTo.trim() }])\n setNewFrom('')\n setNewTo('')\n }}\n >\n {t('addMapping')}\n </button>\n </div>\n <div className=\"mp-actions\">\n <button className=\"mp-button\" disabled={saving} onClick={() => void onApplyMappings()}>\n {saving ? t('saving') : '✓ ' + t('providerMap')}\n </button>\n </div>\n </section>\n\n <hr className=\"mp-divider\" />\n\n <section className=\"mp-group\">\n <h4 className=\"mp-group-title\">{t('report')}</h4>\n {report === null ? (\n <p className=\"mp-muted\">{t('reportNever')}</p>\n ) : (\n <ul className=\"mp-report\">\n <li>{t('reportAt')}: {formatTime(report.at)}</li>\n <li>{t('reportFilled')}: <strong>{report.filled}</strong></li>\n <li>{t('reportTouched')}: <strong>{report.touched}</strong></li>\n <li>\n {t('reportUnmatched')}:{' '}\n {report.unmatched.length === 0\n ? t('reportEmpty')\n : <code className=\"mp-code\">{report.unmatched.join(', ')}</code>}\n </li>\n </ul>\n )}\n </section>\n\n {error !== null && <p className=\"mp-error\">{error}</p>}\n </div>\n )\n}\n","/**\n * @fonlan/dsh-model-parameters client half: the plugin's own Settings section.\n *\n * Registers into the settings.section slot, so the page owns one entry in the\n * settings sidebar and renders in the panel's content column. The page itself\n * lives in ./settings-section.\n *\n * Deps resolved from the browser module table: react only. The slots service\n * is reached through the cordis context; types come from devDependencies and\n * are erased at build time.\n */\nimport type { Context } from '@deepseek-ai/cordis'\ntype ClientContext = Context\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport { LOCALE_NS, zh, en } from './locales'\nimport { makeSettingsSection } from './settings-section'\n\n/** The settings sidebar entry this page owns (must stay stable). */\nconst SECTION_ID = 'model-parameters'\n\n/** The slots service face (subset of @deepseek-ai/dsh-client-ui-slots). */\ninterface SlotEntry {\n name: string\n id?: string\n order?: number\n label?: () => string\n locale?: string\n}\n\ninterface Slots {\n inject(name: string, register: () => unknown): unknown\n register(def: SlotEntry, component: unknown): unknown\n}\n\n/** Services required before mounting (provided by the client runtime). */\nexport const inject = ['slots', 'locale']\n\n/** Client plugin body. */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => {\n const off = ctx.locale.register(LOCALE_NS, { zh, en })\n return () => off()\n }, 'model-parameters: dictionaries')\n\n const t = ctx.locale.bind(LOCALE_NS) as unknown as (key: string) => string\n const Section = makeSettingsSection(ctx)\n const slots = (ctx as unknown as { slots: Slots }).slots\n slots.inject('settings.section', () =>\n slots.register({\n name: 'settings.section',\n id: SECTION_ID,\n order: 320,\n label: () => t('settingsTitle'),\n locale: LOCALE_NS,\n }, Section),\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["useCallback","useSyncExternalStore","useState"],"sources":["../../../src/client/locales.ts","../../../src/client/api.ts","../../../src/client/settings-section.tsx","../../../src/client/index.tsx"],"sourcesContent":["/**\n * Locale dictionaries for @fonlan/dsh-model-parameters settings page.\n * zh is the key source of truth; en must carry the exact same key set.\n */\nexport const LOCALE_NS = 'model-parameters'\n\nexport const zh = {\n settingsTitle: '模型参数补全',\n sectionSub: '补全字段、目录刷新、Provider 映射与补全报告。',\n master: '启用自动补全',\n masterDesc: '关闭后不再自动补全任何字段(已补全的值保留)。',\n fields: '补全字段',\n fillName: '显示名称',\n fillNameDesc: '模型条目缺 name 时写入目录中的显示名称。',\n fillContext: '最大上下文',\n fillContextDesc: '缺 contextWindow 时写入目录的上下文上限。',\n fillMaxTokens: '最大输出',\n fillMaxTokensDesc: '缺 maxTokens 时写入目录的输出上限。',\n fillReasoning: '推理强度',\n fillReasoningDesc: '缺 reasoningEfforts 时按目录 effort 档位写入 identity 字典(如 {high:\"high\", max:\"max\"})。',\n fillInput: '输入模态',\n fillInputDesc: '缺 input 时写入目录中的 text/image 模态。',\n ttl: '目录更新周期(天)',\n ttlDesc: '缓存的 models.dev 目录超过该天数后,在下次补全时自动刷新。',\n providerMap: 'Provider 映射',\n providerMapDesc: 'dsh provider → models.dev provider 的匹配覆盖(可选)。',\n addMapping: '添加映射',\n removeMapping: '移除',\n catalog: '模型目录',\n catalogEntries: '目录条目',\n catalogProviders: '覆盖 Provider',\n catalogFetched: '上次更新',\n catalogFresh: '新鲜',\n catalogStale: '已过期',\n catalogNever: '尚未获取',\n refresh: '立即更新目录并补全',\n refreshBusy: '更新中…',\n reconcileNow: '立即补全一次',\n report: '最近一次补全',\n reportNever: '尚未执行',\n reportFilled: '补全字段',\n reportTouched: '涉及 Provider',\n reportUnmatched: '未匹配模型',\n reportEmpty: '全部匹配,无未匹配项',\n reportAt: '时间',\n error: '加载失败',\n saving: '保存中…',\n officialProviders: '官方厂商优先级',\n officialProvidersDesc: '同一模型 id 在多处出现时,优先采用这些 models.dev provider 的条目。',\n} as const\n\nexport const en: Record<keyof typeof zh, string> = {\n settingsTitle: 'Model Parameters',\n sectionSub: 'Field toggles, catalog refresh, provider mapping and the fill report.',\n master: 'Enable auto-fill',\n masterDesc: 'When off, no fields are filled (already-filled values are kept).',\n fields: 'Fields to fill',\n fillName: 'Display name',\n fillNameDesc: 'Write the catalog display name when a model entry lacks name.',\n fillContext: 'Context window',\n fillContextDesc: 'Write the catalog context limit when contextWindow is missing.',\n fillMaxTokens: 'Max output',\n fillMaxTokensDesc: 'Write the catalog output limit when maxTokens is missing.',\n fillReasoning: 'Reasoning efforts',\n fillReasoningDesc: 'Write an identity reasoningEfforts dict (e.g. {high:\"high\", max:\"max\"}) from catalog effort levels when missing.',\n fillInput: 'Input modalities',\n fillInputDesc: 'Write text/image modalities from the catalog when input is missing.',\n ttl: 'Catalog refresh interval (days)',\n ttlDesc: 'The cached models.dev catalog is refreshed on the next fill once older than this many days.',\n providerMap: 'Provider mapping',\n providerMapDesc: 'Optional dsh provider → models.dev provider matching overrides.',\n addMapping: 'Add mapping',\n removeMapping: 'Remove',\n catalog: 'Model catalog',\n catalogEntries: 'Catalog entries',\n catalogProviders: 'Providers covered',\n catalogFetched: 'Last updated',\n catalogFresh: 'fresh',\n catalogStale: 'stale',\n catalogNever: 'not fetched yet',\n refresh: 'Update catalog & fill now',\n refreshBusy: 'Updating…',\n reconcileNow: 'Fill now',\n report: 'Last fill',\n reportNever: 'never run',\n reportFilled: 'fields filled',\n reportTouched: 'providers touched',\n reportUnmatched: 'unmatched models',\n reportEmpty: 'all matched, nothing unmatched',\n reportAt: 'time',\n error: 'Failed to load',\n saving: 'Saving…',\n officialProviders: 'Official provider priority',\n officialProvidersDesc: 'When one model id appears under several providers, prefer entries from these models.dev providers.',\n}\n","/** Client transport for the model-parameters settings API. */\nimport type { ModelParametersConfig } from '../shared/config.js'\n\nexport interface CatalogView {\n entries: number\n providers: number\n fetchedAt: number\n fresh: boolean\n ttlDays: number\n}\n\nexport interface FillReportView {\n at: number\n filled: number\n touched: number\n unmatched: string[]\n}\n\nexport interface PluginState {\n config: ModelParametersConfig\n catalog: CatalogView\n report: FillReportView | null\n}\n\nexport class ModelParametersApiError extends Error {\n constructor(readonly code: string, message: string) {\n super(message)\n }\n}\n\nasync function call<T>(method: string, payload: Record<string, unknown> = {}): Promise<T> {\n let response: Response\n try {\n response = await fetch(`/plugins/@fonlan/dsh-model-parameters/api/${method}`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n })\n } catch (error) {\n throw new ModelParametersApiError('network', error instanceof Error ? error.message : String(error))\n }\n const parsed: { ok?: boolean; value?: unknown; error?: { code?: string; message?: string } } | null\n = await response.json().catch(() => null)\n if (!response.ok || parsed === null || parsed.ok !== true || parsed.value === undefined) {\n throw new ModelParametersApiError(\n parsed?.error?.code ?? 'http',\n parsed?.error?.message ?? `HTTP ${response.status}`,\n )\n }\n return parsed.value as T\n}\n\nexport const api = {\n state: () => call<PluginState>('state'),\n refresh: () => call<PluginState>('refresh'),\n reconcile: () => call<PluginState>('reconcile'),\n setConfig: (patch: Partial<ModelParametersConfig>) =>\n call<PluginState>('set-config', { patch }),\n}\n","/**\n * The Model Parameters settings page (设置 → 侧栏「模型参数补全」).\n *\n * Registers into the `settings.section` slot, so the page owns one entry in\n * the settings sidebar and renders its content in the panel's content column.\n * Content: master + per-field toggles, catalog TTL, provider→models.dev\n * mapping overrides, a manual refresh/fill action, the catalog freshness line,\n * and the last fill report (filled / touched / unmatched).\n *\n * All reads/mutations go through the plugin's fenced API; config persists into\n * the `model-parameters` profile entry — the plugin's own loader entry config,\n * written through the host settings service.\n */\nimport { useCallback, useEffect, useState, useSyncExternalStore } from 'react'\nimport type { Context } from '@deepseek-ai/cordis'\ntype ClientContext = Context\nimport type { Translate } from '@deepseek-ai/dsh-client-ui-slots'\nimport { LOCALE_NS } from './locales'\nimport { api, type PluginState } from './api'\nimport type { ModelParametersConfig } from '../shared/config.js'\nimport './settings-section.css'\n\nfunction useLocaleRevision(ctx: ClientContext): number {\n const subscribe = useCallback(\n (onChange: () => void) => {\n try {\n return ctx.locale.subscribe(onChange)\n } catch {\n return () => {}\n }\n },\n [ctx],\n )\n const getSnapshot = useCallback(() => {\n try {\n return ctx.locale.getLocale().revision\n } catch {\n return 0\n }\n }, [ctx])\n return useSyncExternalStore(subscribe, getSnapshot)\n}\n\nfunction formatTime(epochMs: number): string {\n try {\n return new Date(epochMs).toLocaleString()\n } catch {\n return String(epochMs)\n }\n}\n\nexport function makeSettingsSection(ctx: ClientContext): () => JSX.Element {\n const t: Translate = (() => {\n try {\n return ctx.locale.bind(LOCALE_NS) as unknown as Translate\n } catch {\n return (key: string) => key\n }\n })()\n\n return function ModelParametersSettingsSection(): JSX.Element {\n useLocaleRevision(ctx)\n const [state, setState] = useState<PluginState | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [loading, setLoading] = useState(true)\n const [busy, setBusy] = useState(false)\n const [saving, setSaving] = useState(false)\n const [mappingRows, setMappingRows] = useState<Array<{ from: string; to: string }>>([])\n const [newFrom, setNewFrom] = useState('')\n const [newTo, setNewTo] = useState('')\n\n const load = useCallback(async () => {\n setLoading(true)\n try {\n const next = await api.state()\n setState(next)\n setMappingRows(Object.entries(next.config.providerMap).map(([from, to]) => ({ from, to })))\n setError(null)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setLoading(false)\n }\n }, [])\n\n useEffect(() => {\n void load()\n }, [load])\n\n const patchConfig = useCallback(async (patch: Partial<ModelParametersConfig>) => {\n setSaving(true)\n try {\n const next = await api.setConfig(patch)\n setState(next)\n setMappingRows(Object.entries(next.config.providerMap).map(([from, to]) => ({ from, to })))\n setError(null)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setSaving(false)\n }\n }, [])\n\n const refresh = useCallback(async () => {\n setBusy(true)\n try {\n setState(await api.refresh())\n setError(null)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setBusy(false)\n }\n }, [])\n\n const reconcileNow = useCallback(async () => {\n setBusy(true)\n try {\n setState(await api.reconcile())\n setError(null)\n } catch (cause) {\n setError(cause instanceof Error ? cause.message : String(cause))\n } finally {\n setBusy(false)\n }\n }, [])\n\n const applyMappings = useCallback(async () => {\n const providerMap: Record<string, string> = {}\n for (const row of mappingRows) {\n const from = row.from.trim()\n const to = row.to.trim()\n if (from.length > 0 && to.length > 0) providerMap[from] = to\n }\n await patchConfig({ providerMap })\n }, [mappingRows, patchConfig])\n\n return (\n <div className=\"mp-page\">\n <header className=\"mp-page-head\">\n <h3 className=\"mp-page-title\">{t('settingsTitle')}</h3>\n <p className=\"mp-page-sub\">{t('sectionSub')}</p>\n </header>\n {error !== null && state === null ? (\n <p className=\"mp-error\">{t('error')}: {error}</p>\n ) : loading || state === null ? (\n <p className=\"mp-muted\">{t('saving')}</p>\n ) : (\n <ModelParametersBody\n t={t}\n state={state}\n error={error}\n saving={saving}\n busy={busy}\n mappingRows={mappingRows}\n newFrom={newFrom}\n newTo={newTo}\n setNewFrom={setNewFrom}\n setNewTo={setNewTo}\n setMappingRows={setMappingRows}\n onPatchConfig={patchConfig}\n onRefresh={refresh}\n onReconcile={reconcileNow}\n onApplyMappings={applyMappings}\n />\n )}\n </div>\n )\n }\n}\n\n/** The page body: divider-separated control groups. */\nfunction ModelParametersBody(props: {\n t: Translate\n state: PluginState\n error: string | null\n saving: boolean\n busy: boolean\n mappingRows: Array<{ from: string; to: string }>\n newFrom: string\n newTo: string\n setNewFrom: (value: string) => void\n setNewTo: (value: string) => void\n setMappingRows: (rows: Array<{ from: string; to: string }>) => void\n onPatchConfig: (patch: Partial<ModelParametersConfig>) => Promise<void>\n onRefresh: () => Promise<void>\n onReconcile: () => Promise<void>\n onApplyMappings: () => Promise<void>\n}): JSX.Element {\n const {\n t, state, error, saving, busy,\n mappingRows, newFrom, newTo,\n setNewFrom, setNewTo, setMappingRows,\n onPatchConfig, onRefresh, onReconcile, onApplyMappings,\n } = props\n\n const config = state.config\n const catalog = state.catalog\n const report = state.report\n\n return (\n <div className=\"mp-body\">\n <section className=\"mp-group\">\n <h4 className=\"mp-group-title\">{t('fields')}</h4>\n <label className=\"mp-toggle\">\n <input\n type=\"checkbox\"\n checked={config.enabled}\n disabled={saving}\n onChange={(event) => void onPatchConfig({ enabled: event.target.checked })}\n />\n <span className=\"mp-toggle-body\">\n <span className=\"mp-toggle-label\">{t('master')}</span>\n <span className=\"mp-toggle-desc\">{t('masterDesc')}</span>\n </span>\n </label>\n {([\n ['fillName', t('fillName'), t('fillNameDesc')],\n ['fillContext', t('fillContext'), t('fillContextDesc')],\n ['fillMaxTokens', t('fillMaxTokens'), t('fillMaxTokensDesc')],\n ['fillReasoning', t('fillReasoning'), t('fillReasoningDesc')],\n ['fillInput', t('fillInput'), t('fillInputDesc')],\n ] as const).map(([key, label, desc]) => (\n <label className=\"mp-toggle mp-toggle-sub\" key={key}>\n <input\n type=\"checkbox\"\n checked={config[key]}\n disabled={saving || !config.enabled}\n onChange={(event) => void onPatchConfig({ [key]: event.target.checked })}\n />\n <span className=\"mp-toggle-body\">\n <span className=\"mp-toggle-label\">{label}</span>\n <span className=\"mp-toggle-desc\">{desc}</span>\n </span>\n </label>\n ))}\n </section>\n\n <hr className=\"mp-divider\" />\n\n <section className=\"mp-group\">\n <h4 className=\"mp-group-title\">{t('catalog')}</h4>\n <div className=\"mp-meta\">\n <span>{t('catalogEntries')}: <strong>{catalog.entries.toLocaleString()}</strong></span>\n <span>{t('catalogProviders')}: <strong>{catalog.providers.toLocaleString()}</strong></span>\n <span>\n {t('catalogFetched')}:{' '}\n <strong>{catalog.fetchedAt > 0 ? formatTime(catalog.fetchedAt) : t('catalogNever')}</strong>{' '}\n {catalog.fetchedAt > 0 && (\n <span className={catalog.fresh ? 'mp-badge mp-badge-ok' : 'mp-badge mp-badge-warn'}>\n {catalog.fresh ? t('catalogFresh') : t('catalogStale')}\n </span>\n )}\n </span>\n <label className=\"mp-ttl\">\n {t('ttl')}\n <input\n className=\"mp-input mp-input-narrow\"\n type=\"number\"\n min={1}\n max={90}\n value={config.ttlDays}\n disabled={saving}\n onChange={(event) => {\n const days = Number(event.target.value)\n if (Number.isFinite(days) && days >= 1 && days <= 90) void onPatchConfig({ ttlDays: Math.round(days) })\n }}\n />\n </label>\n </div>\n <div className=\"mp-actions\">\n <button className=\"mp-button\" disabled={busy} onClick={() => void onRefresh()}>\n {busy ? t('refreshBusy') : t('refresh')}\n </button>\n <button className=\"mp-button mp-button-ghost\" disabled={busy} onClick={() => void onReconcile()}>\n {t('reconcileNow')}\n </button>\n </div>\n </section>\n\n <hr className=\"mp-divider\" />\n\n <section className=\"mp-group\">\n <h4 className=\"mp-group-title\">{t('providerMap')}</h4>\n <p className=\"mp-desc\">{t('providerMapDesc')}</p>\n {mappingRows.map((row, index) => (\n <div className=\"mp-map-row\" key={index}>\n <input\n className=\"mp-input\"\n value={row.from}\n disabled={saving}\n onChange={(event) => {\n const next = [...mappingRows]\n next[index] = { ...row, from: event.target.value }\n setMappingRows(next)\n }}\n />\n <span className=\"mp-map-arrow\">→</span>\n <input\n className=\"mp-input\"\n value={row.to}\n disabled={saving}\n onChange={(event) => {\n const next = [...mappingRows]\n next[index] = { ...row, to: event.target.value }\n setMappingRows(next)\n }}\n />\n <button\n className=\"mp-button mp-button-ghost\"\n disabled={saving}\n onClick={() => setMappingRows(mappingRows.filter((_, i) => i !== index))}\n >\n {t('removeMapping')}\n </button>\n </div>\n ))}\n <div className=\"mp-map-row\">\n <input\n className=\"mp-input\"\n placeholder=\"dsh provider\"\n value={newFrom}\n onChange={(event) => setNewFrom(event.target.value)}\n />\n <span className=\"mp-map-arrow\">→</span>\n <input\n className=\"mp-input\"\n placeholder=\"models.dev provider\"\n value={newTo}\n onChange={(event) => setNewTo(event.target.value)}\n />\n <button\n className=\"mp-button mp-button-ghost\"\n disabled={newFrom.trim().length === 0 || newTo.trim().length === 0}\n onClick={() => {\n setMappingRows([...mappingRows, { from: newFrom.trim(), to: newTo.trim() }])\n setNewFrom('')\n setNewTo('')\n }}\n >\n {t('addMapping')}\n </button>\n </div>\n <div className=\"mp-actions\">\n <button className=\"mp-button\" disabled={saving} onClick={() => void onApplyMappings()}>\n {saving ? t('saving') : '✓ ' + t('providerMap')}\n </button>\n </div>\n </section>\n\n <hr className=\"mp-divider\" />\n\n <section className=\"mp-group\">\n <h4 className=\"mp-group-title\">{t('report')}</h4>\n {report === null ? (\n <p className=\"mp-muted\">{t('reportNever')}</p>\n ) : (\n <ul className=\"mp-report\">\n <li>{t('reportAt')}: {formatTime(report.at)}</li>\n <li>{t('reportFilled')}: <strong>{report.filled}</strong></li>\n <li>{t('reportTouched')}: <strong>{report.touched}</strong></li>\n <li>\n {t('reportUnmatched')}:{' '}\n {report.unmatched.length === 0\n ? t('reportEmpty')\n : <code className=\"mp-code\">{report.unmatched.join(', ')}</code>}\n </li>\n </ul>\n )}\n </section>\n\n {error !== null && <p className=\"mp-error\">{error}</p>}\n </div>\n )\n}\n","/**\n * @fonlan/dsh-model-parameters client half: the plugin's own Settings section.\n *\n * Registers into the settings.section slot, so the page owns one entry in the\n * settings sidebar and renders in the panel's content column. The page itself\n * lives in ./settings-section.\n *\n * Deps resolved from the browser module table: react only. The slots service\n * is reached through the cordis context; types come from devDependencies and\n * are erased at build time.\n */\nimport type { Context } from '@deepseek-ai/cordis'\ntype ClientContext = Context\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport { LOCALE_NS, zh, en } from './locales'\nimport { makeSettingsSection } from './settings-section'\n\n/** The settings sidebar entry this page owns (must stay stable). */\nconst SECTION_ID = 'model-parameters'\n\n/** The slots service face (subset of @deepseek-ai/dsh-client-ui-slots). */\ninterface SlotEntry {\n name: string\n id?: string\n order?: number\n label?: () => string\n locale?: string\n}\n\ninterface Slots {\n inject(name: string, register: () => unknown): unknown\n register(def: SlotEntry, component: unknown): unknown\n}\n\n/** Services required before mounting (provided by the client runtime). */\nexport const inject = ['slots', 'locale']\n\n/** Client plugin body. */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => {\n const off = ctx.locale.register(LOCALE_NS, { zh, en })\n return () => off()\n }, 'model-parameters: dictionaries')\n\n const t = ctx.locale.bind(LOCALE_NS) as unknown as (key: string) => string\n const Section = makeSettingsSection(ctx)\n const slots = (ctx as unknown as { slots: Slots }).slots\n slots.inject('settings.section', () =>\n slots.register({\n name: 'settings.section',\n id: SECTION_ID,\n order: 320,\n label: () => t('settingsTitle'),\n locale: LOCALE_NS,\n }, Section),\n )\n}\n"],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -6,13 +6,16 @@ import { join } from "node:path";
|
|
|
6
6
|
/**
|
|
7
7
|
* Plugin configuration and shared constants for @fonlan/dsh-model-parameters.
|
|
8
8
|
*
|
|
9
|
-
* The plugin
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* models
|
|
9
|
+
* The plugin's own settings ARE its loader entry config (DSH >= 0.1.7): the
|
|
10
|
+
* profile entry whose id is `model-parameters` (see cordis.patch.yml) is
|
|
11
|
+
* validated with {@link Config}, the resolved document is handed to
|
|
12
|
+
* `apply(ctx, config)`, and the settings page is derived from that schema. The
|
|
13
|
+
* plugin OBSERVES the `llm-pi-ai` namespace: whenever provider models are
|
|
14
|
+
* written there with missing capability fields (which is the norm for a freshly
|
|
15
|
+
* synced gateway), it patches the missing fields from the cached models.dev
|
|
16
|
+
* catalog.
|
|
14
17
|
*/
|
|
15
|
-
/** This plugin's own settings namespace. */
|
|
18
|
+
/** This plugin's own settings namespace: its profile entry id. */
|
|
16
19
|
const PLUGIN_NS = "model-parameters";
|
|
17
20
|
/** The namespace whose models this plugin completes: pi-ai provider profiles. */
|
|
18
21
|
const TARGET_NS = "llm-pi-ai";
|
|
@@ -56,16 +59,17 @@ const REASONING_LEVELS = [
|
|
|
56
59
|
"xhigh",
|
|
57
60
|
"max"
|
|
58
61
|
];
|
|
62
|
+
/** The plugin's `Config` schema, exported for the loader and the settings page. */
|
|
59
63
|
const Config = z.object({
|
|
60
|
-
enabled: z.boolean().default(true),
|
|
61
|
-
ttlDays: z.number().min(1).max(90).default(7),
|
|
62
|
-
fillName: z.boolean().default(true),
|
|
63
|
-
fillContext: z.boolean().default(true),
|
|
64
|
-
fillMaxTokens: z.boolean().default(true),
|
|
65
|
-
fillReasoning: z.boolean().default(true),
|
|
66
|
-
fillInput: z.boolean().default(true),
|
|
67
|
-
providerMap: z.dict(z.string()).default({}),
|
|
68
|
-
officialProviders: z.array(z.string()).default([...DEFAULT_OFFICIAL_PROVIDERS])
|
|
64
|
+
enabled: z.boolean().default(true).volatile(),
|
|
65
|
+
ttlDays: z.number().min(1).max(90).default(7).volatile(),
|
|
66
|
+
fillName: z.boolean().default(true).volatile(),
|
|
67
|
+
fillContext: z.boolean().default(true).volatile(),
|
|
68
|
+
fillMaxTokens: z.boolean().default(true).volatile(),
|
|
69
|
+
fillReasoning: z.boolean().default(true).volatile(),
|
|
70
|
+
fillInput: z.boolean().default(true).volatile(),
|
|
71
|
+
providerMap: z.dict(z.string()).default({}).volatile(),
|
|
72
|
+
officialProviders: z.array(z.string()).default([...DEFAULT_OFFICIAL_PROVIDERS]).volatile()
|
|
69
73
|
});
|
|
70
74
|
const DEFAULT_CONFIG = {
|
|
71
75
|
enabled: true,
|
|
@@ -78,6 +82,44 @@ const DEFAULT_CONFIG = {
|
|
|
78
82
|
providerMap: {},
|
|
79
83
|
officialProviders: [...DEFAULT_OFFICIAL_PROVIDERS]
|
|
80
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* Read a config node that may be a plain value or a live (`.volatile()`) node.
|
|
87
|
+
*
|
|
88
|
+
* A plugin whose Config declares volatile fields receives a reactive config
|
|
89
|
+
* whose fields expose `get()`, while hand-written config objects and tests pass
|
|
90
|
+
* plain values. Both shapes are supported here so the same code path serves the
|
|
91
|
+
* settings page, `cordis.patch.yml`, and the unit tests.
|
|
92
|
+
*/
|
|
93
|
+
function liveValue(node, fallback) {
|
|
94
|
+
if (node === void 0 || node === null) return fallback;
|
|
95
|
+
const getter = node.get;
|
|
96
|
+
if (typeof getter === "function") {
|
|
97
|
+
const value = getter.call(node);
|
|
98
|
+
return value === void 0 ? fallback : value;
|
|
99
|
+
}
|
|
100
|
+
return node;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Project the loader entry config into the plain document shape, per field
|
|
104
|
+
* falling back to {@link DEFAULT_CONFIG}. The config is re-read on EVERY call,
|
|
105
|
+
* so settings-page edits (which the loader swaps into the live cells in place)
|
|
106
|
+
* take effect immediately. Container fields are copied so callers can never
|
|
107
|
+
* mutate the live document through a returned reference.
|
|
108
|
+
*/
|
|
109
|
+
function readConfig(config) {
|
|
110
|
+
const section = config ?? {};
|
|
111
|
+
return {
|
|
112
|
+
enabled: liveValue(section.enabled, DEFAULT_CONFIG.enabled),
|
|
113
|
+
ttlDays: liveValue(section.ttlDays, DEFAULT_CONFIG.ttlDays),
|
|
114
|
+
fillName: liveValue(section.fillName, DEFAULT_CONFIG.fillName),
|
|
115
|
+
fillContext: liveValue(section.fillContext, DEFAULT_CONFIG.fillContext),
|
|
116
|
+
fillMaxTokens: liveValue(section.fillMaxTokens, DEFAULT_CONFIG.fillMaxTokens),
|
|
117
|
+
fillReasoning: liveValue(section.fillReasoning, DEFAULT_CONFIG.fillReasoning),
|
|
118
|
+
fillInput: liveValue(section.fillInput, DEFAULT_CONFIG.fillInput),
|
|
119
|
+
providerMap: { ...liveValue(section.providerMap, DEFAULT_CONFIG.providerMap) },
|
|
120
|
+
officialProviders: [...liveValue(section.officialProviders, DEFAULT_CONFIG.officialProviders)]
|
|
121
|
+
};
|
|
122
|
+
}
|
|
81
123
|
//#endregion
|
|
82
124
|
//#region src/server/catalog.ts
|
|
83
125
|
/**
|
|
@@ -309,6 +351,7 @@ const TARGET = TARGET_NS;
|
|
|
309
351
|
const OWN = PLUGIN_NS;
|
|
310
352
|
var ModelParametersService = class {
|
|
311
353
|
ctx;
|
|
354
|
+
entryConfig;
|
|
312
355
|
/**
|
|
313
356
|
* The settings service value captured from the inject callback. The plugin
|
|
314
357
|
* itself declares `inject: []` (settings is optional — headless profiles
|
|
@@ -317,42 +360,45 @@ var ModelParametersService = class {
|
|
|
317
360
|
* a sibling fiber. The injected child ctx itself is only valid while its
|
|
318
361
|
* fiber is active — the provider can be reloaded (and every dependent
|
|
319
362
|
* fiber torn down) while an async reconcile is in flight — so all reads
|
|
320
|
-
* and writes go through the captured service
|
|
321
|
-
* object and never walks the fiber chain.
|
|
363
|
+
* and writes of the `llm-pi-ai` namespace go through the captured service
|
|
364
|
+
* value, which is a plain object and never walks the fiber chain.
|
|
322
365
|
*/
|
|
323
366
|
settings;
|
|
324
|
-
scope;
|
|
325
367
|
lastReport = null;
|
|
326
368
|
catalog;
|
|
327
369
|
reconciling = null;
|
|
328
|
-
|
|
370
|
+
/**
|
|
371
|
+
* @param ctx the plugin context.
|
|
372
|
+
* @param entryConfig this plugin's own live entry config (the profile entry
|
|
373
|
+
* `model-parameters`): the resolved `Config` document, whose `.volatile()`
|
|
374
|
+
* fields are live cells that the loader swaps in place on every settings
|
|
375
|
+
* write. Read through {@link readConfig} on each access, never cached.
|
|
376
|
+
*/
|
|
377
|
+
constructor(ctx, entryConfig) {
|
|
329
378
|
this.ctx = ctx;
|
|
379
|
+
this.entryConfig = entryConfig;
|
|
330
380
|
this.catalog = new CatalogManager(ctx.logger);
|
|
331
381
|
}
|
|
332
382
|
start() {
|
|
333
383
|
this.ctx.inject(["settings"], (sctx) => {
|
|
334
384
|
this.settings = sctx.settings;
|
|
335
|
-
this.scope = this.settings.register(OWN, Config);
|
|
336
385
|
this.reconcile();
|
|
337
386
|
});
|
|
338
|
-
this.ctx.on("settings/updated", (ns) => {
|
|
387
|
+
this.ctx.on("settings/document-updated", (ns) => {
|
|
339
388
|
if (ns !== TARGET) return;
|
|
340
389
|
this.reconcile();
|
|
341
390
|
});
|
|
342
391
|
}
|
|
343
392
|
stop() {
|
|
344
|
-
this.scope = void 0;
|
|
345
393
|
this.settings = void 0;
|
|
346
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* The plugin's current settings, projected from the live entry config.
|
|
397
|
+
* Works with no settings service at all (headless), which is why the plain
|
|
398
|
+
* {@link DEFAULT_CONFIG} fallback lives in {@link readConfig}.
|
|
399
|
+
*/
|
|
347
400
|
config() {
|
|
348
|
-
|
|
349
|
-
return this.scope.get();
|
|
350
|
-
} catch {}
|
|
351
|
-
return {
|
|
352
|
-
...DEFAULT_CONFIG,
|
|
353
|
-
providerMap: {},
|
|
354
|
-
officialProviders: [...DEFAULT_CONFIG.officialProviders]
|
|
355
|
-
};
|
|
401
|
+
return readConfig(this.entryConfig);
|
|
356
402
|
}
|
|
357
403
|
catalogSnapshot() {
|
|
358
404
|
return this.catalog.snapshotOf();
|
|
@@ -365,14 +411,21 @@ var ModelParametersService = class {
|
|
|
365
411
|
await this.catalog.refresh(this.config().ttlDays);
|
|
366
412
|
await this.reconcile();
|
|
367
413
|
}
|
|
368
|
-
/**
|
|
414
|
+
/**
|
|
415
|
+
* Update the plugin's own settings from the settings page. `dsh-settings`
|
|
416
|
+
* addresses the plugin's loader entry by its id and refuses any path the
|
|
417
|
+
* `Config` schema did not mark volatile — every field here is volatile.
|
|
418
|
+
*/
|
|
369
419
|
async updateConfig(patch) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
420
|
+
const settings = this.settings;
|
|
421
|
+
if (settings === void 0) throw new Error("model-parameters: settings service is not available in this profile");
|
|
422
|
+
const { providerMap, ...rest } = patch;
|
|
423
|
+
if (Object.keys(rest).length > 0) await settings.update(OWN, rest);
|
|
424
|
+
if (providerMap !== void 0) await settings.mutate(OWN, [{
|
|
425
|
+
op: "set",
|
|
426
|
+
path: ["providerMap"],
|
|
427
|
+
value: providerMap
|
|
428
|
+
}]);
|
|
376
429
|
}
|
|
377
430
|
/**
|
|
378
431
|
* Fill missing fields on every provider model from the catalog. Serialized:
|
|
@@ -622,8 +675,8 @@ function stateOf(service) {
|
|
|
622
675
|
//#region src/index.ts
|
|
623
676
|
const name = "@fonlan/dsh-model-parameters";
|
|
624
677
|
const inject = [];
|
|
625
|
-
function apply(ctx) {
|
|
626
|
-
const service = new ModelParametersService(ctx);
|
|
678
|
+
function apply(ctx, config = {}) {
|
|
679
|
+
const service = new ModelParametersService(ctx, config);
|
|
627
680
|
ctx.effect(() => {
|
|
628
681
|
service.start();
|
|
629
682
|
return () => service.stop();
|
package/lib/types/index.d.ts
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
* tokens / reasoning efforts / input modalities (the norm for a freshly
|
|
7
7
|
* synced gateway), it fills the missing fields from a cached models.dev
|
|
8
8
|
* catalog (TTL-refreshed), and serves the fenced JSON API the web settings
|
|
9
|
-
* page calls. The plugin's own toggles
|
|
10
|
-
*
|
|
9
|
+
* page calls. The plugin's own toggles are its loader entry config (the
|
|
10
|
+
* `model-parameters` profile entry, schema {@link Config}).
|
|
11
11
|
*/
|
|
12
12
|
import type { Context } from '@deepseek-ai/cordis';
|
|
13
|
-
import { Config } from './shared/config.js';
|
|
13
|
+
import { Config, type ModelParametersLiveConfig } from './shared/config.js';
|
|
14
14
|
import { CatalogManager, acceptedInputModalities, identityReasoningEfforts, type CatalogEntry, type CatalogSnapshot } from './server/catalog.js';
|
|
15
15
|
export declare const name = "@fonlan/dsh-model-parameters";
|
|
16
16
|
export declare const inject: string[];
|
|
17
17
|
export { Config };
|
|
18
18
|
export { CatalogManager, acceptedInputModalities, identityReasoningEfforts };
|
|
19
19
|
export type { CatalogEntry, CatalogSnapshot };
|
|
20
|
-
export declare function apply(ctx: Context): void;
|
|
20
|
+
export declare function apply(ctx: Context, config?: ModelParametersLiveConfig): void;
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The model-parameters service:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The model-parameters service: completes missing model fields in the
|
|
3
|
+
* `llm-pi-ai` settings namespace from the cached models.dev catalog and tracks
|
|
4
|
+
* a fill report. Its own settings ARE this plugin's loader entry config (see
|
|
5
|
+
* shared/config.ts), so there is no per-plugin settings scope to own here.
|
|
5
6
|
*
|
|
6
7
|
* Trigger model:
|
|
7
8
|
* - any committed `llm-pi-ai` settings change (settings UI apply, manual
|
|
8
|
-
*
|
|
9
|
-
* external writes) runs a reconcile;
|
|
9
|
+
* profile-patch edit, model-router-style external writes) runs a reconcile;
|
|
10
10
|
* - plugin start runs a backfill reconcile over the existing configuration.
|
|
11
11
|
* Reconcile is fill-only: it never overwrites a present field, and it writes
|
|
12
12
|
* nothing when nothing is missing — so its own writes cannot re-trigger a
|
|
13
13
|
* second patch (the next event sees the fields present and produces no ops).
|
|
14
14
|
*/
|
|
15
15
|
import type { Context } from '@deepseek-ai/cordis';
|
|
16
|
-
import { type ModelParametersConfig } from '../shared/config.js';
|
|
16
|
+
import { type ModelParametersConfig, type ModelParametersLiveConfig } from '../shared/config.js';
|
|
17
17
|
import { type CatalogSnapshot } from './catalog.js';
|
|
18
18
|
/** One reconcile run's outcome, surfaced to the settings page. */
|
|
19
19
|
export interface FillReport {
|
|
@@ -27,6 +27,7 @@ export interface FillReport {
|
|
|
27
27
|
}
|
|
28
28
|
export declare class ModelParametersService {
|
|
29
29
|
private readonly ctx;
|
|
30
|
+
private readonly entryConfig;
|
|
30
31
|
/**
|
|
31
32
|
* The settings service value captured from the inject callback. The plugin
|
|
32
33
|
* itself declares `inject: []` (settings is optional — headless profiles
|
|
@@ -35,23 +36,38 @@ export declare class ModelParametersService {
|
|
|
35
36
|
* a sibling fiber. The injected child ctx itself is only valid while its
|
|
36
37
|
* fiber is active — the provider can be reloaded (and every dependent
|
|
37
38
|
* fiber torn down) while an async reconcile is in flight — so all reads
|
|
38
|
-
* and writes go through the captured service
|
|
39
|
-
* object and never walks the fiber chain.
|
|
39
|
+
* and writes of the `llm-pi-ai` namespace go through the captured service
|
|
40
|
+
* value, which is a plain object and never walks the fiber chain.
|
|
40
41
|
*/
|
|
41
42
|
private settings;
|
|
42
|
-
private scope;
|
|
43
43
|
private lastReport;
|
|
44
44
|
private readonly catalog;
|
|
45
45
|
private reconciling;
|
|
46
|
-
|
|
46
|
+
/**
|
|
47
|
+
* @param ctx the plugin context.
|
|
48
|
+
* @param entryConfig this plugin's own live entry config (the profile entry
|
|
49
|
+
* `model-parameters`): the resolved `Config` document, whose `.volatile()`
|
|
50
|
+
* fields are live cells that the loader swaps in place on every settings
|
|
51
|
+
* write. Read through {@link readConfig} on each access, never cached.
|
|
52
|
+
*/
|
|
53
|
+
constructor(ctx: Context, entryConfig: ModelParametersLiveConfig | undefined);
|
|
47
54
|
start(): void;
|
|
48
55
|
stop(): void;
|
|
56
|
+
/**
|
|
57
|
+
* The plugin's current settings, projected from the live entry config.
|
|
58
|
+
* Works with no settings service at all (headless), which is why the plain
|
|
59
|
+
* {@link DEFAULT_CONFIG} fallback lives in {@link readConfig}.
|
|
60
|
+
*/
|
|
49
61
|
config(): ModelParametersConfig;
|
|
50
62
|
catalogSnapshot(): CatalogSnapshot;
|
|
51
63
|
report(): FillReport | null;
|
|
52
64
|
/** Force a catalog refresh, then reconcile and report. */
|
|
53
65
|
refreshAndReconcile(): Promise<void>;
|
|
54
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Update the plugin's own settings from the settings page. `dsh-settings`
|
|
68
|
+
* addresses the plugin's loader entry by its id and refuses any path the
|
|
69
|
+
* `Config` schema did not mark volatile — every field here is volatile.
|
|
70
|
+
*/
|
|
55
71
|
updateConfig(patch: Partial<ModelParametersConfig>): Promise<void>;
|
|
56
72
|
/**
|
|
57
73
|
* Fill missing fields on every provider model from the catalog. Serialized:
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plugin configuration and shared constants for @fonlan/dsh-model-parameters.
|
|
3
3
|
*
|
|
4
|
-
* The plugin
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* models
|
|
4
|
+
* The plugin's own settings ARE its loader entry config (DSH >= 0.1.7): the
|
|
5
|
+
* profile entry whose id is `model-parameters` (see cordis.patch.yml) is
|
|
6
|
+
* validated with {@link Config}, the resolved document is handed to
|
|
7
|
+
* `apply(ctx, config)`, and the settings page is derived from that schema. The
|
|
8
|
+
* plugin OBSERVES the `llm-pi-ai` namespace: whenever provider models are
|
|
9
|
+
* written there with missing capability fields (which is the norm for a freshly
|
|
10
|
+
* synced gateway), it patches the missing fields from the cached models.dev
|
|
11
|
+
* catalog.
|
|
9
12
|
*/
|
|
10
13
|
import z from '@deepseek-ai/schemastery';
|
|
11
|
-
/** This plugin's own settings namespace. */
|
|
14
|
+
/** This plugin's own settings namespace: its profile entry id. */
|
|
12
15
|
export declare const PLUGIN_NS = "model-parameters";
|
|
13
16
|
/** The namespace whose models this plugin completes: pi-ai provider profiles. */
|
|
14
17
|
export declare const TARGET_NS = "llm-pi-ai";
|
|
@@ -28,7 +31,7 @@ export declare const ACCEPTED_INPUT_MODALITIES: readonly string[];
|
|
|
28
31
|
* this set are written.
|
|
29
32
|
*/
|
|
30
33
|
export declare const REASONING_LEVELS: readonly string[];
|
|
31
|
-
/** Plugin configuration shape. */
|
|
34
|
+
/** Plugin configuration shape: the PLAIN document, with no live handles in it. */
|
|
32
35
|
export interface ModelParametersConfig {
|
|
33
36
|
/** Master switch: when off, no fills happen (listener is inert). */
|
|
34
37
|
enabled: boolean;
|
|
@@ -49,5 +52,84 @@ export interface ModelParametersConfig {
|
|
|
49
52
|
/** models.dev provider ids treated as first-party vendors for tie-breaking. */
|
|
50
53
|
officialProviders: string[];
|
|
51
54
|
}
|
|
52
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Recursively readonly value a live handle returns — cosmokit's
|
|
57
|
+
* `VolatileSnapshot<T>`, spelled locally for the same reason as
|
|
58
|
+
* {@link LiveSettingsField}.
|
|
59
|
+
*/
|
|
60
|
+
export type LiveSettingsSnapshot<T> = T extends object ? {
|
|
61
|
+
readonly [K in keyof T]: LiveSettingsSnapshot<T[K]>;
|
|
62
|
+
} : T;
|
|
63
|
+
/**
|
|
64
|
+
* One `volatile()` config field as the loader hands it to `apply()`: a live
|
|
65
|
+
* handle, not the value it currently holds. Spelled structurally rather than as
|
|
66
|
+
* cosmokit's `Volatile<T>` on purpose — cosmokit is a transitive dependency
|
|
67
|
+
* here, and importing it would put a second schemastery/cosmokit pair into the
|
|
68
|
+
* emitted declaration.
|
|
69
|
+
*/
|
|
70
|
+
export interface LiveSettingsField<T> {
|
|
71
|
+
get(): LiveSettingsSnapshot<T>;
|
|
72
|
+
}
|
|
73
|
+
/** A hand-written entry config (profile patch / test fixture): every field is optional. */
|
|
74
|
+
export type ModelParametersConfigInput = {
|
|
75
|
+
enabled?: boolean | null;
|
|
76
|
+
ttlDays?: number | null;
|
|
77
|
+
fillName?: boolean | null;
|
|
78
|
+
fillContext?: boolean | null;
|
|
79
|
+
fillMaxTokens?: boolean | null;
|
|
80
|
+
fillReasoning?: boolean | null;
|
|
81
|
+
fillInput?: boolean | null;
|
|
82
|
+
providerMap?: Record<string, string> | null;
|
|
83
|
+
officialProviders?: string[] | null;
|
|
84
|
+
};
|
|
85
|
+
/** The resolved entry config `apply()` receives: every field is a live handle. */
|
|
86
|
+
export type ResolvedModelParametersConfig = {
|
|
87
|
+
enabled: LiveSettingsField<boolean>;
|
|
88
|
+
ttlDays: LiveSettingsField<number>;
|
|
89
|
+
fillName: LiveSettingsField<boolean>;
|
|
90
|
+
fillContext: LiveSettingsField<boolean>;
|
|
91
|
+
fillMaxTokens: LiveSettingsField<boolean>;
|
|
92
|
+
fillReasoning: LiveSettingsField<boolean>;
|
|
93
|
+
fillInput: LiveSettingsField<boolean>;
|
|
94
|
+
providerMap: LiveSettingsField<Record<string, string>>;
|
|
95
|
+
officialProviders: LiveSettingsField<string[]>;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Entry config schema. Deliberately NOT annotated `z<ModelParametersConfig>`:
|
|
99
|
+
* schemastery >= 3.18.3 carries a third `Mode` type parameter, so a
|
|
100
|
+
* `.volatile()` field's OUTPUT type is `Volatile<T>` (a live `{ get() }` cell),
|
|
101
|
+
* and that annotation no longer compiles. The plain document shape stays
|
|
102
|
+
* {@link ModelParametersConfig} and is what {@link readConfig} projects to.
|
|
103
|
+
*
|
|
104
|
+
* The annotation below names the type through the package's own default export
|
|
105
|
+
* (`z<...>`), with locally declared type arguments: the bare inferred type of a
|
|
106
|
+
* volatile schema resolves through a schemastery copy this package does not
|
|
107
|
+
* import, so `tsc -p tsconfig.build.json` (`declaration: true`) could not name
|
|
108
|
+
* it (TS2742) — the emitted declaration stays portable this way.
|
|
109
|
+
*
|
|
110
|
+
* `.volatile()` is REQUIRED on every field the settings page may write, not
|
|
111
|
+
* cosmetic: `dsh-settings` derives `volatileForm(schema)` from this schema, and
|
|
112
|
+
* with no volatile field at all it SKIPS this entry in `describe()` (the
|
|
113
|
+
* settings page disappears) and REJECTS every write as a non-volatile path.
|
|
114
|
+
* The profile entry id (`model-parameters`, see cordis.patch.yml) is the
|
|
115
|
+
* namespace those writes address.
|
|
116
|
+
*/
|
|
117
|
+
export type ModelParametersConfigSchema = z<ModelParametersConfigInput, ResolvedModelParametersConfig>;
|
|
118
|
+
/** The plugin's `Config` schema, exported for the loader and the settings page. */
|
|
119
|
+
export declare const Config: ModelParametersConfigSchema;
|
|
53
120
|
export declare const DEFAULT_CONFIG: ModelParametersConfig;
|
|
121
|
+
/**
|
|
122
|
+
* The entry config as `apply()` receives it. Every field may arrive either as a
|
|
123
|
+
* live `Volatile` cell or as a plain value (a hand-built config object, a
|
|
124
|
+
* profile patch written before the volatile marker existed, or a host that
|
|
125
|
+
* resolves the document without the wrapper).
|
|
126
|
+
*/
|
|
127
|
+
export type ModelParametersLiveConfig = Partial<Record<keyof ModelParametersConfig, unknown>>;
|
|
128
|
+
/**
|
|
129
|
+
* Project the loader entry config into the plain document shape, per field
|
|
130
|
+
* falling back to {@link DEFAULT_CONFIG}. The config is re-read on EVERY call,
|
|
131
|
+
* so settings-page edits (which the loader swaps into the live cells in place)
|
|
132
|
+
* take effect immediately. Container fields are copied so callers can never
|
|
133
|
+
* mutate the live document through a returned reference.
|
|
134
|
+
*/
|
|
135
|
+
export declare function readConfig(config: ModelParametersLiveConfig | undefined): ModelParametersConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonlan/dsh-model-parameters",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "DSH plugin: when a provider's model sync returns only ids, automatically fill display name, context window, max output tokens, reasoning efforts, and input modalities from the models.dev catalog (cached, TTL-refreshed), and surface a settings page with toggles, refresh, and a fill report.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -69,13 +69,13 @@
|
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
72
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
73
|
-
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
|
|
74
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.2",
|
|
75
|
-
"@deepseek-ai/dsh-llm-pi-ai": "^0.1.2-alpha.2",
|
|
76
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.2",
|
|
77
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.2",
|
|
78
|
-
"@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.2",
|
|
72
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
73
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
|
|
74
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
|
|
75
|
+
"@deepseek-ai/dsh-llm-pi-ai": "^0.1.2-alpha.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
|
|
76
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
|
|
79
79
|
"react": "^18.2.0",
|
|
80
80
|
"react-dom": "^18.2.0"
|
|
81
81
|
},
|
|
@@ -85,14 +85,14 @@
|
|
|
85
85
|
"@types/react-dom": "~18.3.0",
|
|
86
86
|
"tsdown": "^0.22.14",
|
|
87
87
|
"typescript": "^5.9.0",
|
|
88
|
-
"@deepseek-ai/cordis": "
|
|
89
|
-
"@deepseek-ai/schemastery": "
|
|
90
|
-
"@deepseek-ai/dsh-settings": "
|
|
91
|
-
"@deepseek-ai/dsh-host-webserver": "
|
|
92
|
-
"@deepseek-ai/dsh-llm-pi-ai": "
|
|
93
|
-
"@deepseek-ai/dsh-client-locale": "
|
|
94
|
-
"@deepseek-ai/dsh-client-ui-slots": "
|
|
95
|
-
"@deepseek-ai/dsh-client-ui-settings": "
|
|
88
|
+
"@deepseek-ai/cordis": "4.0.5-alpha.1",
|
|
89
|
+
"@deepseek-ai/schemastery": "3.18.5-alpha.1",
|
|
90
|
+
"@deepseek-ai/dsh-settings": "0.2.1-alpha.1",
|
|
91
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.1-alpha.1",
|
|
92
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.2.1-alpha.1",
|
|
93
|
+
"@deepseek-ai/dsh-client-locale": "0.2.1-alpha.1",
|
|
94
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-alpha.1",
|
|
95
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.1-alpha.1"
|
|
96
96
|
},
|
|
97
97
|
"packageManager": "pnpm@11.21.0"
|
|
98
98
|
}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* and the last fill report (filled / touched / unmatched).
|
|
9
9
|
*
|
|
10
10
|
* All reads/mutations go through the plugin's fenced API; config persists into
|
|
11
|
-
* the `model-parameters`
|
|
11
|
+
* the `model-parameters` profile entry — the plugin's own loader entry config,
|
|
12
|
+
* written through the host settings service.
|
|
12
13
|
*/
|
|
13
14
|
import { useCallback, useEffect, useState, useSyncExternalStore } from 'react'
|
|
14
15
|
import type { Context } from '@deepseek-ai/cordis'
|
package/src/index.ts
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* tokens / reasoning efforts / input modalities (the norm for a freshly
|
|
7
7
|
* synced gateway), it fills the missing fields from a cached models.dev
|
|
8
8
|
* catalog (TTL-refreshed), and serves the fenced JSON API the web settings
|
|
9
|
-
* page calls. The plugin's own toggles
|
|
10
|
-
*
|
|
9
|
+
* page calls. The plugin's own toggles are its loader entry config (the
|
|
10
|
+
* `model-parameters` profile entry, schema {@link Config}).
|
|
11
11
|
*/
|
|
12
12
|
import type { Context } from '@deepseek-ai/cordis'
|
|
13
|
-
import { Config } from './shared/config.js'
|
|
13
|
+
import { Config, type ModelParametersLiveConfig } from './shared/config.js'
|
|
14
14
|
import { ModelParametersService } from './server/service.js'
|
|
15
15
|
import { registerApiRoutes } from './server/rpc.js'
|
|
16
16
|
import {
|
|
@@ -32,8 +32,8 @@ export { Config }
|
|
|
32
32
|
export { CatalogManager, acceptedInputModalities, identityReasoningEfforts }
|
|
33
33
|
export type { CatalogEntry, CatalogSnapshot }
|
|
34
34
|
|
|
35
|
-
export function apply(ctx: Context): void {
|
|
36
|
-
const service = new ModelParametersService(ctx)
|
|
35
|
+
export function apply(ctx: Context, config: ModelParametersLiveConfig = {}): void {
|
|
36
|
+
const service = new ModelParametersService(ctx, config)
|
|
37
37
|
|
|
38
38
|
ctx.effect(() => {
|
|
39
39
|
service.start()
|
package/src/server/service.ts
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The model-parameters service:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* The model-parameters service: completes missing model fields in the
|
|
3
|
+
* `llm-pi-ai` settings namespace from the cached models.dev catalog and tracks
|
|
4
|
+
* a fill report. Its own settings ARE this plugin's loader entry config (see
|
|
5
|
+
* shared/config.ts), so there is no per-plugin settings scope to own here.
|
|
5
6
|
*
|
|
6
7
|
* Trigger model:
|
|
7
8
|
* - any committed `llm-pi-ai` settings change (settings UI apply, manual
|
|
8
|
-
*
|
|
9
|
-
* external writes) runs a reconcile;
|
|
9
|
+
* profile-patch edit, model-router-style external writes) runs a reconcile;
|
|
10
10
|
* - plugin start runs a backfill reconcile over the existing configuration.
|
|
11
11
|
* Reconcile is fill-only: it never overwrites a present field, and it writes
|
|
12
12
|
* nothing when nothing is missing — so its own writes cannot re-trigger a
|
|
13
13
|
* second patch (the next event sees the fields present and produces no ops).
|
|
14
14
|
*/
|
|
15
15
|
import type { Context } from '@deepseek-ai/cordis'
|
|
16
|
-
import type {
|
|
16
|
+
import type { SettingsPathOp } from '@deepseek-ai/dsh-settings'
|
|
17
17
|
import type { PiAiModelProfile } from '@deepseek-ai/dsh-llm-pi-ai'
|
|
18
18
|
import {
|
|
19
|
-
Config,
|
|
20
|
-
DEFAULT_CONFIG,
|
|
21
19
|
PLUGIN_NS,
|
|
22
20
|
TARGET_NS,
|
|
21
|
+
readConfig,
|
|
23
22
|
type ModelParametersConfig,
|
|
23
|
+
type ModelParametersLiveConfig,
|
|
24
24
|
} from '../shared/config.js'
|
|
25
25
|
import {
|
|
26
26
|
CatalogManager,
|
|
@@ -53,56 +53,66 @@ export class ModelParametersService {
|
|
|
53
53
|
* a sibling fiber. The injected child ctx itself is only valid while its
|
|
54
54
|
* fiber is active — the provider can be reloaded (and every dependent
|
|
55
55
|
* fiber torn down) while an async reconcile is in flight — so all reads
|
|
56
|
-
* and writes go through the captured service
|
|
57
|
-
* object and never walks the fiber chain.
|
|
56
|
+
* and writes of the `llm-pi-ai` namespace go through the captured service
|
|
57
|
+
* value, which is a plain object and never walks the fiber chain.
|
|
58
58
|
*/
|
|
59
59
|
private settings: Context['settings'] | undefined
|
|
60
|
-
private scope: SettingsScope<ModelParametersConfig> | undefined
|
|
61
60
|
private lastReport: FillReport | null = null
|
|
62
61
|
private readonly catalog: CatalogManager
|
|
63
62
|
private reconciling: Promise<void> | null = null
|
|
64
63
|
|
|
65
|
-
|
|
64
|
+
/**
|
|
65
|
+
* @param ctx the plugin context.
|
|
66
|
+
* @param entryConfig this plugin's own live entry config (the profile entry
|
|
67
|
+
* `model-parameters`): the resolved `Config` document, whose `.volatile()`
|
|
68
|
+
* fields are live cells that the loader swaps in place on every settings
|
|
69
|
+
* write. Read through {@link readConfig} on each access, never cached.
|
|
70
|
+
*/
|
|
71
|
+
constructor(
|
|
72
|
+
private readonly ctx: Context,
|
|
73
|
+
private readonly entryConfig: ModelParametersLiveConfig | undefined,
|
|
74
|
+
) {
|
|
66
75
|
this.catalog = new CatalogManager(ctx.logger)
|
|
67
76
|
}
|
|
68
77
|
|
|
69
78
|
start(): void {
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
79
|
+
// The plugin's own settings ARE this entry config, so nothing is
|
|
80
|
+
// registered here: `config()` projects the live entry config and writes
|
|
81
|
+
// go through the settings service (see updateConfig). The inject callback
|
|
82
|
+
// fires asynchronously once the settings service is available, so the
|
|
83
|
+
// first-run backfill reconcile lives INSIDE it — reconcile needs the
|
|
73
84
|
// settings service (to read the target namespace and persist fills).
|
|
74
85
|
this.ctx.inject(['settings'], (sctx) => {
|
|
75
86
|
this.settings = sctx.settings
|
|
76
|
-
this.scope = this.settings.register(OWN, Config) as SettingsScope<ModelParametersConfig>
|
|
77
87
|
// First-run backfill: ensure a fresh-enough catalog, then fill existing
|
|
78
88
|
// models. Never blocks plugin startup.
|
|
79
89
|
void this.reconcile()
|
|
80
90
|
})
|
|
81
91
|
|
|
82
92
|
// Watch the target namespace: any committed provider-model change may
|
|
83
|
-
// leave fields missing -> reconcile.
|
|
84
|
-
|
|
93
|
+
// leave fields missing -> reconcile. `settings/document-updated` is the
|
|
94
|
+
// committed-change signal (dsh-settings bumps a form revision after the
|
|
95
|
+
// profile patch was written); it also fires for this plugin's own entry,
|
|
96
|
+
// which is filtered out here so a toggle write never triggers a reconcile.
|
|
97
|
+
this.ctx.on('settings/document-updated', (ns) => {
|
|
85
98
|
if (ns !== TARGET) return
|
|
86
99
|
void this.reconcile()
|
|
87
100
|
})
|
|
88
101
|
}
|
|
89
102
|
|
|
90
103
|
stop(): void {
|
|
91
|
-
this.scope = undefined
|
|
92
104
|
this.settings = undefined
|
|
93
105
|
}
|
|
94
106
|
|
|
95
107
|
// ── reads for the settings page ────────────────────────────────────────────
|
|
96
108
|
|
|
109
|
+
/**
|
|
110
|
+
* The plugin's current settings, projected from the live entry config.
|
|
111
|
+
* Works with no settings service at all (headless), which is why the plain
|
|
112
|
+
* {@link DEFAULT_CONFIG} fallback lives in {@link readConfig}.
|
|
113
|
+
*/
|
|
97
114
|
config(): ModelParametersConfig {
|
|
98
|
-
|
|
99
|
-
try {
|
|
100
|
-
return this.scope.get()
|
|
101
|
-
} catch {
|
|
102
|
-
// fall through to defaults
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return { ...DEFAULT_CONFIG, providerMap: {}, officialProviders: [...DEFAULT_CONFIG.officialProviders] }
|
|
115
|
+
return readConfig(this.entryConfig)
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
catalogSnapshot(): CatalogSnapshot {
|
|
@@ -119,13 +129,29 @@ export class ModelParametersService {
|
|
|
119
129
|
await this.reconcile()
|
|
120
130
|
}
|
|
121
131
|
|
|
122
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Update the plugin's own settings from the settings page. `dsh-settings`
|
|
134
|
+
* addresses the plugin's loader entry by its id and refuses any path the
|
|
135
|
+
* `Config` schema did not mark volatile — every field here is volatile.
|
|
136
|
+
*/
|
|
123
137
|
async updateConfig(patch: Partial<ModelParametersConfig>): Promise<void> {
|
|
124
|
-
|
|
138
|
+
const settings = this.settings
|
|
139
|
+
if (settings === undefined) {
|
|
125
140
|
throw new Error('model-parameters: settings service is not available in this profile')
|
|
126
141
|
}
|
|
127
|
-
|
|
128
|
-
|
|
142
|
+
// `update` MERGES the patch into the entry's stored section — the
|
|
143
|
+
// "merge a partial into the current config" contract the page has always
|
|
144
|
+
// used. `providerMap` is the one plain OBJECT the page re-sends whole (a
|
|
145
|
+
// removed mapping row must disappear), and a deep merge would resurrect
|
|
146
|
+
// the removed key, so that field rides a path `set` op instead: `mutate`
|
|
147
|
+
// delegates to the same `write()` and replaces the value at the path
|
|
148
|
+
// wholesale. Arrays (`officialProviders`) are replaced by the merge
|
|
149
|
+
// itself, since the merge only recurses into plain objects.
|
|
150
|
+
const { providerMap, ...rest } = patch
|
|
151
|
+
if (Object.keys(rest).length > 0) await settings.update(OWN, rest)
|
|
152
|
+
if (providerMap !== undefined) {
|
|
153
|
+
await settings.mutate(OWN, [{ op: 'set', path: ['providerMap'], value: providerMap }])
|
|
154
|
+
}
|
|
129
155
|
}
|
|
130
156
|
|
|
131
157
|
// ── reconcile ──────────────────────────────────────────────────────────────
|
|
@@ -158,7 +184,7 @@ export class ModelParametersService {
|
|
|
158
184
|
// Operate on the RAW user section (descriptor.user), not the resolved
|
|
159
185
|
// value: the resolved section is schema-normalized (pi-ai's loose object
|
|
160
186
|
// resolution adds empty `input`/`compat` defaults), and persisting that
|
|
161
|
-
// shape would leak schema-default noise into
|
|
187
|
+
// shape would leak schema-default noise into the profile patch. The raw
|
|
162
188
|
// section is exactly what the user document stores.
|
|
163
189
|
const descriptor = settings.describe({ redactSecrets: false }).find((candidate) => candidate.ns === TARGET)
|
|
164
190
|
const section = descriptor?.user as { providers?: Record<string, { models?: PiAiModelProfile[] }> } | undefined
|
package/src/shared/config.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plugin configuration and shared constants for @fonlan/dsh-model-parameters.
|
|
3
3
|
*
|
|
4
|
-
* The plugin
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* models
|
|
4
|
+
* The plugin's own settings ARE its loader entry config (DSH >= 0.1.7): the
|
|
5
|
+
* profile entry whose id is `model-parameters` (see cordis.patch.yml) is
|
|
6
|
+
* validated with {@link Config}, the resolved document is handed to
|
|
7
|
+
* `apply(ctx, config)`, and the settings page is derived from that schema. The
|
|
8
|
+
* plugin OBSERVES the `llm-pi-ai` namespace: whenever provider models are
|
|
9
|
+
* written there with missing capability fields (which is the norm for a freshly
|
|
10
|
+
* synced gateway), it patches the missing fields from the cached models.dev
|
|
11
|
+
* catalog.
|
|
9
12
|
*/
|
|
10
13
|
import z from '@deepseek-ai/schemastery'
|
|
11
14
|
|
|
12
|
-
/** This plugin's own settings namespace. */
|
|
15
|
+
/** This plugin's own settings namespace: its profile entry id. */
|
|
13
16
|
export const PLUGIN_NS = 'model-parameters'
|
|
14
17
|
|
|
15
18
|
/** The namespace whose models this plugin completes: pi-ai provider profiles. */
|
|
@@ -59,7 +62,7 @@ export const REASONING_LEVELS: readonly string[] = [
|
|
|
59
62
|
'max',
|
|
60
63
|
]
|
|
61
64
|
|
|
62
|
-
/** Plugin configuration shape. */
|
|
65
|
+
/** Plugin configuration shape: the PLAIN document, with no live handles in it. */
|
|
63
66
|
export interface ModelParametersConfig {
|
|
64
67
|
/** Master switch: when off, no fills happen (listener is inert). */
|
|
65
68
|
enabled: boolean
|
|
@@ -81,16 +84,88 @@ export interface ModelParametersConfig {
|
|
|
81
84
|
officialProviders: string[]
|
|
82
85
|
}
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Recursively readonly value a live handle returns — cosmokit's
|
|
89
|
+
* `VolatileSnapshot<T>`, spelled locally for the same reason as
|
|
90
|
+
* {@link LiveSettingsField}.
|
|
91
|
+
*/
|
|
92
|
+
export type LiveSettingsSnapshot<T> = T extends object
|
|
93
|
+
? { readonly [K in keyof T]: LiveSettingsSnapshot<T[K]> }
|
|
94
|
+
: T
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* One `volatile()` config field as the loader hands it to `apply()`: a live
|
|
98
|
+
* handle, not the value it currently holds. Spelled structurally rather than as
|
|
99
|
+
* cosmokit's `Volatile<T>` on purpose — cosmokit is a transitive dependency
|
|
100
|
+
* here, and importing it would put a second schemastery/cosmokit pair into the
|
|
101
|
+
* emitted declaration.
|
|
102
|
+
*/
|
|
103
|
+
export interface LiveSettingsField<T> {
|
|
104
|
+
get(): LiveSettingsSnapshot<T>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A hand-written entry config (profile patch / test fixture): every field is optional. */
|
|
108
|
+
export type ModelParametersConfigInput = {
|
|
109
|
+
enabled?: boolean | null
|
|
110
|
+
ttlDays?: number | null
|
|
111
|
+
fillName?: boolean | null
|
|
112
|
+
fillContext?: boolean | null
|
|
113
|
+
fillMaxTokens?: boolean | null
|
|
114
|
+
fillReasoning?: boolean | null
|
|
115
|
+
fillInput?: boolean | null
|
|
116
|
+
providerMap?: Record<string, string> | null
|
|
117
|
+
officialProviders?: string[] | null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The resolved entry config `apply()` receives: every field is a live handle. */
|
|
121
|
+
export type ResolvedModelParametersConfig = {
|
|
122
|
+
enabled: LiveSettingsField<boolean>
|
|
123
|
+
ttlDays: LiveSettingsField<number>
|
|
124
|
+
fillName: LiveSettingsField<boolean>
|
|
125
|
+
fillContext: LiveSettingsField<boolean>
|
|
126
|
+
fillMaxTokens: LiveSettingsField<boolean>
|
|
127
|
+
fillReasoning: LiveSettingsField<boolean>
|
|
128
|
+
fillInput: LiveSettingsField<boolean>
|
|
129
|
+
providerMap: LiveSettingsField<Record<string, string>>
|
|
130
|
+
officialProviders: LiveSettingsField<string[]>
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Entry config schema. Deliberately NOT annotated `z<ModelParametersConfig>`:
|
|
135
|
+
* schemastery >= 3.18.3 carries a third `Mode` type parameter, so a
|
|
136
|
+
* `.volatile()` field's OUTPUT type is `Volatile<T>` (a live `{ get() }` cell),
|
|
137
|
+
* and that annotation no longer compiles. The plain document shape stays
|
|
138
|
+
* {@link ModelParametersConfig} and is what {@link readConfig} projects to.
|
|
139
|
+
*
|
|
140
|
+
* The annotation below names the type through the package's own default export
|
|
141
|
+
* (`z<...>`), with locally declared type arguments: the bare inferred type of a
|
|
142
|
+
* volatile schema resolves through a schemastery copy this package does not
|
|
143
|
+
* import, so `tsc -p tsconfig.build.json` (`declaration: true`) could not name
|
|
144
|
+
* it (TS2742) — the emitted declaration stays portable this way.
|
|
145
|
+
*
|
|
146
|
+
* `.volatile()` is REQUIRED on every field the settings page may write, not
|
|
147
|
+
* cosmetic: `dsh-settings` derives `volatileForm(schema)` from this schema, and
|
|
148
|
+
* with no volatile field at all it SKIPS this entry in `describe()` (the
|
|
149
|
+
* settings page disappears) and REJECTS every write as a non-volatile path.
|
|
150
|
+
* The profile entry id (`model-parameters`, see cordis.patch.yml) is the
|
|
151
|
+
* namespace those writes address.
|
|
152
|
+
*/
|
|
153
|
+
export type ModelParametersConfigSchema = z<
|
|
154
|
+
ModelParametersConfigInput,
|
|
155
|
+
ResolvedModelParametersConfig
|
|
156
|
+
>
|
|
157
|
+
|
|
158
|
+
/** The plugin's `Config` schema, exported for the loader and the settings page. */
|
|
159
|
+
export const Config: ModelParametersConfigSchema = z.object({
|
|
160
|
+
enabled: z.boolean().default(true).volatile(),
|
|
161
|
+
ttlDays: z.number().min(1).max(90).default(7).volatile(),
|
|
162
|
+
fillName: z.boolean().default(true).volatile(),
|
|
163
|
+
fillContext: z.boolean().default(true).volatile(),
|
|
164
|
+
fillMaxTokens: z.boolean().default(true).volatile(),
|
|
165
|
+
fillReasoning: z.boolean().default(true).volatile(),
|
|
166
|
+
fillInput: z.boolean().default(true).volatile(),
|
|
167
|
+
providerMap: z.dict(z.string()).default({}).volatile(),
|
|
168
|
+
officialProviders: z.array(z.string()).default([...DEFAULT_OFFICIAL_PROVIDERS]).volatile(),
|
|
94
169
|
})
|
|
95
170
|
|
|
96
171
|
export const DEFAULT_CONFIG: ModelParametersConfig = {
|
|
@@ -104,3 +179,51 @@ export const DEFAULT_CONFIG: ModelParametersConfig = {
|
|
|
104
179
|
providerMap: {},
|
|
105
180
|
officialProviders: [...DEFAULT_OFFICIAL_PROVIDERS],
|
|
106
181
|
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The entry config as `apply()` receives it. Every field may arrive either as a
|
|
185
|
+
* live `Volatile` cell or as a plain value (a hand-built config object, a
|
|
186
|
+
* profile patch written before the volatile marker existed, or a host that
|
|
187
|
+
* resolves the document without the wrapper).
|
|
188
|
+
*/
|
|
189
|
+
export type ModelParametersLiveConfig = Partial<Record<keyof ModelParametersConfig, unknown>>
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Read a config node that may be a plain value or a live (`.volatile()`) node.
|
|
193
|
+
*
|
|
194
|
+
* A plugin whose Config declares volatile fields receives a reactive config
|
|
195
|
+
* whose fields expose `get()`, while hand-written config objects and tests pass
|
|
196
|
+
* plain values. Both shapes are supported here so the same code path serves the
|
|
197
|
+
* settings page, `cordis.patch.yml`, and the unit tests.
|
|
198
|
+
*/
|
|
199
|
+
function liveValue<T>(node: unknown, fallback: T): T {
|
|
200
|
+
if (node === undefined || node === null) return fallback
|
|
201
|
+
const getter = (node as { get?: unknown }).get
|
|
202
|
+
if (typeof getter === 'function') {
|
|
203
|
+
const value = (getter as () => unknown).call(node)
|
|
204
|
+
return value === undefined ? fallback : (value as T)
|
|
205
|
+
}
|
|
206
|
+
return node as T
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Project the loader entry config into the plain document shape, per field
|
|
211
|
+
* falling back to {@link DEFAULT_CONFIG}. The config is re-read on EVERY call,
|
|
212
|
+
* so settings-page edits (which the loader swaps into the live cells in place)
|
|
213
|
+
* take effect immediately. Container fields are copied so callers can never
|
|
214
|
+
* mutate the live document through a returned reference.
|
|
215
|
+
*/
|
|
216
|
+
export function readConfig(config: ModelParametersLiveConfig | undefined): ModelParametersConfig {
|
|
217
|
+
const section = config ?? {}
|
|
218
|
+
return {
|
|
219
|
+
enabled: liveValue(section.enabled, DEFAULT_CONFIG.enabled),
|
|
220
|
+
ttlDays: liveValue(section.ttlDays, DEFAULT_CONFIG.ttlDays),
|
|
221
|
+
fillName: liveValue(section.fillName, DEFAULT_CONFIG.fillName),
|
|
222
|
+
fillContext: liveValue(section.fillContext, DEFAULT_CONFIG.fillContext),
|
|
223
|
+
fillMaxTokens: liveValue(section.fillMaxTokens, DEFAULT_CONFIG.fillMaxTokens),
|
|
224
|
+
fillReasoning: liveValue(section.fillReasoning, DEFAULT_CONFIG.fillReasoning),
|
|
225
|
+
fillInput: liveValue(section.fillInput, DEFAULT_CONFIG.fillInput),
|
|
226
|
+
providerMap: { ...liveValue<Record<string, string>>(section.providerMap, DEFAULT_CONFIG.providerMap) },
|
|
227
|
+
officialProviders: [...liveValue<readonly string[]>(section.officialProviders, DEFAULT_CONFIG.officialProviders)],
|
|
228
|
+
}
|
|
229
|
+
}
|