@dsh-plus/subagent-model 0.1.6 → 0.1.8

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 CHANGED
@@ -1,9 +1,6 @@
1
- import "@deepseek-ai/dsh-settings";
2
1
  import z from "@deepseek-ai/schemastery";
3
2
  import { Context, Service } from "@deepseek-ai/cordis";
4
-
5
3
  //#region src/config.d.ts
6
-
7
4
  /** 一条条目的规范输出形态。 */
8
5
  interface EntryConfig {
9
6
  enabled: boolean;
package/lib/index.js CHANGED
@@ -1,31 +1,33 @@
1
1
  import { installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
2
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(SETTINGS_NS$1);
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}(继承主代理)/ ${EFFORT_DEFAULT}(跟随模型默认)/ 提供商目录档位 id`).default(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 !== EFFORT_INHERIT) out.reasoningEffort = 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 === EFFORT_DEFAULT) {
65
- const { reasoningEffort: _dropped,...rest } = resolved;
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$1, entry] of Object.entries(entries)) {
86
+ for (const [name, entry] of Object.entries(entries)) {
85
87
  const error = validateEntry(entry);
86
- if (error !== null) return `条目 ${name$1}: ${error}`;
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$1) {
200
- if (name$1 === void 0) return void 0;
201
- const entry = current().entries[name$1];
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$1, request) => {
224
- const injected = entryFor(current, name$1);
225
- if (injected === void 0) return originalStart(name$1, request);
226
- return originalStart(name$1, {
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.6",
3
+ "version": "0.1.8",
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.0-rc.8",
44
- "@deepseek-ai/dsh-host-webserver": "0.1.0-rc.8",
45
- "@deepseek-ai/dsh-llm": "0.1.0-rc.8",
46
- "@deepseek-ai/dsh-session": "0.1.0-rc.8",
47
- "@deepseek-ai/dsh-settings": "0.1.0-rc.8",
48
- "@deepseek-ai/dsh-subagent": "0.1.0-rc.8",
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"
@@ -11,6 +11,14 @@ import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore }
11
11
 
12
12
  import { SETTINGS_NS } from '../ns.ts'
13
13
  import { type CatalogProvider, fetchCatalog, type ModelCatalog } from './api.ts'
14
+ import {
15
+ type ConfigValue,
16
+ type Draft,
17
+ type DraftRow,
18
+ draftFrom,
19
+ EMPTY_ROW,
20
+ toPatch,
21
+ } from './draft.ts'
14
22
  import { CheckRow, SelectField, type SelectOption } from './fields.tsx'
15
23
  import type { Scope, SettingsApi } from './scope.ts'
16
24
 
@@ -20,70 +28,15 @@ export interface CardProps {
20
28
  api: SettingsApi
21
29
  }
22
30
 
23
- /** settings 命名空间的解析值(无 secret 字段)。 */
24
- export interface ConfigValue {
25
- enabled: boolean
26
- entries: Record<string, WireEntry>
27
- }
28
-
29
- export interface WireEntry {
30
- enabled: boolean
31
- provider: string
32
- model: string
33
- reasoningEffort: string
34
- }
35
-
36
- interface DraftRow {
37
- enabled: boolean
38
- provider: string
39
- model: string
40
- reasoningEffort: string
41
- }
42
-
43
- interface Draft {
44
- enabled: boolean
45
- rows: Record<string, DraftRow>
46
- }
47
-
48
31
  interface Status {
49
32
  kind: 'idle' | 'ok' | 'error'
50
33
  text: string
51
34
  }
52
35
 
53
36
  const IDLE_STATUS: Status = { kind: 'idle', text: '' }
54
- const EMPTY_ROW: DraftRow = {
55
- enabled: false,
56
- provider: '',
57
- model: '',
58
- reasoningEffort: 'inherit',
59
- }
60
37
  const EFFORT_INHERIT = 'inherit'
61
38
  const EFFORT_DEFAULT = 'default'
62
39
 
63
- function draftFrom(value: ConfigValue, catalog: ModelCatalog | null): Draft {
64
- const rows: Record<string, DraftRow> = {}
65
- const names = new Set<string>()
66
- for (const name of catalog?.subagentProviders ?? []) names.add(name)
67
- for (const name of Object.keys(value.entries).sort()) names.add(name)
68
- for (const name of names) {
69
- const entry = value.entries[name]
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
- }
79
- }
80
- return { enabled: value.enabled, rows }
81
- }
82
-
83
- function toPatch(draft: Draft): Record<string, unknown> {
84
- return { enabled: draft.enabled, entries: draft.rows }
85
- }
86
-
87
40
  function unknownOption(value: string, label: string): SelectOption {
88
41
  return { value, label: `${value}(${label})` }
89
42
  }
@@ -20,7 +20,7 @@ import type { Context } from '@deepseek-ai/cordis'
20
20
  import { SETTINGS_NS } from '../ns.ts'
21
21
  import { SubagentModelCard } from './card.tsx'
22
22
  import { en, NS, zh } from './i18n.ts'
23
- import type { Scope, SettingsApi } from './scope.ts'
23
+ import type { Scope, ScopeSnapshot, SettingsApi } from './scope.ts'
24
24
  import { injectStyle } from './styles.ts'
25
25
 
26
26
  export const name = 'dsh-plus-subagent-model'
@@ -0,0 +1,69 @@
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
+ }
@@ -35,8 +35,11 @@ export interface SettingsApi {
35
35
  result: {
36
36
  ok: boolean
37
37
  value?: {
38
+ writable: boolean
38
39
  namespaces: Array<{
39
40
  ns: string
41
+ value: unknown
42
+ revision: number
40
43
  secrets: Array<{ path: string[]; set: boolean }>
41
44
  }>
42
45
  }