@deepseek-ai/dsh-tool-fs 0.1.1-rc.2 → 0.1.2-alpha.3
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/README.i18n.yaml +2 -2
- package/README.md +123 -41
- package/README.zh.md +125 -43
- package/lib/index.js +57 -11
- package/lib/types/read-image.d.ts +10 -1
- package/lib/types/session-cwd.d.ts +1 -2
- package/package.json +31 -30
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/fs/tool-fs/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: f00c96d18619e0fb59609eb66729ebfce445dbc6
|
|
6
|
+
README.zh.md: 5915e5a07de10775469d0ebe301564948b357569
|
package/README.md
CHANGED
|
@@ -1,66 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "The model-facing read, read_image, write, and edit tools for users and maintainers composing or debugging filesystem access for agents."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# @deepseek-ai/dsh-tool-fs
|
|
2
7
|
|
|
3
8
|
English | [中文](README.zh.md)
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
`dsh-tool-fs` provides the model-facing filesystem tools — `read`, `read_image`, `write`, and `edit` — and their executor. With them the model reads files with line numbers, creates or replaces them atomically, and applies targeted literal edits; results are capped and failures carry stable codes with recovery instructions, all backed by a mounted `ctx.fs` backend. The read-before-edit policy lives in a separate plugin (`dsh-fs-observation-policy`), so omitting it yields unconditional, still-atomic mutations. `read_image` appears while a durable attachment store is mounted and refuses execution unless the routed model declares image input. Choose this package when the model should read, create, replace, or edit UTF-8 text files; discovery (`glob`/`grep`) is a sibling package.
|
|
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
|
+
Mount the tools after a `ctx.fs` backend and, for read-before-write/edit behavior, the policy plugin. The model then gets line-numbered reads, atomic writes and edits, and — with an attachment store mounted — image reads; every result is capped, and failures carry stable codes with recovery instructions.
|
|
29
|
+
|
|
30
|
+
### Minimal composition
|
|
31
|
+
|
|
32
|
+
A backend, the policy plugin, then the tools; the attachment store is optional and enables `read_image`.
|
|
6
33
|
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
await ctx.plugin(LocalAttachmentStore, { dshHome }) // optional — enables durable read_image results
|
|
12
|
-
await ctx.plugin(ToolFs) // this package — read/write/edit, plus read_image with attachments
|
|
34
|
+
```yaml
|
|
35
|
+
- name: '@deepseek-ai/dsh-fs-local'
|
|
36
|
+
- name: '@deepseek-ai/dsh-fs-observation-policy'
|
|
37
|
+
- name: '@deepseek-ai/dsh-tool-fs'
|
|
13
38
|
```
|
|
14
39
|
|
|
15
|
-
|
|
40
|
+
The policy plugin is optional: without it the tools run against the bare provider (unconditional write, overwrite, and edit with no observed-state). A deployment that loads these tools is expected to also load it, so the behavior is read-before-write/edit. `read_image` registers only while a durable `ctx.attachments` service is mounted; execution additionally refuses on a route whose exact model does not declare image input, so a text route's durable history stays free of image blocks.
|
|
16
41
|
|
|
17
|
-
|
|
42
|
+
### The tools
|
|
18
43
|
|
|
19
|
-
|
|
44
|
+
| Tool | Arguments | Behavior |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `read` | `file_path`, `offset?`, `limit?` | Line-numbered UTF-8 content with a pagination footer; `offset` is 1-based and `limit` defaults to and caps at the configured `readLimit` |
|
|
47
|
+
| `read_image` | `file_path` | Reads and persists a PNG/JPEG/WebP/GIF source; an extension-less path (normalized attachment object paths included) is identified from its file signature; normalization can downscale it before the next model request, so the model need not create a thumbnail first |
|
|
48
|
+
| `write` | `file_path`, `content` | Creates or fully replaces a file; with the policy plugin, overwriting requires a prior `read` at the unchanged version, creating does not |
|
|
49
|
+
| `edit` | `file_path`, `old_string`, `new_string`, `replace_all?` | Literal replacement requiring a unique match unless `replace_all` is true; with the policy plugin, requires a prior `read` and an unchanged file |
|
|
50
|
+
|
|
51
|
+
Field names are snake_case to match Claude Code and existing harness tool schemas. Successes return compact envelopes — a read window, an image reference, or a `Created file`/`Updated file` confirmation — and `write`/`edit` derive replayable diff-card metadata for UI presentation.
|
|
52
|
+
|
|
53
|
+
### Configuration
|
|
20
54
|
|
|
21
55
|
All keys are optional; the defaults are the shipped read caps.
|
|
22
56
|
|
|
23
57
|
| Key | Default | Meaning |
|
|
24
58
|
|---|---|---|
|
|
25
|
-
| `readLimit` | `2000` | Default and maximum lines returned by one `read` call
|
|
26
|
-
| `readMaxLineLength` | `2000` | Characters kept per line before truncation
|
|
27
|
-
| `readMaxBytes` | `51200` | Byte cap on one `read` call's selected lines; overflow ends the window with a
|
|
28
|
-
| `readStreamMinSize` | `10485760` | Files at or above this size (or
|
|
59
|
+
| `readLimit` | `2000` | Default and maximum lines returned by one `read` call |
|
|
60
|
+
| `readMaxLineLength` | `2000` | Characters kept per line before truncation |
|
|
61
|
+
| `readMaxBytes` | `51200` | Byte cap on one `read` call's selected lines; overflow ends the window with a capped footer |
|
|
62
|
+
| `readStreamMinSize` | `10485760` | Files at or above this size (or of unknown size) stream instead of loading whole into memory |
|
|
29
63
|
|
|
30
|
-
|
|
64
|
+
The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-tool-fs) is the exhaustive source for every accepted field and its JSDoc.
|
|
31
65
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
| `edit` | `file_path`, non-empty `old_string`, `new_string`, `replace_all?` | Literal replacement; unique match required unless `replace_all` is true. With the policy plugin: requires a prior `read` (any window) and the file unchanged since. Without it: unconditional. |
|
|
66
|
+
### Policy and sandbox behavior
|
|
67
|
+
|
|
68
|
+
Path authorization for `read` and `read_image` belongs entirely to `ctx.fs`; media-type declarations and file signatures only decide whether `read_image` accepts the bytes returned by that backend.
|
|
69
|
+
|
|
70
|
+
With the policy plugin mounted, `write` and `edit` obtain their guard from the `fs/*` intent slots, so an unread target or a stale observation fails with `FS_NOT_OBSERVED` or `FS_STALE_VERSION` and a recovery instruction. Under a confining backend (`fs-sandbox`), `write`/`edit` additionally advertise `sandbox_permissions` and `justification`; a denied mutation returns the `[sandbox: file access denied under <mode> mode]` marker with the same-turn escalation hint, and an approved retry may stamp a strictly wider mode for that one call.
|
|
38
71
|
|
|
39
|
-
|
|
72
|
+
### Failures and recovery
|
|
40
73
|
|
|
41
|
-
|
|
74
|
+
Failures are normalized as `Error: <message>` with a structured code preserved for callers. Stable messages include `file_path must be a non-empty string`, `limit must be less than or equal to <max>`, `cannot read "<path>": not found`, `cannot read "<path>": not a regular file`, and the image-route refusal `cannot read "<path>" as an image: model "<model>" does not declare image input; switch to an image-capable model to read images`. Guarded-mutation failures append their remedy: `FS_STALE_VERSION` gets `— re-read the file, then retry`, `FS_NOT_OBSERVED` gets `— read the file, then retry`. After the reread confirms absence, `edit` reports `FS_NOT_FOUND` instead of repeating a stale remedy, while `write` uses guarded creation.
|
|
42
75
|
|
|
43
|
-
|
|
76
|
+
-----
|
|
44
77
|
|
|
45
|
-
|
|
78
|
+
<a id="understand-the-implementation"></a>
|
|
79
|
+
## Understand the implementation
|
|
46
80
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- **write** — `ctx.waterfall('fs/write-intent', target, exec, () => undefined)` for the optional guard, then `ctx.fs.writeText(target, content, intent)`, then `fs/observed`. (0 stat.)
|
|
50
|
-
- **edit** — `ctx.waterfall('fs/edit-intent', target, exec, () => undefined)` for the optional guard, then `ctx.fs.editText(target, edit, intent)`, then `fs/observed`. (0 stat.)
|
|
81
|
+
<details>
|
|
82
|
+
<summary>Implementation internals — click to expand</summary>
|
|
51
83
|
|
|
52
|
-
|
|
84
|
+
This section explains the design decisions behind the tool suite and points at the code that realizes them; the observable behavior is fully covered in [Use this package](#use-this-package).
|
|
53
85
|
|
|
54
|
-
|
|
86
|
+
### Design concept
|
|
55
87
|
|
|
56
|
-
|
|
88
|
+
The tools are the executor; policy is an event gate. The tools inject no policy service and inspect no cache — each mutation asks the single intent slot for its guard through `ctx.waterfall`, and each operation emits `fs/observed` only after it succeeded. Reads do exactly one provider `stat` (type and size routing plus the observed version); mutations do none, because the guard comes from the intent slot and the provider re-checks under its lock.
|
|
57
89
|
|
|
58
|
-
|
|
90
|
+
### Source map
|
|
59
91
|
|
|
60
|
-
|
|
92
|
+
| File | Role |
|
|
93
|
+
|---|---|
|
|
94
|
+
| [`src/index.ts`](src/index.ts) | Plugin entry: `Config`, tool composition, `read_image` attachments gate |
|
|
95
|
+
| [`src/read.ts`](src/read.ts) | `read` executor: one stat, streaming decision, window build, observation |
|
|
96
|
+
| [`src/read-image.ts`](src/read-image.ts) | `read_image` executor: route and media-type gates, bounded bytes, attachment save |
|
|
97
|
+
| [`src/write.ts`](src/write.ts) | `write` executor: intent waterfall, atomic write, observation |
|
|
98
|
+
| [`src/edit.ts`](src/edit.ts) | `edit` executor: intent waterfall, literal edit, observation |
|
|
99
|
+
| [`src/read-render.ts`](src/read-render.ts) | Cordis-free windowing and envelope formatting |
|
|
100
|
+
| [`src/sandbox.ts`](src/sandbox.ts) | Escalation API shared by `write`/`edit`: policy resolution and denial-marker mapping |
|
|
101
|
+
| [`src/error.ts`](src/error.ts) | Model-facing remedy appended to `FS_STALE_VERSION` and `FS_NOT_OBSERVED` |
|
|
61
102
|
|
|
62
|
-
|
|
103
|
+
### Per-tool flow
|
|
63
104
|
|
|
105
|
+
All four tools share one flow shape: resolve the path with the calling session's cwd, run the applicable gate, perform exactly one provider operation, and emit `fs/observed` only after success. `read` and `read_image` pay one `stat` for type and size routing; `write` and `edit` pay none because their guard comes from the intent slot, and provider failures surface as typed `FsError` results. The per-tool executors live in `src/read.ts`, `src/read-image.ts`, `src/write.ts`, and `src/edit.ts`.
|
|
106
|
+
|
|
107
|
+
### Observation and concurrency
|
|
108
|
+
|
|
109
|
+
`fs/observed` fires after the operation succeeded via a plain `ctx.emit`; a listener is contractually a synchronous, side-effect-only recorder, so async or fallible observation does not belong on this event. `read` opts into concurrent scheduling because its only mutation is the synchronous version recorder; recorder races fail closed when a later `write` or `edit` re-checks the version under its target lock, and both mutation tools remain exclusive.
|
|
110
|
+
|
|
111
|
+
</details>
|
|
112
|
+
|
|
113
|
+
-----
|
|
114
|
+
|
|
115
|
+
<a id="further-exploration"></a>
|
|
116
|
+
## Further Exploration
|
|
117
|
+
|
|
118
|
+
Read these pages when the package-level contract is not enough. They move from the tools to the contract, backends, and policy they compose with.
|
|
119
|
+
|
|
120
|
+
- [Filesystem subsystem](../../../docs/subsystems/filesystem.md) — exhaustive provider contract, policy events, and error taxonomy.
|
|
121
|
+
- [dsh-fs](../fs/README.md) — the `ctx.fs` contract these tools consume.
|
|
122
|
+
- [fs-local](../fs-local/README.md) — the host-filesystem backend these tools run against.
|
|
123
|
+
- [fs-sandbox](../fs-sandbox/README.md) — the sandbox-enforcing backend that adds the escalation fields.
|
|
124
|
+
- [fs-observation-policy](../fs-observation-policy/README.md) — the policy plugin that guards mutations through the `fs/*` events.
|
|
125
|
+
- [Generated tool catalog](../../../docs/tool-catalog.md#deepseek-aidsh-tool-fs) — the exhaustive schemas this package registers.
|
|
126
|
+
|
|
127
|
+
-----
|
|
128
|
+
|
|
129
|
+
<a id="model-experience"></a>
|
|
64
130
|
## Model Experience
|
|
65
131
|
|
|
66
132
|
### System prompt
|
|
@@ -155,7 +221,7 @@ Append-only; newly visible content follows the reusable request prefix and does
|
|
|
155
221
|
|
|
156
222
|
#### What the model sees
|
|
157
223
|
|
|
158
|
-
Failures are normalized as `Error: <message>`. This package's stable validation and read messages are `file_path must be a non-empty string`, `limit must be less than or equal to <max>`, `old_string must be a non-empty string`, `old_string and new_string must differ`, `cannot read "<path>": not found`, `cannot read "<path>": not a regular file`, `offset <offset> is out of range for "<path>" (<total> lines)`, `cannot read "<path>": read_image
|
|
224
|
+
Failures are normalized as `Error: <message>`. This package's stable validation and read messages are `file_path must be a non-empty string`, `limit must be less than or equal to <max>`, `old_string must be a non-empty string`, `old_string and new_string must differ`, `cannot read "<path>": not found`, `cannot read "<path>": not a regular file`, `offset <offset> is out of range for "<path>" (<total> lines)`, `cannot read "<path>": the <ext> extension does not declare a supported image format; read_image accepts PNG/JPEG/WebP/GIF files, including extension-less files in those formats`, `cannot read "<path>": the file content is not a supported image format; read_image accepts PNG/JPEG/WebP/GIF`, `cannot read "<path>": the bytes do not decode as a supported PNG/JPEG/WebP/GIF image; the file may be truncated or corrupt`, `cannot read "<path>" as an image: model "<model>" does not declare image input; switch to an image-capable model to read images`, and the mismatch repair `cannot read "<path>": the <ext> extension declares <type>, but the bytes use a different image format; rename the file to match its actual format if it is PNG/JPEG/WebP/GIF, or convert it to one of those formats` (an extension-less mismatch reports `cannot read "<path>": the file signature claims <type>, but the bytes decode as a different image format; the file may be corrupt`). A failed 16-bit conversion reports `cannot read "<path>": the 16-bit PNG could not be converted to the normalized 8-bit sRGB form; convert it to an 8-bit PNG/JPEG/WebP and retry`. Provider and policy templates are quoted in their package READMEs. Guarded-mutation failures additionally carry their recovery instruction in the message, appended by this package's model-facing error wrapper: `FS_STALE_VERSION` gets `— re-read the file, then retry`, and `FS_NOT_OBSERVED` gets `— read the file, then retry`; the structured code is preserved. After that reread confirms absence, `edit` reports `FS_NOT_FOUND` instead of repeating a stale remedy, while `write` uses guarded creation.
|
|
159
225
|
|
|
160
226
|
#### Token effect
|
|
161
227
|
|
|
@@ -167,9 +233,25 @@ Append-only; newly visible content follows the reusable request prefix and does
|
|
|
167
233
|
|
|
168
234
|
## Known Limitations and Deferred Work
|
|
169
235
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
236
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
These limits define when the tool suite is a poor fit or needs special operational care. They are current package constraints, not a general filesystem comparison or a task backlog.
|
|
240
|
+
|
|
241
|
+
- **No model-facing directory listing ships** — `ctx.fs.listDir` serves provider code such as skill discovery, while the sibling `dsh-tool-fs-search` package supplies ripgrep-backed `glob` and `grep` rather than extending the filesystem seam.
|
|
242
|
+
- **`read` handles UTF-8 text files only** — images use the separate `read_image` tool; PDF, audio, and video remain deferred. A directory target is `FS_NOT_REGULAR_FILE`.
|
|
243
|
+
- **Extension-declared media type** — an extension selects the declared type and the attachment store's magic-byte validation stays authoritative; a correctly formatted image under a wrong extension is refused with the rename remedy rather than sniffed. Only a path with no extension is identified from its file signature.
|
|
244
|
+
- **Object paths re-enter source admission** — `read_image` on a normalized attachment object re-admits its bytes as a new source, so a deployment whose `maxImageBytes`/`maxMessageImageBytes` sit below the normalized-image byte budget can refuse an object path that `ctx.attachments.readImage` still serves; shipped defaults keep the normalized budget (4 MiB) far under the source caps (20 MiB).
|
|
173
245
|
- **No inline image preview on the tool-result card** — UI surfaces render the image result generically (the durable reference, not pixels); inline rendering is deferred to the UI packages.
|
|
174
|
-
- **No attachment-region tool** — an agent may crop an image through
|
|
175
|
-
- **No timeout surface** — `read`/`write`/`edit` take no timeout argument and declare no
|
|
246
|
+
- **No attachment-region tool** — an agent may crop an image through another available tool when it has a filesystem path; a pasted or dragged image without a path cannot be re-read at higher resolution.
|
|
247
|
+
- **No timeout surface** — `read`/`write`/`edit` take no timeout argument and declare no timeout budget; cancellation rides `exec.signal` only ([provider rationale](../README.md)).
|
|
248
|
+
|
|
249
|
+
<a id="dev-note"></a>
|
|
250
|
+
### Dev Note
|
|
251
|
+
|
|
252
|
+
<details>
|
|
253
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
254
|
+
|
|
255
|
+
None.
|
|
256
|
+
|
|
257
|
+
</details>
|
package/README.zh.md
CHANGED
|
@@ -1,66 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "面向模型的 read、read_image、write 与 edit 工具:供组合或排查 agent 文件系统访问的用户与维护者使用。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# @deepseek-ai/dsh-tool-fs
|
|
2
7
|
|
|
3
8
|
[English](README.md) | 中文
|
|
4
9
|
|
|
5
|
-
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
`dsh-tool-fs` 提供面向模型的文件系统工具——`read`、`read_image`、`write` 与 `edit`——及其执行器。借助它们,模型可以带行号读取文件、原子地创建或替换文件,并执行有针对性的字面量编辑;结果都有上限,失败携带稳定错误码与恢复指令,所有文件操作都运行在已挂载的 `ctx.fs` 后端之上。编辑前读取策略位于独立插件(`dsh-fs-observation-policy`)中,因此省略它只会得到无条件、依然原子的变更。`read_image` 在持久附件存储已挂载时出现,并且只在路由模型声明图片输入时允许执行。当模型需要读取、创建、替换或编辑 UTF-8 文本文件时选择本包;发现工具(`glob`/`grep`)在同级包中。
|
|
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
|
+
在 `ctx.fs` 后端之后挂载工具,并在需要先读后写/编辑行为时挂载策略插件。模型随后获得带行号的读取、原子的写入与编辑,以及——挂载附件存储时——图像读取;每个结果都有上限,失败携带稳定错误码与恢复指令。
|
|
29
|
+
|
|
30
|
+
### 最小组合
|
|
31
|
+
|
|
32
|
+
一个后端、策略插件,然后是工具;附件存储为可选,用于启用 `read_image`。
|
|
6
33
|
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
await ctx.plugin(LocalAttachmentStore, { dshHome }) // optional — enables durable read_image results
|
|
12
|
-
await ctx.plugin(ToolFs) // this package — read/write/edit, plus read_image with attachments
|
|
34
|
+
```yaml
|
|
35
|
+
- name: '@deepseek-ai/dsh-fs-local'
|
|
36
|
+
- name: '@deepseek-ai/dsh-fs-observation-policy'
|
|
37
|
+
- name: '@deepseek-ai/dsh-tool-fs'
|
|
13
38
|
```
|
|
14
39
|
|
|
15
|
-
|
|
40
|
+
策略插件是可选的:省略时,工具直接使用裸提供方(无条件写入、覆盖与编辑,无已观察状态)。加载这些工具的部署也应加载该插件,从而提供写入/编辑前读取行为。`read_image` 只在持久 `ctx.attachments` 服务已挂载时注册;执行时还拒绝确切模型未声明图像输入的路由,因此文本路由的持久历史不会出现图像块。
|
|
16
41
|
|
|
17
|
-
|
|
42
|
+
### 工具
|
|
18
43
|
|
|
19
|
-
|
|
44
|
+
| 工具 | 参数 | 行为 |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `read` | `file_path`、`offset?`、`limit?` | 带行号的 UTF-8 内容与分页 footer;`offset` 从 1 开始,`limit` 默认为配置的 `readLimit`,上限也为该值 |
|
|
47
|
+
| `read_image` | `file_path` | 读取并持久保存 PNG/JPEG/WebP/GIF 源图;无扩展名路径(包括规范化附件对象路径)按文件签名识别格式;规范化可在下一次模型请求前缩小图片,因此模型无需先创建缩略图 |
|
|
48
|
+
| `write` | `file_path`、`content` | 创建或完整替换文件;有策略插件时,覆盖要求先在未变版本上执行 `read`,创建不需要 |
|
|
49
|
+
| `edit` | `file_path`、`old_string`、`new_string`、`replace_all?` | 字面量替换,除非 `replace_all` 为 true 否则要求唯一匹配;有策略插件时,要求先执行 `read` 且文件未变 |
|
|
50
|
+
|
|
51
|
+
字段名使用 snake_case,与 Claude Code 和现有 harness 工具 schema 一致。成功返回紧凑信封——读取窗口、图像引用或 `Created file`/`Updated file` 确认——`write`/`edit` 还会派生可回放的 diff 卡片元数据供 UI 展示。
|
|
52
|
+
|
|
53
|
+
### 配置
|
|
20
54
|
|
|
21
55
|
所有键均为可选;默认值是随产品交付的读取上限。
|
|
22
56
|
|
|
23
57
|
| 键 | 默认值 | 含义 |
|
|
24
58
|
|---|---|---|
|
|
25
|
-
| `readLimit` | `2000` | 一次 `read`
|
|
26
|
-
| `readMaxLineLength` | `2000` |
|
|
27
|
-
| `readMaxBytes` | `51200` | 一次 `read` 调用所选行的字节上限;溢出时以「已达上限」footer
|
|
28
|
-
| `readStreamMinSize` | `10485760` |
|
|
59
|
+
| `readLimit` | `2000` | 一次 `read` 调用返回的默认和最大行数 |
|
|
60
|
+
| `readMaxLineLength` | `2000` | 每行截断前保留的字符数 |
|
|
61
|
+
| `readMaxBytes` | `51200` | 一次 `read` 调用所选行的字节上限;溢出时以「已达上限」footer 结束窗口 |
|
|
62
|
+
| `readStreamMinSize` | `10485760` | 大于等于该大小或大小未知的文件采用流式读取,而不是整体加载到内存 |
|
|
29
63
|
|
|
30
|
-
|
|
64
|
+
生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-tool-fs)是每个受支持字段及其 JSDoc 的穷尽式真源。
|
|
31
65
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
| `edit` | `file_path`、非空 `old_string`、`new_string`、`replace_all?` | 字面量替换;除非 `replace_all` 为 true,否则要求唯一匹配。有策略插件时:要求先执行 `read`(任何窗口),且文件此后未变。没有插件时:无条件执行。 |
|
|
66
|
+
### 策略与沙箱行为
|
|
67
|
+
|
|
68
|
+
`read` 与 `read_image` 的路径授权完全由 `ctx.fs` 负责;媒体类型声明和文件签名只决定 `read_image` 是否接受该后端返回的字节。
|
|
69
|
+
|
|
70
|
+
挂载策略插件后,`write` 与 `edit` 从 `fs/*` 意图槽位取得防护,因此未读目标或陈旧观察会以 `FS_NOT_OBSERVED` 或 `FS_STALE_VERSION` 及恢复指令失败。使用施加沙箱限制的后端(`fs-sandbox`)时,`write`/`edit` 还会公开 `sandbox_permissions` 与 `justification`;被拒绝的变更返回 `[sandbox: file access denied under <mode> mode]` 标记与同轮次升级提示,获批的重试可以在该次调用中加盖严格更宽的模式。
|
|
38
71
|
|
|
39
|
-
|
|
72
|
+
### 失败与恢复
|
|
40
73
|
|
|
41
|
-
|
|
74
|
+
失败被规范化为 `Error: <message>`,并为调用方保留结构化错误码。稳定消息包括 `file_path must be a non-empty string`、`limit must be less than or equal to <max>`、`cannot read "<path>": not found`、`cannot read "<path>": not a regular file`,以及图像路由拒绝 `cannot read "<path>" as an image: model "<model>" does not declare image input; switch to an image-capable model to read images`。防护变更失败会追加恢复指令:`FS_STALE_VERSION` 追加 `— re-read the file, then retry`,`FS_NOT_OBSERVED` 追加 `— read the file, then retry`。该次重新读取确认缺失后,`edit` 报告 `FS_NOT_FOUND` 而不会重复陈旧恢复指令,`write` 则使用防护创建。
|
|
42
75
|
|
|
43
|
-
|
|
76
|
+
-----
|
|
44
77
|
|
|
45
|
-
|
|
78
|
+
<a id="understand-the-implementation"></a>
|
|
79
|
+
## 理解实现
|
|
46
80
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- **write**:调用 `ctx.waterfall('fs/write-intent', target, exec, () => undefined)` 取得可选防护,然后调用 `ctx.fs.writeText(target, content, intent)`,再发出 `fs/observed`。(0 次 stat。)
|
|
50
|
-
- **edit**:调用 `ctx.waterfall('fs/edit-intent', target, exec, () => undefined)` 取得可选防护,然后调用 `ctx.fs.editText(target, edit, intent)`,再发出 `fs/observed`。(0 次 stat。)
|
|
81
|
+
<details>
|
|
82
|
+
<summary>实现细节——点击展开</summary>
|
|
51
83
|
|
|
52
|
-
|
|
84
|
+
本节解释工具套件背后的设计决策,并指出实现它们的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。
|
|
53
85
|
|
|
54
|
-
|
|
86
|
+
### 设计理念
|
|
55
87
|
|
|
56
|
-
|
|
88
|
+
工具就是执行器;策略是事件门禁。工具不注入策略服务,也不检查任何缓存——每次变更都通过 `ctx.waterfall` 向单一意图槽位请求防护,每个操作只在成功后发出 `fs/observed`。读取恰好执行一次提供方 `stat`(类型与大小路由加观察到的版本);变更一次也不执行,因为防护来自意图槽位,提供方在锁内重新检查。
|
|
57
89
|
|
|
58
|
-
|
|
90
|
+
### 源码地图
|
|
59
91
|
|
|
60
|
-
|
|
92
|
+
| 文件 | 职责 |
|
|
93
|
+
|---|---|
|
|
94
|
+
| [`src/index.ts`](src/index.ts) | 插件入口:`Config`、工具组合、`read_image` 附件门禁 |
|
|
95
|
+
| [`src/read.ts`](src/read.ts) | `read` 执行器:一次 stat、流式决策、窗口构建、观察 |
|
|
96
|
+
| [`src/read-image.ts`](src/read-image.ts) | `read_image` 执行器:路由与媒体类型门禁、有界字节、附件保存 |
|
|
97
|
+
| [`src/write.ts`](src/write.ts) | `write` 执行器:意图 waterfall、原子写入、观察 |
|
|
98
|
+
| [`src/edit.ts`](src/edit.ts) | `edit` 执行器:意图 waterfall、字面量编辑、观察 |
|
|
99
|
+
| [`src/read-render.ts`](src/read-render.ts) | 不依赖 Cordis 的窗口构建与信封格式化 |
|
|
100
|
+
| [`src/sandbox.ts`](src/sandbox.ts) | `write`/`edit` 共享的升权 API:策略解析与拒绝标记映射 |
|
|
101
|
+
| [`src/error.ts`](src/error.ts) | 追加到 `FS_STALE_VERSION` 与 `FS_NOT_OBSERVED` 的面向模型恢复指令 |
|
|
61
102
|
|
|
62
|
-
|
|
103
|
+
### 各工具流程
|
|
63
104
|
|
|
105
|
+
四个工具共享同一种流程形态:用调用会话的 cwd 解析路径、运行适用的门禁、恰好执行一次提供方操作,并且只在成功后发出 `fs/observed`。`read` 与 `read_image` 为类型与大小路由付出一次 `stat`;`write` 与 `edit` 不执行 stat,因为防护来自意图槽位,提供方失败以类型化 `FsError` 结果呈现。各工具执行器位于 `src/read.ts`、`src/read-image.ts`、`src/write.ts` 与 `src/edit.ts`。
|
|
106
|
+
|
|
107
|
+
### 观察与并发
|
|
108
|
+
|
|
109
|
+
`fs/observed` 在操作成功之后通过普通 `ctx.emit` 发出;监听器的约定是同步且只有副作用的记录器,因此异步或可能失败的观察不属于该事件。`read` 允许并发调度,因为它唯一改变状态的操作是同步记录版本;稍后的 `write` 或 `edit` 会在目标锁内重新检查版本,因此记录器竞态会安全地失败,两个变更工具仍保持互斥。
|
|
110
|
+
|
|
111
|
+
</details>
|
|
112
|
+
|
|
113
|
+
-----
|
|
114
|
+
|
|
115
|
+
<a id="further-exploration"></a>
|
|
116
|
+
## 进一步探索
|
|
117
|
+
|
|
118
|
+
当包级约定不够用时阅读以下页面。它们从工具逐步进入它们所组合的约定、后端与策略。
|
|
119
|
+
|
|
120
|
+
- [文件系统子系统](../../../docs/subsystems/filesystem.zh.md)——穷尽式提供方约定、策略事件与错误分类体系。
|
|
121
|
+
- [dsh-fs](../fs/README.zh.md)——这些工具消费的 `ctx.fs` 约定。
|
|
122
|
+
- [fs-local](../fs-local/README.zh.md)——这些工具运行于其上的宿主文件系统后端。
|
|
123
|
+
- [fs-sandbox](../fs-sandbox/README.zh.md)——添加升权字段的沙箱强制后端。
|
|
124
|
+
- [fs-observation-policy](../fs-observation-policy/README.zh.md)——通过 `fs/*` 事件防护变更的策略插件。
|
|
125
|
+
- [生成工具目录](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-fs)——本包注册的穷尽式 schema。
|
|
126
|
+
|
|
127
|
+
-----
|
|
128
|
+
|
|
129
|
+
<a id="model-experience"></a>
|
|
64
130
|
## 模型体验
|
|
65
131
|
|
|
66
132
|
### 系统提示词
|
|
@@ -117,7 +183,7 @@ Use the edit tool for targeted changes to existing UTF-8 text files. It replaces
|
|
|
117
183
|
|
|
118
184
|
#### Token 影响
|
|
119
185
|
|
|
120
|
-
读取输出受 `readLimit`、`readMaxLineLength`
|
|
186
|
+
读取输出受 `readLimit`、`readMaxLineLength` 与 `readMaxBytes` 限制;保留的调用与结果会反复发送,直到上下文压缩(compaction)。
|
|
121
187
|
|
|
122
188
|
#### KV Cache 影响
|
|
123
189
|
|
|
@@ -155,7 +221,7 @@ Use the edit tool for targeted changes to existing UTF-8 text files. It replaces
|
|
|
155
221
|
|
|
156
222
|
#### 模型看到的内容
|
|
157
223
|
|
|
158
|
-
失败会规范化为 `Error: <message>`。本包稳定的校验和读取消息是 `file_path must be a non-empty string`、`limit must be less than or equal to <max>`、`old_string must be a non-empty string`、`old_string and new_string must differ`、`cannot read "<path>": not found`、`cannot read "<path>": not a regular file`、`offset <offset> is out of range for "<path>" (<total> lines)`、`cannot read "<path>": read_image
|
|
224
|
+
失败会规范化为 `Error: <message>`。本包稳定的校验和读取消息是 `file_path must be a non-empty string`、`limit must be less than or equal to <max>`、`old_string must be a non-empty string`、`old_string and new_string must differ`、`cannot read "<path>": not found`、`cannot read "<path>": not a regular file`、`offset <offset> is out of range for "<path>" (<total> lines)`、`cannot read "<path>": the <ext> extension does not declare a supported image format; read_image accepts PNG/JPEG/WebP/GIF files, including extension-less files in those formats`、`cannot read "<path>": the file content is not a supported image format; read_image accepts PNG/JPEG/WebP/GIF`、`cannot read "<path>": the bytes do not decode as a supported PNG/JPEG/WebP/GIF image; the file may be truncated or corrupt`、`cannot read "<path>" as an image: model "<model>" does not declare image input; switch to an image-capable model to read images`,以及类型不匹配的修复消息 `cannot read "<path>": the <ext> extension declares <type>, but the bytes use a different image format; rename the file to match its actual format if it is PNG/JPEG/WebP/GIF, or convert it to one of those formats`(无扩展名路径的不匹配报告 `cannot read "<path>": the file signature claims <type>, but the bytes decode as a different image format; the file may be corrupt`)。16-bit 转换失败会报告 `cannot read "<path>": the 16-bit PNG could not be converted to the normalized 8-bit sRGB form; convert it to an 8-bit PNG/JPEG/WebP and retry`。提供方和策略模板在各自包的 README 中逐字列出。防护变更失败还会在消息中携带恢复指令,由本包面向模型的错误包装追加:`FS_STALE_VERSION` 追加 `— re-read the file, then retry`,`FS_NOT_OBSERVED` 追加 `— read the file, then retry`;结构化错误码保持不变。该次重新读取确认缺失后,`edit` 会报告 `FS_NOT_FOUND`,而不会重复陈旧恢复指令;`write` 则使用带防护的创建。
|
|
159
225
|
|
|
160
226
|
#### Token 影响
|
|
161
227
|
|
|
@@ -165,11 +231,27 @@ Use the edit tool for targeted changes to existing UTF-8 text files. It replaces
|
|
|
165
231
|
|
|
166
232
|
仅追加;新增可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
|
|
167
233
|
|
|
168
|
-
##
|
|
234
|
+
## 已知限制与延期工作
|
|
235
|
+
|
|
236
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
这些限制说明工具套件何时不合适,或何时需要特别的运维注意。它们是当前包约束,不是通用文件系统对比或任务积压。
|
|
169
240
|
|
|
170
|
-
- **未交付面向模型的目录列表工具**:`ctx.fs.listDir` 服务于 skill(技能)发现等提供方代码,同级
|
|
171
|
-
- **`read` 只处理 UTF-8
|
|
172
|
-
-
|
|
241
|
+
- **未交付面向模型的目录列表工具**:`ctx.fs.listDir` 服务于 skill(技能)发现等提供方代码,同级 `dsh-tool-fs-search` 包则提供基于 ripgrep 的 `glob` 与 `grep`,而不是扩展文件系统 seam。
|
|
242
|
+
- **`read` 只处理 UTF-8 文本文件**:图像使用独立的 `read_image` 工具;PDF、音频和视频仍延期处理。目录目标为 `FS_NOT_REGULAR_FILE`。
|
|
243
|
+
- **媒体类型按扩展名声明**:扩展名选择声明类型,附件存储的魔数校验保持权威;扩展名错误但格式正确的图像会得到改名修复提示,而不是被嗅探接受。只有没有扩展名的路径按文件签名识别格式。
|
|
244
|
+
- **对象路径重新走源准入**:对规范化附件对象调用 `read_image` 会把其字节作为新来源重新准入,因此把 `maxImageBytes`/`maxMessageImageBytes` 配置得低于规范化图片字节预算的部署可能拒绝 `ctx.attachments.readImage` 仍可读取的对象路径;默认配置下规范化预算(4 MiB)远低于源上限(20 MiB)。
|
|
173
245
|
- **工具结果卡片没有内嵌图像预览**:UI 表面以通用形式渲染图像结果(持久引用而非像素);内嵌渲染延后到 UI 包处理。
|
|
174
|
-
-
|
|
175
|
-
- **没有超时接口**:`read`/`write`/`edit`
|
|
246
|
+
- **没有附件区域工具**:agent 在拥有文件系统路径时可以通过其他可用工具裁剪图片;没有路径的粘贴或拖入图片无法按更高分辨率重新读取。
|
|
247
|
+
- **没有超时接口**:`read`/`write`/`edit` 不接受超时参数,也不声明超时预算;取消只通过 `exec.signal` 传递(见[提供方理由](../README.zh.md))。
|
|
248
|
+
|
|
249
|
+
<a id="dev-note"></a>
|
|
250
|
+
### 开发备注
|
|
251
|
+
|
|
252
|
+
<details>
|
|
253
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
254
|
+
|
|
255
|
+
无。
|
|
256
|
+
|
|
257
|
+
</details>
|
package/lib/index.js
CHANGED
|
@@ -224,8 +224,7 @@ function readMetaFromMeta(meta) {
|
|
|
224
224
|
/**
|
|
225
225
|
* Derive the working directory a filesystem tool resolves relative paths against: the calling
|
|
226
226
|
* agent's per-session workspace (`exec.agent.session.header.cwd`), so each session's
|
|
227
|
-
* `read`/`write`/`edit` act on
|
|
228
|
-
* `dsh-tool-bash` defaults a bash `workdir` to the session cwd.
|
|
227
|
+
* `read`/`write`/`edit` act on its workspace, not the server's launch directory.
|
|
229
228
|
* Non-agent calls return `undefined`, leaving the fallback in the provider rather than reading
|
|
230
229
|
* `process.cwd()` at the tool boundary.
|
|
231
230
|
* @module @deepseek-ai/dsh-tool-fs/session-cwd
|
|
@@ -326,7 +325,7 @@ function parseReadArgs(args, maxLimit) {
|
|
|
326
325
|
function applyReadTool(ctx, caps) {
|
|
327
326
|
ctx.systemPrompt.section({
|
|
328
327
|
name: "tool:read",
|
|
329
|
-
order:
|
|
328
|
+
order: ctx.systemPrompt.getSectionOrder("TOOL_READ"),
|
|
330
329
|
text: "Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files."
|
|
331
330
|
});
|
|
332
331
|
ctx.tools.register(defineTool({
|
|
@@ -597,7 +596,7 @@ ${outcome.operation === "create" ? "Created" : "Updated"} file
|
|
|
597
596
|
function applyWriteTool(ctx, sandbox) {
|
|
598
597
|
ctx.systemPrompt.section({
|
|
599
598
|
name: "tool:write",
|
|
600
|
-
order:
|
|
599
|
+
order: ctx.systemPrompt.getSectionOrder("TOOL_WRITE"),
|
|
601
600
|
text: "Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-observation-policy requires it) and prefer edit for targeted changes."
|
|
602
601
|
});
|
|
603
602
|
ctx.tools.register(defineTool({
|
|
@@ -742,7 +741,7 @@ function formatEditOutput(displayPath, replaceAll) {
|
|
|
742
741
|
function applyEditTool(ctx, sandbox) {
|
|
743
742
|
ctx.systemPrompt.section({
|
|
744
743
|
name: "tool:edit",
|
|
745
|
-
order:
|
|
744
|
+
order: ctx.systemPrompt.getSectionOrder("TOOL_EDIT"),
|
|
746
745
|
text: "Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-observation-policy requires it), unless you just created or edited it in this session."
|
|
747
746
|
});
|
|
748
747
|
ctx.tools.register(defineTool({
|
|
@@ -851,7 +850,10 @@ function applyEditTool(ctx, sandbox) {
|
|
|
851
850
|
//#endregion
|
|
852
851
|
//#region lib/types/read-image.js
|
|
853
852
|
/**
|
|
854
|
-
* The model-facing `read_image` tool commits a PNG/JPEG/WebP/GIF file.
|
|
853
|
+
* The model-facing `read_image` tool commits a PNG/JPEG/WebP/GIF file. A path
|
|
854
|
+
* without a file extension is identified from its file signature, while the
|
|
855
|
+
* attachment service's full decode stays authoritative. The mounted `ctx.fs`
|
|
856
|
+
* backend owns path resolution and read access; names only declare media type.
|
|
855
857
|
*
|
|
856
858
|
* The route gate is deliberately stricter than the host upload preflight. An
|
|
857
859
|
* image-reading tool is useful only when the exact calling route can inspect
|
|
@@ -867,6 +869,41 @@ const IMAGE_EXTENSIONS = {
|
|
|
867
869
|
".webp": "image/webp",
|
|
868
870
|
".gif": "image/gif"
|
|
869
871
|
};
|
|
872
|
+
const PNG_SIGNATURE = [
|
|
873
|
+
137,
|
|
874
|
+
80,
|
|
875
|
+
78,
|
|
876
|
+
71,
|
|
877
|
+
13,
|
|
878
|
+
10,
|
|
879
|
+
26,
|
|
880
|
+
10
|
|
881
|
+
];
|
|
882
|
+
const JPEG_SIGNATURE = [
|
|
883
|
+
255,
|
|
884
|
+
216,
|
|
885
|
+
255
|
|
886
|
+
];
|
|
887
|
+
function matchesBytes(data, offset, expected) {
|
|
888
|
+
if (data.byteLength < offset + expected.length) return false;
|
|
889
|
+
return expected.every((byte, index) => data[offset + index] === byte);
|
|
890
|
+
}
|
|
891
|
+
function matchesAscii(data, offset, value) {
|
|
892
|
+
if (data.byteLength < offset + value.length) return false;
|
|
893
|
+
for (let index = 0; index < value.length; index += 1) if (data[offset + index] !== value.charCodeAt(index)) return false;
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Identify the media type declared by a supported image file signature.
|
|
898
|
+
* @param data - file bytes read through the current filesystem backend.
|
|
899
|
+
* @returns the detected supported media type, or undefined for other bytes.
|
|
900
|
+
*/
|
|
901
|
+
function sniffImageMediaType(data) {
|
|
902
|
+
if (matchesBytes(data, 0, PNG_SIGNATURE)) return "image/png";
|
|
903
|
+
if (matchesBytes(data, 0, JPEG_SIGNATURE)) return "image/jpeg";
|
|
904
|
+
if (matchesAscii(data, 0, "GIF87a") || matchesAscii(data, 0, "GIF89a")) return "image/gif";
|
|
905
|
+
if (matchesAscii(data, 0, "RIFF") && matchesAscii(data, 8, "WEBP")) return "image/webp";
|
|
906
|
+
}
|
|
870
907
|
const IMAGE_VALUE_SCHEMA = {
|
|
871
908
|
type: "object",
|
|
872
909
|
additionalProperties: false,
|
|
@@ -940,6 +977,10 @@ async function assertImageCapableRoute(ctx, exec, requestedPath) {
|
|
|
940
977
|
const active = await llm.resolveModelInfo(provider, model, exec.signal);
|
|
941
978
|
if (active.inputModalities === void 0 || !active.inputModalities.includes("image")) throw new Error(`cannot read "${requestedPath}" as an image: model "${model}" does not declare image input; switch to an image-capable model to read images`);
|
|
942
979
|
}
|
|
980
|
+
/** Refuse a media type outside the deployment's accepted set, naming the offending path. */
|
|
981
|
+
function assertDeploymentAccepts(attachments, mediaType, displayPath) {
|
|
982
|
+
if (!attachments.imageLimits.mediaTypes.includes(mediaType)) throw new Error(`cannot read "${displayPath}": ${mediaType} images are not accepted by this deployment`);
|
|
983
|
+
}
|
|
943
984
|
/**
|
|
944
985
|
* Re-brand a structured image outcome into the durable attachment reference an
|
|
945
986
|
* `ImageBlock` carries.
|
|
@@ -1005,7 +1046,7 @@ function imageReadContent(value) {
|
|
|
1005
1046
|
function applyReadImageTool(ctx) {
|
|
1006
1047
|
ctx.tools.register(defineTool({
|
|
1007
1048
|
name: "read_image",
|
|
1008
|
-
description: "Read a PNG/JPEG/WebP/GIF file and return the image itself. Harness validates and downscales large supported images before the next model request, so use this tool directly instead of installing image libraries or creating thumbnails merely to inspect an image. Independent files may be read concurrently in small batches. Requires the current model to accept image input.",
|
|
1049
|
+
description: "Read a PNG/JPEG/WebP/GIF file and return the image itself. A path without a file extension is accepted; the format is detected from the file content, so normalized attachment paths can be passed directly without copying or renaming. Harness validates and downscales large supported images before the next model request, so use this tool directly instead of installing image libraries or creating thumbnails merely to inspect an image. Independent files may be read concurrently in small batches. Requires the current model to accept image input.",
|
|
1009
1050
|
parameters: { file_path: {
|
|
1010
1051
|
type: "string",
|
|
1011
1052
|
required: true,
|
|
@@ -1028,15 +1069,19 @@ function applyReadImageTool(ctx) {
|
|
|
1028
1069
|
isConcurrencySafe: () => true,
|
|
1029
1070
|
async execute(args, exec) {
|
|
1030
1071
|
if (args.file_path.trim().length === 0) throw new Error("file_path must be a non-empty string");
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1072
|
+
const extension = extname(args.file_path).toLowerCase();
|
|
1073
|
+
const declared = imageMediaTypeForPath(args.file_path);
|
|
1074
|
+
if (declared === void 0 && extension !== "") throw new Error(`cannot read "${args.file_path}": the ${extension} extension does not declare a supported image format; read_image accepts PNG/JPEG/WebP/GIF files, including extension-less files in those formats`);
|
|
1033
1075
|
const attachments = ctx.get("attachments");
|
|
1034
1076
|
if (attachments === void 0) throw new Error(`cannot read "${args.file_path}" as an image: no attachment service is mounted`);
|
|
1035
|
-
if (
|
|
1077
|
+
if (declared !== void 0) assertDeploymentAccepts(attachments, declared, args.file_path);
|
|
1036
1078
|
await assertImageCapableRoute(ctx, exec, args.file_path);
|
|
1037
1079
|
const { target, info } = await resolveRegularReadTarget(ctx, exec, args.file_path);
|
|
1038
1080
|
const byteCap = Math.min(attachments.imageLimits.maxImageBytes, attachments.imageLimits.maxMessageImageBytes);
|
|
1039
1081
|
const data = await ctx.fs.readBytes(target, exec.signal, byteCap);
|
|
1082
|
+
const mediaType = declared ?? sniffImageMediaType(data);
|
|
1083
|
+
if (mediaType === void 0) throw new Error(`cannot read "${target.displayPath}": the file content is not a supported image format; read_image accepts PNG/JPEG/WebP/GIF`);
|
|
1084
|
+
if (declared === void 0) assertDeploymentAccepts(attachments, mediaType, target.displayPath);
|
|
1040
1085
|
let ref;
|
|
1041
1086
|
try {
|
|
1042
1087
|
ref = await attachments.saveImage({
|
|
@@ -1050,8 +1095,9 @@ function applyReadImageTool(ctx) {
|
|
|
1050
1095
|
if (error.code === "IMAGE_TOO_MANY_PIXELS") throw new Error(`cannot read "${target.displayPath}": the image exceeds the ${attachments.imageLimits.maxImagePixels}-pixel decoded-size limit; downscale the image and read the smaller copy`, { cause: error });
|
|
1051
1096
|
if (error.code === "IMAGE_TOO_LARGE") throw new Error(`cannot read "${target.displayPath}": the image cannot be stored within the deployment's byte limits; downscale the image and read the smaller copy`, { cause: error });
|
|
1052
1097
|
if (error.code === "ATTACHMENT_WRITE_FAILED" && /16-bit PNG/iu.test(error.message)) throw new Error(`cannot read "${target.displayPath}": the 16-bit PNG could not be converted to the normalized 8-bit sRGB form; convert it to an 8-bit PNG/JPEG/WebP and retry`, { cause: error });
|
|
1098
|
+
if (error.code === "INVALID_IMAGE" && declared === void 0) throw new Error(`cannot read "${target.displayPath}": the bytes do not decode as a supported PNG/JPEG/WebP/GIF image; the file may be truncated or corrupt`, { cause: error });
|
|
1053
1099
|
if (error.code !== "IMAGE_TYPE_MISMATCH") throw error;
|
|
1054
|
-
|
|
1100
|
+
if (declared === void 0) throw new Error(`cannot read "${target.displayPath}": the file signature claims ${mediaType}, but the bytes decode as a different image format; the file may be corrupt`, { cause: error });
|
|
1055
1101
|
throw new Error(`cannot read "${target.displayPath}": the ${extension} extension declares ${mediaType}, but the bytes use a different image format; rename the file to match its actual format if it is PNG/JPEG/WebP/GIF, or convert it to one of those formats`, { cause: error });
|
|
1056
1102
|
}
|
|
1057
1103
|
ctx.emit("fs/observed", target, {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The model-facing `read_image` tool commits a PNG/JPEG/WebP/GIF file.
|
|
2
|
+
* The model-facing `read_image` tool commits a PNG/JPEG/WebP/GIF file. A path
|
|
3
|
+
* without a file extension is identified from its file signature, while the
|
|
4
|
+
* attachment service's full decode stays authoritative. The mounted `ctx.fs`
|
|
5
|
+
* backend owns path resolution and read access; names only declare media type.
|
|
3
6
|
*
|
|
4
7
|
* The route gate is deliberately stricter than the host upload preflight. An
|
|
5
8
|
* image-reading tool is useful only when the exact calling route can inspect
|
|
@@ -10,6 +13,12 @@
|
|
|
10
13
|
import type { Context } from '@deepseek-ai/cordis';
|
|
11
14
|
import type { ImageAttachmentRef, ImageMediaType } from '@deepseek-ai/dsh-attachment';
|
|
12
15
|
import type { ToolExecution } from '@deepseek-ai/dsh-tools';
|
|
16
|
+
/**
|
|
17
|
+
* Identify the media type declared by a supported image file signature.
|
|
18
|
+
* @param data - file bytes read through the current filesystem backend.
|
|
19
|
+
* @returns the detected supported media type, or undefined for other bytes.
|
|
20
|
+
*/
|
|
21
|
+
export declare function sniffImageMediaType(data: Uint8Array): ImageMediaType | undefined;
|
|
13
22
|
/** The structured outcome declared by the `read_image` output schema. */
|
|
14
23
|
export interface ImageReadValue {
|
|
15
24
|
path: string;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Derive the working directory a filesystem tool resolves relative paths against: the calling
|
|
3
3
|
* agent's per-session workspace (`exec.agent.session.header.cwd`), so each session's
|
|
4
|
-
* `read`/`write`/`edit` act on
|
|
5
|
-
* `dsh-tool-bash` defaults a bash `workdir` to the session cwd.
|
|
4
|
+
* `read`/`write`/`edit` act on its workspace, not the server's launch directory.
|
|
6
5
|
* Non-agent calls return `undefined`, leaving the fallback in the provider rather than reading
|
|
7
6
|
* `process.cwd()` at the tool boundary.
|
|
8
7
|
* @module @deepseek-ai/dsh-tool-fs/session-cwd
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-tool-fs",
|
|
3
3
|
"description": "Model-facing filesystem tools (read, write, edit) over the DeepSeek Harness filesystem seam (ctx.fs)",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2-alpha.3",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -33,38 +33,39 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"diff": "^9.0.0",
|
|
36
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
36
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@deepseek-ai/dsh-
|
|
40
|
-
"@deepseek-ai/dsh-
|
|
41
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
42
|
-
"@deepseek-ai/dsh-
|
|
43
|
-
"@deepseek-ai/dsh-sandbox
|
|
44
|
-
"@deepseek-ai/dsh-
|
|
45
|
-
"@deepseek-ai/dsh-
|
|
46
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
47
|
-
"@deepseek-ai/dsh-
|
|
48
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
49
|
-
"@deepseek-ai/dsh-
|
|
39
|
+
"@deepseek-ai/dsh-attachment": "^0.1.2-alpha.3",
|
|
40
|
+
"@deepseek-ai/dsh-fs": "^0.1.2-alpha.3",
|
|
41
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.3",
|
|
42
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.3",
|
|
43
|
+
"@deepseek-ai/dsh-sandbox": "^0.1.2-alpha.3",
|
|
44
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.2-alpha.3",
|
|
45
|
+
"@deepseek-ai/dsh-session": "^0.1.2-alpha.3",
|
|
46
|
+
"@deepseek-ai/dsh-tools": "^0.1.2-alpha.3",
|
|
47
|
+
"@deepseek-ai/dsh-user-approval": "^0.1.2-alpha.3",
|
|
48
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
49
|
+
"@deepseek-ai/dsh-sandbox-policy": "^0.1.2-alpha.3"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@deepseek-ai/dsh-agent": "^0.1.
|
|
53
|
-
"@deepseek-ai/dsh-agent-loop": "^0.1.
|
|
54
|
-
"@deepseek-ai/dsh-agent-loop-testkit": "^0.1.
|
|
55
|
-
"@deepseek-ai/dsh-
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
58
|
-
"@deepseek-ai/dsh-fs-observation-policy": "^0.1.
|
|
59
|
-
"@deepseek-ai/dsh-
|
|
60
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
61
|
-
"@deepseek-ai/dsh-
|
|
62
|
-
"@deepseek-ai/dsh-sandbox
|
|
63
|
-
"@deepseek-ai/dsh-
|
|
64
|
-
"@deepseek-ai/dsh-
|
|
65
|
-
"@deepseek-ai/dsh-tools": "^0.1.
|
|
66
|
-
"@deepseek-ai/dsh-user-approval": "^0.1.
|
|
67
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
68
|
-
"@deepseek-ai/dsh-
|
|
52
|
+
"@deepseek-ai/dsh-agent": "^0.1.2-alpha.3",
|
|
53
|
+
"@deepseek-ai/dsh-agent-loop": "^0.1.2-alpha.3",
|
|
54
|
+
"@deepseek-ai/dsh-agent-loop-testkit": "^0.1.2-alpha.3",
|
|
55
|
+
"@deepseek-ai/dsh-attachment": "^0.1.2-alpha.3",
|
|
56
|
+
"@deepseek-ai/dsh-fs": "^0.1.2-alpha.3",
|
|
57
|
+
"@deepseek-ai/dsh-fs-local": "^0.1.2-alpha.3",
|
|
58
|
+
"@deepseek-ai/dsh-fs-observation-policy": "^0.1.2-alpha.3",
|
|
59
|
+
"@deepseek-ai/dsh-invariants": "^0.1.2-alpha.3",
|
|
60
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.3",
|
|
61
|
+
"@deepseek-ai/dsh-llm-deepseek": "^0.1.2-alpha.3",
|
|
62
|
+
"@deepseek-ai/dsh-sandbox": "^0.1.2-alpha.3",
|
|
63
|
+
"@deepseek-ai/dsh-sandbox-policy": "^0.1.2-alpha.3",
|
|
64
|
+
"@deepseek-ai/dsh-session": "^0.1.2-alpha.3",
|
|
65
|
+
"@deepseek-ai/dsh-tools": "^0.1.2-alpha.3",
|
|
66
|
+
"@deepseek-ai/dsh-user-approval": "^0.1.2-alpha.3",
|
|
67
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
68
|
+
"@deepseek-ai/dsh-session-projection": "^0.1.2-alpha.3",
|
|
69
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.2-alpha.3"
|
|
69
70
|
}
|
|
70
71
|
}
|