dsh-model-router 0.1.1 → 0.2.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 -3
- package/cordis.patch.yml +8 -0
- package/lib/index.d.ts +45 -4
- package/lib/index.js +34 -6
- package/lib/index.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -42,13 +42,13 @@ An agent is an *executor* if it carries either of the markers the harness stamps
|
|
|
42
42
|
|
|
43
43
|
Everything else is a planner. That logic lives in `src/policy.ts` as a plain function, so it's easy to reason about and test.
|
|
44
44
|
|
|
45
|
-
###
|
|
45
|
+
### What the router does and doesn't override
|
|
46
46
|
|
|
47
|
-
The router
|
|
47
|
+
The router always stamps `provider` + `model`. `reasoningEffort` and `maxTokens` are *optional per role*: set them in the config and they're enforced for that role; leave them out and those fields inherit from your session's selection. So picking "max effort" in the UI but not pinning `reasoningEffort` in the config still gives you max-effort thinking — it just happens on the routed model.
|
|
48
48
|
|
|
49
49
|
## Tuning
|
|
50
50
|
|
|
51
|
-
All configuration lives on the plugin row.
|
|
51
|
+
All configuration lives on the plugin row. Patch it in the profile's `cordis.patch.yml`:
|
|
52
52
|
|
|
53
53
|
```yaml
|
|
54
54
|
- patch:
|
|
@@ -57,15 +57,34 @@ All configuration lives on the plugin row. If you want different models, or you'
|
|
|
57
57
|
planner: # root-agent route
|
|
58
58
|
provider: deepseek-official
|
|
59
59
|
model: deepseek-v4-pro
|
|
60
|
+
reasoningEffort: high # off | low | high | max (omit to inherit)
|
|
61
|
+
maxTokens: 8192 # output cap (omit to inherit)
|
|
60
62
|
executor: # subagent route
|
|
61
63
|
provider: deepseek-official
|
|
62
64
|
model: deepseek-v4-flash
|
|
65
|
+
reasoningEffort: low
|
|
66
|
+
mode: strict # strict | plan (see below)
|
|
63
67
|
promptSection: true # register the always-on routing section
|
|
64
68
|
skill: true # register the pro-flash-routing skill
|
|
65
69
|
```
|
|
66
70
|
|
|
71
|
+
`mode` controls how the root agent is treated: `strict` keeps it on the planner route always; `plan` sends the root to the executor route unless plan mode is active, reserving pro for real planning.
|
|
72
|
+
|
|
67
73
|
The defaults are exactly the table at the top of this page. To switch the router off for a session, disable the row (`disabled: true`) or remove the plugin — `dsh plugin --profile web remove dsh-model-router`.
|
|
68
74
|
|
|
75
|
+
## Reduce pro token usage
|
|
76
|
+
|
|
77
|
+
The planner is the expensive model, so most of the savings come from shrinking its spend:
|
|
78
|
+
|
|
79
|
+
- **Lower `reasoningEffort`.** The harness default runs pro at `max`, which produces a lot of reasoning tokens. `high` (or `low`) on the planner route keeps most of the quality at a fraction of the cost.
|
|
80
|
+
- **Cap output** with `maxTokens` on the planner route so a verbose turn can't balloon.
|
|
81
|
+
- **Reserve pro for planning** with `mode: plan` — trivial Q&A and execution-style turns stop hitting pro at all.
|
|
82
|
+
- **Keep the planner's context lean.** Input tokens dominate after reasoning. Delegate aggressively and trust the subagent's report; don't re-read big files or full transcripts on the planner. Use targeted reads and let auto-compaction (`/compact`) trim history.
|
|
83
|
+
- **Tune the host pruner.** The tool-result pruner truncates oversized results before they reach the model (default ~8 KB); lowering `tool-result-pruner` → `thresholdChars` trims more planner input. That's harness config, not this plugin's row.
|
|
84
|
+
- **Exploit DeepSeek's context cache.** Repeated prefixes are served from cache at a big discount, so keep the system prompt and conversation prefix stable between turns.
|
|
85
|
+
|
|
86
|
+
The first three are one-line changes on this plugin's row; the last three are discipline and host tuning.
|
|
87
|
+
|
|
69
88
|
## Does it work?
|
|
70
89
|
|
|
71
90
|
I verified it against a real session log. Run a task that makes the agent plan and delegate, then check which models actually made the requests:
|
package/cordis.patch.yml
CHANGED
|
@@ -13,10 +13,18 @@
|
|
|
13
13
|
planner:
|
|
14
14
|
provider: deepseek-official
|
|
15
15
|
model: deepseek-v4-pro
|
|
16
|
+
# reasoningEffort: high # optional: off | low | high | max (omit to inherit)
|
|
17
|
+
# maxTokens: 8192 # optional output-token cap (omit to inherit)
|
|
16
18
|
# Every delegated subagent: code writing and execution.
|
|
17
19
|
executor:
|
|
18
20
|
provider: deepseek-official
|
|
19
21
|
model: deepseek-v4-flash
|
|
22
|
+
# reasoningEffort: low
|
|
23
|
+
# maxTokens: 16384
|
|
24
|
+
# strict: root is always the planner (pro).
|
|
25
|
+
# plan: root is pro only while plan mode is active; otherwise it
|
|
26
|
+
# falls back to the executor route to reserve pro for planning.
|
|
27
|
+
mode: strict
|
|
20
28
|
# Publish the always-on routing convention prompt section.
|
|
21
29
|
promptSection: true
|
|
22
30
|
# Register the `pro-flash-routing` skill in the session catalog.
|
package/lib/index.d.ts
CHANGED
|
@@ -6,17 +6,34 @@ import z from '@deepseek-ai/schemastery';
|
|
|
6
6
|
* Kept free of Cordis imports so the policy is trivially unit-testable.
|
|
7
7
|
* @module dsh-model-router/policy
|
|
8
8
|
*/
|
|
9
|
+
/** Reasoning-effort levels a route may pin (mirrors the harness vocabulary). */
|
|
10
|
+
type ReasoningEffort = "off" | "low" | "high" | "max";
|
|
11
|
+
/**
|
|
12
|
+
* How the router treats the root agent.
|
|
13
|
+
* - `strict`: the root agent is always the planner (pro).
|
|
14
|
+
* - `plan`: the root agent is pro only while plan mode is active; otherwise it
|
|
15
|
+
* falls back to the executor route, reserving pro for real planning.
|
|
16
|
+
*/
|
|
17
|
+
type RoutingMode = "strict" | "plan";
|
|
9
18
|
/** One route: a provider/model pair stamped onto an agent request. */
|
|
10
19
|
interface ModelRoute {
|
|
11
20
|
provider: string;
|
|
12
21
|
model: string;
|
|
22
|
+
/**
|
|
23
|
+
* Optional reasoning-effort override. When omitted, the request inherits the
|
|
24
|
+
* session's own selection; when set, the router pins it for that role.
|
|
25
|
+
*/
|
|
26
|
+
reasoningEffort?: ReasoningEffort;
|
|
27
|
+
/** Optional output-token cap for the role; omitted means inherit. */
|
|
28
|
+
maxTokens?: number;
|
|
13
29
|
}
|
|
14
30
|
/** The two roles the router distinguishes. */
|
|
15
31
|
type AgentRole = "planner" | "executor";
|
|
16
|
-
/** Resolved router configuration: one route per role. */
|
|
32
|
+
/** Resolved router configuration: one route per role plus routing mode. */
|
|
17
33
|
interface RouterConfig {
|
|
18
34
|
planner: ModelRoute;
|
|
19
35
|
executor: ModelRoute;
|
|
36
|
+
mode: RoutingMode;
|
|
20
37
|
promptSection: boolean;
|
|
21
38
|
skill: boolean;
|
|
22
39
|
}
|
|
@@ -36,9 +53,11 @@ declare function roleFor(agent: unknown): AgentRole;
|
|
|
36
53
|
* Resolve the route for one agent.
|
|
37
54
|
* @param agent - the live agent.
|
|
38
55
|
* @param config - the resolved router configuration.
|
|
56
|
+
* @param planModeActive - whether plan mode is currently folded active for the
|
|
57
|
+
* agent's session; consulted only in `plan` routing mode.
|
|
39
58
|
* @returns the model route to stamp, or `undefined` to leave the request alone.
|
|
40
59
|
*/
|
|
41
|
-
declare function routeFor(agent: unknown, config: RouterConfig): ModelRoute | undefined;
|
|
60
|
+
declare function routeFor(agent: unknown, config: RouterConfig, planModeActive?: boolean): ModelRoute | undefined;
|
|
42
61
|
|
|
43
62
|
/**
|
|
44
63
|
* dsh-model-router: role-based model routing for the DeepSeek Harness.
|
|
@@ -49,6 +68,10 @@ declare function routeFor(agent: unknown, config: RouterConfig): ModelRoute | un
|
|
|
49
68
|
* in every mode (web / headless / tui) and every agent preset, including
|
|
50
69
|
* subagents the delegation tools create.
|
|
51
70
|
*
|
|
71
|
+
* Each role route may also pin `reasoningEffort` and `maxTokens`; when set,
|
|
72
|
+
* they override the session's selection for that role. A `mode` switch lets a
|
|
73
|
+
* deployment reserve the planner route for actual planning.
|
|
74
|
+
*
|
|
52
75
|
* The plugin also publishes:
|
|
53
76
|
* - a system-prompt section stating the planner/executor convention, and
|
|
54
77
|
* - the `pro-flash-routing` skill teaching the agent to plan itself and
|
|
@@ -64,41 +87,59 @@ declare const Config: z<Schemastery.ObjectS<{
|
|
|
64
87
|
planner: z<Schemastery.ObjectS<{
|
|
65
88
|
provider: z<string, string>;
|
|
66
89
|
model: z<string, string>;
|
|
90
|
+
reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
|
|
91
|
+
maxTokens: z<number, number>;
|
|
67
92
|
}>, Schemastery.ObjectT<{
|
|
68
93
|
provider: z<string, string>;
|
|
69
94
|
model: z<string, string>;
|
|
95
|
+
reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
|
|
96
|
+
maxTokens: z<number, number>;
|
|
70
97
|
}>>;
|
|
71
98
|
executor: z<Schemastery.ObjectS<{
|
|
72
99
|
provider: z<string, string>;
|
|
73
100
|
model: z<string, string>;
|
|
101
|
+
reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
|
|
102
|
+
maxTokens: z<number, number>;
|
|
74
103
|
}>, Schemastery.ObjectT<{
|
|
75
104
|
provider: z<string, string>;
|
|
76
105
|
model: z<string, string>;
|
|
106
|
+
reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
|
|
107
|
+
maxTokens: z<number, number>;
|
|
77
108
|
}>>;
|
|
109
|
+
mode: z<"strict" | "plan", "strict" | "plan">;
|
|
78
110
|
promptSection: z<boolean, boolean>;
|
|
79
111
|
skill: z<boolean, boolean>;
|
|
80
112
|
}>, Schemastery.ObjectT<{
|
|
81
113
|
planner: z<Schemastery.ObjectS<{
|
|
82
114
|
provider: z<string, string>;
|
|
83
115
|
model: z<string, string>;
|
|
116
|
+
reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
|
|
117
|
+
maxTokens: z<number, number>;
|
|
84
118
|
}>, Schemastery.ObjectT<{
|
|
85
119
|
provider: z<string, string>;
|
|
86
120
|
model: z<string, string>;
|
|
121
|
+
reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
|
|
122
|
+
maxTokens: z<number, number>;
|
|
87
123
|
}>>;
|
|
88
124
|
executor: z<Schemastery.ObjectS<{
|
|
89
125
|
provider: z<string, string>;
|
|
90
126
|
model: z<string, string>;
|
|
127
|
+
reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
|
|
128
|
+
maxTokens: z<number, number>;
|
|
91
129
|
}>, Schemastery.ObjectT<{
|
|
92
130
|
provider: z<string, string>;
|
|
93
131
|
model: z<string, string>;
|
|
132
|
+
reasoningEffort: z<"off" | "low" | "high" | "max", "off" | "low" | "high" | "max">;
|
|
133
|
+
maxTokens: z<number, number>;
|
|
94
134
|
}>>;
|
|
135
|
+
mode: z<"strict" | "plan", "strict" | "plan">;
|
|
95
136
|
promptSection: z<boolean, boolean>;
|
|
96
137
|
skill: z<boolean, boolean>;
|
|
97
138
|
}>>;
|
|
98
139
|
declare const SKILL_NAME = "pro-flash-routing";
|
|
99
140
|
declare const SKILL_DESCRIPTION = "Route planning and code execution across models: plan on the pro planner agent, delegate implementation to flash executor subagents.";
|
|
100
141
|
declare const SKILL_WHEN_TO_USE = "Use when a task combines planning and implementation: before writing code, after a plan is approved, when delegating execution work, or when the user asks about the pro/flash routing convention.";
|
|
101
|
-
declare const SKILL_CONTENT = "# Pro planner / Flash executor routing\n\nThis session routes models by role:\n\n- **Planner (this agent)** \u2014 `deepseek-v4-pro`. Planning, design decisions, reviewing delegated output, and user-facing synthesis happen here.\n- **Executors (every subagent)** \u2014 `deepseek-v4-flash`. Implementation work happens there: writing code, running commands, builds, and tests. The harness forces the model automatically; you do not select it.\n\n## Working rhythm\n\n1. **Plan here.** Explore, decide the approach, and (when plan mode is on) submit the plan with `exit_plan_mode`. The plan stays on this agent.\n2. **Delegate the execution.** Once a plan is approved, hand each self-contained chunk of implementation to a subagent with a complete prompt: exact files to touch, the change to make, and how to verify. Subagents are automatically routed to `deepseek-v4-flash`, so keep them execution-focused: give them the decision, not the decision to make.\n3. **Review here.** Read the subagent's result on this agent, verify it yourself (tests, diffs, logs), and iterate with follow-up messages to the same subagent when available.\n4. **Report here.** Summaries, plans, and answers to the user come from this agent.\n\n## Delegation guidelines\n\n- Start independent delegations together in one assistant message and continue useful work while they run (background mode by default).\n- Prefer `subagent` for self-contained work and `workflow` when many independent pieces need fan-out; their workers run on flash as well.\n- Do not delegate design: subagents execute decisions already made.\n- If a subagent's task grows into design work, pull it back to this agent and re-delegate the narrowed execution.\n\n## Verification\n\n- Executor output was produced by `deepseek-v4-flash`; planner output by `deepseek-v4-pro`. If you need to confirm, check the session log's model metadata.\n- If routing ever looks wrong, the `model-router` plugin row in the profile composition is the single place that owns it.";
|
|
142
|
+
declare const SKILL_CONTENT = "# Pro planner / Flash executor routing\n\nThis session routes models by role:\n\n- **Planner (this agent)** \u2014 `deepseek-v4-pro`. Planning, design decisions, reviewing delegated output, and user-facing synthesis happen here.\n- **Executors (every subagent)** \u2014 `deepseek-v4-flash`. Implementation work happens there: writing code, running commands, builds, and tests. The harness forces the model automatically; you do not select it.\n\n## Working rhythm\n\n1. **Plan here.** Explore, decide the approach, and (when plan mode is on) submit the plan with `exit_plan_mode`. The plan stays on this agent.\n2. **Delegate the execution.** Once a plan is approved, hand each self-contained chunk of implementation to a subagent with a complete prompt: exact files to touch, the change to make, and how to verify. Subagents are automatically routed to `deepseek-v4-flash`, so keep them execution-focused: give them the decision, not the decision to make.\n3. **Review here.** Read the subagent's result on this agent, verify it yourself (tests, diffs, logs), and iterate with follow-up messages to the same subagent when available.\n4. **Report here.** Summaries, plans, and answers to the user come from this agent.\n\n## Keep this agent's context lean\n\nInput tokens are the expensive part of the planner. Don't re-read large files or full transcripts on this agent \u2014 trust the subagent's final report. Prefer targeted reads (offset/limit) over whole files. When the context grows, compact rather than re-sending everything.\n\n## Delegation guidelines\n\n- Start independent delegations together in one assistant message and continue useful work while they run (background mode by default).\n- Prefer `subagent` for self-contained work and `workflow` when many independent pieces need fan-out; their workers run on flash as well.\n- Do not delegate design: subagents execute decisions already made.\n- If a subagent's task grows into design work, pull it back to this agent and re-delegate the narrowed execution.\n\n## Verification\n\n- Executor output was produced by `deepseek-v4-flash`; planner output by `deepseek-v4-pro`. If you need to confirm, check the session log's model metadata.\n- If routing ever looks wrong, the `model-router` plugin row in the profile composition is the single place that owns it.";
|
|
102
143
|
/** The plugin row id the bundle patch must insert. */
|
|
103
144
|
declare const ROW_ID = "model-router";
|
|
104
145
|
/**
|
|
@@ -110,4 +151,4 @@ declare class ModelRouter extends Service {
|
|
|
110
151
|
constructor(ctx: Context, rawConfig?: unknown);
|
|
111
152
|
}
|
|
112
153
|
|
|
113
|
-
export { type AgentRole, Config, type ModelRoute, ModelRouter, ROW_ID, type RouterConfig, SKILL_CONTENT, SKILL_DESCRIPTION, SKILL_NAME, SKILL_WHEN_TO_USE, ModelRouter as default, name, roleFor, routeFor };
|
|
154
|
+
export { type AgentRole, Config, type ModelRoute, ModelRouter, ROW_ID, type ReasoningEffort, type RouterConfig, type RoutingMode, SKILL_CONTENT, SKILL_DESCRIPTION, SKILL_NAME, SKILL_WHEN_TO_USE, ModelRouter as default, name, roleFor, routeFor };
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { Service } from "@deepseek-ai/cordis";
|
|
3
3
|
import z from "@deepseek-ai/schemastery";
|
|
4
|
+
import { foldPlanMode } from "@deepseek-ai/dsh-plan-mode";
|
|
4
5
|
|
|
5
6
|
// src/policy.ts
|
|
6
7
|
function roleFor(agent) {
|
|
@@ -12,15 +13,20 @@ function roleFor(agent) {
|
|
|
12
13
|
if (origin === "subagent") return "executor";
|
|
13
14
|
return "planner";
|
|
14
15
|
}
|
|
15
|
-
function routeFor(agent, config) {
|
|
16
|
-
|
|
16
|
+
function routeFor(agent, config, planModeActive = false) {
|
|
17
|
+
const role = roleFor(agent);
|
|
18
|
+
if (role === "executor") return config.executor;
|
|
19
|
+
if (config.mode === "plan" && !planModeActive) return config.executor;
|
|
20
|
+
return config.planner;
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
// src/index.ts
|
|
20
24
|
var name = "model-router";
|
|
21
25
|
var ModelRouteSchema = z.object({
|
|
22
26
|
provider: z.string().min(1),
|
|
23
|
-
model: z.string().min(1)
|
|
27
|
+
model: z.string().min(1),
|
|
28
|
+
reasoningEffort: z.union(["off", "low", "high", "max"]),
|
|
29
|
+
maxTokens: z.number().min(1)
|
|
24
30
|
});
|
|
25
31
|
var Config = z.object({
|
|
26
32
|
planner: ModelRouteSchema.default({
|
|
@@ -31,6 +37,7 @@ var Config = z.object({
|
|
|
31
37
|
provider: "deepseek-official",
|
|
32
38
|
model: "deepseek-v4-flash"
|
|
33
39
|
}),
|
|
40
|
+
mode: z.union(["strict", "plan"]).default("strict"),
|
|
34
41
|
promptSection: z.boolean().default(true),
|
|
35
42
|
skill: z.boolean().default(true)
|
|
36
43
|
});
|
|
@@ -39,12 +46,13 @@ function resolveConfig(raw) {
|
|
|
39
46
|
return {
|
|
40
47
|
planner: parsed.planner,
|
|
41
48
|
executor: parsed.executor,
|
|
49
|
+
mode: parsed.mode,
|
|
42
50
|
promptSection: parsed.promptSection,
|
|
43
51
|
skill: parsed.skill
|
|
44
52
|
};
|
|
45
53
|
}
|
|
46
54
|
var SECTION_ORDER = -50;
|
|
47
|
-
var SECTION_TEXT = `Model routing is role-based
|
|
55
|
+
var SECTION_TEXT = `Model routing is role-based. Planning runs on {PLANNER_MODEL}; implementation runs on {EXECUTOR_MODEL}. You are the root agent: plan, design, review subagent output, and write the final answer here. Delegate implementation \u2014 writing code, running commands, builds, tests \u2014 to subagents with complete, self-contained prompts, preferring background delegation for independent work. Keep plans and replies concise. Do not hand-write large amounts of code or run long executions here; delegate instead.`;
|
|
48
56
|
var SKILL_NAME = "pro-flash-routing";
|
|
49
57
|
var SKILL_DESCRIPTION = "Route planning and code execution across models: plan on the pro planner agent, delegate implementation to flash executor subagents.";
|
|
50
58
|
var SKILL_WHEN_TO_USE = `Use when a task combines planning and implementation: before writing code, after a plan is approved, when delegating execution work, or when the user asks about the pro/flash routing convention.`;
|
|
@@ -62,6 +70,10 @@ This session routes models by role:
|
|
|
62
70
|
3. **Review here.** Read the subagent's result on this agent, verify it yourself (tests, diffs, logs), and iterate with follow-up messages to the same subagent when available.
|
|
63
71
|
4. **Report here.** Summaries, plans, and answers to the user come from this agent.
|
|
64
72
|
|
|
73
|
+
## Keep this agent's context lean
|
|
74
|
+
|
|
75
|
+
Input tokens are the expensive part of the planner. Don't re-read large files or full transcripts on this agent \u2014 trust the subagent's final report. Prefer targeted reads (offset/limit) over whole files. When the context grows, compact rather than re-sending everything.
|
|
76
|
+
|
|
65
77
|
## Delegation guidelines
|
|
66
78
|
|
|
67
79
|
- Start independent delegations together in one assistant message and continue useful work while they run (background mode by default).
|
|
@@ -74,6 +86,15 @@ This session routes models by role:
|
|
|
74
86
|
- Executor output was produced by \`deepseek-v4-flash\`; planner output by \`deepseek-v4-pro\`. If you need to confirm, check the session log's model metadata.
|
|
75
87
|
- If routing ever looks wrong, the \`model-router\` plugin row in the profile composition is the single place that owns it.`;
|
|
76
88
|
var ROW_ID = "model-router";
|
|
89
|
+
function isPlanModeActive(agent) {
|
|
90
|
+
const events = agent.session?.events;
|
|
91
|
+
if (!Array.isArray(events)) return false;
|
|
92
|
+
try {
|
|
93
|
+
return foldPlanMode(events);
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
77
98
|
var ModelRouter = class extends Service {
|
|
78
99
|
static inject = ["skills", "systemPrompt"];
|
|
79
100
|
config;
|
|
@@ -86,9 +107,16 @@ var ModelRouter = class extends Service {
|
|
|
86
107
|
"agent/request",
|
|
87
108
|
async (payload, next) => {
|
|
88
109
|
const resolved = await next();
|
|
89
|
-
const route = routeFor(agent, this.config);
|
|
110
|
+
const route = routeFor(agent, this.config, isPlanModeActive(agent));
|
|
90
111
|
if (route === void 0) return resolved;
|
|
91
|
-
|
|
112
|
+
const stamped = {
|
|
113
|
+
...resolved,
|
|
114
|
+
provider: route.provider,
|
|
115
|
+
model: route.model
|
|
116
|
+
};
|
|
117
|
+
if (route.reasoningEffort !== void 0) stamped.reasoningEffort = route.reasoningEffort;
|
|
118
|
+
if (route.maxTokens !== void 0) stamped.maxTokens = route.maxTokens;
|
|
119
|
+
return stamped;
|
|
92
120
|
},
|
|
93
121
|
{ prepend: true }
|
|
94
122
|
);
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/policy.ts"],"sourcesContent":["/**\n * dsh-model-router: role-based model routing for the DeepSeek Harness.\n *\n * The planner (the session's root agent) runs on `deepseek-v4-pro`; delegated\n * executor subagents run on `deepseek-v4-flash`. Enforcement is a per-agent\n * `agent/request` rewrite registered when the agent is created, so it applies\n * in every mode (web / headless / tui) and every agent preset, including\n * subagents the delegation tools create.\n *\n * The plugin also publishes:\n * - a system-prompt section stating the planner/executor convention, and\n * - the `pro-flash-routing` skill teaching the agent to plan itself and\n * delegate code execution to flash subagents.\n *\n * @module dsh-model-router\n */\nimport { Context, Service } from \"@deepseek-ai/cordis\";\nimport z from \"@deepseek-ai/schemastery\";\nimport { roleFor, routeFor, type RouterConfig } from \"./policy.js\";\n\n/** Plugin row id; the bundle patch inserts it under this id. */\nconst name = \"model-router\";\n\n/** One provider/model pair, with defaults. */\nconst ModelRouteSchema = z.object({\n provider: z.string().min(1),\n model: z.string().min(1),\n});\n\n/** The plugin's public config, validated at row load. */\nconst Config = z.object({\n planner: ModelRouteSchema.default({\n provider: \"deepseek-official\",\n model: \"deepseek-v4-pro\",\n }),\n executor: ModelRouteSchema.default({\n provider: \"deepseek-official\",\n model: \"deepseek-v4-flash\",\n }),\n promptSection: z.boolean().default(true),\n skill: z.boolean().default(true),\n});\n\n/**\n * Resolve raw row config into the internal shape, failing loud on garbage.\n * Schemastery schemas are callable: invoking validates and applies defaults.\n * @param raw - the row's config object.\n * @returns the validated RouterConfig.\n */\nfunction resolveConfig(raw: unknown): RouterConfig {\n const parsed = Config(raw ?? {});\n return {\n planner: parsed.planner,\n executor: parsed.executor,\n promptSection: parsed.promptSection,\n skill: parsed.skill,\n };\n}\n\n/**\n * Always-on guidance section. Negative order renders before the persona, so\n * the convention is established before the agent's identity line.\n */\nconst SECTION_ORDER = -50;\n\nconst SECTION_TEXT = `Model routing is role-based in this session. You are the planner and run on {PLANNER_MODEL}. Do your own planning, design, review of delegated output, and user-facing synthesis on this agent. Code execution runs on {EXECUTOR_MODEL}: after a plan is approved, delegate implementation work — writing code, running commands, builds, and tests — to subagents, which are automatically routed to {EXECUTOR_MODEL}. Give each subagent a complete, self-contained prompt and prefer background delegation for independent work. Do not hand-write large amounts of code or run long executions on this planner agent; delegate instead.`;\n\nconst SKILL_NAME = \"pro-flash-routing\";\n\nconst SKILL_DESCRIPTION =\n \"Route planning and code execution across models: plan on the pro planner agent, delegate implementation to flash executor subagents.\";\n\nconst SKILL_WHEN_TO_USE = `Use when a task combines planning and implementation: before writing code, after a plan is approved, when delegating execution work, or when the user asks about the pro/flash routing convention.`;\n\nconst SKILL_CONTENT = `# Pro planner / Flash executor routing\n\nThis session routes models by role:\n\n- **Planner (this agent)** — \\`deepseek-v4-pro\\`. Planning, design decisions, reviewing delegated output, and user-facing synthesis happen here.\n- **Executors (every subagent)** — \\`deepseek-v4-flash\\`. Implementation work happens there: writing code, running commands, builds, and tests. The harness forces the model automatically; you do not select it.\n\n## Working rhythm\n\n1. **Plan here.** Explore, decide the approach, and (when plan mode is on) submit the plan with \\`exit_plan_mode\\`. The plan stays on this agent.\n2. **Delegate the execution.** Once a plan is approved, hand each self-contained chunk of implementation to a subagent with a complete prompt: exact files to touch, the change to make, and how to verify. Subagents are automatically routed to \\`deepseek-v4-flash\\`, so keep them execution-focused: give them the decision, not the decision to make.\n3. **Review here.** Read the subagent's result on this agent, verify it yourself (tests, diffs, logs), and iterate with follow-up messages to the same subagent when available.\n4. **Report here.** Summaries, plans, and answers to the user come from this agent.\n\n## Delegation guidelines\n\n- Start independent delegations together in one assistant message and continue useful work while they run (background mode by default).\n- Prefer \\`subagent\\` for self-contained work and \\`workflow\\` when many independent pieces need fan-out; their workers run on flash as well.\n- Do not delegate design: subagents execute decisions already made.\n- If a subagent's task grows into design work, pull it back to this agent and re-delegate the narrowed execution.\n\n## Verification\n\n- Executor output was produced by \\`deepseek-v4-flash\\`; planner output by \\`deepseek-v4-pro\\`. If you need to confirm, check the session log's model metadata.\n- If routing ever looks wrong, the \\`model-router\\` plugin row in the profile composition is the single place that owns it.`;\n\n/** The plugin row id the bundle patch must insert. */\nconst ROW_ID = \"model-router\";\n\n/** Minimal structural view of the live agent object the router reads. */\ninterface AgentLike {\n ctx: AgentScopedContext;\n options?: { subagentDepth?: number };\n session?: { header?: { origin?: string } };\n}\n\n/** The agent-scoped context's waterfall surface the router uses. */\ninterface AgentScopedContext {\n on(\n event: \"agent/request\",\n listener: (\n payload: Record<string, unknown>,\n next: () => Promise<Record<string, unknown>>,\n ) => Promise<Record<string, unknown>>,\n options?: { prepend?: boolean },\n ): () => void;\n}\n\n/** Host-plane surface the router consumes (events, prompt registry, skills). */\ninterface HarnessContext {\n on(\n event: \"agent/created\",\n listener: (payload: { agent: AgentLike }) => void,\n ): () => void;\n on(event: \"agent/disposed\", listener: (agent: unknown) => void): () => void;\n systemPrompt: {\n section(section: { name: string; order: number; text: string }): unknown;\n };\n skills: {\n register(skill: {\n name: string;\n description: string;\n whenToUse?: string;\n content: string;\n source: string;\n }): unknown;\n };\n}\n\n/**\n * Cordis service: per-agent request routing plus the convention surface.\n */\nclass ModelRouter extends Service {\n static inject = [\"skills\", \"systemPrompt\"];\n\n config: RouterConfig;\n\n constructor(ctx: Context, rawConfig: unknown = {}) {\n super(ctx, \"modelRouter\");\n this.config = resolveConfig(rawConfig);\n const harness = ctx as unknown as HarnessContext;\n\n // Every agent that gets created — root sessions, delegation children,\n // workflow workers, ralph rounds — passes through here.\n harness.on(\"agent/created\", ({ agent }) => {\n // `prepend` puts this listener OUTERMOST in the `agent/request`\n // waterfall: the harness's model-selection listener runs inside it, so\n // this rewrite is applied LAST and wins over the session's selected\n // model (which dsh-base defaults to deepseek-v4-flash and the user\n // settings or UI can change).\n const dispose = agent.ctx.on(\n \"agent/request\",\n async (payload, next) => {\n const resolved = await next();\n const route = routeFor(agent, this.config);\n if (route === undefined) return resolved;\n return { ...resolved, provider: route.provider, model: route.model };\n },\n { prepend: true },\n );\n harness.on(\"agent/disposed\", (disposed) => {\n if (disposed === agent) dispose();\n });\n });\n\n if (this.config.promptSection) {\n harness.systemPrompt.section({\n name: ROW_ID,\n order: SECTION_ORDER,\n text: SECTION_TEXT.replaceAll(\"{PLANNER_MODEL}\", this.config.planner.model).replaceAll(\n \"{EXECUTOR_MODEL}\",\n this.config.executor.model,\n ),\n });\n }\n\n if (this.config.skill) {\n harness.skills.register({\n name: SKILL_NAME,\n description: SKILL_DESCRIPTION,\n whenToUse: SKILL_WHEN_TO_USE,\n content: SKILL_CONTENT,\n source: \"runtime\",\n });\n }\n }\n}\n\nexport {\n Config,\n ModelRouter,\n ModelRouter as default,\n name,\n ROW_ID,\n SKILL_CONTENT,\n SKILL_DESCRIPTION,\n SKILL_NAME,\n SKILL_WHEN_TO_USE,\n roleFor,\n routeFor,\n};\nexport type { AgentRole, ModelRoute, RouterConfig } from \"./policy.js\";\n","/**\n * Pure routing policy for dsh-model-router: which model each agent role gets.\n * Kept free of Cordis imports so the policy is trivially unit-testable.\n * @module dsh-model-router/policy\n */\n\n/** One route: a provider/model pair stamped onto an agent request. */\nexport interface ModelRoute {\n provider: string;\n model: string;\n}\n\n/** The two roles the router distinguishes. */\nexport type AgentRole = \"planner\" | \"executor\";\n\n/** Resolved router configuration: one route per role. */\nexport interface RouterConfig {\n planner: ModelRoute;\n executor: ModelRoute;\n promptSection: boolean;\n skill: boolean;\n}\n\n/**\n * Classify an agent as planner or executor.\n *\n * The main (root) agent of a session is the planner. Every agent created as a\n * delegation child — `subagent`, `subagent_fork`, workflow workers, ralph\n * rounds — is an executor. The harness stamps two durable facts on children:\n * `options.subagentDepth` (>= 1) and the session header `origin: \"subagent\"`.\n *\n * @param agent - the live agent (any subset of the runtime shape).\n * @returns the role the agent should be routed as.\n */\nexport function roleFor(agent: unknown): AgentRole {\n const options = (agent as { options?: unknown })?.options;\n const depth = (options as { subagentDepth?: unknown })?.subagentDepth;\n if (typeof depth === \"number\" && depth >= 1) return \"executor\";\n const session = (agent as { session?: unknown })?.session;\n const origin = (session as { header?: unknown })?.header\n ? ((session as { header: { origin?: unknown } }).header.origin)\n : undefined;\n if (origin === \"subagent\") return \"executor\";\n return \"planner\";\n}\n\n/**\n * Resolve the route for one agent.\n * @param agent - the live agent.\n * @param config - the resolved router configuration.\n * @returns the model route to stamp, or `undefined` to leave the request alone.\n */\nexport function routeFor(agent: unknown, config: RouterConfig): ModelRoute | undefined {\n return config[roleFor(agent)];\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/policy.ts"],"sourcesContent":["/**\n * dsh-model-router: role-based model routing for the DeepSeek Harness.\n *\n * The planner (the session's root agent) runs on `deepseek-v4-pro`; delegated\n * executor subagents run on `deepseek-v4-flash`. Enforcement is a per-agent\n * `agent/request` rewrite registered when the agent is created, so it applies\n * in every mode (web / headless / tui) and every agent preset, including\n * subagents the delegation tools create.\n *\n * Each role route may also pin `reasoningEffort` and `maxTokens`; when set,\n * they override the session's selection for that role. A `mode` switch lets a\n * deployment reserve the planner route for actual planning.\n *\n * The plugin also publishes:\n * - a system-prompt section stating the planner/executor convention, and\n * - the `pro-flash-routing` skill teaching the agent to plan itself and\n * delegate code execution to flash subagents.\n *\n * @module dsh-model-router\n */\nimport { Context, Service } from \"@deepseek-ai/cordis\";\nimport z from \"@deepseek-ai/schemastery\";\nimport { foldPlanMode } from \"@deepseek-ai/dsh-plan-mode\";\nimport { roleFor, routeFor, type RouterConfig } from \"./policy.js\";\n\n/** Plugin row id; the bundle patch inserts it under this id. */\nconst name = \"model-router\";\n\n/** One provider/model pair, with defaults and optional effort/token caps. */\nconst ModelRouteSchema = z.object({\n provider: z.string().min(1),\n model: z.string().min(1),\n reasoningEffort: z.union([\"off\", \"low\", \"high\", \"max\"]),\n maxTokens: z.number().min(1),\n});\n\n/** The plugin's public config, validated at row load. */\nconst Config = z.object({\n planner: ModelRouteSchema.default({\n provider: \"deepseek-official\",\n model: \"deepseek-v4-pro\",\n } as never),\n executor: ModelRouteSchema.default({\n provider: \"deepseek-official\",\n model: \"deepseek-v4-flash\",\n } as never),\n mode: z.union([\"strict\", \"plan\"]).default(\"strict\"),\n promptSection: z.boolean().default(true),\n skill: z.boolean().default(true),\n});\n\n/**\n * Resolve raw row config into the internal shape, failing loud on garbage.\n * Schemastery schemas are callable: invoking validates and applies defaults.\n * @param raw - the row's config object.\n * @returns the validated RouterConfig.\n */\nfunction resolveConfig(raw: unknown): RouterConfig {\n const parsed = Config(raw ?? {});\n return {\n planner: parsed.planner,\n executor: parsed.executor,\n mode: parsed.mode,\n promptSection: parsed.promptSection,\n skill: parsed.skill,\n };\n}\n\n/**\n * Always-on guidance section. Negative order renders before the persona, so\n * the convention is established before the agent's identity line.\n */\nconst SECTION_ORDER = -50;\n\nconst SECTION_TEXT = `Model routing is role-based. Planning runs on {PLANNER_MODEL}; implementation runs on {EXECUTOR_MODEL}. You are the root agent: plan, design, review subagent output, and write the final answer here. Delegate implementation — writing code, running commands, builds, tests — to subagents with complete, self-contained prompts, preferring background delegation for independent work. Keep plans and replies concise. Do not hand-write large amounts of code or run long executions here; delegate instead.`;\n\nconst SKILL_NAME = \"pro-flash-routing\";\n\nconst SKILL_DESCRIPTION =\n \"Route planning and code execution across models: plan on the pro planner agent, delegate implementation to flash executor subagents.\";\n\nconst SKILL_WHEN_TO_USE = `Use when a task combines planning and implementation: before writing code, after a plan is approved, when delegating execution work, or when the user asks about the pro/flash routing convention.`;\n\nconst SKILL_CONTENT = `# Pro planner / Flash executor routing\n\nThis session routes models by role:\n\n- **Planner (this agent)** — \\`deepseek-v4-pro\\`. Planning, design decisions, reviewing delegated output, and user-facing synthesis happen here.\n- **Executors (every subagent)** — \\`deepseek-v4-flash\\`. Implementation work happens there: writing code, running commands, builds, and tests. The harness forces the model automatically; you do not select it.\n\n## Working rhythm\n\n1. **Plan here.** Explore, decide the approach, and (when plan mode is on) submit the plan with \\`exit_plan_mode\\`. The plan stays on this agent.\n2. **Delegate the execution.** Once a plan is approved, hand each self-contained chunk of implementation to a subagent with a complete prompt: exact files to touch, the change to make, and how to verify. Subagents are automatically routed to \\`deepseek-v4-flash\\`, so keep them execution-focused: give them the decision, not the decision to make.\n3. **Review here.** Read the subagent's result on this agent, verify it yourself (tests, diffs, logs), and iterate with follow-up messages to the same subagent when available.\n4. **Report here.** Summaries, plans, and answers to the user come from this agent.\n\n## Keep this agent's context lean\n\nInput tokens are the expensive part of the planner. Don't re-read large files or full transcripts on this agent — trust the subagent's final report. Prefer targeted reads (offset/limit) over whole files. When the context grows, compact rather than re-sending everything.\n\n## Delegation guidelines\n\n- Start independent delegations together in one assistant message and continue useful work while they run (background mode by default).\n- Prefer \\`subagent\\` for self-contained work and \\`workflow\\` when many independent pieces need fan-out; their workers run on flash as well.\n- Do not delegate design: subagents execute decisions already made.\n- If a subagent's task grows into design work, pull it back to this agent and re-delegate the narrowed execution.\n\n## Verification\n\n- Executor output was produced by \\`deepseek-v4-flash\\`; planner output by \\`deepseek-v4-pro\\`. If you need to confirm, check the session log's model metadata.\n- If routing ever looks wrong, the \\`model-router\\` plugin row in the profile composition is the single place that owns it.`;\n\n/** The plugin row id the bundle patch must insert. */\nconst ROW_ID = \"model-router\";\n\n/** Minimal structural view of the live agent object the router reads. */\ninterface AgentLike {\n ctx: AgentScopedContext;\n options?: { subagentDepth?: number };\n session?: { header?: { origin?: string }; events?: unknown[] };\n}\n\n/** The agent-scoped context's waterfall surface the router uses. */\ninterface AgentScopedContext {\n on(\n event: \"agent/request\",\n listener: (\n payload: Record<string, unknown>,\n next: () => Promise<Record<string, unknown>>,\n ) => Promise<Record<string, unknown>>,\n options?: { prepend?: boolean },\n ): () => void;\n}\n\n/** Host-plane surface the router consumes (events, prompt registry, skills). */\ninterface HarnessContext {\n on(\n event: \"agent/created\",\n listener: (payload: { agent: AgentLike }) => void,\n ): () => void;\n on(event: \"agent/disposed\", listener: (agent: unknown) => void): () => void;\n systemPrompt: {\n section(section: { name: string; order: number; text: string }): unknown;\n };\n skills: {\n register(skill: {\n name: string;\n description: string;\n whenToUse?: string;\n content: string;\n source: string;\n }): unknown;\n };\n}\n\n/** Fold plan-mode state for an agent without trusting the agent's exact shape. */\nfunction isPlanModeActive(agent: AgentLike): boolean {\n const events = agent.session?.events;\n if (!Array.isArray(events)) return false;\n try {\n return foldPlanMode(events as Parameters<typeof foldPlanMode>[0]);\n } catch {\n return false;\n }\n}\n\n/**\n * Cordis service: per-agent request routing plus the convention surface.\n */\nclass ModelRouter extends Service {\n static inject = [\"skills\", \"systemPrompt\"];\n\n config: RouterConfig;\n\n constructor(ctx: Context, rawConfig: unknown = {}) {\n super(ctx, \"modelRouter\");\n this.config = resolveConfig(rawConfig);\n const harness = ctx as unknown as HarnessContext;\n\n // Every agent that gets created — root sessions, delegation children,\n // workflow workers, ralph rounds — passes through here.\n harness.on(\"agent/created\", ({ agent }) => {\n // `prepend` puts this listener OUTERMOST in the `agent/request`\n // waterfall: the harness's model-selection listener runs inside it, so\n // this rewrite is applied LAST and wins over the session's selected\n // model (which dsh-base defaults to deepseek-v4-flash and the user\n // settings or UI can change).\n const dispose = agent.ctx.on(\n \"agent/request\",\n async (payload, next) => {\n const resolved = await next();\n const route = routeFor(agent, this.config, isPlanModeActive(agent));\n if (route === undefined) return resolved;\n const stamped: Record<string, unknown> = {\n ...resolved,\n provider: route.provider,\n model: route.model,\n };\n if (route.reasoningEffort !== undefined) stamped.reasoningEffort = route.reasoningEffort;\n if (route.maxTokens !== undefined) stamped.maxTokens = route.maxTokens;\n return stamped;\n },\n { prepend: true },\n );\n harness.on(\"agent/disposed\", (disposed) => {\n if (disposed === agent) dispose();\n });\n });\n\n if (this.config.promptSection) {\n harness.systemPrompt.section({\n name: ROW_ID,\n order: SECTION_ORDER,\n text: SECTION_TEXT.replaceAll(\"{PLANNER_MODEL}\", this.config.planner.model).replaceAll(\n \"{EXECUTOR_MODEL}\",\n this.config.executor.model,\n ),\n });\n }\n\n if (this.config.skill) {\n harness.skills.register({\n name: SKILL_NAME,\n description: SKILL_DESCRIPTION,\n whenToUse: SKILL_WHEN_TO_USE,\n content: SKILL_CONTENT,\n source: \"runtime\",\n });\n }\n }\n}\n\nexport {\n Config,\n ModelRouter,\n ModelRouter as default,\n name,\n ROW_ID,\n SKILL_CONTENT,\n SKILL_DESCRIPTION,\n SKILL_NAME,\n SKILL_WHEN_TO_USE,\n roleFor,\n routeFor,\n};\nexport type {\n AgentRole,\n ModelRoute,\n ReasoningEffort,\n RoutingMode,\n RouterConfig,\n} from \"./policy.js\";\n","/**\n * Pure routing policy for dsh-model-router: which model each agent role gets.\n * Kept free of Cordis imports so the policy is trivially unit-testable.\n * @module dsh-model-router/policy\n */\n\n/** Reasoning-effort levels a route may pin (mirrors the harness vocabulary). */\nexport type ReasoningEffort = \"off\" | \"low\" | \"high\" | \"max\";\n\n/**\n * How the router treats the root agent.\n * - `strict`: the root agent is always the planner (pro).\n * - `plan`: the root agent is pro only while plan mode is active; otherwise it\n * falls back to the executor route, reserving pro for real planning.\n */\nexport type RoutingMode = \"strict\" | \"plan\";\n\n/** One route: a provider/model pair stamped onto an agent request. */\nexport interface ModelRoute {\n provider: string;\n model: string;\n /**\n * Optional reasoning-effort override. When omitted, the request inherits the\n * session's own selection; when set, the router pins it for that role.\n */\n reasoningEffort?: ReasoningEffort;\n /** Optional output-token cap for the role; omitted means inherit. */\n maxTokens?: number;\n}\n\n/** The two roles the router distinguishes. */\nexport type AgentRole = \"planner\" | \"executor\";\n\n/** Resolved router configuration: one route per role plus routing mode. */\nexport interface RouterConfig {\n planner: ModelRoute;\n executor: ModelRoute;\n mode: RoutingMode;\n promptSection: boolean;\n skill: boolean;\n}\n\n/**\n * Classify an agent as planner or executor.\n *\n * The main (root) agent of a session is the planner. Every agent created as a\n * delegation child — `subagent`, `subagent_fork`, workflow workers, ralph\n * rounds — is an executor. The harness stamps two durable facts on children:\n * `options.subagentDepth` (>= 1) and the session header `origin: \"subagent\"`.\n *\n * @param agent - the live agent (any subset of the runtime shape).\n * @returns the role the agent should be routed as.\n */\nexport function roleFor(agent: unknown): AgentRole {\n const options = (agent as { options?: unknown })?.options;\n const depth = (options as { subagentDepth?: unknown })?.subagentDepth;\n if (typeof depth === \"number\" && depth >= 1) return \"executor\";\n const session = (agent as { session?: unknown })?.session;\n const origin = (session as { header?: unknown })?.header\n ? ((session as { header: { origin?: unknown } }).header.origin)\n : undefined;\n if (origin === \"subagent\") return \"executor\";\n return \"planner\";\n}\n\n/**\n * Resolve the route for one agent.\n * @param agent - the live agent.\n * @param config - the resolved router configuration.\n * @param planModeActive - whether plan mode is currently folded active for the\n * agent's session; consulted only in `plan` routing mode.\n * @returns the model route to stamp, or `undefined` to leave the request alone.\n */\nexport function routeFor(\n agent: unknown,\n config: RouterConfig,\n planModeActive = false,\n): ModelRoute | undefined {\n const role = roleFor(agent);\n if (role === \"executor\") return config.executor;\n // Root agent. In `plan` mode, reserve the planner route for actual planning;\n // otherwise the root falls back to the executor route.\n if (config.mode === \"plan\" && !planModeActive) return config.executor;\n return config.planner;\n}\n"],"mappings":";AAoBA,SAAkB,eAAe;AACjC,OAAO,OAAO;AACd,SAAS,oBAAoB;;;AC+BtB,SAAS,QAAQ,OAA2B;AACjD,QAAM,UAAW,OAAiC;AAClD,QAAM,QAAS,SAAyC;AACxD,MAAI,OAAO,UAAU,YAAY,SAAS,EAAG,QAAO;AACpD,QAAM,UAAW,OAAiC;AAClD,QAAM,SAAU,SAAkC,SAC5C,QAA6C,OAAO,SACtD;AACJ,MAAI,WAAW,WAAY,QAAO;AAClC,SAAO;AACT;AAUO,SAAS,SACd,OACA,QACA,iBAAiB,OACO;AACxB,QAAM,OAAO,QAAQ,KAAK;AAC1B,MAAI,SAAS,WAAY,QAAO,OAAO;AAGvC,MAAI,OAAO,SAAS,UAAU,CAAC,eAAgB,QAAO,OAAO;AAC7D,SAAO,OAAO;AAChB;;;AD1DA,IAAM,OAAO;AAGb,IAAM,mBAAmB,EAAE,OAAO;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC1B,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACvB,iBAAiB,EAAE,MAAM,CAAC,OAAO,OAAO,QAAQ,KAAK,CAAC;AAAA,EACtD,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAC7B,CAAC;AAGD,IAAM,SAAS,EAAE,OAAO;AAAA,EACtB,SAAS,iBAAiB,QAAQ;AAAA,IAChC,UAAU;AAAA,IACV,OAAO;AAAA,EACT,CAAU;AAAA,EACV,UAAU,iBAAiB,QAAQ;AAAA,IACjC,UAAU;AAAA,IACV,OAAO;AAAA,EACT,CAAU;AAAA,EACV,MAAM,EAAE,MAAM,CAAC,UAAU,MAAM,CAAC,EAAE,QAAQ,QAAQ;AAAA,EAClD,eAAe,EAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,EACvC,OAAO,EAAE,QAAQ,EAAE,QAAQ,IAAI;AACjC,CAAC;AAQD,SAAS,cAAc,KAA4B;AACjD,QAAM,SAAS,OAAO,OAAO,CAAC,CAAC;AAC/B,SAAO;AAAA,IACL,SAAS,OAAO;AAAA,IAChB,UAAU,OAAO;AAAA,IACjB,MAAM,OAAO;AAAA,IACb,eAAe,OAAO;AAAA,IACtB,OAAO,OAAO;AAAA,EAChB;AACF;AAMA,IAAM,gBAAgB;AAEtB,IAAM,eAAe;AAErB,IAAM,aAAa;AAEnB,IAAM,oBACJ;AAEF,IAAM,oBAAoB;AAE1B,IAAM,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+BtB,IAAM,SAAS;AA2Cf,SAAS,iBAAiB,OAA2B;AACnD,QAAM,SAAS,MAAM,SAAS;AAC9B,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAG,QAAO;AACnC,MAAI;AACF,WAAO,aAAa,MAA4C;AAAA,EAClE,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,IAAM,cAAN,cAA0B,QAAQ;AAAA,EAChC,OAAO,SAAS,CAAC,UAAU,cAAc;AAAA,EAEzC;AAAA,EAEA,YAAY,KAAc,YAAqB,CAAC,GAAG;AACjD,UAAM,KAAK,aAAa;AACxB,SAAK,SAAS,cAAc,SAAS;AACrC,UAAM,UAAU;AAIhB,YAAQ,GAAG,iBAAiB,CAAC,EAAE,MAAM,MAAM;AAMzC,YAAM,UAAU,MAAM,IAAI;AAAA,QACxB;AAAA,QACA,OAAO,SAAS,SAAS;AACvB,gBAAM,WAAW,MAAM,KAAK;AAC5B,gBAAM,QAAQ,SAAS,OAAO,KAAK,QAAQ,iBAAiB,KAAK,CAAC;AAClE,cAAI,UAAU,OAAW,QAAO;AAChC,gBAAM,UAAmC;AAAA,YACvC,GAAG;AAAA,YACH,UAAU,MAAM;AAAA,YAChB,OAAO,MAAM;AAAA,UACf;AACA,cAAI,MAAM,oBAAoB,OAAW,SAAQ,kBAAkB,MAAM;AACzE,cAAI,MAAM,cAAc,OAAW,SAAQ,YAAY,MAAM;AAC7D,iBAAO;AAAA,QACT;AAAA,QACA,EAAE,SAAS,KAAK;AAAA,MAClB;AACA,cAAQ,GAAG,kBAAkB,CAAC,aAAa;AACzC,YAAI,aAAa,MAAO,SAAQ;AAAA,MAClC,CAAC;AAAA,IACH,CAAC;AAED,QAAI,KAAK,OAAO,eAAe;AAC7B,cAAQ,aAAa,QAAQ;AAAA,QAC3B,MAAM;AAAA,QACN,OAAO;AAAA,QACP,MAAM,aAAa,WAAW,mBAAmB,KAAK,OAAO,QAAQ,KAAK,EAAE;AAAA,UAC1E;AAAA,UACA,KAAK,OAAO,SAAS;AAAA,QACvB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,KAAK,OAAO,OAAO;AACrB,cAAQ,OAAO,SAAS;AAAA,QACtB,MAAM;AAAA,QACN,aAAa;AAAA,QACb,WAAW;AAAA,QACX,SAAS;AAAA,QACT,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
|
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.2.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -34,11 +34,13 @@
|
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
37
|
-
"@deepseek-ai/schemastery": "^3.18.1"
|
|
37
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
38
|
+
"@deepseek-ai/dsh-plan-mode": "^0.1.0-rc.8"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
41
42
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
43
|
+
"@deepseek-ai/dsh-plan-mode": "^0.1.0-rc.8",
|
|
42
44
|
"@types/node": "^22.10.0",
|
|
43
45
|
"tsup": "^8.3.5",
|
|
44
46
|
"typescript": "^5.7.2",
|