返回源码地图

packages/deliverables/tool-present/src/index.ts

main snapshot · da00f7f5358f · 正文引用章节 15;完整原文可核对,不声称全文件人工逐行审计

完整原文供逐行核对;页面收录不代表每行都经过人工语义审核。MIT 许可见 许可证。

1/** Scoped tool that declares filesystem deliveries in their owning Session. */
2import type { Context } from '@deepseek-ai/cordis'
3import z from '@deepseek-ai/schemastery'
4import { FsError } from '@deepseek-ai/dsh-fs'
5import { defineTool, type ToolExecution } from '@deepseek-ai/dsh-tools'
6import type {} from '@deepseek-ai/dsh-agent'
7import type {} from '@deepseek-ai/dsh-session-projection'
8import type { Session } from '@deepseek-ai/dsh-session'
9import type { PresentedFile } from './types.ts'
10
11/** Stable Loader identity. */
12export const name = 'tool-present'
13
14/** Per-call delivery limit. */
15export interface Config {
16 /** Maximum number of files in one call. */
17 maxFiles: number
18}
19
20/** Validated delivery limit. */
21export const Config: z<Config> = z.object({
22 maxFiles: z.number().default(8),
23})
24
25/** Services used by the scoped delivery tool. */
26export const inject = ['tools', 'fs', 'sessionProjections']
27
28/**
29 * Register present with durable file references in its tool result.
30 * @param ctx - agent-scoped services.
31 * @param config - maximum files per call.
32 */
33export function apply(ctx: Context, config: Config): void {
34 if (!Number.isSafeInteger(config.maxFiles) || config.maxFiles < 1) {
35 throw new Error('present requires a positive integer maxFiles')
36 }
37 const pending = new WeakMap<ToolExecution, { session: Session; turn: number; files: PresentedFile[] }>()
38 ctx.tools.register(defineTool({
39 name: 'present',
40 description: 'Declare existing files as final deliverables for the user. '
41 + 'Use it when the user needs a separate file, especially Office documents, spreadsheets, and slide decks; '
42 + 'prefer your final response when that suffices. The user opens the current files; their contents are not copied.',
43 parameters: {
44 files: {
45 type: 'array', required: true,
46 // 4 is the recommended per-call count; `maxFiles` is the enforced ceiling above it.
47 description: 'Usually the 1-2 most important deliverables; at most 4 per call.',
48 items: {
49 type: 'object', additionalProperties: false,
50 properties: {
51 path: { type: 'string', required: true, description: 'Path of an existing regular file. Relative paths use the Session working directory.' },
52 description: { type: 'string', description: 'Brief description for the user.' },
53 },
54 },
55 },
56 },
57 output: {
58 schema: {
59 type: 'object', additionalProperties: false,
60 properties: {
61 turn: { type: 'integer', required: true },
62 files: {
63 type: 'array', required: true,
64 items: {
65 type: 'object', additionalProperties: false,
66 properties: {
67 path: { type: 'string', required: true },
68 description: { type: 'string' },
69 },
70 },
71 },
72 },
73 },
74 render: (_args, value) => [{ type: 'text', text: value.files.map(file => `Presented ${file.path}`).join('\n') }],
75 },
76 async execute(args, exec) {
77 if (exec.agent === undefined) throw new Error('present requires an agent Session')
78 const boundary = ctx.sessionProjections.stateOf(exec.agent.session, 'turnBoundary')
79 if (boundary === undefined || boundary.openTurnStartSeq === null) throw new Error('present requires an open turn')
80 if (args.files.length === 0 || args.files.length > config.maxFiles) throw new Error(`present accepts 1 to ${config.maxFiles} files`)
81 const cwd = exec.agent.session.header.cwd
82 if (cwd === undefined) throw new Error('present requires a workspace')
83 const options = { cwd, signal: exec.signal }
84 const files: PresentedFile[] = []
85 for (const file of args.files) {
86 if (file.path.trim().length === 0) throw new Error('present requires a non-empty file path')
87 const entry = await ctx.fs.lstat(file.path, { cwd }, exec.signal)
88 if (entry !== undefined && entry.type !== 'file') throw new Error(`Cannot present ${file.path}: not a regular file`)
89 const target = await ctx.fs.resolve(file.path, options)
90 const info = await ctx.fs.stat(target, exec.signal)
91 if (info === undefined) throw new FsError(`Cannot present ${file.path}: file not found. Check the path, create the file if needed, and retry.`, 'FS_NOT_FOUND')
92 if (info.type !== 'file') throw new Error(`Cannot present ${file.path}: not a regular file`)
93 files.push({ ...file })
94 }
95 exec.signal.throwIfAborted()
96 pending.set(exec, { session: exec.agent.session, turn: boundary.lastTurn, files })
97 return { turn: boundary.lastTurn, files }
98 },
99 }))
100 ctx.on('tools/result', (exec, result) => {
101 const delivery = pending.get(exec)
102 pending.delete(exec)
103 if (delivery === undefined || result.isError) return
104 const { session, turn, files } = delivery
105 session.append('deliverables/presented', {
106 turn, callId: exec.callId, files,
107 })
108 })
109}