opencode-readseek 0.9.6 → 0.9.7

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.
Files changed (2) hide show
  1. package/dist/index.js +825 -726
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  // index.ts
2
- import { lstat, mkdir, open, readFile, readlink, realpath, stat } from "node:fs/promises";
2
+ import path7 from "node:path";
3
+
4
+ // src/client.ts
3
5
  import { createRequire as createRequire2 } from "node:module";
4
6
  import { homedir } from "node:os";
5
- import path2 from "node:path";
6
- import { tool } from "@opencode-ai/plugin";
7
+ import path3 from "node:path";
8
+ import { stat } from "node:fs/promises";
7
9
 
8
10
  // ../readseek-api/src/contract.ts
9
11
  var VISION_MODES = ["none", "all", "ocr", "caption", "objects"];
@@ -34,6 +36,8 @@ var TOOL_NAMES = [
34
36
  ];
35
37
  var PARAM_DESCRIPTIONS = {
36
38
  path: "Absolute or relative file path",
39
+ projectPath: "Path relative to the project directory",
40
+ projectSearchPath: "File or directory to search; defaults to the project directory",
37
41
  searchPath: "File or directory to search; defaults to the current directory",
38
42
  language: "Language override when auto-detection is ambiguous",
39
43
  languageOverride: "Language override",
@@ -87,6 +91,7 @@ var PARAM_DESCRIPTIONS = {
87
91
  symbol: "Mapped symbol name",
88
92
  newBody: "Complete replacement body for the symbol",
89
93
  apply: "When supported by the host, apply the verified plan instead of previewing only",
94
+ applyDefaultTrue: "Apply the verified edits; defaults to true",
90
95
  planHash: "Require the apply plan to match this dry-run plan hash",
91
96
  workspace: "Rename across the project",
92
97
  to: "New symbol name",
@@ -391,6 +396,88 @@ var TOOL_CONTRACTS = [
391
396
  }
392
397
  }
393
398
  ];
399
+ // ../readseek-api/src/policy.ts
400
+ var BUILTIN_FALLBACKS = {
401
+ read: "digest",
402
+ edit: "edit",
403
+ write: "write",
404
+ grep: "grep",
405
+ apply_patch: "edit"
406
+ };
407
+ var PREFERENCE_LEADS = {
408
+ digest: "Preferred for reading source with anchors and facets.",
409
+ edit: "Preferred for editing existing text files with fresh LINE:HASH anchors.",
410
+ write: "Preferred for creating or replacing a complete text file; returns LINE:HASH anchors.",
411
+ grep: "Preferred text/regex search; returns edit-ready LINE:HASH anchors.",
412
+ search: "Preferred for AST or structural code search with ast-grep-style patterns.",
413
+ def: "Preferred for go-to-definition or declaration lookup by name.",
414
+ refs: "Preferred for finding identifier-name matches or binding-specific references before rename or delete.",
415
+ rename: "Preferred for renaming a symbol with binding-aware, verified edits.",
416
+ view: "Preferred for digesting document structure or selected content."
417
+ };
418
+ var FALLBACK_TEMPLATES = {
419
+ read: "Fallback only. Prefer `digest` with fresh LINE:HASH anchors.",
420
+ edit: "Fallback only. Prefer `edit` with fresh LINE:HASH anchors.",
421
+ write: "Fallback only. Prefer `write` so the result returns LINE:HASH anchors.",
422
+ grep: "Fallback only. Prefer `grep` for text/regex search with edit-ready anchors.",
423
+ apply_patch: "Fallback only. Prefer `edit` for existing text files."
424
+ };
425
+ function resolveToolName(name, aliases = {}) {
426
+ return aliases[name] ?? name;
427
+ }
428
+ function rewriteBacktickedToolNames(text, aliases = {}) {
429
+ return TOOL_NAMES.reduce((rewritten, toolName) => rewritten.replaceAll(`\`${toolName}\``, `\`${resolveToolName(toolName, aliases)}\``), text);
430
+ }
431
+ function renderPreferredToolDescription(name, aliases = {}) {
432
+ return rewriteBacktickedToolNames(PREFERENCE_LEADS[name], aliases);
433
+ }
434
+ function renderFallbackToolDescription(builtIn, aliases = {}) {
435
+ const template = FALLBACK_TEMPLATES[builtIn];
436
+ if (!template)
437
+ return;
438
+ return rewriteBacktickedToolNames(template, aliases);
439
+ }
440
+ function renderToolRoutingPolicy(options = {}) {
441
+ const aliases = options.aliases ?? {};
442
+ const digest = resolveToolName("digest", aliases);
443
+ const edit = resolveToolName("edit", aliases);
444
+ const write = resolveToolName("write", aliases);
445
+ const grep = resolveToolName("grep", aliases);
446
+ const search = resolveToolName("search", aliases);
447
+ const def = resolveToolName("def", aliases);
448
+ const refs = resolveToolName("refs", aliases);
449
+ const rename = resolveToolName("rename", aliases);
450
+ const view = resolveToolName("view", aliases);
451
+ if (options.style === "server") {
452
+ return [
453
+ "Structural code navigation, document viewing, and hash-verified edits with readseek.",
454
+ "For structural questions, prefer these tools to text Grep/Bash: definitions, references, AST search, multi-facet digest (content/map/diagnostics/identity), and document views.",
455
+ `Use ${edit} for LINE:HASH-anchored mutations (preview with apply=false; apply defaults to true and re-checks plan_hash).`,
456
+ "Paths resolve against CLAUDE_PROJECT_DIR. Native Read/Grep/Edit hooks provide passive context;",
457
+ "call MCP tools for active navigation, document questions, or anchored edits."
458
+ ].join(" ");
459
+ }
460
+ const title = options.title ?? "ReadSeek tool policy:";
461
+ const bullets = [];
462
+ if (options.preferOverBuiltIns) {
463
+ bullets.push("- Prefer ReadSeek tools over built-ins when they can do the job.");
464
+ }
465
+ bullets.push(`- Read with ${digest} first; ${edit} needs fresh LINE:HASH anchors.`, `- Use ${grep} for text/regex, ${search} for AST patterns, and ${digest}/${def}/${refs} for symbol navigation.`, `- Use ${edit} for existing files, ${write} for whole-file creation or replacement, and ${rename} for symbol renames.`, `- Use ${view} for document structure; run ${digest} with select diagnostics after source edits for a quick syntax check.`);
466
+ if (options.demoteBuiltIns && options.demoteBuiltIns.length > 0) {
467
+ bullets.push(`- Do not use built-in ${options.demoteBuiltIns.join(", ")} when a ReadSeek tool can do the job.`);
468
+ }
469
+ return [title, ...bullets].join(`
470
+ `);
471
+ }
472
+ function preferredToolDescriptionMap(aliases = {}) {
473
+ return Object.fromEntries(TOOL_NAMES.map((name) => [resolveToolName(name, aliases), renderPreferredToolDescription(name, aliases)]));
474
+ }
475
+ function fallbackToolDescriptionMap(builtIns = Object.keys(BUILTIN_FALLBACKS), aliases = {}) {
476
+ return Object.fromEntries(builtIns.flatMap((builtIn) => {
477
+ const description = renderFallbackToolDescription(builtIn, aliases);
478
+ return description ? [[builtIn, description]] : [];
479
+ }));
480
+ }
394
481
  // ../readseek-api/src/validation.ts
