@theokit/sdk-tools 0.9.0 → 0.10.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.
package/dist/index.d.cts CHANGED
@@ -424,6 +424,13 @@ interface CreateListDirToolOptions {
424
424
  projectRoot: string;
425
425
  /** Maximum number of entries returned per call. Default 500. */
426
426
  max?: number;
427
+ /**
428
+ * SE31 — optional pluggable filesystem backend (`@theokit/sdk/filesystem`), or
429
+ * a per-request resolver. When provided, listings route through the backend
430
+ * (its own boundary + `basePath`) instead of the local `projectRoot`; omitted
431
+ * ⇒ identical current behavior (multi-tenant / per-request roots).
432
+ */
433
+ filesystem?: FilesystemProvider;
427
434
  }
428
435
  declare function createListDirTool(opts: CreateListDirToolOptions): CustomTool;
429
436
 
@@ -559,9 +566,39 @@ interface CreateReadFileToolOptions {
559
566
  * `requireReadBeforeWrite`) can refuse a blind or stale overwrite.
560
567
  */
561
568
  readTracker?: ReadTracker;
569
+ /**
570
+ * SE31 — optional pluggable filesystem backend (`@theokit/sdk/filesystem`), or
571
+ * a per-request resolver. When provided, reads route through the backend (its
572
+ * own boundary + `basePath`) instead of the local `projectRoot`; omitted ⇒
573
+ * identical current behavior (multi-tenant / per-request roots).
574
+ */
575
+ filesystem?: FilesystemProvider;
562
576
  }
563
577
  declare function createReadFileTool(opts: CreateReadFileToolOptions): CustomTool;
564
578
 
579
+ /**
580
+ * SE37 — the public reasoning toolkit: `ReasoningTools.create()` returns a
581
+ * `think` + `analyze` scratchpad (no-side-effect tools that echo the model's
582
+ * structured reasoning back as an observation, so the ReAct loop feeds it
583
+ * forward). Mirrors Anthropic's "think" tool + Agno's `ReasoningTools`.
584
+ *
585
+ * Lives in `@theokit/sdk-tools` (not core) so the full toolkit stays out of the
586
+ * core bundle. The `reasoning: true` agent flag auto-attaches an equivalent
587
+ * lean `think` from core; add these explicitly (`tools: [...ReasoningTools.create()]`)
588
+ * when you want the model to reason with a scratchpad on any model.
589
+ *
590
+ * @public
591
+ */
592
+
593
+ /** Reasoning scratchpad tools. `ReasoningTools.create()` → `[think, analyze]`. */
594
+ declare class ReasoningTools {
595
+ private constructor();
596
+ /** Build the tools. Pass `{ analyze: false }` for `think` only. */
597
+ static create(opts?: {
598
+ analyze?: boolean;
599
+ }): CustomTool[];
600
+ }
601
+
565
602
  /**
566
603
  * `run_vitest` — built-in tool for coding agents.
567
604
  *
@@ -916,4 +953,4 @@ interface CreateWriteFileToolOptions {
916
953
  }
917
954
  declare function createWriteFileTool(opts: CreateWriteFileToolOptions): CustomTool;
918
955
 
919
- export { CatastrophicCommandError, type CommandPolicy, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGlobToolOptions, 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, RedirectBlockedError, type RepoMapOptions, type ResolveAndScreenOptions, type ScreenedFetchOptions, type SessionArtifactStore, type SessionArtifactStoreOptions, SsrfBlockedError, type TodoItem, type TodolistTool, type ToolGuidanceMap, type TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withShellExitGuidance, withToolResultGuidance };
956
+ export { CatastrophicCommandError, type CommandPolicy, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGlobToolOptions, 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 TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withShellExitGuidance, withToolResultGuidance };
package/dist/index.d.ts CHANGED
@@ -424,6 +424,13 @@ interface CreateListDirToolOptions {
424
424
  projectRoot: string;
425
425
  /** Maximum number of entries returned per call. Default 500. */
426
426
  max?: number;
