Back to home

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)

license DSH

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; the pluginId is 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_route tool 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_run card.
  • Model discovery: discovers available models at startup via llm.listModels('deepseek-official').

Routing rules

Task typeExamplesModelEffort
Simplegreetings, translation, summarization, quick Q&Adeepseek-v4-flashoff
Complex (default)coding, refactoring, analysis, planningdeepseek-v4-prohigh
Hardmath proofs, algorithms, crypto/security, performance optimizationdeepseek-v4-promax

Tech stack

LayerTech
RuntimeDeepSeek Harness (DSH) · Cordis plugin system
HostNode.js — dynamic-plugin sandbox inside the DSH host process
ClientReact (React.createElement, no JSX / no bundling)
Host↔ClientPackage-private JSON RPC (harness.handle / host.call)
Tool registrationharness.defineTool + harness.registerTool (model-visible dynamic tool)
Event interceptionagent/pre-step (task classification) · agent/request (routing, waterfall)
UI mountCordis 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

  1. agent/request is the authoritative routing point: dsh-agent-loop passes through the agent/request waterfall before issuing a model call; the returned replacement config is adopted and written into request/header (see dsh-agent-loop/src/agent.ts and dsh-agent/src/model-selection.ts).

  2. agent/pre-step provides the task text: the payload carries this step's claimed UserMessage[]; the plugin extracts type === 'text' blocks for keyword classification.

  3. Scope: the plugin is mounted on the Host root context (no scope tag). agent/request is dispatched via scopeTarget(agent, agent), and an untagged listener is let through globally (see the scopeTarget filter in dsh-scope/src/index.ts), so it can intercept every agent's requests.

  4. Fully reversible side effects: ctx.on, harness.handle, harness.registerTool, and slots.inject are 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_route tool.
  • Or open the model selector at the bottom-right of the composer and choose Auto / Flash / Pro.
  • Or use the panel in the cordis_run card 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 at client.js. The inline code below matches those files and is convenient for copying straight into cordis_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

License

MIT