dsh-model-switch 0.4.21 → 0.4.23

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.js CHANGED
@@ -12,7 +12,83 @@ import { dirname, join } from "node:path";
12
12
  import { renderPrompt } from "@deepseek-ai/dsh-system-prompt";
13
13
  import { boundContextSummary, createMessage, createUserMessage } from "@deepseek-ai/dsh-llm";
14
14
  import { canonicalHeader } from "@deepseek-ai/dsh-session";
15
+ //#region lib/types/provider-group.js
16
+ /**
17
+ * Provider configuration groups: per-provider presets for the compaction,
18
+ * subagent, and auto-review routes.
19
+ *
20
+ * A group stores the exact flat settings fields it will write, so applying a
21
+ * group is a plain settings-layer write: every capability consumer keeps
22
+ * reading the live values unchanged. All functions here are pure and never
23
+ * throw on malformed storage.
24
+ */
25
+ const ROUTE_FIELDS = Object.freeze([
26
+ "compactProvider",
27
+ "compactModel",
28
+ "autoReviewProvider",
29
+ "autoReviewModel",
30
+ "subagentProvider",
31
+ "subagentModel",
32
+ "subagentReasoningEffort"
33
+ ]);
34
+ /** Own data property, so a literal '__proto__' provider id can never touch a prototype. */
35
+ function setKey(target, key, value) {
36
+ Object.defineProperty(target, key, {
37
+ value,
38
+ enumerable: true,
39
+ writable: true,
40
+ configurable: true
41
+ });
42
+ }
43
+ /** A non-blank string. Blank values behave exactly like an absent field. */
44
+ function present(value) {
45
+ return typeof value === "string" && value.trim() !== "";
46
+ }
47
+ /** Normalize one raw entry to its owning provider and route view; undefined for non-object junk. */
48
+ function normalizeEntry(value) {
49
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
50
+ const source = value;
51
+ const group = {};
52
+ for (const field of ROUTE_FIELDS) {
53
+ const item = source[field];
54
+ if (present(item)) group[field] = item;
55
+ }
56
+ return {
57
+ provider: present(source.provider) ? source.provider : "",
58
+ group
59
+ };
60
+ }
61
+ /**
62
+ * Strip a raw stored value to well-formed groups; never throws. Both storage
63
+ * shapes are accepted: the stored entry array (keys live in each entry's
64
+ * `provider` field) and a hand-written provider → routes map (keys are the
65
+ * providers). All-empty entries stay: they pin a provider to every default.
66
+ */
67
+ function normalizeProviderGroups(raw) {
68
+ const groups = {};
69
+ if (Array.isArray(raw)) {
70
+ for (const entry of raw) {
71
+ const view = normalizeEntry(entry);
72
+ if (view === void 0 || view.provider === "") continue;
73
+ setKey(groups, view.provider, view.group);
74
+ }
75
+ return groups;
76
+ }
77
+ if (raw !== null && typeof raw === "object") for (const [key, value] of Object.entries(raw)) {
78
+ if (key.trim() === "") continue;
79
+ const view = normalizeEntry(value);
80
+ if (view === void 0) continue;
81
+ setKey(groups, key, view.group);
82
+ }
83
+ return groups;
84
+ }
85
+ //#endregion
15
86
  //#region lib/types/host-settings.js
