瀏覽代碼

test(fs): cover read result decline paths

Turtle 1 月之前
父節點
當前提交
bdb3e4da0e
共有 2 個文件被更改,包括 47 次插入2 次删除
  1. 11 1
      packages/fs/tool-fs/src/read.ts
  2. 36 1
      packages/fs/tool-fs/tests/tools.spec.ts

+ 11 - 1
packages/fs/tool-fs/src/read.ts

@@ -6,7 +6,7 @@
 
 import type { Context } from 'cordis'
 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 type {} from '@deepseek-ai/dsh-fs'
 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)
       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
     // 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

+ 36 - 1
packages/fs/tool-fs/tests/tools.spec.ts

@@ -10,7 +10,7 @@ import { tmpdir } from 'node:os'
 import { join, resolve, sep } from 'node:path'
 import { CallId } from '@deepseek-ai/dsh-llm'
 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 type {
   FsDirEntry,
@@ -432,6 +432,11 @@ describe('tool-owned presentation (pure presentCall)', () => {
     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 () => {
     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',
@@ -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 () => {
     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 }],