Files
deepseek-harness/packages/core/agent-tool-mode/tests/agent-tool-mode.spec.ts
T
Yichen Jiang 9eaa9d22a5 feat(tools): let one agent choose its tool presentation, and ship code
Code Mode was a deployment-wide field on the host `tools` row: a
deployment ran every session that way or none. The obvious product
shape — 代码模式 beside 标准/极简/创造 in the preset picker — had
nothing to hang on.

The registry itself cannot move into a preset; the agent loop's
scheduler, the api-proxy's presenters, and every tool plugin are its
consumers. So split the registry from its projection: `presentAs(mode)`
writes one cell on the calling agent's scope layer, exactly as
`restrict()` does, and the three reads that decided presentation take
that scope's mode instead of the service's. The config `mode` becomes
the default agents shadow rather than a process-wide fact.

Two consequences are load-bearing. `run_code` now enters a view only
for scopes whose own mode presents it — a native agent must not find it
dispatchable because another agent in the process does — and the
reserved name holds whatever the configured mode, since any agent may
select a code mode later.

`dsh-agent-tool-mode` is the row a preset carries to declare this. A
code mode waits for the host's `codeRuntime` rather than assuming it,
so a runtime-less deployment fails the preset at mount, naming the
row, instead of at the session's first request.

The shipped `code` preset is `standard` plus that row, ordered second.
2026-08-07 00:44:49 +08:00

130 lines
5.3 KiB
TypeScript

/**
* The row an agent preset carries to pick its tool presentation. What it owes
* its caller: the choice reaches THIS agent and no other, it unwinds with the
* agent, and a code mode composed against a deployment with no code runtime
* stops at mount — where a preset's activation audit can name it — rather
* than at the first prompt assembly.
*/
import { describe, expect, it } from 'vitest'
import { Context } from 'cordis'
import { createScope } from '@deepseek-ai/dsh-scope'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import { CodeRuntime } from '@deepseek-ai/dsh-code-runtime'
import type { CodeRunRequest, CodeRunResult } from '@deepseek-ai/dsh-code-runtime'
import ToolRegistry, { RUN_CODE_NAME, defineTool } from '@deepseek-ai/dsh-tools'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { SessionId } from '@deepseek-ai/dsh-session'
import { apply, Config, inject, name } from '@deepseek-ai/dsh-agent-tool-mode'
/** A runtime that never runs anything: presentation never dispatches. */
class StubRuntime extends CodeRuntime {
readonly language = 'typescript'
readonly isolation = 'stub'
run(_request: CodeRunRequest): Promise<CodeRunResult> {
return Promise.resolve({ logs: [] })
}
}
/** A host plane with one tool, optionally carrying a code runtime. */
async function host(options: { runtime?: boolean } = {}) {
const ctx = new Context()
await ctx.plugin(SystemPrompt, {})
await ctx.plugin(ToolRegistry, {})
if (options.runtime !== false) await ctx.plugin(StubRuntime)
ctx.tools.register(defineTool({
name: 'echo',
description: 'Echo tool.',
parameters: { value: { type: 'string', required: true } },
output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: value }] },
execute: args => Promise.resolve(args.value),
}))
return ctx
}
/** Mount the row under one agent's scope, as a preset subtree does. */
async function mount(ctx: Context, config: Config, id = 'agent') {
const agent = { id: SessionId(id) } as Agent
let inner!: Context
const fiber = ctx.plugin(Object.assign((host: Context) => {
inner = createScope(host, agent).ctx
}, { inject: ['tools', 'systemPrompt'] }))
await fiber.await()
const row = inner.plugin({ name, inject: [...inject], Config, apply }, config)
await row.await()
return { agent, fiber, row }
}
describe('the tool-mode row', () => {
it('declares the services it uses without holding a code runtime hostage', () => {
// A `native` row must mount where no runtime is composed, so the wait is
// conditional inside apply rather than static metadata.
expect(inject).toEqual(['tools'])
})
it('gives its own agent Code Mode and leaves the rest native', async () => {
const ctx = await host()
const coded = await mount(ctx, { mode: 'code' }, 'coded')
const plain = await mount(ctx, { mode: 'native' }, 'plain')
const codedAssembly = await ctx.systemPrompt.assemble({ scope: coded.agent })
const plainAssembly = await ctx.systemPrompt.assemble({ scope: plain.agent })
expect(codedAssembly.tools.map(tool => tool.name)).toEqual([RUN_CODE_NAME])
expect(codedAssembly.sections.find(section => section.name === 'tools:sdk')?.text).toContain('echo')
expect(plainAssembly.tools.map(tool => tool.name)).toEqual(['echo'])
})
it('presents both forms when asked for both', async () => {
const ctx = await host()
const { agent } = await mount(ctx, { mode: 'both' })
const assembly = await ctx.systemPrompt.assemble({ scope: agent })
expect(assembly.tools.map(tool => tool.name)).toEqual(['echo', RUN_CODE_NAME])
})
it('restores the deployment default when the agent unloads', async () => {
const ctx = await host()
const { agent, row } = await mount(ctx, { mode: 'code' })
await row.dispose()
// HMR safety: the preset subtree is torn down with its agent, and the
// presentation must go with it rather than outliving the composition.
const assembly = await ctx.systemPrompt.assemble({ scope: agent })
expect(assembly.tools.map(tool => tool.name)).toEqual(['echo'])
expect(assembly.sections.some(section => section.name === 'tools:sdk')).toBe(false)
})
it('waits for a code runtime the deployment does not compose', async () => {
const ctx = await host({ runtime: false })
const { agent, row } = await mount(ctx, { mode: 'code' })
// Pending, not applied: `dsh-agent-presets` rejects a mount holding a row
// that never reached a usable state, naming this id — so the preset fails
// where the operator can act, instead of at the first request.
expect(row.ctx.get('codeRuntime')).toBeUndefined()
const assembly = await ctx.systemPrompt.assemble({ scope: agent })
expect(assembly.tools.map(tool => tool.name)).toEqual(['echo'])
})
it('applies once the runtime arrives', async () => {
const ctx = await host({ runtime: false })
const { agent } = await mount(ctx, { mode: 'code' })
await ctx.plugin(StubRuntime)
const assembly = await ctx.systemPrompt.assemble({ scope: agent })
expect(assembly.tools.map(tool => tool.name)).toEqual([RUN_CODE_NAME])
})
it('requires a mode rather than defaulting one', () => {
// An omitted value would mean the row was composed for nothing: a preset
// without this row already gets the deployment default.
expect(() => Config({} as never)).toThrow()
})
})