@dsh-plus/subagent-model 0.1.10 → 0.2.0
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 -4
- package/lib/index.js +51 -193
- package/package.json +8 -31
- package/src/config.ts +33 -42
- package/src/delegation.ts +23 -54
- package/src/index.ts +5 -4
- package/src/ns.ts +3 -6
- package/src/service.ts +20 -29
- package/lib/client.js +0 -850
- package/src/catalog.ts +0 -127
- package/src/client/api.ts +0 -45
- package/src/client/card.tsx +0 -340
- package/src/client/client.ts +0 -78
- package/src/client/draft.ts +0 -69
- package/src/client/i18n.ts +0 -65
- package/src/client/styles.ts +0 -30
- package/src/config-api.ts +0 -53
package/src/catalog.ts
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 提供商/模型/思考档位目录:配置卡片的下拉数据源。
|
|
3
|
-
* 镜像官方 api-proxy `buildModelCatalog`(session.models RPC)的构建方式:
|
|
4
|
-
* `ctx.llm.listProviders()` → `listModels()` → `resolveModelInfo()`,
|
|
5
|
-
* 附 `ctx.subagents.list()` 的已注册子代理 provider 名(卡片按此行合成空行)。
|
|
6
|
-
* 目录仅作建议性数据源,不参与运行期路由。
|
|
7
|
-
* @module subagent-model/catalog
|
|
8
|
-
*/
|
|
9
|
-
import type { Context } from '@deepseek-ai/cordis'
|
|
10
|
-
import type { LlmReasoningEffortInfo } from '@deepseek-ai/dsh-llm'
|
|
11
|
-
|
|
12
|
-
export interface CatalogEffort {
|
|
13
|
-
id: string
|
|
14
|
-
name: string
|
|
15
|
-
description?: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface CatalogModel {
|
|
19
|
-
id: string
|
|
20
|
-
name: string
|
|
21
|
-
description?: string
|
|
22
|
-
reasoning?: {
|
|
23
|
-
defaultEffort?: string
|
|
24
|
-
efforts: CatalogEffort[]
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface CatalogProvider {
|
|
29
|
-
id: string
|
|
30
|
-
name: string
|
|
31
|
-
models: CatalogModel[]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface CatalogFailure {
|
|
35
|
-
id: string
|
|
36
|
-
name: string
|
|
37
|
-
message: string
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface ModelCatalog {
|
|
41
|
-
providers: CatalogProvider[]
|
|
42
|
-
failures: CatalogFailure[]
|
|
43
|
-
/** 已注册的子代理 provider 名(spawn/fork/…),卡片据此合成配置行。 */
|
|
44
|
-
subagentProviders: string[]
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function toEffort(effort: LlmReasoningEffortInfo): CatalogEffort {
|
|
48
|
-
return {
|
|
49
|
-
id: String(effort.id),
|
|
50
|
-
name: effort.name,
|
|
51
|
-
...(effort.description === undefined ? {} : { description: effort.description }),
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 构建目录。单个提供商失败只进入 failures,不拖垮其余组;
|
|
57
|
-
* 无模型的组被丢弃(与官方 buildModelCatalog 同语义)。
|
|
58
|
-
*/
|
|
59
|
-
export async function buildModelCatalog(ctx: Context): Promise<ModelCatalog> {
|
|
60
|
-
const llm = ctx.get('llm')
|
|
61
|
-
if (llm === undefined) {
|
|
62
|
-
return {
|
|
63
|
-
providers: [],
|
|
64
|
-
failures: [],
|
|
65
|
-
subagentProviders: subagentProvidersOf(ctx),
|
|
66
|
-
}
|
|
67
|
-
}
|
|
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 },
|
|
97
|
-
}
|
|
98
|
-
} catch (error) {
|
|
99
|
-
return {
|
|
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
|
-
},
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}),
|
|
109
|
-
)
|
|
110
|
-
return {
|
|
111
|
-
providers: catalog
|
|
112
|
-
.filter((item) => item.kind === 'group' && item.group.models.length > 0)
|
|
113
|
-
.map((item) => (item.kind === 'group' ? item.group : undefined))
|
|
114
|
-
.filter((group): group is CatalogProvider => group !== undefined),
|
|
115
|
-
failures: catalog
|
|
116
|
-
.filter((item) => item.kind === 'failure')
|
|
117
|
-
.map((item) => (item.kind === 'failure' ? item.failure : undefined))
|
|
118
|
-
.filter((failure): failure is CatalogFailure => failure !== undefined),
|
|
119
|
-
subagentProviders: subagentProvidersOf(ctx),
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** 已注册的子代理 provider 名(llm 服务缺失时同样返回,卡片仍可渲染)。 */
|
|
124
|
-
function subagentProvidersOf(ctx: Context): string[] {
|
|
125
|
-
const subagents = ctx.get('subagents')
|
|
126
|
-
return subagents === undefined ? [] : [...subagents.list()]
|
|
127
|
-
}
|
package/src/client/api.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 自定义端点通道:仅剩「模型目录」(配置读写已迁移到官方 settingsScope
|
|
3
|
-
* 传输,见 scope.ts 与 card.tsx)。
|
|
4
|
-
* @module subagent-model/client/api
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export interface CatalogEffort {
|
|
8
|
-
id: string
|
|
9
|
-
name: string
|
|
10
|
-
description?: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface CatalogModel {
|
|
14
|
-
id: string
|
|
15
|
-
name: string
|
|
16
|
-
description?: string
|
|
17
|
-
reasoning?: {
|
|
18
|
-
defaultEffort?: string
|
|
19
|
-
efforts: CatalogEffort[]
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface CatalogProvider {
|
|
24
|
-
id: string
|
|
25
|
-
name: string
|
|
26
|
-
models: CatalogModel[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface ModelCatalog {
|
|
30
|
-
providers: CatalogProvider[]
|
|
31
|
-
failures: { id: string; name: string; message: string }[]
|
|
32
|
-
subagentProviders: string[]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const ROUTE_CATALOG = '/dsh-plus/subagent-model/catalog'
|
|
36
|
-
|
|
37
|
-
async function parse<T>(res: Response): Promise<T> {
|
|
38
|
-
const body = (await res.json()) as T & { error?: string }
|
|
39
|
-
if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`)
|
|
40
|
-
return body
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function fetchCatalog(): Promise<ModelCatalog> {
|
|
44
|
-
return parse<ModelCatalog>(await fetch(ROUTE_CATALOG, { credentials: 'same-origin' }))
|
|
45
|
-
}
|
package/src/client/card.tsx
DELETED
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 「子代理模型配置」配置卡片:注册进 settings.plugin.item 插槽(官方插件配置页)。
|
|
3
|
-
* 外壳与基础控件走 @dsh-plus/shared/client 套件(CardChrome/CheckRow/SelectField)。
|
|
4
|
-
* 配置读写走官方 settingsScope 传输:value 为 schema 解析后的命名空间值
|
|
5
|
-
* (enabled + entries),行集合 = 目录返回的已注册子代理 provider
|
|
6
|
-
* ∪ 已配置条目,保存时全量写回 entries(未配置的新 provider 行以默认空值
|
|
7
|
-
* 落盘,自文档化);「模型目录」为唯一保留的自定义端点。
|
|
8
|
-
* @module subagent-model/client/card
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
CardChrome,
|
|
13
|
-
type CardStatusState,
|
|
14
|
-
CheckRow,
|
|
15
|
-
IDLE_STATUS,
|
|
16
|
-
type Scope,
|
|
17
|
-
SelectField,
|
|
18
|
-
type SelectOption,
|
|
19
|
-
type SettingsApi,
|
|
20
|
-
} from '@dsh-plus/shared/client'
|
|
21
|
-
import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
22
|
-
import { SETTINGS_NS } from '../ns.ts'
|
|
23
|
-
import { type CatalogProvider, fetchCatalog, type ModelCatalog } from './api.ts'
|
|
24
|
-
import {
|
|
25
|
-
type ConfigValue,
|
|
26
|
-
type Draft,
|
|
27
|
-
type DraftRow,
|
|
28
|
-
draftFrom,
|
|
29
|
-
EMPTY_ROW,
|
|
30
|
-
toPatch,
|
|
31
|
-
} from './draft.ts'
|
|
32
|
-
|
|
33
|
-
export interface CardProps {
|
|
34
|
-
t(key: string): string
|
|
35
|
-
scope: Scope
|
|
36
|
-
api: SettingsApi
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const EFFORT_INHERIT = 'inherit'
|
|
40
|
-
const EFFORT_DEFAULT = 'default'
|
|
41
|
-
|
|
42
|
-
function unknownOption(value: string, label: string): SelectOption {
|
|
43
|
-
return { value, label: `${value}(${label})` }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function providerOptions(
|
|
47
|
-
catalog: ModelCatalog | null,
|
|
48
|
-
row: DraftRow,
|
|
49
|
-
t: (k: string) => string,
|
|
50
|
-
): SelectOption[] {
|
|
51
|
-
const options: SelectOption[] = [{ value: '', label: t('inheritProvider') }]
|
|
52
|
-
for (const provider of catalog?.providers ?? [])
|
|
53
|
-
options.push({ value: provider.id, label: provider.name })
|
|
54
|
-
if (row.provider.length > 0 && !options.some((o) => o.value === row.provider)) {
|
|
55
|
-
options.push(unknownOption(row.provider, t('unknownValue')))
|
|
56
|
-
}
|
|
57
|
-
return options
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function modelOptions(
|
|
61
|
-
catalog: ModelCatalog | null,
|
|
62
|
-
row: DraftRow,
|
|
63
|
-
t: (k: string) => string,
|
|
64
|
-
): SelectOption[] {
|
|
65
|
-
const options: SelectOption[] = [{ value: '', label: t('inheritModel') }]
|
|
66
|
-
const provider = catalog?.providers.find((p) => p.id === row.provider)
|
|
67
|
-
for (const model of provider?.models ?? []) options.push({ value: model.id, label: model.name })
|
|
68
|
-
if (row.model.length > 0 && !options.some((o) => o.value === row.model)) {
|
|
69
|
-
options.push(unknownOption(row.model, t('unknownValue')))
|
|
70
|
-
}
|
|
71
|
-
return options
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function effortOptions(
|
|
75
|
-
catalog: ModelCatalog | null,
|
|
76
|
-
row: DraftRow,
|
|
77
|
-
t: (k: string) => string,
|
|
78
|
-
): SelectOption[] {
|
|
79
|
-
const options: SelectOption[] = [
|
|
80
|
-
{ value: EFFORT_INHERIT, label: t('effortInherit') },
|
|
81
|
-
{ value: EFFORT_DEFAULT, label: t('effortDefault') },
|
|
82
|
-
]
|
|
83
|
-
const provider = catalog?.providers.find((p) => p.id === row.provider)
|
|
84
|
-
const model = provider?.models.find((m) => m.id === row.model)
|
|
85
|
-
for (const effort of model?.reasoning?.efforts ?? [])
|
|
86
|
-
options.push({ value: effort.id, label: effort.name })
|
|
87
|
-
if (row.reasoningEffort.length > 0 && !options.some((o) => o.value === row.reasoningEffort)) {
|
|
88
|
-
options.push(unknownOption(row.reasoningEffort, t('unknownValue')))
|
|
89
|
-
}
|
|
90
|
-
return options
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
interface RowBlockProps {
|
|
94
|
-
name: string
|
|
95
|
-
row: DraftRow
|
|
96
|
-
catalog: ModelCatalog | null
|
|
97
|
-
disabled: boolean
|
|
98
|
-
t(key: string): string
|
|
99
|
-
onEdit(row: DraftRow): void
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function RowBlock(props: RowBlockProps): ReactElement {
|
|
103
|
-
const { name, row, catalog, disabled, t, onEdit } = props
|
|
104
|
-
const invalidModel = row.model.length > 0 && row.provider.length === 0
|
|
105
|
-
return (
|
|
106
|
-
<div className="dsm-row">
|
|
107
|
-
<div className="dsm-rowHead">
|
|
108
|
-
<span className="dsm-rowName">{name}</span>
|
|
109
|
-
<CheckRow
|
|
110
|
-
prefix="dsm"
|
|
111
|
-
id={`dsm-row-${name}`}
|
|
112
|
-
label={t('rowEnabled')}
|
|
113
|
-
checked={row.enabled}
|
|
114
|
-
disabled={disabled}
|
|
115
|
-
onEdit={(v) => onEdit({ ...row, enabled: v })}
|
|
116
|
-
/>
|
|
117
|
-
</div>
|
|
118
|
-
<p className="dsm-rowHint">{t('rowHint')}</p>
|
|
119
|
-
<SelectField
|
|
120
|
-
prefix="dsm"
|
|
121
|
-
id={`dsm-provider-${name}`}
|
|
122
|
-
label={t('provider')}
|
|
123
|
-
hint={t('providerHint')}
|
|
124
|
-
value={row.provider}
|
|
125
|
-
options={providerOptions(catalog, row, t)}
|
|
126
|
-
disabled={disabled}
|
|
127
|
-
onEdit={(v) => onEdit({ ...row, provider: v })}
|
|
128
|
-
/>
|
|
129
|
-
<SelectField
|
|
130
|
-
prefix="dsm"
|
|
131
|
-
id={`dsm-model-${name}`}
|
|
132
|
-
label={t('model')}
|
|
133
|
-
hint={t('modelHint')}
|
|
134
|
-
value={row.model}
|
|
135
|
-
options={modelOptions(catalog, row, t)}
|
|
136
|
-
disabled={disabled || row.provider.length === 0}
|
|
137
|
-
invalid={invalidModel}
|
|
138
|
-
invalidLabel={t('invalidModel')}
|
|
139
|
-
onEdit={(v) => onEdit({ ...row, model: v })}
|
|
140
|
-
/>
|
|
141
|
-
<SelectField
|
|
142
|
-
prefix="dsm"
|
|
143
|
-
id={`dsm-effort-${name}`}
|
|
144
|
-
label={t('effort')}
|
|
145
|
-
hint={t('effortHint')}
|
|
146
|
-
value={row.reasoningEffort}
|
|
147
|
-
options={effortOptions(catalog, row, t)}
|
|
148
|
-
disabled={disabled}
|
|
149
|
-
onEdit={(v) => onEdit({ ...row, reasoningEffort: v })}
|
|
150
|
-
/>
|
|
151
|
-
</div>
|
|
152
|
-
)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function SubagentModelCard(props: CardProps): ReactElement | null {
|
|
156
|
-
const { t, scope, api } = props
|
|
157
|
-
const snapshot = useSyncExternalStore(
|
|
158
|
-
(listener: () => void) => scope.subscribe(listener),
|
|
159
|
-
() => scope.getSnapshot(),
|
|
160
|
-
)
|
|
161
|
-
const value = snapshot.value as ConfigValue | undefined
|
|
162
|
-
const [open, setOpen] = useState(false)
|
|
163
|
-
const [draft, setDraft] = useState<Draft | null>(null)
|
|
164
|
-
const [catalog, setCatalog] = useState<ModelCatalog | null>(null)
|
|
165
|
-
const [catalogFailed, setCatalogFailed] = useState(false)
|
|
166
|
-
const [saving, setSaving] = useState(false)
|
|
167
|
-
const [status, setStatus] = useState<CardStatusState>(IDLE_STATUS)
|
|
168
|
-
|
|
169
|
-
// 播种草稿(行集 = 已配置条目);catalog 到达后只补缺失的 provider 空行,
|
|
170
|
-
// 不覆盖在途编辑;后续 Host 更新同样不覆盖。
|
|
171
|
-
useEffect(() => {
|
|
172
|
-
if (value === undefined) return
|
|
173
|
-
setDraft((current) => {
|
|
174
|
-
if (current === null) return draftFrom(value, catalog)
|
|
175
|
-
if (catalog === null) return current
|
|
176
|
-
const rows = { ...current.rows }
|
|
177
|
-
let changed = false
|
|
178
|
-
for (const name of catalog.subagentProviders) {
|
|
179
|
-
if (name in rows) continue
|
|
180
|
-
rows[name] = { ...EMPTY_ROW }
|
|
181
|
-
changed = true
|
|
182
|
-
}
|
|
183
|
-
return changed ? { ...current, rows } : current
|
|
184
|
-
})
|
|
185
|
-
}, [value, catalog])
|
|
186
|
-
|
|
187
|
-
useEffect(() => {
|
|
188
|
-
if (!open || catalog !== null) return
|
|
189
|
-
let alive = true
|
|
190
|
-
fetchCatalog()
|
|
191
|
-
.then((loaded) => {
|
|
192
|
-
if (!alive) return
|
|
193
|
-
setCatalog(loaded)
|
|
194
|
-
setCatalogFailed(false)
|
|
195
|
-
})
|
|
196
|
-
.catch(() => {
|
|
197
|
-
if (alive) setCatalogFailed(true)
|
|
198
|
-
})
|
|
199
|
-
return () => {
|
|
200
|
-
alive = false
|
|
201
|
-
}
|
|
202
|
-
}, [open, catalog])
|
|
203
|
-
|
|
204
|
-
const dirty = useMemo(
|
|
205
|
-
() =>
|
|
206
|
-
value !== undefined &&
|
|
207
|
-
draft !== null &&
|
|
208
|
-
JSON.stringify(toPatch(draft)) !== JSON.stringify(toPatch(draftFrom(value, catalog))),
|
|
209
|
-
[value, draft, catalog],
|
|
210
|
-
)
|
|
211
|
-
const invalid = useMemo(
|
|
212
|
-
() =>
|
|
213
|
-
draft !== null &&
|
|
214
|
-
Object.values(draft.rows).some((row) => row.model.length > 0 && row.provider.length === 0),
|
|
215
|
-
[draft],
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
if (value === undefined || draft === null) {
|
|
219
|
-
return (
|
|
220
|
-
<li className="dsm-card">
|
|
221
|
-
<p className="dsm-empty">{t('loading')}</p>
|
|
222
|
-
</li>
|
|
223
|
-
)
|
|
224
|
-
}
|
|
225
|
-
const edit = <K extends keyof Draft>(key: K, editValue: Draft[K]): void => {
|
|
226
|
-
setDraft({ ...draft, [key]: editValue })
|
|
227
|
-
setStatus(IDLE_STATUS)
|
|
228
|
-
}
|
|
229
|
-
const editRow = (name: string, row: DraftRow): void => {
|
|
230
|
-
setDraft({ ...draft, rows: { ...draft.rows, [name]: row } })
|
|
231
|
-
setStatus(IDLE_STATUS)
|
|
232
|
-
}
|
|
233
|
-
const onSave = (): void => {
|
|
234
|
-
setSaving(true)
|
|
235
|
-
const revision = scope.getSnapshot().revision
|
|
236
|
-
api
|
|
237
|
-
.update({
|
|
238
|
-
ns: SETTINGS_NS,
|
|
239
|
-
patch: toPatch(draft),
|
|
240
|
-
...(revision !== undefined ? { expectedRevision: revision } : {}),
|
|
241
|
-
})
|
|
242
|
-
.then(async (response) => {
|
|
243
|
-
if (!response.result.ok) {
|
|
244
|
-
throw new Error(response.result.error?.message ?? t('saveFailed'))
|
|
245
|
-
}
|
|
246
|
-
await scope.load()
|
|
247
|
-
const next = scope.getSnapshot().value as ConfigValue | undefined
|
|
248
|
-
if (next !== undefined) setDraft(draftFrom(next, catalog))
|
|
249
|
-
setStatus(IDLE_STATUS)
|
|
250
|
-
})
|
|
251
|
-
.catch((error: unknown) => {
|
|
252
|
-
setStatus({
|
|
253
|
-
kind: 'error',
|
|
254
|
-
text: `${t('saveFailed')}${error instanceof Error ? error.message : ''}`,
|
|
255
|
-
})
|
|
256
|
-
})
|
|
257
|
-
.finally(() => setSaving(false))
|
|
258
|
-
}
|
|
259
|
-
const onRetryCatalog = (): void => {
|
|
260
|
-
setCatalog(null)
|
|
261
|
-
setCatalogFailed(false)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const disabled = !snapshot.writable
|
|
265
|
-
const rowNames = Object.keys(draft.rows).sort()
|
|
266
|
-
const providerCount = (catalog?.providers ?? []).length
|
|
267
|
-
return (
|
|
268
|
-
<CardChrome
|
|
269
|
-
prefix="dsm"
|
|
270
|
-
title={t('title')}
|
|
271
|
-
description={t('description')}
|
|
272
|
-
open={open}
|
|
273
|
-
onToggle={setOpen}
|
|
274
|
-
statusBadge={{ text: t(draft.enabled ? 'enabledOn' : 'enabledOff'), on: draft.enabled }}
|
|
275
|
-
dirty={dirty}
|
|
276
|
-
dirtyLabel={t('unsaved')}
|
|
277
|
-
readOnlyNotice={disabled ? t('readOnly') : undefined}
|
|
278
|
-
status={status}
|
|
279
|
-
actions={[
|
|
280
|
-
{
|
|
281
|
-
key: 'discard',
|
|
282
|
-
label: t('discard'),
|
|
283
|
-
disabled: !dirty || saving,
|
|
284
|
-
onClick: () => {
|
|
285
|
-
setDraft(draftFrom(value, catalog))
|
|
286
|
-
setStatus(IDLE_STATUS)
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
{
|
|
290
|
-
key: 'save',
|
|
291
|
-
label: t(saving ? 'saving' : 'save'),
|
|
292
|
-
variant: 'primary',
|
|
293
|
-
disabled: !dirty || invalid || saving || disabled,
|
|
294
|
-
onClick: onSave,
|
|
295
|
-
},
|
|
296
|
-
]}
|
|
297
|
-
>
|
|
298
|
-
<CheckRow
|
|
299
|
-
prefix="dsm"
|
|
300
|
-
id="dsm-enabled"
|
|
301
|
-
label={t('enabled')}
|
|
302
|
-
checked={draft.enabled}
|
|
303
|
-
disabled={disabled}
|
|
304
|
-
onEdit={(v) => edit('enabled', v)}
|
|
305
|
-
/>
|
|
306
|
-
{catalogFailed ? (
|
|
307
|
-
<div className="dsm-banner" role="status">
|
|
308
|
-
<span>{t('catalogError')}</span>
|
|
309
|
-
<button type="button" className="dsm-bannerRetry" onClick={onRetryCatalog}>
|
|
310
|
-
{t('catalogRetry')}
|
|
311
|
-
</button>
|
|
312
|
-
</div>
|
|
313
|
-
) : null}
|
|
314
|
-
{catalog === null && !catalogFailed ? (
|
|
315
|
-
<p className="dsm-empty" role="status">
|
|
316
|
-
{t('catalogLoading')}
|
|
317
|
-
</p>
|
|
318
|
-
) : null}
|
|
319
|
-
{rowNames.length === 0 ? (
|
|
320
|
-
<p className="dsm-empty" role="status">
|
|
321
|
-
{t('noRows')}
|
|
322
|
-
</p>
|
|
323
|
-
) : null}
|
|
324
|
-
{rowNames.map((name) => (
|
|
325
|
-
<RowBlock
|
|
326
|
-
key={name}
|
|
327
|
-
name={name}
|
|
328
|
-
row={draft.rows[name] ?? { ...EMPTY_ROW }}
|
|
329
|
-
catalog={catalog}
|
|
330
|
-
disabled={disabled}
|
|
331
|
-
t={t}
|
|
332
|
-
onEdit={(row) => editRow(name, row)}
|
|
333
|
-
/>
|
|
334
|
-
))}
|
|
335
|
-
<p className="dsm-hint dsm-rowHint">{providerCount > 0 ? t('rowDesc') : ''}</p>
|
|
336
|
-
</CardChrome>
|
|
337
|
-
)
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
export type { CatalogProvider }
|
package/src/client/client.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 浏览器半入口:注册 locale 字典 + 向 settings.plugin.item 插槽注册「子代理模型配置」卡片。
|
|
3
|
-
* 构建产物为 window.__ModuleLoader__.load({id, factory}) 形式的 CJS factory
|
|
4
|
-
* (包装见 tsdown.config.ts);样式沿用官方 data-plugin-css 约定,HMR 据此卸载。
|
|
5
|
-
*
|
|
6
|
-
* 类型说明:浏览器半只用到 slots/locale/connection/remote 的很窄一面,
|
|
7
|
-
* scope 与基础控件走 @dsh-plus/shared/client 套件(收编自本插件原实现)。
|
|
8
|
-
* rc7 起该插槽为 keyed 槽位:key 必须是卡片编辑的 settings 命名空间
|
|
9
|
-
* (即服务端 settingsNamespace() 注册的同一字面量,见 ../ns.ts),
|
|
10
|
-
* 官方配置页按此 key 与 Host 已注册命名空间配对分发。
|
|
11
|
-
* 配置读写经官方 settings RPC 直连(rc7 起第三方命名空间全量开放;
|
|
12
|
-
* 不复用 settingsScope 服务——非 loopback 页面下它无数据);
|
|
13
|
-
* 自定义端点仅剩「模型目录」(api.ts)。
|
|
14
|
-
* @module @dsh-plus/subagent-model/client
|
|
15
|
-
*/
|
|
16
|
-
import type { Context } from '@deepseek-ai/cordis'
|
|
17
|
-
import { createApiScope } from '@dsh-plus/shared/client'
|
|
18
|
-
import { SETTINGS_NS } from '../ns.ts'
|
|
19
|
-
import { SubagentModelCard } from './card.tsx'
|
|
20
|
-
import { en, NS, zh } from './i18n.ts'
|
|
21
|
-
import { injectStyle } from './styles.ts'
|
|
22
|
-
|
|
23
|
-
export const name = 'dsh-plus-subagent-model'
|
|
24
|
-
|
|
25
|
-
/** 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject 管包加载顺序)。 */
|
|
26
|
-
export const inject = ['slots', 'locale', 'connection', 'remote'] as const
|
|
27
|
-
|
|
28
|
-
interface SlotsLike {
|
|
29
|
-
inject(key: string, callback: () => unknown): unknown
|
|
30
|
-
register(options: Record<string, unknown>, component: unknown): () => void
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface LocaleLike {
|
|
34
|
-
register(ns: string, dict: { zh: Record<string, string>; en: Record<string, string> }): () => void
|
|
35
|
-
bind(ns: string): (key: string) => string
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface RemoteLike {
|
|
39
|
-
$on(event: string, listener: (payload?: unknown) => void): () => void
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
interface ConnectionLike {
|
|
43
|
-
api: { settings: Parameters<typeof createApiScope>[0] }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface ClientContext {
|
|
47
|
-
slots: SlotsLike
|
|
48
|
-
locale: LocaleLike
|
|
49
|
-
get(key: 'connection'): ConnectionLike
|
|
50
|
-
get(key: 'remote'): RemoteLike
|
|
51
|
-
on(event: string, listener: () => void): () => void
|
|
52
|
-
effect(execute: () => () => void, label?: string): unknown
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function apply(ctx: Context): void {
|
|
56
|
-
const c = ctx as unknown as ClientContext
|
|
57
|
-
const tag = injectStyle()
|
|
58
|
-
c.effect(
|
|
59
|
-
() => () => {
|
|
60
|
-
tag?.remove()
|
|
61
|
-
},
|
|
62
|
-
'subagent-model: style',
|
|
63
|
-
)
|
|
64
|
-
c.effect(() => c.locale.register(NS, { zh, en }), 'subagent-model: locale')
|
|
65
|
-
const api = c.get('connection').api.settings
|
|
66
|
-
const scope = createApiScope(api, SETTINGS_NS, c, 'subagent-model: settings scope')
|
|
67
|
-
c.slots.inject('settings.plugin.item', () =>
|
|
68
|
-
c.slots.register(
|
|
69
|
-
{
|
|
70
|
-
name: 'settings.plugin.item',
|
|
71
|
-
key: SETTINGS_NS,
|
|
72
|
-
locale: NS,
|
|
73
|
-
inject: () => ({ t: c.locale.bind(NS), scope, api }),
|
|
74
|
-
},
|
|
75
|
-
SubagentModelCard,
|
|
76
|
-
),
|
|
77
|
-
)
|
|
78
|
-
}
|
package/src/client/draft.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 配置卡片的可编辑草稿模型:ConfigValue ↔ Draft 转换与提交补丁(纯函数,
|
|
3
|
-
* 与渲染分离以便单测)。行集合 = 目录返回的已注册子代理 provider ∪ 已配置条目;
|
|
4
|
-
* 未配置的新 provider 行以默认空值落盘(自文档化)。
|
|
5
|
-
* @module subagent-model/client/draft
|
|
6
|
-
*/
|
|
7
|
-
import type { ModelCatalog } from './api.ts'
|
|
8
|
-
|
|
9
|
-
/** settings 命名空间的解析值(无 secret 字段)。 */
|
|
10
|
-
export interface ConfigValue {
|
|
11
|
-
enabled: boolean
|
|
12
|
-
entries: Record<string, WireEntry>
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface WireEntry {
|
|
16
|
-
enabled: boolean
|
|
17
|
-
provider: string
|
|
18
|
-
model: string
|
|
19
|
-
reasoningEffort: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface DraftRow {
|
|
23
|
-
enabled: boolean
|
|
24
|
-
provider: string
|
|
25
|
-
model: string
|
|
26
|
-
reasoningEffort: string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface Draft {
|
|
30
|
-
enabled: boolean
|
|
31
|
-
rows: Record<string, DraftRow>
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export const EMPTY_ROW: DraftRow = {
|
|
35
|
-
enabled: false,
|
|
36
|
-
provider: '',
|
|
37
|
-
model: '',
|
|
38
|
-
reasoningEffort: 'inherit',
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** 行集 = 目录 subagentProviders ∪ 已配置条目;目录行缺省为空行。 */
|
|
42
|
-
export function draftFrom(value: ConfigValue, catalog: ModelCatalog | null): Draft {
|
|
43
|
-
const rows: Record<string, DraftRow> = {}
|
|
44
|
-
const names = new Set<string>()
|
|
45
|
-
for (const name of catalog?.subagentProviders ?? []) names.add(name)
|
|
46
|
-
for (const name of Object.keys(value.entries).sort()) names.add(name)
|
|
47
|
-
for (const name of names) {
|
|
48
|
-
const entry = value.entries[name]
|
|
49
|
-
rows[name] =
|
|
50
|
-
entry === undefined
|
|
51
|
-
? { ...EMPTY_ROW }
|
|
52
|
-
: {
|
|
53
|
-
enabled: entry.enabled,
|
|
54
|
-
provider: entry.provider,
|
|
55
|
-
model: entry.model,
|
|
56
|
-
reasoningEffort: entry.reasoningEffort,
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return { enabled: value.enabled, rows }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function toPatch(draft: Draft): Record<string, unknown> {
|
|
63
|
-
// entries 按键排序物化:dirty 比较走 JSON.stringify,而草稿的播种路径
|
|
64
|
-
// (先 entries 后补目录空行)与 draftFrom(先目录后 entries)键序不同,
|
|
65
|
-
// 不排序会出现「未改动却永久 dirty」的假阳性。
|
|
66
|
-
const entries: Record<string, DraftRow> = {}
|
|
67
|
-
for (const name of Object.keys(draft.rows).sort()) entries[name] = draft.rows[name]
|
|
68
|
-
return { enabled: draft.enabled, entries }
|
|
69
|
-
}
|