dsh-model-switch 0.4.19 → 0.4.21

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.
@@ -0,0 +1,205 @@
1
+ //#region lib/types/compaction.js
2
+ /** Official compaction row this module supersedes. */
3
+ const OFFICIAL_COMPACTION_ROW = "compaction-basic";
4
+ /** Official compaction package this module mounts in its place. */
5
+ const OFFICIAL_COMPACTION_PACKAGE = "@deepseek-ai/dsh-compaction-basic";
6
+ /** Cordis plugin identity; matches the cordis.patch.yml insert row id. */
7
+ const name = "model-switch-compaction";
8
+ /** The loader is awaited at row activation; settings and the official row are still read structurally so stock compaction survives a disabled Model Switch row. */
9
+ const inject = ["loader"];
10
+ /**
11
+ * Resolve the configurable summarization route from live settings.
12
+ * @param settings - Model Switch settings carrying the optional compaction pair.
13
+ * @returns Both identifiers trimmed and non-empty, or undefined (official default).
14
+ */
15
+ function compactionRouteOf(settings) {
16
+ const provider = settings?.compactProvider?.trim();
17
+ const model = settings?.compactModel?.trim();
18
+ if (provider === void 0 || provider === "" || model === void 0 || model === "") return void 0;
19
+ return {
20
+ provider,
21
+ model
22
+ };
23
+ }
24
+ /**
25
+ * Merge the resolved route over the official row's own config.
26
+ * @param forwarded - config the official compaction-basic row was configured with.
27
+ * @param route - resolved summarization route; undefined keeps official behavior.
28
+ * @returns A detached config safe to hand to a fresh engine construction.
29
+ */
30
+ function composeCompactionConfig(forwarded, route) {
31
+ if (!route) return { ...forwarded };
32
+ return {
33
+ ...forwarded,
34
+ summarizationProvider: route.provider,
35
+ summarizationModel: route.model
36
+ };
37
+ }
38
+ /**
39
+ * Locate the official compaction row in the loader tree.
40
+ * Disabled entries stay in the tree, so a name-matched disable still matches.
41
+ * @param loader - the host loader, when one is reachable.
42
+ * @returns The official entry, or undefined when this profile has no such row.
43
+ */
44
+ function officialCompactionEntry(loader) {
45
+ if (!loader) return void 0;
46
+ for (const entry of loader.entries()) {
47
+ if (entry.id === OFFICIAL_COMPACTION_ROW) return entry;
48
+ if (entry.options?.name === OFFICIAL_COMPACTION_PACKAGE) return entry;
49
+ }
50
+ }
51
+ /**
52
+ * Copy the official row's stored config, ignoring non-object payloads.
53
+ * @param entry - the official compaction entry.
54
+ * @returns A plain object spread; missing or invalid configs become `{}`.
55
+ */
56
+ function forwardedConfig(entry) {
57
+ const raw = entry.options?.config;
58
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) return raw;
59
+ return {};
60
+ }
61
+ /**
62
+ * Read the live summarization route from the Model Switch host.
63
+ *
64
+ * The host is read structurally instead of injected so this row keeps mounting
65
+ * the official engine (on official defaults) when the `model-switch` row is
66
+ * disabled.
67
+ * @param ctx - this row's context.
68
+ * @returns The configured route, or undefined when unset or host-unavailable.
69
+ */
70
+ function currentCompactionRoute(ctx) {
71
+ let settings;
72
+ try {
73
+ settings = ctx.get("modelSwitch", false)?.currentSettings();
74
+ } catch {
75
+ settings = void 0;
76
+ }
77
+ if (!settings) {
78
+ ctx.logger.info("model-switch: host settings unavailable; compaction uses the official summarization default");
79
+ return;
80
+ }
81
+ return compactionRouteOf(settings);
82
+ }
83
+ /**
84
+ * Load and mount the official basic compaction engine under `config`.
85
+ * @param ctx - this row's context; the engine mounts unisolated so `/compact` sees it.
86
+ * @param config - composed engine config.
87
+ * @param label - which config attempt failed, for the error message.
88
+ * @returns The mounted fiber, or undefined with an explicit error log.
89
+ */
90
+ async function mountEngine(ctx, config, label) {
91
+ let loaded;
92
+ try {
93
+ loaded = await import("@deepseek-ai/dsh-compaction-basic");
94
+ } catch (cause) {
95
+ ctx.logger.error("model-switch: failed to load the official compaction engine package");
96
+ ctx.logger.error(cause);
97
+ return;
98
+ }
99
+ let fiber;
100
+ try {
101
+ fiber = ctx.plugin(loaded.default, config);
102
+ await fiber;
103
+ return fiber;
104
+ } catch (cause) {
105
+ if (fiber) try {
106
+ await fiber.dispose();
107
+ } catch {}
108
+ ctx.logger.error(`model-switch: failed to mount the compaction engine with ${label}`);
109
+ ctx.logger.error(cause);
110
+ return;
111
+ }
112
+ }
113
+ /**
114
+ * Dispose the live engine, keeping the ledger clean for the next mount.
115
+ * @param ctx - this row's context.
116
+ * @param state - the row's reconcile ledger.
117
+ */
118
+ async function disposeEngine(ctx, state) {
119
+ const previous = state.engine;
120
+ state.engine = void 0;
121
+ if (!previous) return;
122
+ try {
123
+ await previous.dispose();
124
+ } catch (cause) {
125
+ ctx.logger.error("model-switch: failed to dispose the previous compaction engine");
126
+ ctx.logger.error(cause);
127
+ }
128
+ }
129
+ /**
130
+ * Reconcile one compaction mount: remount only when the composed config changed.
131
+ *
132
+ * Failure ladder: configured config → official defaults (`{}`) → unmounted with
133
+ * explicit errors, so the next settings event retries the whole ladder.
134
+ * @param ctx - this row's context.
135
+ * @param state - the row's reconcile ledger.
136
+ */
137
+ async function runReconcile(ctx, state) {
138
+ const entry = officialCompactionEntry(ctx.get("loader", false));
139
+ if (!entry) {
140
+ if (!state.missingRow) {
141
+ state.missingRow = true;
142
+ ctx.logger.info("model-switch: no official compaction-basic row in this profile; the compaction engine stays unmounted");
143
+ }
144
+ return;
145
+ }
146
+ state.missingRow = false;
147
+ const config = composeCompactionConfig(forwardedConfig(entry), currentCompactionRoute(ctx));
148
+ const signature = JSON.stringify(config);
149
+ if (state.signature === signature && state.engine) return;
150
+ await disposeEngine(ctx, state);
151
+ state.signature = signature;
152
+ const fiber = await mountEngine(ctx, config, "the configured summarization route");
153
+ if (fiber) {
154
+ state.engine = fiber;
155
+ return;
156
+ }
157
+ ctx.logger.error("model-switch: retrying the compaction engine on the official defaults");
158
+ const fallback = await mountEngine(ctx, {}, "the official defaults");
159
+ if (fallback) {
160
+ state.engine = fallback;
161
+ return;
162
+ }
163
+ ctx.logger.error("model-switch: compaction engine is unmounted; /compact and automatic compaction are unavailable until settings change");
164
+ }
165
+ /**
166
+ * Queue a reconcile behind the row's serialized chain, absorbing failures.
167
+ * @param ctx - this row's context.
168
+ * @param state - the row's reconcile ledger.
169
+ * @returns The chain tail; never rejects.
170
+ */
171
+ function scheduleReconcile(ctx, state) {
172
+ state.chain = state.chain.then(() => runReconcile(ctx, state)).catch((cause) => {
173
+ ctx.logger.error("model-switch: compaction engine reconcile failed");
174
+ ctx.logger.error(cause);
175
+ });
176
+ return state.chain;
177
+ }
178
+ /**
179
+ * Mount the Model Switch compaction engine for this profile.
180
+ *
181
+ * Installs a sibling row that replaces the official compaction-basic engine
182
+ * with the same package under the configurable summarization route. Host
183
+ * settings and the loader are read structurally so a disabled `model-switch`
184
+ * row leaves official compaction untouched. Reconciles run on
185
+ * `model-switch/settings-update` (emitted by the host on volatile commits and
186
+ * ordinary saves); the listener is global so instances of this row inside
187
+ * agent-preset realms still hear the host row's emission. The initial
188
+ * reconcile is awaited so `/compact` resolves the service as soon as this row
189
+ * completes.
190
+ * @param ctx - this row's context.
191
+ */
192
+ async function apply(ctx) {
193
+ const state = {
194
+ chain: Promise.resolve(),
195
+ signature: null,
196
+ engine: void 0,
197
+ missingRow: false
198
+ };
199
+ ctx.on("model-switch/settings-update", () => {
200
+ scheduleReconcile(ctx, state);
201
+ }, { global: true });
202
+ await scheduleReconcile(ctx, state);
203
+ }
204
+ //#endregion
205
+ export { apply, compactionRouteOf, composeCompactionConfig, forwardedConfig, inject, name, officialCompactionEntry };
package/lib/index.js CHANGED
@@ -22,7 +22,11 @@ const Config = z.object({
22
22
  searchModel: z.string().volatile(),
23
23
  imageProvider: z.string().volatile(),
24
24
  imageModel: z.string().volatile(),
25
- compactOnSwitch: z.boolean().default(true).volatile()
25
+ compactOnSwitch: z.boolean().default(true).volatile(),
26
+ autoReviewProvider: z.string().volatile(),
27
+ autoReviewModel: z.string().volatile(),
28
+ compactProvider: z.string().volatile(),
29
+ compactModel: z.string().volatile()
26
30
  });
