test(fs): cover read result decline paths
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import type { Context } from 'cordis'
|
import type { Context } from 'cordis'
|
||||||
import { defineTool } from '@deepseek-ai/dsh-tools'
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
||||||
import type { GenericCallView } from '@deepseek-ai/dsh-tools'
|
import type { GenericCallView, GenericResultView, ToolResult } from '@deepseek-ai/dsh-tools'
|
||||||
import { FsError } from '@deepseek-ai/dsh-fs'
|
import { FsError } from '@deepseek-ai/dsh-fs'
|
||||||
import type {} from '@deepseek-ai/dsh-fs'
|
import type {} from '@deepseek-ai/dsh-fs'
|
||||||
import type {} from '@deepseek-ai/dsh-system-prompt'
|
import type {} from '@deepseek-ai/dsh-system-prompt'
|
||||||
@@ -154,6 +154,16 @@ export function applyReadTool(ctx: Context, caps: ReadToolCaps): void {
|
|||||||
ctx.emit('fs/observed', target, info.version, exec)
|
ctx.emit('fs/observed', target, info.version, exec)
|
||||||
return outcome
|
return outcome
|
||||||
},
|
},
|
||||||
|
presentResult(_args, result: ToolResult): GenericResultView | undefined {
|
||||||
|
if (result.isError) return undefined
|
||||||
|
const only = result.content.length === 1 ? result.content[0] : undefined
|
||||||
|
const text = only?.type === 'text' ? only.text : undefined
|
||||||
|
if (text === undefined) return undefined
|
||||||
|
// Group 1 always captures (possibly empty) when the envelope matches.
|
||||||
|
const body = /^<path>[^\n]*<\/path>\n<type>file<\/type>\n<content>\n([\s\S]*)\n<\/content>$/u.exec(text)?.[1]
|
||||||
|
if (body === undefined) return undefined
|
||||||
|
return { card: 'generic', content: [{ type: 'text', text: body }] }
|
||||||
|
},
|
||||||
// Pure display: a generic card titled by the file with the read window appended (`Read
|
// Pure display: a generic card titled by the file with the read window appended (`Read
|
||||||
// foo.txt (5 - 8)`), `read` kind (icon), and a follow-along location whose line is the
|
// foo.txt (5 - 8)`), `read` kind (icon), and a follow-along location whose line is the
|
||||||
// read's offset (defaulting to 1). The window reflects raw args, so an omitted limit keeps
|
// read's offset (defaulting to 1). The window reflects raw args, so an omitted limit keeps
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { tmpdir } from 'node:os'
|
|||||||
import { join, resolve, sep } from 'node:path'
|
import { join, resolve, sep } from 'node:path'
|
||||||
import { CallId } from '@deepseek-ai/dsh-llm'
|
import { CallId } from '@deepseek-ai/dsh-llm'
|
||||||
import SystemPrompt, { renderPrompt } from '@deepseek-ai/dsh-system-prompt'
|
import SystemPrompt, { renderPrompt } from '@deepseek-ai/dsh-system-prompt'
|
||||||
import ToolRegistry from '@deepseek-ai/dsh-tools'
|
import ToolRegistry, { type ToolResult } from '@deepseek-ai/dsh-tools'
|
||||||
import { FileSystem, FsError, FsTargetKey, FsVersion } from '@deepseek-ai/dsh-fs'
|
import { FileSystem, FsError, FsTargetKey, FsVersion } from '@deepseek-ai/dsh-fs'
|
||||||
import type {
|
import type {
|
||||||
FsDirEntry,
|
FsDirEntry,
|
||||||
@@ -432,6 +432,11 @@ describe('tool-owned presentation (pure presentCall)', () => {
|
|||||||
return ctx.tools.get(name)?.presentCall?.(args)
|
return ctx.tools.get(name)?.presentCall?.(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const presentResult = async (name: string, args: unknown, result: ToolResult) => {
|
||||||
|
const { ctx } = await setup()
|
||||||
|
return ctx.tools.get(name)?.presentResult?.(args, result)
|
||||||
|
}
|
||||||
|
|
||||||
it('read: generic card titled by file with the read window, read kind, location with the offset line', async () => {
|
it('read: generic card titled by file with the read window, read kind, location with the offset line', async () => {
|
||||||
expect(await presentCall('read', { file_path: 'src/a.ts', offset: 12, limit: 40 })).toEqual({
|
expect(await presentCall('read', { file_path: 'src/a.ts', offset: 12, limit: 40 })).toEqual({
|
||||||
card: 'generic', title: 'Read src/a.ts (12 - 51)', kind: 'read',
|
card: 'generic', title: 'Read src/a.ts (12 - 51)', kind: 'read',
|
||||||
@@ -445,6 +450,36 @@ describe('tool-owned presentation (pure presentCall)', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('read: completed presentation removes the model-facing XML envelope', async () => {
|
||||||
|
expect(await presentResult('read', { file_path: 'a.txt' }, {
|
||||||
|
content: [{ type: 'text', text: '<path>/tmp/a.txt</path>\n<type>file</type>\n<content>\n1: hello\n\n(End of file - total 1 lines)\n</content>' }],
|
||||||
|
isError: false,
|
||||||
|
})).toEqual({
|
||||||
|
card: 'generic',
|
||||||
|
content: [{ type: 'text', text: '1: hello\n\n(End of file - total 1 lines)' }],
|
||||||
|
})
|
||||||
|
expect(await presentResult('read', { file_path: 'a.txt' }, {
|
||||||
|
content: [{ type: 'text', text: 'malformed replay' }],
|
||||||
|
isError: false,
|
||||||
|
})).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('read: completed presentation declines errors and non-single-text content', async () => {
|
||||||
|
const envelope = '<path>/tmp/a.txt</path>\n<type>file</type>\n<content>\nbody\n</content>'
|
||||||
|
expect(await presentResult('read', { file_path: 'a.txt' }, {
|
||||||
|
content: [{ type: 'text', text: envelope }],
|
||||||
|
isError: true,
|
||||||
|
})).toBeUndefined()
|
||||||
|
expect(await presentResult('read', { file_path: 'a.txt' }, {
|
||||||
|
content: [{ type: 'text', text: envelope }, { type: 'text', text: 'second' }],
|
||||||
|
isError: false,
|
||||||
|
})).toBeUndefined()
|
||||||
|
expect(await presentResult('read', { file_path: 'a.txt' }, {
|
||||||
|
content: [{ type: 'reasoning', text: envelope }],
|
||||||
|
isError: false,
|
||||||
|
})).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
it('read: "from line N" window when only offset is set', async () => {
|
it('read: "from line N" window when only offset is set', async () => {
|
||||||
expect(await presentCall('read', { file_path: 'a.txt', offset: 5 })).toEqual({
|
expect(await presentCall('read', { file_path: 'a.txt', offset: 5 })).toEqual({
|
||||||
card: 'generic', title: 'Read a.txt (from line 5)', kind: 'read', locations: [{ path: 'a.txt', line: 5 }],
|
card: 'generic', title: 'Read a.txt (from line 5)', kind: 'read', locations: [{ path: 'a.txt', line: 5 }],
|
||||||
|
|||||||
Reference in New Issue
Block a user