feat: add canonical typed tool outputs

This commit is contained in:
Tianyi Cui
2026-07-21 03:08:35 +08:00
parent 8500974fd4
commit 66c36e7325
173 changed files with 3298 additions and 954 deletions
@@ -22,7 +22,7 @@ import type {
} from '@deepseek-ai/dsh-fs'
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
import ToolRegistry, { defineTool } from '@deepseek-ai/dsh-tools'
import ToolRegistry, { defineContentToolFixture } from '@deepseek-ai/dsh-tools'
import type { ToolExecution, ToolExecutionToken } from '@deepseek-ai/dsh-tools'
import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
import {
@@ -800,6 +800,7 @@ describe('workspace context request injection', () => {
agent: stubAgent('/virtual/repo'),
}), {
isError: false,
value: null,
content: [{ type: 'text', text: 'file content' }],
}, async () => ({
kind: 'accept',
@@ -835,7 +836,8 @@ describe('workspace context request injection', () => {
agent,
})
const result = {
isError: false,
isError: false as const,
value: null,
content: [{ type: 'text' as const, text: 'hello' }],
}
@@ -1589,7 +1591,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.plugin(AgentLoop, { agents: [] })
ctx.llm.registerAdapter(['mock'], adapter)
const agent = ctx.agentLoop.create(SessionId('workspace-context-abort'), { provider: 'mock', model: 'mock' }, { cwd: root })
ctx.tools.register(defineTool({
ctx.tools.register(defineContentToolFixture({
name: 'abort_step',
description: 'Abort the current test step.',
parameters: {},
@@ -1660,6 +1662,7 @@ describe('dynamic nested workspace context injection', () => {
const pending = ctx.waterfall('tools/post-execute', exec, {
content: [{ type: 'text', text: 'ok' }],
isError: false,
value: null,
}, () => Promise.resolve({ kind: 'accept' as const }))
await expect(pending).rejects.toBe(reason)
@@ -2380,7 +2383,8 @@ describe('dynamic nested workspace context injection', () => {
const result = {
callId: CallId('provider-probe-result'),
content: [{ type: 'text' as const, text: 'ok' }],
isError: false,
isError: false as const,
value: null,
}
const failedStat = await ctx.waterfall('tools/post-execute', stubToolExecution({
@@ -2429,7 +2433,7 @@ describe('dynamic nested workspace context injection', () => {
}
})
it('preserves nested and downstream post-execute contexts as separate entries', async () => {
it('preserves a downstream canonical value replacement and keeps contexts separate', async () => {
const root = await tempRepo()
const home = await tempRepo()
try {
@@ -2440,7 +2444,12 @@ describe('dynamic nested workspace context injection', () => {
await mountFileToolsAndWorkspaceContext(ctx, { dshHome: home, maxBytes: 65536 })
ctx.on('tools/post-execute', async () => ({
kind: 'accept' as const,
content: [{ type: 'text' as const, text: 'downstream replacement' }],
value: {
path: 'pkg/deep/file.txt',
offset: 1,
lines: [{ number: 1, text: 'downstream replacement' }],
totalLines: 1,
},
additionalContexts: [{
content: [{ type: 'text' as const, text: 'downstream context' }],
source: { kind: 'plugin' as const, plugin: 'downstream' },
@@ -2454,7 +2463,15 @@ describe('dynamic nested workspace context injection', () => {
agent: stubAgent(root),
})
expect(blocksText(result.content)).toBe('downstream replacement')
expect(result.isError).toBe(false)
if (result.isError) throw new Error('expected read replacement success')
expect(result.value).toEqual({
path: 'pkg/deep/file.txt',
offset: 1,
lines: [{ number: 1, text: 'downstream replacement' }],
totalLines: 1,
})
expect(blocksText(result.content)).toContain('downstream replacement')
expect(result.additionalContexts).toHaveLength(2)
expect(workspaceContextOf(result)?.source).toEqual({ kind: 'plugin', plugin: 'workspace-context' })
expect(workspaceContextOf(result)?.meta).toMatchObject({
@@ -2568,7 +2585,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.plugin(ToolRegistry)
await ctx.plugin(LocalFileSystem, { cwd: '/' })
await ctx.plugin(ToolFs)
ctx.tools.register(defineTool({
ctx.tools.register(defineContentToolFixture({
name: 'composite-read',
description: 'read through a nested dispatch',
parameters: {},
@@ -2620,7 +2637,7 @@ describe('dynamic nested workspace context injection', () => {
await ctx.plugin(workspaceContext, { maxBytes: 65536 })
const agent = stubAgent('/')
const parent = Symbol('parent') as ToolExecutionToken
const plainResult = { callId: CallId('plain'), content: [], isError: false }
const plainResult = { callId: CallId('plain'), content: [], isError: false as const, value: null }
ctx.emit('tools/result', stubToolExecution({
callId: CallId('agentless-child'), name: 'read', arguments: {}, parent,
@@ -2658,7 +2675,8 @@ describe('dynamic nested workspace context injection', () => {
const result = {
callId: CallId('manual'),
content: [{ type: 'text' as const, text: 'manual result' }],
isError: false,
isError: false as const,
value: null,
}
const cases = [
{ name: 'read', arguments: { file_path: 'pkg/deep/file.txt' }, agent: undefined },