427
+ /**
428
+ * SE31 — optional pluggable filesystem backend (`@theokit/sdk/filesystem`), or
429
+ * a per-request resolver. When provided, listings route through the backend
430
+ * (its own boundary + `basePath`) instead of the local `projectRoot`; omitted
431
+ * ⇒ identical current behavior (multi-tenant / per-request roots).
432
+ */
433
+ filesystem?: FilesystemProvider;
427
434
  }
428
435
  declare function createListDirTool(opts: CreateListDirToolOptions): CustomTool;
429
436
 
@@ -559,9 +566,39 @@ interface CreateReadFileToolOptions {
559
566
  * `requireReadBeforeWrite`) can refuse a blind or stale overwrite.
560
567
  */
561
568
  readTracker?: ReadTracker;
569
+ /**
570
+ * SE31 — optional pluggable filesystem backend (`@theokit/sdk/filesystem`), or
571
+ * a per-request resolver. When provided, reads route through the backend (its
572
+ * own boundary + `basePath`) instead of the local `projectRoot`; omitted ⇒
573
+ * identical current behavior (multi-tenant / per-request roots).
574
+ */
575
+ filesystem?: FilesystemProvider;
562
576
  }
563
577
  declare function createReadFileTool(opts: CreateReadFileToolOptions): CustomTool;
564
578
 