395
482
  function validateGitSelection(input) {
396
483
  if (input.ignored && !input.others) {
@@ -739,48 +826,103 @@ async function runReadSeekBunText(binaryPath, args, options = {}) {
739
826
  async function runReadSeekBunJson(binaryPath, args, options = {}) {
740
827
  return parseSuccessfulJson(await runReadSeekBun(binaryPath, args, options));
741
828
  }
742
- // index.ts
743
- var resolveFromHost = createRequire2(import.meta.url).resolve;
829
+ // src/constants.ts
744
830
  var DEFAULT_VISION_TIMEOUT_MS = 30 * 60 * 1000;
745
831
  var DEFAULT_READ_LIMIT = 2000;
746
832
  var EDIT_RESULT_READ_LIMIT = 40;
747
833
  var MAX_DOCUMENT_OUTPUT_BYTES = 256 * 1024;
748
834
  var MAX_DOCUMENT_OUTPUT_LINES = 2000;
749
- var TOOL_POLICY = [
750
- "ReadSeek tool policy:",
751
- "- Prefer readseek_* tools over built-ins when they can do the job.",
752
- "- Read with readseek_digest first; readseek_edit needs fresh LINE:HASH anchors.",
753
- "- Use readseek_grep for text/regex, readseek_search for AST patterns, and readseek_digest/def/refs for symbol navigation.",
754
- "- Use readseek_edit for existing files, readseek_write for whole-file creation or replacement, and readseek_rename for symbol renames.",
755
- "- Do not use built-in read, edit, write, grep, or apply_patch when a ReadSeek tool can do the job.",
756
- "- Run readseek_digest with select=diagnostics after source edits for a quick syntax check."
757
- ].join(`
758
- `);
759
- var OPENCODE_TOOL_ALIASES = Object.fromEntries(TOOL_NAMES.map((name) => [name, `readseek_${name}`]));
760
- function openCodeToolDescription(name) {
761
- return renderToolDescription(name, OPENCODE_TOOL_ALIASES);
762
- }
763
- var PREFERRED_TOOL_DESCRIPTIONS = {
764
- readseek_edit: "Preferred tool for editing existing text files with fresh LINE:HASH anchors.",
765
- readseek_write: "Preferred tool for creating or replacing a complete text file; returns LINE:HASH anchors.",
766
- readseek_rename: "Use to rename a symbol with binding-aware, verified edits.",
767
- readseek_grep: "Preferred text/regex search; returns edit-ready LINE:HASH anchors.",
768
- readseek_search: "Use for AST or structural code search with ast-grep-style patterns.",
769
- readseek_digest: "Use to digest native facets (metadata, content, map, diagnostics, identity) and return the full envelope.",
770
- readseek_def: "Use for go-to-definition or declaration lookup by name.",
771
- readseek_refs: "Use to find identifier-name matches or binding-specific references before rename or delete.",
772
- readseek_view: "Use to digest document structure or selected content."
773
- };
774
- var FALLBACK_TOOL_DESCRIPTIONS = {
775
- edit: "Fallback only. Prefer readseek_edit with fresh LINE:HASH anchors.",
776
- write: "Fallback only. Prefer readseek_write so the result returns LINE:HASH anchors.",
777
- grep: "Fallback only. Prefer readseek_grep for text/regex search with edit-ready anchors.",
778
- apply_patch: "Fallback only. Prefer readseek_edit for existing text files."
779
- };
835
+ var MAX_PERMISSION_DIFF_BYTES = 32 * 1024;
780
836
  var IMAGE_MODES = [...VISION_MODES];
781
837
  var VISION_ANALYSIS_MODE_VALUES = [...VISION_ANALYSIS_MODES];
782
838
  var VISION_LEVEL_VALUES = [...VISION_LEVELS];
783
839
  var DOCUMENT_NODE_KIND_VALUES = [...DOCUMENT_NODE_KINDS];
840
+
841
+ // src/util.ts
842
+ import path2 from "node:path";
843
+ function render(value) {
844
+ return JSON.stringify(value, null, 2);
845
+ }
846
+ function record(value) {
847
+ return value && typeof value === "object" ? value : {};
848
+ }
849
+ function items(value) {
850
+ return Array.isArray(value) ? value : [];
851
+ }
852
+ function identifiedName(value) {
853
+ if (!value || typeof value !== "object")
854
+ return;
855
+ const identifier = value.identifier;
856
+ if (!identifier || typeof identifier !== "object")
857
+ return;
858
+ const text = identifier.text;
859
+ return typeof text === "string" ? text : undefined;
860
+ }
861
+ function collectAnchoredFiles(value, files) {
862
+ if (Array.isArray(value)) {
863
+ for (const item of value)
864
+ collectAnchoredFiles(item, files);
865
+ return;
866
+ }
867
+ if (!value || typeof value !== "object")
868
+ return;
869
+ const output = value;
870
+ const hashlines = output.hashlines;
871
+ if (typeof output.file === "string" && Array.isArray(hashlines) && hashlines.some((line) => typeof record(line).line === "number" && typeof record(line).hash === "string"))
872
+ files.add(output.file);
873
+ for (const item of Object.values(output))
874
+ collectAnchoredFiles(item, files);
875
+ }
876
+ function renameFiles(value) {
877
+ const output = record(value);
878
+ const files = [];
879
+ if (typeof output.file === "string" && items(output.edits).length > 0)
880
+ files.push(path2.resolve(output.file));
881
+ for (const other of items(output.others)) {
882
+ const item = record(other);
883
+ if (typeof item.file === "string" && items(item.edits).length > 0)
884
+ files.push(path2.resolve(item.file));
885
+ }
886
+ return files;
887
+ }
888
+ function normalizeToLf(content) {
889
+ return content.replace(/\r\n/g, `
890
+ `).replace(/\r/g, `
891
+ `);
892
+ }
893
+ function diffLines(content) {
894
+ if (content === "")
895
+ return [];
896
+ const lines = content.split(`
897
+ `);
898
+ if (lines.at(-1) === "")
899
+ lines.pop();
900
+ return lines.map((line) => line.replace(/\r/g, "\\r"));
901
+ }
902
+ function hasFinalNewline(content) {
903
+ return content === "" || /[\r\n]$/.test(content);
904
+ }
905
+ function diffRange(start, count) {
906
+ return count === 0 ? `${start},0` : `${start + 1},${count}`;
907
+ }
908
+ function utf8Prefix(value, maxBytes) {
909
+ const bytes = Buffer.from(value, "utf8");
910
+ if (bytes.length <= maxBytes)
911
+ return value;
912
+ let end = maxBytes;
913
+ const decoder = new TextDecoder("utf-8", { fatal: true });
914
+ while (end > 0) {
915
+ try {
916
+ return decoder.decode(bytes.subarray(0, end));
917
+ } catch {
918
+ end--;
919
+ }
920
+ }
921
+ return "";
922
+ }
923
+
924
+ // src/client.ts
925
+ var resolveFromHost = createRequire2(import.meta.url).resolve;
784
926
  function resolveImagePolicy(options) {
785
927
  const value = options?.imageMode;
786
928
  if (value === undefined)
@@ -799,6 +941,119 @@ function requireDigestFacet(envelope, facet) {
799
941
  }
800
942
  return value;
801
943
  }
944
+ function withAt(args, line, column) {
945
+ args.push("--at", buildLineLocation(line, column));
946
+ }
947
+ function readRangeArgs(filePath, start, end) {
948
+ const extra = [];
949
+ if (start > 1)
950
+ withAt(extra, start);
951
+ extra.push("--end", String(end));
952
+ return digestArgs(filePath, "content", extra);
953
+ }
954
+ function readSeekBinaryPath() {
955
+ return resolveReadSeekBinaryFromPackages(resolveFromHost);
956
+ }
957
+ async function preserveAbortReason(signal, cancelable, invoke) {
958
+ try {
959
+ const value = await invoke();
960
+ if (cancelable)
961
+ signal.throwIfAborted();
962
+ return value;
963
+ } catch (error) {
964
+ if (cancelable)
965
+ signal.throwIfAborted();
966
+ throw error;
967
+ }
968
+ }
969
+ async function runReadSeekRaw(context, args, options = {}) {
970
+ context.abort.throwIfAborted();
971
+ return preserveAbortReason(context.abort, options.cancelable !== false, () => runReadSeekBunText(readSeekBinaryPath(), args, {
972
+ cwd: context.directory,
973
+ signal: context.abort,
974
+ cancelable: options.cancelable,
975
+ stdin: options.stdin,
976
+ timeoutMs: options.timeoutMs
977
+ }));
978
+ }
979
+ async function runReadSeek(context, args, options = {}) {
980
+ context.abort.throwIfAborted();
981
+ return preserveAbortReason(context.abort, options.cancelable !== false, () => runReadSeekBunJson(readSeekBinaryPath(), args, {
982
+ cwd: context.directory,
983
+ signal: context.abort,
984
+ cancelable: options.cancelable,
985
+ stdin: options.stdin,
986
+ timeoutMs: options.timeoutMs
987
+ }));
988
+ }
989
+ async function runDigestContent(context, args, options = {}) {
990
+ return requireDigestFacet(await runReadSeek(context, args, options), "content");
991
+ }
992
+ async function postMutationPreview(context, args, filePath, changed) {
993
+ try {
994
+ return await runDigestContent(context, args, {
995
+ cancelable: false,
996
+ timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
997
+ });
998
+ } catch (error) {
999
+ const previewError = error instanceof Error ? error.message : String(error);
1000
+ return {
1001
+ file: filePath,
1002
+ changed,
1003
+ preview_error: previewError,
1004
+ warning: "File mutation completed, but the post-write preview failed. Do not retry the mutation; read the file to verify content and refresh anchors."
1005
+ };
1006
+ }
1007
+ }
1008
+ var readSeekCacheInit;
1009
+ function readSeekCacheDir() {
1010
+ const xdgCacheHome = process.env.XDG_CACHE_HOME?.trim();
1011
+ const cacheHome = xdgCacheHome || path3.join(homedir(), ".cache");
1012
+ return path3.join(cacheHome, "opencode", "readseek");
1013
+ }
1014
+ async function ensureReadSeekCache(context) {
1015
+ const cacheDir = readSeekCacheDir();
1016
+ try {
1017
+ const info = await stat(cacheDir);
1018
+ if (!info.isDirectory())
1019
+ throw new Error(`readseek cache is not a directory: ${cacheDir}`);
1020
+ return cacheDir;
1021
+ } catch (error) {
1022
+ if (error.code !== "ENOENT")
1023
+ throw error;
1024
+ }
1025
+ readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "init"]).then(() => cacheDir).finally(() => {
1026
+ readSeekCacheInit = undefined;
1027
+ });
1028
+ return readSeekCacheInit;
1029
+ }
1030
+ function boundDocumentOutput(value) {
1031
+ const lines = value.split(`
1032
+ `);
1033
+ let output = lines.length > MAX_DOCUMENT_OUTPUT_LINES ? lines.slice(0, MAX_DOCUMENT_OUTPUT_LINES).join(`
1034
+ `) : value;
1035
+ const truncated = output !== value || Buffer.byteLength(output, "utf8") > MAX_DOCUMENT_OUTPUT_BYTES;
1036
+ output = utf8Prefix(output, MAX_DOCUMENT_OUTPUT_BYTES);
1037
+ return truncated ? `${output}
1038
+ [… document view truncated; narrow it with page, node, kind, or depth]` : output;
1039
+ }
1040
+
1041
+ // src/policy.ts
1042
+ var OPENCODE_TOOL_ALIASES = Object.fromEntries(TOOL_NAMES.map((name) => [name, `readseek_${name}`]));
1043
+ var TOOL_POLICY = renderToolRoutingPolicy({
1044
+ aliases: OPENCODE_TOOL_ALIASES,
1045
+ preferOverBuiltIns: true,
1046
+ demoteBuiltIns: ["read", "edit", "write", "grep", "apply_patch"]
1047
+ });
1048
+ var PREFERRED_TOOL_DESCRIPTIONS = preferredToolDescriptionMap(OPENCODE_TOOL_ALIASES);
1049
+ var FALLBACK_TOOL_DESCRIPTIONS = fallbackToolDescriptionMap(["edit", "write", "grep", "apply_patch"], OPENCODE_TOOL_ALIASES);
1050
+ function openCodeToolDescription(name) {
1051
+ return renderToolDescription(name, OPENCODE_TOOL_ALIASES);
1052
+ }
1053
+
1054
+ // src/session-anchors.ts
1055
+ import path4 from "node:path";
1056
+
802
1057
  class SessionAnchors {
803
1058
  #pathsBySession = new Map;
804
1059
  #renamePlans = new Map;
@@ -811,7 +1066,7 @@ class SessionAnchors {
811
1066
  paths.add(filePath);
812
1067
  }
813
1068
  forget(filePath) {
814
- const absolutePath = path2.resolve(filePath);
1069
+ const absolutePath = path4.resolve(filePath);
815
1070
  for (const paths of this.#pathsBySession.values())
816
1071
  paths.delete(absolutePath);
817
1072
  for (const [sessionID, plan] of this.#renamePlans) {
@@ -826,22 +1081,22 @@ class SessionAnchors {
826
1081
  planRename(sessionID, output) {
827
1082
  if (!output || typeof output !== "object")
828
1083
  return;
829
- const record = output;
830
- if (record.applied === true) {
1084
+ const record2 = output;
1085
+ if (record2.applied === true) {
831
1086
  this.#renamePlans.delete(sessionID);
832
1087
  return;
833
1088
  }
834
- const { file, old_name: oldName, new_name: newName, others } = record;
1089
+ const { file, old_name: oldName, new_name: newName, others } = record2;
835
1090
  if (typeof file !== "string" || typeof oldName !== "string" || typeof newName !== "string")
836
1091
  return;
837
- const files = new Set([path2.resolve(file)]);
1092
+ const files = new Set([path4.resolve(file)]);
838
1093
  if (Array.isArray(others)) {
839
1094
  for (const item of others) {
840
1095
  if (!item || typeof item !== "object")
841
1096
  continue;
842
1097
  const otherFile = item.file;
843
1098
  if (typeof otherFile === "string")
844
- files.add(path2.resolve(otherFile));
1099
+ files.add(path4.resolve(otherFile));
845
1100
  }
846
1101
  }
847
1102
  this.#renamePlans.set(sessionID, { summary: `${oldName} -> ${newName}`, files });
@@ -864,253 +1119,41 @@ ${[...paths].sort().map((filePath) => `- ${filePath}`).join(`
864
1119
  `);
865
1120
  }
866
1121
  }
867
- function resolvePath(directory, filePath) {
868
- return path2.resolve(directory, filePath);
1122
+
1123
+ // src/tools.ts
1124
+ import { readFile as readFile2, stat as stat3 } from "node:fs/promises";
1125
+ import { tool as tool2 } from "@opencode-ai/plugin";
1126
+
1127
+ // src/edits.ts
1128
+ import { lstat, mkdir, open, readFile } from "node:fs/promises";
1129
+ import path5 from "node:path";
1130
+ var fileMutationTails = new Map;
1131
+ async function withFileMutationQueue(filePath, operation) {
1132
+ const key = path5.resolve(filePath);
1133
+ const previous = fileMutationTails.get(key);
1134
+ let release = () => {};
1135
+ const current = new Promise((resolve) => {
1136
+ release = resolve;
1137
+ });
1138
+ fileMutationTails.set(key, current);
1139
+ if (previous)
1140
+ await previous;
1141
+ try {
1142
+ return await operation();
1143
+ } finally {
1144
+ release();
1145
+ if (fileMutationTails.get(key) === current)
1146
+ fileMutationTails.delete(key);
1147
+ }
869
1148
  }
870
- function containsPath(directory, filePath) {
871
- const relative = path2.relative(directory, filePath);
872
- return relative === "" || !relative.startsWith(`..${path2.sep}`) && relative !== ".." && !path2.isAbsolute(relative);
873
- }
874
- async function resolveSymlinks(filePath) {
875
- let unresolved = path2.resolve(filePath);
876
- let symlinks = 0;
877
- while (true) {
878
- const parsed = path2.parse(unresolved);
879
- const parts = unresolved.slice(parsed.root.length).split(path2.sep).filter(Boolean);
880
- let resolved = parsed.root;
881
- let restart = false;
882
- for (let index = 0;index < parts.length; index++) {
883
- const candidate = path2.join(resolved, parts[index]);
884
- const info = await lstat(candidate);
885
- if (!info.isSymbolicLink()) {
886
- resolved = candidate;
887
- continue;
888
- }
889
- symlinks++;
890
- if (symlinks > 40) {
891
- const error = new Error(`too many symbolic links: ${filePath}`);
892
- error.code = "ELOOP";
893
- throw error;
894
- }
895
- const target = await readlink(candidate);
896
- unresolved = path2.resolve(path2.dirname(candidate), target, ...parts.slice(index + 1));
897
- restart = true;
898
- break;
899
- }
900
- if (!restart)
901
- return resolved;
902
- }
903
- }
904
- async function portableRealpath(filePath) {
905
- try {
906
- return await realpath(filePath);
907
- } catch (error) {
908
- if (error.code !== "EACCES")
909
- throw error;
910
- return resolveSymlinks(filePath);
911
- }
912
- }
913
- async function canonicalPath(filePath) {
914
- const missing = [];
915
- let existing = filePath;
916
- while (true) {
917
- try {
918
- return path2.join(await portableRealpath(existing), ...missing.reverse());
919
- } catch (error) {
920
- if (error.code !== "ENOENT")
921
- throw error;
922
- const parent = path2.dirname(existing);
923
- if (parent === existing)
924
- throw error;
925
- missing.push(path2.basename(existing));
926
- existing = parent;
927
- }
928
- }
929
- }
930
- async function authorizeExternal(context, filePath) {
931
- const [accessPath, directory, worktree] = await Promise.all([
932
- canonicalPath(filePath),
933
- canonicalPath(context.directory),
934
- context.worktree === "/" ? Promise.resolve("/") : canonicalPath(context.worktree)
935
- ]);
936
- if (containsPath(directory, accessPath) || worktree !== "/" && containsPath(worktree, accessPath)) {
937
- return;
938
- }
939
- const info = await stat(accessPath).catch(() => {
940
- return;
941
- });
942
- const parentDir = info?.isDirectory() ? accessPath : path2.dirname(accessPath);
943
- const pattern = path2.join(parentDir, "*").replaceAll("\\", "/");
944
- await context.ask({
945
- permission: "external_directory",
946
- patterns: [pattern],
947
- always: [pattern],
948
- metadata: { filepath: filePath, parentDir }
949
- });
950
- }
951
- async function rejectSymlinkMutation(filePath) {
952
- const info = await lstat(filePath).catch((error) => {
953
- if (error.code === "ENOENT")
954
- return;
955
- throw error;
956
- });
957
- if (info?.isSymbolicLink())
958
- throw new Error(`refusing to modify symbolic link: ${filePath}`);
959
- }
960
- async function authorizeRead(context, filePath) {
961
- await authorizeExternal(context, filePath);
962
- await context.ask({
963
- permission: "read",
964
- patterns: [path2.relative(context.worktree, filePath).replaceAll("\\", "/")],
965
- always: ["*"],
966
- metadata: {}
967
- });
968
- }
969
- async function authorizeSearch(context, filePath, pattern) {
970
- await context.ask({
971
- permission: "grep",
972
- patterns: [pattern],
973
- always: ["*"],
974
- metadata: { pattern, path: filePath }
975
- });
976
- await authorizeExternal(context, filePath);
977
- }
978
- async function authorizeEdit(context, filePaths, diff = "", authorizeExternalPaths = true) {
979
- if (authorizeExternalPaths) {
980
- for (const filePath of filePaths)
981
- await authorizeExternal(context, filePath);
982
- }
983
- const patterns = filePaths.map((filePath) => path2.relative(context.worktree, filePath).replaceAll("\\", "/"));
984
- await context.ask({
985
- permission: "edit",
986
- patterns,
987
- always: ["*"],
988
- metadata: { filepath: filePaths.join(", "), diff }
989
- });
990
- }
991
- function withAt(args, line, column) {
992
- args.push("--at", buildLineLocation(line, column));
993
- }
994
- function readRangeArgs(filePath, start, end) {
995
- const extra = [];
996
- if (start > 1)
997
- withAt(extra, start);
998
- extra.push("--end", String(end));
999
- return digestArgs(filePath, "content", extra);
1000
- }
1001
- async function runDigestContent(context, args, options = {}) {
1002
- return requireDigestFacet(await runReadSeek(context, args, options), "content");
1003
- }
1004
- function readSeekBinaryPath() {
1005
- return resolveReadSeekBinaryFromPackages(resolveFromHost);
1006
- }
1007
- async function preserveAbortReason(signal, cancelable, invoke) {
1008
- try {
1009
- const value = await invoke();
1010
- if (cancelable)
1011
- signal.throwIfAborted();
1012
- return value;
1013
- } catch (error) {
1014
- if (cancelable)
1015
- signal.throwIfAborted();
1016
- throw error;
1017
- }
1018
- }
1019
- async function runReadSeekRaw(context, args, options = {}) {
1020
- context.abort.throwIfAborted();
1021
- return preserveAbortReason(context.abort, options.cancelable !== false, () => runReadSeekBunText(readSeekBinaryPath(), args, {
1022
- cwd: context.directory,
1023
- signal: context.abort,
1024
- cancelable: options.cancelable,
1025
- stdin: options.stdin,
1026
- timeoutMs: options.timeoutMs
1027
- }));
1028
- }
1029
- async function runReadSeek(context, args, options = {}) {
1030
- context.abort.throwIfAborted();
1031
- return preserveAbortReason(context.abort, options.cancelable !== false, () => runReadSeekBunJson(readSeekBinaryPath(), args, {
1032
- cwd: context.directory,
1033
- signal: context.abort,
1034
- cancelable: options.cancelable,
1035
- stdin: options.stdin,
1036
- timeoutMs: options.timeoutMs
1037
- }));
1038
- }
1039
- async function postMutationPreview(context, args, filePath, changed) {
1040
- try {
1041
- return await runDigestContent(context, args, {
1042
- cancelable: false,
1043
- timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
1044
- });
1045
- } catch (error) {
1046
- const previewError = error instanceof Error ? error.message : String(error);
1047
- return {
1048
- file: filePath,
1049
- changed,
1050
- preview_error: previewError,
1051
- warning: "File mutation completed, but the post-write preview failed. Do not retry the mutation; read the file to verify content and refresh anchors."
1052
- };
1053
- }
1054
- }
1055
- var readSeekCacheInit;
1056
- function readSeekCacheDir() {
1057
- const xdgCacheHome = process.env.XDG_CACHE_HOME?.trim();
1058
- const cacheHome = xdgCacheHome || path2.join(homedir(), ".cache");
1059
- return path2.join(cacheHome, "opencode", "readseek");
1060
- }
1061
- async function ensureReadSeekCache(context) {
1062
- const cacheDir = readSeekCacheDir();
1063
- try {
1064
- const info = await stat(cacheDir);
1065
- if (!info.isDirectory())
1066
- throw new Error(`readseek cache is not a directory: ${cacheDir}`);
1067
- return cacheDir;
1068
- } catch (error) {
1069
- if (error.code !== "ENOENT")
1070
- throw error;
1071
- }
1072
- readSeekCacheInit ??= runReadSeekRaw(context, ["--readseek-dir", cacheDir, "init"]).then(() => cacheDir).finally(() => {
1073
- readSeekCacheInit = undefined;
1074
- });
1075
- return readSeekCacheInit;
1076
- }
1077
- function render(value) {
1078
- return JSON.stringify(value, null, 2);
1079
- }
1080
- function collectAnchoredFiles(value, files) {
1081
- if (Array.isArray(value)) {
1082
- for (const item of value)
1083
- collectAnchoredFiles(item, files);
1084
- return;
1085
- }
1086
- if (!value || typeof value !== "object")
1087
- return;
1088
- const output = value;
1089
- const hashlines = output.hashlines;
1090
- if (typeof output.file === "string" && Array.isArray(hashlines) && hashlines.some((line) => typeof record(line).line === "number" && typeof record(line).hash === "string"))
1091
- files.add(output.file);
1092
- for (const item of Object.values(output))
1093
- collectAnchoredFiles(item, files);
1094
- }
1095
- var fileMutationTails = new Map;
1096
- var MAX_PERMISSION_DIFF_BYTES = 32 * 1024;
1097
- async function withFileMutationQueue(filePath, operation) {
1098
- const key = path2.resolve(filePath);
1099
- const previous = fileMutationTails.get(key);
1100
- let release = () => {};
1101
- const current = new Promise((resolve) => {
1102
- release = resolve;
1103
- });
1104
- fileMutationTails.set(key, current);
1105
- if (previous)
1106
- await previous;
1107
- try {
1108
- return await operation();
1109
- } finally {
1110
- release();
1111
- if (fileMutationTails.get(key) === current)
1112
- fileMutationTails.delete(key);
1113
- }
1149
+ function parseAnchor(anchor) {
1150
+ const match = /^(\d+):([0-9a-fA-F]{3})$/.exec(anchor.trim());
1151
+ if (!match)
1152
+ throw new Error(`invalid LINE:HASH anchor: ${anchor}`);
1153
+ const line = Number(match[1]);
1154
+ if (line === 0)
1155
+ throw new Error(`anchor line must be greater than zero: ${anchor}`);
1156
+ return { line, hash: match[2].toLowerCase() };
1114
1157
  }
1115
1158
  function validateEdits(value) {
1116
1159
  if (!Array.isArray(value) || value.length === 0)
@@ -1153,15 +1196,6 @@ function validateEdits(value) {
1153
1196
  }
1154
1197
  }
1155
1198
  }
1156
- function parseAnchor(anchor) {
1157
- const match = /^(\d+):([0-9a-fA-F]{3})$/.exec(anchor.trim());
1158
- if (!match)
1159
- throw new Error(`invalid LINE:HASH anchor: ${anchor}`);
1160
- const line = Number(match[1]);
1161
- if (line === 0)
1162
- throw new Error(`anchor line must be greater than zero: ${anchor}`);
1163
- return { line, hash: match[2].toLowerCase() };
1164
- }
1165
1199
  function firstChangedLine(before, after) {
1166
1200
  const beforeLines = normalizeToLf(before).split(`
1167
1201
  `);
@@ -1195,7 +1229,7 @@ async function writeHandle(handle, content) {
1195
1229
  }
1196
1230
  }
1197
1231
  async function writeText(filePath, expected, content) {
1198
- await mkdir(path2.dirname(filePath), { recursive: true });
1232
+ await mkdir(path5.dirname(filePath), { recursive: true });
1199
1233
  if (!expected.exists) {
1200
1234
  let handle2;
1201
1235
  try {
@@ -1232,63 +1266,6 @@ async function writeText(filePath, expected, content) {
1232
1266
  await handle.close();
1233
1267
  }
1234
1268
  }
1235
- function renameFiles(value) {
1236
- const output = record(value);
1237
- const files = [];
1238
- if (typeof output.file === "string" && items(output.edits).length > 0)
1239
- files.push(path2.resolve(output.file));
1240
- for (const other of items(output.others)) {
1241
- const item = record(other);
1242
- if (typeof item.file === "string" && items(item.edits).length > 0)
1243
- files.push(path2.resolve(item.file));
1244
- }
1245
- return files;
1246
- }
1247
- function normalizeToLf(content) {
1248
- return content.replace(/\r\n/g, `
1249
- `).replace(/\r/g, `
1250
- `);
1251
- }
1252
- function diffLines(content) {
1253
- if (content === "")
1254
- return [];
1255
- const lines = content.split(`
1256
- `);
1257
- if (lines.at(-1) === "")
1258
- lines.pop();
1259
- return lines.map((line) => line.replace(/\r/g, "\\r"));
1260
- }
1261
- function hasFinalNewline(content) {
1262
- return content === "" || /[\r\n]$/.test(content);
1263
- }
1264
- function diffRange(start, count) {
1265
- return count === 0 ? `${start},0` : `${start + 1},${count}`;
1266
- }
1267
- function utf8Prefix(value, maxBytes) {
1268
- const bytes = Buffer.from(value, "utf8");
1269
- if (bytes.length <= maxBytes)
1270
- return value;
1271
- let end = maxBytes;
1272
- const decoder = new TextDecoder("utf-8", { fatal: true });
1273
- while (end > 0) {
1274
- try {
1275
- return decoder.decode(bytes.subarray(0, end));
1276
- } catch {
1277
- end--;
1278
- }
1279
- }
1280
- return "";
1281
- }
1282
- function boundDocumentOutput(value) {
1283
- const lines = value.split(`
1284
- `);
1285
- let output = lines.length > MAX_DOCUMENT_OUTPUT_LINES ? lines.slice(0, MAX_DOCUMENT_OUTPUT_LINES).join(`
1286
- `) : value;
1287
- const truncated = output !== value || Buffer.byteLength(output, "utf8") > MAX_DOCUMENT_OUTPUT_BYTES;
1288
- output = utf8Prefix(output, MAX_DOCUMENT_OUTPUT_BYTES);
1289
- return truncated ? `${output}
1290
- [… document view truncated; narrow it with page, node, kind, or depth]` : output;
1291
- }
1292
1269
  function previewDiffLine(prefix, line) {
1293
1270
  const value = `${prefix}${line}`;
1294
1271
  const maxBytes = 256;
@@ -1381,21 +1358,137 @@ function simpleDiff(filePath, before, after) {
1381
1358
  ].join(`
1382
1359
  `);
1383
1360
  }
1384
- function identifiedName(value) {
1385
- if (!value || typeof value !== "object")
1361
+
1362
+ // src/paths.ts
1363
+ import { lstat as lstat2, readlink, realpath, stat as stat2 } from "node:fs/promises";
1364
+ import path6 from "node:path";
1365
+ function resolvePath(directory, filePath) {
1366
+ return path6.resolve(directory, filePath);
1367
+ }
1368
+ function containsPath(directory, filePath) {
1369
+ const relative = path6.relative(directory, filePath);
1370
+ return relative === "" || !relative.startsWith(`..${path6.sep}`) && relative !== ".." && !path6.isAbsolute(relative);
1371
+ }
1372
+ async function resolveSymlinks(filePath) {
1373
+ let unresolved = path6.resolve(filePath);
1374
+ let symlinks = 0;
1375
+ while (true) {
1376
+ const parsed = path6.parse(unresolved);
1377
+ const parts = unresolved.slice(parsed.root.length).split(path6.sep).filter(Boolean);
1378
+ let resolved = parsed.root;
1379
+ let restart = false;
1380
+ for (let index = 0;index < parts.length; index++) {
1381
+ const candidate = path6.join(resolved, parts[index]);
1382
+ const info = await lstat2(candidate);
1383
+ if (!info.isSymbolicLink()) {
1384
+ resolved = candidate;
1385
+ continue;
1386
+ }
1387
+ symlinks++;
1388
+ if (symlinks > 40) {
1389
+ const error = new Error(`too many symbolic links: ${filePath}`);
1390
+ error.code = "ELOOP";
1391
+ throw error;
1392
+ }
1393
+ const target = await readlink(candidate);
1394
+ unresolved = path6.resolve(path6.dirname(candidate), target, ...parts.slice(index + 1));
1395
+ restart = true;
1396
+ break;
1397
+ }
1398
+ if (!restart)
1399
+ return resolved;
1400
+ }
1401
+ }
1402
+ async function portableRealpath(filePath) {
1403
+ try {
1404
+ return await realpath(filePath);
1405
+ } catch (error) {
1406
+ if (error.code !== "EACCES")
1407
+ throw error;
1408
+ return resolveSymlinks(filePath);
1409
+ }
1410
+ }
1411
+ async function canonicalPath(filePath) {
1412
+ const missing = [];
1413
+ let existing = filePath;
1414
+ while (true) {
1415
+ try {
1416
+ return path6.join(await portableRealpath(existing), ...missing.reverse());
1417
+ } catch (error) {
1418
+ if (error.code !== "ENOENT")
1419
+ throw error;
1420
+ const parent = path6.dirname(existing);
1421
+ if (parent === existing)
1422
+ throw error;
1423
+ missing.push(path6.basename(existing));
1424
+ existing = parent;
1425
+ }
1426
+ }
1427
+ }
1428
+ async function authorizeExternal(context, filePath) {
1429
+ const [accessPath, directory, worktree] = await Promise.all([
1430
+ canonicalPath(filePath),
1431
+ canonicalPath(context.directory),
1432
+ context.worktree === "/" ? Promise.resolve("/") : canonicalPath(context.worktree)
1433
+ ]);
1434
+ if (containsPath(directory, accessPath) || worktree !== "/" && containsPath(worktree, accessPath)) {
1386
1435
  return;
1387
- const identifier = value.identifier;
1388
- if (!identifier || typeof identifier !== "object")
1436
+ }
1437
+ const info = await stat2(accessPath).catch(() => {
1389
1438
  return;
1390
- const text = identifier.text;
1391
- return typeof text === "string" ? text : undefined;
1439
+ });
1440
+ const parentDir = info?.isDirectory() ? accessPath : path6.dirname(accessPath);
1441
+ const pattern = path6.join(parentDir, "*").replaceAll("\\", "/");
1442
+ await context.ask({
1443
+ permission: "external_directory",
1444
+ patterns: [pattern],
1445
+ always: [pattern],
1446
+ metadata: { filepath: filePath, parentDir }
1447
+ });
1392
1448
  }
1393
- function record(value) {
1394
- return value && typeof value === "object" ? value : {};
1449
+ async function rejectSymlinkMutation(filePath) {
1450
+ const info = await lstat2(filePath).catch((error) => {
1451
+ if (error.code === "ENOENT")
1452
+ return;
1453
+ throw error;
1454
+ });
1455
+ if (info?.isSymbolicLink())
1456
+ throw new Error(`refusing to modify symbolic link: ${filePath}`);
1395
1457
  }
1396
- function items(value) {
1397
- return Array.isArray(value) ? value : [];
1458
+ async function authorizeRead(context, filePath) {
1459
+ await authorizeExternal(context, filePath);
1460
+ await context.ask({
1461
+ permission: "read",
1462
+ patterns: [path6.relative(context.worktree, filePath).replaceAll("\\", "/")],
1463
+ always: ["*"],
1464
+ metadata: {}
1465
+ });
1466
+ }
1467
+ async function authorizeSearch(context, filePath, pattern) {
1468
+ await context.ask({
1469
+ permission: "grep",
1470
+ patterns: [pattern],
1471
+ always: ["*"],
1472
+ metadata: { pattern, path: filePath }
1473
+ });
1474
+ await authorizeExternal(context, filePath);
1475
+ }
1476
+ async function authorizeEdit(context, filePaths, diff = "", authorizeExternalPaths = true) {
1477
+ if (authorizeExternalPaths) {
1478
+ for (const filePath of filePaths)
1479
+ await authorizeExternal(context, filePath);
1480
+ }
1481
+ const patterns = filePaths.map((filePath) => path6.relative(context.worktree, filePath).replaceAll("\\", "/"));
1482
+ await context.ask({
1483
+ permission: "edit",
1484
+ patterns,
1485
+ always: ["*"],
1486
+ metadata: { filepath: filePaths.join(", "), diff }
1487
+ });
1398
1488
  }
1489
+
1490
+ // src/presentation.ts
1491
+ import { tool } from "@opencode-ai/plugin";
1399
1492
  function initialTitle(kind, args) {
1400
1493
  switch (kind) {
1401
1494
  case "read":
@@ -1531,384 +1624,390 @@ function readseekTool(description, args, kind, execute, formatResult = (result)
1531
1624
  }
1532
1625
  });
1533
1626
  }
1534
- var ReadSeekPlugin = async (_input, options) => {
1535
- const anchors = new SessionAnchors;
1536
- const imagePolicy = resolveImagePolicy(options);
1537
- const imageModes = imagePolicy === "auto" ? ["none", "all", "ocr", "caption", "objects"] : ["all", "ocr", "caption", "objects"];
1627
+
1628
+ // src/tools.ts
1629
+ function createReadSeekTools(imagePolicy) {
1538
1630
  return {
1539
- tool: {
1540
- readseek_view: readseekTool(openCodeToolDescription("view"), {
1541
- path: tool.schema.string().describe("Document path relative to the project directory"),
1542
- node: tool.schema.string().min(1).optional().describe(PARAM_DESCRIPTIONS.node),
1543
- page: tool.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.page),
1544
- kind: tool.schema.enum(DOCUMENT_NODE_KIND_VALUES).optional().describe(PARAM_DESCRIPTIONS.kind),
1545
- depth: tool.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.depth),
1546
- outline: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.outline),
1547
- visionMode: tool.schema.enum(VISION_ANALYSIS_MODE_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionModeView),
1548
- visionLevel: tool.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionLevel)
1549
- }, "view", async (input, context) => {
1550
- const filePath = resolvePath(context.directory, input.path);
1551
- await authorizeRead(context, filePath);
1552
- const visionMode = input.visionMode;
1553
- const visionLevel = input.visionLevel;
1554
- const vision = validateVisionLevelVsMode({
1555
- visionMode,
1556
- visionLevel,
1557
- outline: input.outline
1558
- });
1559
- if (!vision.ok)
1560
- throw new Error(vision.error);
1561
- const cacheDir = await ensureReadSeekCache(context);
1562
- const args = buildViewArgs({
1563
- path: filePath,
1564
- node: input.node,
1565
- page: input.page,
1566
- kind: input.kind,
1567
- depth: input.depth,
1568
- outline: input.outline,
1569
- visionMode,
1570
- visionLevel,
1571
- readseekDir: cacheDir
1572
- });
1573
- return runReadSeekRaw(context, args, visionMode === undefined ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
1574
- }, (result) => ({ output: boundDocumentOutput(String(result)) })),
1575
- readseek_digest: readseekTool(openCodeToolDescription("digest"), {
1576
- path: tool.schema.string().describe("Path relative to the project directory"),
1577
- select: tool.schema.string().optional().describe("Comma-separated facets: metadata, content, map, diagnostics, identity; defaults to content"),
1578
- at: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.at),
1579
- end: tool.schema.number().int().positive().optional().describe("Last line to include; content facet only"),
1580
- limit: tool.schema.number().int().positive().optional().describe("Maximum number of lines; content facet only"),
1581
- language: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1582
- visionMode: tool.schema.enum(IMAGE_MODES).optional().describe("Standalone image mode for the content facet: none, all, ocr, caption, or objects"),
1583
- visionLevel: tool.schema.enum(VISION_LEVEL_VALUES).optional().describe("Vision inference level: low (default), medium, or high; content facet only")
1584
- }, "digest", async (input, context) => {
1585
- const filePath = resolvePath(context.directory, input.path);
1586
- await authorizeRead(context, filePath);
1587
- const select = (input.select ?? "content").trim() || "content";
1588
- const visionMode = input.visionMode;
1589
- const visionLevel = input.visionLevel;
1590
- const vision = validateVisionLevelVsMode({ visionMode, visionLevel });
1591
- if (!vision.ok)
1592
- throw new Error(vision.error);
1593
- const cacheDir = await ensureReadSeekCache(context);
1594
- const args = buildDigestArgs({
1595
- path: filePath,
1596
- select,
1597
- at: input.at,
1598
- end: input.end,
1599
- limit: input.limit,
1600
- language: input.language,
1601
- visionMode,
1602
- visionLevel,
1603
- readseekDir: cacheDir
1604
- });
1605
- return runReadSeek(context, args, visionMode === undefined || visionMode === "none" ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
1606
- }),
1607
- readseek_grep: readseekTool(openCodeToolDescription("grep"), {
1608
- pattern: tool.schema.string().describe("Text or regular expression to search for; set literal for exact text"),
1609
- path: tool.schema.string().optional().describe("File or directory to search; defaults to the project directory"),
1610
- glob: tool.schema.string().optional().describe("File-name glob, such as **/*.ts"),
1611
- literal: tool.schema.boolean().optional().describe("Treat pattern as literal text"),
1612
- ignoreCase: tool.schema.boolean().optional().describe("Ignore case"),
1613
- context: tool.schema.number().int().nonnegative().optional().describe("Surrounding lines for each match"),
1614
- limit: tool.schema.number().int().positive().optional().describe("Maximum matching lines; defaults to 100")
1615
- }, "grep", async (input, context) => {
1616
- const target = resolvePath(context.directory, input.path ?? ".");
1617
- const pattern = input.pattern;
1618
- await authorizeSearch(context, target, pattern);
1619
- let expression;
1620
- try {
1621
- const source = input.literal ? pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") : pattern;
1622
- expression = new RegExp(source, input.ignoreCase ? "i" : "");
1623
- } catch (error) {
1624
- throw new Error(`invalid regular expression: ${error instanceof Error ? error.message : String(error)}`);
1631
+ readseek_view: readseekTool(openCodeToolDescription("view"), {
1632
+ path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
1633
+ node: tool2.schema.string().min(1).optional().describe(PARAM_DESCRIPTIONS.node),
1634
+ page: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.page),
1635
+ kind: tool2.schema.enum(DOCUMENT_NODE_KIND_VALUES).optional().describe(PARAM_DESCRIPTIONS.kind),
1636
+ depth: tool2.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.depth),
1637
+ outline: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.outline),
1638
+ visionMode: tool2.schema.enum(VISION_ANALYSIS_MODE_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionModeView),
1639
+ visionLevel: tool2.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionLevel)
1640
+ }, "view", async (input, context) => {
1641
+ const filePath = resolvePath(context.directory, input.path);
1642
+ await authorizeRead(context, filePath);
1643
+ const visionMode = input.visionMode;
1644
+ const visionLevel = input.visionLevel;
1645
+ const vision = validateVisionLevelVsMode({
1646
+ visionMode,
1647
+ visionLevel,
1648
+ outline: input.outline
1649
+ });
1650
+ if (!vision.ok)
1651
+ throw new Error(vision.error);
1652
+ const cacheDir = await ensureReadSeekCache(context);
1653
+ const args = buildViewArgs({
1654
+ path: filePath,
1655
+ node: input.node,
1656
+ page: input.page,
1657
+ kind: input.kind,
1658
+ depth: input.depth,
1659
+ outline: input.outline,
1660
+ visionMode,
1661
+ visionLevel,
1662
+ readseekDir: cacheDir
1663
+ });
1664
+ return runReadSeekRaw(context, args, visionMode === undefined ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
1665
+ }, (result) => ({ output: boundDocumentOutput(String(result)) })),
1666
+ readseek_digest: readseekTool(openCodeToolDescription("digest"), {
1667
+ path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
1668
+ select: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.selectComma),
1669
+ at: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.at),
1670
+ end: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.end),
1671
+ limit: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.limit),
1672
+ language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageOverride),
1673
+ visionMode: tool2.schema.enum(IMAGE_MODES).optional().describe(PARAM_DESCRIPTIONS.visionModeDigest),
1674
+ visionLevel: tool2.schema.enum(VISION_LEVEL_VALUES).optional().describe(PARAM_DESCRIPTIONS.visionLevel)
1675
+ }, "digest", async (input, context) => {
1676
+ const filePath = resolvePath(context.directory, input.path);
1677
+ await authorizeRead(context, filePath);
1678
+ const select = (input.select ?? "content").trim() || "content";
1679
+ const visionMode = input.visionMode;
1680
+ const visionLevel = input.visionLevel;
1681
+ const vision = validateVisionLevelVsMode({ visionMode, visionLevel });
1682
+ if (!vision.ok)
1683
+ throw new Error(vision.error);
1684
+ const cacheDir = await ensureReadSeekCache(context);
1685
+ const args = buildDigestArgs({
1686
+ path: filePath,
1687
+ select,
1688
+ at: input.at,
1689
+ end: input.end,
1690
+ limit: input.limit,
1691
+ language: input.language,
1692
+ visionMode,
1693
+ visionLevel,
1694
+ readseekDir: cacheDir
1695
+ });
1696
+ return runReadSeek(context, args, visionMode === undefined || visionMode === "none" ? {} : { timeoutMs: DEFAULT_VISION_TIMEOUT_MS });
1697
+ }),
1698
+ readseek_grep: readseekTool(openCodeToolDescription("grep"), {
1699
+ pattern: tool2.schema.string().describe(PARAM_DESCRIPTIONS.grepPattern),
1700
+ path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1701
+ glob: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.glob),
1702
+ literal: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.literal),
1703
+ ignoreCase: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignoreCase),
1704
+ context: tool2.schema.number().int().nonnegative().optional().describe(PARAM_DESCRIPTIONS.context),
1705
+ limit: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.matchLimit)
1706
+ }, "grep", async (input, context) => {
1707
+ const target = resolvePath(context.directory, input.path ?? ".");
1708
+ const pattern = input.pattern;
1709
+ await authorizeSearch(context, target, pattern);
1710
+ let expression;
1711
+ try {
1712
+ const source = input.literal ? pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") : pattern;
1713
+ expression = new RegExp(source, input.ignoreCase ? "i" : "");
1714
+ } catch (error) {
1715
+ throw new Error(`invalid regular expression: ${error instanceof Error ? error.message : String(error)}`);
1716
+ }
1717
+ const info = await stat3(target);
1718
+ const files = [];
1719
+ if (info.isFile())
1720
+ files.push(target);
1721
+ else if (info.isDirectory()) {
1722
+ const glob = new Bun.Glob(input.glob ?? "**/*");
1723
+ for await (const filePath of glob.scan({ cwd: target, absolute: true, onlyFiles: true, followSymlinks: false })) {
1724
+ files.push(filePath);
1625
1725
  }
1626
- const info = await stat(target);
1627
- const files = [];
1628
- if (info.isFile())
1629
- files.push(target);
1630
- else if (info.isDirectory()) {
1631
- const glob = new Bun.Glob(input.glob ?? "**/*");
1632
- for await (const filePath of glob.scan({ cwd: target, absolute: true, onlyFiles: true, followSymlinks: false })) {
1633
- files.push(filePath);
1634
- }
1635
- } else
1636
- throw new Error(`grep target is not a file or directory: ${target}`);
1637
- const maxMatches = input.limit ?? 100;
1638
- const contextLines = input.context ?? 0;
1639
- const results = [];
1640
- let totalMatches = 0;
1641
- let truncated = false;
1642
- for (const filePath of files.sort()) {
1643
- context.abort.throwIfAborted();
1644
- if (truncated)
1645
- break;
1646
- const buffer = await readFile(filePath);
1647
- if (buffer.includes(0))
1648
- continue;
1649
- const lines = buffer.toString("utf8").replace(/\r\n/g, `
1726
+ } else
1727
+ throw new Error(`grep target is not a file or directory: ${target}`);
1728
+ const maxMatches = input.limit ?? 100;
1729
+ const contextLines = input.context ?? 0;
1730
+ const results = [];
1731
+ let totalMatches = 0;
1732
+ let truncated = false;
1733
+ for (const filePath of files.sort()) {
1734
+ context.abort.throwIfAborted();
1735
+ if (truncated)
1736
+ break;
1737
+ const buffer = await readFile2(filePath);
1738
+ if (buffer.includes(0))
1739
+ continue;
1740
+ const lines = buffer.toString("utf8").replace(/\r\n/g, `
1650
1741
  `).split(`
1651
1742
  `);
1652
- const matchLines = new Set;
1653
- const ranges = [];
1654
- for (let index = 0;index < lines.length; index++) {
1655
- expression.lastIndex = 0;
1656
- if (!expression.test(lines[index] ?? ""))
1657
- continue;
1658
- if (totalMatches === maxMatches) {
1659
- truncated = true;
1660
- break;
1661
- }
1662
- const line = index + 1;
1663
- matchLines.add(line);
1664
- ranges.push([Math.max(1, line - contextLines), Math.min(lines.length, line + contextLines)]);
1665
- totalMatches++;
1666
- }
1667
- if (ranges.length === 0)
1743
+ const matchLines = new Set;
1744
+ const ranges = [];
1745
+ for (let index = 0;index < lines.length; index++) {
1746
+ expression.lastIndex = 0;
1747
+ if (!expression.test(lines[index] ?? ""))
1668
1748
  continue;
1669
- const mergedRanges = [];
1670
- for (const [start, end] of ranges) {
1671
- const previous = mergedRanges.at(-1);
1672
- if (previous && start <= previous[1] + 1)
1673
- previous[1] = Math.max(previous[1], end);
1674
- else
1675
- mergedRanges.push([start, end]);
1676
- }
1677
- const linesByNumber = new Map;
1678
- for (const [start, end] of mergedRanges) {
1679
- const output = record(await runDigestContent(context, readRangeArgs(filePath, start, end)));
1680
- for (const hashline of items(output.hashlines)) {
1681
- const line = record(hashline).line;
1682
- if (typeof line === "number" && !linesByNumber.has(line))
1683
- linesByNumber.set(line, hashline);
1684
- }
1749
+ if (totalMatches === maxMatches) {
1750
+ truncated = true;
1751
+ break;
1685
1752
  }
1686
- const hashlines = [...linesByNumber.values()];
1687
- const matches = hashlines.filter((hashline) => {
1753
+ const line = index + 1;
1754
+ matchLines.add(line);
1755
+ ranges.push([Math.max(1, line - contextLines), Math.min(lines.length, line + contextLines)]);
1756
+ totalMatches++;
1757
+ }
1758
+ if (ranges.length === 0)
1759
+ continue;
1760
+ const mergedRanges = [];
1761
+ for (const [start, end] of ranges) {
1762
+ const previous = mergedRanges.at(-1);
1763
+ if (previous && start <= previous[1] + 1)
1764
+ previous[1] = Math.max(previous[1], end);
1765
+ else
1766
+ mergedRanges.push([start, end]);
1767
+ }
1768
+ const linesByNumber = new Map;
1769
+ for (const [start, end] of mergedRanges) {
1770
+ const output = record(await runDigestContent(context, readRangeArgs(filePath, start, end)));
1771
+ for (const hashline of items(output.hashlines)) {
1688
1772
  const line = record(hashline).line;
1689
- return typeof line === "number" && matchLines.has(line);
1690
- });
1691
- results.push({ file: filePath, matches, hashlines });
1773
+ if (typeof line === "number" && !linesByNumber.has(line))
1774
+ linesByNumber.set(line, hashline);
1775
+ }
1692
1776
  }
1693
- return { results, truncated };
1694
- }),
1695
- readseek_search: readseekTool(openCodeToolDescription("search"), {
1696
- pattern: tool.schema.string().describe(PARAM_DESCRIPTIONS.pattern),
1697
- path: tool.schema.string().optional().describe("File or directory to search; defaults to the project directory"),
1698
- language: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1699
- cached: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1700
- others: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1701
- ignored: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
1702
- }, "search", async (input, context) => {
1703
- const target = resolvePath(context.directory, input.path ?? ".");
1704
- const args = buildSearchArgs({
1705
- path: target,
1706
- pattern: input.pattern,
1707
- language: input.language,
1708
- cached: input.cached,
1709
- others: input.others,
1710
- ignored: input.ignored
1711
- });
1712
- await authorizeSearch(context, target, input.pattern);
1713
- return runReadSeek(context, args);
1714
- }),
1715
- readseek_def: readseekTool(openCodeToolDescription("def"), {
1716
- name: tool.schema.string().describe(PARAM_DESCRIPTIONS.name),
1717
- path: tool.schema.string().optional().describe("File or directory to search; defaults to the project directory"),
1718
- language: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1719
- cached: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1720
- others: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1721
- ignored: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
1722
- }, "def", async (input, context) => {
1723
- const target = resolvePath(context.directory, input.path ?? ".");
1724
- const args = buildDefArgs({
1725
- path: target,
1726
- name: input.name,
1727
- language: input.language,
1728
- format: "plain",
1729
- cached: input.cached,
1730
- others: input.others,
1731
- ignored: input.ignored
1777
+ const hashlines = [...linesByNumber.values()];
1778
+ const matches = hashlines.filter((hashline) => {
1779
+ const line = record(hashline).line;
1780
+ return typeof line === "number" && matchLines.has(line);
1732
1781
  });
1733
- await authorizeSearch(context, target, input.name);
1734
- return runReadSeek(context, args);
1735
- }),
1736
- readseek_refs: readseekTool(openCodeToolDescription("refs"), {
1737
- name: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.refsName),
1738
- path: tool.schema.string().optional().describe("File or directory to search; defaults to the project directory"),
1739
- language: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1740
- scope: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.scope),
1741
- line: tool.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.line),
1742
- column: tool.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.column),
1743
- cached: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1744
- others: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1745
- ignored: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
1746
- }, "refs", async (input, context) => {
1747
- const target = resolvePath(context.directory, input.path ?? ".");
1748
- const name = input.name;
1749
- const args = buildRefsArgs({
1750
- path: target,
1751
- name,
1752
- scope: input.scope,
1753
- line: input.line,
1754
- column: input.column,
1755
- language: input.language,
1756
- cached: input.cached,
1757
- others: input.others,
1758
- ignored: input.ignored
1759
- });
1760
- await authorizeSearch(context, target, name ?? "*");
1761
- return runReadSeek(context, args);
1762
- }),
1763
- readseek_rename: readseekTool(openCodeToolDescription("rename"), {
1764
- path: tool.schema.string().describe("Path relative to the project directory"),
1765
- line: tool.schema.number().int().positive().describe(PARAM_DESCRIPTIONS.renameLine),
1766
- column: tool.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.column),
1767
- to: tool.schema.string().min(1).describe(PARAM_DESCRIPTIONS.to),
1768
- workspace: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.workspace),
1769
- apply: tool.schema.boolean().optional().describe("Apply the verified edits; defaults to true")
1770
- }, "rename", async (input, context) => {
1771
- const filePath = resolvePath(context.directory, input.path);
1782
+ results.push({ file: filePath, matches, hashlines });
1783
+ }
1784
+ return { results, truncated };
1785
+ }),
1786
+ readseek_search: readseekTool(openCodeToolDescription("search"), {
1787
+ pattern: tool2.schema.string().describe(PARAM_DESCRIPTIONS.pattern),
1788
+ path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1789
+ language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1790
+ cached: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1791
+ others: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1792
+ ignored: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
1793
+ }, "search", async (input, context) => {
1794
+ const target = resolvePath(context.directory, input.path ?? ".");
1795
+ const args = buildSearchArgs({
1796
+ path: target,
1797
+ pattern: input.pattern,
1798
+ language: input.language,
1799
+ cached: input.cached,
1800
+ others: input.others,
1801
+ ignored: input.ignored
1802
+ });
1803
+ await authorizeSearch(context, target, input.pattern);
1804
+ return runReadSeek(context, args);
1805
+ }),
1806
+ readseek_def: readseekTool(openCodeToolDescription("def"), {
1807
+ name: tool2.schema.string().describe(PARAM_DESCRIPTIONS.name),
1808
+ path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1809
+ language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1810
+ cached: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1811
+ others: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1812
+ ignored: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
1813
+ }, "def", async (input, context) => {
1814
+ const target = resolvePath(context.directory, input.path ?? ".");
1815
+ const args = buildDefArgs({
1816
+ path: target,
1817
+ name: input.name,
1818
+ language: input.language,
1819
+ format: "plain",
1820
+ cached: input.cached,
1821
+ others: input.others,
1822
+ ignored: input.ignored
1823
+ });
1824
+ await authorizeSearch(context, target, input.name);
1825
+ return runReadSeek(context, args);
1826
+ }),
1827
+ readseek_refs: readseekTool(openCodeToolDescription("refs"), {
1828
+ name: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.refsName),
1829
+ path: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.projectSearchPath),
1830
+ language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.language),
1831
+ scope: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.scope),
1832
+ line: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.line),
1833
+ column: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.column),
1834
+ cached: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.cached),
1835
+ others: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.others),
1836
+ ignored: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.ignored)
1837
+ }, "refs", async (input, context) => {
1838
+ const target = resolvePath(context.directory, input.path ?? ".");
1839
+ const name = input.name;
1840
+ const args = buildRefsArgs({
1841
+ path: target,
1842
+ name,
1843
+ scope: input.scope,
1844
+ line: input.line,
1845
+ column: input.column,
1846
+ language: input.language,
1847
+ cached: input.cached,
1848
+ others: input.others,
1849
+ ignored: input.ignored
1850
+ });
1851
+ await authorizeSearch(context, target, name ?? "*");
1852
+ return runReadSeek(context, args);
1853
+ }),
1854
+ readseek_rename: readseekTool(openCodeToolDescription("rename"), {
1855
+ path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
1856
+ line: tool2.schema.number().int().positive().describe(PARAM_DESCRIPTIONS.renameLine),
1857
+ column: tool2.schema.number().int().positive().optional().describe(PARAM_DESCRIPTIONS.column),
1858
+ to: tool2.schema.string().min(1).describe(PARAM_DESCRIPTIONS.to),
1859
+ workspace: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.workspace),
1860
+ apply: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.applyDefaultTrue)
1861
+ }, "rename", async (input, context) => {
1862
+ const filePath = resolvePath(context.directory, input.path);
1863
+ await authorizeRead(context, filePath);
1864
+ if (input.workspace) {
1865
+ const identifyArgs = digestArgs(filePath, "identity");
1866
+ withAt(identifyArgs, input.line, input.column);
1867
+ const envelope = await runReadSeek(context, identifyArgs);
1868
+ const name = identifiedName(requireDigestFacet(envelope, "identity"));
1869
+ if (!name)
1870
+ throw new Error("readseek could not identify a binding at the rename cursor");
1871
+ await authorizeSearch(context, context.directory, name);
1872
+ }
1873
+ const args = buildRenameArgs({
1874
+ path: filePath,
1875
+ to: input.to,
1876
+ line: input.line,
1877
+ column: input.column,
1878
+ workspace: input.workspace ? context.directory : undefined
1879
+ });
1880
+ const plan = await runReadSeek(context, args);
1881
+ if (input.apply === false)
1882
+ return plan;
1883
+ const files = renameFiles(plan);
1884
+ const planHash = record(plan).plan_hash;
1885
+ if (typeof planHash !== "string" || planHash.length === 0) {
1886
+ throw new Error("readseek rename plan did not include a plan hash");
1887
+ }
1888
+ await authorizeEdit(context, files);
1889
+ return runReadSeek(context, buildRenameArgs({
1890
+ path: filePath,
1891
+ to: input.to,
1892
+ line: input.line,
1893
+ column: input.column,
1894
+ workspace: input.workspace ? context.directory : undefined,
1895
+ apply: true,
1896
+ planHash
1897
+ }), {
1898
+ cancelable: false,
1899
+ timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
1900
+ });
1901
+ }),
1902
+ readseek_edit: readseekTool(openCodeToolDescription("edit"), {
1903
+ path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
1904
+ edits: tool2.schema.array(tool2.schema.union([
1905
+ tool2.schema.object({
1906
+ set_line: tool2.schema.object({
1907
+ anchor: tool2.schema.string().describe(PARAM_DESCRIPTIONS.anchor),
1908
+ new_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newTextLine)
1909
+ }).strict()
1910
+ }).strict(),
1911
+ tool2.schema.object({
1912
+ replace_lines: tool2.schema.object({
1913
+ start_anchor: tool2.schema.string().describe(PARAM_DESCRIPTIONS.startAnchor),
1914
+ end_anchor: tool2.schema.string().describe(PARAM_DESCRIPTIONS.endAnchor),
1915
+ new_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newTextRange)
1916
+ }).strict()
1917
+ }).strict(),
1918
+ tool2.schema.object({
1919
+ insert_after: tool2.schema.object({
1920
+ anchor: tool2.schema.string().describe(PARAM_DESCRIPTIONS.insertAnchor),
1921
+ new_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newTextInsert)
1922
+ }).strict()
1923
+ }).strict(),
1924
+ tool2.schema.object({
1925
+ replace: tool2.schema.object({
1926
+ old_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.oldText),
1927
+ new_text: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newText),
1928
+ all: tool2.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.all)
1929
+ }).strict()
1930
+ }).strict(),
1931
+ tool2.schema.object({
1932
+ replace_symbol: tool2.schema.object({
1933
+ symbol: tool2.schema.string().describe(PARAM_DESCRIPTIONS.symbol),
1934
+ new_body: tool2.schema.string().describe(PARAM_DESCRIPTIONS.newBody)
1935
+ }).strict()
1936
+ }).strict()
1937
+ ])).min(1).describe(PARAM_DESCRIPTIONS.edits),
1938
+ language: tool2.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageSymbol)
1939
+ }, "edit", async (input, context) => {
1940
+ const filePath = resolvePath(context.directory, input.path);
1941
+ validateEdits(input.edits);
1942
+ return withFileMutationQueue(filePath, async () => {
1943
+ context.abort.throwIfAborted();
1772
1944
  await authorizeRead(context, filePath);
1773
- if (input.workspace) {
1774
- const identifyArgs = digestArgs(filePath, "identity");
1775
- withAt(identifyArgs, input.line, input.column);
1776
- const envelope = await runReadSeek(context, identifyArgs);
1777
- const name = identifiedName(requireDigestFacet(envelope, "identity"));
1778
- if (!name)
1779
- throw new Error("readseek could not identify a binding at the rename cursor");
1780
- await authorizeSearch(context, context.directory, name);
1781
- }
1782
- const args = buildRenameArgs({
1945
+ await rejectSymlinkMutation(filePath);
1946
+ const edits = input.edits;
1947
+ const request = JSON.stringify({ edits });
1948
+ const baseArgs = buildEditArgs({
1783
1949
  path: filePath,
1784
- to: input.to,
1785
- line: input.line,
1786
- column: input.column,
1787
- workspace: input.workspace ? context.directory : undefined
1950
+ language: input.language,
1951
+ requestFromStdin: true
1788
1952
  });
1789
- const plan = await runReadSeek(context, args);
1790
- if (input.apply === false)
1791
- return plan;
1792
- const files = renameFiles(plan);
1793
- const planHash = record(plan).plan_hash;
1794
- if (typeof planHash !== "string" || planHash.length === 0) {
1795
- throw new Error("readseek rename plan did not include a plan hash");
1953
+ const plan = record(await runReadSeek(context, baseArgs, { stdin: request }));
1954
+ if (typeof plan.before_content !== "string" || typeof plan.content !== "string" || typeof plan.plan_hash !== "string") {
1955
+ throw new Error("readseek edit plan is missing before_content, content, or plan_hash");
1956
+ }
1957
+ const before = plan.before_content;
1958
+ const after = plan.content;
1959
+ const previewStart = Math.max(1, firstChangedLine(before, after) - 3);
1960
+ const previewArgs = readRangeArgs(filePath, previewStart, previewStart + EDIT_RESULT_READ_LIMIT - 1);
1961
+ if (before === after) {
1962
+ return postMutationPreview(context, previewArgs, filePath, false);
1796
1963
  }
1797
- await authorizeEdit(context, files);
1798
- return runReadSeek(context, buildRenameArgs({
1964
+ await authorizeEdit(context, [filePath], simpleDiff(filePath, before, after), false);
1965
+ context.abort.throwIfAborted();
1966
+ const applied = record(await runReadSeek(context, buildEditArgs({
1799
1967
  path: filePath,
1800
- to: input.to,
1801
- line: input.line,
1802
- column: input.column,
1803
- workspace: input.workspace ? context.directory : undefined,
1968
+ language: input.language,
1969
+ requestFromStdin: true,
1804
1970
  apply: true,
1805
- planHash
1971
+ planHash: plan.plan_hash
1806
1972
  }), {
1807
1973
  cancelable: false,
1974
+ stdin: request,
1808
1975
  timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
1809
- });
1810
- }),
1811
- readseek_edit: readseekTool(openCodeToolDescription("edit"), {
1812
- path: tool.schema.string().describe("Path relative to the project directory"),
1813
- edits: tool.schema.array(tool.schema.union([
1814
- tool.schema.object({
1815
- set_line: tool.schema.object({
1816
- anchor: tool.schema.string().describe(PARAM_DESCRIPTIONS.anchor),
1817
- new_text: tool.schema.string().describe(PARAM_DESCRIPTIONS.newTextLine)
1818
- }).strict()
1819
- }).strict(),
1820
- tool.schema.object({
1821
- replace_lines: tool.schema.object({
1822
- start_anchor: tool.schema.string().describe(PARAM_DESCRIPTIONS.startAnchor),
1823
- end_anchor: tool.schema.string().describe(PARAM_DESCRIPTIONS.endAnchor),
1824
- new_text: tool.schema.string().describe(PARAM_DESCRIPTIONS.newTextRange)
1825
- }).strict()
1826
- }).strict(),
1827
- tool.schema.object({
1828
- insert_after: tool.schema.object({
1829
- anchor: tool.schema.string().describe(PARAM_DESCRIPTIONS.insertAnchor),
1830
- new_text: tool.schema.string().describe(PARAM_DESCRIPTIONS.newTextInsert)
1831
- }).strict()
1832
- }).strict(),
1833
- tool.schema.object({
1834
- replace: tool.schema.object({
1835
- old_text: tool.schema.string().describe(PARAM_DESCRIPTIONS.oldText),
1836
- new_text: tool.schema.string().describe(PARAM_DESCRIPTIONS.newText),
1837
- all: tool.schema.boolean().optional().describe(PARAM_DESCRIPTIONS.all)
1838
- }).strict()
1839
- }).strict(),
1840
- tool.schema.object({
1841
- replace_symbol: tool.schema.object({
1842
- symbol: tool.schema.string().describe(PARAM_DESCRIPTIONS.symbol),
1843
- new_body: tool.schema.string().describe(PARAM_DESCRIPTIONS.newBody)
1844
- }).strict()
1845
- }).strict()
1846
- ])).min(1).describe(PARAM_DESCRIPTIONS.edits),
1847
- language: tool.schema.string().optional().describe(PARAM_DESCRIPTIONS.languageSymbol)
1848
- }, "edit", async (input, context) => {
1849
- const filePath = resolvePath(context.directory, input.path);
1850
- validateEdits(input.edits);
1851
- return withFileMutationQueue(filePath, async () => {
1852
- context.abort.throwIfAborted();
1853
- await authorizeRead(context, filePath);
1854
- await rejectSymlinkMutation(filePath);
1855
- const edits = input.edits;
1856
- const request = JSON.stringify({ edits });
1857
- const baseArgs = buildEditArgs({
1858
- path: filePath,
1859
- language: input.language,
1860
- requestFromStdin: true
1861
- });
1862
- const plan = record(await runReadSeek(context, baseArgs, { stdin: request }));
1863
- if (typeof plan.before_content !== "string" || typeof plan.content !== "string" || typeof plan.plan_hash !== "string") {
1864
- throw new Error("readseek edit plan is missing before_content, content, or plan_hash");
1865
- }
1866
- const before = plan.before_content;
1867
- const after = plan.content;
1868
- const previewStart = Math.max(1, firstChangedLine(before, after) - 3);
1869
- const previewArgs = readRangeArgs(filePath, previewStart, previewStart + EDIT_RESULT_READ_LIMIT - 1);
1870
- if (before === after) {
1871
- return postMutationPreview(context, previewArgs, filePath, false);
1872
- }
1873
- await authorizeEdit(context, [filePath], simpleDiff(filePath, before, after), false);
1874
- context.abort.throwIfAborted();
1875
- const applied = record(await runReadSeek(context, buildEditArgs({
1876
- path: filePath,
1877
- language: input.language,
1878
- requestFromStdin: true,
1879
- apply: true,
1880
- planHash: plan.plan_hash
1881
- }), {
1882
- cancelable: false,
1883
- stdin: request,
1884
- timeoutMs: DEFAULT_READSEEK_TIMEOUT_MS
1885
- }));
1886
- if (applied.applied !== true) {
1887
- throw new Error("readseek did not confirm that the edit was applied");
1888
- }
1889
- return postMutationPreview(context, previewArgs, filePath, true);
1890
- });
1891
- }),
1892
- readseek_write: readseekTool(openCodeToolDescription("write"), {
1893
- path: tool.schema.string().describe("Path relative to the project directory"),
1894
- content: tool.schema.string().describe("Complete text file content")
1895
- }, "write", async (input, context) => {
1896
- const filePath = resolvePath(context.directory, input.path);
1897
- await authorizeExternal(context, filePath);
1898
- return withFileMutationQueue(filePath, async () => {
1899
- context.abort.throwIfAborted();
1900
- await rejectSymlinkMutation(filePath);
1901
- const before = await readTextSnapshot(filePath);
1902
- const content = input.content;
1903
- if (content.includes("\x00"))
1904
- throw new Error("write content must be text");
1905
- await authorizeEdit(context, [filePath], simpleDiff(filePath, before.content, content), false);
1906
- context.abort.throwIfAborted();
1907
- await writeText(filePath, before, content);
1908
- return postMutationPreview(context, digestArgs(filePath, "content", ["--end", String(DEFAULT_READ_LIMIT)]), filePath, !before.exists || before.content !== content);
1909
- });
1910
- })
1911
- },
1976
+ }));
1977
+ if (applied.applied !== true) {
1978
+ throw new Error("readseek did not confirm that the edit was applied");
1979
+ }
1980
+ return postMutationPreview(context, previewArgs, filePath, true);
1981
+ });
1982
+ }),
1983
+ readseek_write: readseekTool(openCodeToolDescription("write"), {
1984
+ path: tool2.schema.string().describe(PARAM_DESCRIPTIONS.projectPath),
1985
+ content: tool2.schema.string().describe(PARAM_DESCRIPTIONS.content)
1986
+ }, "write", async (input, context) => {
1987
+ const filePath = resolvePath(context.directory, input.path);
1988
+ await authorizeExternal(context, filePath);
1989
+ return withFileMutationQueue(filePath, async () => {
1990
+ context.abort.throwIfAborted();
1991
+ await rejectSymlinkMutation(filePath);
1992
+ const before = await readTextSnapshot(filePath);
1993
+ const content = input.content;
1994
+ if (content.includes("\x00"))
1995
+ throw new Error("write content must be text");
1996
+ await authorizeEdit(context, [filePath], simpleDiff(filePath, before.content, content), false);
1997
+ context.abort.throwIfAborted();
1998
+ await writeText(filePath, before, content);
1999
+ return postMutationPreview(context, digestArgs(filePath, "content", ["--end", String(DEFAULT_READ_LIMIT)]), filePath, !before.exists || before.content !== content);
2000
+ });
2001
+ })
2002
+ };
2003
+ }
2004
+
2005
+ // index.ts
2006
+ var ReadSeekPlugin = async (_input, options) => {
2007
+ const anchors = new SessionAnchors;
2008
+ const imagePolicy = resolveImagePolicy(options);
2009
+ return {
2010
+ tool: createReadSeekTools(imagePolicy),
1912
2011
  "experimental.chat.system.transform": async (_input2, output) => {
1913
2012
  output.system.push(TOOL_POLICY);
1914
2013
  },
@@ -1945,7 +2044,7 @@ var ReadSeekPlugin = async (_input, options) => {
1945
2044
  const files = new Set;
1946
2045
  collectAnchoredFiles(result, files);
1947
2046
  for (const filePath of files)
1948
- anchors.mark(input.sessionID, path2.resolve(filePath));
2047
+ anchors.mark(input.sessionID, path7.resolve(filePath));
1949
2048
  } catch {}
1950
2049
  },
1951
2050
  "experimental.session.compacting": async (input, output) => {