@deepseek-ai/dsh-tool-present 0.1.5-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +102 -0
- package/README.zh.md +102 -0
- package/lib/index.js +123 -0
- package/lib/types/index.d.ts +21 -0
- package/lib/types/index.js +102 -0
- package/lib/types/types.d.ts +20 -0
- package/lib/types/types.js +2 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/fs/tool-present/README.md
|
|
5
|
+
README.md: 93003711545f678336a02c9bf8589176f844af65
|
|
6
|
+
README.zh.md: 152ba39be7184ca536a56ed9b4cfb5dfa4a5fe82
|
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Declare accessible files as deliverables with present; configuration, Session ownership, and source-file opening."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-tool-present
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
Use `present` to declare final files accessible through the Session filesystem, including files created through shell commands. Users open the current source files in their default application. The tool records paths and optional descriptions without copying file contents.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
The `standard`, `ptc`, and `cordis` agent presets mount this plugin. Call `present` with `files: [{ path, description? }]` after creating the files. Files must be regular files accessible through the Session filesystem. Relative paths resolve against the Session working directory; absolute paths may name files outside it, including `/tmp` or Downloads. Missing files, directories, final symbolic links, and provider-denied paths fail the call. Files in a shell sandbox’s private `/tmp` must first be written somewhere the Session filesystem can access.
|
|
29
|
+
|
|
30
|
+
Mount it in an agent's Cordis composition with `tools`, `fs`, and the `turnBoundary` Session projection available:
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
- name: '@deepseek-ai/dsh-tool-present'
|
|
34
|
+
config:
|
|
35
|
+
maxFiles: 8
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| Field | Default | Meaning |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `maxFiles` | `8` | Positive maximum file count per call |
|
|
41
|
+
|
|
42
|
+
The file-count limit is validated at mount. The tool requires an agent Session with a workspace and an open turn. Delivery belongs to the calling Session; a parent must call `present` itself to declare files created by a subagent.
|
|
43
|
+
|
|
44
|
+
-----
|
|
45
|
+
|
|
46
|
+
<a id="understand-the-implementation"></a>
|
|
47
|
+
## Understand the implementation
|
|
48
|
+
|
|
49
|
+
<details>
|
|
50
|
+
<summary>Implementation internals — click to expand</summary>
|
|
51
|
+
|
|
52
|
+
The tool resolves paths through the configured filesystem provider and checks regular-file metadata without reading contents. Successful final `tools/result` notifications append `deliverables/presented`, including nested calls. A later enclosing program failure does not revoke an already completed declaration. Blocked results publish none. Each plugin instance records only calls it executed; scoped tools with the same name cannot publish through another instance.
|
|
53
|
+
|
|
54
|
+
The pure `./types` entry declares `PresentedFile` and the Session event without importing Host runtime code. The Web consumer validates persisted declarations before displaying or opening them. The event stores no Session ID, so forked history resolves relative paths against the viewed Session's workspace.
|
|
55
|
+
|
|
56
|
+
**Runtime invariant:** No companion is published. Tool and event registrations are effect-owned, and the Session log owns file declarations; the plugin maintains no independent file-content store.
|
|
57
|
+
|
|
58
|
+
</details>
|
|
59
|
+
|
|
60
|
+
-----
|
|
61
|
+
|
|
62
|
+
<a id="further-exploration"></a>
|
|
63
|
+
## Further Exploration
|
|
64
|
+
|
|
65
|
+
- [Filesystem subsystem](../../../docs/subsystems/filesystem.md) — provider paths and errors.
|
|
66
|
+
- [Web deliverables](../../client/ui-deliverables/README.md) — source-file opening and cards.
|
|
67
|
+
- [Delivery decision](../../../.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.md) — Session ownership and required-on-read events.
|
|
68
|
+
|
|
69
|
+
<a id="model-experience"></a>
|
|
70
|
+
## Model Experience
|
|
71
|
+
|
|
72
|
+
### present
|
|
73
|
+
|
|
74
|
+
#### What the model sees
|
|
75
|
+
|
|
76
|
+
The [present schema](../../../docs/tool-catalog.md#present) asks for existing accessible files: “Declare existing files accessible through the Session filesystem as final deliverables. When a file you create or update is an output the user asked to receive, you must call present after writing it and before your final response, including files created through Bash or code execution. Mentioning its path in your reply does not replace this call. The files must already exist. The user opens the current source files; their contents are not copied or preserved.” Results report `Presented <path>` for each file; the program result and durable event contain paths and optional descriptions.
|
|
77
|
+
|
|
78
|
+
#### Token effect
|
|
79
|
+
|
|
80
|
+
One tool schema per mounted agent and one result line per delivered file. File bytes do not enter model messages.
|
|
81
|
+
|
|
82
|
+
#### KV Cache effect
|
|
83
|
+
|
|
84
|
+
The tool schema is static for the mount lifetime. Delivery result text extends the conversation without rewriting its prompt prefix.
|
|
85
|
+
|
|
86
|
+
## Known Limitations and Deferred Work
|
|
87
|
+
|
|
88
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
89
|
+
|
|
90
|
+
- Metadata and Host-path checks cannot atomically prevent replacement before a desktop application opens a file.
|
|
91
|
+
- Edits change what opens. Deleted or moved source files cannot be opened from their declarations.
|
|
92
|
+
- Session ZIP exports contain declarations, not file contents. Persistent delivery versions and copy-on-write storage are deferred.
|
|
93
|
+
|
|
94
|
+
<a id="dev-note"></a>
|
|
95
|
+
### Dev Note
|
|
96
|
+
|
|
97
|
+
<details>
|
|
98
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
99
|
+
|
|
100
|
+
None.
|
|
101
|
+
|
|
102
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "通过 present 声明交付可访问的文件;配置、Session 归属与源文件打开。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-tool-present
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
使用 `present` 声明交付Session 文件系统可访问的最终文件,包括通过 shell 命令创建的文件。用户使用默认应用打开当前源文件。工具记录路径和可选说明,不复制文件内容。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
`standard`、`ptc` 与 `cordis` Agent preset 挂载本插件。创建文件后,以 `files: [{ path, description? }]` 调用 `present`。文件必须是 Session 文件系统可访问的普通文件。相对路径按 Session 工作目录解析;绝对路径可以指向工作区外的文件,包括 `/tmp` 或 Downloads。文件缺失、为目录、最终路径为符号链接或提供方拒绝访问时,调用失败。Shell 沙箱私有 `/tmp` 中的文件需要先写入 Session 文件系统可访问的位置。
|
|
29
|
+
|
|
30
|
+
在 Agent 的 Cordis 组合中挂载,并提供 `tools`、`fs` 和 `turnBoundary` Session 投影:
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
- name: '@deepseek-ai/dsh-tool-present'
|
|
34
|
+
config:
|
|
35
|
+
maxFiles: 8
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| 字段 | 默认值 | 含义 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `maxFiles` | `8` | 每次调用的最大文件数,为正整数 |
|
|
41
|
+
|
|
42
|
+
挂载时校验文件数量上限。工具要求 Agent Session 具有工作区和已开始的 turn。交付归调用方 Session 所有;父 Session 如需声明交付子 Agent 创建的文件,必须自行调用 `present`。
|
|
43
|
+
|
|
44
|
+
-----
|
|
45
|
+
|
|
46
|
+
<a id="understand-the-implementation"></a>
|
|
47
|
+
## 理解实现
|
|
48
|
+
|
|
49
|
+
<details>
|
|
50
|
+
<summary>实现细节——点击展开</summary>
|
|
51
|
+
|
|
52
|
+
工具通过配置的文件系统提供方解析路径,检查普通文件元数据,不读取内容。成功的最终 `tools/result` 通知追加 `deliverables/presented`,嵌套调用也适用。外层程序随后失败不会撤销已完成的声明。被阻止的结果不发布声明。每个插件实例只记录其实际执行的调用;同名作用域工具不能通过其他实例发布交付。
|
|
53
|
+
|
|
54
|
+
纯 `./types` 入口声明 `PresentedFile` 与 Session 事件,不导入 Host 运行时代码。Web 消费方在展示或打开文件前校验持久声明。事件不保存 Session ID,因此 fork 历史中的相对路径按当前查看的 Session 工作区解析。
|
|
55
|
+
|
|
56
|
+
**运行时不变式:** 不发布伴生入口。工具与事件注册归 effect 所有,Session 日志拥有文件声明;插件不维护独立的文件内容存储。
|
|
57
|
+
|
|
58
|
+
</details>
|
|
59
|
+
|
|
60
|
+
-----
|
|
61
|
+
|
|
62
|
+
<a id="further-exploration"></a>
|
|
63
|
+
## 进一步探索
|
|
64
|
+
|
|
65
|
+
- [文件系统子系统](../../../docs/subsystems/filesystem.zh.md)——提供方路径与错误。
|
|
66
|
+
- [Web 交付](../../client/ui-deliverables/README.zh.md)——源文件打开与卡片。
|
|
67
|
+
- [交付决策](../../../.agents/notes/implemented/feature/2026-09-08-present-workspace-source-files.zh.md)——Session 归属与读取端必须识别的事件。
|
|
68
|
+
|
|
69
|
+
<a id="model-experience"></a>
|
|
70
|
+
## 模型体验
|
|
71
|
+
|
|
72
|
+
### present
|
|
73
|
+
|
|
74
|
+
#### 模型看到的内容
|
|
75
|
+
|
|
76
|
+
[present schema](../../../docs/tool-catalog.zh.md#present)要求已有且可访问的文件:“Declare existing files accessible through the Session filesystem as final deliverables. When a file you create or update is an output the user asked to receive, you must call present after writing it and before your final response, including files created through Bash or code execution. Mentioning its path in your reply does not replace this call. The files must already exist. The user opens the current source files; their contents are not copied or preserved.” 每个文件的结果为 `Presented <path>`;程序结果和持久事件包含路径及可选说明。
|
|
77
|
+
|
|
78
|
+
#### Token 影响
|
|
79
|
+
|
|
80
|
+
每个挂载的 Agent 增加一个工具 schema,每个交付文件增加一行结果。文件字节不进入模型消息。
|
|
81
|
+
|
|
82
|
+
#### KV Cache 影响
|
|
83
|
+
|
|
84
|
+
工具 schema 在挂载期间保持静态。交付结果文本扩展对话,不重写提示词前缀。
|
|
85
|
+
|
|
86
|
+
## 已知限制与延期工作
|
|
87
|
+
|
|
88
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
89
|
+
|
|
90
|
+
- 元数据和 Host 路径检查无法原子性地阻止桌面应用打开文件前发生的路径替换。
|
|
91
|
+
- 编辑会改变打开的内容。源文件删除或移动后,无法通过原声明打开。
|
|
92
|
+
- Session ZIP 导出包含声明,不包含文件内容。交付版本持久化和写时复制存储延期实现。
|
|
93
|
+
|
|
94
|
+
<a id="dev-note"></a>
|
|
95
|
+
### 开发备注
|
|
96
|
+
|
|
97
|
+
<details>
|
|
98
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
99
|
+
|
|
100
|
+
无。
|
|
101
|
+
|
|
102
|
+
</details>
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
import { FsError } from "@deepseek-ai/dsh-fs";
|
|
3
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
4
|
+
//#region lib/types/index.js
|
|
5
|
+
/** Stable Loader identity. */
|
|
6
|
+
const name = "tool-present";
|
|
7
|
+
/** Validated delivery limit. */
|
|
8
|
+
const Config = z.object({ maxFiles: z.number().default(8) });
|
|
9
|
+
/** Services used by the scoped delivery tool. */
|
|
10
|
+
const inject = [
|
|
11
|
+
"tools",
|
|
12
|
+
"fs",
|
|
13
|
+
"sessionProjections"
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Register present with durable file references in its tool result.
|
|
17
|
+
* @param ctx - agent-scoped services.
|
|
18
|
+
* @param config - maximum files per call.
|
|
19
|
+
*/
|
|
20
|
+
function apply(ctx, config) {
|
|
21
|
+
if (!Number.isSafeInteger(config.maxFiles) || config.maxFiles < 1) throw new Error("present requires a positive integer maxFiles");
|
|
22
|
+
const pending = /* @__PURE__ */ new WeakMap();
|
|
23
|
+
ctx.tools.register(defineTool({
|
|
24
|
+
name: "present",
|
|
25
|
+
description: "Declare existing files accessible through the Session filesystem as final deliverables. When a file you create or update is an output the user asked to receive, you must call present after writing it and before your final response, including files created through Bash or code execution. Mentioning its path in your reply does not replace this call. The files must already exist. The user opens the current source files; their contents are not copied or preserved.",
|
|
26
|
+
parameters: { files: {
|
|
27
|
+
type: "array",
|
|
28
|
+
required: true,
|
|
29
|
+
items: {
|
|
30
|
+
type: "object",
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
properties: {
|
|
33
|
+
path: {
|
|
34
|
+
type: "string",
|
|
35
|
+
required: true,
|
|
36
|
+
description: "Path of an existing regular file. Relative paths use the Session working directory."
|
|
37
|
+
},
|
|
38
|
+
description: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Brief description for the user."
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} },
|
|
45
|
+
output: {
|
|
46
|
+
schema: {
|
|
47
|
+
type: "object",
|
|
48
|
+
additionalProperties: false,
|
|
49
|
+
properties: {
|
|
50
|
+
turn: {
|
|
51
|
+
type: "integer",
|
|
52
|
+
required: true
|
|
53
|
+
},
|
|
54
|
+
files: {
|
|
55
|
+
type: "array",
|
|
56
|
+
required: true,
|
|
57
|
+
items: {
|
|
58
|
+
type: "object",
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
properties: {
|
|
61
|
+
path: {
|
|
62
|
+
type: "string",
|
|
63
|
+
required: true
|
|
64
|
+
},
|
|
65
|
+
description: { type: "string" }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
render: (_args, value) => [{
|
|
72
|
+
type: "text",
|
|
73
|
+
text: value.files.map((file) => `Presented ${file.path}`).join("\n")
|
|
74
|
+
}]
|
|
75
|
+
},
|
|
76
|
+
async execute(args, exec) {
|
|
77
|
+
if (exec.agent === void 0) throw new Error("present requires an agent Session");
|
|
78
|
+
const boundary = ctx.sessionProjections.stateOf(exec.agent.session, "turnBoundary");
|
|
79
|
+
if (boundary === void 0 || 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 === void 0) throw new Error("present requires a workspace");
|
|
83
|
+
const options = {
|
|
84
|
+
cwd,
|
|
85
|
+
signal: exec.signal
|
|
86
|
+
};
|
|
87
|
+
const files = [];
|
|
88
|
+
for (const file of args.files) {
|
|
89
|
+
if (file.path.trim().length === 0) throw new Error("present requires a non-empty file path");
|
|
90
|
+
const entry = await ctx.fs.lstat(file.path, { cwd }, exec.signal);
|
|
91
|
+
if (entry !== void 0 && entry.type !== "file") throw new Error(`Cannot present ${file.path}: not a regular file`);
|
|
92
|
+
const target = await ctx.fs.resolve(file.path, options);
|
|
93
|
+
const info = await ctx.fs.stat(target, exec.signal);
|
|
94
|
+
if (info === void 0) throw new FsError(`Cannot present ${file.path}: file not found. Check the path, create the file if needed, and retry.`, "FS_NOT_FOUND");
|
|
95
|
+
if (info.type !== "file") throw new Error(`Cannot present ${file.path}: not a regular file`);
|
|
96
|
+
files.push({ ...file });
|
|
97
|
+
}
|
|
98
|
+
exec.signal.throwIfAborted();
|
|
99
|
+
pending.set(exec, {
|
|
100
|
+
session: exec.agent.session,
|
|
101
|
+
turn: boundary.lastTurn,
|
|
102
|
+
files
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
turn: boundary.lastTurn,
|
|
106
|
+
files
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}));
|
|
110
|
+
ctx.on("tools/result", (exec, result) => {
|
|
111
|
+
const delivery = pending.get(exec);
|
|
112
|
+
pending.delete(exec);
|
|
113
|
+
if (delivery === void 0 || result.isError) return;
|
|
114
|
+
const { session, turn, files } = delivery;
|
|
115
|
+
session.append("deliverables/presented", {
|
|
116
|
+
turn,
|
|
117
|
+
callId: exec.callId,
|
|
118
|
+
files
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
export { Config, apply, inject, name };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Scoped tool that declares filesystem deliveries in their owning Session. */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
import z from '@deepseek-ai/schemastery';
|
|
4
|
+
/** Stable Loader identity. */
|
|
5
|
+
export declare const name = "tool-present";
|
|
6
|
+
/** Per-call delivery limit. */
|
|
7
|
+
export interface Config {
|
|
8
|
+
/** Maximum number of files in one call. */
|
|
9
|
+
maxFiles: number;
|
|
10
|
+
}
|
|
11
|
+
/** Validated delivery limit. */
|
|
12
|
+
export declare const Config: z<Config>;
|
|
13
|
+
/** Services used by the scoped delivery tool. */
|
|
14
|
+
export declare const inject: string[];
|
|
15
|
+
/**
|
|
16
|
+
* Register present with durable file references in its tool result.
|
|
17
|
+
* @param ctx - agent-scoped services.
|
|
18
|
+
* @param config - maximum files per call.
|
|
19
|
+
*/
|
|
20
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import z from '@deepseek-ai/schemastery';
|
|
2
|
+
import { FsError } from '@deepseek-ai/dsh-fs';
|
|
3
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
4
|
+
/** Stable Loader identity. */
|
|
5
|
+
export const name = 'tool-present';
|
|
6
|
+
/** Validated delivery limit. */
|
|
7
|
+
export const Config = z.object({
|
|
8
|
+
maxFiles: z.number().default(8),
|
|
9
|
+
});
|
|
10
|
+
/** Services used by the scoped delivery tool. */
|
|
11
|
+
export const inject = ['tools', 'fs', 'sessionProjections'];
|
|
12
|
+
/**
|
|
13
|
+
* Register present with durable file references in its tool result.
|
|
14
|
+
* @param ctx - agent-scoped services.
|
|
15
|
+
* @param config - maximum files per call.
|
|
16
|
+
*/
|
|
17
|
+
export function apply(ctx, config) {
|
|
18
|
+
if (!Number.isSafeInteger(config.maxFiles) || config.maxFiles < 1) {
|
|
19
|
+
throw new Error('present requires a positive integer maxFiles');
|
|
20
|
+
}
|
|
21
|
+
const pending = new WeakMap();
|
|
22
|
+
ctx.tools.register(defineTool({
|
|
23
|
+
name: 'present',
|
|
24
|
+
description: 'Declare existing files accessible through the Session filesystem as final deliverables. '
|
|
25
|
+
+ 'When a file you create or update is an output the user asked to receive, you must call present after writing it and before your final response, including files created through Bash or code execution. '
|
|
26
|
+
+ 'Mentioning its path in your reply does not replace this call. The files must already exist. '
|
|
27
|
+
+ 'The user opens the current source files; their contents are not copied or preserved.',
|
|
28
|
+
parameters: {
|
|
29
|
+
files: {
|
|
30
|
+
type: 'array', required: true,
|
|
31
|
+
items: {
|
|
32
|
+
type: 'object', additionalProperties: false,
|
|
33
|
+
properties: {
|
|
34
|
+
path: { type: 'string', required: true, description: 'Path of an existing regular file. Relative paths use the Session working directory.' },
|
|
35
|
+
description: { type: 'string', description: 'Brief description for the user.' },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
output: {
|
|
41
|
+
schema: {
|
|
42
|
+
type: 'object', additionalProperties: false,
|
|
43
|
+
properties: {
|
|
44
|
+
turn: { type: 'integer', required: true },
|
|
45
|
+
files: {
|
|
46
|
+
type: 'array', required: true,
|
|
47
|
+
items: {
|
|
48
|
+
type: 'object', additionalProperties: false,
|
|
49
|
+
properties: {
|
|
50
|
+
path: { type: 'string', required: true },
|
|
51
|
+
description: { type: 'string' },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
render: (_args, value) => [{ type: 'text', text: value.files.map(file => `Presented ${file.path}`).join('\n') }],
|
|
58
|
+
},
|
|
59
|
+
async execute(args, exec) {
|
|
60
|
+
if (exec.agent === undefined)
|
|
61
|
+
throw new Error('present requires an agent Session');
|
|
62
|
+
const boundary = ctx.sessionProjections.stateOf(exec.agent.session, 'turnBoundary');
|
|
63
|
+
if (boundary === undefined || boundary.openTurnStartSeq === null)
|
|
64
|
+
throw new Error('present requires an open turn');
|
|
65
|
+
if (args.files.length === 0 || args.files.length > config.maxFiles)
|
|
66
|
+
throw new Error(`present accepts 1 to ${config.maxFiles} files`);
|
|
67
|
+
const cwd = exec.agent.session.header.cwd;
|
|
68
|
+
if (cwd === undefined)
|
|
69
|
+
throw new Error('present requires a workspace');
|
|
70
|
+
const options = { cwd, signal: exec.signal };
|
|
71
|
+
const files = [];
|
|
72
|
+
for (const file of args.files) {
|
|
73
|
+
if (file.path.trim().length === 0)
|
|
74
|
+
throw new Error('present requires a non-empty file path');
|
|
75
|
+
const entry = await ctx.fs.lstat(file.path, { cwd }, exec.signal);
|
|
76
|
+
if (entry !== undefined && entry.type !== 'file')
|
|
77
|
+
throw new Error(`Cannot present ${file.path}: not a regular file`);
|
|
78
|
+
const target = await ctx.fs.resolve(file.path, options);
|
|
79
|
+
const info = await ctx.fs.stat(target, exec.signal);
|
|
80
|
+
if (info === undefined)
|
|
81
|
+
throw new FsError(`Cannot present ${file.path}: file not found. Check the path, create the file if needed, and retry.`, 'FS_NOT_FOUND');
|
|
82
|
+
if (info.type !== 'file')
|
|
83
|
+
throw new Error(`Cannot present ${file.path}: not a regular file`);
|
|
84
|
+
files.push({ ...file });
|
|
85
|
+
}
|
|
86
|
+
exec.signal.throwIfAborted();
|
|
87
|
+
pending.set(exec, { session: exec.agent.session, turn: boundary.lastTurn, files });
|
|
88
|
+
return { turn: boundary.lastTurn, files };
|
|
89
|
+
},
|
|
90
|
+
}));
|
|
91
|
+
ctx.on('tools/result', (exec, result) => {
|
|
92
|
+
const delivery = pending.get(exec);
|
|
93
|
+
pending.delete(exec);
|
|
94
|
+
if (delivery === undefined || result.isError)
|
|
95
|
+
return;
|
|
96
|
+
const { session, turn, files } = delivery;
|
|
97
|
+
session.append('deliverables/presented', {
|
|
98
|
+
turn, callId: exec.callId, files,
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Durable file deliveries produced by the present tool. */
|
|
2
|
+
import type { ToolCallId } from '@deepseek-ai/dsh-llm/brand';
|
|
3
|
+
/** A declared filesystem file whose current contents remain at its source path. */
|
|
4
|
+
export interface PresentedFile {
|
|
5
|
+
/** Original absolute path or path relative to the Session working directory. */
|
|
6
|
+
path: string;
|
|
7
|
+
/** Optional description supplied by the model. */
|
|
8
|
+
description?: string;
|
|
9
|
+
}
|
|
10
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
11
|
+
interface SessionEventMap {
|
|
12
|
+
/** Declared filesystem files from a successful final present result, including nested calls. */
|
|
13
|
+
'deliverables/presented': {
|
|
14
|
+
turn: number;
|
|
15
|
+
callId: ToolCallId;
|
|
16
|
+
files: PresentedFile[];
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=types.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-tool-present",
|
|
3
|
+
"description": "Explicit workspace file delivery declarations for the DeepSeek Harness",
|
|
4
|
+
"version": "0.1.5-alpha.2",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/fs/tool-present"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./types": {
|
|
22
|
+
"types": "./lib/types/types.d.ts",
|
|
23
|
+
"default": "./lib/types/types.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"files": [
|
|
30
|
+
"lib/index.js",
|
|
31
|
+
"lib/types/**/*.js",
|
|
32
|
+
"lib/types/**/*.d.ts"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
39
|
+
"@deepseek-ai/dsh-agent": "^0.1.5-alpha.2",
|
|
40
|
+
"@deepseek-ai/dsh-fs": "^0.1.5-alpha.2",
|
|
41
|
+
"@deepseek-ai/dsh-llm": "^0.1.5-alpha.2",
|
|
42
|
+
"@deepseek-ai/dsh-session": "^0.1.5-alpha.2",
|
|
43
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.5-alpha.2",
|
|
44
|
+
"@deepseek-ai/dsh-tools": "^0.1.5-alpha.2"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
48
|
+
"@deepseek-ai/dsh-agent": "^0.1.5-alpha.2",
|
|
49
|
+
"@deepseek-ai/dsh-fs": "^0.1.5-alpha.2",
|
|
50
|
+
"@deepseek-ai/dsh-llm": "^0.1.5-alpha.2",
|
|
51
|
+
"@deepseek-ai/dsh-session": "^0.1.5-alpha.2",
|
|
52
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.5-alpha.2",
|
|
53
|
+
"@deepseek-ai/dsh-tools": "^0.1.5-alpha.2",
|
|
54
|
+
"@deepseek-ai/dsh-agent-loop": "^0.1.5-alpha.2",
|
|
55
|
+
"@deepseek-ai/dsh-agent-loop-testkit": "^0.1.5-alpha.2",
|
|
56
|
+
"@deepseek-ai/dsh-fs-local": "^0.1.5-alpha.2",
|
|
57
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.5-alpha.2",
|
|
58
|
+
"@deepseek-ai/dsh-scope": "^0.1.5-alpha.2"
|
|
59
|
+
}
|
|
60
|
+
}
|