feat: Code Mode — the registry's mode config, the SDK codegen, and the run_code bridge

The dsh-tools half of the Code Mode RFC (its fourth, final change): the
registry gains its first config — mode: native | code | both — and OWNS how
its tools reach the model. 'code' contributes exactly one wire tool,
run_code, plus a lazy tools:sdk prompt section declaring every other tool
as a generated TypeScript API (jsonSchemaToTs: total over the defineTool
subset, unknown degradation, lexicographic byte-identical rendering);
'both' ships both representations; 'native' is byte-for-byte the old
behavior. Non-native modes fail every assembly loudly without a
typescript-language ctx.codeRuntime.

run_code's dispatch bridge: JSON-normalizes each binding argument before
dispatch (what dispatches is what the tool/code-dispatch event logs — the
append can never fail on payload shape; BigInt/circulars reject that one
call), serializes all program tool calls through a per-run queue (even
Promise.all — no concurrency-safety metadata yet), routes every sub-call
through tools/pre-execute → tools/post-execute (a deny rejects the
program-side promise), drops sub-call additionalContext (no safe outlet
mid-run; pinned), owns a run-scoped abort that follows the outer signal in
and fires on settlement (in-flight sub-dispatch aborted, queued abandoned,
queue drained before returning), and converts a failed run into
CodeRunFailedError → a structured isError carrying kind + captured logs.
tool/code-dispatch joins SessionEventMap by declaration merging (log-only;
deriveMessages ignores it).

