@arnilo/prism-coding-agent 0.0.5 → 0.0.6
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/CHANGELOG.md +14 -1
- package/README.md +10 -6
- package/dist/bounded-file.d.ts +2 -0
- package/dist/bounded-file.js +25 -0
- package/dist/edit-diff.d.ts +0 -10
- package/dist/edit-diff.js +1 -33
- package/dist/edit.d.ts +23 -4
- package/dist/edit.js +55 -8
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/limits.d.ts +22 -0
- package/dist/limits.js +28 -0
- package/dist/output-accumulator.d.ts +17 -10
- package/dist/output-accumulator.js +127 -86
- package/dist/read.d.ts +39 -8
- package/dist/read.js +174 -60
- package/dist/shell.d.ts +4 -0
- package/dist/shell.js +98 -74
- package/dist/truncate.d.ts +7 -4
- package/dist/truncate.js +6 -6
- package/dist/write.d.ts +9 -2
- package/dist/write.js +10 -5
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,20 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.0.6] - 2026-07-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Finite validated defaults/hard caps for text scans, image/file/input/edit counts, shell wall time, display output, and total shell output.
|
|
13
|
+
- Exported coding limit constants plus bounded `ReadOperations.readText` page contracts.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Text reads stream one bounded page instead of loading the entire file; edit/image reads use a shared bounded file reader after stat checks.
|
|
18
|
+
- Shell defaults to a 600-second timeout, kills the operation at 64 MiB combined output, and creates exclusive Unix `0600` spill files.
|
|
19
|
+
- Failed/aborted/timed-out/output-limited shell calls delete unpublished spill files; successful truncated output remains host-owned at `metadata.fullOutputPath`.
|
|
20
|
+
- Custom `ReadOperations` now require `readText` and `statFile`; custom `EditOperations` require `statFile` and receive byte/signal options.
|
|
21
|
+
- Removed non-exported filesystem edit-preview helpers that duplicated the edit tool's file read path.
|
|
9
22
|
|
|
10
23
|
## [0.0.5] - 2026-07-16
|
|
11
24
|
|
package/README.md
CHANGED
|
@@ -44,6 +44,8 @@ const shell = createShellTool(process.cwd(), {
|
|
|
44
44
|
shellPath: "/bin/bash", // force bash; default: SHELL env → /bin/bash → sh
|
|
45
45
|
commandPrefix: "set -euo pipefail",
|
|
46
46
|
maxLines: 500,
|
|
47
|
+
timeout: 600,
|
|
48
|
+
maxTotalOutputBytes: 64 * 1024 * 1024,
|
|
47
49
|
});
|
|
48
50
|
|
|
49
51
|
const remoteWrite = createWriteTool(process.cwd(), {
|
|
@@ -58,10 +60,10 @@ const remoteWrite = createWriteTool(process.cwd(), {
|
|
|
58
60
|
|
|
59
61
|
| Tool | Input | Result |
|
|
60
62
|
| --- | --- | --- |
|
|
61
|
-
| `shell` | `{ command, timeout? }` | Combined output + `metadata.exitCode
|
|
62
|
-
| `read` | `{ path, offset?, limit? }` |
|
|
63
|
-
| `write` | `{ path, content }` | `Successfully wrote N bytes (M lines) to <abs>`. |
|
|
64
|
-
| `edit` | `{ path, edits: [{oldText,newText}] }` | `Successfully replaced N block(s)` +
|
|
63
|
+
| `shell` | `{ command, timeout? }` | Combined output + `metadata.exitCode`; 600-second default timeout and 64 MiB total-output cap. Non-zero exit is **not** an error. |
|
|
64
|
+
| `read` | `{ path, offset?, limit? }` | Streamed bounded text page or bounded `[note, ImageContent]`. |
|
|
65
|
+
| `write` | `{ path, content }` | Bounded UTF-8 input; `Successfully wrote N bytes (M lines) to <abs>`. |
|
|
66
|
+
| `edit` | `{ path, edits: [{oldText,newText}] }` | Bounded target/input/count; `Successfully replaced N block(s)` + diff metadata. |
|
|
65
67
|
|
|
66
68
|
### pi name mapping
|
|
67
69
|
|
|
@@ -74,9 +76,11 @@ const remoteWrite = createWriteTool(process.cwd(), {
|
|
|
74
76
|
|
|
75
77
|
Factories: `createShellTool`, `createReadTool`, `createWriteTool`, `createEditTool`, `createCodingTools`, `createReadOnlyTools`, `createAllTools`, `createLocalBashOperations`.
|
|
76
78
|
|
|
77
|
-
Helpers: `detectSupportedImageMimeType`, `detectSupportedImageMimeTypeFromFile`, `getShellConfig`, `killProcessTree`, `waitForChildProcess`, `withFileMutationQueue`.
|
|
79
|
+
Helpers: `detectSupportedImageMimeType`, `detectSupportedImageMimeTypeFromFile`, `getShellConfig`, `killProcessTree`, `waitForChildProcess`, `withFileMutationQueue`. Default/hard coding limit constants are exported for host configuration.
|
|
78
80
|
|
|
79
|
-
Option/operation types: `ToolsOptions`, `ShellToolOptions`/`BashOperations`, `ReadToolOptions`/`ReadOperations`, `WriteToolOptions`/`WriteOperations`, `EditToolOptions`/`EditOperations`/`EditToolDetails`.
|
|
81
|
+
Option/operation types: `ToolsOptions`, `ShellToolOptions`/`BashOperations`, `ReadToolOptions`/`ReadOperations`/`ReadTextOptions`/`ReadTextResult`, `WriteToolOptions`/`WriteOperations`, `EditToolOptions`/`EditOperations`/`EditToolDetails`.
|
|
82
|
+
|
|
83
|
+
Text reads stop after one page or `maxScanBytes` instead of loading the file. Custom `ReadOperations` must implement bounded `readText` and `statFile`; custom `EditOperations` must implement `statFile` and honor the supplied read cap/signal. Successful truncated shell output is retained in an exclusive Unix `0600` temp file owned by the host; timeout, abort, output-limit, and spill failures remove unpublished spill files. Hosts should delete published `metadata.fullOutputPath` files after use.
|
|
80
84
|
|
|
81
85
|
## License
|
|
82
86
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { open } from "node:fs/promises";
|
|
2
|
+
/** Read one regular-file snapshot no larger than `maxBytes`; always closes its handle. */
|
|
3
|
+
export async function readFileBounded(path, maxBytes, signal) {
|
|
4
|
+
const handle = await open(path, "r");
|
|
5
|
+
try {
|
|
6
|
+
const size = (await handle.stat()).size;
|
|
7
|
+
if (size > maxBytes)
|
|
8
|
+
throw new Error(`File exceeds ${maxBytes} byte limit`);
|
|
9
|
+
const buffer = Buffer.allocUnsafe(size);
|
|
10
|
+
let offset = 0;
|
|
11
|
+
while (offset < size) {
|
|
12
|
+
if (signal?.aborted)
|
|
13
|
+
throw new Error("Operation aborted");
|
|
14
|
+
const { bytesRead } = await handle.read(buffer, offset, size - offset, offset);
|
|
15
|
+
if (bytesRead === 0)
|
|
16
|
+
break;
|
|
17
|
+
offset += bytesRead;
|
|
18
|
+
}
|
|
19
|
+
return buffer.subarray(0, offset);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
await handle.close();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=bounded-file.js.map
|
package/dist/edit-diff.d.ts
CHANGED
|
@@ -75,19 +75,9 @@ export interface EditDiffResult {
|
|
|
75
75
|
diff: string;
|
|
76
76
|
firstChangedLine: number | undefined;
|
|
77
77
|
}
|
|
78
|
-
export interface EditDiffError {
|
|
79
|
-
error: string;
|
|
80
|
-
}
|
|
81
78
|
/**
|
|
82
79
|
* Generate a display-oriented diff string with line numbers and context.
|
|
83
80
|
* Returns both the diff string and the first changed line number (in the new file).
|
|
84
81
|
*/
|
|
85
82
|
export declare function generateDiffString(oldContent: string, newContent: string, contextLines?: number): EditDiffResult;
|
|
86
|
-
/**
|
|
87
|
-
* Compute the diff for one or more edit operations without applying them.
|
|
88
|
-
* Used for preview before the edit tool executes.
|
|
89
|
-
*/
|
|
90
|
-
export declare function computeEditsDiff(path: string, edits: Edit[], cwd: string): Promise<EditDiffResult | EditDiffError>;
|
|
91
|
-
/** Compute the diff for a single edit operation without applying it. */
|
|
92
|
-
export declare function computeEditDiff(path: string, oldText: string, newText: string, cwd: string): Promise<EditDiffResult | EditDiffError>;
|
|
93
83
|
export {};
|
package/dist/edit-diff.js
CHANGED
|
@@ -2,14 +2,11 @@
|
|
|
2
2
|
* Shared diff computation utilities for the edit and similar tools.
|
|
3
3
|
*
|
|
4
4
|
* Behavioral port of pi's core/tools/edit-diff for @arnilo/prism-coding-agent.
|
|
5
|
-
*
|
|
5
|
+
* The `diff` package provides unified-patch / display-diff generation;
|
|
6
6
|
* unified-patch / display-diff generation. Fuzzy matching, replacement
|
|
7
7
|
* preservation, BOM/line-ending handling are dep-free.
|
|
8
8
|
*/
|
|
9
9
|
import * as Diff from "diff";
|
|
10
|
-
import { constants } from "node:fs";
|
|
11
|
-
import { access, readFile } from "node:fs/promises";
|
|
12
|
-
import { resolveToCwd } from "./path-utils.js";
|
|
13
10
|
export function detectLineEnding(content) {
|
|
14
11
|
const crlfIdx = content.indexOf("\r\n");
|
|
15
12
|
const lfIdx = content.indexOf("\n");
|
|
@@ -387,33 +384,4 @@ export function generateDiffString(oldContent, newContent, contextLines = 4) {
|
|
|
387
384
|
}
|
|
388
385
|
return { diff: output.join("\n"), firstChangedLine };
|
|
389
386
|
}
|
|
390
|
-
/**
|
|
391
|
-
* Compute the diff for one or more edit operations without applying them.
|
|
392
|
-
* Used for preview before the edit tool executes.
|
|
393
|
-
*/
|
|
394
|
-
export async function computeEditsDiff(path, edits, cwd) {
|
|
395
|
-
const absolutePath = resolveToCwd(path, cwd);
|
|
396
|
-
try {
|
|
397
|
-
try {
|
|
398
|
-
await access(absolutePath, constants.R_OK);
|
|
399
|
-
}
|
|
400
|
-
catch (error) {
|
|
401
|
-
const errorMessage = error instanceof Error && "code" in error ? `Error code: ${String(error.code)}` : String(error);
|
|
402
|
-
return { error: `Could not edit file: ${path}. ${errorMessage}.` };
|
|
403
|
-
}
|
|
404
|
-
const rawContent = await readFile(absolutePath, "utf-8");
|
|
405
|
-
// Strip BOM before matching (LLM won't include invisible BOM in oldText).
|
|
406
|
-
const { text: content } = stripBom(rawContent);
|
|
407
|
-
const normalizedContent = normalizeToLF(content);
|
|
408
|
-
const { baseContent, newContent } = applyEditsToNormalizedContent(normalizedContent, edits, path);
|
|
409
|
-
return generateDiffString(baseContent, newContent);
|
|
410
|
-
}
|
|
411
|
-
catch (err) {
|
|
412
|
-
return { error: err instanceof Error ? err.message : String(err) };
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
/** Compute the diff for a single edit operation without applying it. */
|
|
416
|
-
export async function computeEditDiff(path, oldText, newText, cwd) {
|
|
417
|
-
return computeEditsDiff(path, [{ oldText, newText }], cwd);
|
|
418
|
-
}
|
|
419
387
|
//# sourceMappingURL=edit-diff.js.map
|
package/dist/edit.d.ts
CHANGED
|
@@ -39,17 +39,36 @@ export interface EditToolDetails {
|
|
|
39
39
|
* (e.g. SSH) while keeping the tool's matching + per-path serialization behavior.
|
|
40
40
|
*/
|
|
41
41
|
export interface EditOperations {
|
|
42
|
-
/** Read file contents as a Buffer. */
|
|
43
|
-
readFile: (absolutePath: string
|
|
42
|
+
/** Read bounded file contents as a Buffer. */
|
|
43
|
+
readFile: (absolutePath: string, options: {
|
|
44
|
+
maxBytes: number;
|
|
45
|
+
signal?: AbortSignal;
|
|
46
|
+
}) => Promise<Buffer>;
|
|
44
47
|
/** Write content to a file. */
|
|
45
|
-
writeFile: (absolutePath: string, content: string
|
|
48
|
+
writeFile: (absolutePath: string, content: string, options?: {
|
|
49
|
+
signal?: AbortSignal;
|
|
50
|
+
}) => Promise<void>;
|
|
46
51
|
/** Check the file is readable and writable (throw if not). */
|
|
47
|
-
access: (absolutePath: string
|
|
52
|
+
access: (absolutePath: string, options?: {
|
|
53
|
+
signal?: AbortSignal;
|
|
54
|
+
}) => Promise<void>;
|
|
55
|
+
/** Return target size before the bounded read. */
|
|
56
|
+
statFile: (absolutePath: string, options?: {
|
|
57
|
+
signal?: AbortSignal;
|
|
58
|
+
}) => Promise<{
|
|
59
|
+
size: number;
|
|
60
|
+
}>;
|
|
48
61
|
}
|
|
49
62
|
export interface EditToolOptions {
|
|
50
63
|
/** Structured pre-execution policy checked before filesystem writes. */
|
|
51
64
|
executionPolicy?: ExecutionPolicy;
|
|
52
65
|
/** Custom operations backend (default: local filesystem). */
|
|
53
66
|
operations?: EditOperations;
|
|
67
|
+
/** Maximum target file bytes read for matching (default 8 MiB). */
|
|
68
|
+
maxFileBytes?: number;
|
|
69
|
+
/** Maximum aggregate UTF-8 bytes across old/new edit text (default 2 MiB). */
|
|
70
|
+
maxInputBytes?: number;
|
|
71
|
+
/** Maximum replacements per call (default 100). */
|
|
72
|
+
maxEdits?: number;
|
|
54
73
|
}
|
|
55
74
|
export declare function createEditTool(cwd: string, options?: EditToolOptions): ToolDefinition;
|
package/dist/edit.js
CHANGED
|
@@ -1,13 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Edit tool: precise text replacement in an existing file via exact-then-fuzzy matching.
|
|
3
|
+
*
|
|
4
|
+
* Behavioral port of pi's core/tools/edit for @arnilo/prism-coding-agent, adapted to Prism's
|
|
5
|
+
* `ToolDefinition` contract. Faithfully ports the access → read → stripBom → line-ending-normalize →
|
|
6
|
+
* `applyEditsToNormalizedContent` (exact then fuzzy, with duplicate/overlap/empty/no-change guards) →
|
|
7
|
+
* write flow, serialized per-path via `withFileMutationQueue`. Also ports `prepareEditArguments`
|
|
8
|
+
* (tolerates models that send `edits` as a JSON string or as legacy top-level `oldText`/`newText`).
|
|
9
|
+
*
|
|
10
|
+
* Drops pi's TUI (`renderCall`/`renderResult`, live preview cache, theme/syntax-highlight).
|
|
11
|
+
*
|
|
12
|
+
* Deviations from pi (documented):
|
|
13
|
+
* - Abort + every failure (missing/unreadable file, no-match, duplicate, overlap, empty oldText,
|
|
14
|
+
* no-change) return a Prism `error` result (pi throws/rejects). On no-match the file is untouched
|
|
15
|
+
* because `applyEditsToNormalizedContent` throws before `writeFile`.
|
|
16
|
+
* - pi's per-tool `details: { diff, patch, firstChangedLine }` (TUI-facing) is surfaced as
|
|
17
|
+
* `ToolResult.metadata` (host-readable, keeps model context small — the model only sees the short
|
|
18
|
+
* `Successfully replaced N block(s)` confirmation).
|
|
19
|
+
* - The post-`writeFile` abort check is dropped (consistent with the write tool): if the write
|
|
20
|
+
* completed, the edit is real and is reported as success rather than a misleading "aborted".
|
|
21
|
+
*/
|
|
22
|
+
import { Buffer } from "node:buffer";
|
|
23
|
+
import { access as fsAccess, stat as fsStat, writeFile as fsWriteFile, } from "node:fs/promises";
|
|
2
24
|
import { constants } from "node:fs";
|
|
25
|
+
import { readFileBounded } from "./bounded-file.js";
|
|
3
26
|
import { enforceExecutionPolicy } from "./execution-policy.js";
|
|
4
27
|
import { resolveToCwd } from "./path-utils.js";
|
|
5
28
|
import { withFileMutationQueue } from "./file-mutation-queue.js";
|
|
29
|
+
import { DEFAULT_MAX_EDIT_FILE_BYTES, DEFAULT_MAX_EDIT_INPUT_BYTES, DEFAULT_MAX_EDITS, HARD_MAX_EDIT_FILE_BYTES, HARD_MAX_EDIT_INPUT_BYTES, HARD_MAX_EDITS, validateCodingLimit, } from "./limits.js";
|
|
6
30
|
import { applyEditsToNormalizedContent, detectLineEnding, generateDiffString, generateUnifiedPatch, normalizeToLF, restoreLineEndings, stripBom, } from "./edit-diff.js";
|
|
7
31
|
const defaultEditOperations = {
|
|
8
|
-
readFile: (path) =>
|
|
9
|
-
writeFile: (path, content) => fsWriteFile(path, content, "utf-8"),
|
|
32
|
+
readFile: (path, options) => readFileBounded(path, options.maxBytes, options.signal),
|
|
33
|
+
writeFile: (path, content, options) => fsWriteFile(path, content, { encoding: "utf-8", signal: options?.signal }),
|
|
10
34
|
access: (path) => fsAccess(path, constants.R_OK | constants.W_OK),
|
|
35
|
+
statFile: async (path) => ({ size: (await fsStat(path)).size }),
|
|
11
36
|
};
|
|
12
37
|
function errorResult(toolCallId, message) {
|
|
13
38
|
return {
|
|
@@ -40,22 +65,32 @@ function prepareEditArguments(input) {
|
|
|
40
65
|
}
|
|
41
66
|
return { path, edits };
|
|
42
67
|
}
|
|
43
|
-
function validateEdits(edits) {
|
|
68
|
+
function validateEdits(edits, maxEdits, maxInputBytes) {
|
|
44
69
|
if (!Array.isArray(edits) || edits.length === 0) {
|
|
45
70
|
return "edits must contain at least one replacement.";
|
|
46
71
|
}
|
|
72
|
+
if (edits.length > maxEdits)
|
|
73
|
+
return `edits contains ${edits.length} replacements, exceeds ${maxEdits} limit.`;
|
|
47
74
|
const out = [];
|
|
75
|
+
let inputBytes = 0;
|
|
48
76
|
for (let i = 0; i < edits.length; i++) {
|
|
49
77
|
const e = edits[i];
|
|
50
78
|
if (typeof e?.oldText !== "string" || typeof e?.newText !== "string") {
|
|
51
79
|
return `edits[${i}] must have string oldText and newText.`;
|
|
52
80
|
}
|
|
81
|
+
inputBytes += Buffer.byteLength(e.oldText, "utf-8") + Buffer.byteLength(e.newText, "utf-8");
|
|
82
|
+
if (inputBytes > maxInputBytes) {
|
|
83
|
+
return `edit input is ${inputBytes} bytes, exceeds ${maxInputBytes} byte limit.`;
|
|
84
|
+
}
|
|
53
85
|
out.push({ oldText: e.oldText, newText: e.newText });
|
|
54
86
|
}
|
|
55
87
|
return out;
|
|
56
88
|
}
|
|
57
89
|
export function createEditTool(cwd, options) {
|
|
58
90
|
const ops = options?.operations ?? defaultEditOperations;
|
|
91
|
+
const maxFileBytes = validateCodingLimit("maxFileBytes", options?.maxFileBytes ?? DEFAULT_MAX_EDIT_FILE_BYTES, HARD_MAX_EDIT_FILE_BYTES);
|
|
92
|
+
const maxInputBytes = validateCodingLimit("maxInputBytes", options?.maxInputBytes ?? DEFAULT_MAX_EDIT_INPUT_BYTES, HARD_MAX_EDIT_INPUT_BYTES);
|
|
93
|
+
const maxEdits = validateCodingLimit("maxEdits", options?.maxEdits ?? DEFAULT_MAX_EDITS, HARD_MAX_EDITS);
|
|
59
94
|
return {
|
|
60
95
|
name: "edit",
|
|
61
96
|
description: "Edit a single file using exact text replacement. Every edits[].oldText must match a unique, non-overlapping region of the original file. If two changes affect the same block or nearby lines, merge them into one edit instead of emitting overlapping edits. Do not include large unchanged regions just to connect distant changes.",
|
|
@@ -85,11 +120,14 @@ export function createEditTool(cwd, options) {
|
|
|
85
120
|
},
|
|
86
121
|
async execute(args, context) {
|
|
87
122
|
const toolCallId = context.toolCallId;
|
|
123
|
+
if (typeof args.edits === "string" && Buffer.byteLength(args.edits, "utf-8") > maxInputBytes) {
|
|
124
|
+
return errorResult(toolCallId, `edit input exceeds ${maxInputBytes} byte limit.`);
|
|
125
|
+
}
|
|
88
126
|
const prepared = prepareEditArguments(args);
|
|
89
127
|
if (prepared.path.length === 0) {
|
|
90
128
|
return errorResult(toolCallId, "path is required and must be a non-empty string.");
|
|
91
129
|
}
|
|
92
|
-
const editsOrError = validateEdits(prepared.edits);
|
|
130
|
+
const editsOrError = validateEdits(prepared.edits, maxEdits, maxInputBytes);
|
|
93
131
|
if (typeof editsOrError === "string") {
|
|
94
132
|
return errorResult(toolCallId, editsOrError);
|
|
95
133
|
}
|
|
@@ -111,7 +149,7 @@ export function createEditTool(cwd, options) {
|
|
|
111
149
|
return errorResult(toolCallId, "Operation aborted");
|
|
112
150
|
// Check the file is readable + writable.
|
|
113
151
|
try {
|
|
114
|
-
await ops.access(allowedPath);
|
|
152
|
+
await ops.access(allowedPath, { signal: context.signal });
|
|
115
153
|
}
|
|
116
154
|
catch (error) {
|
|
117
155
|
if (context.signal?.aborted)
|
|
@@ -122,7 +160,16 @@ export function createEditTool(cwd, options) {
|
|
|
122
160
|
}
|
|
123
161
|
if (context.signal?.aborted)
|
|
124
162
|
return errorResult(toolCallId, "Operation aborted");
|
|
125
|
-
const
|
|
163
|
+
const { size } = await ops.statFile(allowedPath, { signal: context.signal });
|
|
164
|
+
if (size > maxFileBytes) {
|
|
165
|
+
return errorResult(toolCallId, `Edit target is ${size} bytes, exceeds ${maxFileBytes} byte limit.`);
|
|
166
|
+
}
|
|
167
|
+
if (context.signal?.aborted)
|
|
168
|
+
return errorResult(toolCallId, "Operation aborted");
|
|
169
|
+
const buffer = await ops.readFile(allowedPath, { maxBytes: maxFileBytes, signal: context.signal });
|
|
170
|
+
if (buffer.length > maxFileBytes) {
|
|
171
|
+
return errorResult(toolCallId, `Edit target is ${buffer.length} bytes, exceeds ${maxFileBytes} byte limit.`);
|
|
172
|
+
}
|
|
126
173
|
if (context.signal?.aborted)
|
|
127
174
|
return errorResult(toolCallId, "Operation aborted");
|
|
128
175
|
const rawContent = buffer.toString("utf-8");
|
|
@@ -143,7 +190,7 @@ export function createEditTool(cwd, options) {
|
|
|
143
190
|
if (context.signal?.aborted)
|
|
144
191
|
return errorResult(toolCallId, "Operation aborted");
|
|
145
192
|
const finalContent = bom + restoreLineEndings(newContent, originalEnding);
|
|
146
|
-
await ops.writeFile(allowedPath, finalContent);
|
|
193
|
+
await ops.writeFile(allowedPath, finalContent, { signal: context.signal });
|
|
147
194
|
const diffResult = generateDiffString(baseContent, newContent);
|
|
148
195
|
const patch = generateUnifiedPatch(prepared.path, baseContent, newContent);
|
|
149
196
|
return {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
export { createShellTool, createLocalBashOperations, getShellConfig, killProcessTree, waitForChildProcess, } from "./shell.js";
|
|
2
2
|
export type { ShellToolOptions, ShellConfig, BashOperations, BashExecOptions, BashSpawnContext, BashSpawnHook, } from "./shell.js";
|
|
3
3
|
export { createReadTool, detectSupportedImageMimeType, detectSupportedImageMimeTypeFromFile, DEFAULT_MAX_IMAGE_BYTES, } from "./read.js";
|
|
4
|
-
export type { ReadToolOptions, ReadOperations, TransformImage, TransformImageInput, } from "./read.js";
|
|
4
|
+
export type { ReadToolOptions, ReadOperations, ReadTextOptions, ReadTextResult, TransformImage, TransformImageInput, } from "./read.js";
|
|
5
5
|
export { createWriteTool } from "./write.js";
|
|
6
6
|
export type { WriteToolOptions, WriteOperations } from "./write.js";
|
|
7
7
|
export { createEditTool } from "./edit.js";
|
|
8
8
|
export type { EditToolOptions, EditOperations, EditToolDetails, Edit } from "./edit.js";
|
|
9
9
|
export { withFileMutationQueue } from "./file-mutation-queue.js";
|
|
10
10
|
export { enforceExecutionPolicy } from "./execution-policy.js";
|
|
11
|
+
export { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, DEFAULT_MAX_TEXT_SCAN_BYTES, DEFAULT_MAX_WRITE_BYTES, DEFAULT_MAX_EDIT_FILE_BYTES, DEFAULT_MAX_EDIT_INPUT_BYTES, DEFAULT_MAX_EDITS, DEFAULT_SHELL_TIMEOUT_SECONDS, DEFAULT_MAX_TOTAL_OUTPUT_BYTES, HARD_MAX_BYTES, HARD_MAX_LINES, HARD_MAX_TEXT_SCAN_BYTES, HARD_MAX_IMAGE_BYTES, HARD_MAX_WRITE_BYTES, HARD_MAX_EDIT_FILE_BYTES, HARD_MAX_EDIT_INPUT_BYTES, HARD_MAX_EDITS, HARD_SHELL_TIMEOUT_SECONDS, HARD_MAX_TOTAL_OUTPUT_BYTES, } from "./limits.js";
|
|
11
12
|
import type { ExecutionPolicy, ToolDefinition } from "@arnilo/prism";
|
|
12
13
|
import type { ShellToolOptions } from "./shell.js";
|
|
13
14
|
import type { ReadToolOptions } from "./read.js";
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export { createEditTool } from "./edit.js";
|
|
|
11
11
|
// --- generic primitives (re-exported for hosts that want them) ---
|
|
12
12
|
export { withFileMutationQueue } from "./file-mutation-queue.js";
|
|
13
13
|
export { enforceExecutionPolicy } from "./execution-policy.js";
|
|
14
|
+
export { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, DEFAULT_MAX_TEXT_SCAN_BYTES, DEFAULT_MAX_WRITE_BYTES, DEFAULT_MAX_EDIT_FILE_BYTES, DEFAULT_MAX_EDIT_INPUT_BYTES, DEFAULT_MAX_EDITS, DEFAULT_SHELL_TIMEOUT_SECONDS, DEFAULT_MAX_TOTAL_OUTPUT_BYTES, HARD_MAX_BYTES, HARD_MAX_LINES, HARD_MAX_TEXT_SCAN_BYTES, HARD_MAX_IMAGE_BYTES, HARD_MAX_WRITE_BYTES, HARD_MAX_EDIT_FILE_BYTES, HARD_MAX_EDIT_INPUT_BYTES, HARD_MAX_EDITS, HARD_SHELL_TIMEOUT_SECONDS, HARD_MAX_TOTAL_OUTPUT_BYTES, } from "./limits.js";
|
|
14
15
|
import { createShellTool } from "./shell.js";
|
|
15
16
|
import { createReadTool } from "./read.js";
|
|
16
17
|
import { createWriteTool } from "./write.js";
|
package/dist/limits.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const DEFAULT_MAX_LINES = 2000;
|
|
2
|
+
export declare const HARD_MAX_LINES = 100000;
|
|
3
|
+
export declare const DEFAULT_MAX_BYTES: number;
|
|
4
|
+
export declare const HARD_MAX_BYTES: number;
|
|
5
|
+
export declare const DEFAULT_MAX_TEXT_SCAN_BYTES: number;
|
|
6
|
+
export declare const HARD_MAX_TEXT_SCAN_BYTES: number;
|
|
7
|
+
export declare const DEFAULT_MAX_IMAGE_BYTES = 10000000;
|
|
8
|
+
export declare const HARD_MAX_IMAGE_BYTES: number;
|
|
9
|
+
export declare const DEFAULT_MAX_WRITE_BYTES: number;
|
|
10
|
+
export declare const HARD_MAX_WRITE_BYTES: number;
|
|
11
|
+
export declare const DEFAULT_MAX_EDIT_FILE_BYTES: number;
|
|
12
|
+
export declare const HARD_MAX_EDIT_FILE_BYTES: number;
|
|
13
|
+
export declare const DEFAULT_MAX_EDIT_INPUT_BYTES: number;
|
|
14
|
+
export declare const HARD_MAX_EDIT_INPUT_BYTES: number;
|
|
15
|
+
export declare const DEFAULT_MAX_EDITS = 100;
|
|
16
|
+
export declare const HARD_MAX_EDITS = 1000;
|
|
17
|
+
export declare const DEFAULT_SHELL_TIMEOUT_SECONDS = 600;
|
|
18
|
+
export declare const HARD_SHELL_TIMEOUT_SECONDS = 3600;
|
|
19
|
+
export declare const DEFAULT_MAX_TOTAL_OUTPUT_BYTES: number;
|
|
20
|
+
export declare const HARD_MAX_TOTAL_OUTPUT_BYTES: number;
|
|
21
|
+
/** Validate one configurable coding resource limit. Invalid values fail instead of clamping. */
|
|
22
|
+
export declare function validateCodingLimit(name: string, value: number, hardCap: number): number;
|
package/dist/limits.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const DEFAULT_MAX_LINES = 2_000;
|
|
2
|
+
export const HARD_MAX_LINES = 100_000;
|
|
3
|
+
export const DEFAULT_MAX_BYTES = 50 * 1024;
|
|
4
|
+
export const HARD_MAX_BYTES = 1024 * 1024;
|
|
5
|
+
export const DEFAULT_MAX_TEXT_SCAN_BYTES = 64 * 1024 * 1024;
|
|
6
|
+
export const HARD_MAX_TEXT_SCAN_BYTES = 1024 * 1024 * 1024;
|
|
7
|
+
export const DEFAULT_MAX_IMAGE_BYTES = 10_000_000;
|
|
8
|
+
export const HARD_MAX_IMAGE_BYTES = 32 * 1024 * 1024;
|
|
9
|
+
export const DEFAULT_MAX_WRITE_BYTES = 8 * 1024 * 1024;
|
|
10
|
+
export const HARD_MAX_WRITE_BYTES = 64 * 1024 * 1024;
|
|
11
|
+
export const DEFAULT_MAX_EDIT_FILE_BYTES = 8 * 1024 * 1024;
|
|
12
|
+
export const HARD_MAX_EDIT_FILE_BYTES = 64 * 1024 * 1024;
|
|
13
|
+
export const DEFAULT_MAX_EDIT_INPUT_BYTES = 2 * 1024 * 1024;
|
|
14
|
+
export const HARD_MAX_EDIT_INPUT_BYTES = 16 * 1024 * 1024;
|
|
15
|
+
export const DEFAULT_MAX_EDITS = 100;
|
|
16
|
+
export const HARD_MAX_EDITS = 1_000;
|
|
17
|
+
export const DEFAULT_SHELL_TIMEOUT_SECONDS = 600;
|
|
18
|
+
export const HARD_SHELL_TIMEOUT_SECONDS = 3_600;
|
|
19
|
+
export const DEFAULT_MAX_TOTAL_OUTPUT_BYTES = 64 * 1024 * 1024;
|
|
20
|
+
export const HARD_MAX_TOTAL_OUTPUT_BYTES = 1024 * 1024 * 1024;
|
|
21
|
+
/** Validate one configurable coding resource limit. Invalid values fail instead of clamping. */
|
|
22
|
+
export function validateCodingLimit(name, value, hardCap) {
|
|
23
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > hardCap) {
|
|
24
|
+
throw new Error(`${name} must be a positive safe integer at most ${hardCap}`);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=limits.js.map
|
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type TruncationResult } from "./truncate.js";
|
|
2
2
|
export interface OutputAccumulatorOptions {
|
|
3
3
|
maxLines?: number;
|
|
4
4
|
maxBytes?: number;
|
|
5
|
+
maxTotalOutputBytes?: number;
|
|
5
6
|
tempFilePrefix?: string;
|
|
7
|
+
onLimit?: () => void;
|
|
8
|
+
onStorageError?: () => void;
|
|
6
9
|
}
|
|
7
10
|
export interface OutputSnapshot {
|
|
8
11
|
content: string;
|
|
9
12
|
truncation: TruncationResult;
|
|
10
13
|
fullOutputPath?: string;
|
|
11
14
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Incrementally tracks streaming output with bounded memory.
|
|
14
|
-
*
|
|
15
|
-
* Appends decode chunks with a streaming UTF-8 decoder, keeps only a decoded
|
|
16
|
-
* tail for display snapshots, and opens a temp file when the full output needs
|
|
17
|
-
* to be preserved.
|
|
18
|
-
*/
|
|
19
15
|
export declare class OutputAccumulator {
|
|
20
16
|
private readonly maxLines;
|
|
21
17
|
private readonly maxBytes;
|
|
18
|
+
private readonly maxTotalOutputBytes;
|
|
22
19
|
private readonly maxRollingBytes;
|
|
23
20
|
private readonly tempFilePrefix;
|
|
21
|
+
private readonly onLimit?;
|
|
22
|
+
private readonly onStorageError?;
|
|
24
23
|
private readonly decoder;
|
|
25
24
|
private rawChunks;
|
|
26
25
|
private tailText;
|
|
@@ -33,19 +32,27 @@ export declare class OutputAccumulator {
|
|
|
33
32
|
private currentLineBytes;
|
|
34
33
|
private hasOpenLine;
|
|
35
34
|
private finished;
|
|
35
|
+
private exceeded;
|
|
36
36
|
private tempFilePath?;
|
|
37
|
-
private
|
|
37
|
+
private tempFileFd?;
|
|
38
|
+
private tempFileError?;
|
|
38
39
|
constructor(options?: OutputAccumulatorOptions);
|
|
39
|
-
append(data: Buffer):
|
|
40
|
+
append(data: Buffer): boolean;
|
|
40
41
|
finish(): void;
|
|
41
42
|
snapshot(options?: {
|
|
42
43
|
persistIfTruncated?: boolean;
|
|
43
44
|
}): OutputSnapshot;
|
|
44
45
|
closeTempFile(): Promise<void>;
|
|
46
|
+
cleanupTempFile(): Promise<void>;
|
|
45
47
|
getLastLineBytes(): number;
|
|
48
|
+
getTotalRawBytes(): number;
|
|
49
|
+
isOutputLimitExceeded(): boolean;
|
|
50
|
+
hasStorageError(): boolean;
|
|
46
51
|
private appendDecodedText;
|
|
47
52
|
private trimTail;
|
|
48
53
|
private getSnapshotText;
|
|
49
54
|
private shouldUseTempFile;
|
|
50
55
|
private ensureTempFile;
|
|
56
|
+
private writeTemp;
|
|
57
|
+
private recordTempError;
|
|
51
58
|
}
|