dsh-model-switch 0.4.20 → 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
@@ -25,7 +25,8 @@ const Config = z.object({
25
25
  compactOnSwitch: z.boolean().default(true).volatile(),
26
26
  autoReviewProvider: z.string().volatile(),
27
27
  autoReviewModel: z.string().volatile(),
28
- planCompactThreshold: z.number().default(2e5).volatile()
28
+ compactProvider: z.string().volatile(),
29
+ compactModel: z.string().volatile()
29
30
  });
30
31
  function readConfig(config) {
31
32
  const subagentProvider = config.subagentProvider.get();
@@ -37,6 +38,8 @@ function readConfig(config) {
37
38
  const imageModel = config.imageModel.get();
38
39
  const autoReviewProvider = config.autoReviewProvider.get();
39
40
  const autoReviewModel = config.autoReviewModel.get();
41
+ const compactProvider = config.compactProvider.get();
42
+ const compactModel = config.compactModel.get();
40
43
  return {
41
44
  subagentMode: config.subagentMode.get(),
42
45
  ...subagentProvider === void 0 ? {} : { subagentProvider },
@@ -49,7 +52,8 @@ function readConfig(config) {
49
52
  compactOnSwitch: config.compactOnSwitch.get(),
50
53
  ...autoReviewProvider === void 0 || autoReviewProvider === "" ? {} : { autoReviewProvider },
51
54
  ...autoReviewModel === void 0 || autoReviewModel === "" ? {} : { autoReviewModel },
52
- planCompactThreshold: config.planCompactThreshold.get()
55
+ ...compactProvider === void 0 || compactProvider === "" ? {} : { compactProvider },
56
+ ...compactModel === void 0 || compactModel === "" ? {} : { compactModel }
53
57
  };
54
58
  }
55
59
  //#endregion
@@ -347,28 +351,8 @@ async function handleFetchRpc(request, handler, operator) {
347
351
  return statusResponse(500);
348
352
  }
349
353
  }
350
- /** Validate one plan-compact payload; returns the request or an error string. */
351
- function planCompactRequest(payload) {
352
- if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return {
353
- ok: false,
354
- message: "Expected an object payload"
355
- };
356
- const { sessionId, provider, model } = payload;
357
- if (typeof sessionId !== "string" || sessionId.length === 0 || typeof provider !== "string" || provider.length === 0 || typeof model !== "string" || model.length === 0) return {
358
- ok: false,
359
- message: "Expected sessionId, provider, and model strings"
360
- };
361
- return {
362
- ok: true,
363
- request: {
364
- sessionId,
365
- provider,
366
- model
367
- }
368
- };
369
- }
370
354
  /** Bounded long-poll on the existing registry; each caller owns and disposes its subscription. */
371
- function capabilitiesRpc(registry, snapshot, lifetime, planCompact) {
355
+ function capabilitiesRpc(registry, snapshot, lifetime) {
372
356
  let revision = 0;
373
357
  let previous;
374
358
  const read = () => {
@@ -382,39 +366,6 @@ function capabilitiesRpc(registry, snapshot, lifetime, planCompact) {
382
366
  };
383
367
  };
384
368
  return async (endpoint, payload, signal) => {
385
- if (endpoint === "plan-compact") {
386
- if (planCompact === void 0) return {
387
- ok: false,
388
- error: {
389
- details: {},
390
- code: "unavailable",
391
- message: "Plan compaction is not mounted on this Host"
392
- }
393
- };
394
- const request = planCompactRequest(payload);
395
- if (!request.ok) return {
396
- ok: false,
397
- error: {
398
- details: {},
399
- code: "invalid-request",
400
- message: request.message
401
- }
402
- };
403
- try {
404
- return {
405
- ok: true,
406
- value: await planCompact(request.request, signal)
407
- };
408
- } catch (cause) {
409
- return {
410
- ok: true,
411
- value: {
412
- status: "error",
413
- message: cause instanceof Error ? cause.message : String(cause)
414
- }
415
- };
416
- }
417
- }
418
369
  if (endpoint !== "capabilities") return {
419
370
  ok: false,
420
371
  error: {
@@ -467,10 +418,10 @@ function capabilitiesRpc(registry, snapshot, lifetime, planCompact) {
467
418
  };
468
419
  };
469
420
  }
470
- function installCapabilitiesRpc(ctx, registry, snapshot, planCompact) {
421
+ function installCapabilitiesRpc(ctx, registry, snapshot) {
471
422
  ctx.inject(["connection"], (scope) => scope.effect(() => {
472
423
  const lifetime = new AbortController();
473
- const handler = capabilitiesRpc(registry, snapshot, lifetime.signal, planCompact);
424
+ const handler = capabilitiesRpc(registry, snapshot, lifetime.signal);
474
425
  const dispose = scope.connection.fetch.register({
475
426
  path: "/api/plugin-rpc/model-switch",
476
427
  methods: ["POST"],
@@ -925,189 +876,6 @@ async function compactWithPrevious(ctx, agent, previous, signal) {
925
876
  }
926
877
  }
927
878
  //#endregion
928
- //#region lib/types/picker/plan-compact.js
929
- /**
930
- * Plan-exit compaction vocabulary shared by the card and the Host RPC.
931
- *
932
- * The card decides *when* to ask (only an approval that switches the execution
933
- * model); the Host owns measurement, threshold policy, and the compaction
934
- * itself. Failures are explicit: the Plan stays pending and can be retried,
935
- * while an empty history is a completed attempt that may proceed.
936
- *
937
- * @module dsh-model-switch/picker/plan-compact
938
- */
939
- /** Whether the user's threshold arms the plan-exit mechanism at all. */
940
- function planCompactEnabled(threshold) {
941
- return Number.isFinite(threshold) && threshold > 0;
942
- }
943
- /**
944
- * Decide whether a plan exit must compact before it switches models.
945
- * @param threshold — user-configured token threshold; 0 disables the mechanism.
946
- * @param estimate — measured next-request input tokens.
947
- * @returns `disabled` when off, `compact` only above the threshold, else `skip`.
948
- */
949
- function planCompactDecision(threshold, estimate) {
950
- if (!planCompactEnabled(threshold)) return "disabled";
951
- if (!Number.isFinite(estimate) || estimate < 0) return "skip";
952
- return estimate > threshold ? "compact" : "skip";
953
- }
954
- //#endregion
955
- //#region lib/types/plan-compact.js
956
- /**
957
- * Plan-exit compaction on the Host: measure, threshold, then summarize the
958
- * history with the switched-to model before the approval resumes the session.
959
- *
960
- * The approval answer lands inside a still-open `exit_plan_mode` tool call, so
961
- * the agent phase is `running` and the idle-only `compactNow` seam is off
962
- * limits. This module therefore uses `compactIfNeeded(agent, 'context-overflow')`
963
- * — the same turn-safe, threshold-free operation official overflow recovery
964
- * uses — under an isolated `compaction-basic` engine configured to summarize
965
- * with the route the human just chose. The user's threshold decides *whether*
966
- * to call it; the engine's overflow policy guarantees one useful balanced
967
- * reduction whenever a safe range exists.
968
- *
969
- * @module dsh-model-switch/plan-compact
970
- */
971
- /**
972
- * Estimate the next request's input tokens under both envelopes.
973
- *
974
- * Takes the larger of the target-route heuristic price and the previous
975
- * envelope's anchored price — the same conservative stance the send-time
976
- * switch guard takes, so dense histories cannot under-trigger the threshold.
977
- *
978
- * @param meter — replay token meter.
979
- * @param session — session about to resume.
980
- * @param route — the switched-to provider/model.
981
- * @returns Estimated tokens, or undefined when no prior request exists to price.
982
- */
983
- function estimatePlanExitTokens(meter, session, route) {
984
- const previousHeader = session.requestHeader();
985
- if (previousHeader === void 0) return void 0;
986
- try {
987
- const targetHeader = canonicalHeader({ config: {
988
- provider: route.provider,
989
- model: route.model
990
- } });
991
- return Math.max(meter.measure(session, targetHeader).totalTokens, meter.measure(session, previousHeader).totalTokens);
992
- } catch {
993
- return;
994
- }
995
- }
996
- /**
997
- * Drive one compaction through an already-mounted isolated engine.
998
- * @param engine — engine whose overflow policy selects the range.
999
- * @param agent — live agent owning the session.
1000
- * @param signal — cancellation scoped to this request.
1001
- * @returns `empty` when no safe range exists, `compacted` after a summary, `error` on failure.
1002
- */
1003
- async function planCompactWithEngine(engine, agent, signal) {
1004
- try {
1005
- const result = await engine.compactIfNeeded(agent, "context-overflow", signal);
1006
- return result === null || result === void 0 ? { status: "empty" } : { status: "compacted" };
1007
- } catch (cause) {
1008
- if (signal.aborted) return {
1009
- status: "error",
1010
- message: "compaction was cancelled"
1011
- };
1012
- return {
1013
- status: "error",
1014
- message: cause instanceof Error ? cause.message : String(cause)
1015
- };
1016
- }
1017
- }
1018
- /**
1019
- * Mount an isolated compaction-basic engine pinned to the switched-to route
1020
- * and run one overflow-policy reduction over the agent's session.
1021
- * @param ctx — host context that owns token metering and session state.
1022
- * @param agent — live agent about to resume the approved plan.
1023
- * @param route — provider/model whose model must write the summary.
1024
- * @param signal — cancellation scoped to this request.
1025
- * @param loadEngine — dynamic import seam (tests inject the fake module).
1026
- * @returns The compaction outcome for this request.
1027
- */
1028
- async function runPlanCompaction(ctx, agent, route, signal, loadEngine) {
1029
- const load = loadEngine ?? (async () => import("@deepseek-ai/dsh-compaction-basic"));
1030
- let loaded;
1031
- try {
1032
- loaded = await load();
1033
- } catch (cause) {
1034
- return {
1035
- status: "error",
1036
- message: cause instanceof Error ? cause.message : String(cause)
1037
- };
1038
- }
1039
- const isolated = ctx.isolate("plan-compaction");
1040
- const fiber = isolated.plugin(loaded.default, {
1041
- auto: false,
1042
- summarizationProvider: route.provider,
1043
- summarizationModel: route.model,
1044
- compactionRetries: 0
1045
- });
1046
- try {
1047
- await fiber;
1048
- const engine = isolated.get("compaction", false);
1049
- if (engine === void 0 || typeof engine.compactIfNeeded !== "function") return {
1050
- status: "error",
1051
- message: "private compaction engine is unavailable"
1052
- };
1053
- return await planCompactWithEngine(engine, agent, signal);
1054
- } finally {
1055
- await fiber.dispose();
1056
- }
1057
- }
1058
- /**
1059
- * Build the `plan-compact` RPC runner bound to this host's live settings.
1060
- * @param ctx — host context resolving the live agent registry.
1061
- * @param settings — live Model Switch settings (`planCompactThreshold`).
1062
- * @param loadEngine — dynamic import seam (tests inject the fake module).
1063
- * @returns The runner the connection RPC dispatches to.
1064
- */
1065
- function planCompactRunner(ctx, settings, loadEngine) {
1066
- return async (request, signal) => {
1067
- const threshold = settings().planCompactThreshold;
1068
- if (!planCompactEnabled(threshold)) return {
1069
- status: "disabled",
1070
- threshold
1071
- };
1072
- const provider = request.provider.trim();
1073
- const model = request.model.trim();
1074
- if (provider === "" || model === "") return {
1075
- status: "error",
1076
- message: "the execution route is incomplete"
1077
- };
1078
- const route = {
1079
- provider,
1080
- model
1081
- };
1082
- const meter = ctx.get("tokenMeter", false);
1083
- const agent = ctx.get("agents", false)?.get(request.sessionId);
1084
- if (agent === void 0) return {
1085
- status: "error",
1086
- message: "the session has no live agent"
1087
- };
1088
- if (meter === void 0) return {
1089
- status: "error",
1090
- message: "token metering is unavailable"
1091
- };
1092
- const estimate = estimatePlanExitTokens(meter, agent.session, route);
1093
- const decision = planCompactDecision(threshold, estimate ?? NaN);
1094
- if (decision === "disabled") return {
1095
- status: "disabled",
1096
- threshold
1097
- };
1098
- if (decision === "skip") return {
1099
- status: "skipped",
1100
- threshold,
1101
- ...estimate === void 0 ? {} : { estimate }
1102
- };
1103
- return {
1104
- ...await runPlanCompaction(ctx, agent, route, signal, loadEngine),
1105
- threshold,
1106
- ...estimate === void 0 ? {} : { estimate }
1107
- };
1108
- };
1109
- }
1110
- //#endregion
1111
879
  //#region lib/types/host-runtime.js
1112
880
  /** Host owner for Model Switch settings and the released Main-default adapter. */
1113
881
  var ModelSwitchRuntime = class extends Service {
@@ -1134,14 +902,16 @@ var ModelSwitchRuntime = class extends Service {
1134
902
  installSwitchCompaction(ctx, () => this.source(), (provider) => this.adapters.get(provider)?.role);
1135
903
  installDeepSeekSearchAdapter(ctx);
1136
904
  installModelSwitchSearchProvider(ctx, this);
1137
- installCapabilitiesRpc(ctx, this.adapters, () => this.capabilities, planCompactRunner(ctx, () => this.source()));
905
+ installCapabilitiesRpc(ctx, this.adapters, () => this.capabilities);
1138
906
  const imageTool = installGenerateImageTool(ctx, this);
1139
907
  ctx.on("loader/volatile-update", () => {
1140
908
  imageTool.reconcile().catch((error) => {
1141
909
  ctx.logger.error("Model Switch: failed to regenerate generate_image schema");
1142
910
  ctx.logger.error(error);
1143
911
  });
912
+ ctx.emit("model-switch/settings-update");
1144
913
  });
914
+ ctx.emit("model-switch/settings-update");
1145
915
  }
1146
916
  currentSettings() {
1147
917
  return { ...this.source() };
@@ -2,12 +2,11 @@ import type { Context } from '@deepseek-ai/cordis';
2
2
  import { type ConnectionRpcHandler } from '@deepseek-ai/dsh-client-connection';
3
3
  import type { ModelSwitchAdapterRegistry } from './adapter-registry.js';
4
4
  import type { RuntimeCapabilities } from './runtime-capabilities.js';
5
- import type { PlanCompactRunner } from './plan-compact.js';
6
5
  export declare const CAPABILITIES_RPC_METHOD = "plugin-rpc/model-switch";
7
6
  export interface CapabilitiesSnapshot {
8
7
  readonly revision: number;
9
8
  readonly capabilities: RuntimeCapabilities;
10
9
  }
11
10
  /** Bounded long-poll on the existing registry; each caller owns and disposes its subscription. */
12
- export declare function capabilitiesRpc(registry: ModelSwitchAdapterRegistry, snapshot: () => RuntimeCapabilities, lifetime: AbortSignal, planCompact?: PlanCompactRunner): ConnectionRpcHandler;
13
- export declare function installCapabilitiesRpc(ctx: Context, registry: ModelSwitchAdapterRegistry, snapshot: () => RuntimeCapabilities, planCompact?: PlanCompactRunner): void;
11
+ export declare function capabilitiesRpc(registry: ModelSwitchAdapterRegistry, snapshot: () => RuntimeCapabilities, lifetime: AbortSignal): ConnectionRpcHandler;
12
+ export declare function installCapabilitiesRpc(ctx: Context, registry: ModelSwitchAdapterRegistry, snapshot: () => RuntimeCapabilities): void;
@@ -15,14 +15,14 @@ export interface ModelSwitchSettingsFace {
15
15
  searchSettings: ConfigViewForm<CapabilityRouteView>;
16
16
  imageSettings: ConfigViewForm<CapabilityRouteView>;
17
17
  autoReviewSettings: ConfigViewForm<CapabilityRouteView>;
18
+ compactionSettings: ConfigViewForm<CapabilityRouteView>;
18
19
  switchSettings: Pick<ConfigForm<ModelSwitchSettingsView>, 'getSnapshot' | 'subscribe'>;
19
20
  };
20
21
  capabilities: RuntimeCapabilities;
21
22
  saveMain: (next: MainSettingsView, expectedRevision: number) => Promise<number>;
22
23
  setSubagent: (field: 'mode' | 'provider' | 'model' | 'reasoningEffort', value: string | undefined) => Promise<void>;
23
- setCapability: (route: 'search' | 'image' | 'autoReview', field: 'provider' | 'model', value: string | undefined) => Promise<void>;
24
+ setCapability: (route: 'search' | 'image' | 'autoReview' | 'compaction', field: 'provider' | 'model', value: string | undefined) => Promise<void>;
24
25
  setCompactOnSwitch: (value: boolean) => Promise<void>;
25
- setPlanCompactThreshold: (value: number) => Promise<void>;
26
26
  loadCatalog: () => Promise<readonly ModelProviderGroup[]>;
27
27
  /** Host capabilities long-poll; absent in legacy faces, which keep the static-capabilities path. */
28
28
  loadCapabilities?: (revision?: number, signal?: AbortSignal) => Promise<CapabilitiesSnapshot>;
@@ -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>;
@@ -38,9 +38,10 @@ export declare const zh: {
38
38
  readonly autoReviewFollow: "跟随会话模型";
39
39
  readonly autoReviewHelp: "为官方 auto-review(Auto 权限预设的逐调用审查)指定审查模型;需启用官方 auto-review 能力包。关闭时审查跟随当前会话模型。";
40
40
  readonly autoReviewRouteHelp: "审查请求固定使用这里的 Provider/模型,与会话模型无关;保存后对下一次审查立即生效。";
41
- readonly planCompactThreshold: "计划执行前压缩阈值";
42
- readonly planCompactThresholdHelp: "切换模型执行计划时,若上下文估算超过该 Token 数,先用切换后的模型压缩一次历史再提交执行;未切换模型不触发。0 = 关闭。压缩会产生额外模型用量,摘要可能省略细节。";
43
- readonly planCompactThresholdInvalid: "请输入不小于 0 的整数。";
41
+ readonly compaction: "压缩模型";
42
+ readonly compactionFollow: "跟随会话模型";
43
+ readonly compactionHelp: "为官方压缩(/compact 与官方自动压缩)指定摘要模型;关闭(两个字段都留空)时使用官方默认,即当前会话的目标模型。";
44
+ readonly compactionRouteHelp: "摘要请求固定使用这里的 Provider/模型,与会话目标模型无关;保存后对下一次压缩立即生效。保存时若恰有压缩进行中,可能被打断并在下次重试。";
44
45
  readonly 'reason.central-subagent-routing': "Alpha.4 没有全局子代理启动路由接口。";
45
46
  readonly 'reason.packaged-preset-roots': "Alpha.4 不支持插件提供额外 preset root。";
46
47
  readonly 'reason.tool-owner-suppression': "Alpha.4 没有工具所有者或来源抑制接口。";
@@ -3,7 +3,6 @@ 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 { PlanCompactOutcome } from '../../picker/plan-compact.ts';
7
6
  import type { PlanPreviewBinding } from './plan-preview.ts';
8
7
  import { type PickerDirectoryFace } from './PickerDirectory.ts';
9
8
  import type { PickerInteractionOperations } from './popup-dismissal.ts';
@@ -29,8 +28,6 @@ export interface PlanReviewFace extends PickerDirectoryFace {
29
28
  goalRemote: () => GoalRemote | undefined;
30
29
  /** Full-plan sidebar navigation (ui-plan's registered opener). */
31
30
  planPreview: PlanPreviewBinding;
32
- /** Host-backed pre-execution compaction; absent seams degrade to no compaction. */
33
- planCompact?: (sessionId: string, provider: string, model: string) => Promise<PlanCompactOutcome>;
34
31
  /** Live catalog-group-id → card-key map from ProviderDirectory. */
35
32
  catalogRoutes?: () => Readonly<Record<string, string>>;
36
33
  }
@@ -51,10 +51,6 @@ export declare const zh: {
51
51
  'plan.goalUnsupported': string;
52
52
  'plan.responseRejected': string;
53
53
  'plan.cancelRejected': string;
54
- 'plan.compacting': string;
55
- 'plan.compacted': string;
56
- 'plan.compactEmpty': string;
57
- 'plan.compactFailed': string;
58
54
  'markdown.copy': string;
59
55
  'markdown.copied': string;
60
56
  'markdown.footnotes': string;
@@ -112,10 +108,6 @@ export declare const en: {
112
108
  'plan.goalUnsupported': string;
113
109
  'plan.responseRejected': string;
114
110
  'plan.cancelRejected': string;
115
- 'plan.compacting': string;
116
- 'plan.compacted': string;
117
- 'plan.compactEmpty': string;
118
- 'plan.compactFailed': string;
119
111
  'markdown.copy': string;
120
112
  'markdown.copied': string;
121
113
  'markdown.footnotes': string;