@theokit/sdk-tools 0.26.3 → 0.27.1
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 +233 -0
- package/LICENSE +2 -2
- package/README.md +15 -2
- package/dist/index.cjs +123 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +452 -9
- package/dist/index.d.ts +452 -9
- package/dist/index.js +124 -12
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
package/dist/index.d.cts
CHANGED
|
@@ -34,6 +34,21 @@ interface CreateApplyPatchToolOptions {
|
|
|
34
34
|
/** Absolute path to the project root. Every hunk path is gated against this boundary. */
|
|
35
35
|
projectRoot: string;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Build the `apply_patch` tool over Codex's V4A patch grammar — one call that adds, updates, deletes
|
|
39
|
+
* and moves several files at once.
|
|
40
|
+
*
|
|
41
|
+
* Prefer it to a run of {@link createEditFileTool} calls when a change spans files, because the whole
|
|
42
|
+
* patch is planned before anything is written: every file read, every new content computed and every
|
|
43
|
+
* path security-checked first, and any failure aborts with zero writes. A sequence of `edit_file`
|
|
44
|
+
* calls has no such property — the third can fail with the first two already on disk. The price is
|
|
45
|
+
* that it leaves no backup, where `edit_file` writes a `.bak`.
|
|
46
|
+
*
|
|
47
|
+
* Refusals: `parse_error`, `path_traversal`, `forbidden_path` (checked at every path segment here,
|
|
48
|
+
* not just the first), `not_found`, `patch_failed` (context did not match), `duplicate_target` (two
|
|
49
|
+
* hunks touching one file, which would silently lose the earlier edit), `file_exists` (Add over an
|
|
50
|
+
* existing file) and `io_error` for anything the filesystem raises during the write phase.
|
|
51
|
+
*/
|
|
37
52
|
declare function createApplyPatchTool(opts: CreateApplyPatchToolOptions): CustomTool;
|
|
38
53
|
|
|
39
54
|
/**
|
|
@@ -114,6 +129,17 @@ interface CreateCurrentTimeToolOptions {
|
|
|
114
129
|
/** Injectable clock — defaults to `() => new Date()`. Pass a fixed clock for deterministic tests. */
|
|
115
130
|
clock?: () => Date;
|
|
116
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Build the `current_time` tool, so the model reads a clock instead of stating a date from training
|
|
134
|
+
* data.
|
|
135
|
+
*
|
|
136
|
+
* The model may pass an IANA `timezone`; omitted, the answer is UTC. An unknown zone comes back as
|
|
137
|
+
* `{ ok: false, error: "invalid_timezone" }` — the `RangeError` `Intl` raises is caught, so the
|
|
138
|
+
* handler never throws at the model.
|
|
139
|
+
*
|
|
140
|
+
* Pass `clock` in tests. The default reads the real wall clock, and a test asserting on a formatted
|
|
141
|
+
* date without injecting one is asserting on the day it was written.
|
|
142
|
+
*/
|
|
117
143
|
declare function createCurrentTimeTool(opts?: CreateCurrentTimeToolOptions): CustomTool;
|
|
118
144
|
|
|
119
145
|
/**
|
|
@@ -124,7 +150,7 @@ declare function createCurrentTimeTool(opts?: CreateCurrentTimeToolOptions): Cus
|
|
|
124
150
|
*
|
|
125
151
|
* Return shape (always a JSON string):
|
|
126
152
|
* - `{ ok: true, replacements: 1 }` on success
|
|
127
|
-
* - `{ ok: false, error: 'no_match' | 'not_found' | 'path_traversal' |
|
|
153
|
+
* - `{ ok: false, error: 'no_change' | 'no_match' | 'not_found' | 'path_traversal' |
|
|
128
154
|
* 'forbidden_path' }` on refusal
|
|
129
155
|
*/
|
|
130
156
|
|
|
@@ -146,6 +172,21 @@ interface CreateEditFileToolOptions {
|
|
|
146
172
|
* write go through the backend (surface-agnostic); omitted ⇒ the local `fs` path (byte-identical). */
|
|
147
173
|
filesystem?: FilesystemProvider;
|
|
148
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Build the `edit_file` tool: replace one occurrence of `old_string` with `new_string` in place,
|
|
177
|
+
* after copying the previous content to `<path>.bak`.
|
|
178
|
+
*
|
|
179
|
+
* Three matching attempts run in order and the first hit wins — the exact substring, then the same
|
|
180
|
+
* text with runs of whitespace collapsed, then a line-based ladder tolerating trailing whitespace and
|
|
181
|
+
* typographic punctuation. The FIRST exact occurrence is taken with no ambiguity check, so a short
|
|
182
|
+
* `old_string` appearing twice edits the earlier one silently: include enough surrounding context to
|
|
183
|
+
* make it unique. Only the ladder refuses an ambiguous target, and it is reached only when the exact
|
|
184
|
+
* and whitespace-normalised passes have both failed.
|
|
185
|
+
*
|
|
186
|
+
* `old_string === new_string` is refused up front as `no_change`; the other refusals are `no_match`,
|
|
187
|
+
* `not_found`, `forbidden_path` and `path_traversal`. Exactly one occurrence changes per call, so
|
|
188
|
+
* replacing every occurrence means calling until `no_match`.
|
|
189
|
+
*/
|
|
149
190
|
declare function createEditFileTool(opts: CreateEditFileToolOptions): CustomTool;
|
|
150
191
|
|
|
151
192
|
/**
|
|
@@ -192,13 +233,31 @@ interface CreateGitDiffToolOptions {
|
|
|
192
233
|
name?: string;
|
|
193
234
|
/** M76 — description exposed to the model. Omitted => today's literal (additive). */
|
|
194
235
|
description?: string;
|
|
236
|
+
/** Absolute path to the project root. `git` runs here and every `path` scope is gated against it. */
|
|
195
237
|
projectRoot: string;
|
|
238
|
+
/** Wall-clock cap on the local `git` child; the process group is killed on expiry. Default 30_000. */
|
|
196
239
|
timeoutMs?: number;
|
|
240
|
+
/** Cap on captured stdout; excess sets `truncated: true`. Default 5 MB. Local path only. */
|
|
197
241
|
maxStdoutBytes?: number;
|
|
198
242
|
/** Optional injected execution backend (`@theokit/sdk/sandbox`) — when provided, `git diff` runs via
|
|
199
243
|
* `SandboxBackend.execute` (surface-agnostic); omitted ⇒ the local `git` child process (unchanged). */
|
|
200
244
|
sandbox?: SandboxProvider;
|
|
201
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Build the `git_diff` tool: `git diff --no-color` over the working tree, or the staged changes when
|
|
248
|
+
* the model passes `cached`.
|
|
249
|
+
*
|
|
250
|
+
* Reach for it before a commit or after a run of edits, when the question is what CHANGED rather than
|
|
251
|
+
* what a file now contains — `read_file` answers the latter and spends a whole file doing it.
|
|
252
|
+
*
|
|
253
|
+
* Refusals are `not_a_repo`, `path_traversal`, `timeout` and `git_failed`. The local path kills the
|
|
254
|
+
* process group at `timeoutMs` and caps captured stdout at `maxStdoutBytes`, flagging
|
|
255
|
+
* `truncated: true`; neither limit applies on the `sandbox` path, where the backend's own `timeoutMs`
|
|
256
|
+
* is the only bound and `truncated` always comes back false.
|
|
257
|
+
*
|
|
258
|
+
* With `sandbox` set the local `.git` probe is skipped deliberately — the repository lives in the
|
|
259
|
+
* backend, and a missing one surfaces as git's own "not a git repository".
|
|
260
|
+
*/
|
|
202
261
|
declare function createGitDiffTool(opts: CreateGitDiffToolOptions): CustomTool;
|
|
203
262
|
|
|
204
263
|
/**
|
|
@@ -229,8 +288,12 @@ interface CreateGitStatusToolOptions {
|
|
|
229
288
|
* `SandboxBackend.execute`; omitted ⇒ the local `git` (unchanged).
|
|
230
289
|
*
|
|
231
290
|
* Symmetry with `createGitDiffTool`, flagged by the M76 review: without it `git_diff` would run
|
|
232
|
-
* confined and `git_status` not, in the same session — and the asymmetry would be invisible until
|
|
233
|
-
*
|
|
291
|
+
* confined and `git_status` not, in the same session — and the asymmetry would be invisible until
|
|
292
|
+
* someone noticed that one of the two escapes the sandbox.
|
|
293
|
+
*
|
|
294
|
+
* When set, the repository question is answered by the BACKEND (from git's own stderr), not by a
|
|
295
|
+
* probe of the host's filesystem. #346 — the host probe used to run first, so a session whose
|
|
296
|
+
* checkout lives inside the backend got `not_a_repo` for a repository that was there.
|
|
234
297
|
*/
|
|
235
298
|
sandbox?: SandboxProvider;
|
|
236
299
|
/**
|
|
@@ -250,6 +313,23 @@ interface CreateGitStatusToolOptions {
|
|
|
250
313
|
*/
|
|
251
314
|
includeBranch?: boolean;
|
|
252
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Build the `git_status` tool. Returns `git status --porcelain=v1` output, which is the reason to use
|
|
318
|
+
* this rather than a `shell_exec` of plain `git status`: the human format is not stable across git
|
|
319
|
+
* versions and the porcelain one is.
|
|
320
|
+
*
|
|
321
|
+
* The output arrives in a field named `diff`, shared with {@link createGitDiffTool} rather than named
|
|
322
|
+
* after what it holds.
|
|
323
|
+
*
|
|
324
|
+
* A missing `.git` is `{ ok: false, error: "not_a_repo" }` rather than an empty string, so the model
|
|
325
|
+
* cannot read "not a repository" as "nothing changed". The other refusals are `path_traversal`,
|
|
326
|
+
* `timeout` and `git_failed`.
|
|
327
|
+
*
|
|
328
|
+
* With `sandbox` the command itself runs through the injected backend — but the local `.git` probe
|
|
329
|
+
* runs BEFORE that branch, so a sandboxed status still requires `<projectRoot>/.git` to exist on the
|
|
330
|
+
* host. {@link createGitDiffTool} skips its equivalent probe when sandboxed. Configure both tools the
|
|
331
|
+
* same way in one session: one confined and one not is an asymmetry nothing surfaces.
|
|
332
|
+
*/
|
|
253
333
|
declare function createGitStatusTool(opts: CreateGitStatusToolOptions): CustomTool;
|
|
254
334
|
|
|
255
335
|
/**
|
|
@@ -275,6 +355,22 @@ interface CreateGlobToolOptions {
|
|
|
275
355
|
* backend (Local/remote), so the tool is surface-agnostic; omitted ⇒ the local `readdir` (unchanged). */
|
|
276
356
|
filesystem?: FilesystemProvider;
|
|
277
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Build the `glob_files` tool: find files by the shape of their name. Use it when you know what the
|
|
360
|
+
* file is called, `search_text` when you know what is inside it, `read_file` when you know the path.
|
|
361
|
+
*
|
|
362
|
+
* The pattern is matched against paths relative to `cwd`, while the paths returned are relative to
|
|
363
|
+
* `projectRoot` — `{ pattern: "*.ts", cwd: "src" }` matches `index.ts` and returns `src/index.ts`.
|
|
364
|
+
* Only `*`, `**` and `?` are wildcards; braces and character classes are escaped to literals, so
|
|
365
|
+
* `*.{ts,js}` matches a file actually named that. Matching is anchored at both ends, which is why a
|
|
366
|
+
* bare `*.ts` sees only the top level of the search root and a leading `**` is usually what is meant.
|
|
367
|
+
*
|
|
368
|
+
* `node_modules`, `.git`, `dist` and `.theo` are skipped by name at any depth and cannot be
|
|
369
|
+
* re-enabled. Nothing else is filtered: this returns names, so a `.env` file is listed even though
|
|
370
|
+
* `read_file` would refuse to open it. An unreadable directory is skipped without a word rather than
|
|
371
|
+
* failing the call, so a permission problem looks like an empty directory. The only refusal is
|
|
372
|
+
* `path_traversal` on `cwd`; a pattern matching nothing is `{ ok: true, files: [] }`.
|
|
373
|
+
*/
|
|
278
374
|
declare function createGlobTool(opts: CreateGlobToolOptions): CustomTool;
|
|
279
375
|
|
|
280
376
|
/**
|
|
@@ -356,6 +452,15 @@ declare function isCommandAllowed(command: string, policies: CommandPolicy[]): b
|
|
|
356
452
|
* its own `{ ok: false, error }` shape.
|
|
357
453
|
*/
|
|
358
454
|
type ContextMatchReason = "empty" | "ambiguous" | "not_found";
|
|
455
|
+
/**
|
|
456
|
+
* Thrown by {@link replaceUnique} when the replacement cannot be made safely. `reason` names the
|
|
457
|
+
* case: `empty` (no target given), `ambiguous` (the target matched in more than one place, so nothing
|
|
458
|
+
* was replaced), `not_found` (no rung of the ladder matched).
|
|
459
|
+
*
|
|
460
|
+
* `ambiguous` is the one worth handling apart from the others. It says the caller's target text is
|
|
461
|
+
* too short, not that the file lacks it, so the remedy is more surrounding context rather than a
|
|
462
|
+
* different search.
|
|
463
|
+
*/
|
|
359
464
|
declare class ContextMatchError extends Error {
|
|
360
465
|
readonly reason: ContextMatchReason;
|
|
361
466
|
constructor(reason: ContextMatchReason, message: string);
|
|
@@ -457,6 +562,11 @@ interface RepoMapOptions {
|
|
|
457
562
|
/** Max directory depth to descend. Default 4. */
|
|
458
563
|
maxDepth?: number;
|
|
459
564
|
}
|
|
565
|
+
/**
|
|
566
|
+
* Options for {@link buildEnvContext}. Both fields exist to make the block deterministic under test:
|
|
567
|
+
* `now` fixes the date line, `gitHeadPath` points the branch lookup at a fixture instead of
|
|
568
|
+
* `<cwd>/.git/HEAD`. Neither changes what the block contains.
|
|
569
|
+
*/
|
|
460
570
|
interface EnvContextOptions {
|
|
461
571
|
/** Injectable clock for the date line (deterministic tests). Default `new Date()`. */
|
|
462
572
|
now?: Date;
|
|
@@ -647,6 +757,22 @@ interface CreateListDirToolOptions {
|
|
|
647
757
|
*/
|
|
648
758
|
filesystem?: FilesystemProvider;
|
|
649
759
|
}
|
|
760
|
+
/**
|
|
761
|
+
* Build the `list_dir` tool: the direct entries of one directory as `{ name, type }`. Never
|
|
762
|
+
* recursive — use `glob_files` to search a tree.
|
|
763
|
+
*
|
|
764
|
+
* The result always carries `truncated` and `totalCount`, so an agent that hits the `max` cap
|
|
765
|
+
* (default 500) can tell a large directory from a complete listing and switch to `glob_files` or
|
|
766
|
+
* `search_text`. There is no offset input, so paging past the cap is not possible; `totalCount`
|
|
767
|
+
* counts everything the directory holds, including what was cut.
|
|
768
|
+
*
|
|
769
|
+
* `path` is project-relative and `"."` means the root. Refusals are `forbidden_path`,
|
|
770
|
+
* `path_traversal` and `not_found`, the last of which also covers a path that exists but is a file.
|
|
771
|
+
*
|
|
772
|
+
* On a `filesystem` backend each entry costs a `stat` to learn its type, and an entry whose `stat`
|
|
773
|
+
* fails is reported as a file rather than dropped — so `type` is best-effort there and exact on the
|
|
774
|
+
* local path.
|
|
775
|
+
*/
|
|
650
776
|
declare function createListDirTool(opts: CreateListDirToolOptions): CustomTool;
|
|
651
777
|
|
|
652
778
|
/**
|
|
@@ -663,6 +789,13 @@ declare function createListDirTool(opts: CreateListDirToolOptions): CustomTool;
|
|
|
663
789
|
*/
|
|
664
790
|
|
|
665
791
|
type Mode = "normal" | "plan";
|
|
792
|
+
/**
|
|
793
|
+
* The `plan_mode` tool object returned by the no-argument {@link createPlanModeTool}. Its `handler`
|
|
794
|
+
* is synchronous, where {@link PlanModeToolWithStore}'s returns a promise — so the two are not
|
|
795
|
+
* interchangeable at a call site that does not await.
|
|
796
|
+
*
|
|
797
|
+
* `currentMode` is a test seam; the mode also comes back in every result.
|
|
798
|
+
*/
|
|
666
799
|
interface PlanModeTool {
|
|
667
800
|
name: string;
|
|
668
801
|
description: string;
|
|
@@ -706,6 +839,20 @@ interface PlanModeToolOptions {
|
|
|
706
839
|
/** Artifact id under which the plan is stored. Default `"plan"`. */
|
|
707
840
|
artifactId?: string;
|
|
708
841
|
}
|
|
842
|
+
/**
|
|
843
|
+
* Build the `plan_mode` tool, which flips a flag and returns instruction text telling the model to
|
|
844
|
+
* outline before it edits. It enforces nothing — no other tool consults the mode, so a model that
|
|
845
|
+
* ignores the instructions is not stopped, and the mode lives in this object rather than in the
|
|
846
|
+
* session.
|
|
847
|
+
*
|
|
848
|
+
* With no argument it returns a {@link PlanModeTool} whose handler is synchronous. With options it
|
|
849
|
+
* returns a {@link PlanModeToolWithStore} whose handler is async and accepts a `plan` string,
|
|
850
|
+
* persisted to `artifactStore` under `artifactId` (default `"plan"`) when the model exits plan mode.
|
|
851
|
+
* An empty or absent `plan` is not written, and `persisted` in the result says which happened.
|
|
852
|
+
*
|
|
853
|
+
* `name` and `description` are read on the options overload only. The no-argument form always
|
|
854
|
+
* publishes `plan_mode` with the built-in description, since it takes nothing to override it with.
|
|
855
|
+
*/
|
|
709
856
|
declare function createPlanModeTool(): PlanModeTool;
|
|
710
857
|
declare function createPlanModeTool(options: PlanModeToolOptions): PlanModeToolWithStore;
|
|
711
858
|
|
|
@@ -714,7 +861,9 @@ declare function createPlanModeTool(options: PlanModeToolOptions): PlanModeToolW
|
|
|
714
861
|
*
|
|
715
862
|
* Return shape (always a JSON string):
|
|
716
863
|
* - `{ ok: true, answer: string }`
|
|
717
|
-
* - `{ ok: false, error: "timeout" }`
|
|
864
|
+
* - `{ ok: false, error: "timeout" }` — nobody answered within `timeoutMs`
|
|
865
|
+
* - `{ ok: false, error: "no_asker" }` — neither `ctx.context.askUser` nor the factory's `askUser`
|
|
866
|
+
* was available, so there was nobody to ask
|
|
718
867
|
*/
|
|
719
868
|
interface QuestionToolOptions {
|
|
720
869
|
/**
|
|
@@ -770,6 +919,23 @@ interface QuestionTool {
|
|
|
770
919
|
threadId?: string;
|
|
771
920
|
}) => Promise<string>;
|
|
772
921
|
}
|
|
922
|
+
/**
|
|
923
|
+
* Build the `question` tool: the agent stops, asks the user something, and the turn waits for the
|
|
924
|
+
* answer.
|
|
925
|
+
*
|
|
926
|
+
* The asker is resolved per call — run context first (`ctx.context.askUser`), then the factory's
|
|
927
|
+
* `askUser` — so one tool object shared across sessions can still reach the right user. With neither,
|
|
928
|
+
* the call returns `{ ok: false, error: "no_asker" }` at once rather than hanging until the timeout;
|
|
929
|
+
* that error means the host is mis-wired, not that the user declined.
|
|
930
|
+
*
|
|
931
|
+
* After `timeoutMs` (default 5 minutes) the result is `{ ok: false, error: "timeout" }` and
|
|
932
|
+
* `onAbandon` fires. Supply `onAbandon` whenever your asker holds a slot per thread: the promise the
|
|
933
|
+
* tool stopped awaiting is still pending on your side, so without the callback the UI keeps rendering
|
|
934
|
+
* a prompt nobody is waiting on and rejects every later question as already pending.
|
|
935
|
+
*
|
|
936
|
+
* Anything else the asker rejects with propagates out of the handler — only its own timeout is
|
|
937
|
+
* converted.
|
|
938
|
+
*/
|
|
773
939
|
declare function createQuestionTool(opts: QuestionToolOptions): QuestionTool;
|
|
774
940
|
|
|
775
941
|
/**
|
|
@@ -851,6 +1017,26 @@ interface CreateReadFileToolOptions {
|
|
|
851
1017
|
*/
|
|
852
1018
|
allowAbsolute?: boolean;
|
|
853
1019
|
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Build the `read_file` tool.
|
|
1022
|
+
*
|
|
1023
|
+
* Success is `{ ok: true, content, size }`. Every refusal is a `{ ok: false, error }` value rather
|
|
1024
|
+
* than a throw, so the model can correct itself: `not_found`, `forbidden_path` (the sensitive-file
|
|
1025
|
+
* blocklist), `path_traversal` (escapes `projectRoot`, or the backend refused it), `binary_file` (a
|
|
1026
|
+
* null byte in the first 8 KB) and `too_large` (over 5 MB — a fixed ceiling, not an option).
|
|
1027
|
+
*
|
|
1028
|
+
* `offset`/`limit` page by line and `lineNumbers` renders a `cat -n` view (`<n>\t<line>`). Both are
|
|
1029
|
+
* applied after the whole file has been read, so they bound what the model sees, not what is read
|
|
1030
|
+
* from disk; the 5 MB cap is what bounds the read.
|
|
1031
|
+
*
|
|
1032
|
+
* `allowAbsolute` widens the boundary to the entire filesystem for absolute paths, leaving only the
|
|
1033
|
+
* any-depth secret guard in front of it. It is for a trusted local agent; on a shared or
|
|
1034
|
+
* multi-tenant host pass `filesystem` instead and let the backend own the boundary.
|
|
1035
|
+
*
|
|
1036
|
+
* Pass one {@link ReadTracker} here and the same instance to {@link createWriteFileTool} to enable
|
|
1037
|
+
* read-before-write: this tool records the mtime it saw, and the write tool then refuses to
|
|
1038
|
+
* overwrite a file that was never read or has changed since.
|
|
1039
|
+
*/
|
|
854
1040
|
declare function createReadFileTool(opts: CreateReadFileToolOptions): CustomTool;
|
|
855
1041
|
|
|
856
1042
|
/**
|
|
@@ -895,10 +1081,12 @@ declare class ReasoningTools {
|
|
|
895
1081
|
* - `{ ok: false, error: 'path_traversal' | 'forbidden_path' | 'timeout' |
|
|
896
1082
|
* 'no_vitest' | 'unparseable_output' }`
|
|
897
1083
|
*
|
|
898
|
-
* Implementation note: invokes vitest via `npx --no-install vitest`. The
|
|
899
|
-
*
|
|
900
|
-
*
|
|
901
|
-
*
|
|
1084
|
+
* Implementation note: invokes vitest via `npx --no-install vitest`. The `--no-install` avoids the
|
|
1085
|
+
* agent triggering a multi-megabyte download mid-turn when vitest is missing. That case surfaces as
|
|
1086
|
+
* `no_vitest` (#347), recognised from npm's own complaint on stderr — `npx` itself starts, so the
|
|
1087
|
+
* spawn succeeds and only the text distinguishes a missing package from a run that produced no
|
|
1088
|
+
* parseable JSON. A spawn failure (`npx` not on PATH) is `no_vitest` too. If npm ever rewords its
|
|
1089
|
+
* message the case falls back to `unparseable_output`, with the real reason in the payload.
|
|
902
1090
|
*/
|
|
903
1091
|
|
|
904
1092
|
interface CreateRunVitestToolOptions {
|
|
@@ -911,12 +1099,30 @@ interface CreateRunVitestToolOptions {
|
|
|
911
1099
|
timeoutMs?: number;
|
|
912
1100
|
maxStdoutBytes?: number;
|
|
913
1101
|
}
|
|
1102
|
+
/**
|
|
1103
|
+
* The fields lifted from vitest's JSON report. All optional, because the object is the report's own
|
|
1104
|
+
* top level passed through unvalidated — a vitest version that renames a field yields `undefined`
|
|
1105
|
+
* here rather than an error, so treat a missing `success` as unknown, never as failed.
|
|
1106
|
+
*/
|
|
914
1107
|
interface VitestSummary {
|
|
915
1108
|
numTotalTests?: number;
|
|
916
1109
|
numPassedTests?: number;
|
|
917
1110
|
numFailedTests?: number;
|
|
918
1111
|
success?: boolean;
|
|
919
1112
|
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Build the `run_vitest` tool: run the project's suite and return the counts rather than the log.
|
|
1115
|
+
*
|
|
1116
|
+
* A FAILING suite is `{ ok: true, summary }` with `success: false`. `ok` reports only that vitest
|
|
1117
|
+
* ran; an agent that branches on `ok` reads a red suite as a green one. And the summary carries
|
|
1118
|
+
* counts alone — which test failed, and why, is not in the result, so chasing a failure means falling
|
|
1119
|
+
* back to `shell_exec`.
|
|
1120
|
+
*
|
|
1121
|
+
* Runs `npx --no-install vitest run --reporter=json`, so a project without vitest installed fails
|
|
1122
|
+
* instead of downloading it mid-turn. That failure arrives as `no_vitest` (#347), as does `npx` not
|
|
1123
|
+
* being on PATH. The remaining refusals are `path_traversal`, `forbidden_path` and `timeout`
|
|
1124
|
+
* (default 120s, process group killed).
|
|
1125
|
+
*/
|
|
920
1126
|
declare function createRunVitestTool(opts: CreateRunVitestToolOptions): CustomTool;
|
|
921
1127
|
|
|
922
1128
|
/**
|
|
@@ -970,6 +1176,29 @@ interface CreateSearchTextToolOptions {
|
|
|
970
1176
|
* sandbox). Forbidden dirs are still skipped. Default `false` ⇒ absolute scope rejected (unchanged). */
|
|
971
1177
|
allowAbsolute?: boolean;
|
|
972
1178
|
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Build the `search_text` tool: scan file CONTENTS across the tree. Use it when you know what the
|
|
1181
|
+
* code says, and `glob_files` when you know what the file is called.
|
|
1182
|
+
*
|
|
1183
|
+
* `regex` is fixed at construction, not chosen per call — it changes both the input schema and the
|
|
1184
|
+
* description the model sees, so one instance is either literal-substring or regex, never both.
|
|
1185
|
+
* Literal matching is case-sensitive and there is no case-insensitive mode. In regex mode the pattern
|
|
1186
|
+
* is compiled with no flags (an inline `(?i)` is a syntax error, not a modifier) and an invalid
|
|
1187
|
+
* pattern returns `{ ok: false, error: "invalid_regex" }` before any file is opened.
|
|
1188
|
+
*
|
|
1189
|
+
* Matches carry `{ file, line, preview }` with `line` 1-based and `preview` cut at 200 characters.
|
|
1190
|
+
* The walk stops at the first match past `maxMatches` (default 100), so on a truncated run
|
|
1191
|
+
* `totalMatches` is `maxMatches + 1` — it counts what was scanned, not how many matches exist. Files
|
|
1192
|
+
* over `maxFileSize` (default 1 MB), files with a null byte in their first 8 KB, and anything
|
|
1193
|
+
* unreadable are skipped in silence.
|
|
1194
|
+
*
|
|
1195
|
+
* `allowAbsolute` honours an absolute `path` scope. Be aware that the sensitive-path filter applied
|
|
1196
|
+
* during the walk inspects only the first segment of each entry's path relative to `projectRoot`,
|
|
1197
|
+
* plus its basename. Under an absolute scope every entry's relative path begins with `..`, so a
|
|
1198
|
+
* nested `.env` is neither skipped nor refused and its matching lines are returned.
|
|
1199
|
+
* {@link createReadFileTool} and {@link createListDirTool} carry an any-depth guard for exactly this
|
|
1200
|
+
* case; this tool does not.
|
|
1201
|
+
*/
|
|
973
1202
|
declare function createSearchTextTool(opts: CreateSearchTextToolOptions): CustomTool;
|
|
974
1203
|
|
|
975
1204
|
/**
|
|
@@ -1015,6 +1244,28 @@ interface CreateShellToolOptions {
|
|
|
1015
1244
|
*/
|
|
1016
1245
|
sandbox?: SandboxProvider;
|
|
1017
1246
|
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Build the `shell_exec` tool: run a command through `/bin/sh -c` with `projectRoot` as the working
|
|
1249
|
+
* directory.
|
|
1250
|
+
*
|
|
1251
|
+
* Reserve it for what has no dedicated tool — tests, git, package managers, build steps. Reading,
|
|
1252
|
+
* writing, editing and searching files each have a path-checked tool in this package, and routing
|
|
1253
|
+
* them through a shell gives up every one of those checks.
|
|
1254
|
+
*
|
|
1255
|
+
* The model's `timeout_ms` and `defaultTimeoutMs` (30s) are both clamped to a 5-minute ceiling that
|
|
1256
|
+
* is not configurable. On expiry the process group is killed and the result is
|
|
1257
|
+
* `{ ok: false, error: "timeout" }`. stdout and stderr are capped at 5 MB each and cut silently at
|
|
1258
|
+
* the cap — there is no truncation flag, so a command producing more looks like one that produced
|
|
1259
|
+
* exactly 5 MB.
|
|
1260
|
+
*
|
|
1261
|
+
* A non-zero exit is `{ ok: true, ..., exit_code }`, not an error: `ok` reports that the command ran,
|
|
1262
|
+
* `exit_code` reports whether it worked.
|
|
1263
|
+
*
|
|
1264
|
+
* The catastrophic-command screen runs before either execution path and refuses on a heuristic match
|
|
1265
|
+
* over the command string. It is a guardrail, not a sandbox, and `allowCatastrophic: true` removes it
|
|
1266
|
+
* outright. For real confinement pass `sandbox`, which routes execution through the backend — the
|
|
1267
|
+
* 5 MB output caps belong to the local path and do not apply there.
|
|
1268
|
+
*/
|
|
1018
1269
|
declare function createShellTool(opts: CreateShellToolOptions): CustomTool;
|
|
1019
1270
|
|
|
1020
1271
|
/**
|
|
@@ -1042,6 +1293,13 @@ interface TodoItem {
|
|
|
1042
1293
|
createdAt: number;
|
|
1043
1294
|
completedAt?: number;
|
|
1044
1295
|
}
|
|
1296
|
+
/**
|
|
1297
|
+
* The `todolist` tool object. It is hand-built rather than produced by `Tool.create`, so
|
|
1298
|
+
* `inputSchema` is a plain JSON Schema value and `handler` is synchronous.
|
|
1299
|
+
*
|
|
1300
|
+
* `getItems` is a test seam onto the same session state the handler mutates, reachable without going
|
|
1301
|
+
* through an action.
|
|
1302
|
+
*/
|
|
1045
1303
|
interface TodolistTool {
|
|
1046
1304
|
name: string;
|
|
1047
1305
|
description: string;
|
|
@@ -1073,6 +1331,19 @@ type TodoInput = {
|
|
|
1073
1331
|
} | {
|
|
1074
1332
|
action: "clear_completed";
|
|
1075
1333
|
};
|
|
1334
|
+
/**
|
|
1335
|
+
* Build the `todolist` tool: an in-memory checklist the agent keeps across the turns of one session.
|
|
1336
|
+
*
|
|
1337
|
+
* State lives in the closure. It is never written anywhere and dies with the process, so this is a
|
|
1338
|
+
* working memo for the model, not a task store. One tool object can serve many sessions — lists are
|
|
1339
|
+
* keyed by `ctx.threadId`, and every call arriving without one shares a single default list, which
|
|
1340
|
+
* means a multi-session host that forgets to thread the id merges all its users' tasks together.
|
|
1341
|
+
*
|
|
1342
|
+
* Ids are `todo-1`, `todo-2`, … per session and are never reused, so a removed item's id stays gone.
|
|
1343
|
+
* Each successful action returns the whole list twice: `items` structured for a UI, `items_summary`
|
|
1344
|
+
* formatted for the model. Failures are `missing_title`, `missing_id`, `not_found` and
|
|
1345
|
+
* `invalid_action`.
|
|
1346
|
+
*/
|
|
1076
1347
|
declare function createTodolistTool(): TodolistTool;
|
|
1077
1348
|
|
|
1078
1349
|
/**
|
|
@@ -1129,6 +1400,10 @@ declare function todoItemsToPlanNodes(items: readonly TodoItem[]): PlanNode[];
|
|
|
1129
1400
|
*/
|
|
1130
1401
|
/** How the middle is dropped when output exceeds the budget. */
|
|
1131
1402
|
type TruncationMode = "head" | "head-tail";
|
|
1403
|
+
/**
|
|
1404
|
+
* Options for {@link truncateOutput}. Every field has a default, so `truncateOutput(text)` is a
|
|
1405
|
+
* complete call.
|
|
1406
|
+
*/
|
|
1132
1407
|
interface TruncationOptions {
|
|
1133
1408
|
/** Maximum output size in bytes before truncation. Default: 30_000. */
|
|
1134
1409
|
maxBytes?: number;
|
|
@@ -1141,6 +1416,13 @@ interface TruncationOptions {
|
|
|
1141
1416
|
*/
|
|
1142
1417
|
mode?: TruncationMode;
|
|
1143
1418
|
}
|
|
1419
|
+
/**
|
|
1420
|
+
* What {@link truncateOutput} returns.
|
|
1421
|
+
*
|
|
1422
|
+
* `truncated` is the field to branch on. On the false branch `content` is the input unchanged and
|
|
1423
|
+
* `overflowPath` is absent; reading `overflowPath` without checking `truncated` first is how a
|
|
1424
|
+
* consumer ends up joining `undefined` into a path.
|
|
1425
|
+
*/
|
|
1144
1426
|
interface TruncationResult {
|
|
1145
1427
|
/** The (possibly truncated) content. */
|
|
1146
1428
|
content: string;
|
|
@@ -1155,6 +1437,29 @@ interface TruncationResult {
|
|
|
1155
1437
|
/** Path to the full output file, present only when truncated. */
|
|
1156
1438
|
overflowPath?: string;
|
|
1157
1439
|
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Bound a block of tool output to `maxBytes`, spilling the full text to a file when it does not fit.
|
|
1442
|
+
*
|
|
1443
|
+
* Nothing is written and nothing is copied when the output already fits — the comparison is
|
|
1444
|
+
* `originalBytes <= maxBytes`, so a payload exactly at the limit passes through untouched. Above the
|
|
1445
|
+
* limit the WHOLE original is written under `outputDir` (created recursively) and the returned
|
|
1446
|
+
* `content` is the cut text plus a trailer naming that file. The trailer is appended after the cut,
|
|
1447
|
+
* so the returned string is longer than `maxBytes`: the budget bounds what is copied out of the
|
|
1448
|
+
* input, not what comes back.
|
|
1449
|
+
*
|
|
1450
|
+
* Choose the mode by where the information sits. `"head"` keeps the opening, which suits a document
|
|
1451
|
+
* or a listing. `"head-tail"` splits the budget between the start and the end, which is what command
|
|
1452
|
+
* output needs — the exit status, the failing assertion and the summary all live in the last lines,
|
|
1453
|
+
* and `"head"` discards exactly those.
|
|
1454
|
+
*
|
|
1455
|
+
* The head is cut on a UTF-8 code-point boundary and never ends in a replacement character. The tail
|
|
1456
|
+
* of `"head-tail"` starts mid-buffer, so it can open with one U+FFFD where a multi-byte character was
|
|
1457
|
+
* split; the omitted-byte count in the separator is computed from `maxBytes` and is approximate for
|
|
1458
|
+
* the same reason.
|
|
1459
|
+
*
|
|
1460
|
+
* Touches the filesystem on the truncating path — a `mkdirSync`/`writeFileSync` failure propagates
|
|
1461
|
+
* rather than degrading to an untruncated return.
|
|
1462
|
+
*/
|
|
1158
1463
|
declare function truncateOutput(output: string, opts?: TruncationOptions): TruncationResult;
|
|
1159
1464
|
|
|
1160
1465
|
/**
|
|
@@ -1173,6 +1478,65 @@ declare function truncateOutput(output: string, opts?: TruncationOptions): Trunc
|
|
|
1173
1478
|
|
|
1174
1479
|
declare function createUpdatePlanTool(): CustomTool;
|
|
1175
1480
|
|
|
1481
|
+
/**
|
|
1482
|
+
* `view_image` — let the agent LOOK at an image in the project.
|
|
1483
|
+
*
|
|
1484
|
+
* ## Why this is a built-in
|
|
1485
|
+
*
|
|
1486
|
+
* It was the one tool a consumer had to write from scratch (89 LOC), and its shape — a `handler`
|
|
1487
|
+
* returning a structured result plus `toModelOutput` shaping it into an `ImageBlock` — is the
|
|
1488
|
+
* canonical multimodal shape the SDK already defines (SE17). Every product that wants an agent to look at a
|
|
1489
|
+
* screenshot rewrites the same base64 + media-type + confinement logic.
|
|
1490
|
+
*
|
|
1491
|
+
* The confinement is the part that is easy to get wrong, and the reason this belongs in a reviewed
|
|
1492
|
+
* built-in rather than in each product: **an image reader that honours any path is a file
|
|
1493
|
+
* exfiltration primitive with a friendly name.** `/etc/passwd` renamed to `.png` is not a
|
|
1494
|
+
* hypothetical — it is one prompt away.
|
|
1495
|
+
*
|
|
1496
|
+
* ## The two channels
|
|
1497
|
+
*
|
|
1498
|
+
* This built-in uses the SE17 split. The handler returns the envelope as a JSON **string** — which
|
|
1499
|
+
* is what `Tool.create` types it to return — and `toModelOutput` turns that string into an
|
|
1500
|
+
* `ImageBlock` for the model, while `defineTool` routes the full value to `onToolEnd` through a
|
|
1501
|
+
* resolver under `TOOL_SPLIT_RESOLVER`.
|
|
1502
|
+
*
|
|
1503
|
+
* So `tool.handler(...)` yields image blocks on success and the JSON string on failure: the factory
|
|
1504
|
+
* has already applied the shaping. There is no `tool.toModelOutput` left to call, and returning the
|
|
1505
|
+
* envelope unshaped would send the model a base64 blob as TEXT — something it cannot look at, which
|
|
1506
|
+
* is the failure this tool exists to avoid.
|
|
1507
|
+
*
|
|
1508
|
+
* ## Result shape (the APP channel)
|
|
1509
|
+
*
|
|
1510
|
+
* - `{ ok: true, path, media_type, bytes, data }`
|
|
1511
|
+
* - `{ ok: false, error: "path_traversal" | "not_found" | "unsupported_image_type" | "image_too_large", … }`
|
|
1512
|
+
*/
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* Default ceiling: 5 MB on disk.
|
|
1516
|
+
*
|
|
1517
|
+
* Base64 inflates by 4/3 and the result lands directly in the model's context. A 20 MB screenshot is
|
|
1518
|
+
* not a slow request — it is a failed turn, and an expensive one.
|
|
1519
|
+
*/
|
|
1520
|
+
declare const DEFAULT_MAX_IMAGE_BYTES: number;
|
|
1521
|
+
/**
|
|
1522
|
+
* Options for {@link createViewImageTool}. `maxBytes` is measured on disk, before base64 inflates the
|
|
1523
|
+
* payload by roughly a third on its way into the model's context — so the real context cost of a file
|
|
1524
|
+
* at the limit is about 6.7 MB of text, not 5 MB.
|
|
1525
|
+
*/
|
|
1526
|
+
interface CreateViewImageToolOptions {
|
|
1527
|
+
/** Root the tool reads from. Every path is resolved inside it. */
|
|
1528
|
+
projectRoot: string;
|
|
1529
|
+
/** Name exposed to the model. Omitted ⇒ `view_image`. The name is a contract: it is the approval
|
|
1530
|
+
* key, what the model sees, and what telemetry records. */
|
|
1531
|
+
name?: string;
|
|
1532
|
+
/** Description exposed to the model. Omitted ⇒ the literal below. */
|
|
1533
|
+
description?: string;
|
|
1534
|
+
/** Ceiling in bytes, measured on disk. Omitted ⇒ {@link DEFAULT_MAX_IMAGE_BYTES}. */
|
|
1535
|
+
maxBytes?: number;
|
|
1536
|
+
}
|
|
1537
|
+
/** Read an image from the project so the model can look at it. */
|
|
1538
|
+
declare function createViewImageTool(options: CreateViewImageToolOptions): CustomTool;
|
|
1539
|
+
|
|
1176
1540
|
/**
|
|
1177
1541
|
* `web_fetch` — built-in tool for coding agents.
|
|
1178
1542
|
*
|
|
@@ -1210,6 +1574,26 @@ interface CreateWebFetchToolOptions {
|
|
|
1210
1574
|
/** DNS resolver (injectable for tests — drive the SSRF path with no real DNS). */
|
|
1211
1575
|
lookup?: ScreenedFetchOptions["lookup"];
|
|
1212
1576
|
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Build the `web_fetch` tool: retrieve one URL over HTTP or HTTPS and hand the model the body as
|
|
1579
|
+
* text. Every option is optional — `createWebFetchTool()` gives an SSRF-guarded fetch with a
|
|
1580
|
+
* 30-second timeout.
|
|
1581
|
+
*
|
|
1582
|
+
* The body is decoded as UTF-8 whatever the content type says, so a PDF or an image comes back as
|
|
1583
|
+
* mojibake rather than an error: check `content_type` before trusting `content`. The 1 MB body cap is
|
|
1584
|
+
* fixed rather than an option, and is enforced twice — against `content-length` when the server sends
|
|
1585
|
+
* one, then against the bytes actually downloaded.
|
|
1586
|
+
*
|
|
1587
|
+
* The SSRF guard screens the resolved ADDRESS rather than the hostname, and screens every redirect
|
|
1588
|
+
* hop again. `maxRedirects: 0` refuses any 3xx outright with `redirect_blocked`, which is the strict
|
|
1589
|
+
* setting for a URL the model chose itself. `allowPrivateHosts: true` disables the guard entirely,
|
|
1590
|
+
* and with a model-supplied URL that is enough to reach a cloud metadata endpoint — it is for local
|
|
1591
|
+
* development against your own services, not for production.
|
|
1592
|
+
*
|
|
1593
|
+
* Refusals: `invalid_url` (unparseable, or a scheme other than http/https), `ssrf_blocked`,
|
|
1594
|
+
* `redirect_blocked`, `too_large`, `timeout`, `fetch_failed`. A 4xx or 5xx is NOT a refusal — it
|
|
1595
|
+
* returns `ok: true` with the body and `status_code`, so check the status.
|
|
1596
|
+
*/
|
|
1213
1597
|
declare function createWebFetchTool(opts?: CreateWebFetchToolOptions): CustomTool;
|
|
1214
1598
|
|
|
1215
1599
|
/**
|
|
@@ -1228,7 +1612,22 @@ interface WebSearchResult {
|
|
|
1228
1612
|
url: string;
|
|
1229
1613
|
snippet: string;
|
|
1230
1614
|
}
|
|
1615
|
+
/**
|
|
1616
|
+
* The search provider {@link createWebSearchTool} calls.
|
|
1617
|
+
*
|
|
1618
|
+
* `maxResults` is what the caller asked for, not a limit you must enforce — the tool slices the array
|
|
1619
|
+
* itself — but returning far more than asked wastes the round trip that produced them.
|
|
1620
|
+
*
|
|
1621
|
+
* Reject the promise to signal a failed search: the tool turns a rejection into
|
|
1622
|
+
* `{ ok: false, error: "search_failed" }` and never lets it escape into the agent turn. Resolving
|
|
1623
|
+
* with `[]` says something different — that the search ran and found nothing.
|
|
1624
|
+
*/
|
|
1231
1625
|
type WebSearchCallback = (query: string, maxResults: number) => Promise<WebSearchResult[]>;
|
|
1626
|
+
/**
|
|
1627
|
+
* Options for {@link createWebSearchTool}. `search` is required: the tool ships no provider of its
|
|
1628
|
+
* own, and the two adapters in this package — {@link createBraveWebSearchAdapter} and
|
|
1629
|
+
* {@link createGenericHttpSearchAdapter} — exist to fill it.
|
|
1630
|
+
*/
|
|
1232
1631
|
interface CreateWebSearchToolOptions {
|
|
1233
1632
|
/** M76 — name exposed to the model. Omitted => today's literal (additive). The name is a contract:
|
|
1234
1633
|
* the approval key, what the model sees and what telemetry records. */
|
|
@@ -1240,6 +1639,19 @@ interface CreateWebSearchToolOptions {
|
|
|
1240
1639
|
/** Default max results if not specified by the LLM. */
|
|
1241
1640
|
defaultMaxResults?: number;
|
|
1242
1641
|
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Build the `web_search` tool over a caller-supplied provider.
|
|
1644
|
+
*
|
|
1645
|
+
* The tool holds no API key and talks to no service; everything network-facing lives in the `search`
|
|
1646
|
+
* callback, which is why one tool serves Brave, a self-hosted endpoint, or a fixture in a test. Use
|
|
1647
|
+
* it to find pages and `web_fetch` to read one — results carry a snippet, not a body.
|
|
1648
|
+
*
|
|
1649
|
+
* The model's `max_results` (1..20) wins over `defaultMaxResults`, and the list is sliced to it even
|
|
1650
|
+
* when the provider returns more. A rejecting provider yields `{ ok: false, error: "search_failed" }`
|
|
1651
|
+
* with its message attached; a provider resolving `[]` yields `{ ok: true, results: [] }`. The two
|
|
1652
|
+
* are different signals, so an adapter that swallows its own failures — as
|
|
1653
|
+
* {@link createGenericHttpSearchAdapter} deliberately does — reports the first as the second.
|
|
1654
|
+
*/
|
|
1243
1655
|
declare function createWebSearchTool(opts: CreateWebSearchToolOptions): CustomTool;
|
|
1244
1656
|
|
|
1245
1657
|
/**
|
|
@@ -1255,6 +1667,11 @@ declare function createWebSearchTool(opts: CreateWebSearchToolOptions): CustomTo
|
|
|
1255
1667
|
*/
|
|
1256
1668
|
|
|
1257
1669
|
type FetchLike$1 = (url: string, init?: RequestInit) => Promise<Response>;
|
|
1670
|
+
/**
|
|
1671
|
+
* Options for {@link createBraveWebSearchAdapter}. `apiKey` falls back to `BRAVE_API_KEY` and
|
|
1672
|
+
* `endpoint` to Brave's public search URL, both read once at construction, so a process that exports
|
|
1673
|
+
* the env var can call the factory with no arguments.
|
|
1674
|
+
*/
|
|
1258
1675
|
interface CreateBraveWebSearchAdapterOptions {
|
|
1259
1676
|
/** Brave API key. Defaults to `process.env.BRAVE_API_KEY`. */
|
|
1260
1677
|
apiKey?: string;
|
|
@@ -1291,6 +1708,12 @@ declare function createBraveWebSearchAdapter(opts?: CreateBraveWebSearchAdapterO
|
|
|
1291
1708
|
*/
|
|
1292
1709
|
|
|
1293
1710
|
type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
|
|
1711
|
+
/**
|
|
1712
|
+
* Options for {@link createGenericHttpSearchAdapter}. Unlike the Brave adapter, an absent `apiKey` or
|
|
1713
|
+
* `endpoint` is not an error — the callback is still built and simply returns no results — so if you
|
|
1714
|
+
* need to know whether search is actually configured, check both here rather than waiting for an
|
|
1715
|
+
* empty result set to tell you.
|
|
1716
|
+
*/
|
|
1294
1717
|
interface CreateGenericHttpSearchAdapterOptions {
|
|
1295
1718
|
/** Bearer token. Defaults to `process.env.THEOKIT_SEARCH_API_KEY`. */
|
|
1296
1719
|
apiKey?: string;
|
|
@@ -1323,6 +1746,10 @@ type WriteToolContext = {
|
|
|
1323
1746
|
signal?: AbortSignal;
|
|
1324
1747
|
context?: unknown;
|
|
1325
1748
|
};
|
|
1749
|
+
/**
|
|
1750
|
+
* Options for {@link createWriteFileTool}. `requireReadBeforeWrite` and `readTracker` are a pair —
|
|
1751
|
+
* the flag without the tracker is refused at construction rather than quietly disabling the guard.
|
|
1752
|
+
*/
|
|
1326
1753
|
interface CreateWriteFileToolOptions {
|
|
1327
1754
|
/** M76 — name exposed to the model. Omitted => today's literal (additive). The name is a contract:
|
|
1328
1755
|
* the approval key, what the model sees and what telemetry records. */
|
|
@@ -1349,6 +1776,22 @@ interface CreateWriteFileToolOptions {
|
|
|
1349
1776
|
/** SE32 — the per-run tracker populated by the paired `read_file` tool. */
|
|
1350
1777
|
readTracker?: ReadTracker;
|
|
1351
1778
|
}
|
|
1779
|
+
/**
|
|
1780
|
+
* Build the `write_file` tool. It OVERWRITES: reach for {@link createEditFileTool} when the file
|
|
1781
|
+
* exists and only part of it changes.
|
|
1782
|
+
*
|
|
1783
|
+
* Parent directories are created recursively. Refusals arrive as `{ ok: false, error }`:
|
|
1784
|
+
* `forbidden_path`, `path_traversal`, `binary_file`, plus `read_required` / `stale_file` when
|
|
1785
|
+
* read-before-write is on, plus `read_only` / `write_failed` on the backend path.
|
|
1786
|
+
*
|
|
1787
|
+
* The `binary_file` probe (a null byte in the first 8 KB of the file being replaced) runs on the
|
|
1788
|
+
* local path only. A `filesystem` backend has no equivalent, so overwriting a binary file through a
|
|
1789
|
+
* backend is not refused.
|
|
1790
|
+
*
|
|
1791
|
+
* Throws at construction — not at call time — when `requireReadBeforeWrite` is set without a
|
|
1792
|
+
* `readTracker`. A guard that silently does nothing is worse than no guard, and construction is the
|
|
1793
|
+
* only place the pairing can be checked.
|
|
1794
|
+
*/
|
|
1352
1795
|
declare function createWriteFileTool(opts: CreateWriteFileToolOptions): CustomTool;
|
|
1353
1796
|
|
|
1354
|
-
export { CatastrophicCommandError, type CommandPolicy, ContextMatchError, type ContextMatchReason, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateCurrentTimeToolOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGitStatusToolOptions, type CreateGlobToolOptions, type CreateInteractiveShellToolOptions, type CreateListDirToolOptions, type CreateReadFileToolOptions, type CreateRunVitestToolOptions, type CreateSearchTextToolOptions, type CreateShellToolOptions, type CreateWebFetchToolOptions, type CreateWebSearchToolOptions, type CreateWriteFileToolOptions, DEFAULT_TOOL_GUIDANCE, type EnvContextOptions, type PlanModeTool, type PlanModeToolOptions, type PlanModeToolWithStore, type PlanNode, type QuestionTool, type QuestionToolOptions, ReadTracker, ReasoningTools, RedirectBlockedError, type RepoMapOptions, type ResolveAndScreenOptions, type ScreenedFetchOptions, type SessionArtifactStore, type SessionArtifactStoreOptions, SsrfBlockedError, type TodoItem, type TodolistTool, type ToolGuidanceMap, type TruncationMode, type TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createCurrentTimeTool, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGitStatusTool, createGlobTool, createInteractiveShellTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createUpdatePlanTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, createWriteStdinTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, replaceUnique, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withName, withShellExitGuidance, withToolResultGuidance };
|
|
1797
|
+
export { CatastrophicCommandError, type CommandPolicy, ContextMatchError, type ContextMatchReason, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateCurrentTimeToolOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGitStatusToolOptions, type CreateGlobToolOptions, type CreateInteractiveShellToolOptions, type CreateListDirToolOptions, type CreateReadFileToolOptions, type CreateRunVitestToolOptions, type CreateSearchTextToolOptions, type CreateShellToolOptions, type CreateViewImageToolOptions, type CreateWebFetchToolOptions, type CreateWebSearchToolOptions, type CreateWriteFileToolOptions, DEFAULT_MAX_IMAGE_BYTES, DEFAULT_TOOL_GUIDANCE, type EnvContextOptions, type PlanModeTool, type PlanModeToolOptions, type PlanModeToolWithStore, type PlanNode, type QuestionTool, type QuestionToolOptions, ReadTracker, ReasoningTools, RedirectBlockedError, type RepoMapOptions, type ResolveAndScreenOptions, type ScreenedFetchOptions, type SessionArtifactStore, type SessionArtifactStoreOptions, SsrfBlockedError, type TodoItem, type TodolistTool, type ToolGuidanceMap, type TruncationMode, type TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createCurrentTimeTool, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGitStatusTool, createGlobTool, createInteractiveShellTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createUpdatePlanTool, createViewImageTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, createWriteStdinTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, replaceUnique, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withName, withShellExitGuidance, withToolResultGuidance };
|