27
31
  function readConfig(config) {
28
32
  const subagentProvider = config.subagentProvider.get();
@@ -32,6 +36,10 @@ function readConfig(config) {
32
36
  const searchModel = config.searchModel.get();
33
37
  const imageProvider = config.imageProvider.get();
34
38
  const imageModel = config.imageModel.get();
39
+ const autoReviewProvider = config.autoReviewProvider.get();
40
+ const autoReviewModel = config.autoReviewModel.get();
41
+ const compactProvider = config.compactProvider.get();
42
+ const compactModel = config.compactModel.get();
35
43
  return {
36
44
  subagentMode: config.subagentMode.get(),
37
45
  ...subagentProvider === void 0 ? {} : { subagentProvider },
@@ -41,7 +49,11 @@ function readConfig(config) {
41
49
  ...searchModel === void 0 ? {} : { searchModel },
42
50
  ...imageProvider === void 0 ? {} : { imageProvider },
43
51
  ...imageModel === void 0 ? {} : { imageModel },
44
- compactOnSwitch: config.compactOnSwitch.get()
52
+ compactOnSwitch: config.compactOnSwitch.get(),
53
+ ...autoReviewProvider === void 0 || autoReviewProvider === "" ? {} : { autoReviewProvider },
54
+ ...autoReviewModel === void 0 || autoReviewModel === "" ? {} : { autoReviewModel },
55
+ ...compactProvider === void 0 || compactProvider === "" ? {} : { compactProvider },
56
+ ...compactModel === void 0 || compactModel === "" ? {} : { compactModel }
45
57
  };
46
58
  }
47
59
  //#endregion
@@ -897,7 +909,9 @@ var ModelSwitchRuntime = class extends Service {
897
909
  ctx.logger.error("Model Switch: failed to regenerate generate_image schema");
898
910
  ctx.logger.error(error);
899
911
  });
912
+ ctx.emit("model-switch/settings-update");
900
913
  });
