@theokit/sdk-tools 0.1.0

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.
@@ -0,0 +1,430 @@
1
+ import { CustomTool } from '@theokit/sdk';
2
+
3
+ /**
4
+ * `apply_patch` — built-in tool for coding agents.
5
+ *
6
+ * Parses a unified diff string and applies it to the project files.
7
+ * Creates `.bak` backups before modifying each file.
8
+ *
9
+ * Return shape (always a JSON string):
10
+ * - `{ ok: true, files_patched: string[] }`
11
+ * - `{ ok: false, error: 'parse_error' | 'path_traversal' |
12
+ * 'forbidden_path' | 'patch_failed' }`
13
+ */
14
+
15
+ interface CreateApplyPatchToolOptions {
16
+ /** Absolute path to the project root. */
17
+ projectRoot: string;
18
+ }
19
+ declare function createApplyPatchTool(opts: CreateApplyPatchToolOptions): CustomTool;
20
+
21
+ /**
22
+ * `edit_file` — built-in tool for coding agents.
23
+ *
24
+ * Replaces a string in a project-relative file. Tries exact match first,
25
+ * then whitespace-normalized match. Creates a `.bak` backup before editing.
26
+ *
27
+ * Return shape (always a JSON string):
28
+ * - `{ ok: true, replacements: 1 }` on success
29
+ * - `{ ok: false, error: 'no_match' | 'not_found' | 'path_traversal' |
30
+ * 'forbidden_path' }` on refusal
31
+ */
32
+
33
+ interface CreateEditFileToolOptions {
34
+ /** Absolute path to the project root. Every edit is gated against this boundary. */
35
+ projectRoot: string;
36
+ }
37
+ declare function createEditFileTool(opts: CreateEditFileToolOptions): CustomTool;
38
+
39
+ /**
40
+ * `formatter` — output formatting utilities for tool results.
41
+ *
42
+ * Wraps code, diffs, file lists, errors, and truncated output references.
43
+ */
44
+ /**
45
+ * Wrap code in a fenced code block with language tag.
46
+ */
47
+ declare function formatCode(language: string, code: string): string;
48
+ /**
49
+ * Format a unified diff with +/- prefixes preserved.
50
+ */
51
+ declare function formatDiff(diff: string): string;
52
+ /**
53
+ * Format a list of file paths as a bulleted markdown list.
54
+ */
55
+ declare function formatFileList(files: string[]): string;
56
+ /**
57
+ * Format an error message as a markdown block.
58
+ */
59
+ declare function formatError(message: string, code?: string): string;
60
+
61
+ /**
62
+ * `git_diff` — built-in tool for coding agents.
63
+ *
64
+ * Returns the unified diff of the working tree (or staged changes when
65
+ * `cached=true`). Implemented as a thin `git diff` subprocess wrapper
66
+ * with a couple of hard limits:
67
+ *
68
+ * - 30s wall clock timeout (kills the process group on expiry)
69
+ * - 5 MB stdout cap (truncate + flag `truncated=true`)
70
+ * - Path scope validated through `safePathJoin` + `assertNoSymlinkEscape`
71
+ *
72
+ * Result shape (always a JSON string):
73
+ * - `{ ok: true, diff: string, truncated?: boolean }`
74
+ * - `{ ok: false, error: 'not_a_repo' | 'path_traversal' | 'timeout' }`
75
+ */
76
+
77
+ interface CreateGitDiffToolOptions {
78
+ projectRoot: string;
79
+ timeoutMs?: number;
80
+ maxStdoutBytes?: number;
81
+ }
82
+ declare function createGitDiffTool(opts: CreateGitDiffToolOptions): CustomTool;
83
+
84
+ /**
85
+ * `glob_files` — built-in tool for coding agents.
86
+ *
87
+ * Lists project files matching a glob pattern. Excludes node_modules,
88
+ * .git, and dist by default. Returns relative paths.
89
+ *
90
+ * Return shape (always a JSON string):
91
+ * - `{ ok: true, files: string[] }`
92
+ * - `{ ok: false, error: 'path_traversal' | 'no_matches' }`
93
+ */
94
+
95
+ interface CreateGlobToolOptions {
96
+ /** Absolute path to the project root. */
97
+ projectRoot: string;
98
+ }
99
+ declare function createGlobTool(opts: CreateGlobToolOptions): CustomTool;
100
+
101
+ /**
102
+ * `list_dir` — built-in tool for coding agents.
103
+ *
104
+ * Returns the direct entries of a project-relative directory. Hardened
105
+ * against the same four bug families as `read_file` plus the
106
+ * **EC-6 unbounded output** failure mode: in a project with 10k files,
107
+ * a naive listing returns a 5 MB JSON payload that freezes the browser
108
+ * and (more importantly) blows past the LLM context window.
109
+ *
110
+ * Defaults:
111
+ * - `max = 500` entries (override via factory option `{ max }`)
112
+ * - Result includes `{ truncated: boolean, totalCount: number }` so the
113
+ * agent can decide whether to drill deeper or call `search_text` instead
114
+ *
115
+ * Result shape (always a JSON string):
116
+ * - `{ ok: true, entries: Array<{ name, type }>, truncated, totalCount }`
117
+ * - `{ ok: false, error: 'path_traversal' | 'forbidden_path' | 'not_found' }`
118
+ */
119
+
120
+ interface CreateListDirToolOptions {
121
+ /** Absolute path to the project root. Every listing is gated against this boundary. */
122
+ projectRoot: string;
123
+ /** Maximum number of entries returned per call. Default 500. */
124
+ max?: number;
125
+ }
126
+ declare function createListDirTool(opts: CreateListDirToolOptions): CustomTool;
127
+
128
+ /**
129
+ * `plan_mode` — tool for toggling between normal and planning mode.
130
+ *
131
+ * In plan mode, the agent focuses on outlining steps rather than executing them.
132
+ *
133
+ * Return shape (always a JSON string):
134
+ * - `{ ok: true, mode: string, message: string }`
135
+ * - `{ ok: false, error: "invalid_action" }`
136
+ */
137
+ type Mode = "normal" | "plan";
138
+ interface PlanModeTool {
139
+ name: string;
140
+ description: string;
141
+ inputSchema: unknown;
142
+ handler: (input: {
143
+ action: string;
144
+ }) => string;
145
+ /** Expose current mode for testing. */
146
+ currentMode: () => Mode;
147
+ }
148
+ declare function createPlanModeTool(): PlanModeTool;
149
+
150
+ /**
151
+ * `question` — interactive tool that asks the user a question and waits for a response.
152
+ *
153
+ * Return shape (always a JSON string):
154
+ * - `{ ok: true, answer: string }`
155
+ * - `{ ok: false, error: "timeout" }`
156
+ */
157
+ interface QuestionToolOptions {
158
+ /** Callback that presents a question to the user and resolves with their answer. */
159
+ askUser: (question: string) => Promise<string>;
160
+ /** Maximum time to wait for user response in ms. Default: 300_000 (5 min). */
161
+ timeoutMs?: number;
162
+ }
163
+ interface QuestionTool {
164
+ name: string;
165
+ description: string;
166
+ inputSchema: unknown;
167
+ handler: (input: {
168
+ question: string;
169
+ }) => Promise<string>;
170
+ }
171
+ declare function createQuestionTool(opts: QuestionToolOptions): QuestionTool;
172
+
173
+ /**
174
+ * `read_file` — built-in tool for coding agents.
175
+ *
176
+ * Reads a project-relative file as UTF-8 and returns its contents.
177
+ * Hardened against the four bug families a coding agent normally hits:
178
+ *
179
+ * 1. **Path traversal** — `safePathJoin` rejects literal `..`,
180
+ * normalised escape, absolute segments, null-byte injection.
181
+ * 2. **Symlink escape** — `assertNoSymlinkEscape` follows the full
182
+ * chain (and any intermediate symlinks; defence-in-depth fix v1.x).
183
+ * 3. **Sensitive files** — `isForbiddenPath` blocks `.env*` (except
184
+ * `.env.example`), `.git/`, `node_modules/`, `.theo/`, lock files.
185
+ * 4. **Binary files** — null-byte check on the first 8 KB. PNG/JPEG/
186
+ * compiled binaries return a structured `binary_file` error
187
+ * instead of garbled UTF-8 (EC-5 from the TheoKit Studio review).
188
+ *
189
+ * Return shape (always a JSON string the LLM consumes):
190
+ * - `{ ok: true, content: string }` on success
191
+ * - `{ ok: false, error: 'path_traversal' | 'forbidden_path' |
192
+ * 'binary_file' | 'not_found' | 'too_large' }` on refusal
193
+ *
194
+ * The handler intentionally never throws on a "user mistake"
195
+ * (traversal / forbidden / not found). Throwing reserved for SDK-side
196
+ * mistakes that should crash the agent loop (input parse errors).
197
+ */
198
+
199
+ interface CreateReadFileToolOptions {
200
+ /** Absolute path to the project root. Every read is gated against this boundary. */
201
+ projectRoot: string;
202
+ }
203
+ declare function createReadFileTool(opts: CreateReadFileToolOptions): CustomTool;
204
+
205
+ /**
206
+ * `run_vitest` — built-in tool for coding agents.
207
+ *
208
+ * Runs vitest against an optional file/pattern scope and returns the
209
+ * parsed JSON report. Hardened against the same subprocess failure
210
+ * modes as `git_diff`:
211
+ *
212
+ * - 120s wall clock timeout (vitest can be slow on first run)
213
+ * - Process group kill on timeout (EC-7 — defeats vitest workers as
214
+ * grandchildren of the spawned shell)
215
+ * - **EC-12**: vitest stdout may contain deprecation warnings BEFORE
216
+ * the JSON payload. The parser scans line-by-line and extracts the
217
+ * LAST valid JSON object, not the first.
218
+ *
219
+ * Result shape (always a JSON string):
220
+ * - `{ ok: true, summary: { numTotalTests, numPassedTests, numFailedTests, success } }`
221
+ * - `{ ok: false, error: 'path_traversal' | 'forbidden_path' | 'timeout' |
222
+ * 'no_vitest' | 'unparseable_output' }`
223
+ *
224
+ * Implementation note: invokes vitest via `npx --no-install vitest`. The
225
+ * `--no-install` avoids the agent triggering a multi-megabyte download
226
+ * mid-turn if vitest is missing — the tool fails cleanly with
227
+ * `no_vitest` instead.
228
+ */
229
+
230
+ interface CreateRunVitestToolOptions {
231
+ projectRoot: string;
232
+ timeoutMs?: number;
233
+ maxStdoutBytes?: number;
234
+ }
235
+ interface VitestSummary {
236
+ numTotalTests?: number;
237
+ numPassedTests?: number;
238
+ numFailedTests?: number;
239
+ success?: boolean;
240
+ }
241
+ declare function createRunVitestTool(opts: CreateRunVitestToolOptions): CustomTool;
242
+
243
+ /**
244
+ * `search_text` — built-in tool for coding agents.
245
+ *
246
+ * Literal text search across the project tree (recursive). Sensible
247
+ * defaults:
248
+ *
249
+ * - Skips forbidden directories (`.env`, `.git/`, `node_modules/`,
250
+ * `.theo/`) so the agent never wastes context on dependency soup
251
+ * or VCS internals.
252
+ * - Skips files larger than `maxFileSize` (default 1 MB) and binary
253
+ * files (null-byte detection on first 8 KB) so a megabyte of
254
+ * minified JS never blows up the result.
255
+ * - Caps total matches at `maxMatches` (default 100) — the agent
256
+ * should refine the query if it gets close to the cap.
257
+ *
258
+ * Result shape (always a JSON string):
259
+ * - `{ ok: true, matches: Array<{ file, line, preview }>, truncated, totalMatches }`
260
+ * - `{ ok: false, error: 'path_traversal' | 'forbidden_path' | 'not_found' }`
261
+ *
262
+ * Implementation note: this is a plain JS recursive walk. For very
263
+ * large repos a future iteration can shell out to `rg` (ripgrep) when
264
+ * present, but the JS path stays as the dependency-free fallback.
265
+ */
266
+
267
+ interface CreateSearchTextToolOptions {
268
+ projectRoot: string;
269
+ /** Cap on total matches returned. Default 100. */
270
+ maxMatches?: number;
271
+ /** Skip files larger than this (bytes). Default 1 MB. */
272
+ maxFileSize?: number;
273
+ }
274
+ declare function createSearchTextTool(opts: CreateSearchTextToolOptions): CustomTool;
275
+
276
+ /**
277
+ * `shell_exec` — built-in tool for coding agents.
278
+ *
279
+ * Executes a shell command via `/bin/sh -c` with a configurable timeout
280
+ * and output size cap.
281
+ *
282
+ * Return shape (always a JSON string):
283
+ * - `{ ok: true, stdout, stderr, exit_code }`
284
+ * - `{ ok: false, error: 'timeout' | 'exec_failed' }`
285
+ */
286
+
287
+ interface CreateShellToolOptions {
288
+ /** Absolute path to the project root. Commands execute in this cwd. */
289
+ projectRoot: string;
290
+ /** Default timeout in ms. Capped at 300s. */
291
+ defaultTimeoutMs?: number;
292
+ }
293
+ declare function createShellTool(opts: CreateShellToolOptions): CustomTool;
294
+
295
+ /**
296
+ * `todolist` — in-session task tracking for multi-step work.
297
+ *
298
+ * The agent uses this to plan complex tasks and track progress.
299
+ * Inspired by OpenCode's todo.ts and Claude Code's TodoWrite.
300
+ *
301
+ * Actions:
302
+ * - add(title) → add a new todo item
303
+ * - complete(id) → mark an item done
304
+ * - remove(id) → remove an item
305
+ * - list() → show all items with status
306
+ * - clear_completed() → remove all done items
307
+ */
308
+ interface TodoItem {
309
+ id: string;
310
+ title: string;
311
+ status: "pending" | "in_progress" | "done";
312
+ createdAt: number;
313
+ completedAt?: number;
314
+ }
315
+ interface TodolistTool {
316
+ name: string;
317
+ description: string;
318
+ inputSchema: unknown;
319
+ handler: (input: TodoInput) => string;
320
+ /** Expose items for testing. */
321
+ getItems: () => TodoItem[];
322
+ }
323
+ type TodoInput = {
324
+ action: "add";
325
+ title: string;
326
+ } | {
327
+ action: "complete";
328
+ id: string;
329
+ } | {
330
+ action: "in_progress";
331
+ id: string;
332
+ } | {
333
+ action: "remove";
334
+ id: string;
335
+ } | {
336
+ action: "list";
337
+ } | {
338
+ action: "clear_completed";
339
+ };
340
+ declare function createTodolistTool(): TodolistTool;
341
+
342
+ /**
343
+ * `truncateOutput` — utility for truncating large tool output.
344
+ *
345
+ * When output exceeds `maxBytes`, writes the full content to a temp file
346
+ * and returns a truncated version with a reference to the full output.
347
+ *
348
+ * Return shape:
349
+ * - `{ content: string, truncated: false }`
350
+ * - `{ content: string, truncated: true, overflowPath: string }`
351
+ */
352
+ interface TruncationOptions {
353
+ /** Maximum output size in bytes before truncation. Default: 30_000. */
354
+ maxBytes?: number;
355
+ /** Directory for overflow files. Default: ".theocode/tool-output". */
356
+ outputDir?: string;
357
+ }
358
+ interface TruncationResult {
359
+ /** The (possibly truncated) content. */
360
+ content: string;
361
+ /** Whether the output was truncated. */
362
+ truncated: boolean;
363
+ /** Path to the full output file, present only when truncated. */
364
+ overflowPath?: string;
365
+ }
366
+ declare function truncateOutput(output: string, opts?: TruncationOptions): TruncationResult;
367
+
368
+ /**
369
+ * `web_fetch` — built-in tool for coding agents.
370
+ *
371
+ * Fetches a URL via native `fetch()`. Rejects non-http(s) protocols.
372
+ * Size-capped at 1 MB response body.
373
+ *
374
+ * Return shape (always a JSON string):
375
+ * - `{ ok: true, content, status_code, content_type }`
376
+ * - `{ ok: false, error: 'invalid_url' | 'fetch_failed' |
377
+ * 'timeout' | 'too_large' }`
378
+ */
379
+
380
+ interface CreateWebFetchToolOptions {
381
+ /** Default timeout in ms. */
382
+ defaultTimeoutMs?: number;
383
+ }
384
+ declare function createWebFetchTool(opts?: CreateWebFetchToolOptions): CustomTool;
385
+
386
+ /**
387
+ * `web_search` — built-in tool for coding agents.
388
+ *
389
+ * Accepts a search callback via DIP (consumer provides the search
390
+ * provider). The tool itself is provider-agnostic.
391
+ *
392
+ * Return shape (always a JSON string):
393
+ * - `{ ok: true, results: Array<{ title, url, snippet }> }`
394
+ * - `{ ok: false, error: 'search_failed' | 'no_provider' }`
395
+ */
396
+
397
+ interface WebSearchResult {
398
+ title: string;
399
+ url: string;
400
+ snippet: string;
401
+ }
402
+ type WebSearchCallback = (query: string, maxResults: number) => Promise<WebSearchResult[]>;
403
+ interface CreateWebSearchToolOptions {
404
+ /** Search provider callback — consumer injects the implementation. */
405
+ search: WebSearchCallback;
406
+ /** Default max results if not specified by the LLM. */
407
+ defaultMaxResults?: number;
408
+ }
409
+ declare function createWebSearchTool(opts: CreateWebSearchToolOptions): CustomTool;
410
+
411
+ /**
412
+ * `write_file` — built-in tool for coding agents.
413
+ *
414
+ * Writes UTF-8 content to a project-relative path. Creates parent
415
+ * directories recursively. Refuses binary-file overwrites, path
416
+ * traversal, and sensitive paths.
417
+ *
418
+ * Return shape (always a JSON string):
419
+ * - `{ ok: true, path, bytes }` on success
420
+ * - `{ ok: false, error: 'path_traversal' | 'forbidden_path' |
421
+ * 'binary_file' }` on refusal
422
+ */
423
+
424
+ interface CreateWriteFileToolOptions {
425
+ /** Absolute path to the project root. Every write is gated against this boundary. */
426
+ projectRoot: string;
427
+ }
428
+ declare function createWriteFileTool(opts: CreateWriteFileToolOptions): CustomTool;
429
+
430
+ export { type CreateApplyPatchToolOptions, type CreateEditFileToolOptions, type CreateGitDiffToolOptions, type CreateGlobToolOptions, type CreateListDirToolOptions, type CreateReadFileToolOptions, type CreateRunVitestToolOptions, type CreateSearchTextToolOptions, type CreateShellToolOptions, type CreateWebFetchToolOptions, type CreateWebSearchToolOptions, type CreateWriteFileToolOptions, type PlanModeTool, type QuestionTool, type QuestionToolOptions, type TodoItem, type TodolistTool, type TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, createApplyPatchTool, createEditFileTool, createGitDiffTool, createGlobTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, formatCode, formatDiff, formatError, formatFileList, truncateOutput };