dsh-model-router 0.4.0 → 0.5.0
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/README.md +22 -0
- package/cordis.patch.yml +1 -0
- package/lib/client.js +297 -0
- package/package.json +16 -2
package/README.md
CHANGED
|
@@ -61,6 +61,28 @@ With the router off, requests use the session's selected model (your
|
|
|
61
61
|
`agent-default-model` setting or the base default) — the router is simply not
|
|
62
62
|
rewriting them.
|
|
63
63
|
|
|
64
|
+
## The GUI toggle (v0.5.0+)
|
|
65
|
+
|
|
66
|
+
Since v0.5.0 the package ships a browser half, and the harness serves it
|
|
67
|
+
automatically — no extra config. After installing (or updating to) v0.5.0+
|
|
68
|
+
and restarting the profile, **Settings → Plugins** shows a **Model router**
|
|
69
|
+
card with a live **Enabled** switch, an "Overridden" badge and **Reset to
|
|
70
|
+
default** button once you've changed it, and a read-only view of the current
|
|
71
|
+
planner/executor routes and mode. Flipping the switch applies immediately (no
|
|
72
|
+
restart) and persists in `settings.yaml` under `model-router:` — the same
|
|
73
|
+
mechanism the GUI toggle described above uses.
|
|
74
|
+
|
|
75
|
+
One harness-wide caveat (it applies to **all** settings pages — Models,
|
|
76
|
+
Plugins, everything — not to this plugin specifically): the harness serves
|
|
77
|
+
settings pages only to **loopback** browsers (`localhost` / `127.x`). A remote
|
|
78
|
+
browser sees "settings are unavailable" instead of the card. Fallbacks that
|
|
79
|
+
work everywhere:
|
|
80
|
+
|
|
81
|
+
- **Patch row** — set `enabled: false` in the profile's `cordis.patch.yml`
|
|
82
|
+
(takes effect on the next boot), or
|
|
83
|
+
- **`settings.yaml`** — add `model-router: { enabled: false }` under the
|
|
84
|
+
settings file the profile uses; this applies live, same as the GUI.
|
|
85
|
+
|
|
64
86
|
## Tuning
|
|
65
87
|
|
|
66
88
|
All configuration lives on the plugin row. Patch it in the profile's `cordis.patch.yml`:
|
package/cordis.patch.yml
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
# falls back to the executor route to reserve pro for planning.
|
|
30
30
|
mode: strict
|
|
31
31
|
# enabled: false # off switch — GUI: Settings → Plugins → model-router (live, no restart); patch row needs a reboot
|
|
32
|
+
# GUI: Settings → Plugins → "Model router" card (v0.5.0+, loopback browser only)
|
|
32
33
|
# Publish the always-on routing convention prompt section.
|
|
33
34
|
promptSection: true
|
|
34
35
|
# Register the `pro-flash-routing` skill in the session catalog.
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "dsh-model-router",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
"use strict";
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
25
|
+
|
|
26
|
+
// src/client.tsx
|
|
27
|
+
var client_exports = {};
|
|
28
|
+
__export(client_exports, {
|
|
29
|
+
apply: () => apply,
|
|
30
|
+
inject: () => inject
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(client_exports);
|
|
33
|
+
var import_react = require("react");
|
|
34
|
+
var NS = "model-router";
|
|
35
|
+
var inject = ["slots", "locale", "settingsScope"];
|
|
36
|
+
var en = {
|
|
37
|
+
nav: "Model router",
|
|
38
|
+
title: "Model router",
|
|
39
|
+
description: "Role-based model routing: planning runs on deepseek-v4-pro, delegated executor subagents on deepseek-v4-flash.",
|
|
40
|
+
enabled: "Enabled",
|
|
41
|
+
enabledHint: "Off stops rewriting requests and unregisters the prompt section and skill; on restores them.",
|
|
42
|
+
on: "On",
|
|
43
|
+
off: "Off",
|
|
44
|
+
reset: "Reset to default",
|
|
45
|
+
overridden: "Overridden",
|
|
46
|
+
readOnly: "This deployment stores settings read-only in this browser; use the plugin row in the profile's cordis.patch.yml instead.",
|
|
47
|
+
plannerRoute: "Planner model",
|
|
48
|
+
executorRoute: "Executor model",
|
|
49
|
+
mode: "Mode",
|
|
50
|
+
modeStrict: "strict \u2014 the root agent is always the planner",
|
|
51
|
+
modePlan: "plan \u2014 the root agent is pro only while plan mode is active"
|
|
52
|
+
};
|
|
53
|
+
var zh = {
|
|
54
|
+
nav: "\u6A21\u578B\u8DEF\u7531",
|
|
55
|
+
title: "\u6A21\u578B\u8DEF\u7531",
|
|
56
|
+
description: "\u6309\u89D2\u8272\u5206\u914D\u6A21\u578B\uFF1A\u89C4\u5212\u5728 deepseek-v4-pro \u4E0A\u8FD0\u884C\uFF0C\u59D4\u6D3E\u7684\u6267\u884C\u5B50\u4EE3\u7406\u5728 deepseek-v4-flash \u4E0A\u8FD0\u884C\u3002",
|
|
57
|
+
enabled: "\u542F\u7528",
|
|
58
|
+
enabledHint: "\u5173\u95ED\u540E\u505C\u6B62\u6539\u5199\u8BF7\u6C42\uFF0C\u5E76\u6CE8\u9500\u63D0\u793A\u533A\u5757\u4E0E\u6280\u80FD\uFF1B\u91CD\u65B0\u6253\u5F00\u5373\u6062\u590D\u3002",
|
|
59
|
+
on: "\u5F00",
|
|
60
|
+
off: "\u5173",
|
|
61
|
+
reset: "\u6062\u590D\u9ED8\u8BA4",
|
|
62
|
+
overridden: "\u5DF2\u8986\u76D6",
|
|
63
|
+
readOnly: "\u5F53\u524D\u6D4F\u89C8\u5668\u4E2D\u8BE5\u90E8\u7F72\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\uFF1B\u8BF7\u6539\u7528 profile \u7684 cordis.patch.yml \u4E2D\u7684\u63D2\u4EF6\u884C\u3002",
|
|
64
|
+
plannerRoute: "\u89C4\u5212\u6A21\u578B",
|
|
65
|
+
executorRoute: "\u6267\u884C\u6A21\u578B",
|
|
66
|
+
mode: "\u6A21\u5F0F",
|
|
67
|
+
modeStrict: "strict \u2014 \u6839 agent \u59CB\u7EC8\u4E3A\u89C4\u5212\u8005",
|
|
68
|
+
modePlan: "plan \u2014 \u6839 agent \u4EC5\u5728\u8BA1\u5212\u6A21\u5F0F\u6FC0\u6D3B\u65F6\u4F7F\u7528 pro"
|
|
69
|
+
};
|
|
70
|
+
var ModelRouterCardController = class {
|
|
71
|
+
scope;
|
|
72
|
+
constructor(scope) {
|
|
73
|
+
this.scope = scope;
|
|
74
|
+
}
|
|
75
|
+
get store() {
|
|
76
|
+
return {
|
|
77
|
+
subscribe: (listener) => this.scope.subscribe(listener),
|
|
78
|
+
getSnapshot: () => this.scope.getSnapshot()
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** Build the face the card's slot registration injects. */
|
|
82
|
+
inject() {
|
|
83
|
+
return {
|
|
84
|
+
hooks: { modelRouterCard: this.store },
|
|
85
|
+
setEnabled: async (next) => {
|
|
86
|
+
try {
|
|
87
|
+
await this.scope.set("enabled", next);
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
reset: async () => {
|
|
92
|
+
try {
|
|
93
|
+
await this.scope.unset("enabled");
|
|
94
|
+
} catch {
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Stop the scope's mirror subscription when the fiber stops. */
|
|
100
|
+
dispose() {
|
|
101
|
+
void this.scope.dispose();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
function routeRow(label, value) {
|
|
105
|
+
return (0, import_react.createElement)("div", { style: styles.routeRow }, [
|
|
106
|
+
(0, import_react.createElement)("dt", { style: styles.routeLabel }, label),
|
|
107
|
+
(0, import_react.createElement)("dd", { style: styles.routeValue }, value)
|
|
108
|
+
]);
|
|
109
|
+
}
|
|
110
|
+
function modeLabel(t, mode) {
|
|
111
|
+
return mode === "plan" ? t("modePlan") : t("modeStrict");
|
|
112
|
+
}
|
|
113
|
+
function ModelRouterCard(props) {
|
|
114
|
+
const { t } = props;
|
|
115
|
+
const state = props.useModelRouterCard((snapshot) => snapshot);
|
|
116
|
+
const unavailable = !state.writable || state.mode !== "host";
|
|
117
|
+
const overridden = state.user?.enabled !== void 0;
|
|
118
|
+
return (0, import_react.createElement)("section", { style: styles.card }, [
|
|
119
|
+
(0, import_react.createElement)("div", { style: styles.headRow }, [
|
|
120
|
+
(0, import_react.createElement)("h3", { style: styles.title }, t("title")),
|
|
121
|
+
overridden ? (0, import_react.createElement)("span", { style: styles.badge }, t("overridden")) : null
|
|
122
|
+
]),
|
|
123
|
+
(0, import_react.createElement)("p", { style: styles.description }, t("description")),
|
|
124
|
+
...unavailable ? [(0, import_react.createElement)("p", { style: styles.note }, t("readOnly"))] : state.value ? [
|
|
125
|
+
(0, import_react.createElement)("div", { style: styles.switchRow }, [
|
|
126
|
+
(0, import_react.createElement)("label", { htmlFor: "model-router-enabled", style: styles.label }, t("enabled")),
|
|
127
|
+
(0, import_react.createElement)("input", {
|
|
128
|
+
id: "model-router-enabled",
|
|
129
|
+
type: "checkbox",
|
|
130
|
+
checked: Boolean(state.value.enabled),
|
|
131
|
+
onChange: (event) => {
|
|
132
|
+
void props.setEnabled(event.currentTarget.checked);
|
|
133
|
+
},
|
|
134
|
+
style: styles.switch
|
|
135
|
+
}),
|
|
136
|
+
(0, import_react.createElement)("span", { style: styles.status }, state.value.enabled ? t("on") : t("off"))
|
|
137
|
+
]),
|
|
138
|
+
(0, import_react.createElement)("p", { style: styles.hint }, t("enabledHint")),
|
|
139
|
+
overridden ? (0, import_react.createElement)(
|
|
140
|
+
"button",
|
|
141
|
+
{ type: "button", style: styles.reset, onClick: () => void props.reset() },
|
|
142
|
+
t("reset")
|
|
143
|
+
) : null,
|
|
144
|
+
(0, import_react.createElement)("dl", { style: styles.routes }, [
|
|
145
|
+
routeRow(t("plannerRoute"), state.value.planner?.model ?? "\u2014"),
|
|
146
|
+
routeRow(t("executorRoute"), state.value.executor?.model ?? "\u2014"),
|
|
147
|
+
routeRow(t("mode"), modeLabel(t, state.value.mode))
|
|
148
|
+
])
|
|
149
|
+
] : []
|
|
150
|
+
]);
|
|
151
|
+
}
|
|
152
|
+
var styles = {
|
|
153
|
+
card: {
|
|
154
|
+
display: "flex",
|
|
155
|
+
flexDirection: "column",
|
|
156
|
+
gap: "10px",
|
|
157
|
+
padding: "16px 18px",
|
|
158
|
+
background: "var(--dsw-alias-bg-layer-3)",
|
|
159
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
160
|
+
borderRadius: "12px",
|
|
161
|
+
color: "var(--dsw-alias-label-primary)",
|
|
162
|
+
font: "inherit"
|
|
163
|
+
},
|
|
164
|
+
headRow: {
|
|
165
|
+
display: "flex",
|
|
166
|
+
alignItems: "center",
|
|
167
|
+
gap: "8px"
|
|
168
|
+
},
|
|
169
|
+
title: {
|
|
170
|
+
margin: 0,
|
|
171
|
+
fontSize: "15px",
|
|
172
|
+
fontWeight: 600,
|
|
173
|
+
lineHeight: 1.4
|
|
174
|
+
},
|
|
175
|
+
badge: {
|
|
176
|
+
whiteSpace: "nowrap",
|
|
177
|
+
background: "var(--dsw-alias-bg-module-platform)",
|
|
178
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
179
|
+
borderRadius: "999px",
|
|
180
|
+
padding: "1px 8px",
|
|
181
|
+
fontSize: "11px",
|
|
182
|
+
fontWeight: 500,
|
|
183
|
+
lineHeight: "17px"
|
|
184
|
+
},
|
|
185
|
+
description: {
|
|
186
|
+
margin: 0,
|
|
187
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
188
|
+
fontSize: "13px",
|
|
189
|
+
lineHeight: 1.5
|
|
190
|
+
},
|
|
191
|
+
switchRow: {
|
|
192
|
+
display: "flex",
|
|
193
|
+
alignItems: "center",
|
|
194
|
+
gap: "10px",
|
|
195
|
+
padding: "10px 0"
|
|
196
|
+
},
|
|
197
|
+
label: {
|
|
198
|
+
flex: 1,
|
|
199
|
+
minWidth: 0,
|
|
200
|
+
fontSize: "13px",
|
|
201
|
+
fontWeight: 500,
|
|
202
|
+
lineHeight: 1.5
|
|
203
|
+
},
|
|
204
|
+
switch: {
|
|
205
|
+
width: "16px",
|
|
206
|
+
height: "16px",
|
|
207
|
+
accentColor: "var(--dsw-alias-brand-primary)",
|
|
208
|
+
cursor: "pointer"
|
|
209
|
+
},
|
|
210
|
+
status: {
|
|
211
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
212
|
+
fontSize: "12px",
|
|
213
|
+
lineHeight: 1.5,
|
|
214
|
+
minWidth: "24px"
|
|
215
|
+
},
|
|
216
|
+
hint: {
|
|
217
|
+
margin: 0,
|
|
218
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
219
|
+
fontSize: "12px",
|
|
220
|
+
lineHeight: 1.5
|
|
221
|
+
},
|
|
222
|
+
reset: {
|
|
223
|
+
alignSelf: "flex-start",
|
|
224
|
+
font: "inherit",
|
|
225
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
226
|
+
background: "none",
|
|
227
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
228
|
+
borderRadius: "8px",
|
|
229
|
+
padding: "4px 10px",
|
|
230
|
+
fontSize: "12px",
|
|
231
|
+
lineHeight: 1.5,
|
|
232
|
+
cursor: "pointer"
|
|
233
|
+
},
|
|
234
|
+
routes: {
|
|
235
|
+
margin: "4px 0 0",
|
|
236
|
+
padding: "10px 0 0",
|
|
237
|
+
borderTop: "1px solid var(--dsw-alias-border-l2)",
|
|
238
|
+
display: "flex",
|
|
239
|
+
flexDirection: "column",
|
|
240
|
+
gap: "6px"
|
|
241
|
+
},
|
|
242
|
+
routeRow: {
|
|
243
|
+
display: "flex",
|
|
244
|
+
alignItems: "center",
|
|
245
|
+
gap: "8px"
|
|
246
|
+
},
|
|
247
|
+
routeLabel: {
|
|
248
|
+
minWidth: 0,
|
|
249
|
+
flex: 1,
|
|
250
|
+
margin: 0,
|
|
251
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
252
|
+
fontSize: "12px",
|
|
253
|
+
lineHeight: 1.5
|
|
254
|
+
},
|
|
255
|
+
routeValue: {
|
|
256
|
+
margin: 0,
|
|
257
|
+
color: "var(--dsw-alias-label-primary)",
|
|
258
|
+
fontSize: "12px",
|
|
259
|
+
lineHeight: 1.5,
|
|
260
|
+
fontVariantNumeric: "tabular-nums"
|
|
261
|
+
},
|
|
262
|
+
note: {
|
|
263
|
+
margin: 0,
|
|
264
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
265
|
+
fontSize: "12px",
|
|
266
|
+
lineHeight: 1.5
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
function apply(ctx) {
|
|
270
|
+
const t = ctx.locale.bind(NS);
|
|
271
|
+
ctx.effect(
|
|
272
|
+
() => {
|
|
273
|
+
ctx.locale.register(NS, { zh, en });
|
|
274
|
+
},
|
|
275
|
+
"model-router: card dictionaries"
|
|
276
|
+
);
|
|
277
|
+
const controller = new ModelRouterCardController(ctx.settingsScope.bind({ namespace: NS }));
|
|
278
|
+
ctx.slots.inject("settings.plugin.item", function* () {
|
|
279
|
+
yield ctx.slots.register(
|
|
280
|
+
{
|
|
281
|
+
name: "settings.plugin.item",
|
|
282
|
+
key: NS,
|
|
283
|
+
locale: NS,
|
|
284
|
+
inject: () => controller.inject()
|
|
285
|
+
},
|
|
286
|
+
ModelRouterCard
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
ctx.effect(
|
|
290
|
+
() => () => controller.dispose(),
|
|
291
|
+
"model-router: card controller"
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return module.exports;
|
|
296
|
+
}
|
|
297
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-model-router",
|
|
3
3
|
"description": "DeepSeek Harness plugin: role-based model routing — the planner agent runs on deepseek-v4-pro, delegated executor subagents run on deepseek-v4-flash.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
"types": "./lib/index.d.ts",
|
|
14
14
|
"default": "./lib/index.js"
|
|
15
15
|
},
|
|
16
|
+
"./client": {
|
|
17
|
+
"default": "./lib/client.js"
|
|
18
|
+
},
|
|
16
19
|
"./package.json": "./package.json"
|
|
17
20
|
},
|
|
18
21
|
"files": [
|
|
@@ -24,10 +27,19 @@
|
|
|
24
27
|
"dsh": {
|
|
25
28
|
"bundle": {
|
|
26
29
|
"patch": "./cordis.patch.yml"
|
|
30
|
+
},
|
|
31
|
+
"client": {
|
|
32
|
+
"inject": [
|
|
33
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
35
|
+
"@deepseek-ai/dsh-client-locale"
|
|
36
|
+
],
|
|
37
|
+
"platform": "web"
|
|
27
38
|
}
|
|
28
39
|
},
|
|
29
40
|
"scripts": {
|
|
30
|
-
"build": "tsup",
|
|
41
|
+
"build": "tsup && npm run build:client",
|
|
42
|
+
"build:client": "node scripts/build-client.mjs",
|
|
31
43
|
"prepare": "npm run build",
|
|
32
44
|
"typecheck": "tsc --noEmit",
|
|
33
45
|
"test": "vitest run"
|
|
@@ -44,6 +56,8 @@
|
|
|
44
56
|
"@deepseek-ai/dsh-plan-mode": "^0.1.0-rc.8",
|
|
45
57
|
"@deepseek-ai/dsh-settings": "^0.1.0-rc.8",
|
|
46
58
|
"@types/node": "^22.10.0",
|
|
59
|
+
"@types/react": "^18.3.12",
|
|
60
|
+
"react": "^18.3.1",
|
|
47
61
|
"tsup": "^8.3.5",
|
|
48
62
|
"typescript": "^5.7.2",
|
|
49
63
|
"vitest": "^2.1.8"
|