914
+ ctx.emit("model-switch/settings-update");
901
915
  }
902
916
  currentSettings() {
903
917
  return { ...this.source() };
@@ -1003,6 +1017,7 @@ function planReviewOf(questions) {
1003
1017
  id: question.id,
1004
1018
  question: question.question,
1005
1019
  plan: question.detail,
1020
+ ...intent.callId === void 0 ? {} : { callId: intent.callId },
1006
1021
  approve,
1007
1022
  ...decline === void 0 ? {} : { decline }
1008
1023
  };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Model Switch-owned Auto reviewer row.
3
+ *
4
+ * The official `@deepseek-ai/dsh-experimental-auto-review` gate hardwires its
5
+ * reviewer to the session's own request route (`header.config`). This row runs
6
+ * the official `apply` under a scoped child context whose `llm` service is
7
+ * shadowed by a wrapper that rewrites only `stream()`'s provider/model — and
8
+ * only while the profile has a complete reviewer route configured. With no
9
+ * route configured the wrapper passes options through by identity, so the
10
+ * reviewer follows the session model exactly.
11
+ *
12
+ * The official gate stays an opt-in capability: the row mounts it only when
13
+ * the profile actually started with the experimental-auto-review bundle.
14
+ * `cordis.patch.yml` disables the official row (one `registerAuto` per
15
+ * context), so this row is its replacement rather than a second registration.
16
+ *
17
+ * @module dsh-model-switch/auto-review
18
+ */
19
+ import type { Context } from '@deepseek-ai/cordis';
20
+ import type { GenerateOptions, StreamChunk } from '@deepseek-ai/dsh-llm';
21
+ /** Cordis plugin name used by loader diagnostics. */
22
+ export declare const name = "model-switch-auto-review";
23
+ /** The official gate's host services plus Model Switch's settings owner. */
24
+ export declare const inject: string[];
25
+ /** Pinned reviewer route; both identifiers are non-empty. */
26
+ export interface AutoReviewRoute {
27
+ readonly provider: string;
28
+ readonly model: string;
29
+ }
30
+ /** Minimal public llm face the official gate uses (it calls exactly `stream`). */
31
+ export interface ReviewLlm {
32
+ stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
33
+ }
34
+ /**
35
+ * Resolve the pinned reviewer route from live settings.
36
+ * @param settings - Model Switch settings carrying the optional reviewer pair.
37
+ * @returns Both identifiers trimmed and non-empty, or undefined (follow session).
38
+ */
39
+ export declare function autoReviewRouteOf(settings: {
40
+ readonly autoReviewProvider?: string;
41
+ readonly autoReviewModel?: string;
42
+ } | undefined): AutoReviewRoute | undefined;
43
+ /**
44
+ * Wrap the real llm service so only `stream()` route fields are rewritten.
45
+ *
46
+ * Non-`stream` members pass through bound to the real service (Proxy reads
47
+ * would otherwise hand methods the proxy as `this`). The route is re-read per
48
+ * call so a settings change applies to the very next review without a reload.
49
+ *
50
+ * @param real - the host's `ctx.llm`.
51
+ * @param route - live route resolver; undefined means "do not rewrite".
52
+ * @returns A structurally compatible llm face for the scoped context.
53
+ */
54
+ export declare function reviewLlm(real: ReviewLlm, route: () => AutoReviewRoute | undefined): ReviewLlm;
55
+ /**
56
+ * Build the scoped context the official gate runs under.
57
+ * @param ctx - host context whose modelSwitch settings pin the reviewer route.
58
+ * @returns A child context shadowing only the llm service.
59
+ */
60
+ export declare function reviewScope(ctx: Context): Context;
61
+ /**
62
+ * Mount the official gate under the reviewer-route scope.
63
+ *
64
+ * A profile that never enabled the optional bundle keeps no Auto preset and no
65
+ * reviewer; a deployment missing the official package reports the lost
66
+ * capability explicitly instead of guessing another path.
67
+ */
68
+ export declare function apply(ctx: Context): Promise<void>;
@@ -14,12 +14,14 @@ export interface ModelSwitchSettingsFace {
14
14
  subagentSettings: ConfigViewForm<SubagentSettingsView>;
15
15
  searchSettings: ConfigViewForm<CapabilityRouteView>;
16
16
  imageSettings: ConfigViewForm<CapabilityRouteView>;
17
+ autoReviewSettings: ConfigViewForm<CapabilityRouteView>;
18
+ compactionSettings: ConfigViewForm<CapabilityRouteView>;
17
19
  switchSettings: Pick<ConfigForm<ModelSwitchSettingsView>, 'getSnapshot' | 'subscribe'>;
18
20
  };
19
21
  capabilities: RuntimeCapabilities;
20
22
  saveMain: (next: MainSettingsView, expectedRevision: number) => Promise<number>;
21
23
  setSubagent: (field: 'mode' | 'provider' | 'model' | 'reasoningEffort', value: string | undefined) => Promise<void>;
22
- setCapability: (route: 'search' | 'image', field: 'provider' | 'model', value: string | undefined) => Promise<void>;
24
+ setCapability: (route: 'search' | 'image' | 'autoReview' | 'compaction', field: 'provider' | 'model', value: string | undefined) => Promise<void>;
23
25
  setCompactOnSwitch: (value: boolean) => Promise<void>;
24
26
  loadCatalog: () => Promise<readonly ModelProviderGroup[]>;
25
27
  /** Host capabilities long-poll; absent in legacy faces, which keep the static-capabilities path. */
@@ -1,4 +1,4 @@
1
- import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
2
- export type SettingsFieldMap<View> = Readonly<Partial<Record<keyof View & string, string>>>;
3
- /** Projection over one shared SettingsScope controller/store, with explicit view-to-owner field mapping. */
4
- export declare function deriveSettingsScope<Source, View>(source: SettingsScope<Source>, project: (value: Source) => View, fields?: SettingsFieldMap<View>): SettingsScope<View>;
1
+ import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
2
+ export type SettingsFieldMap<View> = Readonly<Partial<Record<keyof View & string, string>>>;
3
+ /** Projection over one shared SettingsScope controller/store, with explicit view-to-owner field mapping. */
4
+ export declare function deriveSettingsScope<Source, View>(source: SettingsScope<Source>, project: (value: Source) => View, fields?: SettingsFieldMap<View>): SettingsScope<View>;
@@ -34,6 +34,14 @@ export declare const zh: {
34
34
  readonly requestFailed: "保存失败";
35
35
  readonly catalogFailed: "无法加载模型目录。";
36
36
  readonly conflict: "设置已在其他位置更改,请检查最新值后重试。";
37
+ readonly autoReview: "自动审查模型";
38
+ readonly autoReviewFollow: "跟随会话模型";
39
+ readonly autoReviewHelp: "为官方 auto-review(Auto 权限预设的逐调用审查)指定审查模型;需启用官方 auto-review 能力包。关闭时审查跟随当前会话模型。";
40
+ readonly autoReviewRouteHelp: "审查请求固定使用这里的 Provider/模型,与会话模型无关;保存后对下一次审查立即生效。";
41
+ readonly compaction: "压缩模型";
42
+ readonly compactionFollow: "跟随会话模型";
43
+ readonly compactionHelp: "为官方压缩(/compact 与官方自动压缩)指定摘要模型;关闭(两个字段都留空)时使用官方默认,即当前会话的目标模型。";
44
+ readonly compactionRouteHelp: "摘要请求固定使用这里的 Provider/模型,与会话目标模型无关;保存后对下一次压缩立即生效。保存时若恰有压缩进行中,可能被打断并在下次重试。";
37
45
  readonly 'reason.central-subagent-routing': "Alpha.4 没有全局子代理启动路由接口。";
38
46
  readonly 'reason.packaged-preset-roots': "Alpha.4 不支持插件提供额外 preset root。";
39
47
  readonly 'reason.tool-owner-suppression': "Alpha.4 没有工具所有者或来源抑制接口。";
@@ -3,6 +3,7 @@ import type { ConfigFormSnapshot } from '@deepseek-ai/dsh-client-ui-settings/cli
3
3
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
4
4
  import type { MainSettingsView } from '../../client-contract.ts';
5
5
  import { type GoalRemote } from '../../picker/plan-goal.ts';
6
+ import type { PlanPreviewBinding } from './plan-preview.ts';
6
7
  import { type PickerDirectoryFace } from './PickerDirectory.ts';
7
8
  import type { PickerInteractionOperations } from './popup-dismissal.ts';
8
9
  import { type RuntimeProviderLock } from '../runtime-lock.ts';
@@ -25,6 +26,8 @@ export interface PlanReviewFace extends PickerDirectoryFace {
25
26
  getMainDefaultsSnapshot: () => ConfigFormSnapshot<MainSettingsView>;
26
27
  /** Released goal remote; absent when this deployment mounts no goal service. */
27
28
  goalRemote: () => GoalRemote | undefined;
29
+ /** Full-plan sidebar navigation (ui-plan's registered opener). */
30
+ planPreview: PlanPreviewBinding;
28
31
  /** Live catalog-group-id → card-key map from ProviderDirectory. */
29
32
  catalogRoutes?: () => Readonly<Record<string, string>>;
30
33
  }
@@ -34,6 +34,7 @@ export declare const zh: {
34
34
  'choice.unavailable': string;
35
35
  'plan.kicker': string;
36
36
  'plan.header': string;
37
+ 'plan.openFull': string;
37
38
  'plan.execution': string;
38
39
  'plan.approve': string;
39
40
  'plan.keep': string;
@@ -90,6 +91,7 @@ export declare const en: {
90
91
  'choice.unavailable': string;
91
92
  'plan.kicker': string;
92
93
  'plan.header': string;
94
+ 'plan.openFull': string;
93
95
  'plan.execution': string;
94
96
  'plan.approve': string;
95
97
  'plan.keep': string;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Restore the official full-plan sidebar preview under the takeover card.
3
+ *
4
+ * The takeover replaces the official QuestionComposer in the
5
+ * `conversation.composer` chain, and that composer is the only entry allowed
6
+ * to render `conversation.plan-review.actions` (the SlotRegistry enforces one
7
+ * declarer per child key, and `renderSlot` dispatch checks the *rendering*
8
+ * entry's own children table). This module therefore drives ui-plan's own
9
+ * registered opener — its inject face owns the `dsh-resource://plan-review`
10
+ * address, the logged-plan branch, and the sidebar-mounted gate — instead of
11
+ * re-deriving any of them here. If ui-plan is absent, every operation
12
+ * degrades to "unavailable" and the card keeps working.
13
+ */
14
+ import type { StoredEntry } from '@deepseek-ai/dsh-client-ui-slots';
15
+ import type { SessionId } from '@deepseek-ai/dsh-session/types';
16
+ import type { PlanReview } from '@deepseek-ai/dsh-client-ui-user-questions/client';
17
+ /** Minimal registry face the binding reads (the public slot ledger snapshot). */
18
+ export interface PlanPreviewSlots {
19
+ entries(key: string): readonly StoredEntry[];
20
+ }
21
+ /** Sideways navigation controls for the full plan document in the right sidebar. */
22
+ export interface PlanPreviewBinding {
23
+ /** Whether ui-plan's opener entry is registered right now. */
24
+ available(): boolean;
25
+ /** Open the full plan immediately; returns false when unavailable. */
26
+ open(sessionId: SessionId, review: PlanReview, requestKey: string): boolean;
27
+ /** Open once as soon as a Session is on screen; idempotent per review identity. */
28
+ autoOpen(sessionId: SessionId, review: PlanReview, requestKey: string): () => void;
29
+ }
30
+ /**
31
+ * Stable identity of one review's sidebar preview.
32
+ * @param sessionId - session that owns the review.
33
+ * @param review - narrowed plan review.
34
+ * @param requestKey - pending-question request key.
35
+ * @returns `session:call:<callId>` for a logged plan, else `session:review:<requestKey>`.
36
+ */
37
+ export declare function planPreviewIdentity(sessionId: SessionId, review: PlanReview, requestKey: string): string;
38
+ /**
39
+ * Whether this process already opened the preview for one review identity.
40
+ * @param identity - value from {@link planPreviewIdentity}.
41
+ * @returns True once opened.
42
+ */
43
+ export declare function wasPlanPreviewOpened(identity: string): boolean;
44
+ /**
45
+ * Bind the preview navigation to one slot registry.
46
+ * @param slots - the live registry ledger.
47
+ * @returns an idempotent binding usable from the card's render and effects.
48
+ */
49
+ export declare function createPlanPreviewBinding(slots: PlanPreviewSlots): PlanPreviewBinding;
@@ -1,45 +1,45 @@
1
- import type { SettingsPathOpView } from '@deepseek-ai/dsh-api-remotes/client';
2
- import type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client';
3
- interface RemoteError {
4
- code: string;
5
- message: string;
6
- }
7
- interface RemoteResult<T> {
8
- ok: boolean;
9
- value?: T;
10
- error?: RemoteError;
11
- }
12
- interface NamespaceView {
13
- ns: string;
14
- value: unknown;
15
- base?: unknown;
16
- user?: unknown;
17
- revision: number;
18
- }
19
- interface DescribeValue {
20
- writable: boolean;
21
- namespaces: readonly NamespaceView[];
22
- }
23
- export interface RemoteSettingsFace {
24
- describe(): Promise<RemoteResult<DescribeValue>>;
25
- mutate(ns: string, ops: readonly unknown[], expectedRevision: number | undefined): Promise<RemoteResult<NamespaceView>>;
26
- }
27
- /** Host-backed settings scope used by this plugin on authenticated remote browsers. */
28
- export declare class RemoteSettingsScope<T> implements SettingsScope<T> {
29
- private readonly api;
30
- private readonly namespace;
31
- private readonly decode;
32
- private snapshot;
33
- private readonly listeners;
34
- constructor(api: RemoteSettingsFace, namespace: string, decode: (value: unknown) => T | undefined);
35
- getSnapshot: () => SettingsScopeSnapshot<T>;
36
- subscribe: (listener: () => void) => (() => void);
37
- reload(): Promise<void>;
38
- set(field: string, value: unknown): Promise<void>;
39
- unset(field: string): Promise<void>;
40
- mutate(ops: readonly SettingsPathOpView[], expectedRevision?: number): Promise<void>;
41
- private write;
42
- private accept;
43
- private publish;
44
- }
45
- export {};
1
+ import type { SettingsPathOpView } from '@deepseek-ai/dsh-api-remotes/client';
2
+ import type { SettingsScope, SettingsScopeSnapshot } from '@deepseek-ai/dsh-client-ui-settings/client';
3
+ interface RemoteError {
4
+ code: string;
5
+ message: string;
6
+ }
7
+ interface RemoteResult<T> {
8
+ ok: boolean;
9
+ value?: T;
10
+ error?: RemoteError;
11
+ }
12
+ interface NamespaceView {
13
+ ns: string;
14
+ value: unknown;
15
+ base?: unknown;
16
+ user?: unknown;
17
+ revision: number;
18
+ }
19
+ interface DescribeValue {
20
+ writable: boolean;
21
+ namespaces: readonly NamespaceView[];
22
+ }
23
+ export interface RemoteSettingsFace {
24
+ describe(): Promise<RemoteResult<DescribeValue>>;
25
+ mutate(ns: string, ops: readonly unknown[], expectedRevision: number | undefined): Promise<RemoteResult<NamespaceView>>;
26
+ }
27
+ /** Host-backed settings scope used by this plugin on authenticated remote browsers. */
28
+ export declare class RemoteSettingsScope<T> implements SettingsScope<T> {
29
+ private readonly api;
30
+ private readonly namespace;
31
+ private readonly decode;
32
+ private snapshot;
33
+ private readonly listeners;
34
+ constructor(api: RemoteSettingsFace, namespace: string, decode: (value: unknown) => T | undefined);
35
+ getSnapshot: () => SettingsScopeSnapshot<T>;
36
+ subscribe: (listener: () => void) => (() => void);
37
+ reload(): Promise<void>;
38
+ set(field: string, value: unknown): Promise<void>;
39
+ unset(field: string): Promise<void>;
40
+ mutate(ops: readonly SettingsPathOpView[], expectedRevision?: number): Promise<void>;
41
+ private write;
42
+ private accept;
43
+ private publish;
44
+ }
45
+ export {};
@@ -19,6 +19,10 @@ export interface ModelSwitchSettingsView {
19
19
  imageProvider?: string;
20
20
  imageModel?: string;
21
21
  compactOnSwitch?: boolean;
22
+ autoReviewProvider?: string;
23
+ autoReviewModel?: string;
24
+ compactProvider?: string;
25
+ compactModel?: string;
22
26
  }
23
27
  export interface SubagentSettingsView {
24
28
  mode: 'follow-main' | 'fixed';
@@ -46,6 +50,20 @@ export declare const IMAGE_SETTINGS_FIELDS: Readonly<{
46
50
  readonly provider: "imageProvider";
47
51
  readonly model: "imageModel";
48
52
  }>;
53
+ /** Auto-preset reviewer route; both unset means the reviewer follows the session model. */
54
+ export declare const AUTO_REVIEW_SETTINGS_FIELDS: Readonly<{
55
+ readonly provider: "autoReviewProvider";
56
+ readonly model: "autoReviewModel";
57
+ }>;
58
+ /** Compaction summarization route for `/compact` and automatic compaction; both unset means the official default (the conversation target). */
59
+ export declare const COMPACTION_SETTINGS_FIELDS: Readonly<{
60
+ readonly provider: "compactProvider";
61
+ readonly model: "compactModel";
62
+ }>;
49
63
  export declare function deriveSubagentSettings(settings: ModelSwitchSettingsView): SubagentSettingsView;
50
64
  export declare function deriveSearchSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
51
65
  export declare function deriveImageSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
66
+ export declare function deriveAutoReviewSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
67
+ export declare function deriveCompactionSettings(settings: ModelSwitchSettingsView): CapabilityRouteView;
68
+ /** Whether both fields of a model route form a complete pair; an incomplete pair stays off. */
69
+ export declare function routePairConfigured(route: CapabilityRouteView | undefined): boolean;
@@ -0,0 +1,56 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { Entry, Loader } from '@deepseek-ai/cordis-plugin-loader';
3
+ import type { BasicCompactionConfig } from '@deepseek-ai/dsh-compaction-basic';
4
+ /** Cordis plugin identity; matches the cordis.patch.yml insert row id. */
5
+ export declare const name = "model-switch-compaction";
6
+ /** The loader is awaited at row activation; settings and the official row are still read structurally so stock compaction survives a disabled Model Switch row. */
7
+ export declare const inject: string[];
8
+ /** Both identifiers trimmed and non-empty; undefined means "official default". */
9
+ export interface CompactionRoute {
10
+ readonly provider: string;
11
+ readonly model: string;
12
+ }
13
+ /**
14
+ * Resolve the configurable summarization route from live settings.
15
+ * @param settings - Model Switch settings carrying the optional compaction pair.
16
+ * @returns Both identifiers trimmed and non-empty, or undefined (official default).
17
+ */
18
+ export declare function compactionRouteOf(settings: {
19
+ readonly compactProvider?: string;
20
+ readonly compactModel?: string;
21
+ } | undefined): CompactionRoute | undefined;
22
+ /**
23
+ * Merge the resolved route over the official row's own config.
24
+ * @param forwarded - config the official compaction-basic row was configured with.
25
+ * @param route - resolved summarization route; undefined keeps official behavior.
26
+ * @returns A detached config safe to hand to a fresh engine construction.
27
+ */
28
+ export declare function composeCompactionConfig(forwarded: BasicCompactionConfig, route: CompactionRoute | undefined): BasicCompactionConfig;
29
+ /**
30
+ * Locate the official compaction row in the loader tree.
31
+ * Disabled entries stay in the tree, so a name-matched disable still matches.
32
+ * @param loader - the host loader, when one is reachable.
33
+ * @returns The official entry, or undefined when this profile has no such row.
34
+ */
35
+ export declare function officialCompactionEntry(loader: Loader | undefined): Entry | undefined;
36
+ /**
37
+ * Copy the official row's stored config, ignoring non-object payloads.
38
+ * @param entry - the official compaction entry.
39
+ * @returns A plain object spread; missing or invalid configs become `{}`.
40
+ */
41
+ export declare function forwardedConfig(entry: Entry): BasicCompactionConfig;
42
+ /**
43
+ * Mount the Model Switch compaction engine for this profile.
44
+ *
45
+ * Installs a sibling row that replaces the official compaction-basic engine
46
+ * with the same package under the configurable summarization route. Host
47
+ * settings and the loader are read structurally so a disabled `model-switch`
48
+ * row leaves official compaction untouched. Reconciles run on
49
+ * `model-switch/settings-update` (emitted by the host on volatile commits and
50
+ * ordinary saves); the listener is global so instances of this row inside
51
+ * agent-preset realms still hear the host row's emission. The initial
52
+ * reconcile is awaited so `/compact` resolves the service as soon as this row
53
+ * completes.
54
+ * @param ctx - this row's context.
55
+ */
56
+ export declare function apply(ctx: Context): Promise<void>;