579
+ /**
580
+ * SE37 — the public reasoning toolkit: `ReasoningTools.create()` returns a
581
+ * `think` + `analyze` scratchpad (no-side-effect tools that echo the model's
582
+ * structured reasoning back as an observation, so the ReAct loop feeds it
583
+ * forward). Mirrors Anthropic's "think" tool + Agno's `ReasoningTools`.
584
+ *
585
+ * Lives in `@theokit/sdk-tools` (not core) so the full toolkit stays out of the
586
+ * core bundle. The `reasoning: true` agent flag auto-attaches an equivalent
587
+ * lean `think` from core; add these explicitly (`tools: [...ReasoningTools.create()]`)
588
+ * when you want the model to reason with a scratchpad on any model.
589
+ *
590
+ * @public
591
+ */
592
+
593
+ /** Reasoning scratchpad tools. `ReasoningTools.create()` → `[think, analyze]`. */
594
+ declare class ReasoningTools {
595
+ private constructor();
596
+ /** Build the tools. Pass `{ analyze: false }` for `think` only. */
597
+ static create(opts?: {
598
+ analyze?: boolean;
599
+ }): CustomTool[];
600
+ }
601
+
565
602
  /**
566
603
  * `run_vitest` — built-in tool for coding agents.
567
604
  *
@@ -916,4 +953,4 @@ interface CreateWriteFileToolOptions {
916
953
  }
917
954
  declare function createWriteFileTool(opts: CreateWriteFileToolOptions): CustomTool;
918
955
 
919
- export { CatastrophicCommandError, type CommandPolicy, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGlobToolOptions, 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, RedirectBlockedError, type RepoMapOptions, type ResolveAndScreenOptions, type ScreenedFetchOptions, type SessionArtifactStore, type SessionArtifactStoreOptions, SsrfBlockedError, type TodoItem, type TodolistTool, type ToolGuidanceMap, type TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withShellExitGuidance, withToolResultGuidance };
956
+ export { CatastrophicCommandError, type CommandPolicy, type CreateApplyPatchToolOptions, type CreateBraveWebSearchAdapterOptions, type CreateEditFileToolOptions, type CreateGenericHttpSearchAdapterOptions, type CreateGitDiffToolOptions, type CreateGlobToolOptions, 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 TruncationOptions, type TruncationResult, type VitestSummary, type WebSearchCallback, type WebSearchResult, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withShellExitGuidance, withToolResultGuidance };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readFile, copyFile, mkdir, writeFile, readdir, open, stat } from 'fs/promises';
2
2
  import { dirname, join, relative, resolve, sep } from 'path';
3
- import { defineTool, ConfigurationError } from '@theokit/sdk';
3
+ import { Tool, ConfigurationError } from '@theokit/sdk';
4
4
  import { z } from 'zod';
5
5
  import { existsSync, statSync, mkdirSync, writeFileSync, realpathSync, readFileSync, lstatSync, readlinkSync, readdirSync } from 'fs';
6
6
  import { replaceFileAtomic } from '@theokit/sdk/internal/persistence';
@@ -8,7 +8,7 @@ import { safeFilenameForId, safePathJoin as safePathJoin$1 } from '@theokit/sdk/
8
8
  import { spawn } from 'child_process';
9
9
  import { lookup } from 'dns/promises';
10
10
  import { isIP } from 'net';
11
- import { resolveFilesystem, FilesystemSecurityError, FilesystemReadOnlyError, StaleFileError, FilesystemError, FileNotFoundError } from '@theokit/sdk/filesystem';
11
+ import { resolveFilesystem, FileNotFoundError, FilesystemSecurityError, FilesystemReadOnlyError, StaleFileError, FilesystemError } from '@theokit/sdk/filesystem';
12
12
 
13
13
  // src/apply-patch.ts
14
14
  var PathTraversalError = class extends ConfigurationError {
@@ -103,7 +103,7 @@ function isForbiddenPath(input) {
103
103
  // src/apply-patch.ts
104
104
  function createApplyPatchTool(opts) {
105
105
  const { projectRoot } = opts;
106
- return defineTool({
106
+ return Tool.create({
107
107
  name: "apply_patch",
108
108
  description: "Apply a unified diff patch to project files. Each file in the diff is security-checked against the project root. Creates .bak backups before modifying. Returns { ok, files_patched } or { ok: false, error }.",
109
109
  inputSchema: z.object({
@@ -265,7 +265,7 @@ function createSessionArtifactStore(options) {
265
265
  }
266
266
  function createEditFileTool(opts) {
267
267
  const { projectRoot } = opts;
268
- return defineTool({
268
+ return Tool.create({
269
269
  name: "edit_file",
270
270
  description: "Make an exact string replacement in a project-relative file. Replaces the FIRST occurrence of old_string with new_string (a whitespace-normalized fallback is attempted if the exact match fails) and writes a .bak backup first. Read the file first so old_string matches the on-disk text exactly; include enough surrounding context to make it unique \u2014 only the first match is replaced, so a too-short old_string can edit the wrong location. old_string must be non-empty and differ from new_string; to change every occurrence, call edit_file repeatedly. Returns { ok, replacements } or { ok: false, error }.",
271
271
  inputSchema: z.object({
@@ -442,7 +442,7 @@ function createGitDiffTool(opts) {
442
442
  timeoutMs = DEFAULT_TIMEOUT_MS,
443
443
  maxStdoutBytes = DEFAULT_MAX_STDOUT_BYTES
444
444
  } = opts;
445
- return defineTool({
445
+ return Tool.create({
446
446
  name: "git_diff",
447
447
  description: "Return the unified diff of the project's working tree (or staged changes when cached=true). Scoped to a single file when 'path' is provided. Requires the project to be a git repository. Returns { ok, diff, truncated? } or { ok: false, error }.",
448
448
  inputSchema: z.object({
@@ -524,7 +524,7 @@ function runGitProcess(cwd, args, timeoutMs, maxStdoutBytes) {
524
524
  var DEFAULT_EXCLUDES = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".theo"]);
525
525
  function createGlobTool(opts) {
526
526
  const { projectRoot } = opts;
527
- return defineTool({
527
+ return Tool.create({
528
528
  name: "glob_files",
529
529
  description: "Find files by glob pattern across the project \u2014 fast at any repo size. Use glob_files when you know the filename SHAPE; use search_text when you know the file CONTENT; use read_file when you know the exact path. The pattern supports * and ** wildcards (e.g. '**/*.ts', 'src/**/*.json'); node_modules/.git/dist/.theo are excluded and results are relative paths. Returns { ok, files } or { ok: false, error }.",