The composed surface: the tools config forwards through agent-core and
both app packages; examples/code-agent + demo:code run the worker runtime
under mode code (keyless boot smoke + a with-key e2e proving the collapsed
[run_code] header, the dispatch events, and the file the program wrote);
two new snapshot scenarios (code-mode-turn, both-mode-turn) record the SDK
section, collapsed header, dispatch events, and result card — each its own
header-pinning class (the harness gains per-scenario config overlays and
per-class pins). Catalogs, graphs, cookbook, hooks-bridge notes, and the
RFC (moved to implemented/, restructured to decision-era headings) updated
in the same change.
This commit is contained in:
Tianyi Cui
2026-07-08 12:58:23 +08:00
parent aa2a7f9a8a
commit b59d245c7c
56 files changed
+2395 -102

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
# code-agent — the Code Mode demo
The [Code Mode](../../docs/rfc/implemented/feature/2026-06-15-code-mode.md) form of the coding agent: instead of one native tool call per step, the model is offered exactly ONE wire tool — `run_code` — plus a generated TypeScript SDK section declaring every other registered tool (`bash`, `read`, `write`, `edit`, `todo_write`). The model composes tools by writing a program; the program runs in a fresh worker thread (`@deepseek-ai/dsh-code-runtime-worker`), its tool calls bridge back through the ordinary `tools/pre-execute`/`post-execute` pipeline one at a time, each is logged as a `tool/code-dispatch` session event, and ONLY what the program prints or returns re-enters the model's context.
```sh
pnpm run demo:code # needs DEEPSEEK_API_KEY (repo-root .env works)
```
Try a task that spans several tool calls, e.g.:
> Count the lines of every `*.md` file under docs/ and write the three largest to summary.txt.
and watch the transcript: one `run_code` call, a program looping over tools, and a result the model curated instead of five round-trips of raw tool output.
Two lines of `cordis.yml` make the difference from [examples/coding-agent](../coding-agent/README.md): the `code-runtime` entry (the worker-thread backend registering `ctx.codeRuntime`) and `tools: { mode: code }` on the app (flip it to `both` to offer native calls AND `run_code` side by side; remove both lines and it IS the coding agent).
Tests: `tests/keyless-smoke.e2e.ts` boots the real `cordis.yml` through the Loader with no prompt (the export-shape guard); `tests/code-mode.e2e.ts` is the with-key proof — a real model, a two-tool task, asserting the wire tool list was exactly `[run_code]`, the `tool/code-dispatch` events landed, and the curated answer came back.
+84
View File
@@ -0,0 +1,84 @@
# The code-agent plugin tree: the Code Mode demo. The same spine as
# examples/coding-agent — the DeepSeek adapter, local bash, filesystem and
# todo tool stacks over the stdio chat app — with TWO differences that turn
# it into Cloudflare-style Code Mode:
#
# 1. `code-runtime` loads the worker-thread code-execution backend
# (`ctx.codeRuntime`): one fresh Node worker per run, TypeScript in.
# 2. `stdio-agent` sets `tools: { mode: code }`, so the model is offered
# exactly ONE wire tool — `run_code` — plus a generated TypeScript SDK
# prompt section declaring bash/read/write/edit/todo_write; the model
# composes them by WRITING A PROGRAM, and only what it prints or
# returns re-enters its context.
#
# Requires DEEPSEEK_API_KEY (and optionally DEEPSEEK_BASE_URL) in the
# environment — the dsh-stdio-agent bin loads the gitignored repo-root .env
# first. cordis.yml reads them via the `!!js` tag.
# Hot-module reload for the dev/demo loop (needs `node --expose-internals`).
- id: hmr
name: '@cordisjs/plugin-hmr'
config:
root: ['.']
# The DeepSeek adapter.
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek'
config:
apiKey: !!js process.env.DEEPSEEK_API_KEY
baseURL: !!js process.env.DEEPSEEK_BASE_URL
models:
- deepseek-v4-pro
- deepseek-v4-flash
# Local bash executor for the spine's `bash` tool schemas.
- id: bash
name: '@deepseek-ai/dsh-bash-local'
config:
timeoutMs: 60000
# The code-execution backend: `run_code` programs execute here, in one fresh
# worker thread per run with an empty environment, port-bridged tool
# bindings, and busy-time/wall-clock/heap caps (all overridable here).
- id: code-runtime
name: '@deepseek-ai/dsh-code-runtime-worker'
# The stdio chat app with the registry flipped to Code Mode: the wire tool
# list collapses to [run_code] and the `tools:sdk` prompt section carries the
# generated TypeScript declarations for every other registered tool.
- id: stdio-agent
name: '@deepseek-ai/dsh-stdio-agent'
config:
model: deepseek-v4-flash
tools:
mode: code
# Set RESUME_SESSION_ID to continue a prior persisted session (the ids
# live under ./.sessions); unset starts a fresh session each run.
resumeSessionId: !!js process.env.RESUME_SESSION_ID
persistenceRoot: './.sessions'
welcome: 'code-mode agent ready. Give it a multi-tool task.'
persona: |
You are code-agent, a coding assistant powered by the {{model}} model.
You work by writing TypeScript programs for run_code: batch related
tool work into one program, loop and branch where it helps, and print
or return ONLY the findings that matter.
# The model-facing todo_write tool: whole-list task tracking written to the
# session log (todo/write), rendered as a stdio checklist.
- id: tool-todo
name: '@deepseek-ai/dsh-tool-todo'
# Filesystem capability stack: local provider, read-before-write/edit policy
# gate, then the model-facing read/write/edit tools — all reachable from a
# run_code program as `tools.read(...)` / `tools.write(...)` / `tools.edit(...)`.
- id: fs-local
name: '@deepseek-ai/dsh-fs-local'
config:
cwd: !!js process.cwd()
- id: fs-policy
name: '@deepseek-ai/dsh-fs-policy'
- id: tool-fs
name: '@deepseek-ai/dsh-tool-fs'
+7
View File
@@ -0,0 +1,7 @@
{
"name": "code-agent-example",
"private": true,
"version": "0.0.1",
"type": "module",
"description": "Runnable demo: Code Mode — the model writes TypeScript against the tool registry"
}
+115
View File
@@ -0,0 +1,115 @@
import { mkdtemp, readFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import LlmService from '@deepseek-ai/dsh-llm'
import SessionStore from '@deepseek-ai/dsh-session'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry, { RUN_CODE_NAME } from '@deepseek-ai/dsh-tools'
import AgentRegistry, { AgentId } from '@deepseek-ai/dsh-agent'
import AgentLoop, { type ReactLoopAgent } from '@deepseek-ai/dsh-agent-loop'
import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
import * as ToolBash from '@deepseek-ai/dsh-tool-bash'
import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek'
import { WorkerCodeRuntime } from '@deepseek-ai/dsh-code-runtime-worker'
/**
* The Code Mode with-key proof (the RFC's e2e tier): a REAL model under
* `mode: 'code'`, a task that requires composing two tool calls, verified
* against the WORLD — the persisted request header carried exactly
* `[run_code]` as the wire tool list, each sub-call landed as a
* `tool/code-dispatch` event, the file the program wrote exists on disk, and
* the final answer is the program's curated output. Key-gated (see
* vitest.e2e.config.ts); the keyless Loader-path smoke lives in
* `keyless-smoke.e2e.ts`.
*/
const PERSONA = 'You are code-agent. You work by writing TypeScript programs for run_code: '
+ 'batch related tool work into one program and print or return ONLY the findings that matter.'
let ctx: Context | undefined
let workdir: string | undefined
afterEach(async () => {
// Always dispose, even on failure/retry/timeout: agent-loop teardown stops
// the loop, the executor kills stray processes, and the code runtime's
// dispose awaits worker exits.
await ctx?.fiber.dispose()
ctx = undefined
if (workdir !== undefined) await rm(workdir, { recursive: true, force: true })
workdir = undefined
})
async function codeModeHarness(cwd: string): Promise<Context> {
const harness = new Context()
await harness.plugin(LlmService)
await harness.plugin(SessionStore)
await harness.plugin(SystemPrompt, { persona: PERSONA })
await harness.plugin(ToolRegistry, { mode: 'code' })
await harness.plugin(AgentRegistry)
await harness.plugin(AgentLoop, { agents: [] })
await harness.plugin(LlmDeepSeek, { models: ['deepseek-v4-flash'] })
await harness.plugin(LocalBashExecutor, { cwd, timeoutMs: 30_000 })
await harness.plugin(ToolBash)
await harness.plugin(WorkerCodeRuntime, {})
return harness
}
function waitForIdle(harness: Context, agent: ReactLoopAgent): Promise<void> {
return new Promise((resolve) => {
const dispose = harness.on('agent/status', (subject, status) => {
if (subject === agent && status === 'idle') {
dispose()
resolve()
}
})
})
}
describe.skipIf(!process.env.DEEPSEEK_API_KEY)('Code Mode: real model writes a program over real tools', () => {
it('collapses the wire tool list to [run_code], bridges sub-calls, and returns curated output', async () => {
workdir = await mkdtemp(join(tmpdir(), 'dsh-code-mode-e2e-'))
ctx = await codeModeHarness(workdir)
const agent = ctx.agentLoop.create(AgentId('e2e-code-mode'), { model: 'deepseek-v4-flash' })
agent.send([{
type: 'text',
text: 'Using one run_code program: run `echo alpha-7` with the bash tool, run `echo beta-9` with the bash tool, '
+ 'then write both outputs joined by a plus sign into combined.txt (bash heredoc or redirect), '
+ 'and return only the joined string.',
}])
await waitForIdle(ctx, agent)
const events: SessionEvent[] = [...agent.session.events]
// The wire contract: every request this session made offered EXACTLY ONE
// tool — run_code (the logged header snapshots the assembled list).
const headers = events.filter(event => event.type === 'request/header')
expect(headers.length).toBeGreaterThan(0)
for (const header of headers) {
expect(header.data.header.tools?.map(tool => tool.name)).toEqual([RUN_CODE_NAME])
}
// The model actually went through run_code…
const calls = events.filter(event => event.type === 'tool/call')
expect(calls.length).toBeGreaterThan(0)
expect(calls.every(event => event.data.name === RUN_CODE_NAME)).toBe(true)
// …and the program's tool calls landed as dispatch events under it.
const dispatches = events.filter(event => event.type === 'tool/code-dispatch')
expect(dispatches.length).toBeGreaterThanOrEqual(2)
expect(dispatches.every(event => event.data.name === 'bash')).toBe(true)
const parents = new Set(calls.map(event => event.data.callId))
expect(dispatches.every(event => parents.has(event.data.parentCallId))).toBe(true)
// World verification: the file the program wrote, and the curated answer.
const combined = await readFile(join(workdir, 'combined.txt'), 'utf8')
expect(combined).toContain('alpha-7')
expect(combined).toContain('beta-9')
const finalMessage = events.findLast(event => event.type === 'assistant/message')
const finalText = finalMessage !== undefined
? finalMessage.data.content.filter(block => block.type === 'text').map(block => block.text).join('')
: ''
expect(finalText).toContain('alpha-7')
expect(finalText).toContain('beta-9')
}, 180_000)
})
@@ -0,0 +1,90 @@
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { afterEach, describe, expect, it } from 'vitest'
/**
* Keyless Loader-path smoke for examples/code-agent: boot the REAL example
* through the `@deepseek-ai/dsh-stdio-agent` bin against its `cordis.yml`
* (the cordis Loader, `unwrapExports`, the full plugin tree incl. the
* worker-thread code runtime and the registry in `mode: code`), then close
* stdin with no prompt and assert the ready banner + a clean exit.
*
* No prompt is ever sent, so the model is NEVER called and no `run_code`
* turn happens — a dummy key lets `llm-deepseek`'s key-PRESENT check boot
* the tree. This is the export-shape guard (postmortem 0001) for the Code
* Mode composition; the with-key proof lives in `code-mode.e2e.ts`.
*/
const binScript = fileURLToPath(new URL('../../../packages/ui/stdio-agent/src/bin.ts', import.meta.url))
const configPath = fileURLToPath(new URL('../cordis.yml', import.meta.url))
const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
// Dev/test run UNBUILT: resolve `@deepseek-ai/dsh-*` through the root tsconfig
// `paths` map; tsx searches UP from cwd, and we spawn from a temp dir outside
// the repo, so point it at the repo tsconfig.
const repoTsconfig = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
let child: ChildProcessWithoutNullStreams | undefined
let workdir: string | undefined
afterEach(async () => {
if (child !== undefined && child.exitCode === null) child.kill('SIGKILL')
child = undefined
if (workdir !== undefined) await rm(workdir, { recursive: true, force: true })
workdir = undefined
})
async function bootAndEof(): Promise<{ stdout: string; code: number }> {
workdir = await mkdtemp(join(tmpdir(), 'code-agent-smoke-'))
const cwd = workdir
return new Promise((resolve, reject) => {
const proc = spawn(
process.execPath,
// --expose-internals: cordis.yml loads the HMR plugin (mirrors demo:code).
['--expose-internals', '--import', tsxLoader, binScript, configPath],
{
cwd,
env: {
...process.env,
TSX_TSCONFIG_PATH: repoTsconfig,
// A dummy key so llm-deepseek's apply() (key-PRESENT check only) boots.
// No prompt is sent, so the adapter never streams — no network call.
DEEPSEEK_API_KEY: 'keyless-smoke-no-call',
},
stdio: ['pipe', 'pipe', 'pipe'],
},
)
child = proc
let stdout = ''
let stderr = ''
proc.stdout.setEncoding('utf8')
proc.stdout.on('data', (chunk: string) => { stdout += chunk })
proc.stderr.setEncoding('utf8')
proc.stderr.on('data', (chunk: string) => { stderr += chunk })
const timer = setTimeout(() => {
proc.kill('SIGKILL')
reject(new Error(`code-agent did not exit within 10s. stdout:\n${stdout}\nstderr:\n${stderr}`))
}, 10_000)
proc.on('exit', (code) => {
clearTimeout(timer)
if (code === 0) resolve({ stdout, code })
else reject(new Error(`code-agent exited ${code}. stderr:\n${stderr}`))
})
proc.on('error', (err) => { clearTimeout(timer); reject(err) })
// No prompt — just EOF, so the stdio UI exits without ever running a turn.
proc.stdin.end()
})
}
describe('code-agent keyless smoke (real cordis.yml via the Loader)', () => {
it('boots the Code Mode plugin tree, prints its banner, and exits cleanly on EOF', async () => {
const { stdout, code } = await bootAndEof()
expect(code).toBe(0)
expect(stdout).toContain('code-mode agent ready.')
}, 15_000)
})