dsh-model-params 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.md +6 -2
- package/lib/client.js +242 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## English
|
|
4
4
|
|
|
5
|
-
**Current release: 0.1.
|
|
5
|
+
**Current release: 0.1.6** — Portable settings transport: the plugin now resolves the official pi-ai settings scope on both the `settingsScope` (≤ 0.1.5) and `configForms` (≥ 0.1.7-rc.1) hosts, so the provider card keeps working across the rename.
|
|
6
6
|
|
|
7
7
|
For every configured `llm-pi-ai` provider card (custom OpenAI-compatible gateways such as the ones you add under Settings → Models), a `models.dev 参数` control fetches the official models.dev records for the provider's configured model ids and proposes the metadata pi-ai needs: context window, max output tokens, and reasoning-effort levels. One click writes the merged `models` array back to that provider profile through the official revision-aware Settings API.
|
|
8
8
|
|
|
9
|
+
On hosts that declare the root-scope `settings.section` list seat, that same card is also registered as a first-class settings page — identity `YOTK · Model Params`, id `yotk-model-params`, order `63` — additive beside the Models-page extension and never an activation gate. The page needs nothing selected elsewhere: it reads the `llm-pi-ai` providers you have configured through the same settings scope, addresses the first one (with a picker whenever several exist), and states plainly when the namespace is loading, unserved, or has no provider yet.
|
|
10
|
+
|
|
9
11
|
Safety boundaries:
|
|
10
12
|
|
|
11
13
|
- The control never auto-writes. You open the panel, review the per-model diff, then click **应用并写入该 provider** — it writes only that provider's `models` array in the `llm-pi-ai` namespace.
|
|
@@ -20,6 +22,8 @@ models.dev model ids may collide across catalog providers; the matcher prefers t
|
|
|
20
22
|
|
|
21
23
|
面向 DeepSeek Harness Web 官方 **设置 → 模型** 页的 models.dev 参数助手。每个已配置的 `llm-pi-ai` provider 卡片内提供 `models.dev 参数` 入口:按该 provider 的模型 id 在 models.dev 官方目录中匹配,预览上下文窗口 / max 输出 / 推理档位,确认后一次性写回该 provider 的 `models` 配置(官方 revision-aware Settings API,仅写 `llm-pi-ai` 命名空间下当前 provider 的数组)。
|
|
22
24
|
|
|
25
|
+
在声明根级 `settings.section` 列表座的宿主上,同一张卡片还会注册为设置页的一级入口 —— 标识 `YOTK · Model Params`、id `yotk-model-params`、order `63` —— 与「设置 → 模型」页的扩展并存,且从不成为激活门槛。该页不依赖别处选中的 provider:它通过同一设置作用域读取已配置的 `llm-pi-ai` provider,默认处理第一个(配置多个时可在页内切换),并在命名空间仍在读取、宿主未提供或尚未配置 provider 时明确说明。
|
|
26
|
+
|
|
23
27
|
- 默认仅补缺失参数;勾选「覆盖已有值」才替换差异值。
|
|
24
28
|
- 窗口顶部可启用一个所有 provider 共用的 models.dev 请求代理并编辑地址;它只影响下一次参数查询,不改变任何 LLM provider 的 `baseURL`。
|
|
25
29
|
- Host 只负责拉取并缓存 models.dev 目录(6 小时,支持公共代理或 `HTTPS_PROXY` 回退),不接触任何 LLM 配置与凭据。
|
|
@@ -31,7 +35,7 @@ models.dev model ids may collide across catalog providers; the matcher prefers t
|
|
|
31
35
|
dsh plugin --profile web add dsh-model-params
|
|
32
36
|
```
|
|
33
37
|
|
|
34
|
-
Restart the existing DSH Web process afterwards: the Host scans the browser plugin roster at startup, so the provider-card control appears only after that restart. Then open **Settings → Models** and look for `models.dev 参数` on a configured provider card.
|
|
38
|
+
Restart the existing DSH Web process afterwards: the Host scans the browser plugin roster at startup, so the provider-card control appears only after that restart. Then open **Settings → Models** and look for `models.dev 参数` on a configured provider card. Where the host also exposes the settings page seat, the same control is one click deep in **Settings** as **YOTK · Model Params**.
|
|
35
39
|
|
|
36
40
|
Local development, from this package directory:
|
|
37
41
|
|
package/lib/client.js
CHANGED
|
@@ -3,12 +3,18 @@
|
|
|
3
3
|
// Renders a compact "models.dev 参数" affordance inside every official
|
|
4
4
|
// llm-pi-ai provider card on the Models settings page
|
|
5
5
|
// (settings.models.provider-card, keyed by the pi-ai settings namespace).
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
6
|
+
// The same card is ALSO registered on the root-scope `settings.section` list
|
|
7
|
+
// seat (id `yotk-model-params`), which makes it a first-class settings page of
|
|
8
|
+
// its own on hosts that declare that seat. That seat's owner share is
|
|
9
|
+
// `{ close }` alone, so the page is handed no provider: it resolves one of the
|
|
10
|
+
// routes the llm-pi-ai namespace itself declares, from the same scope and
|
|
11
|
+
// never through a second read path.
|
|
12
|
+
// Either way it reads the current provider profile from the official llm-pi-ai
|
|
13
|
+
// Settings scope, asks the Host for the matching models.dev records, previews the
|
|
14
|
+
// proposed context / max-output / reasoning-effort values per model, and — only
|
|
15
|
+
// on an explicit "应用" click — writes the merged models array back through the
|
|
16
|
+
// revision-aware official Settings API. Default policy fills missing parameters
|
|
17
|
+
// only; "覆盖已有值" also replaces differing ones.
|
|
12
18
|
// The plugin never auto-writes, never touches other providers/namespaces,
|
|
13
19
|
// and never sends credentials anywhere.
|
|
14
20
|
|
|
@@ -22,6 +28,30 @@ window.__ModuleLoader__.load({
|
|
|
22
28
|
|
|
23
29
|
const PI_NS = 'llm-pi-ai'
|
|
24
30
|
const API = '/api/model-params/lookup'
|
|
31
|
+
|
|
32
|
+
// ── settings-scope portability (DSH 0.1.5 ↔ 0.1.7-rc.1) ──────────────────
|
|
33
|
+
//
|
|
34
|
+
// This plugin owns no settings namespace: it extends the OFFICIAL pi-ai
|
|
35
|
+
// Models page and writes that provider's own `llm-pi-ai` section. The two
|
|
36
|
+
// supported hosts expose the same scope CONTRACT under different names:
|
|
37
|
+
//
|
|
38
|
+
// ≤ 0.1.5 settingsScope.bind({ namespace }) → SettingsScope<T>
|
|
39
|
+
// ≥ 0.1.7 configForms.get(entryId) → ConfigForm<T>
|
|
40
|
+
//
|
|
41
|
+
// Both return getSnapshot()/subscribe()/set()/unset()/mutate() over the same
|
|
42
|
+
// { status, value, base, user, revision, writable, mode } snapshot, so the
|
|
43
|
+
// resolved object is used unchanged by the provider card. On 0.1.7-rc.1 a
|
|
44
|
+
// form's namespace IS the profile entry id, and the pi-ai LLM entry is
|
|
45
|
+
// mounted as `llm-pi-ai`, so the same key resolves on either host.
|
|
46
|
+
// `ctx.get` (never a direct property read) keeps the lookup safe when only
|
|
47
|
+
// one service is mounted: an uninjected property read throws.
|
|
48
|
+
function resolveSettingsScopeFrom(ctx, namespace) {
|
|
49
|
+
const binder = ctx.get('settingsScope')
|
|
50
|
+
if (binder && typeof binder.bind === 'function') return binder.bind({ namespace })
|
|
51
|
+
const forms = ctx.get('configForms')
|
|
52
|
+
if (forms && typeof forms.get === 'function') return forms.get(namespace)
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
|
25
55
|
const FETCH_TIMEOUT_MS = 20000
|
|
26
56
|
const CSS_ID = 'dsh-model-params/style'
|
|
27
57
|
const LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']
|
|
@@ -40,6 +70,8 @@ window.__ModuleLoader__.load({
|
|
|
40
70
|
'.dpmProxy{display:flex;flex-direction:column;gap:6px;padding:7px 8px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2)}',
|
|
41
71
|
'.dpmProxy label{display:flex;align-items:center;gap:6px;color:var(--dsw-alias-label-secondary);cursor:pointer}',
|
|
42
72
|
'.dpmProxy input[type=text]{width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:4px 6px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:11px}',
|
|
73
|
+
'.dpmRoute{display:flex;align-items:center;gap:6px;color:var(--dsw-alias-label-secondary)}',
|
|
74
|
+
'.dpmRoute select{font:inherit;font-size:11px;max-width:100%;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;padding:3px 6px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}',
|
|
43
75
|
'.dpmHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}',
|
|
44
76
|
'.dpmErr{color:var(--dsw-alias-state-error-primary);margin:0;font-size:11px;word-break:break-all}',
|
|
45
77
|
'.dpmList{display:flex;flex-direction:column;gap:6px}',
|
|
@@ -71,6 +103,48 @@ window.__ModuleLoader__.load({
|
|
|
71
103
|
const clean = (value) => typeof value === 'string' && value.trim() ? value.trim() : undefined
|
|
72
104
|
const intersectLevels = (values) => (values || []).filter((value) => LEVELS.includes(value))
|
|
73
105
|
|
|
106
|
+
// ── the section page's own provider (the `settings.section` seat) ─────────
|
|
107
|
+
//
|
|
108
|
+
// The root-scope section seat's owner share is `{ close }` ALONE
|
|
109
|
+
// (SettingsSectionOwnerProps), so that page is handed NO provider while its
|
|
110
|
+
// Models-page sibling receives one per card. It therefore resolves its
|
|
111
|
+
// provider from the ONE settings transport this package already reads: the
|
|
112
|
+
// `llm-pi-ai` section itself, whose `providers` dict IS keyed by provider
|
|
113
|
+
// route (PiAiProviderProfile). Every route this page addresses is one the
|
|
114
|
+
// namespace really declares — never a synthesized id, never a second read or
|
|
115
|
+
// write path — and the profile supplies the real display name when it has one.
|
|
116
|
+
function piAiValue(snapshot) {
|
|
117
|
+
return snapshot && snapshot.status === 'ready' && snapshot.value && typeof snapshot.value === 'object' ? snapshot.value : undefined
|
|
118
|
+
}
|
|
119
|
+
function piAiRoutes(snapshot) {
|
|
120
|
+
const value = piAiValue(snapshot)
|
|
121
|
+
const providers = value && value.providers && typeof value.providers === 'object' && !Array.isArray(value.providers) ? value.providers : undefined
|
|
122
|
+
return providers ? Object.keys(providers).filter((route) => typeof route === 'string' && route.length > 0) : []
|
|
123
|
+
}
|
|
124
|
+
function piAiProfile(snapshot, route) {
|
|
125
|
+
const value = piAiValue(snapshot)
|
|
126
|
+
const profile = value && value.providers && typeof value.providers === 'object' ? value.providers[route] : undefined
|
|
127
|
+
return profile && typeof profile === 'object' ? profile : undefined
|
|
128
|
+
}
|
|
129
|
+
function piAiModels(snapshot, route) {
|
|
130
|
+
const profile = piAiProfile(snapshot, route)
|
|
131
|
+
return profile && Array.isArray(profile.models) ? profile.models : []
|
|
132
|
+
}
|
|
133
|
+
function piAiDisplayName(snapshot, route) {
|
|
134
|
+
const profile = piAiProfile(snapshot, route)
|
|
135
|
+
return (profile && clean(profile.displayName)) || route
|
|
136
|
+
}
|
|
137
|
+
// What the page says when it has no card to draw. A namespace still loading, a
|
|
138
|
+
// namespace this host does not serve, and a namespace with no provider
|
|
139
|
+
// configured are three different facts, and none may look like a blank page.
|
|
140
|
+
function sectionPageHint(snapshot, scope) {
|
|
141
|
+
if (!scope) return '设置传输不可用:本机未挂载 settingsScope / configForms,无法读写 llm-pi-ai 配置。'
|
|
142
|
+
const status = snapshot && snapshot.status
|
|
143
|
+
if (status === 'loading') return '正在读取 llm-pi-ai 设置…'
|
|
144
|
+
if (status !== 'ready') return '当前 Host 未提供 llm-pi-ai 设置,无法读写模型参数。'
|
|
145
|
+
return 'llm-pi-ai 命名空间下还没有已配置的 provider;请先在 设置 → 模型 中添加一个自定义 provider。'
|
|
146
|
+
}
|
|
147
|
+
|
|
74
148
|
function paramLine(label, value) {
|
|
75
149
|
if (value === undefined || value === null || value === '') return null
|
|
76
150
|
return e('span', { key: label }, label + ': ' + value)
|
|
@@ -79,11 +153,23 @@ window.__ModuleLoader__.load({
|
|
|
79
153
|
function ModelParamsExtension(props) {
|
|
80
154
|
const row = props.provider
|
|
81
155
|
const entry = row && row.entry && typeof row.entry === 'object' ? row.entry : row
|
|
82
|
-
|
|
83
|
-
|
|
156
|
+
// The provider the Models page hands this card through the seat's own owner
|
|
157
|
+
// props. The section seat hands NONE (`{ close }` is its whole owner share),
|
|
158
|
+
// so there this card IS the page and picks its own provider from the routes
|
|
159
|
+
// the namespace declares — see the resolvers above.
|
|
160
|
+
const seatRoute = entry && (entry.provider || row && row.provider)
|
|
84
161
|
const api = props.api
|
|
85
162
|
const scope = props.scope
|
|
86
|
-
|
|
163
|
+
// The panel starts COLLAPSED on the Models-page provider card, which renders
|
|
164
|
+
// one control per provider beside the others; it starts EXPANDED wherever the
|
|
165
|
+
// card is the whole page (`settings.section` passes `defaultOpen: true`), so
|
|
166
|
+
// the single-card seat opens with its content already visible. The trigger
|
|
167
|
+
// button below keeps folding it either way.
|
|
168
|
+
const [open, setOpen] = React.useState(props.defaultOpen === true)
|
|
169
|
+
// Which real route this card addresses on the page seat. Ignored while a
|
|
170
|
+
// provider arrives from the host, because a provider CARD is bound to the
|
|
171
|
+
// row it extends and must never follow a module-level selection.
|
|
172
|
+
const [picked, setPicked] = React.useState('')
|
|
87
173
|
const [busy, setBusy] = React.useState(false)
|
|
88
174
|
const [failed, setFailed] = React.useState('')
|
|
89
175
|
const [saved, setSaved] = React.useState('')
|
|
@@ -94,7 +180,22 @@ window.__ModuleLoader__.load({
|
|
|
94
180
|
proxyState.listeners.add(rerender)
|
|
95
181
|
return () => proxyState.listeners.delete(rerender)
|
|
96
182
|
}, [])
|
|
97
|
-
|
|
183
|
+
const [, refreshScope] = React.useState(0)
|
|
184
|
+
// The page's only reason to re-render beyond its own clicks: the namespace
|
|
185
|
+
// may still be loading at first paint, and a provider added in 设置 → 模型
|
|
186
|
+
// must appear here without a reload. ONE subscription on the same transport,
|
|
187
|
+
// disposed with the card.
|
|
188
|
+
React.useEffect(() => {
|
|
189
|
+
if (!scope || typeof scope.subscribe !== 'function') return undefined
|
|
190
|
+
const unsubscribe = scope.subscribe(() => refreshScope((value) => value + 1))
|
|
191
|
+
return () => { if (typeof unsubscribe === 'function') unsubscribe() }
|
|
192
|
+
}, [scope])
|
|
193
|
+
// A lookup answer is stamped with the route it was requested for: this card
|
|
194
|
+
// can change its addressed provider (the page seat's chooser, or the
|
|
195
|
+
// namespace dropping the picked route) while a request is in flight, and the
|
|
196
|
+
// previous provider's matches must never be previewed beside this one's
|
|
197
|
+
// models.
|
|
198
|
+
const [rawResult, setRawResult] = React.useState(null) // { route, matched: [], unmatched: [] }
|
|
98
199
|
const ref = React.useRef(null)
|
|
99
200
|
|
|
100
201
|
React.useEffect(() => {
|
|
@@ -109,29 +210,47 @@ window.__ModuleLoader__.load({
|
|
|
109
210
|
}
|
|
110
211
|
}, [open])
|
|
111
212
|
|
|
112
|
-
if (!route || settingsNs !== PI_NS) return null
|
|
113
|
-
|
|
114
213
|
const snap = scope && typeof scope.getSnapshot === 'function' ? scope.getSnapshot() : { status: 'unavailable' }
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
214
|
+
// Two seats, ONE card. Where the host supplies a provider, this is the
|
|
215
|
+
// per-provider control it always was. Where none arrives (the
|
|
216
|
+
// `settings.section` page), this card is the page: it addresses the first
|
|
217
|
+
// route the llm-pi-ai namespace declares, or the one the user picked from the
|
|
218
|
+
// namespace's real route list.
|
|
219
|
+
const sectionSeat = !seatRoute
|
|
220
|
+
const routes = sectionSeat ? piAiRoutes(snap) : []
|
|
221
|
+
const route = sectionSeat ? (routes.includes(picked) ? picked : routes[0]) : seatRoute
|
|
222
|
+
const settingsNs = sectionSeat ? PI_NS : entry && entry.settingsNs
|
|
223
|
+
if (!route || settingsNs !== PI_NS) {
|
|
224
|
+
// A provider CARD whose namespace is not ours renders nothing (the seat
|
|
225
|
+
// dispatches by settingsNs, so this is defensive). The page seat owns the
|
|
226
|
+
// whole surface instead, so it always explains what it cannot draw rather
|
|
227
|
+
// than showing an empty page.
|
|
228
|
+
if (!sectionSeat) return null
|
|
229
|
+
return e('div', { className: 'dpmWrap' }, e('p', { className: 'dpmHint' }, sectionPageHint(snap, scope)))
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const models = piAiModels(snap, route)
|
|
233
|
+
// Only this route's answer is this route's preview.
|
|
234
|
+
const result = rawResult && rawResult.route === route ? rawResult : null
|
|
118
235
|
const writable = snap.status === 'ready' && snap.writable === true && !!api && !!api.settings
|
|
119
236
|
|
|
120
|
-
async function load() {
|
|
121
|
-
|
|
237
|
+
async function load(routeOverride) {
|
|
238
|
+
const targetRoute = routeOverride || route
|
|
239
|
+
const targetModels = routeOverride ? piAiModels(snap, routeOverride) : models
|
|
240
|
+
if (!targetModels.length || busy) return
|
|
122
241
|
setBusy(true)
|
|
123
242
|
setFailed('')
|
|
124
243
|
setSaved('')
|
|
125
|
-
|
|
244
|
+
setRawResult(null)
|
|
126
245
|
const controller = new AbortController()
|
|
127
246
|
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS)
|
|
128
247
|
try {
|
|
129
|
-
const ids =
|
|
248
|
+
const ids = targetModels.map((model) => model.id).filter(Boolean)
|
|
130
249
|
const query = '?ids=' + encodeURIComponent(ids.join(',')) + (proxyState.enabled ? '&proxy=' + encodeURIComponent((proxyState.url.trim() || PROXY_DEFAULT)) : '')
|
|
131
250
|
const response = await fetch(API + query, { cache: 'no-store', signal: controller.signal })
|
|
132
251
|
const data = await response.json()
|
|
133
252
|
if (!data || data.ok !== true) throw new Error((data && data.error) || 'lookup failed')
|
|
134
|
-
|
|
253
|
+
setRawResult({ route: targetRoute, matched: data.matched || [], unmatched: data.unmatched || [] })
|
|
135
254
|
} catch (error) {
|
|
136
255
|
setFailed(String((error && error.name === 'AbortError') ? '请求超时' : (error && error.message) || error))
|
|
137
256
|
} finally {
|
|
@@ -197,7 +316,7 @@ window.__ModuleLoader__.load({
|
|
|
197
316
|
}
|
|
198
317
|
setSaved('已写入 provider "' + route + '" 的模型参数。')
|
|
199
318
|
setOpen(false)
|
|
200
|
-
|
|
319
|
+
setRawResult(null)
|
|
201
320
|
} catch (error) {
|
|
202
321
|
setFailed(String((error && error.message) || error))
|
|
203
322
|
} finally {
|
|
@@ -205,7 +324,10 @@ window.__ModuleLoader__.load({
|
|
|
205
324
|
}
|
|
206
325
|
}
|
|
207
326
|
|
|
208
|
-
|
|
327
|
+
// The Models page joins the official directory, so its `displayName` is the
|
|
328
|
+
// richest one. The page seat has only the namespace's own profile, which may
|
|
329
|
+
// declare one; otherwise the route itself is the honest label.
|
|
330
|
+
const displayName = (entry && entry.displayName) || piAiDisplayName(snap, route)
|
|
209
331
|
const hasModels = models.length > 0
|
|
210
332
|
const summary = result && result.matched ? result.matched.length + '/' + models.length + ' 匹配' : ''
|
|
211
333
|
|
|
@@ -214,6 +336,24 @@ window.__ModuleLoader__.load({
|
|
|
214
336
|
busy ? '查询中…' : ('models.dev 参数' + (summary ? ' · ' + summary : ''))),
|
|
215
337
|
open && e('div', { className: 'dpmPanel', role: 'dialog', 'aria-label': displayName + ' models.dev 参数' },
|
|
216
338
|
e('h4', null, displayName + ' · models.dev 官方参数'),
|
|
339
|
+
// A namespace may declare SEVERAL llm-pi-ai providers. This page holds ONE
|
|
340
|
+
// card, so it offers the namespace's real routes instead of silently
|
|
341
|
+
// addressing whichever one is declared first; a single-route namespace gets
|
|
342
|
+
// no chooser at all, because there is nothing to choose.
|
|
343
|
+
routes.length > 1 && e('label', { className: 'dpmRoute' }, 'provider',
|
|
344
|
+
e('select', {
|
|
345
|
+
value: route,
|
|
346
|
+
'aria-label': '选择要处理的 llm-pi-ai provider',
|
|
347
|
+
onChange: (event) => {
|
|
348
|
+
const next = event.target.value
|
|
349
|
+
setPicked(next)
|
|
350
|
+
setRawResult(null)
|
|
351
|
+
setSaved('')
|
|
352
|
+
setFailed('')
|
|
353
|
+
void load(next)
|
|
354
|
+
},
|
|
355
|
+
}, routes.map((item) => e('option', { key: item, value: item }, piAiDisplayName(snap, item)))),
|
|
356
|
+
),
|
|
217
357
|
e('div', { className: 'dpmProxy' },
|
|
218
358
|
e('label', null,
|
|
219
359
|
e('input', { type: 'checkbox', checked: proxyState.enabled, onChange: (ev) => { proxyState.enabled = ev.target.checked; notifyProxy() } }),
|
|
@@ -274,15 +414,89 @@ window.__ModuleLoader__.load({
|
|
|
274
414
|
const api = remote && remote.settings
|
|
275
415
|
? { settings: { mutate: (payload) => remote.settings.mutate(payload.ns, payload.ops, payload.expectedRevision).then((result) => ({ result })) } }
|
|
276
416
|
: undefined
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
417
|
+
// Version-portable scope on the official pi-ai namespace. Both transport
|
|
418
|
+
// names are awaited WITHOUT gating activation, and whichever this host
|
|
419
|
+
// mounts resolves; see resolveSettingsScopeFrom.
|
|
420
|
+
let scope
|
|
421
|
+
let cardRegistered = false
|
|
422
|
+
// Slot-registration disposers, released from apply's own disposer on stop /
|
|
423
|
+
// update / HMR so no seat entry outlives the plugin.
|
|
424
|
+
const disposeSlots = []
|
|
425
|
+
// The provider card MUST register from inside a context that has the
|
|
426
|
+
// settings transport, i.e. from the `ctx.inject([...], cb)` callback and on
|
|
427
|
+
// the CHILD context it hands us. Registering
|
|
428
|
+
// `settings.models.provider-card` from the bare apply context puts the
|
|
429
|
+
// entry where the Models page's ledger never sees it, so the extension
|
|
430
|
+
// never renders even though `register` returned normally. (dshmarket, an
|
|
431
|
+
// out-of-repo bundle that does render its card, uses this nested shape.)
|
|
432
|
+
const registerCard = (sctx) => {
|
|
433
|
+
if (cardRegistered) return
|
|
434
|
+
const resolved = resolveSettingsScopeFrom(sctx, PI_NS)
|
|
435
|
+
if (resolved === undefined) return
|
|
436
|
+
scope = resolved
|
|
437
|
+
cardRegistered = true
|
|
438
|
+
disposeSlots.push(sctx.slots.inject('settings.models.provider-card', () => sctx.slots.register(
|
|
439
|
+
{ name: 'settings.models.provider-card', key: PI_NS },
|
|
440
|
+
(props) => e(ModelParamsExtension, { ...props, api, scope }),
|
|
441
|
+
)))
|
|
442
|
+
}
|
|
443
|
+
// The first wait fires only when settingsScope exists; the second covers a
|
|
444
|
+
// host that renamed the transport, and its guard keeps the card from
|
|
445
|
+
// registering twice when both names exist.
|
|
446
|
+
ctx.inject(['settingsScope'], registerCard)
|
|
447
|
+
ctx.inject(['configForms'], (sctx) => { if (!cardRegistered) registerCard(sctx) })
|
|
448
|
+
|
|
449
|
+
// ── additive seat: the `settings.section` page ─────────────────────────
|
|
450
|
+
//
|
|
451
|
+
// 0.1.7-rc.2 also declares the root-scope LIST slot `settings.section`
|
|
452
|
+
// ("one settings page per list entry"), which puts this card one click deep
|
|
453
|
+
// inside 设置 as a first-class page of its own. It is purely ADDITIVE: the
|
|
454
|
+
// provider-card extension above stays the Models-page affordance, and both
|
|
455
|
+
// render the SAME `ModelParamsExtension` through the SAME resolved transport
|
|
456
|
+
// — no second settings UI and no second read/write path. This package owns
|
|
457
|
+
// no bundle row, so there is no `plugins.row.config` occupant to mirror:
|
|
458
|
+
// its card is the official Models-page extension.
|
|
459
|
+
//
|
|
460
|
+
// That seat's owner share is `{ close }` alone, so this page is handed NO
|
|
461
|
+
// provider. It addresses one of the routes the llm-pi-ai namespace itself
|
|
462
|
+
// declares — read from the SAME resolved scope, so no second read path and no
|
|
463
|
+
// synthesized route — and explains a loading, unavailable or provider-less
|
|
464
|
+
// namespace instead of rendering a blank page. The page passes no `provider`
|
|
465
|
+
// prop, which is exactly how the card knows it is the whole page.
|
|
466
|
+
//
|
|
467
|
+
// The wait on `slots` is NON-GATING (the seat is host-version dependent: a
|
|
468
|
+
// host that does not declare it simply never fires the inner `slots.inject`,
|
|
469
|
+
// so this registration can never gate the plugin), the callback RETURNS the
|
|
470
|
+
// registration disposer, and that disposer is pushed onto disposeSlots so
|
|
471
|
+
// the entry is owned by the registering fiber and released with the rest on
|
|
472
|
+
// stop / update / HMR.
|
|
473
|
+
const registerSettingsSection = (sctx) => {
|
|
474
|
+
disposeSlots.push(sctx.slots.inject('settings.section', () => sctx.slots.register({
|
|
475
|
+
name: 'settings.section',
|
|
476
|
+
id: 'yotk-model-params',
|
|
477
|
+
order: 63,
|
|
478
|
+
label: () => 'YOTK · Model Params',
|
|
479
|
+
}, function ModelParamsSettingsSection() {
|
|
480
|
+
// The seat owner shares `close` and nothing else; this card needs
|
|
481
|
+
// neither it nor the host-owned optional `form` prop, because values
|
|
482
|
+
// keep flowing through the one resolved transport — so the page is the
|
|
483
|
+
// same card component the provider-card seat renders, with the panel
|
|
484
|
+
// expanded: one card alone on the page. The trigger button still folds
|
|
485
|
+
// it back up.
|
|
486
|
+
return e(ModelParamsExtension, { api, scope, defaultOpen: true })
|
|
487
|
+
})))
|
|
488
|
+
}
|
|
489
|
+
ctx.inject(['slots'], registerSettingsSection)
|
|
490
|
+
|
|
491
|
+
return () => { for (const dispose of disposeSlots) { try { dispose() } catch (_) {} } }
|
|
282
492
|
}
|
|
283
493
|
|
|
284
494
|
exports.name = 'dsh-model-params'
|
|
285
|
-
|
|
495
|
+
// 'settingsScope' and 'configForms' are alternatives, not both-required:
|
|
496
|
+
// cordis resolves each inject name as its own gate, so activation succeeds on
|
|
497
|
+
// whichever settings transport the host mounts (0.1.5 vs 0.1.7-rc.1). The
|
|
498
|
+
// dotted remote names stay declared — dropping them fails the loader fiber.
|
|
499
|
+
exports.inject = ['slots', 'remote', 'remote.settings']
|
|
286
500
|
exports.apply = apply
|
|
287
501
|
return module.exports
|
|
288
502
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-model-params",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "EN: models.dev parameter assistant for the official DeepSeek Harness Models page: per-provider one-click context / max-output / reasoning-effort fill. ZH: 官方模型设置页的 models.dev 参数助手:按 provider 一键补全上下文、max 输出与推理档位。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|