@dsh-plus/subagent-model 0.1.5 → 0.1.6
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/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/package.json +1 -1
- package/src/catalog.ts +46 -33
- package/src/client/card.tsx +139 -56
- package/src/client/client.ts +20 -9
- package/src/client/i18n.ts +8 -4
- package/src/client/scope.ts +22 -3
- package/src/config.ts +16 -9
- package/src/delegation.ts +8 -2
- package/src/index.ts +6 -3
- package/src/service.ts +1 -2
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
package/package.json
CHANGED
package/src/catalog.ts
CHANGED
|
@@ -48,7 +48,7 @@ function toEffort(effort: LlmReasoningEffortInfo): CatalogEffort {
|
|
|
48
48
|
return {
|
|
49
49
|
id: String(effort.id),
|
|
50
50
|
name: effort.name,
|
|
51
|
-
...effort.description === undefined ? {} : { description: effort.description },
|
|
51
|
+
...(effort.description === undefined ? {} : { description: effort.description }),
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -59,49 +59,62 @@ function toEffort(effort: LlmReasoningEffortInfo): CatalogEffort {
|
|
|
59
59
|
export async function buildModelCatalog(ctx: Context): Promise<ModelCatalog> {
|
|
60
60
|
const llm = ctx.get('llm')
|
|
61
61
|
if (llm === undefined) {
|
|
62
|
-
return {
|
|
62
|
+
return {
|
|
63
|
+
providers: [],
|
|
64
|
+
failures: [],
|
|
65
|
+
subagentProviders: subagentProvidersOf(ctx),
|
|
66
|
+
}
|
|
63
67
|
}
|
|
64
|
-
const catalog = await Promise.all(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
const catalog = await Promise.all(
|
|
69
|
+
llm.listProviders().map(async (provider) => {
|
|
70
|
+
try {
|
|
71
|
+
const models = await llm.listModels(provider.id)
|
|
72
|
+
const entries = await Promise.all(
|
|
73
|
+
models.map(async (model) => {
|
|
74
|
+
const resolved = await llm.resolveModelInfo(provider.id, model.id)
|
|
75
|
+
const reasoning =
|
|
76
|
+
resolved.reasoning === undefined
|
|
77
|
+
? undefined
|
|
78
|
+
: {
|
|
79
|
+
efforts: resolved.reasoning.efforts.map(toEffort),
|
|
80
|
+
...(resolved.reasoning.defaultEffort === undefined
|
|
81
|
+
? {}
|
|
82
|
+
: {
|
|
83
|
+
defaultEffort: String(resolved.reasoning.defaultEffort),
|
|
84
|
+
}),
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
id: model.id,
|
|
88
|
+
name: model.name,
|
|
89
|
+
...(model.description === undefined ? {} : { description: model.description }),
|
|
90
|
+
...(reasoning === undefined ? {} : { reasoning }),
|
|
91
|
+
}
|
|
92
|
+
}),
|
|
93
|
+
)
|
|
94
|
+
return {
|
|
95
|
+
kind: 'group' as const,
|
|
96
|
+
group: { id: provider.id, name: provider.name, models: entries },
|
|
74
97
|
}
|
|
98
|
+
} catch (error) {
|
|
75
99
|
return {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
100
|
+
kind: 'failure' as const,
|
|
101
|
+
failure: {
|
|
102
|
+
id: provider.id,
|
|
103
|
+
name: provider.name,
|
|
104
|
+
message: error instanceof Error ? error.message : String(error),
|
|
105
|
+
},
|
|
80
106
|
}
|
|
81
|
-
}))
|
|
82
|
-
return {
|
|
83
|
-
kind: 'group' as const,
|
|
84
|
-
group: { id: provider.id, name: provider.name, models: entries },
|
|
85
107
|
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
kind: 'failure' as const,
|
|
89
|
-
failure: {
|
|
90
|
-
id: provider.id,
|
|
91
|
-
name: provider.name,
|
|
92
|
-
message: error instanceof Error ? error.message : String(error),
|
|
93
|
-
},
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}))
|
|
108
|
+
}),
|
|
109
|
+
)
|
|
97
110
|
return {
|
|
98
111
|
providers: catalog
|
|
99
112
|
.filter((item) => item.kind === 'group' && item.group.models.length > 0)
|
|
100
|
-
.map((item) => item.kind === 'group' ? item.group : undefined)
|
|
113
|
+
.map((item) => (item.kind === 'group' ? item.group : undefined))
|
|
101
114
|
.filter((group): group is CatalogProvider => group !== undefined),
|
|
102
115
|
failures: catalog
|
|
103
116
|
.filter((item) => item.kind === 'failure')
|
|
104
|
-
.map((item) => item.kind === 'failure' ? item.failure : undefined)
|
|
117
|
+
.map((item) => (item.kind === 'failure' ? item.failure : undefined))
|
|
105
118
|
.filter((failure): failure is CatalogFailure => failure !== undefined),
|
|
106
119
|
subagentProviders: subagentProvidersOf(ctx),
|
|
107
120
|
}
|
package/src/client/card.tsx
CHANGED
|
@@ -7,12 +7,10 @@
|
|
|
7
7
|
* 落盘,自文档化);「模型目录」为唯一保留的自定义端点。
|
|
8
8
|
* @module subagent-model/client/card
|
|
9
9
|
*/
|
|
10
|
-
import { useEffect, useMemo,
|
|
10
|
+
import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
11
11
|
|
|
12
12
|
import { SETTINGS_NS } from '../ns.ts'
|
|
13
|
-
import {
|
|
14
|
-
fetchCatalog, type CatalogProvider, type ModelCatalog,
|
|
15
|
-
} from './api.ts'
|
|
13
|
+
import { type CatalogProvider, fetchCatalog, type ModelCatalog } from './api.ts'
|
|
16
14
|
import { CheckRow, SelectField, type SelectOption } from './fields.tsx'
|
|
17
15
|
import type { Scope, SettingsApi } from './scope.ts'
|
|
18
16
|
|
|
@@ -53,7 +51,12 @@ interface Status {
|
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
const IDLE_STATUS: Status = { kind: 'idle', text: '' }
|
|
56
|
-
const EMPTY_ROW: DraftRow = {
|
|
54
|
+
const EMPTY_ROW: DraftRow = {
|
|
55
|
+
enabled: false,
|
|
56
|
+
provider: '',
|
|
57
|
+
model: '',
|
|
58
|
+
reasoningEffort: 'inherit',
|
|
59
|
+
}
|
|
57
60
|
const EFFORT_INHERIT = 'inherit'
|
|
58
61
|
const EFFORT_DEFAULT = 'default'
|
|
59
62
|
|
|
@@ -64,14 +67,15 @@ function draftFrom(value: ConfigValue, catalog: ModelCatalog | null): Draft {
|
|
|
64
67
|
for (const name of Object.keys(value.entries).sort()) names.add(name)
|
|
65
68
|
for (const name of names) {
|
|
66
69
|
const entry = value.entries[name]
|
|
67
|
-
rows[name] =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
rows[name] =
|
|
71
|
+
entry === undefined
|
|
72
|
+
? { ...EMPTY_ROW }
|
|
73
|
+
: {
|
|
74
|
+
enabled: entry.enabled,
|
|
75
|
+
provider: entry.provider,
|
|
76
|
+
model: entry.model,
|
|
77
|
+
reasoningEffort: entry.reasoningEffort,
|
|
78
|
+
}
|
|
75
79
|
}
|
|
76
80
|
return { enabled: value.enabled, rows }
|
|
77
81
|
}
|
|
@@ -84,16 +88,25 @@ function unknownOption(value: string, label: string): SelectOption {
|
|
|
84
88
|
return { value, label: `${value}(${label})` }
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
function providerOptions(
|
|
91
|
+
function providerOptions(
|
|
92
|
+
catalog: ModelCatalog | null,
|
|
93
|
+
row: DraftRow,
|
|
94
|
+
t: (k: string) => string,
|
|
95
|
+
): SelectOption[] {
|
|
88
96
|
const options: SelectOption[] = [{ value: '', label: t('inheritProvider') }]
|
|
89
|
-
for (const provider of catalog?.providers ?? [])
|
|
97
|
+
for (const provider of catalog?.providers ?? [])
|
|
98
|
+
options.push({ value: provider.id, label: provider.name })
|
|
90
99
|
if (row.provider.length > 0 && !options.some((o) => o.value === row.provider)) {
|
|
91
100
|
options.push(unknownOption(row.provider, t('unknownValue')))
|
|
92
101
|
}
|
|
93
102
|
return options
|
|
94
103
|
}
|
|
95
104
|
|
|
96
|
-
function modelOptions(
|
|
105
|
+
function modelOptions(
|
|
106
|
+
catalog: ModelCatalog | null,
|
|
107
|
+
row: DraftRow,
|
|
108
|
+
t: (k: string) => string,
|
|
109
|
+
): SelectOption[] {
|
|
97
110
|
const options: SelectOption[] = [{ value: '', label: t('inheritModel') }]
|
|
98
111
|
const provider = catalog?.providers.find((p) => p.id === row.provider)
|
|
99
112
|
for (const model of provider?.models ?? []) options.push({ value: model.id, label: model.name })
|
|
@@ -103,14 +116,19 @@ function modelOptions(catalog: ModelCatalog | null, row: DraftRow, t: (k: string
|
|
|
103
116
|
return options
|
|
104
117
|
}
|
|
105
118
|
|
|
106
|
-
function effortOptions(
|
|
119
|
+
function effortOptions(
|
|
120
|
+
catalog: ModelCatalog | null,
|
|
121
|
+
row: DraftRow,
|
|
122
|
+
t: (k: string) => string,
|
|
123
|
+
): SelectOption[] {
|
|
107
124
|
const options: SelectOption[] = [
|
|
108
125
|
{ value: EFFORT_INHERIT, label: t('effortInherit') },
|
|
109
126
|
{ value: EFFORT_DEFAULT, label: t('effortDefault') },
|
|
110
127
|
]
|
|
111
128
|
const provider = catalog?.providers.find((p) => p.id === row.provider)
|
|
112
129
|
const model = provider?.models.find((m) => m.id === row.model)
|
|
113
|
-
for (const effort of model?.reasoning?.efforts ?? [])
|
|
130
|
+
for (const effort of model?.reasoning?.efforts ?? [])
|
|
131
|
+
options.push({ value: effort.id, label: effort.name })
|
|
114
132
|
if (row.reasoningEffort.length > 0 && !options.some((o) => o.value === row.reasoningEffort)) {
|
|
115
133
|
options.push(unknownOption(row.reasoningEffort, t('unknownValue')))
|
|
116
134
|
}
|
|
@@ -133,20 +151,44 @@ function RowBlock(props: RowBlockProps): ReactElement {
|
|
|
133
151
|
<div className="dsm-row">
|
|
134
152
|
<div className="dsm-rowHead">
|
|
135
153
|
<span className="dsm-rowName">{name}</span>
|
|
136
|
-
<CheckRow
|
|
137
|
-
|
|
154
|
+
<CheckRow
|
|
155
|
+
id={`dsm-row-${name}`}
|
|
156
|
+
label={t('rowEnabled')}
|
|
157
|
+
checked={row.enabled}
|
|
158
|
+
disabled={disabled}
|
|
159
|
+
onEdit={(v) => onEdit({ ...row, enabled: v })}
|
|
160
|
+
/>
|
|
138
161
|
</div>
|
|
139
162
|
<p className="dsm-rowHint">{t('rowHint')}</p>
|
|
140
|
-
<SelectField
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
value={row.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
163
|
+
<SelectField
|
|
164
|
+
id={`dsm-provider-${name}`}
|
|
165
|
+
label={t('provider')}
|
|
166
|
+
hint={t('providerHint')}
|
|
167
|
+
value={row.provider}
|
|
168
|
+
options={providerOptions(catalog, row, t)}
|
|
169
|
+
disabled={disabled}
|
|
170
|
+
onEdit={(v) => onEdit({ ...row, provider: v })}
|
|
171
|
+
/>
|
|
172
|
+
<SelectField
|
|
173
|
+
id={`dsm-model-${name}`}
|
|
174
|
+
label={t('model')}
|
|
175
|
+
hint={t('modelHint')}
|
|
176
|
+
value={row.model}
|
|
177
|
+
options={modelOptions(catalog, row, t)}
|
|
178
|
+
disabled={disabled || row.provider.length === 0}
|
|
179
|
+
invalid={invalidModel}
|
|
180
|
+
invalidLabel={t('invalidModel')}
|
|
181
|
+
onEdit={(v) => onEdit({ ...row, model: v })}
|
|
182
|
+
/>
|
|
183
|
+
<SelectField
|
|
184
|
+
id={`dsm-effort-${name}`}
|
|
185
|
+
label={t('effort')}
|
|
186
|
+
hint={t('effortHint')}
|
|
187
|
+
value={row.reasoningEffort}
|
|
188
|
+
options={effortOptions(catalog, row, t)}
|
|
189
|
+
disabled={disabled}
|
|
190
|
+
onEdit={(v) => onEdit({ ...row, reasoningEffort: v })}
|
|
191
|
+
/>
|
|
150
192
|
</div>
|
|
151
193
|
)
|
|
152
194
|
}
|
|
@@ -202,18 +244,24 @@ export function SubagentModelCard(props: CardProps): ReactElement | null {
|
|
|
202
244
|
|
|
203
245
|
const dirty = useMemo(
|
|
204
246
|
() =>
|
|
205
|
-
value !== undefined &&
|
|
247
|
+
value !== undefined &&
|
|
248
|
+
draft !== null &&
|
|
206
249
|
JSON.stringify(toPatch(draft)) !== JSON.stringify(toPatch(draftFrom(value, catalog))),
|
|
207
250
|
[value, draft, catalog],
|
|
208
251
|
)
|
|
209
252
|
const invalid = useMemo(
|
|
210
|
-
() =>
|
|
211
|
-
|
|
253
|
+
() =>
|
|
254
|
+
draft !== null &&
|
|
255
|
+
Object.values(draft.rows).some((row) => row.model.length > 0 && row.provider.length === 0),
|
|
212
256
|
[draft],
|
|
213
257
|
)
|
|
214
258
|
|
|
215
259
|
if (value === undefined || draft === null) {
|
|
216
|
-
return
|
|
260
|
+
return (
|
|
261
|
+
<li className="dsm-card">
|
|
262
|
+
<p className="dsm-empty">{t('loading')}</p>
|
|
263
|
+
</li>
|
|
264
|
+
)
|
|
217
265
|
}
|
|
218
266
|
const edit = <K extends keyof Draft>(key: K, editValue: Draft[K]): void => {
|
|
219
267
|
setDraft({ ...draft, [key]: editValue })
|
|
@@ -226,11 +274,12 @@ export function SubagentModelCard(props: CardProps): ReactElement | null {
|
|
|
226
274
|
const onSave = (): void => {
|
|
227
275
|
setSaving(true)
|
|
228
276
|
const revision = scope.getSnapshot().revision
|
|
229
|
-
api
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
277
|
+
api
|
|
278
|
+
.update({
|
|
279
|
+
ns: SETTINGS_NS,
|
|
280
|
+
patch: toPatch(draft),
|
|
281
|
+
...(revision !== undefined ? { expectedRevision: revision } : {}),
|
|
282
|
+
})
|
|
234
283
|
.then(async (response) => {
|
|
235
284
|
if (!response.result.ok) {
|
|
236
285
|
throw new Error(response.result.error?.message ?? t('saveFailed'))
|
|
@@ -241,7 +290,10 @@ export function SubagentModelCard(props: CardProps): ReactElement | null {
|
|
|
241
290
|
setStatus(IDLE_STATUS)
|
|
242
291
|
})
|
|
243
292
|
.catch((error: unknown) => {
|
|
244
|
-
setStatus({
|
|
293
|
+
setStatus({
|
|
294
|
+
kind: 'error',
|
|
295
|
+
text: `${t('saveFailed')}${error instanceof Error ? error.message : ''}`,
|
|
296
|
+
})
|
|
245
297
|
})
|
|
246
298
|
.finally(() => setSaving(false))
|
|
247
299
|
}
|
|
@@ -271,9 +323,18 @@ export function SubagentModelCard(props: CardProps): ReactElement | null {
|
|
|
271
323
|
</button>
|
|
272
324
|
{open ? (
|
|
273
325
|
<div className="dsm-body">
|
|
274
|
-
{disabled ?
|
|
275
|
-
|
|
276
|
-
|
|
326
|
+
{disabled ? (
|
|
327
|
+
<p className="dsm-empty" role="status">
|
|
328
|
+
{t('readOnly')}
|
|
329
|
+
</p>
|
|
330
|
+
) : null}
|
|
331
|
+
<CheckRow
|
|
332
|
+
id="dsm-enabled"
|
|
333
|
+
label={t('enabled')}
|
|
334
|
+
checked={draft.enabled}
|
|
335
|
+
disabled={disabled}
|
|
336
|
+
onEdit={(v) => edit('enabled', v)}
|
|
337
|
+
/>
|
|
277
338
|
{catalogFailed ? (
|
|
278
339
|
<div className="dsm-banner" role="status">
|
|
279
340
|
<span>{t('catalogError')}</span>
|
|
@@ -283,31 +344,53 @@ export function SubagentModelCard(props: CardProps): ReactElement | null {
|
|
|
283
344
|
</div>
|
|
284
345
|
) : null}
|
|
285
346
|
{catalog === null && !catalogFailed ? (
|
|
286
|
-
<p className="dsm-empty" role="status">
|
|
347
|
+
<p className="dsm-empty" role="status">
|
|
348
|
+
{t('catalogLoading')}
|
|
349
|
+
</p>
|
|
287
350
|
) : null}
|
|
288
351
|
{rowNames.length === 0 ? (
|
|
289
|
-
<p className="dsm-empty" role="status">
|
|
352
|
+
<p className="dsm-empty" role="status">
|
|
353
|
+
{t('noRows')}
|
|
354
|
+
</p>
|
|
290
355
|
) : null}
|
|
291
356
|
{rowNames.map((name) => (
|
|
292
|
-
<RowBlock
|
|
293
|
-
|
|
294
|
-
|
|
357
|
+
<RowBlock
|
|
358
|
+
key={name}
|
|
359
|
+
name={name}
|
|
360
|
+
row={draft.rows[name] ?? { ...EMPTY_ROW }}
|
|
361
|
+
catalog={catalog}
|
|
362
|
+
disabled={disabled}
|
|
363
|
+
t={t}
|
|
364
|
+
onEdit={(row) => editRow(name, row)}
|
|
365
|
+
/>
|
|
295
366
|
))}
|
|
296
|
-
<p className="dsm-hint dsm-rowHint">
|
|
297
|
-
{providerCount > 0 ? t('rowDesc') : ''}
|
|
298
|
-
</p>
|
|
367
|
+
<p className="dsm-hint dsm-rowHint">{providerCount > 0 ? t('rowDesc') : ''}</p>
|
|
299
368
|
<div className="dsm-footer">
|
|
300
369
|
{status.kind !== 'idle' ? (
|
|
301
|
-
<p
|
|
370
|
+
<p
|
|
371
|
+
className={`dsm-status${status.kind === 'error' ? ' dsm-statusError' : ''}`}
|
|
372
|
+
role="status"
|
|
373
|
+
>
|
|
302
374
|
{status.text}
|
|
303
375
|
</p>
|
|
304
376
|
) : null}
|
|
305
|
-
<button
|
|
306
|
-
|
|
377
|
+
<button
|
|
378
|
+
type="button"
|
|
379
|
+
className="dsm-btn dsm-btnGhost"
|
|
380
|
+
disabled={!dirty || saving}
|
|
381
|
+
onClick={() => {
|
|
382
|
+
setDraft(draftFrom(value, catalog))
|
|
383
|
+
setStatus(IDLE_STATUS)
|
|
384
|
+
}}
|
|
385
|
+
>
|
|
307
386
|
{t('discard')}
|
|
308
387
|
</button>
|
|
309
|
-
<button
|
|
310
|
-
|
|
388
|
+
<button
|
|
389
|
+
type="button"
|
|
390
|
+
className="dsm-btn dsm-btnPrimary"
|
|
391
|
+
disabled={!dirty || invalid || saving || disabled}
|
|
392
|
+
onClick={onSave}
|
|
393
|
+
>
|
|
311
394
|
{t(saving ? 'saving' : 'save')}
|
|
312
395
|
</button>
|
|
313
396
|
</div>
|
package/src/client/client.ts
CHANGED
|
@@ -63,7 +63,12 @@ interface ClientContext {
|
|
|
63
63
|
* (loopback 或 tailnet 信任域名)下行为一致。
|
|
64
64
|
*/
|
|
65
65
|
function createApiScope(api: SettingsApi, ns: string, c: ClientContext): Scope {
|
|
66
|
-
let state: ScopeSnapshot = {
|
|
66
|
+
let state: ScopeSnapshot = {
|
|
67
|
+
status: 'loading',
|
|
68
|
+
value: undefined,
|
|
69
|
+
revision: undefined,
|
|
70
|
+
writable: false,
|
|
71
|
+
}
|
|
67
72
|
const listeners = new Set<() => void>()
|
|
68
73
|
let generation = 0
|
|
69
74
|
const publish = (next: ScopeSnapshot): void => {
|
|
@@ -72,7 +77,7 @@ function createApiScope(api: SettingsApi, ns: string, c: ClientContext): Scope {
|
|
|
72
77
|
}
|
|
73
78
|
const load = async (): Promise<void> => {
|
|
74
79
|
const gen = ++generation
|
|
75
|
-
let response
|
|
80
|
+
let response: Awaited<ReturnType<SettingsApi['describe']>>
|
|
76
81
|
try {
|
|
77
82
|
response = await api.describe({})
|
|
78
83
|
} catch {
|
|
@@ -82,10 +87,20 @@ function createApiScope(api: SettingsApi, ns: string, c: ClientContext): Scope {
|
|
|
82
87
|
const writable = response.result.value?.writable ?? false
|
|
83
88
|
const view = response.result.value?.namespaces.find((candidate) => candidate.ns === ns)
|
|
84
89
|
if (view === undefined) {
|
|
85
|
-
publish({
|
|
90
|
+
publish({
|
|
91
|
+
status: 'unavailable',
|
|
92
|
+
value: undefined,
|
|
93
|
+
revision: undefined,
|
|
94
|
+
writable,
|
|
95
|
+
})
|
|
86
96
|
return
|
|
87
97
|
}
|
|
88
|
-
publish({
|
|
98
|
+
publish({
|
|
99
|
+
status: 'ready',
|
|
100
|
+
value: view.value,
|
|
101
|
+
revision: view.revision,
|
|
102
|
+
writable,
|
|
103
|
+
})
|
|
89
104
|
}
|
|
90
105
|
const refresh = (namespace?: unknown): void => {
|
|
91
106
|
if (namespace !== undefined && namespace !== ns) return
|
|
@@ -117,7 +132,6 @@ function createApiScope(api: SettingsApi, ns: string, c: ClientContext): Scope {
|
|
|
117
132
|
}
|
|
118
133
|
|
|
119
134
|
export function apply(ctx: Context): void {
|
|
120
|
-
|
|
121
135
|
const c = ctx as unknown as ClientContext
|
|
122
136
|
const tag = injectStyle()
|
|
123
137
|
c.effect(
|
|
@@ -126,10 +140,7 @@ export function apply(ctx: Context): void {
|
|
|
126
140
|
},
|
|
127
141
|
'subagent-model: style',
|
|
128
142
|
)
|
|
129
|
-
c.effect(
|
|
130
|
-
() => c.locale.register(NS, { zh, en }),
|
|
131
|
-
'subagent-model: locale',
|
|
132
|
-
)
|
|
143
|
+
c.effect(() => c.locale.register(NS, { zh, en }), 'subagent-model: locale')
|
|
133
144
|
const api = c.get('connection').api.settings
|
|
134
145
|
const scope = createApiScope(api, SETTINGS_NS, c)
|
|
135
146
|
c.slots.inject('settings.plugin.item', () =>
|
package/src/client/i18n.ts
CHANGED
|
@@ -20,7 +20,8 @@ export const zh: Record<string, string> = {
|
|
|
20
20
|
providerHint: '留空 = 子代理沿用主代理的提供商与模型。',
|
|
21
21
|
modelHint: '留空 = 沿用主代理的模型(需与所选提供商匹配)。',
|
|
22
22
|
effortHint: 'inherit = 不干预(fork 会随会话继承);default = 显式使用模型默认档位。',
|
|
23
|
-
rowHint:
|
|
23
|
+
rowHint:
|
|
24
|
+
'按子代理 provider 名匹配;standard preset 中 spawn ↔ subagent 工具、fork ↔ subagent_fork 工具。',
|
|
24
25
|
rowDesc: '未注册的 provider 行不生效,可保留待用。',
|
|
25
26
|
noRows: '当前未发现子代理 provider;配置将在 provider 注册后生效。',
|
|
26
27
|
catalogError: '提供商目录加载失败:',
|
|
@@ -42,7 +43,8 @@ export const zh: Record<string, string> = {
|
|
|
42
43
|
|
|
43
44
|
export const en: Record<string, string> = {
|
|
44
45
|
title: 'Subagent model config',
|
|
45
|
-
description:
|
|
46
|
+
description:
|
|
47
|
+
'Pick a provider, model, and thinking effort for subagent / subagent_fork children, or inherit from the main agent.',
|
|
46
48
|
enabled: 'Enable plugin',
|
|
47
49
|
rowEnabled: 'Enable row',
|
|
48
50
|
provider: 'Provider',
|
|
@@ -54,8 +56,10 @@ export const en: Record<string, string> = {
|
|
|
54
56
|
effortDefault: 'Follow model default',
|
|
55
57
|
providerHint: 'Empty = the child follows the main agent\u2019s provider and model.',
|
|
56
58
|
modelHint: 'Empty = follow the main agent\u2019s model (must match the chosen provider).',
|
|
57
|
-
effortHint:
|
|
58
|
-
|
|
59
|
+
effortHint:
|
|
60
|
+
'inherit = do not touch (forks inherit via session seed); default = explicitly use the model\u2019s default level.',
|
|
61
|
+
rowHint:
|
|
62
|
+
'Matched by subagent provider name; in the standard preset spawn \u2194 subagent tool, fork \u2194 subagent_fork tool.',
|
|
59
63
|
rowDesc: 'Rows for unregistered providers are inert; keep them for later.',
|
|
60
64
|
noRows: 'No subagent provider registered yet; config takes effect once one appears.',
|
|
61
65
|
catalogError: 'Failed to load provider catalog: ',
|
package/src/client/scope.ts
CHANGED
|
@@ -31,7 +31,26 @@ export interface Scope {
|
|
|
31
31
|
|
|
32
32
|
/** connection api.settings 的 RPC 面(本插件用到的三个方法)。 */
|
|
33
33
|
export interface SettingsApi {
|
|
34
|
-
describe(payload?: Record<string, never>): Promise<{
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
describe(payload?: Record<string, never>): Promise<{
|
|
35
|
+
result: {
|
|
36
|
+
ok: boolean
|
|
37
|
+
value?: {
|
|
38
|
+
namespaces: Array<{
|
|
39
|
+
ns: string
|
|
40
|
+
secrets: Array<{ path: string[]; set: boolean }>
|
|
41
|
+
}>
|
|
42
|
+
}
|
|
43
|
+
error?: { message?: string }
|
|
44
|
+
}
|
|
45
|
+
}>
|
|
46
|
+
update(request: {
|
|
47
|
+
ns: string
|
|
48
|
+
patch: Record<string, unknown>
|
|
49
|
+
expectedRevision?: number
|
|
50
|
+
}): Promise<{ result: { ok: boolean; error?: { message?: string } } }>
|
|
51
|
+
replace(request: {
|
|
52
|
+
ns: string
|
|
53
|
+
section: Record<string, unknown>
|
|
54
|
+
expectedRevision?: number
|
|
55
|
+
}): Promise<{ result: { ok: boolean; error?: { message?: string } } }>
|
|
37
56
|
}
|
package/src/config.ts
CHANGED
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* 显式声明输入/输出接口并注解 schema(输出为规范型,输入为可空宽松型)。
|
|
15
15
|
* @module subagent-model/config
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
|
|
18
18
|
import { settingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
19
|
+
import z from '@deepseek-ai/schemastery'
|
|
19
20
|
|
|
20
21
|
import { SETTINGS_NS as NS_LITERAL } from './ns.ts'
|
|
21
22
|
|
|
@@ -44,20 +45,26 @@ export interface SubagentModelConfig {
|
|
|
44
45
|
/** 配置的宽松输入形态(schema 校验前的用户数据)。 */
|
|
45
46
|
export interface SubagentModelConfigInput {
|
|
46
47
|
enabled?: boolean | null
|
|
47
|
-
entries?: Record<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
entries?: Record<
|
|
49
|
+
string,
|
|
50
|
+
{
|
|
51
|
+
enabled?: boolean | null
|
|
52
|
+
provider?: string | null
|
|
53
|
+
model?: string | null
|
|
54
|
+
reasoningEffort?: string | null
|
|
55
|
+
}
|
|
56
|
+
> | null
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
const EntrySchema = z.object({
|
|
56
60
|
enabled: z.boolean().description('该子代理 provider 行的开关').default(false),
|
|
57
61
|
provider: z.string().description('LLM 提供商 id;留空 = 继承主代理').default(''),
|
|
58
62
|
model: z.string().description('模型 id;留空 = 继承主代理').default(''),
|
|
59
|
-
reasoningEffort: z
|
|
60
|
-
.
|
|
63
|
+
reasoningEffort: z
|
|
64
|
+
.string()
|
|
65
|
+
.description(
|
|
66
|
+
`思考程度:${EFFORT_INHERIT}(继承主代理)/ ${EFFORT_DEFAULT}(跟随模型默认)/ 提供商目录档位 id`,
|
|
67
|
+
)
|
|
61
68
|
.default(EFFORT_INHERIT),
|
|
62
69
|
})
|
|
63
70
|
|
package/src/delegation.ts
CHANGED
|
@@ -19,11 +19,17 @@
|
|
|
19
19
|
* @module subagent-model/delegation
|
|
20
20
|
*/
|
|
21
21
|
import type { Context } from '@deepseek-ai/cordis'
|
|
22
|
-
import type {
|
|
22
|
+
import type { AgentOptions } from '@deepseek-ai/dsh-agent'
|
|
23
23
|
import type { LlmCallConfig } from '@deepseek-ai/dsh-llm'
|
|
24
24
|
import type { ContinuableStartSpec, SubagentStartRequest } from '@deepseek-ai/dsh-subagent'
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
applyEffort,
|
|
28
|
+
mergeAgentOptions,
|
|
29
|
+
type RequestConfig,
|
|
30
|
+
resolveEntry,
|
|
31
|
+
type SubagentModelConfig,
|
|
32
|
+
} from './config.ts'
|
|
27
33
|
|
|
28
34
|
const WRAPPED = Symbol('dsh-plus-subagent-model.wrapped')
|
|
29
35
|
|
package/src/index.ts
CHANGED
|
@@ -15,10 +15,13 @@ export const name = 'dsh-plus-subagent-model'
|
|
|
15
15
|
|
|
16
16
|
export const inject = ['subagents', 'llm'] as const
|
|
17
17
|
|
|
18
|
-
export {
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
export type {
|
|
19
|
+
EntryConfig,
|
|
20
|
+
InjectedOptions,
|
|
21
|
+
SubagentModelConfig,
|
|
22
|
+
} from './config.ts'
|
|
21
23
|
export { SubagentModelService } from './service.ts'
|
|
24
|
+
export { Config }
|
|
22
25
|
|
|
23
26
|
export function apply(ctx: Context, config: SubagentModelConfig): void {
|
|
24
27
|
ctx.plugin(SubagentModelService, config)
|
package/src/service.ts
CHANGED
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { Context, Service } from '@deepseek-ai/cordis'
|
|
13
13
|
import { installSettingsSection } from '@deepseek-ai/dsh-settings'
|
|
14
|
-
|
|
14
|
+
import { Config, SETTINGS_NS, type SubagentModelConfig, validateEntries } from './config.ts'
|
|
15
15
|
import { registerCatalogApi } from './config-api.ts'
|
|
16
|
-
import { Config, SETTINGS_NS, validateEntries, type SubagentModelConfig } from './config.ts'
|
|
17
16
|
import { installChildRoute, installDelegationHook } from './delegation.ts'
|
|
18
17
|
|
|
19
18
|
declare module '@deepseek-ai/cordis' {
|