87
+ /**
88
+ * Annotated rather than inferred: the nested entry schema's inferred type
89
+ * expands to cosmokit internals (`Dict`) that cannot be named in the emitted
90
+ * declaration, and the annotation pins the output shape to this interface.
91
+ */
16
92
  const Config = z.object({
17
93
  subagentMode: z.union(["follow-main", "fixed"]).default("follow-main").volatile(),
18
94
  subagentProvider: z.string().volatile(),
@@ -26,7 +102,17 @@ const Config = z.object({
26
102
  autoReviewProvider: z.string().volatile(),
27
103
  autoReviewModel: z.string().volatile(),
28
104
  compactProvider: z.string().volatile(),
29
- compactModel: z.string().volatile()
105
+ compactModel: z.string().volatile(),
106
+ providerGroups: z.array(z.object({
107
+ provider: z.string().default(""),
108
+ compactProvider: z.string().default(""),
109
+ compactModel: z.string().default(""),
110
+ autoReviewProvider: z.string().default(""),
111
+ autoReviewModel: z.string().default(""),
112
+ subagentProvider: z.string().default(""),
113
+ subagentModel: z.string().default(""),
114
+ subagentReasoningEffort: z.string().default("")
115
+ })).default([]).volatile()
30
116
  });
31
117
  function readConfig(config) {
32
118
  const subagentProvider = config.subagentProvider.get();
@@ -40,6 +126,7 @@ function readConfig(config) {
40
126
  const autoReviewModel = config.autoReviewModel.get();
41
127
  const compactProvider = config.compactProvider.get();
42
128
  const compactModel = config.compactModel.get();
129
+ const providerGroups = normalizeProviderGroups(config.providerGroups.get());
43
130
  return {
44
131
  subagentMode: config.subagentMode.get(),
45
132
  ...subagentProvider === void 0 ? {} : { subagentProvider },
@@ -53,7 +140,8 @@ function readConfig(config) {
53
140
  ...autoReviewProvider === void 0 || autoReviewProvider === "" ? {} : { autoReviewProvider },
54
141
  ...autoReviewModel === void 0 || autoReviewModel === "" ? {} : { autoReviewModel },
55
142
  ...compactProvider === void 0 || compactProvider === "" ? {} : { compactProvider },
56
- ...compactModel === void 0 || compactModel === "" ? {} : { compactModel }
143
+ ...compactModel === void 0 || compactModel === "" ? {} : { compactModel },
144
+ ...Object.keys(providerGroups).length === 0 ? {} : { providerGroups }
57
145
  };
58
146
  }
59
147
  //#endregion
@@ -877,9 +965,8 @@ async function compactWithPrevious(ctx, agent, previous, signal) {
877
965
  }
878
966
  //#endregion
879
967
  //#region lib/types/host-runtime.js
880
- /** Host owner for Model Switch settings and the released Main-default adapter. */
968
+ /** Host owner for Model Switch settings. */
881
969
  var ModelSwitchRuntime = class extends Service {
882
- static inject = ["agentDefaultModel"];
883
970
  static Config = Config;
884
971
  get capabilities() {
885
972
  const catalog = this.adapters.searchCatalog();
@@ -916,19 +1003,7 @@ var ModelSwitchRuntime = class extends Service {
916
1003
  currentSettings() {
917
1004
  return { ...this.source() };
918
1005
  }
919
- currentMainSelection() {
920
- return { ...this.ctx.agentDefaultModel.currentSelection() };
921
- }
922
- async saveMainSelection(selection) {
923
- await this.ctx.agentDefaultModel.saveSelection(selection);
924
- }
925
1006
  };
926
- function mainDefaultPort(service) {
927
- return {
928
- currentSelection: () => ({ ...service.currentSelection() }),
929
- saveSelection: async (selection) => service.saveSelection(selection)
930
- };
931
- }
932
1007
  const CONTEXT_SUFFIX = /-(\d+)(k|m)$/iu;
933
1008
  /** Peel Fast and `-<n>k` / `-<n>m` in either order. Product names like `-max` stay. */
934
1009
  function parsePickerId(id) {
@@ -1029,17 +1104,12 @@ function selectPlanReview(owner) {
1029
1104
  }
1030
1105
  async function approvePlanReview(args) {
1031
1106
  const current = args.current;
1032
- const commit = current !== void 0 && current !== null && current.provider === args.selection.provider && current.model === args.selection.model && current.reasoningEffort === args.selection.reasoningEffort ? {
1033
- committed: true,
1034
- mainDefaultRestored: true
1035
- } : await args.select(args.selection);
1036
- if (!commit.committed) return false;
1107
+ if (!(current !== void 0 && current !== null && current.provider === args.selection.provider && current.model === args.selection.model && current.reasoningEffort === args.selection.reasoningEffort ? { committed: true } : await args.select(args.selection)).committed) return false;
1037
1108
  await args.answer();
1038
- if (!commit.mainDefaultRestored) args.onNotRestored?.(args.selection.model);
1039
1109
  return true;
1040
1110
  }
1041
1111
  //#endregion
1042
1112
  //#region lib/types/index.js
1043
1113
  const name = "dsh-model-switch";
1044
1114
  //#endregion
1045
- export { Config, ModelSwitchAdapterRegistry, ModelSwitchRuntime, ModelSwitchRuntime as default, RUNTIME_CAPABILITIES, approvePlanReview, groupFamilies, mainDefaultPort, name, parsePickerId, planReviewOf, readConfig, selectPlanReview };
1115
+ export { Config, ModelSwitchAdapterRegistry, ModelSwitchRuntime, ModelSwitchRuntime as default, RUNTIME_CAPABILITIES, approvePlanReview, groupFamilies, name, parsePickerId, planReviewOf, readConfig, selectPlanReview };
@@ -2,7 +2,8 @@ import { type ReactNode } from 'react';
2
2
  import type { ModelProviderGroup } from '@deepseek-ai/dsh-api-session-controller/types';
3
3
  import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client';
4
4
  import type { InjectFace, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
5
- import { type CapabilityRouteView, type MainSettingsView, type ModelSwitchSettingsView, type SubagentSettingsView } from '../client-contract.js';
5
+ import { type CapabilityRouteView, type ModelSwitchSettingsView, type SubagentSettingsView } from '../client-contract.js';
6
+ import { type ProviderGroupConfig } from '../provider-group.js';
6
7
  import type { RuntimeCapabilities } from '../runtime-capabilities.js';
7
8
  import { type CapabilitiesSnapshot } from './search-capabilities.js';
8
9
  import type { ModelSwitchLocaleKey } from './locales.js';
@@ -10,7 +11,6 @@ import type { ConfigViewForm } from './derived-config-form.js';
10
11
  export interface ModelSwitchSettingsFace {
11
12
  t: (key: ModelSwitchLocaleKey) => string;
12
13
  hooks: {
13
- mainSettings: ConfigViewForm<MainSettingsView>;
14
14
  subagentSettings: ConfigViewForm<SubagentSettingsView>;
15
15
  searchSettings: ConfigViewForm<CapabilityRouteView>;
16
16
  imageSettings: ConfigViewForm<CapabilityRouteView>;
@@ -19,10 +19,11 @@ export interface ModelSwitchSettingsFace {
19
19
  switchSettings: Pick<ConfigForm<ModelSwitchSettingsView>, 'getSnapshot' | 'subscribe'>;
20
20
  };
21
21
  capabilities: RuntimeCapabilities;
22
- saveMain: (next: MainSettingsView, expectedRevision: number) => Promise<number>;
23
22
  setSubagent: (field: 'mode' | 'provider' | 'model' | 'reasoningEffort', value: string | undefined) => Promise<void>;
24
23
  setCapability: (route: 'search' | 'image' | 'autoReview' | 'compaction', field: 'provider' | 'model', value: string | undefined) => Promise<void>;
25
24
  setCompactOnSwitch: (value: boolean) => Promise<void>;
25
+ /** Replace or remove (undefined) one provider's saved capability group. */
26
+ saveProviderGroup: (provider: string, group: ProviderGroupConfig | undefined) => Promise<void>;
26
27
  loadCatalog: () => Promise<readonly ModelProviderGroup[]>;
27
28
  /** Host capabilities long-poll; absent in legacy faces, which keep the static-capabilities path. */
28
29
  loadCapabilities?: (revision?: number, signal?: AbortSignal) => Promise<CapabilitiesSnapshot>;
@@ -1,22 +1,21 @@
1
1
  export declare const zh: {
2
2
  readonly nav: "模型切换";
3
3
  readonly title: "模型切换";
4
- readonly subtitle: "为主会话、子代理和能力工具设置默认模型。修改只影响新请求。";
4
+ readonly subtitle: "为子代理与能力路由设置固定模型;当前会话模型用输入框旁的选择器直接切换。";
5
5
  readonly conversationRoutes: "对话路由";
6
6
  readonly capabilityRoutes: "能力路由";
7
7
  readonly sendProtection: "发送保护";
8
8
  readonly settingsSynced: "设置已同步";
9
9
  readonly remoteSettingsUnavailable: "官方 DSH 在非本机浏览器禁用配置读取与写入;请在 Host 的 127.0.0.1 页面打开设置。";
10
- readonly defaultBadge: "默认";
11
10
  readonly cancel: "取消";
12
11
  readonly compactOnSwitch: "切换时压缩";
13
12
  readonly compactOnSwitchHelp: "发送且换了模型时,先按目标窗口检查上下文;超了就用切换前的模型压一次历史,再继续请求。只选模型、不发送,不会压缩。官方自动压缩和 /compact 不受影响。压缩会产生额外模型用量,摘要可能省略细节。";
14
- readonly main: "主模型";
15
13
  readonly provider: "提供商";
16
14
  readonly model: "模型";
17
15
  readonly providerDefault: "使用提供商默认值";
18
16
  readonly providerDefaultShort: "Provider 默认";
19
17
  readonly effort: "推理强度";
18
+ readonly effortPinned: "已固定";
20
19
  readonly save: "保存";
21
20
  readonly saving: "保存中…";
22
21
  readonly saved: "已保存";
@@ -30,10 +29,8 @@ export declare const zh: {
30
29
  readonly imageHelp: "统一 generate_image 会调用所选 Codex 或 Grok Adapter;Provider 原有图片工具仍保留。";
31
30
  readonly unavailable: "未接入";
32
31
  readonly loading: "正在加载设置…";
33
- readonly readonly: "设置为只读";
34
32
  readonly requestFailed: "保存失败";
35
33
  readonly catalogFailed: "无法加载模型目录。";
36
- readonly conflict: "设置已在其他位置更改,请检查最新值后重试。";
37
34
  readonly autoReview: "自动审查模型";
38
35
  readonly autoReviewFollow: "跟随会话模型";
39
36
  readonly autoReviewHelp: "为官方 auto-review(Auto 权限预设的逐调用审查)指定审查模型;需启用官方 auto-review 能力包。关闭时审查跟随当前会话模型。";
@@ -42,6 +39,13 @@ export declare const zh: {
42
39
  readonly compactionFollow: "跟随会话模型";
43
40
  readonly compactionHelp: "为官方压缩(/compact 与官方自动压缩)指定摘要模型;关闭(两个字段都留空)时使用官方默认,即当前会话的目标模型。";
44
41
  readonly compactionRouteHelp: "摘要请求固定使用这里的 Provider/模型,与会话目标模型无关;保存后对下一次压缩立即生效。保存时若恰有压缩进行中,可能被打断并在下次重试。";
42
+ readonly providerGroups: "供应商配置组";
43
+ readonly providerGroupsCard: "按供应商预设路由";
44
+ readonly providerGroupsSummary: "会话模型切换到该供应商时自动应用对应组";
45
+ readonly providerGroupsHelp: "为压缩、子代理、审查按供应商分别保存路由。";
46
+ readonly providerGroupsBodyHelp: "每个供应商保存一组压缩、子代理、审查路由;本会话的模型切换到该供应商时(无论通过插件的模型选择器还是 TUI 等其他界面),这三条路由自动写入上方对应卡片。切换到未配置的供应商时现有路由保持不变;应用会覆盖三张卡片的当前值(含手动修改)。";
47
+ readonly providerGroupPick: "选择供应商以编辑其配置组。";
48
+ readonly providerGroupDelete: "删除该组";
45
49
  readonly 'reason.central-subagent-routing': "Alpha.4 没有全局子代理启动路由接口。";
46
50
  readonly 'reason.packaged-preset-roots': "Alpha.4 不支持插件提供额外 preset root。";
47
51
  readonly 'reason.tool-owner-suppression': "Alpha.4 没有工具所有者或来源抑制接口。";
@@ -1,49 +1,30 @@
1
1
  import type { ModelProviderGroup } from '@deepseek-ai/dsh-api-session-controller/types';
2
- import type { ConfigFormSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client';
3
- import { type CapabilityRouteView, type MainSettingsView, type SubagentSettingsView } from '../client-contract.js';
2
+ import type { CapabilityRouteView } from '../client-contract.js';
4
3
  import type { ModelSwitchLocaleKey } from './locales.js';
5
- type Share<T> = (selector: (snapshot: ConfigFormSnapshot<T>) => ConfigFormSnapshot<T>) => ConfigFormSnapshot<T>;
6
- export interface SettingsControllerInputs {
7
- useMainSettings: Share<MainSettingsView>;
8
- useSubagentSettings: Share<SubagentSettingsView>;
9
- saveMain(next: MainSettingsView, expectedRevision: number): Promise<number>;
10
- loadCatalog(): Promise<readonly ModelProviderGroup[]>;
11
- subscribeProviderOrder?: (listener: () => void) => () => void;
12
- t(key: ModelSwitchLocaleKey): string;
13
- }
14
4
  export interface Choice {
15
5
  id: string;
16
6
  name: string;
17
7
  unavailable?: true;
18
8
  }
9
+ /** A concrete provider/model/effort triple; route editors copy these fields. */
10
+ export interface RouteModelSelection {
11
+ provider: string;
12
+ model: string;
13
+ reasoningEffort?: string;
14
+ }
19
15
  /** Build a settings route for a newly selected model using only that model's default effort. */
20
- export declare function selectRouteModel(groups: readonly ModelProviderGroup[], provider: string, model: string): MainSettingsView;
16
+ export declare function selectRouteModel(groups: readonly ModelProviderGroup[], provider: string, model: string): RouteModelSelection;
21
17
  export declare function deriveRouteChoices(groups: readonly ModelProviderGroup[], route: CapabilityRouteView | undefined, allowedProviders?: readonly string[]): {
22
18
  providers: Choice[];
23
19
  models: Choice[];
24
20
  };
25
- export declare function expectedMainRevision(mirror: number, accepted?: number): number;
26
- export declare function acceptedRevisionAfterFailure(accepted: number | undefined, error: unknown): number | undefined;
27
- export declare function deriveMainChoices(groups: readonly ModelProviderGroup[], draft?: MainSettingsView): {
28
- providers: Choice[];
29
- models: Choice[];
30
- efforts: Choice[];
31
- };
32
- export declare function useModelSwitchSettingsController(input: SettingsControllerInputs): {
33
- main: ConfigFormSnapshot<MainSettingsView>;
34
- subagent: ConfigFormSnapshot<SubagentSettingsView>;
35
- draft: MainSettingsView | undefined;
21
+ export interface SettingsCatalogInputs {
22
+ loadCatalog(): Promise<readonly ModelProviderGroup[]>;
23
+ subscribeProviderOrder?: (listener: () => void) => () => void;
24
+ t(key: ModelSwitchLocaleKey): string;
25
+ }
26
+ /** Load the model catalog backing every route editor, reloading on provider-order changes. */
27
+ export declare function useSettingsCatalog(input: SettingsCatalogInputs): {
36
28
  groups: readonly ModelProviderGroup[];
37
- providers: Choice[];
38
- models: Choice[];
39
- efforts: Choice[];
40
- busy: boolean;
41
- message: string | undefined;
42
- disabled: boolean;
43
- setProvider: (provider: string) => void;
44
- setModel: (id: string) => void;
45
- setReasoningEffort: (value: string) => void;
46
- reset: () => void;
47
- save: () => Promise<void>;
29
+ catalogError: string | undefined;
48
30
  };
49
- export {};
@@ -13,8 +13,6 @@ interface ComposerPickerBaseProps {
13
13
  /** Hide/disable Agent-role groups on existing DSH sessions that are not native-bound. */
14
14
  agentLocked?: boolean;
15
15
  available: boolean;
16
- /** Explain a disabled picker when the official Host has no persistent settings channel. */
17
- unavailableReason?: string;
18
16
  directory: PickerDirectoryView;
19
17
  t: (key: PickerKey, params?: Record<string, string>) => string;
20
18
  embedded?: boolean;
@@ -44,4 +42,4 @@ export interface ModelPaneHeaderProps {
44
42
  onQueryChange: (query: string) => void;
45
43
  }
46
44
  export declare function ModelPaneHeader({ title, backLabel, searchLabel, closeSearchLabel, searchable, searching, query, onBack, onStartSearch, onCloseSearch, onQueryChange, }: ModelPaneHeaderProps): import("react").JSX.Element;
47
- export declare function ComposerPicker({ locked, providerLock, agentLocked, available, unavailableReason, directory, t, draft, onDraftChange, embedded, tone, resolveInteractionOperations, roleOf, }: ComposerPickerProps): import("react").JSX.Element | null;
45
+ export declare function ComposerPicker({ locked, providerLock, agentLocked, available, directory, t, draft, onDraftChange, embedded, tone, resolveInteractionOperations, roleOf, }: ComposerPickerProps): import("react").JSX.Element | null;
@@ -1,7 +1,5 @@
1
1
  import type { PendingQuestion } from '@deepseek-ai/dsh-client-ui-user-questions/client';
2
- import type { ConfigFormSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client';
3
2
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
4
- import type { MainSettingsView } from '../../client-contract.ts';
5
3
  import { type GoalRemote } from '../../picker/plan-goal.ts';
6
4
  import type { PlanPreviewBinding } from './plan-preview.ts';
7
5
  import { type PickerDirectoryFace } from './PickerDirectory.ts';
@@ -22,8 +20,6 @@ export interface PlanReviewFace extends PickerDirectoryFace {
22
20
  };
23
21
  /** Re-read the native binding now (mount, turn transitions, pre-selection). */
24
22
  refreshProviderLock: () => void;
25
- subscribeMainDefaults: (listener: () => void) => () => void;
26
- getMainDefaultsSnapshot: () => ConfigFormSnapshot<MainSettingsView>;
27
23
  /** Released goal remote; absent when this deployment mounts no goal service. */
28
24
  goalRemote: () => GoalRemote | undefined;
29
25
  /** Full-plan sidebar navigation (ui-plan's registered opener). */
@@ -34,8 +30,6 @@ export interface PlanReviewFace extends PickerDirectoryFace {
34
30
  export type PlanReviewCardProps = PropsRuntime<'conversation.composer'> & PropsLocale<'composer-picker'> & InjectFace<PlanReviewFace> & {
35
31
  matched: PendingQuestion;
36
32
  };
37
- /** Match the model seat's admission guard before letting either picker offer a change. */
38
- export declare function mainDefaultsUnavailableReason(snapshot: ConfigFormSnapshot<MainSettingsView>, t: PlanReviewCardProps['t']): string | undefined;
39
33
  /** Inline failed lock-read status; history and log reading stay unaffected. */
40
34
  export declare function ProviderLockHint(props: {
41
35
  t: PlanReviewCardProps['t'];
@@ -20,9 +20,6 @@ export declare const zh: {
20
20
  'thinking.off': string;
21
21
  'effort.providerDefault': string;
22
22
  'status.loading': string;
23
- 'settings.remoteUnavailable': string;
24
- 'settings.loading': string;
25
- 'settings.unavailable': string;
26
23
  'error.action': string;
27
24
  'error.picker': string;
28
25
  'action.reload': string;
@@ -43,7 +40,6 @@ export declare const zh: {
43
40
  'plan.pickerCrash': string;
44
41
  'plan.feedback': string;
45
42
  'plan.feedbackPlaceholder': string;
46
- 'plan.notRestored': string;
47
43
  'plan.dismissUnsupported': string;
48
44
  'plan.goalRun': string;
49
45
  'plan.goalFailed': string;
@@ -77,9 +73,6 @@ export declare const en: {
77
73
  'thinking.off': string;
78
74
  'effort.providerDefault': string;
79
75
  'status.loading': string;
80
- 'settings.remoteUnavailable': string;
81
- 'settings.loading': string;
82
- 'settings.unavailable': string;
83
76
  'error.action': string;
84
77
  'error.picker': string;
85
78
  'action.reload': string;
@@ -100,7 +93,6 @@ export declare const en: {
100
93
  'plan.pickerCrash': string;
101
94
  'plan.feedback': string;
102
95
  'plan.feedbackPlaceholder': string;
103
- 'plan.notRestored': string;
104
96
  'plan.dismissUnsupported': string;
105
97
  'plan.goalRun': string;
106
98
  'plan.goalFailed': string;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Apply a provider's saved capability group whenever the seat session's
3
+ * provider changes — on any switch surface.
4
+ *
5
+ * The official model-selection directory projects the Session's durable
6
+ * `model/selection` events into `store.current` (the sole emitter of those
7
+ * events is the `session.selectModel` RPC), so a switch performed through the
8
+ * TUI or any other client lands in the same store this plugin already reads.
9
+ * One watcher therefore covers every surface for sessions open in this Web
10
+ * UI, where the previous wiring only covered this plugin's own two call sites.
11
+ *
12
+ * Semantics stay exactly as before: a provider change applies the target
13
+ * provider's group when one exists; a provider without a group, a same-model
14
+ * change within one provider, and the subscription baseline itself write
15
+ * nothing. A failed apply is reported and never disturbs the switch.
16
+ */
17
+ import type { ModelSelection } from '@deepseek-ai/dsh-api-session-controller/types';
18
+ /** The directory store slice the watcher observes. */
19
+ export interface DirectoryWatchSource {
20
+ subscribe: (listener: () => void) => () => void;
21
+ getSnapshot: () => {
22
+ current: ModelSelection | null;
23
+ };
24
+ }
25
+ export interface ProviderGroupWatchOptions {
26
+ source: DirectoryWatchSource;
27
+ /** Adopt the target provider's saved group; a group-less provider must write nothing. */
28
+ apply: (provider: string) => Promise<void>;
29
+ /** Reported apply failure; defaults to a console warning. */
30
+ onFailure?: (error: unknown) => void;
31
+ }
32
+ export interface ProviderGroupWatch {
33
+ /** Detach from the store; in-flight applies are left to finish. */
34
+ dispose: () => void;
35
+ }
36
+ /**
37
+ * Watch one directory store and apply groups on provider changes.
38
+ *
39
+ * - The first observed provider (or the one already loaded) is the baseline
40
+ * and never triggers a write, so mounting and initial load stay silent.
41
+ * - Applies are serialized: a change arriving mid-apply coalesces to the
42
+ * latest target and runs once the current apply finishes, so two writes
43
+ * never interleave pair writes.
44
+ * - `current` being null (catalog still loading) is ignored, not a change.
45
+ * @param options - the store to watch and the apply to run.
46
+ * @returns the watch handle.
47
+ */
48
+ export declare function createProviderGroupWatch(options: ProviderGroupWatchOptions): ProviderGroupWatch;
@@ -1,14 +1,6 @@
1
+ import type { ProviderGroupsStorage } from './provider-group.js';
1
2
  export declare const MODEL_SWITCH_CONFIG_ID = "model-switch";
2
- export declare const MAIN_DEFAULT_CONFIG_ID = "agent-default-model";
3
3
  export declare const PROVIDERS_CONFIG_ID = "llm-providers-ui";
4
- export interface MainSettingsView {
5
- provider: string;
6
- model: string;
7
- reasoningEffort?: string;
8
- }
9
- export declare class MainSettingsConflictError extends Error {
10
- readonly name = "MainSettingsConflictError";
11
- }
12
4
  export interface ModelSwitchSettingsView {
13
5
  subagentMode: 'follow-main' | 'fixed';
14
6
  subagentProvider?: string;
@@ -23,6 +15,8 @@ export interface ModelSwitchSettingsView {
23
15
  autoReviewModel?: string;
24
16
  compactProvider?: string;
25
17
  compactModel?: string;
18
+ /** Raw saved per-provider groups; edited whole through the provider-groups card. */
19
+ providerGroups?: ProviderGroupsStorage;
26
20
  }
27
21
  export interface SubagentSettingsView {
28
22
  mode: 'follow-main' | 'fixed';
@@ -1,7 +1,5 @@
1
1
  import { Context, Service } from '@deepseek-ai/cordis';
2
- import type { ModelSelection } from '@deepseek-ai/dsh-agent';
3
- import type { AgentDefaultModelConfig } from '@deepseek-ai/dsh-agent-default-model';
4
- import { type Config as ModelSwitchSettingsConfig, type ModelSwitchSettings } from './host-settings.js';
2
+ import { Config, type Config as ModelSwitchSettingsConfig, type ModelSwitchSettings } from './host-settings.js';
5
3
  import { ModelSwitchAdapterRegistry } from './adapter-registry.js';
6
4
  declare module '@deepseek-ai/cordis' {
7
5
  interface Context {
@@ -12,38 +10,9 @@ declare module '@deepseek-ai/cordis' {
12
10
  'model-switch/settings-update'(): void;
13
11
  }
14
12
  }
15
- /** Host owner for Model Switch settings and the released Main-default adapter. */
13
+ /** Host owner for Model Switch settings. */
16
14
  export declare class ModelSwitchRuntime extends Service {
17
- static inject: string[];
18
- static Config: import("@deepseek-ai/schemastery").default<Schemastery.ObjectS<NoInfer<{
19
- subagentMode: import("@deepseek-ai/schemastery").default<"follow-main" | "fixed", "follow-main" | "fixed", "volatile-defined">;
20
- subagentProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
21
- subagentModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
22
- subagentReasoningEffort: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
23
- searchProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
24
- searchModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
25
- imageProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
26
- imageModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
27
- compactOnSwitch: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
28
- autoReviewProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
29
- autoReviewModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
30
- compactProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
31
- compactModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
32
- }>>, Schemastery.ObjectT<NoInfer<{
33
- subagentMode: import("@deepseek-ai/schemastery").default<"follow-main" | "fixed", "follow-main" | "fixed", "volatile-defined">;
34
- subagentProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
35
- subagentModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
36
- subagentReasoningEffort: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
37
- searchProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
38
- searchModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
39
- imageProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
40
- imageModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
41
- compactOnSwitch: import("@deepseek-ai/schemastery").default<boolean, boolean, "volatile-defined">;
42
- autoReviewProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
43
- autoReviewModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
44
- compactProvider: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
45
- compactModel: import("@deepseek-ai/schemastery").default<string, string, "volatile">;
46
- }>>, "plain">;
15
+ static Config: import("@deepseek-ai/schemastery").default<any, Config, "plain">;
47
16
  get capabilities(): {
48
17
  searchProviderAdapters: {
49
18
  available: boolean;
@@ -80,11 +49,4 @@ export declare class ModelSwitchRuntime extends Service {
80
49
  private source;
81
50
  constructor(ctx: Context, entry: ModelSwitchSettingsConfig);
82
51
  currentSettings(): ModelSwitchSettings;
83
- currentMainSelection(): ModelSelection;
84
- saveMainSelection(selection: ModelSelection): Promise<void>;
85
52
  }
86
- export interface MainDefaultPort {
87
- currentSelection(): ModelSelection;
88
- saveSelection(selection: ModelSelection): Promise<void>;
89
- }
90
- export declare function mainDefaultPort(service: AgentDefaultModelConfig): MainDefaultPort;
@@ -1,5 +1,6 @@
1
1
  import type { Volatile } from '@deepseek-ai/cordis';
2
2
  import z from '@deepseek-ai/schemastery';
3
+ import { type ProviderGroupEntry, type ProviderGroups } from './provider-group.js';
3
4
  export type SubagentMode = 'follow-main' | 'fixed';
4
5
  /** Resolved live values consumed by Model Switch. */
5
6
  export interface ModelSwitchSettings {
@@ -20,6 +21,8 @@ export interface ModelSwitchSettings {
20
21
  compactProvider?: string;
21
22
  /** Summarization model for `/compact` and automatic compaction; set together with `compactProvider`. */
22
23
  compactModel?: string;
24
+ /** Per-provider compaction/subagent/review presets, keyed by provider id; absent when no group is saved. */
25
+ providerGroups?: ProviderGroups;
23
26
  }
24
27
  /** Loader-owned references; every field is directly editable in the profile form. */
25
28
  export interface Config {
@@ -36,34 +39,13 @@ export interface Config {
36
39
  autoReviewModel: Volatile<string | undefined>;
37
40
  compactProvider: Volatile<string | undefined>;
38
41
  compactModel: Volatile<string | undefined>;
42
+ /** Saved per-provider capability groups as one entry per provider; entry route fields are '' when the capability stays at its default. */
43
+ providerGroups: Volatile<readonly ProviderGroupEntry[]>;
39
44
  }
40
- export declare const Config: z<Schemastery.ObjectS<NoInfer<{
41
- subagentMode: z<"follow-main" | "fixed", "follow-main" | "fixed", "volatile-defined">;
42
- subagentProvider: z<string, string, "volatile">;
43
- subagentModel: z<string, string, "volatile">;
44
- subagentReasoningEffort: z<string, string, "volatile">;
45
- searchProvider: z<string, string, "volatile">;
46
- searchModel: z<string, string, "volatile">;
47
- imageProvider: z<string, string, "volatile">;
48
- imageModel: z<string, string, "volatile">;
49
- compactOnSwitch: z<boolean, boolean, "volatile-defined">;
50
- autoReviewProvider: z<string, string, "volatile">;
51
- autoReviewModel: z<string, string, "volatile">;
52
- compactProvider: z<string, string, "volatile">;
53
- compactModel: z<string, string, "volatile">;
54
- }>>, Schemastery.ObjectT<NoInfer<{
55
- subagentMode: z<"follow-main" | "fixed", "follow-main" | "fixed", "volatile-defined">;
56
- subagentProvider: z<string, string, "volatile">;
57
- subagentModel: z<string, string, "volatile">;
58
- subagentReasoningEffort: z<string, string, "volatile">;
59
- searchProvider: z<string, string, "volatile">;
60
- searchModel: z<string, string, "volatile">;
61
- imageProvider: z<string, string, "volatile">;
62
- imageModel: z<string, string, "volatile">;
63
- compactOnSwitch: z<boolean, boolean, "volatile-defined">;
64
- autoReviewProvider: z<string, string, "volatile">;
65
- autoReviewModel: z<string, string, "volatile">;
66
- compactProvider: z<string, string, "volatile">;
67
- compactModel: z<string, string, "volatile">;
68
- }>>, "plain">;
45
+ /**
46
+ * Annotated rather than inferred: the nested entry schema's inferred type
47
+ * expands to cosmokit internals (`Dict`) that cannot be named in the emitted
48
+ * declaration, and the annotation pins the output shape to this interface.
49
+ */
50
+ export declare const Config: z<any, Config, 'plain'>;
69
51
  export declare function readConfig(config: Config): ModelSwitchSettings;
@@ -1,5 +1,5 @@
1
1
  export { Config, readConfig, type ModelSwitchSettings } from './host-settings.js';
2
- export { mainDefaultPort, ModelSwitchRuntime, type MainDefaultPort } from './host-runtime.js';
2
+ export { ModelSwitchRuntime } from './host-runtime.js';
3
3
  export { ModelSwitchAdapterRegistry, type ModelSwitchProviderAdapters, type ModelSwitchSearchAdapter, type ModelSwitchImageAdapter, type ModelSwitchImageRequest, type ModelSwitchGeneratedImage } from './adapter-registry.js';
4
4
  export { RUNTIME_CAPABILITIES, type RuntimeCapabilities, type RuntimeCapability } from './runtime-capabilities.js';
5
5
  export { parsePickerId, groupFamilies, planReviewOf, selectPlanReview, approvePlanReview, } from './picker/public.ts';
@@ -14,18 +14,12 @@ export declare class PlanApprovalResponseError extends Error {
14
14
  /**
15
15
  * Result of committing the draft execution model before answering a review.
16
16
  *
17
- * A commit and the compensation of the Host's own deployment-default write are
18
- * separate facts: only a failed commit may hold the answer back, because the
19
- * Host persists a Session selection as the deployment default on several
20
- * releases and this plugin restores the captured Main default afterwards. A
21
- * failed restore is announced, never fatal — the Session selection the human
22
- * asked for is already installed.
17
+ * Only a failed commit may hold the answer back: once `committed` is true the
18
+ * Session runs the requested selection, and the answer proceeds under it.
23
19
  */
24
20
  export interface PlanCommitOutcome {
25
21
  /** The Session now runs the requested provider/model/effort. */
26
22
  committed: boolean;
27
- /** The captured Main default is the effective one again. */
28
- mainDefaultRestored: boolean;
29
23
  }
30
24
  /**
31
25
  * Diagnostic text for one failed action, keeping the wire code visible.
@@ -41,8 +35,6 @@ export declare function approvePlanReview(args: {
41
35
  selection: ModelSelection;
42
36
  current?: ModelSelection | null;
43
37
  answer: () => Promise<void>;
44
- /** Announced after a successful answer when the Main default could not be restored. */
45
- onNotRestored?: (notice: string) => void;
46
38
  }): Promise<boolean>;
47
39
  export interface PlanActionState {
48
40
  busy: boolean;