nanoPyCodeAgent 0.6.0__tar.gz → 0.7.0__tar.gz
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.
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/.agents/skills/land-pr/SKILL.md +27 -16
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/AGENTS.md +13 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/PKG-INFO +1 -1
- nanopycodeagent-0.7.0/docs/changelogs/0.7.x.md +32 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/dev_notes/en/0.5.x.md +2 -2
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/dev_notes/en/0.6.x.md +1 -1
- nanopycodeagent-0.7.0/docs/dev_notes/en/0.7.x.md +230 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/dev_notes/zh-CN/0.5.x.md +2 -2
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/dev_notes/zh-CN/0.6.x.md +1 -1
- nanopycodeagent-0.7.0/docs/dev_notes/zh-CN/0.7.x.md +230 -0
- nanopycodeagent-0.7.0/docs/research/README.md +29 -0
- nanopycodeagent-0.7.0/docs/research/en/agent_tools.md +154 -0
- nanopycodeagent-0.7.0/docs/research/en/edit_tool.md +412 -0
- nanopycodeagent-0.7.0/docs/research/en/read_tool.md +337 -0
- nanopycodeagent-0.7.0/docs/research/en/write_tool.md +606 -0
- {nanopycodeagent-0.6.0/docs/research → nanopycodeagent-0.7.0/docs/research/zh-CN}/agent_tools.md +1 -1
- nanopycodeagent-0.7.0/docs/research/zh-CN/edit_tool.md +412 -0
- {nanopycodeagent-0.6.0/docs/research → nanopycodeagent-0.7.0/docs/research/zh-CN}/read_tool.md +1 -1
- {nanopycodeagent-0.6.0/docs/research → nanopycodeagent-0.7.0/docs/research/zh-CN}/write_tool.md +1 -1
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/src/nanopycodeagent/agent.py +21 -8
- nanopycodeagent-0.7.0/src/nanopycodeagent/edit_tool.py +260 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/tests/helpers.py +7 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/tests/test_agent.py +87 -0
- nanopycodeagent-0.7.0/tests/test_edit_tool.py +382 -0
- nanopycodeagent-0.6.0/docs/research/README.md +0 -14
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/.agents/skills/release/SKILL.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/.agents/skills/release/scripts/verify-release.sh +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/.github/workflows/ci.yml +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/.github/workflows/release.yml +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/.gitignore +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/.ignore +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/.python-version +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/.zed/debug.json +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/CLAUDE.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/LICENSE +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/README.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/README.zh-CN.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/RELEASING.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/changelogs/0.1.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/changelogs/0.2.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/changelogs/0.3.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/changelogs/0.4.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/changelogs/0.5.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/changelogs/0.6.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/changelogs/README.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/dev_notes/README.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/dev_notes/en/0.1.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/dev_notes/en/0.2.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/dev_notes/en/0.3.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/dev_notes/en/0.4.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/dev_notes/zh-CN/0.1.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/dev_notes/zh-CN/0.2.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/dev_notes/zh-CN/0.3.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/dev_notes/zh-CN/0.4.x.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/superpowers/plans/2026-06-21-release-skills.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/superpowers/specs/2026-06-21-release-skill-design.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/docs/superpowers/specs/2026-06-29-config-file-support-design.md +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/pyproject.toml +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/src/nanopycodeagent/__init__.py +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/src/nanopycodeagent/bash_tool.py +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/src/nanopycodeagent/read_tool.py +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/src/nanopycodeagent/settings.py +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/src/nanopycodeagent/terminal.py +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/src/nanopycodeagent/write_tool.py +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/tests/conftest.py +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/tests/test_bash_tool.py +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/tests/test_line_editing.py +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/tests/test_read_tool.py +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/tests/test_settings.py +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/tests/test_terminal.py +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/tests/test_write_tool.py +0 -0
- {nanopycodeagent-0.6.0 → nanopycodeagent-0.7.0}/uv.lock +0 -0
|
@@ -5,16 +5,18 @@ description: >
|
|
|
5
5
|
Makes the PR self-contained: rewrites the PR description from the PR's own
|
|
6
6
|
contents, appends a changelog [Unreleased] entry when warranted, regenerates the
|
|
7
7
|
English dev notes from the Chinese source when touched, and keeps the bilingual
|
|
8
|
-
README in sync.
|
|
9
|
-
|
|
8
|
+
README in sync. It also checks bilingual research notes and reminds the maintainer
|
|
9
|
+
when their English version needs refreshing. Then — only after explicit maintainer
|
|
10
|
+
confirmation — merges the PR. Does NOT tag or publish. Triggers on
|
|
10
11
|
"合并 PR / land PR / 合并这个 PR / merge this PR".
|
|
11
12
|
---
|
|
12
13
|
|
|
13
14
|
# Land a PR
|
|
14
15
|
|
|
15
16
|
Merge a feature/bugfix PR into `main`, making the PR **self-contained** first:
|
|
16
|
-
its code, changelog entry, dev notes (zh + en),
|
|
17
|
-
within the PR. This skill never tags or
|
|
17
|
+
its code, changelog entry, dev notes (zh + en), research-note translation status,
|
|
18
|
+
and README (zh + en) all accounted for within the PR. This skill never tags or
|
|
19
|
+
publishes — that is the `release` skill's job.
|
|
18
20
|
|
|
19
21
|
## Prerequisites
|
|
20
22
|
|
|
@@ -31,38 +33,46 @@ file, e.g. `docs/changelogs/0.1.x.md` / `docs/dev_notes/{zh-CN,en}/0.1.x.md`.
|
|
|
31
33
|
- `git status --porcelain` — empty (clean tree).
|
|
32
34
|
- current branch is **not** `main` (`git branch --show-current`).
|
|
33
35
|
|
|
34
|
-
### 2.
|
|
36
|
+
### 2. Check English research notes before PR creation
|
|
37
|
+
If the branch modified any `docs/research/zh-CN/<name>.md`, check the
|
|
38
|
+
corresponding `docs/research/en/<name>.md`. Explicitly remind the maintainer
|
|
39
|
+
that the English version must also be translated or refreshed, and report
|
|
40
|
+
whether it is in sync. Do this even when the English file was already updated.
|
|
41
|
+
If it is missing or stale, surface that clearly before opening or landing the
|
|
42
|
+
PR; do not silently treat the Chinese-only change as complete.
|
|
43
|
+
|
|
44
|
+
### 3. Ensure a PR exists
|
|
35
45
|
```bash
|
|
36
46
|
gh pr view --json number,url 2>/dev/null || gh pr create --fill
|
|
37
47
|
```
|
|
38
48
|
Note the PR number for later steps.
|
|
39
49
|
|
|
40
|
-
###
|
|
50
|
+
### 4. Append a changelog entry (when warranted)
|
|
41
51
|
Inspect `git diff main...HEAD`. If the changes are worth recording for users, add
|
|
42
52
|
an entry under `## [Unreleased]` in `<series>` (changelogs) in the right group
|
|
43
53
|
(Added / Changed / Fixed / Removed). Purely internal/tooling changes may need
|
|
44
54
|
**no** entry — decide, and surface the decision at the gate.
|
|
45
55
|
|
|
46
|
-
###
|
|
56
|
+
### 5. Sync English dev notes (if the Chinese source changed)
|
|
47
57
|
If this PR modified `docs/dev_notes/zh-CN/<series>.md`, regenerate the whole
|
|
48
58
|
`docs/dev_notes/en/<series>.md` by translating the Chinese source. The English
|
|
49
59
|
file is generated — do not hand-edit beyond this regeneration.
|
|
50
60
|
|
|
51
|
-
###
|
|
61
|
+
### 6. Keep the bilingual README in sync
|
|
52
62
|
If this PR changed only one of `README.md` / `README.zh-CN.md`, mirror the change
|
|
53
63
|
into the other (translate/align) so the pair stays consistent. Both READMEs are
|
|
54
64
|
hand-written sources — propose the synced change and let the maintainer adjust the
|
|
55
65
|
wording at the gate; do not blindly overwrite. If both were already changed, skip.
|
|
56
66
|
|
|
57
|
-
###
|
|
58
|
-
Commit any changes from steps
|
|
67
|
+
### 7. Commit and push the sync changes
|
|
68
|
+
Commit any changes from steps 4–6 to the feature branch and push:
|
|
59
69
|
```bash
|
|
60
70
|
git commit -am "docs: sync changelog/dev-notes/README for this PR"
|
|
61
71
|
git push
|
|
62
72
|
```
|
|
63
|
-
(Skip if steps
|
|
73
|
+
(Skip if steps 4–6 produced no changes.)
|
|
64
74
|
|
|
65
|
-
###
|
|
75
|
+
### 8. Rewrite the PR description from scratch
|
|
66
76
|
Build the description **only** from the PR's actual contents — commits, changed
|
|
67
77
|
files, `gh pr diff`. **Do not read or extend the old description** (it may be
|
|
68
78
|
stale). Update via the GitHub API (`gh pr edit` can fail on deprecated Projects
|
|
@@ -71,13 +81,14 @@ Classic):
|
|
|
71
81
|
gh api repos/{owner}/{repo}/pulls/{number} -X PATCH -f body="..." --silent
|
|
72
82
|
```
|
|
73
83
|
|
|
74
|
-
###
|
|
84
|
+
### 9. ⛔ Confirmation gate (mandatory)
|
|
75
85
|
Present to the maintainer: the rewritten **PR description**, the **changelog
|
|
76
86
|
entry** added (or "none, because …"), and any **dev notes / README sync** done
|
|
77
|
-
(or a README drift warning)
|
|
78
|
-
Do not merge
|
|
87
|
+
(or a README drift warning), plus the **English research-note sync status** when
|
|
88
|
+
applicable. **Wait for explicit confirmation** (e.g. "确认 / go"). Do not merge
|
|
89
|
+
until approved.
|
|
79
90
|
|
|
80
|
-
###
|
|
91
|
+
### 10. Merge
|
|
81
92
|
```bash
|
|
82
93
|
gh pr merge --merge
|
|
83
94
|
```
|
|
@@ -37,6 +37,19 @@ The **only** exception is documentation explicitly designated as Chinese:
|
|
|
37
37
|
Everything outside that list — including code under `src/`, this file, and all
|
|
38
38
|
other docs — is English.
|
|
39
39
|
|
|
40
|
+
### Bilingual research notes
|
|
41
|
+
|
|
42
|
+
Research notes are bilingual:
|
|
43
|
+
|
|
44
|
+
- `docs/research/zh-CN/` contains the hand-written Chinese sources of truth.
|
|
45
|
+
- `docs/research/en/` contains English versions generated from the Chinese
|
|
46
|
+
sources; regenerate the whole corresponding file instead of hand-editing it.
|
|
47
|
+
|
|
48
|
+
When a change to a Chinese research source is headed into a pull request, the
|
|
49
|
+
agent MUST remind the user that the corresponding English version also needs to
|
|
50
|
+
be translated or refreshed. Before opening or landing the PR, report whether
|
|
51
|
+
the English version is in sync, even when it has already been updated.
|
|
52
|
+
|
|
40
53
|
## Commits & PRs
|
|
41
54
|
|
|
42
55
|
All agents working in this repo MUST follow
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: nanoPyCodeAgent
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A nano code agent built from scratch in pure Python.
|
|
5
5
|
Project-URL: Homepage, https://github.com/minixalpha/nanoPyCodeAgent
|
|
6
6
|
Project-URL: Repository, https://github.com/minixalpha/nanoPyCodeAgent
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog — 0.7.x
|
|
2
|
+
|
|
3
|
+
All notable changes in the **0.7.x** release series are documented here.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.7.0] - 2026-08-17
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- An `edit` tool that replaces an exact stretch of an existing text file,
|
|
11
|
+
so changing a few lines no longer means rewriting the whole file. The
|
|
12
|
+
match is literal — no regex, no whitespace or similarity guessing — and
|
|
13
|
+
must be unique unless `replace_all` is set, so a call that no longer fits
|
|
14
|
+
the file fails with a message saying how to recover instead of editing
|
|
15
|
+
the wrong place. A UTF-8 BOM and a CRLF file's line endings are handled
|
|
16
|
+
for you; creating a file and rewriting one whole stay with `write`.
|
|
17
|
+
- English translations for all research notes, organized alongside their
|
|
18
|
+
hand-written Chinese sources of truth. Development notes now link to the
|
|
19
|
+
matching research language, and the pull-request workflow reports whether
|
|
20
|
+
English research translations are in sync when Chinese sources change.
|
|
21
|
+
|
|
22
|
+
<!--
|
|
23
|
+
When cutting a release, copy the relevant items from [Unreleased] into a new
|
|
24
|
+
version section above it, e.g.:
|
|
25
|
+
|
|
26
|
+
## [0.7.0] - YYYY-MM-DD
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
### Changed
|
|
30
|
+
### Fixed
|
|
31
|
+
### Removed
|
|
32
|
+
-->
|
|
@@ -10,7 +10,7 @@ When the tool starts up without printing its version, I sometimes cannot tell wh
|
|
|
10
10
|
|
|
11
11
|
### Built-in tools in common code agents
|
|
12
12
|
|
|
13
|
-
For a survey of the built-in tools of mainstream code agents, see [Built-in Tools in Common Code Agents](../../research/agent_tools.md)
|
|
13
|
+
For a survey of the built-in tools of mainstream code agents, see [Built-in Tools in Common Code Agents](../../research/en/agent_tools.md).
|
|
14
14
|
|
|
15
15
|
### Designing the built-in tools
|
|
16
16
|
|
|
@@ -18,7 +18,7 @@ As the survey shows, beyond the core file/command tools that something like pi o
|
|
|
18
18
|
|
|
19
19
|
#### The read tool
|
|
20
20
|
|
|
21
|
-
We also surveyed the [read tool](../../research/read_tool.md)
|
|
21
|
+
We also surveyed the [read tool](../../research/en/read_tool.md): apart from codex, every code agent has a dedicated read tool. pi and opencode are model-agnostic, and you can see it in their read tools — their limits are line counts and file sizes, with no token limit. The other, model-specific code agents all have token limits; presumably because they only support their own vendor's models, where tokens are easier to count?
|
|
22
22
|
|
|
23
23
|
Functionally, read and bash both seem able to satisfy the need to read a file. Even accounting for files that may exceed a size limit, bash can still read them fine. Take codex: in actual testing, whatever the size of the file, it always starts with a `sed -n '1,260p' foo.md`, and then, if it finds there is more, it may follow up with `wc -l foo.md && sed -n '1,260p' foo.md` — reading chunk by chunk, which looks a lot like the `limit` of a dedicated read tool. But I'm not sure whether, for any given model, reading files is better done with a dedicated tool or with `bash`.
|
|
24
24
|
|
|
@@ -35,7 +35,7 @@ print("Hello, World!")
|
|
|
35
35
|
Running it outputs: `Hello, World!`
|
|
36
36
|
````
|
|
37
37
|
|
|
38
|
-
Based on the revised conclusion of the [write tool research](../../research/write_tool.md), the write tool should implement:
|
|
38
|
+
Based on the revised conclusion of the [write tool research](../../research/en/write_tool.md), the write tool should implement:
|
|
39
39
|
|
|
40
40
|
- Structured `{path, content}` input: the content lands on disk as data, never passing through shell expansion, heredoc delimiters, or quote escaping — eliminating the whole class of failures where a heredoc silently corrupts a file;
|
|
41
41
|
- Create the file when it does not exist, overwrite it whole when it does, and state which of the two happened in the result;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Development Notes — 0.7.x
|
|
2
|
+
|
|
3
|
+
> Generated from the Chinese source [`../zh-CN/0.7.x.md`](../zh-CN/0.7.x.md). Do not edit by hand.
|
|
4
|
+
|
|
5
|
+
## 0.7.0 - 2026.08.17
|
|
6
|
+
|
|
7
|
+
This version builds the edit tool. It starts from a survey of the edit tools of mainstream code agents, see [edit tool research](../../research/en/edit_tool.md).
|
|
8
|
+
|
|
9
|
+
There are two basic motivations for building an edit tool.
|
|
10
|
+
|
|
11
|
+
First, when changing a few lines of code, sending just the small stretch of old text and its replacement is enough — there is no need to make the model regenerate the entire file. That saves output tokens, and it keeps a full copy of the file from occupying the context for a long time.
|
|
12
|
+
|
|
13
|
+
Second, a whole-file rewrite easily changes places nobody asked it to change, whereas a unique `old_text` match is itself a verifiable precondition — when the old content is no longer there the call simply fails and the model re-reads, instead of overwriting unconditionally.
|
|
14
|
+
|
|
15
|
+
Of course, partial edits can also be made through bash, but at agent runtime it is hard to reliably derive from a dynamic shell snippet which file was changed, what old content it assumed, and what diff it actually produced. A dedicated tool also fails in a structured way (no match / N matches / the file has changed), which is easier to recover from than a chunk of shell stderr.
|
|
16
|
+
|
|
17
|
+
I tested what happens without an edit tool: I first wrote a `quick_sort.py`, then asked for comments to be added. nanoPyCodeAgent 0.6.x called the write tool and rewrote the whole file — confirming that without an edit tool the context grows.
|
|
18
|
+
|
|
19
|
+
Based on the research, here is what this round's edit tool implements.
|
|
20
|
+
|
|
21
|
+
### The edit tool contract
|
|
22
|
+
|
|
23
|
+
**Input** (`input_schema`, in the same style as `read`/`write`):
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
path string required File path, absolute or relative to the agent's working directory; a leading ~ is expanded
|
|
27
|
+
old_text string required The text to replace, character for character as it appears in the file; must not be empty
|
|
28
|
+
new_text string required The replacement text; an empty string means deleting old_text exactly
|
|
29
|
+
replace_all boolean optional Defaults to false; when true, replaces every non-overlapping match
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Output**: like `read`/`write`, it returns a single stretch of plain text plus an `is_error` flag, not a structured object. On success it returns a one-line summary and does not feed the file's content back to the model:
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
[edited src/app.py: replaced 1 occurrence at line 42]
|
|
36
|
+
[edited src/app.py: replaced 3 occurrences, first at line 12]
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
On failure `is_error=true`, and every kind of failure has to state both what happened and what to do next:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
[no match for old_text in src/app.py; read the file again and copy the text exactly — the CRLF retry was already tried]
|
|
43
|
+
[old_text matches 3 times in src/app.py; add surrounding context to make it unique, or set replace_all=true]
|
|
44
|
+
[old_text and new_text are identical: nothing to change]
|
|
45
|
+
[old_text is empty; use write to create a file or replace it whole]
|
|
46
|
+
[file not found: src/app.py]
|
|
47
|
+
[src/app.py is a directory, not a file]
|
|
48
|
+
[src/app.py is not valid UTF-8; edit is text-only]
|
|
49
|
+
[src/app.py is 12000000 bytes, over the 10000000 limit; edit it with bash]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
In the terminal the call shows up as `[edit] src/app.py` plus a folded little old/new diff. The full `old_text`/`new_text` is no longer fed back through the tool result — it is already in the tool input of that assistant message.
|
|
53
|
+
|
|
54
|
+
### Other implementation points for the edit tool
|
|
55
|
+
|
|
56
|
+
- **Field naming follows this project**: use `path` and snake_case, consistent with the existing `read`/`write`. Claude Code's `old_string` and OpenCode's `filePath`/`oldString` are deliberately not copied — a clear tool description is enough to teach the model, and there is no reason to fracture this project's naming just to align with some other product.
|
|
57
|
+
- **Unique match, fail outright when it is not**: by default `old_text` has to occur exactly once in the file; zero matches fails, and multiple matches without `replace_all` also fails and reports the match count. With `replace_all=True` it replaces every non-overlapping match and returns the actual count. Better to make the model retry once more than to guess a location.
|
|
58
|
+
- **Exact only, no fuzzy matching**: no regex, and no fallbacks such as trimming, indentation flexibility, similarity scoring or Unicode normalization. The only input conversions allowed are the BOM and line endings — both directional, both enumerable in scope, and both of which must be stated honestly in the tool description. Unlike OpenCode V1 and Pi, we must not claim "exact" to the model while the real matching boundary is wider.
|
|
59
|
+
- **Draw a clear line against `write`**: it only edits regular files that already exist; creating new files and rewriting whole ones stay with `write`. `old_text` must not be empty, and `old_text == new_text` fails. `new_text=""` means an exact deletion, with no hidden semantics like "and also take the following newline with it."
|
|
60
|
+
- **Strict UTF-8 and BOM handling**: files that are not valid UTF-8, and files containing NUL, are refused — `read` displays bad bytes as replacement characters to make them inspectable, and if edit round-tripped that view it would corrupt the original bytes for good. A UTF-8 BOM is stripped before matching and restored on write; otherwise an `old_text` aimed at the first line would mysteriously fail to match because of one invisible character.
|
|
61
|
+
- **Match `read`'s newline view with one directional CRLF retry**: first do a raw exact pass on `old_text` as given. Only when that finds nothing, and the file contains `\r\n`, and `old_text` contains `\n` but no `\r`, is its LF→CRLF form retried once — with `new_text` converted along with it. The two passes are never unioned: uniqueness and counting both happen in whichever pass actually matched. In a file with mixed line endings, a fragment in the other style simply does not match this time; that is a deliberate fail-closed, and the error message has to say that the CRLF retry was already tried.
|
|
62
|
+
- **File size reuses `MAX_READ_BYTES`, a 10 MB cap**: the implementation is a whole-file read-compute-write, so anything over the cap is refused outright with a suggestion to use bash or a dedicated script instead.
|
|
63
|
+
- **Paths, symlinks and error style align with `read`/`write`**: `~` expansion, the regular-file check and the error format all stay consistent. The first version writes back directly and explicitly offers no mtime check, no CAS and no atomic replace — do not pretend in the copy to have guarantees that do not exist.
|
|
64
|
+
- **The tool description has to teach the model how to choose**: prefer edit for ordinary partial changes, write for new files or whole-file rewrites, bash for bulk mechanical transforms. `old_text` has to be character-for-character identical, two to four lines is usually enough, it must not carry read's line-number prefix, and the model must not splice in `\r` itself.
|
|
65
|
+
- **Test matrix**: unique replacement / deletion / Unicode / no-op; not-found, repeated matches and `replace_all` counting; untouched content keeping its exact bytes under LF, CRLF, no trailing newline and mixed line endings; BOM stripping and restoration; invalid UTF-8 and NUL refusal; missing files, directories, FIFOs, over-cap files; `~` and relative paths, symlink behavior consistent with `write`; the folded terminal display and the `is_error` setting; several edits in one reply taking effect in order.
|
|
66
|
+
|
|
67
|
+
Explicitly out of scope for this version: no enforced prior Read (there is no read revision registry today, and a unique `old_text` is already a local precondition), no `edits[]` batching, no `apply_patch`, no local imitation of a formatter / LSP / history / approval UI, and no per-file queue (tool calls currently execute single-threaded and in order). These wait until parallel calls, approvals and remote filesystems genuinely show up, at which point they all get upgraded together into a mutation core shared by write and edit.
|
|
68
|
+
|
|
69
|
+
### QA
|
|
70
|
+
|
|
71
|
+
What follows are questions and answers from discussions with the agent: I asked the questions, the agent organized the answers, providing the explanation the material above needs.
|
|
72
|
+
|
|
73
|
+
#### Q: What exactly are the fallbacks mentioned under "exact only, no fuzzy matching"? What do "supporting regex" and "supporting trim" look like as code behavior?
|
|
74
|
+
|
|
75
|
+
First, a baseline: **exact means taking `old_text`'s byte sequence and finding an identical substring in the file**. The four spaces in `" return x\n"`, the `\n` at the end of the line, whether each quote is straight or curly — all of it has to line up. Every item below opens a hole in that baseline, and each one happens *after* exact matching fails, answering the question "should we try again with a relaxed standard?"
|
|
76
|
+
|
|
77
|
+
**1. Regex**
|
|
78
|
+
|
|
79
|
+
"Supporting regex" = treating `old_text` as a pattern instead of a literal:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
content.find(old_text) # literal matching (what we want)
|
|
83
|
+
re.sub(old_text, new_text, content) # regex matching (what we do not want)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The difference is that `. * + ? ( ) [ ] { } | ^ $ \` become **metacharacters**: the model wants to delete `foo(bar)` from the source, and the regex reads it as "foo followed by a capture group bar", actually matching `foobar`; `a.b` matches `axb`. These symbols are extremely dense in code, and the model would have to escape each one to express "I mean this literal text" — which it frequently forgets. Conversely, regex can express bulk patterns like `def \w+\(`, but that is bash's job (`sed`/`perl`). None of the five surveyed projects does regex in edit; this rule keeps sed's mindset out.
|
|
87
|
+
|
|
88
|
+
**2. Trim (comparing after stripping whitespace per line / on both sides)**
|
|
89
|
+
|
|
90
|
+
"Supporting trim" = after exact fails, split both sides into lines, `strip()` the leading and trailing whitespace off each, and compare line by line:
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
The line in the file (two trailing spaces): " return x "
|
|
94
|
+
The old_text the model submitted: " return x"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Exact does not match (two trailing spaces missing); with per-line trim on, both sides strip down to `"return x"` and it counts as a hit, then that whole line in the file is replaced. Codex's `seek_sequence` does "ignore trailing whitespace → trim both sides" (`en/edit_tool.md:273`), Pi does per-line trailing-whitespace normalization (`en/edit_tool.md:170`), and OpenCode V1 has a dedicated trim replacer (`en/edit_tool.md:224`).
|
|
98
|
+
|
|
99
|
+
Why they do it: trailing whitespace is especially easy to lose in transit. Claude Code's API message normalization silently strips non-Markdown per-line trailing whitespace from `new_string` (`en/edit_tool.md:208`), so the model never gets a chance to submit accurate trailing whitespace at all.
|
|
100
|
+
|
|
101
|
+
Why we do not: first, the matched range stops being equal to the bytes the model wrote (it thinks it is changing `" return x"`, while what actually gets changed is `" return x "`). Second, **what to write back** becomes another question that has to be settled — keep the file's original trailing whitespace, or erase it per the model's version? Two trailing spaces in Markdown are a hard line break, and erasing them changes the meaning. Once this hole is open, the sentence "the tool changes exactly what the model submitted" is no longer true.
|
|
102
|
+
|
|
103
|
+
**3. Indentation flexibility**
|
|
104
|
+
|
|
105
|
+
"Supporting indentation flexibility" = allowing `old_text` as a whole to be one level less (or more) indented than the file, and after matching, re-indenting `new_text` to the file's original indentation:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
In the file (inside a function, 8-space indent): The old_text the model submitted (flush left):
|
|
109
|
+
if x: if x:
|
|
110
|
+
return 1 return 1
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
With this level enabled, the tool notices every line is missing the same 8-space prefix, decides it is the same block, and adds the 8 spaces back to each line of `new_text` when replacing. OpenCode V1 has this replacer (`en/edit_tool.md:224`). It addresses the model's habit of rewriting from memory rather than copying, at the cost that in a language like Python — where indentation *is* syntax — getting the level wrong while re-indenting is a silent semantic error, and what the model receives is "success".
|
|
114
|
+
|
|
115
|
+
**4. Similarity (block anchor + Levenshtein)**
|
|
116
|
+
|
|
117
|
+
The widest level of all: use only `old_text`'s first and last lines as anchors to locate the region, and require the lines in between not to be identical but merely "similar". OpenCode V1 finds candidate regions by the first/last lines, computes a Levenshtein edit distance over the middle content to get a similarity score between 0 and 1, and **accepts anything ≥ 0.65**, taking the highest-scoring candidate and keeping the first on a tie (`en/edit_tool.md:224-225`):
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
The old_text the model submitted: What is actually in the file:
|
|
121
|
+
def run(x): def run(x):
|
|
122
|
+
y = x + 1 y = x * 2 ← the middle lines all differ
|
|
123
|
+
z = y * 3 z = y - 7
|
|
124
|
+
return z log(z)
|
|
125
|
+
return z
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The ends line up, the middle clears the similarity bar, and the tool deletes those 5 lines from the file as a block and swaps in `new_text` — **the old content the model quoted and the content actually deleted are not the same thing**, and the reply it gets is "replaced 1 occurrence". OpenCode V1 has to add an extra guard for this ("reject when the matched span is far larger than `old_text`", `en/edit_tool.md:224`). It can afford this because V1 has a human approval diff as a backstop before anything reaches disk (`en/edit_tool.md:226`); our first version has no approval UI, so there is nothing here to catch what falls through this hole.
|
|
129
|
+
|
|
130
|
+
**5. Unicode normalization**
|
|
131
|
+
|
|
132
|
+
"Supporting" it = folding characters that "look alike but have different code points" into the same thing before comparing:
|
|
133
|
+
|
|
134
|
+
| Category | What the model submitted | What is actually in the file |
|
|
135
|
+
| --- | --- | --- |
|
|
136
|
+
| smart quotes | `"hello"` (U+201C/U+201D) | `"hello"` (U+0022) |
|
|
137
|
+
| dash | `a – b` (en dash) | `a - b` (hyphen) |
|
|
138
|
+
| special spaces | NBSP (U+00A0) | ordinary space (U+0020) |
|
|
139
|
+
| NFKC | `(` fullwidth parenthesis, `fi` ligature | `(`, `fi` |
|
|
140
|
+
|
|
141
|
+
Model output passes through rendering and the tokenizer, which easily turns straight quotes into curly ones, so all five projects implement some version of this level: Grok has optional confusable normalization (`en/edit_tool.md:116`), Pi does NFKC + punctuation + special spaces (`en/edit_tool.md:170`), Claude Code does only quote normalization plus a fixed desanitize set (`en/edit_tool.md:202`), and Codex normalizes punctuation and spaces (`en/edit_tool.md:273`). The crucial difference is what gets written back: Grok maps the matched position back to the original UTF-8 bytes and fails closed when the mapping is not clean; Pi replaces on the normalized text and copies back only the lines it did not touch, so **the lines it did touch incidentally undergo NFKC / punctuation / trailing-whitespace changes**, producing modifications the model never asked for (`en/edit_tool.md:307`).
|
|
142
|
+
|
|
143
|
+
**So why do the BOM and line-ending conversions we kept not count as fuzzy?**
|
|
144
|
+
|
|
145
|
+
| | The five levels above | BOM / line endings |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| Cause | Guessing the model copied it wrong | Caused by our own `read` view |
|
|
148
|
+
| Direction | Two-way folding, both sides change | One-way conversion, only the `old_text` side changes |
|
|
149
|
+
| Trigger | Heuristic scoring | Can be written as a single if |
|
|
150
|
+
| Matched range | May be larger than the literal the model submitted | Strictly equal |
|
|
151
|
+
|
|
152
|
+
`read` drops the `\r` of CRLF when displaying, so multi-line original text in the model's hands necessarily has LF only — it could not produce `\r` even if it wanted to. That is not the model copying it wrong, it is a mismatch our own view created, so the tool supplies the conversion: the file contains `\r\n`, `old_text` contains `\n` and no `\r`, and the first pass came up empty — only when all three hold does one LF→CRLF retry happen. The BOM is the same story: `read` does not strip U+FEFF, so an invisible character hangs at the start of the first line, and without stripping it the match fails mysteriously. Both can be written into the tool description in one sentence for the model to verify; a similarity threshold of 0.65 cannot, and even if written down the model would have no way to judge which stretch was actually matched.
|
|
153
|
+
|
|
154
|
+
**The trade-off in one sentence**: fuzzy matching raises the first-try success rate, and what it costs is that the range the tool is actually authorized to modify becomes larger than the range the model expressed. Products with a human approval diff can afford that cost; our first version has no approval, no undo and no checkpoint, so one wrong replacement lands straight on disk. So we choose fail closed — the cost of failure is only that the model reads the file once more and retries, and that happens to be what agents are best at.
|
|
155
|
+
|
|
156
|
+
#### Q: What are the mechanisms behind those five items under "explicitly out of scope for this version", and why not do them now?
|
|
157
|
+
|
|
158
|
+
**1. No enforced prior Read (read-before-edit)**
|
|
159
|
+
|
|
160
|
+
"What it looks like if done": the agent keeps a table in the session recording, for each file, **whether it has been read in this session and which version was read** (a timestamp or content hash). Before an edit runs, it consults that table: never read means refuse outright and make the model read first; read but the file's mtime is newer than that read means someone changed it in the meantime, so refuse as well.
|
|
161
|
+
|
|
162
|
+
Claude Code does the heaviest version of this among the five projects: it does not merely suggest reading in the tool description, it enforces session read state at runtime, and a system-injected partial view does not count. Before writing it re-reads the current metadata synchronously to double-check, and deliberately inserts no `await` between the check and the write, squeezing the race window into a single event loop tick (`en/edit_tool.md:200`).
|
|
163
|
+
|
|
164
|
+
Why not now: we do not have that table, and building one means first building a read revision registry — and `read` supports `offset`/`limit` anyway, so what it returns is often just a window onto the file, which makes "has been read" an ambiguous state to begin with. More importantly, a unique `old_text` match **is itself a precondition**: if the old content is gone, the call fails, and that already covers the main scenario of "the file changed, so do not blind-write". Recording mtime only narrows the window; it does not eliminate TOCTOU (between the check and the actual write another process can still cut in), while adding a pile of state to carry (`en/edit_tool.md:367`).
|
|
165
|
+
|
|
166
|
+
When to add it: once there is undo/checkpoint or an approval UI and we need an explicit answer to "which version was the model's judgment based on".
|
|
167
|
+
|
|
168
|
+
**2. No `edits[]` batching**
|
|
169
|
+
|
|
170
|
+
"What it looks like if done": the schema goes from a single old/new to an array, submitting several changes to the same file in one call:
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
path: string
|
|
174
|
+
edits: [ {old_text, new_text}, {old_text, new_text}, ... ]
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Pi has exactly this shape: every `old_text` matches against **the same original file** (not against the result of the previous item), everything is validated first, overlapping ranges are rejected, and then they are applied in reverse order with a single write to disk (`en/edit_tool.md:149-177`). The benefit is one read, one write, a group of changes that logically all hold or all fail together, and fewer tool round trips.
|
|
178
|
+
|
|
179
|
+
Why not now: our agent loop is single-threaded and sequential — multiple `tool_use` blocks in one assistant reply are executed one at a time in the order the model gave them (`src/nanopycodeagent/agent.py:172-179`), so the model can already fire several edits in one turn, with each seeing the previous one's result. Batching would only save a few I/O operations, in exchange for overlap detection, per-item validation, and the question of how to report partial failure (`en/edit_tool.md:368`).
|
|
180
|
+
|
|
181
|
+
There is one more lesson from Pi: it once supported both a single old/new and `edits[]`, and the model kept mixing the two and producing invalid calls; in the end only the array survived, with the old shape demoted to a runtime input-migration layer (`en/edit_tool.md:161`, `en/edit_tool.md:315`). So if we ever do upgrade, only one public schema can remain — the two cannot coexist.
|
|
182
|
+
|
|
183
|
+
When to add it: when tool round-trip latency, or "many changes to one file", becomes a measured bottleneck.
|
|
184
|
+
|
|
185
|
+
**3. No `apply_patch`**
|
|
186
|
+
|
|
187
|
+
"What it looks like if done": instead of old/new strings, the call carries a whole patch text, expressing additions, deletions, changes and moves across several files at once:
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
*** Begin Patch
|
|
191
|
+
*** Update File: src/app.py
|
|
192
|
+
@@ def run():
|
|
193
|
+
- old line
|
|
194
|
+
+ new line
|
|
195
|
+
*** Add File: src/new.py
|
|
196
|
+
+content
|
|
197
|
+
*** Delete File: src/old.py
|
|
198
|
+
*** End Patch
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Codex has only this one file-modification tool; even creating a file is expressed with `*** Add File`, and no whole-file write is exposed (`en/edit_tool.md:246-267`). It is the most expressive option, with one parser and one permission entry point shared across files.
|
|
202
|
+
|
|
203
|
+
Why not now: it means writing a parser for a patch language, handling error recovery when a hunk fails to locate, facing partial commits where **the first few files write successfully and a later one fails** during a multi-file sequential submission (Codex itself does not roll back either, `en/edit_tool.md:277`), and redefining which set of paths permissions apply to. All of that would significantly inflate the core of a nano agent. Besides, the default model is Claude Sonnet, which is more familiar with the old/new form of Edit (`en/edit_tool.md:369`); OpenCode simply dispatches by model — `apply_patch` for the GPT family, `edit` + `write` for everything else (`en/edit_tool.md:218`).
|
|
204
|
+
|
|
205
|
+
When to add it: when a single call needs to express something atomically across several files (a rename plus updating all references, say), or when adapting to a model whose training distribution is patch-shaped.
|
|
206
|
+
|
|
207
|
+
**4. No local imitation of a formatter / LSP / history / approval UI**
|
|
208
|
+
|
|
209
|
+
These four are an entire lifecycle mature agents hang around "writing to disk". One at a time:
|
|
210
|
+
|
|
211
|
+
- **formatter**: after writing, automatically run something like prettier/black, then **recompute the diff from the formatted result** before returning it to the model. OpenCode V1 does this (`en/edit_tool.md:222`); the cost is that what finally lands on disk may be larger than the diff shown at approval time.
|
|
212
|
+
- **LSP**: after writing, notify the language server to re-analyze and stuff the newly produced diagnostics (type errors, undefined variables) into the tool result, so the model sees on the spot whether it broke something. OpenCode V1 has it, and Claude Code has it when an IDE is connected (`en/edit_tool.md:206`, `en/edit_tool.md:222`).
|
|
213
|
+
- **history / undo**: save a snapshot before every write, supporting rewind to a given step. Grok's `FileWritten` event carries previous/new content precisely to feed the hunk tracker and rewind (`en/edit_tool.md:120`).
|
|
214
|
+
- **approval UI**: put the diff in front of a human before writing, and only write once approved. OpenCode V1/V2's `edit` permission is exactly this (`en/edit_tool.md:222`, `en/edit_tool.md:238`).
|
|
215
|
+
|
|
216
|
+
Why not now: these are **not capabilities of the edit tool but of the whole mutation / exec control plane**. Adding a layer only on edit is bypassed by one `sed -i` in bash — approval, snapshots, path protection, all of it: as long as bash can still write arbitrary files, the defense has a hole (`en/edit_tool.md:370`). A half-installed security boundary is more dangerous than none, because it makes people believe there is one.
|
|
217
|
+
|
|
218
|
+
When to add it: once bash and the file tools land inside the same OS / container / VM filesystem boundary, build it as a whole then, rather than simulating one on edit first.
|
|
219
|
+
|
|
220
|
+
**5. No per-file queue**
|
|
221
|
+
|
|
222
|
+
"What it looks like if done": take an in-process lock keyed by the file's canonical path (the real path with symlinks resolved), serializing the whole "read current content → compute new content → write back" stretch, so two mutations of the same file cannot interleave while different files still run in parallel. Pi uses a mutation queue keyed by `realpath` (`en/edit_tool.md:177`); OpenCode V2 goes further and does `writeIfUnchanged(expectedBytes)` inside that lock — comparing the current bytes against the bytes read at approval time and declaring it stale if they differ (`en/edit_tool.md:238-242`).
|
|
223
|
+
|
|
224
|
+
What it prevents is a **lost update**: two changes run concurrently, A reads the old content, B reads the old content, A writes, B writes, and A's change is silently overwritten by B.
|
|
225
|
+
|
|
226
|
+
Why not now: we have no concurrency at all. Tool calls are dispatched sequentially in a single while loop (`src/nanopycodeagent/agent.py:172-179`), only one edit runs at any moment, and an in-process lock would always be available and never contended (`en/edit_tool.md:333`, `en/edit_tool.md:371`). And to be clear: this kind of lock **can only coordinate the tools that participate in it** — external editors and bash subprocesses can still cut in between the read and the write, so it was never a cross-writer CAS (`en/edit_tool.md:242`).
|
|
227
|
+
|
|
228
|
+
When to add it: the day parallel tool calls actually arrive — at which point the whole read-compute-write stretch goes into a shared queue keyed by canonical path, rather than locking only the write itself.
|
|
229
|
+
|
|
230
|
+
**The shared logic behind these five**: either they address **a problem that does not exist yet** under the current architecture (concurrency, batching round trips), or they **cannot be solved at all** under the current architecture (approval and snapshots are bypassed by bash), or they **would inflate the core for unclear benefit** (a patch parser, a revision registry). The closing line of the research puts it well: first solve the token and accidental-overwrite problems of partial edits with a unique old-text precondition, and wait until the architecture genuinely develops concurrency, approval and remote needs before upgrading the mutation core — rather than simulating safety ahead of time (`en/edit_tool.md:412`).
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
工具启动时,如果不打印版本号,有时候自己分辨不出来是哪个版本。先开发一下这个功能。
|
|
9
9
|
|
|
10
10
|
### 常见 code agent 内置工具
|
|
11
|
-
主流 Code Agent 的内置工具调研见 [《常见 Code Agent 内置工具》](../../research/agent_tools.md)。
|
|
11
|
+
主流 Code Agent 的内置工具调研见 [《常见 Code Agent 内置工具》](../../research/zh-CN/agent_tools.md)。
|
|
12
12
|
|
|
13
13
|
### 内置工具设计
|
|
14
14
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
#### read 工具
|
|
18
18
|
|
|
19
|
-
我们对 [read工具](../../research/read_tool.md) 也做了调研,除了 codex 外,其他 code agent 都有专门的 read 工具。pi 和 opencode 是模型无关的,可以看出他们的 read 工具,limit 除了行数,还有文件大小,并没有 token 限制。而其他专用的 code agent,都有 token 限制,大概是只支持自家模型,token 更方便计算?
|
|
19
|
+
我们对 [read工具](../../research/zh-CN/read_tool.md) 也做了调研,除了 codex 外,其他 code agent 都有专门的 read 工具。pi 和 opencode 是模型无关的,可以看出他们的 read 工具,limit 除了行数,还有文件大小,并没有 token 限制。而其他专用的 code agent,都有 token 限制,大概是只支持自家模型,token 更方便计算?
|
|
20
20
|
|
|
21
21
|
功能上讲,似乎 read 和 bash 都能实现读文件需求。即使考虑文件大小可能超出限制,bash 也能正常读取。例如 codex,实际测试中发现 codex 读文件的操作,不管读什么大小文件,他都会先来个 `sed -n '1,260p' foo.md`,之后如果发现还有内容,可能会 `wc -l foo.md && sed -n '1,260p' foo.md`,相当于一块一块读,和专用 read 工具的 limit 似乎差不多。但不太确定各个模型,在读文件的时候,是用专用工具好,还是用 `bash` 更好。
|
|
22
22
|
|
|
@@ -33,7 +33,7 @@ print("Hello, World!")
|
|
|
33
33
|
Running it outputs: `Hello, World!`
|
|
34
34
|
````
|
|
35
35
|
|
|
36
|
-
基于 [write 工具调研](../../research/write_tool.md)修正后的结论,write 工具要实现的功能:
|
|
36
|
+
基于 [write 工具调研](../../research/zh-CN/write_tool.md)修正后的结论,write 工具要实现的功能:
|
|
37
37
|
|
|
38
38
|
- 结构化 `{path, content}` 入参:内容作为数据直接落盘,不经过 shell 展开、heredoc delimiter 和引号转义,消除 heredoc 静默写坏文件这一整类失败;
|
|
39
39
|
- 文件不存在则创建,存在则整文件覆盖,并在结果中说明是哪一种;
|