dsh-model-switch 0.4.25 → 0.4.27

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
@@ -612,6 +612,153 @@ function allowDshRuntime(logger, pluginName, candidates) {
612
612
  return shouldMountDshRuntime(logger, pluginName, version, new Set(Object.entries(compatibility?.dshReleases ?? {}).filter(([, status]) => status === "compatible" || status === "verified").map(([release]) => release)), compatibility?.blocklist);
613
613
  }
614
614
  //#endregion
615
+ //#region lib/types/preset-sync.js
616
+ /** The preset ids those rows declare (`preset-…` is only the row id). */
617
+ const PRESET_IDS = [
618
+ "standard",
619
+ "ptc",
620
+ "cordis"
621
+ ];
622
+ /** The presentation row every preset uses for its child plugin list. */
623
+ const PRESET_PACKAGE = "@deepseek-ai/dsh-agent-preset";
624
+ /** DSH's own compaction engine inside a preset's compaction group. */
625
+ const OFFICIAL_COMPACTION_NAME = "@deepseek-ai/dsh-compaction-basic";
626
+ /** This plugin's routed engine row. */
627
+ const BRIDGED_COMPACTION_ID = "model-switch-compaction";
628
+ const BRIDGED_COMPACTION_NAME = "dsh-model-switch/compaction";
629
+ function isRecord(value) {
630
+ return typeof value === "object" && value !== null && !Array.isArray(value);
631
+ }
632
+ function isOfficialCompaction(row) {
633
+ return row.name === OFFICIAL_COMPACTION_NAME;
634
+ }
635
+ function isBridgedCompaction(row) {
636
+ return row !== void 0 && row.id === BRIDGED_COMPACTION_ID && row.name === BRIDGED_COMPACTION_NAME;
637
+ }
638
+ /**
639
+ * Swap the nested official compaction engine for this plugin's routed engine.
640
+ *
641
+ * Recurses through every nested entry list, so a preset that wraps its
642
+ * compaction group differently is still bridged. A list that carries no
643
+ * official engine is returned untouched. The transform is idempotent: a list
644
+ * whose official row is already disabled and already followed by the routed row
645
+ * reports `changed: false`.
646
+ * @param rows - one plugin list (a preset's `plugins` or a nested group's `config`).
647
+ * @returns The new list and whether anything changed.
648
+ */
649
+ function bridgePresetPlugins(rows) {
650
+ let changed = false;
651
+ const bridged = [];
652
+ for (let index = 0; index < rows.length; index += 1) {
653
+ const source = rows[index];
654
+ let row = source;
655
+ if (Array.isArray(source.config)) {
656
+ const nested = bridgePresetPlugins(source.config);
657
+ if (nested.changed) {
658
+ row = {
659
+ ...source,
660
+ config: nested.rows
661
+ };
662
+ changed = true;
663
+ }
664
+ }
665
+ if (isOfficialCompaction(row)) {
666
+ const follower = rows[index + 1];
667
+ const already = isBridgedCompaction(follower);
668
+ if (!already || row.disabled !== true) {
669
+ bridged.push({
670
+ ...row,
671
+ disabled: true
672
+ });
673
+ if (!already) bridged.push({
674
+ id: BRIDGED_COMPACTION_ID,
675
+ name: BRIDGED_COMPACTION_NAME
676
+ });
677
+ changed = true;
678
+ continue;
679
+ }
680
+ }
681
+ bridged.push(row);
682
+ }
683
+ return {
684
+ rows: bridged,
685
+ changed
686
+ };
687
+ }
688
+ /**
689
+ * Bridge one preset definition's plugin list.
690
+ * @param config - the preset row's stored config (`{ id, order, plugins }`).
691
+ * @returns The bridged config and whether anything changed.
692
+ */
693
+ function bridgePresetConfig(config) {
694
+ if (!isRecord(config) || !Array.isArray(config.plugins)) return {
695
+ config,
696
+ changed: false
697
+ };
698
+ const { rows, changed } = bridgePresetPlugins(config.plugins);
699
+ if (!changed) return {
700
+ config,
701
+ changed: false
702
+ };
703
+ return {
704
+ config: {
705
+ ...config,
706
+ plugins: rows
707
+ },
708
+ changed: true
709
+ };
710
+ }
711
+ /**
712
+ * Locate one preset row in the loader tree.
713
+ * @param loader - the host loader.
714
+ * @param id - the preset id (`standard`, `ptc`, `cordis`).
715
+ * @returns The declaring entry, or undefined when this profile has no such preset.
716
+ */
717
+ function presetEntry(loader, id) {
718
+ if (!loader) return void 0;
719
+ for (const entry of loader.entries()) {
720
+ if (entry.options?.name !== PRESET_PACKAGE) continue;
721
+ const config = entry.options?.config;
722
+ if (isRecord(config) && config.id === id) return entry;
723
+ }
724
+ }
725
+ /**
726
+ * Bridge every shipped preset in the running composition.
727
+ *
728
+ * Runs at Host startup, before any session can use a preset, so the wrapped
729
+ * engine is in place for the first session. An entry that has not started yet
730
+ * has its raw config replaced (so the first activation already carries the
731
+ * bridge); a started entry is reloaded through the public loader update. A
732
+ * preset without a compaction group is skipped, and a bridge failure is logged
733
+ * without failing the row: the official presets keep working.
734
+ * @param ctx - the Host plugin context.
735
+ * @returns The preset ids that were bridged.
736
+ */
737
+ async function bridgeAgentPresets(ctx) {
738
+ let loader;
739
+ try {
740
+ loader = ctx.get("loader", false);
741
+ } catch {
742
+ loader = void 0;
743
+ }
744
+ if (loader === void 0) return [];
745
+ const bridged = [];
746
+ for (const id of PRESET_IDS) {
747
+ const entry = presetEntry(loader, id);
748
+ if (entry === void 0) continue;
749
+ const { config, changed } = bridgePresetConfig(entry.options.config);
750
+ if (!changed) continue;
751
+ try {
752
+ if (entry.fiber === void 0) entry.options.config = config;
753
+ else await entry.update({ config });
754
+ bridged.push(id);
755
+ } catch (error) {
756
+ ctx.logger.warn(`model-switch: failed to bridge agent preset ${id}: ${String(error)}`);
757
+ }
758
+ }
759
+ return bridged;
760
+ }
761
+ //#endregion
615
762
  //#region lib/types/host-runtime.js
