@dsh-plus/subagent-model 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/lib/client.js +625 -649
- package/lib/index.d.ts +0 -3
- package/lib/index.js +43 -36
- package/package.json +7 -7
- 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
|
@@ -1,31 +1,33 @@
|
|
|
1
|
-
import z from "@deepseek-ai/schemastery";
|
|
2
1
|
import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
2
|
+
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
4
|
-
|
|
5
|
-
//#region src/ns.ts
|
|
6
|
-
/**
|
|
7
|
-
* settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
|
|
8
|
-
* 服务端经 settingsNamespace() 品牌化后使用(config.ts),浏览器半将其作为
|
|
9
|
-
* settings.plugin.item keyed 槽位的 key —— 同一字面量两处共用,防止漂移。
|
|
10
|
-
* 契约:dsh rc7 起该槽位按「卡片编辑的 settings 命名空间」作 key 分发,
|
|
11
|
-
* 官方配置页只渲染 key 命中 Host 已注册命名空间的卡片。
|
|
12
|
-
* @module subagent-model/ns
|
|
13
|
-
*/
|
|
14
|
-
const SETTINGS_NS$1 = "dsh-plus-subagent-model";
|
|
15
|
-
|
|
16
4
|
//#endregion
|
|
17
5
|
//#region src/config.ts
|
|
6
|
+
/**
|
|
7
|
+
* 配置单一事实源:cordis 行级 Config(组合默认值,dev/prod patch 层可覆盖)
|
|
8
|
+
* 与 settings namespace(用户层,经 dsh-settings-file 持久化到 $DSH_HOME/settings.yaml)
|
|
9
|
+
* 共用同一 schemastery schema。条目按【子代理 provider 名】键控
|
|
10
|
+
* (standard preset 事实映射:subagent↔spawn、subagent_fork↔fork)。
|
|
11
|
+
*
|
|
12
|
+
* 运行时语义(详见 delegation.ts):
|
|
13
|
+
* - provider/model 显式时注入子代理 `agentOptions`(覆盖"继承主代理"快照);
|
|
14
|
+
* - reasoningEffort 显式时经私有字段随 agentOptions 抵达子代理,再由
|
|
15
|
+
* `agent/request` 瀑布监听器应用(dsh 原生 AgentOptions 不含 effort 字段)。
|
|
16
|
+
*
|
|
17
|
+
* 类型说明:z.dict 的推断类型引用 cosmokit 的 Dict,直接导出会触发
|
|
18
|
+
* dts 的 TS2742(不可具名引用);按官方 dsh-permission-presets 模式,
|
|
19
|
+
* 显式声明输入/输出接口并注解 schema(输出为规范型,输入为可空宽松型)。
|
|
20
|
+
* @module subagent-model/config
|
|
21
|
+
*/
|
|
18
22
|
/** settings 命名空间;webui 配置卡片与插件运行期读取同一份(字面量见 ./ns.ts)。 */
|
|
19
|
-
const SETTINGS_NS = settingsNamespace(
|
|
23
|
+
const SETTINGS_NS = settingsNamespace("dsh-plus-subagent-model");
|
|
20
24
|
/** 思考程度哨兵值:继承主代理(不注入 effort 字段)。 */
|
|
21
25
|
const EFFORT_INHERIT = "inherit";
|
|
22
|
-
/** 思考程度哨兵值:跟随模型默认(显式剥离任何继承/推导出的 effort)。 */
|
|
23
|
-
const EFFORT_DEFAULT = "default";
|
|
24
26
|
const EntrySchema = z.object({
|
|
25
27
|
enabled: z.boolean().description("该子代理 provider 行的开关").default(false),
|
|
26
28
|
provider: z.string().description("LLM 提供商 id;留空 = 继承主代理").default(""),
|
|
27
29
|
model: z.string().description("模型 id;留空 = 继承主代理").default(""),
|
|
28
|
-
reasoningEffort: z.string().description(`思考程度:${EFFORT_INHERIT}(继承主代理)/
|
|
30
|
+
reasoningEffort: z.string().description(`思考程度:${EFFORT_INHERIT}(继承主代理)/ default(跟随模型默认)/ 提供商目录档位 id`).default(EFFORT_INHERIT)
|
|
29
31
|
});
|
|
30
32
|
const Config = z.object({
|
|
31
33
|
enabled: z.boolean().description("总开关").default(false),
|
|
@@ -40,7 +42,7 @@ function resolveEntry(entry) {
|
|
|
40
42
|
const out = {};
|
|
41
43
|
if (entry.provider.length > 0) out.provider = entry.provider;
|
|
42
44
|
if (entry.model.length > 0) out.model = entry.model;
|
|
43
|
-
if (entry.reasoningEffort !==
|
|
45
|
+
if (entry.reasoningEffort !== "inherit") out.reasoningEffort = entry.reasoningEffort;
|
|
44
46
|
return out.provider !== void 0 || out.model !== void 0 || out.reasoningEffort !== void 0 ? out : void 0;
|
|
45
47
|
}
|
|
46
48
|
/**
|
|
@@ -61,8 +63,8 @@ function mergeAgentOptions(injected, existing) {
|
|
|
61
63
|
*/
|
|
62
64
|
function applyEffort(resolved, effort) {
|
|
63
65
|
if (effort === void 0) return resolved;
|
|
64
|
-
if (effort ===
|
|
65
|
-
const { reasoningEffort: _dropped
|
|
66
|
+
if (effort === "default") {
|
|
67
|
+
const { reasoningEffort: _dropped, ...rest } = resolved;
|
|
66
68
|
return rest;
|
|
67
69
|
}
|
|
68
70
|
return {
|
|
@@ -81,13 +83,12 @@ function validateEntry(entry) {
|
|
|
81
83
|
}
|
|
82
84
|
/** 校验整组条目(settings 写入的 validate 钩子用);返回首个错误消息,合法时返回 null。 */
|
|
83
85
|
function validateEntries(entries) {
|
|
84
|
-
for (const [name
|
|
86
|
+
for (const [name, entry] of Object.entries(entries)) {
|
|
85
87
|
const error = validateEntry(entry);
|
|
86
|
-
if (error !== null) return `条目 ${name
|
|
88
|
+
if (error !== null) return `条目 ${name}: ${error}`;
|
|
87
89
|
}
|
|
88
90
|
return null;
|
|
89
91
|
}
|
|
90
|
-
|
|
91
92
|
//#endregion
|
|
92
93
|
//#region src/catalog.ts
|
|
93
94
|
function toEffort(effort) {
|
|
@@ -154,7 +155,6 @@ function subagentProvidersOf(ctx) {
|
|
|
154
155
|
const subagents = ctx.get("subagents");
|
|
155
156
|
return subagents === void 0 ? [] : [...subagents.list()];
|
|
156
157
|
}
|
|
157
|
-
|
|
158
158
|
//#endregion
|
|
159
159
|
//#region src/config-api.ts
|
|
160
160
|
const ROUTE_CATALOG = "/dsh-plus/subagent-model/catalog";
|
|
@@ -192,13 +192,12 @@ function registerCatalogApi(ctx) {
|
|
|
192
192
|
})
|
|
193
193
|
});
|
|
194
194
|
}
|
|
195
|
-
|
|
196
195
|
//#endregion
|
|
197
196
|
//#region src/delegation.ts
|
|
198
197
|
const WRAPPED = Symbol("dsh-plus-subagent-model.wrapped");
|
|
199
|
-
function entryFor(current, name
|
|
200
|
-
if (name
|
|
201
|
-
const entry = current().entries[name
|
|
198
|
+
function entryFor(current, name) {
|
|
199
|
+
if (name === void 0) return void 0;
|
|
200
|
+
const entry = current().entries[name];
|
|
202
201
|
return entry === void 0 ? void 0 : resolveEntry(entry);
|
|
203
202
|
}
|
|
204
203
|
/**
|
|
@@ -220,10 +219,10 @@ function installDelegationHook(ctx, current) {
|
|
|
220
219
|
}
|
|
221
220
|
const originalStart = subagents.start.bind(subagents);
|
|
222
221
|
const originalContinuable = subagents.startContinuable.bind(subagents);
|
|
223
|
-
subagents.start = async (name
|
|
224
|
-
const injected = entryFor(current, name
|
|
225
|
-
if (injected === void 0) return originalStart(name
|
|
226
|
-
return originalStart(name
|
|
222
|
+
subagents.start = async (name, request) => {
|
|
223
|
+
const injected = entryFor(current, name);
|
|
224
|
+
if (injected === void 0) return originalStart(name, request);
|
|
225
|
+
return originalStart(name, {
|
|
227
226
|
...request,
|
|
228
227
|
agentOptions: mergeAgentOptions(injected, request.agentOptions)
|
|
229
228
|
});
|
|
@@ -268,9 +267,19 @@ function installChildRoute(ctx) {
|
|
|
268
267
|
return applyEffort(resolved, effort);
|
|
269
268
|
});
|
|
270
269
|
}
|
|
271
|
-
|
|
272
270
|
//#endregion
|
|
273
271
|
//#region src/service.ts
|
|
272
|
+
/**
|
|
273
|
+
* SubagentModelService:子代理模型配置中枢。
|
|
274
|
+
* - 配置:installSettingsSection 接入 settings 用户层($DSH_HOME/settings.yaml,热生效),
|
|
275
|
+
* 无 settings provider 时退化为 cordis 行级 config;写入经 validate 钩子
|
|
276
|
+
* 走 validateEntries 规则(model 不能脱离 provider 等),非法即拒。
|
|
277
|
+
* - 委托挂钩:包装 ctx.subagents.start/startContinuable,按 provider 名注入
|
|
278
|
+
* agentOptions(provider/model/effort 私有标记),未命中条目完全直通。
|
|
279
|
+
* - 子代理路由:根 ctx 的 agent/request 瀑布监听,把条目 effort 应用到子代理请求。
|
|
280
|
+
* - 自定义端点:ctx.webServer 仅注册模型目录路由(配置读写已走官方 settings RPC)。
|
|
281
|
+
* @module @dsh-plus/subagent-model
|
|
282
|
+
*/
|
|
274
283
|
var SubagentModelService = class extends Service {
|
|
275
284
|
static [Context.inject] = ["subagents", "llm"];
|
|
276
285
|
current;
|
|
@@ -303,7 +312,6 @@ var SubagentModelService = class extends Service {
|
|
|
303
312
|
return subagents === void 0 ? [] : [...subagents.list()];
|
|
304
313
|
}
|
|
305
314
|
};
|
|
306
|
-
|
|
307
315
|
//#endregion
|
|
308
316
|
//#region src/index.ts
|
|
309
317
|
const name = "dsh-plus-subagent-model";
|
|
@@ -311,6 +319,5 @@ const inject = ["subagents", "llm"];
|
|
|
311
319
|
function apply(ctx, config) {
|
|
312
320
|
ctx.plugin(SubagentModelService, config);
|
|
313
321
|
}
|
|
314
|
-
|
|
315
322
|
//#endregion
|
|
316
|
-
export { Config, SubagentModelService, apply, inject, name };
|
|
323
|
+
export { Config, SubagentModelService, apply, inject, name };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plus/subagent-model",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "dsh-plus service+ui plugin: 为 subagent/subagent_fork 等子代理按提供商配置模型与思考程度(或继承主代理)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@deepseek-ai/cordis": "4.0.1",
|
|
42
42
|
"@deepseek-ai/cosmokit": "1.8.2",
|
|
43
|
-
"@deepseek-ai/dsh-agent": "0.1.
|
|
44
|
-
"@deepseek-ai/dsh-host-webserver": "0.1.
|
|
45
|
-
"@deepseek-ai/dsh-llm": "0.1.
|
|
46
|
-
"@deepseek-ai/dsh-session": "0.1.
|
|
47
|
-
"@deepseek-ai/dsh-settings": "0.1.
|
|
48
|
-
"@deepseek-ai/dsh-subagent": "0.1.
|
|
43
|
+
"@deepseek-ai/dsh-agent": "0.1.1-rc.2",
|
|
44
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.1-rc.2",
|
|
45
|
+
"@deepseek-ai/dsh-llm": "0.1.1-rc.2",
|
|
46
|
+
"@deepseek-ai/dsh-session": "0.1.1-rc.2",
|
|
47
|
+
"@deepseek-ai/dsh-settings": "0.1.1-rc.2",
|
|
48
|
+
"@deepseek-ai/dsh-subagent": "0.1.1-rc.2",
|
|
49
49
|
"@deepseek-ai/schemastery": "3.18.1",
|
|
50
50
|
"@types/react": "~18.3.1",
|
|
51
51
|
"react": "^18.2.0"
|
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>
|