test(tools): cover executor cancellation and model fixtures

This commit is contained in:
_Kerman committed 2026-10-19 15:20:48 +08:00
1 parent f0987e7f31
commit 7ebe78d231
5 files changed
+70 -1

No files matched your search

+5 -1
View File
@@ -257,7 +257,11 @@ class BatchSettlement {
message: createToolResultMessage({ callId: block.id, content: result.content, isError: result.isError }),
...result.error?.info ? { error: result.error.info } : {},
...result.meta !== undefined ? { meta: result.meta } : {},
}, { surfaceOp: 'append', ...callSeq === undefined ? {} : { sourceEventSeqs: [callSeq] } })
}, {
surfaceOp: 'append',
// oxlint-disable-next-line typescript/no-non-null-assertion -- start records a call seq whenever this batch logs results
sourceEventSeqs: [callSeq!],
})
}
this.results.push({ callId: block.id, result })
this.contexts.push(...result.additionalContexts ?? [])
+20
View File
@@ -1,4 +1,5 @@
import { executeToolFixture } from '@deepseek-ai/dsh-tool-testkit'
import { ToolExecutionPipeline } from '@deepseek-ai/dsh-tools/pipeline'
import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native'
import { describe, expect, expectTypeOf, it } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
@@ -1761,6 +1762,25 @@ describe('ToolRuntime', () => {
expect(observed).toBe(1)
})
it('reports cancellation before an executor refusal without entering policy', async ({ onTestFinished }) => {
const ctx = await setup()
onTestFinished(() => ctx.fiber.dispose())
let policyCalls = 0
ctx.on('tools/pre-execute', async (_exec, next) => { policyCalls += 1; return next() })
const pipeline = new ToolExecutionPipeline(ctx, {
get: () => echoTool,
reject: () => new Error('executor refuses direct calls'),
})
const prepared = await pipeline.prepare({
callId: ToolCallId('cancelled-refusal'), name: 'echo', arguments: {}, signal: AbortSignal.abort(),
})
expect(prepared.kind).toBe('final-result')
if (prepared.kind === 'dispatch') throw new Error('cancelled call was dispatched')
const result = await pipeline.complete(prepared.exec, prepared)
expect(result.error?.info?.code).toBe(TOOL_ABORTED_BEFORE_DISPATCH)
expect(policyCalls).toBe(0)
})
it('a pre-execute deny short-circuits before tools/execute (the seam never runs)', async () => {
const ctx = await setup()
ctx.tools.register(echoTool)
@@ -34,6 +34,7 @@
"devDependencies": {
"@deepseek-ai/cordis": "workspace:~",
"@deepseek-ai/dsh-llm": "workspace:*",
"@deepseek-ai/dsh-system-prompt": "workspace:*",
"@deepseek-ai/dsh-tools": "workspace:*"
}
}
@@ -0,0 +1,41 @@
/** Model-call fixtures preserve raw JSON input and reject incomplete execution. */
import { Context } from '@deepseek-ai/cordis'
import { ToolCallId } from '@deepseek-ai/dsh-llm'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import Tools, { type ToolExecutor } from '@deepseek-ai/dsh-tools'
import { expect, it, onTestFinished } from 'vitest'
import { executeModelToolFixture } from '../src/index.ts'
async function setup(execute: ToolExecutor['execute']): Promise<Context> {
const ctx = new Context()
onTestFinished(() => ctx.fiber.dispose())
await ctx.plugin(SystemPrompt)
await ctx.plugin(Tools)
ctx.tools.setExecutor({ describe: () => ({ schemas: [], knownNames: [] }), execute })
return ctx
}
const call = { callId: ToolCallId('fixture'), name: 'echo', signal: new AbortController().signal }
it('serializes model arguments and returns the selected executor result without an Agent', async () => {
const result = { isError: false as const, value: 'done', content: [{ type: 'text' as const, text: 'done' }] }
const ctx = await setup(async (input) => {
expect(input.calls).toEqual([{ type: 'tool-call', id: call.callId, name: 'echo', arguments: '{"text":"hello"}' }])
expect(input.signal).toBe(call.signal)
expect(input.agent).toBeUndefined()
return { results: [{ callId: call.callId, result }], additionalContexts: [], concluded: false }
})
expect(await executeModelToolFixture(ctx, { ...call, arguments: { text: 'hello' } })).toBe(result)
})
it('rejects unencodable model arguments before invoking the executor', async () => {
const ctx = await setup(async () => { throw new Error('executor must not run') })
await expect(executeModelToolFixture(ctx, { ...call, arguments: undefined }))
.rejects.toThrow('Model tool fixture arguments must encode as JSON')
})
it('reports when an executor omits the requested result', async () => {
const ctx = await setup(async () => ({ results: [], additionalContexts: [], concluded: false }))
await expect(executeModelToolFixture(ctx, { ...call, arguments: {} }))
.rejects.toThrow('Executor omitted the model tool fixture result')
})
+3
View File
@@ -15481,6 +15481,9 @@ importers:
'@deepseek-ai/dsh-llm':
specifier: workspace:*
version: link:../../llm/llm
'@deepseek-ai/dsh-system-prompt':
specifier: workspace:*
version: link:../../core/system-prompt
'@deepseek-ai/dsh-tools':
specifier: workspace:*
version: link:../../core/tools