616
763
  /** Host owner for Model Switch settings. */
617
764
  var ModelSwitchRuntime = class extends Service {
@@ -646,6 +793,9 @@ var ModelSwitchRuntime = class extends Service {
646
793
  ctx.emit("model-switch/settings-update");
647
794
  });
648
795
  ctx.emit("model-switch/settings-update");
796
+ bridgeAgentPresets(ctx).then((bridged) => {
797
+ if (bridged.length > 0) ctx.logger.info(`model-switch: bridged agent preset compaction rows (${bridged.join(", ")})`);
798
+ }).catch((error) => ctx.logger.warn(`model-switch: agent preset bridge failed: ${String(error)}`));
649
799
  }
650
800
  currentSettings() {
651
801
  return { ...this.source() };
@@ -0,0 +1,172 @@
1
+ import { t as groupOf } from "./provider-group-CwadImhH.js";
2
+ //#region lib/types/session-route.js
3
+ /**
4
+ * Per-session capability route derivation.
5
+ *
6
+ * Model Switch stores one deployment-wide route per capability (the flat
7
+ * settings fields) plus, per provider, a saved provider group. Nothing is
8
+ * written when a session changes provider: this module DERIVES the route a
9
+ * capability should use for one session from (a) the session's own provider and
10
+ * (b) the stored group table. Two sessions on different providers therefore
11
+ * resolve independently, and no session's actions rewrite another session's
12
+ * route.
13
+ *
14
+ * The session's provider is read from durable, official state in this order:
15
+ * the `modelSelection` session projection (`pending` before `lastUsed`, so a
16
+ * selection the user just made counts), then the latest logged request-context
17
+ * route, then the latest `request/header` route. Every read is optional: a
18
+ * session with no observable provider, an unregistered projection, or a
19
+ * profile without the projection package all resolve to the deployment default,
20
+ * which is exactly the pre-derivation behavior.
21
+ *
22
+ * All functions are pure except the readers, which never throw and never write.
23
+ */
24
+ /** Flat deployment-default fields per capability, in group-storage field names. */
25
+ const CAPABILITY_FIELDS = Object.freeze({
26
+ compaction: {
27
+ provider: "compactProvider",
28
+ model: "compactModel"
29
+ },
30
+ autoReview: {
31
+ provider: "autoReviewProvider",
32
+ model: "autoReviewModel"
33
+ },
34
+ title: {
35
+ provider: "titleProvider",
36
+ model: "titleModel"
37
+ },
38
+ subagent: {
39
+ provider: "subagentProvider",
40
+ model: "subagentModel"
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
+ /** Read one capability's pair from a flat route record; undefined unless both halves are present. */
48
+ function pairOf(source, fields) {
49
+ if (source === void 0) return void 0;
50
+ const record = source;
51
+ const provider = record[fields.provider];
52
+ const model = record[fields.model];
53
+ if (!present(provider) || !present(model)) return void 0;
54
+ return {
55
+ provider,
56
+ model
57
+ };
58
+ }
59
+ /**
60
+ * The deployment-wide route for one capability: the flat settings pair.
61
+ * @param settings - live Model Switch settings; undefined means none are reachable.
62
+ * @param capability - capability whose flat pair is read.
63
+ * @returns The complete pair, or undefined when the capability stays at its official/derived default.
64
+ */
65
+ function deploymentRoute(settings, capability) {
66
+ return pairOf(settings, CAPABILITY_FIELDS[capability]);
67
+ }
68
+ /**
69
+ * The route a subagent spawn should use for one parent-session provider.
70
+ *
71
+ * A saved group for that provider pins the spawn; otherwise the flat Subagent
72
+ * card pins it, but only in `fixed` mode — `follow-main` IS the official inherit
73
+ * path, expressed by leaving the request untouched rather than by naming it.
74
+ * @param settings - live Model Switch settings.
75
+ * @param provider - the delegating session's provider, when one is observable.
76
+ * @returns The complete route, or undefined for official inherit.
77
+ */
78
+ function subagentRouteOf(settings, provider) {
79
+ const group = groupRoute(settings, provider, "subagent");
80
+ if (group !== void 0) {
81
+ const effort = groupOf(settings?.providerGroups, provider ?? "")?.subagentReasoningEffort;
82
+ return present(effort) ? {
83
+ ...group,
84
+ reasoningEffort: effort
85
+ } : group;
86
+ }
87
+ if (settings?.subagentMode !== "fixed") return void 0;
88
+ const fixed = deploymentRoute(settings, "subagent");
89
+ if (fixed === void 0) return void 0;
90
+ return present(settings.subagentReasoningEffort) ? {
91
+ ...fixed,
92
+ reasoningEffort: settings.subagentReasoningEffort
93
+ } : fixed;
94
+ }
95
+ /**
96
+ * One provider's saved route for one capability.
97
+ * @param settings - live Model Switch settings carrying the group table.
98
+ * @param provider - the session's provider; blank or unknown resolves to undefined.
99
+ * @param capability - capability whose group pin is read.
100
+ * @returns The complete group pair, or undefined when the group pins nothing here.
101
+ */
102
+ function groupRoute(settings, provider, capability) {
103
+ if (!present(provider)) return void 0;
104
+ return pairOf(groupOf(settings?.providerGroups, provider), CAPABILITY_FIELDS[capability]);
105
+ }
106
+ /**
107
+ * The route one capability should use for one session.
108
+ *
109
+ * A saved provider group for the session's provider wins; otherwise the flat
110
+ * deployment default applies. An unknown session provider skips every group, so
111
+ * a session Model Switch cannot attribute keeps the deployment default.
112
+ * @param settings - live Model Switch settings.
113
+ * @param provider - the session's provider, when one is observable.
114
+ * @param capability - capability being resolved.
115
+ * @returns The complete pair, or undefined for the capability's official default.
116
+ */
117
+ function resolveCapabilityRoute(settings, provider, capability) {
118
+ return groupRoute(settings, provider, capability) ?? deploymentRoute(settings, capability);
119
+ }
120
+ /**
121
+ * Read the provider from the durable model-selection projection.
122
+ * @param ctx - context that may expose `sessionProjections`.
123
+ * @param session - the session whose selection is read.
124
+ * @returns The pending provider, else the last-used provider, else undefined.
125
+ */
126
+ function projectionProvider(ctx, session) {
127
+ let state;
128
+ try {
129
+ state = ctx.get("sessionProjections", false)?.stateOf(session, "modelSelection");
130
+ } catch {
131
+ state = void 0;
132
+ }
133
+ if (state === void 0) return void 0;
134
+ if (present(state.pending?.provider)) return state.pending.provider;
135
+ if (present(state.lastUsed?.provider)) return state.lastUsed.provider;
136
+ }
137
+ /**
138
+ * The provider one session is using, from official durable state.
139
+ *
140
+ * Preferred source is the session's own model selection (it covers a switch the
141
+ * client has sent but no request has consumed yet); the logged request-context
142
+ * and request-header routes cover profiles without the projection package and
143
+ * replayed sessions that never emitted a selection.
144
+ * @param ctx - context used for the optional projection read.
145
+ * @param session - the session to resolve; undefined resolves to undefined.
146
+ * @returns The provider, or undefined when nothing durable names one.
147
+ */
148
+ function sessionProviderOf(ctx, session) {
149
+ if (session === void 0) return void 0;
150
+ const selected = projectionProvider(ctx, session);
151
+ if (selected !== void 0) return selected;
152
+ try {
153
+ const context = session.requestContext()?.provider;
154
+ if (present(context)) return context;
155
+ const header = session.requestHeader()?.config.provider;
156
+ if (present(header)) return header;
157
+ } catch {
158
+ return;
159
+ }
160
+ }
161
+ /**
162
+ * The subagent route one exact session's spawns should use.
163
+ * @param ctx - context used for the optional session reads.
164
+ * @param session - the delegating session; undefined keeps official inherit.
165
+ * @param settings - live Model Switch settings.
166
+ * @returns The complete route, or undefined for official inherit.
167
+ */
168
+ function sessionSubagentRouteOf(ctx, session, settings) {
169
+ return subagentRouteOf(settings, sessionProviderOf(ctx, session));
170
+ }
171
+ //#endregion
172
+ export { sessionSubagentRouteOf as a, sessionProviderOf as i, groupRoute as n, resolveCapabilityRoute as r, deploymentRoute as t };