test(tools): cover executor cancellation and model fixtures
This commit is contained in:
1 parent
f0987e7f31
commit
7ebe78d231
5 files changed
+70
-1
No files matched your search
@@ -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 ?? [])
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
Generated
+3
@@ -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
|
||||
|
||||
Reference in new issue
Block a user