momo-gen
deepseek-model-router
No description
- Stars
- 1
- Language
- JavaScript
- Created
- Aug 14, 2026
- Updated
- Aug 14, 2026
Introduction
DeepSeek Task Model Router (DSH Dynamic Plugin)
English · 🌏 中文文档
A dynamic Cordis plugin for the DeepSeek Harness (DSH)
that automatically routes each agent turn between DeepSeek models — simple tasks to deepseek-v4-flash,
complex tasks to deepseek-v4-pro — and supports manual model / reasoning-effort selection. It runs on
DSH's "dynamic Cordis plugin" mechanism: install it live, no restart required.
Plugin ID:
modelr-1(session-scoped dynamic plugin; thepluginIdis assigned by the Host).
Features
- Automatic per-task routing: intercepts every model request and picks the model + reasoning effort from the current turn's user input.
- Manual selection: switch via the
deepseek_routetool or the model selector at the bottom-right of the composer. - Visual panel: a full "model router" control panel (auto/manual, model, effort) rendered inside the
cordis_runcard. - Model discovery: discovers available models at startup via
llm.listModels('deepseek-official').
Routing rules
| Task type | Examples | Model | Effort |
|---|---|---|---|
| Simple | greetings, translation, summarization, quick Q&A | deepseek-v4-flash | off |
| Complex (default) | coding, refactoring, analysis, planning | deepseek-v4-pro | high |
| Hard | math proofs, algorithms, crypto/security, performance optimization | deepseek-v4-pro | max |
Tech stack
| Layer | Tech |
|---|---|
| Runtime | DeepSeek Harness (DSH) · Cordis plugin system |
| Host | Node.js — dynamic-plugin sandbox inside the DSH host process |
| Client | React (React.createElement, no JSX / no bundling) |
| Host↔Client | Package-private JSON RPC (harness.handle / host.call) |
| Tool registration | harness.defineTool + harness.registerTool (model-visible dynamic tool) |
| Event interception | agent/pre-step (task classification) · agent/request (routing, waterfall) |
| UI mount | Cordis Slots (conversation.input.model and tool.view.cordis) |
Architecture
user input
│
▼
agent/pre-step (waterfall) ← grab this step's claimed messages, extract text
│ classify(text) → { model, effort }, stored in memory keyed by agentId
▼
agent/request (waterfall) ← await next() to get the default config
│ manual mode: use the user-selected model
│ auto mode: use the classification from the previous step
│ return { ...resolved, provider, model, reasoningEffort }
▼
llm.prepareCall / stream ← the request is actually issued with the replaced provider/model/effort
Key mechanisms
-
agent/requestis the authoritative routing point:dsh-agent-looppasses through theagent/requestwaterfall before issuing a model call; the returned replacement config is adopted and written intorequest/header(seedsh-agent-loop/src/agent.tsanddsh-agent/src/model-selection.ts). -
agent/pre-stepprovides the task text: the payload carries this step's claimedUserMessage[]; the plugin extractstype === 'text'blocks for keyword classification. -
Scope: the plugin is mounted on the Host root context (no scope tag).
agent/requestis dispatched viascopeTarget(agent, agent), and an untagged listener is let through globally (see thescopeTargetfilter indsh-scope/src/index.ts), so it can intercept every agent's requests. -
Fully reversible side effects:
ctx.on,harness.handle,harness.registerTool, andslots.injectare all fiber effects, cleaned up automatically on plugin stop / update / undefine.
Install & usage
This is a DSH in-session dynamic plugin, loaded into the running harness with cordis_define +
cordis_run (no process restart). The Host and Client halves are below and can be submitted directly to
cordis_define.
Usage
- In the input box, say "switch to flash" / "use deepseek-v4-pro with max effort" — the model will
call the
deepseek_routetool. - Or open the model selector at the bottom-right of the composer and choose Auto / Flash / Pro.
- Or use the panel in the
cordis_runcard to toggle auto/manual and pick the effort. - Say "resume auto routing" to return to per-task automatic selection.
The deepseek_route tool
{
"action": "status | auto | manual | list", // required
"model": "deepseek-v4-flash | deepseek-v4-pro | ...", // used in manual mode
"effort": "off | high | max" // used in manual mode
}
Full source
Standalone files: Host half at
host.js, Client half atclient.js. The inline code below matches those files and is convenient for copying straight intocordis_define.
Host half (code.host)
return {
name: 'deepseek-task-router',
apply(ctx) {
const llm = ctx.get('llm')
if (llm === undefined) return
const PROVIDER = 'deepseek-official'
const state = { mode: 'auto', manualModel: null, manualEffort: null }
let models = []
let fastModel = 'deepseek-v4-flash'
let reasonModel = 'deepseek-v4-pro'
const decisions = new Map()
function extractText(messages) {
if (!Array.isArray(messages)) return ''
let out = ''
for (const m of messages) {
const content = m && m.content
if (!Array.isArray(content)) continue
for (const block of content) {
if (block && block.type === 'text' && typeof block.text === 'string') out += block.text + '\n'
}
}
return out
}
function classify(text) {
const t = String(text || '').toLowerCase()
const hard = /(数学|证明|定理|算法|密码|加密|解密|逆向|漏洞|渗透|安全审计|形式化|推理|逻辑|高并发|分布式|系统架构|性能|优化)/
if (hard.test(t)) return { model: reasonModel, effort: 'max' }
const simple = /^(你好|您好|hi\b|hello|hey\b|在吗|谢谢|再见|翻译|translate|总结|概括|summarize|什么是|what\s+is|what's|介绍一下|简述|列出|list|解释一下|读音|发音|中译英|英译中|润色|改写)/
if (simple.test(t.trim())) return { model: fastModel, effort: 'off' }
return { model: reasonModel, effort: 'high' }
}
;(async () => {
try {
const list = await llm.listModels(PROVIDER)
if (Array.isArray(list) && list.length > 0) {
const rows = []
for (const m of list) {
const id = m && m.id
if (typeof id !== 'string' || id.length === 0) continue
rows.push({ id, name: (m && typeof m.name === 'string' && m.name) ? m.name : id })
}
if (rows.length > 0) {
models = rows
const ids = rows.map(r => r.id)
const flash = ids.find(id => /flash/i.test(id))
const reason = ids.find(id => /pro|reasoner|reasoning/i.test(id))
if (flash) fastModel = flash
if (reason) reasonModel = reason
}
}
} catch (e) { console.error('deepseek-router: model discovery failed:', e && e.message) }
})()
ctx.on('agent/pre-step', async (payload, next) => {
const decision = await next()
const id = payload && payload.agent && payload.agent.id
if (id === undefined || id === null) return decision
const text = extractText(payload && payload.messages)
if (text.length > 0) decisions.set(id, classify(text))
return decision
})
ctx.on('agent/request', async (payload, next) => {
const resolved = await next()
if (resolved === null || typeof resolved !== 'object') return resolved
let chosen = null
if (state.mode === 'manual' && state.manualModel) {
chosen = { model: state.manualModel, effort: state.manualEffort }
} else {
const id = payload && payload.agent && payload.agent.id
if (id !== undefined && id !== null) chosen = decisions.get(id) || null
}
if (chosen === null || typeof chosen.model !== 'string' || chosen.model.length === 0) return resolved
const rest = {}
for (const k in resolved) {
if (k !== 'reasoningEffort' && Object.prototype.hasOwnProperty.call(resolved, k)) rest[k] = resolved[k]
}
return {
...rest,
provider: PROVIDER,
model: chosen.model,
...(chosen.effort === undefined || chosen.effort === null ? {} : { reasoningEffort: chosen.effort }),
}
})
function snapshot() {
return { mode: state.mode, manualModel: state.manualModel, manualEffort: state.manualEffort, fastModel, reasonModel, models }
}
harness.handle('router/status', async () => snapshot())
harness.handle('router/set', async (args) => {
const a = (args && typeof args === 'object') ? args : {}
if (a.mode === 'auto') { state.mode = 'auto'; state.manualModel = null; state.manualEffort = null }
else if (a.mode === 'manual') {
state.mode = 'manual'
if (typeof a.model === 'string' && a.model.length > 0) state.manualModel = a.model
if (a.effort === 'off' || a.effort === 'high' || a.effort === 'max') state.manualEffort = a.effort
if (!state.manualModel) state.manualModel = reasonModel
}
return snapshot()
})
const tool = harness.defineTool({
name: 'deepseek_route',
description: '查看或切换 DeepSeek 模型路由。action=status 查看当前状态;action=auto 切回自动路由;action=manual 手动指定模型(配合 model 与 effort);action=list 列出可用模型。effort 可选 off/high/max。',
parameters: {
action: { type: 'string', required: true, enum: ['status', 'auto', 'manual', 'list'], description: '操作类型' },
model: { type: 'string', description: 'manual 模式下要使用的模型 id' },
effort: { type: 'string', enum: ['off', 'high', 'max'], description: 'manual 模式下的推理强度' },
},
output: {
schema: { type: 'json' },
render: (_args, value) => [{ type: 'text', text: typeof value === 'string' ? value : JSON.stringify(value, null, 2) }],
},
execute: async (args) => {
const a = (args && typeof args === 'object') ? args : {}
if (a.action === 'auto') { state.mode = 'auto'; state.manualModel = null; state.manualEffort = null; return { ok: true, message: '已切换为自动路由', ...snapshot() } }
if (a.action === 'manual') {
state.mode = 'manual'
if (typeof a.model === 'string' && a.model.length > 0) state.manualModel = a.model
if (a.effort === 'off' || a.effort === 'high' || a.effort === 'max') state.manualEffort = a.effort
if (!state.manualModel) state.manualModel = reasonModel
return { ok: true, message: '已手动指定模型', ...snapshot() }
}
return { ok: true, message: '当前 DeepSeek 模型路由状态', ...snapshot() }
},
})
harness.registerTool(ctx, tool)
},
}
Client half (code.client)
return {
apply(ctx) {
const slots = ctx.get('slots')
if (slots === undefined) return
const box = { display: 'flex', flexDirection: 'column', gap: '8px', padding: '6px 0', fontSize: '13px', fontFamily: 'system-ui, sans-serif' }
const row = { display: 'flex', gap: '8px', alignItems: 'center', flexWrap: 'wrap' }
const btn = { padding: '4px 12px', borderRadius: '6px', border: '1px solid #666', background: 'transparent', color: 'inherit', cursor: 'pointer' }
const btnOn = { padding: '4px 12px', borderRadius: '6px', border: '1px solid #3b82f6', background: '#3b82f6', color: '#fff', cursor: 'pointer' }
const sel = { padding: '4px 6px', borderRadius: '6px', background: 'transparent', border: '1px solid #666', color: 'inherit' }
function useRouter() {
const [snap, setSnap] = React.useState(null)
React.useEffect(() => {
let alive = true
host.call('router/status').then(s => { if (alive) setSnap(s) }).catch(() => {})
return () => { alive = false }
}, [])
const set = (args) => host.call('router/set', args).then(s => setSnap(s)).catch(() => {})
return [snap, set]
}
// 对话输入框右下角的模型选择器(替换 shipped 选择器为 Auto)
function ComposerSelect(props) {
const [snap, set] = useRouter()
const locked = props && props.locked
if (snap === null) {
return React.createElement('span', { style: { fontSize: '13px', opacity: 0.75 } }, 'Auto')
}
const models = Array.isArray(snap.models) ? snap.models : []
const isAuto = snap.mode !== 'manual'
const value = isAuto ? '__auto__' : snap.manualModel
return React.createElement('select', {
value,
disabled: !!locked,
onChange: e => {
const v = e.target.value
if (v === '__auto__') set({ mode: 'auto' })
else set({ mode: 'manual', model: v, effort: snap.manualEffort || 'high' })
},
style: { ...sel, fontWeight: 600 },
},
React.createElement('option', { value: '__auto__' }, 'Auto'),
...models.map(m => React.createElement('option', { key: m.id, value: m.id }, m.name)),
)
}
slots.inject('conversation.input.model', () => slots.register(
{ name: 'conversation.input.model' },
props => React.createElement(ComposerSelect, props),
))
// cordis_run 卡片内的完整控制面板
function Panel() {
const [snap, set] = useRouter()
if (snap === null) return React.createElement('div', { style: box }, 'DeepSeek 模型路由加载中…')
const models = Array.isArray(snap.models) ? snap.models : []
const isAuto = snap.mode !== 'manual'
const currentModel = snap.mode === 'manual' ? snap.manualModel : snap.reasonModel
return React.createElement('div', { style: box },
React.createElement('div', { style: { fontWeight: 600 } }, 'DeepSeek 模型路由'),
React.createElement('div', { style: row },
React.createElement('button', { onClick: () => set({ mode: 'auto' }), style: isAuto ? btnOn : btn }, '自动'),
React.createElement('button', { onClick: () => set({ mode: 'manual', model: currentModel, effort: snap.manualEffort || 'high' }), style: isAuto ? btn : btnOn }, '手动'),
),
React.createElement('div', { style: row },
React.createElement('select', {
value: currentModel, disabled: isAuto,
onChange: e => set({ mode: 'manual', model: e.target.value, effort: snap.manualEffort || 'high' }),
style: sel,
}, models.map(m => React.createElement('option', { key: m.id, value: m.id }, m.name + ' (' + m.id + ')'))),
React.createElement('select', {
value: snap.manualEffort || 'high', disabled: isAuto,
onChange: e => set({ mode: 'manual', model: currentModel, effort: e.target.value }),
style: sel,
},
React.createElement('option', { value: 'off' }, 'off'),
React.createElement('option', { value: 'high' }, 'high'),
React.createElement('option', { value: 'max' }, 'max'),
),
),
React.createElement('div', { style: { opacity: 0.75 } },
'当前:' + (snap.mode === 'manual'
? snap.manualModel + ' · ' + (snap.manualEffort || 'high')
: '自动(简单 → flash,复杂 → pro)'),
),
)
}
slots.inject('tool.view.cordis', () => slots.register(
{ name: 'tool.view.cordis', key: 'self' },
() => React.createElement(Panel, null),
))
},
}
Repository layout
├── host.js # Host half source (code.host)
├── client.js # Client half source (code.client)
├── README.md # architecture, install, full source (English)
├── README.zh.md # 中文文档
├── SECURITY.md # threat model and disclosure
├── CONTRIBUTING.md # contribution guide
├── CHANGELOG.md # release history
└── LICENSE # MIT