530
530
  inputSchema: z.object({
@@ -1087,26 +1087,61 @@ function withShellExitGuidance(tool) {
1087
1087
  }
1088
1088
  var DEFAULT_MAX_ENTRIES = 500;
1089
1089
  function createListDirTool(opts) {
1090
- const { projectRoot, max = DEFAULT_MAX_ENTRIES } = opts;
1091
- return defineTool({
1090
+ const { projectRoot, max = DEFAULT_MAX_ENTRIES, filesystem } = opts;
1091
+ return Tool.create({
1092
1092
  name: "list_dir",
1093
1093
  description: `Return the direct entries of a project-relative directory. Refuses paths outside the project root or in the sensitive-file blocklist (.env, .git/, node_modules/, .theo/, lock files). Caps at ${String(max)} entries by default; result carries truncated + totalCount.`,
1094
1094
  inputSchema: z.object({
1095
1095
  path: z.string().min(1).describe("Project-relative directory path. Use '.' for root.")
1096
1096
  }),
1097
- handler: async ({ path }) => {
1097
+ handler: async ({ path }, ctx) => {
1098
1098
  const relative2 = path === "" || path === "." ? "." : path;
1099
1099
  if (relative2 !== "." && isForbiddenPath(relative2)) {
1100
1100
  return JSON.stringify({ ok: false, error: "forbidden_path", path });
1101
1101
  }
1102
- const boundary = resolveDirBoundary(relative2, projectRoot, path);
1103
- if ("error" in boundary) return boundary.error;
1104
- const readResult = await readDirSafe(boundary.absolutePath, path);
1105
- if ("error" in readResult) return readResult.error;
1106
- return formatListing(readResult.dirents, max);
1102
+ if (filesystem) {
1103
+ const backend = await resolveFilesystem(filesystem, ctx ?? {});
1104
+ return listViaBackend(backend, relative2, path, max);
1105
+ }
1106
+ return listViaLocalFs(projectRoot, relative2, path, max);
1107
1107
  }
1108
1108
  });
1109
1109
  }
1110
+ async function listViaLocalFs(projectRoot, relative2, originalPath, max) {
1111
+ const boundary = resolveDirBoundary(relative2, projectRoot, originalPath);
1112
+ if ("error" in boundary) return boundary.error;
1113
+ const readResult = await readDirSafe(boundary.absolutePath, originalPath);
1114
+ if ("error" in readResult) return readResult.error;
1115
+ return formatListing(readResult.dirents, max);
1116
+ }
1117
+ async function listViaBackend(backend, relative2, originalPath, max) {
1118
+ let names;
1119
+ try {
1120
+ names = await backend.list(relative2);
1121
+ } catch (err) {
1122
+ if (err instanceof FileNotFoundError) {
1123
+ return JSON.stringify({ ok: false, error: "not_found", path: originalPath });
1124
+ }
1125
+ if (err instanceof FilesystemSecurityError) {
1126
+ return JSON.stringify({ ok: false, error: "path_traversal", path: originalPath });
1127
+ }
1128
+ throw err;
1129
+ }
1130
+ const totalCount = names.length;
1131
+ const windowed = names.slice(0, max);
1132
+ const entries = await Promise.all(
1133
+ windowed.map(async (name) => {
1134
+ const child = relative2 === "." ? name : `${relative2}/${name}`;
1135
+ let type = "file";
1136
+ try {
1137
+ type = (await backend.stat(child)).isDirectory ? "directory" : "file";
1138
+ } catch {
1139
+ }
1140
+ return { name, type };
1141
+ })
1142
+ );
1143
+ return JSON.stringify({ ok: true, entries, truncated: totalCount > max, totalCount });
1144
+ }
1110
1145
  function resolveDirBoundary(relative2, projectRoot, originalPath) {
1111
1146
  try {
1112
1147
  const absolutePath = relative2 === "." ? projectRoot : safePathJoin(projectRoot, relative2);
@@ -1267,17 +1302,21 @@ function createQuestionTool(opts) {
1267
1302
  var MAX_FILE_SIZE = 5 * 1024 * 1024;
1268
1303
  var BINARY_PROBE_BYTES = 8 * 1024;
1269
1304
  function createReadFileTool(opts) {
1270
- const { projectRoot, readTracker } = opts;
1271
- return defineTool({
1305
+ const { projectRoot, readTracker, filesystem } = opts;
1306
+ return Tool.create({
1272
1307
  name: "read_file",
1273
1308
  description: "Read a project-relative text file as UTF-8. ALWAYS read a file before you edit it (edit_file) or overwrite it (write_file), so your old_string / new content matches the real bytes exactly. Returns the WHOLE file (there is no offset or line-range parameter); to locate a symbol inside a large file, use search_text instead of re-reading. Refuses paths that escape the project root, sensitive files (.env, .git/, node_modules/, .theo/, lock files), and binary files (null byte in the first 8 KB); caps at 5 MB. Returns { ok, content, size } or { ok: false, error }.",
1274
1309
  inputSchema: z.object({
1275
1310
  path: z.string().min(1).describe("Project-relative file path.")
1276
1311
  }),
1277
- handler: async ({ path }) => {
1312
+ handler: async ({ path }, ctx) => {
1278
1313
  if (isForbiddenPath(path)) {
1279
1314
  return JSON.stringify({ ok: false, error: "forbidden_path", path });
1280
1315
  }
1316
+ if (filesystem) {
1317
+ const backend = await resolveFilesystem(filesystem, ctx ?? {});
1318
+ return readViaBackend(backend, path, (mtimeMs) => readTracker?.record(path, mtimeMs));
1319
+ }
1281
1320
  const boundary = resolveBoundary(path, projectRoot);
1282
1321
  if ("error" in boundary) return boundary.error;
1283
1322
  const opened = await openHandleSafe(boundary.absolutePath, path);
@@ -1294,6 +1333,34 @@ function createReadFileTool(opts) {
1294
1333
  }
1295
1334
  });
1296
1335
  }
1336
+ async function readViaBackend(backend, path, onRead) {
1337
+ try {
1338
+ const stat2 = await backend.stat(path);
1339
+ if (stat2.size > MAX_FILE_SIZE) {
1340
+ return JSON.stringify({
1341
+ ok: false,
1342
+ error: "too_large",
1343
+ path,
1344
+ size: stat2.size,
1345
+ limit: MAX_FILE_SIZE
1346
+ });
1347
+ }
1348
+ const content = await backend.readFile(path);
1349
+ if (content.includes("\0")) {
1350
+ return JSON.stringify({ ok: false, error: "binary_file", path, size: stat2.size });
1351
+ }
1352
+ onRead?.(stat2.mtimeMs);
1353
+ return JSON.stringify({ ok: true, content, size: stat2.size });
1354
+ } catch (err) {
1355
+ if (err instanceof FileNotFoundError) {
1356
+ return JSON.stringify({ ok: false, error: "not_found", path });
1357
+ }
1358
+ if (err instanceof FilesystemSecurityError) {
1359
+ return JSON.stringify({ ok: false, error: "path_traversal", path });
1360
+ }
1361
+ throw err;
1362
+ }
1363
+ }
1297
1364
  function resolveBoundary(path, projectRoot) {
1298
1365
  try {
1299
1366
  const absolutePath = safePathJoin(projectRoot, path);
@@ -1366,6 +1433,37 @@ function evaluateReadBeforeWrite(tracker, path, currentMtimeMs) {
1366
1433
  if (recorded !== currentMtimeMs) return "stale";
1367
1434
  return "ok";
1368
1435
  }
1436
+ var ReasoningTools = class {
1437
+ constructor() {
1438
+ }
1439
+ /** Build the tools. Pass `{ analyze: false }` for `think` only. */
1440
+ static create(opts) {
1441
+ const think = Tool.create({
1442
+ name: "think",
1443
+ description: "Use this as a scratchpad to think step by step BEFORE answering or acting. Write out your reasoning for one step. Nothing else happens \u2014 it is only your private reasoning space. Call it as many times as you need before the final answer.",
1444
+ inputSchema: z.object({
1445
+ thought: z.string().min(1, "think: `thought` must be a non-empty string.")
1446
+ }),
1447
+ handler: ({ thought }) => thought
1448
+ });
1449
+ if (opts?.analyze === false) return [think];
1450
+ const analyze = Tool.create({
1451
+ name: "analyze",
1452
+ description: "Analyze the result of a previous step or tool call. State what you looked at, your analysis, and whether to `continue` reasoning, `validate` (double-check) your work, or give the `final_answer`. Use this to catch your own mistakes before answering.",
1453
+ inputSchema: z.object({
1454
+ title: z.string().optional(),
1455
+ result: z.string().min(1, "analyze: `result` must describe what you are analyzing."),
1456
+ analysis: z.string().min(1, "analyze: `analysis` must contain your reasoning."),
1457
+ next_action: z.enum(["continue", "validate", "final_answer"])
1458
+ }),
1459
+ handler: ({ title, result, analysis, next_action }) => `${title ? `# ${title}
1460
+ ` : ""}Result: ${result}
1461
+ Analysis: ${analysis}
1462
+ Next: ${next_action}`
1463
+ });
1464
+ return [think, analyze];
1465
+ }
1466
+ };
1369
1467
  var DEFAULT_TIMEOUT_MS2 = 12e4;
1370
1468
  var DEFAULT_MAX_STDOUT_BYTES2 = 10 * 1024 * 1024;
1371
1469
  function createRunVitestTool(opts) {
@@ -1374,7 +1472,7 @@ function createRunVitestTool(opts) {
1374
1472
  timeoutMs = DEFAULT_TIMEOUT_MS2,
1375
1473
  maxStdoutBytes = DEFAULT_MAX_STDOUT_BYTES2
1376
1474
  } = opts;
1377
- return defineTool({
1475
+ return Tool.create({
1378
1476
  name: "run_vitest",
1379
1477
  description: "Run the project's vitest suite, optionally scoped to a file or pattern via 'path'. Returns parsed { ok, summary } or { ok: false, error }. Vitest stdout warnings are stripped \u2014 the parser extracts the trailing JSON report.",
1380
1478
  inputSchema: z.object({
@@ -1483,7 +1581,7 @@ function createSearchTextTool(opts) {
1483
1581
  maxMatches = DEFAULT_MAX_MATCHES,
1484
1582
  maxFileSize = DEFAULT_MAX_FILE_SIZE
1485
1583
  } = opts;
1486
- return defineTool({
1584
+ return Tool.create({
1487
1585
  name: "search_text",
1488
1586
  description: `Search file CONTENTS for a LITERAL, CASE-SENSITIVE query across the project tree (the query is matched as a substring, not a regex). Use search_text when you know the content; use glob_files when you know the filename shape; use read_file when you know the exact path. Skips sensitive dirs (.env/.git/node_modules/.theo), binary files, and files over 1 MB; 'path' scopes the search to a subdirectory. Returns up to ${String(maxMatches)} matches as { file, line, preview } \u2014 cite locations to the user as file:line. Returns { ok, matches } or { ok: false, error }.`,
1489
1587
  inputSchema: z.object({
@@ -1594,7 +1692,7 @@ var MAX_TIMEOUT_MS = 3e5;
1594
1692
  var MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
1595
1693
  function createShellTool(opts) {
1596
1694
  const { projectRoot, defaultTimeoutMs = DEFAULT_TIMEOUT_MS3, allowCatastrophic = false } = opts;
1597
- return defineTool({
1695
+ return Tool.create({
1598
1696
  name: "shell_exec",
1599
1697
  description: "Execute a shell command in the project directory. Use this for terminal operations \u2014 running tests, git, package managers, build tools. Do NOT use it for file operations (reading, writing, editing, finding files): prefer the specialized read_file/write_file/edit_file/glob_files/search_text tools, which are path-checked and safer. Only commit, push, or change git state when the user explicitly asks. timeout_ms defaults to 30000 (max 300000); stdout/stderr are capped (~5 MB). Returns { ok, stdout, stderr, exit_code } or { ok: false, error }.",
1600
1698
  inputSchema: z.object({
@@ -1831,7 +1929,7 @@ function createWebFetchTool(opts) {
1831
1929
  const maxRedirects = opts?.maxRedirects;
1832
1930
  const fetchImpl = opts?.fetchImpl;
1833
1931
  const lookup = opts?.lookup;
1834
- return defineTool({
1932
+ return Tool.create({
1835
1933
  name: "web_fetch",
1836
1934
  description: "Fetch the contents of a URL via HTTP/HTTPS. Use only for URLs the user provided or that you are confident help with the task; never invent or guess URLs. Rejects non-http(s) URLs and is SSRF-guarded by default (private/loopback/link-local/cloud-metadata hosts are refused with an ssrf_blocked error). The response body is capped at 1 MB. Returns { ok, content, status_code, content_type } or { ok: false, error }.",
1837
1935
  inputSchema: z.object({
@@ -1918,7 +2016,7 @@ function createWebFetchTool(opts) {
1918
2016
  }
1919
2017
  function createWebSearchTool(opts) {
1920
2018
  const { search, defaultMaxResults = 5 } = opts;
1921
- return defineTool({
2019
+ return Tool.create({
1922
2020
  name: "web_search",
1923
2021
  description: "Search the web for a query \u2014 use when you need current information beyond the repo or your training cutoff (library docs, an error message, an API). Returns a list of results with title, URL, and snippet; follow up with web_fetch on a promising result to read it in full. The search provider is injected by the consumer. Returns { ok, results } or { ok: false, error }.",
1924
2022
  inputSchema: z.object({
@@ -2005,7 +2103,7 @@ function createWriteFileTool(opts) {
2005
2103
  );
2006
2104
  }
2007
2105
  const guard = opts.requireReadBeforeWrite ? opts.readTracker : void 0;
2008
- return defineTool({
2106
+ return Tool.create({
2009
2107
  name: "write_file",
2010
2108
  description: "Write UTF-8 content to a project-relative file, creating parent directories as needed. OVERWRITES any existing file at the path. Prefer editing an existing file with edit_file over rewriting it; use write_file to create a NEW file or fully replace a small one. If the file already exists, read_file it first so you do not discard content you have not seen. Refuses paths that escape the write root and sensitive files (.env, .git/, node_modules/, .theo/, lock files); the default local root also refuses binary-file overwrites. Returns { ok, path, bytes } or { ok: false, error }.",
2011
2109
  inputSchema: z.object({
@@ -2125,6 +2223,6 @@ async function isBinaryFile(absolutePath) {
2125
2223
  }
2126
2224
  }
2127
2225
 
2128
- export { CatastrophicCommandError, DEFAULT_TOOL_GUIDANCE, ReadTracker, RedirectBlockedError, SsrfBlockedError, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withShellExitGuidance, withToolResultGuidance };
2226
+ export { CatastrophicCommandError, DEFAULT_TOOL_GUIDANCE, ReadTracker, ReasoningTools, RedirectBlockedError, SsrfBlockedError, buildEnvContext, buildRepoMap, catastrophicShellReason, commandDenialReason, createApplyPatchTool, createBraveWebSearchAdapter, createEditFileTool, createGenericHttpSearchAdapter, createGitDiffTool, createGlobTool, createListDirTool, createPlanModeTool, createQuestionTool, createReadFileTool, createRunVitestTool, createSearchTextTool, createSessionArtifactStore, createShellTool, createTodolistTool, createWebFetchTool, createWebSearchTool, createWriteFileTool, denyCatastrophicCommands, formatCode, formatDiff, formatError, formatFileList, injectGuidance, isBlockedIp, isCommandAllowed, renderToolList, resolveAndScreen, screenedFetch, todoItemsToPlanNodes, truncateOutput, withDefaultGuidance, withDescription, withShellExitGuidance, withToolResultGuidance };
2129
2227
  //# sourceMappingURL=index.js.map
2130
2228
  //# sourceMappingURL=index.js.map