@mikode13/harness 8.0.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/dist/engines/domain/model/llmAgent.js +26 -8
- package/dist/engines/domain/model/llmAgent.js.map +1 -1
- package/dist/factory/infrastructure/agentLLMFactory.d.ts +2 -2
- package/dist/factory/infrastructure/agentLLMFactory.js +13 -9
- package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
- package/dist/llm/infrastructure/claudeLLMClient.js +3 -3
- package/dist/llm/infrastructure/claudeLLMClient.js.map +1 -1
- package/dist/tools/infrastructure/fileTools.d.ts +3 -2
- package/dist/tools/infrastructure/fileTools.js +7 -6
- package/dist/tools/infrastructure/fileTools.js.map +1 -1
- package/dist/tools/infrastructure/trackedReadFile.d.ts +5 -0
- package/dist/tools/infrastructure/trackedReadFile.js +21 -1
- package/dist/tools/infrastructure/trackedReadFile.js.map +1 -1
- package/dist/tools/infrastructure/workspaceTools.d.ts +23 -1
- package/dist/tools/infrastructure/workspaceTools.js +53 -1
- package/dist/tools/infrastructure/workspaceTools.js.map +1 -1
- package/package.json +1 -1
|
@@ -21,6 +21,24 @@ export declare const readFileInput: z.ZodObject<{
|
|
|
21
21
|
fromLine: z.ZodNullable<z.ZodNumber>;
|
|
22
22
|
lineCount: z.ZodNullable<z.ZodNumber>;
|
|
23
23
|
}, z.core.$strip>;
|
|
24
|
+
/** What `readFiles` takes, shared with the harness's own version of it. */
|
|
25
|
+
export declare const readFilesDescription: string;
|
|
26
|
+
export declare const readFilesInput: z.ZodObject<{
|
|
27
|
+
files: z.ZodArray<z.ZodObject<{
|
|
28
|
+
path: z.ZodString;
|
|
29
|
+
fromLine: z.ZodNullable<z.ZodNumber>;
|
|
30
|
+
lineCount: z.ZodNullable<z.ZodNumber>;
|
|
31
|
+
}, z.core.$strip>>;
|
|
32
|
+
}, z.core.$strip>;
|
|
33
|
+
type ReadRequest = z.infer<typeof readFileInput>;
|
|
34
|
+
export declare function readFilesSummary({ files }: {
|
|
35
|
+
files: ReadRequest[];
|
|
36
|
+
}): string;
|
|
37
|
+
/**
|
|
38
|
+
* Reads each request with `read`, in order, each under its own heading, and reports a file it
|
|
39
|
+
* could not read in its place. Fails only when it read none of them, or was cancelled.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readEach(files: ReadRequest[], read: (file: ReadRequest) => Promise<string>, signal: AbortSignal): Promise<string>;
|
|
24
42
|
/** The range a `readFile` call asks for, checked: strict mode allows the schema no minimum. */
|
|
25
43
|
export declare function readRange({ fromLine, lineCount, }: {
|
|
26
44
|
fromLine: number | null;
|
|
@@ -35,5 +53,9 @@ export declare function readFileSummary({ path, fromLine, lineCount, }: {
|
|
|
35
53
|
fromLine: number | null;
|
|
36
54
|
lineCount: number | null;
|
|
37
55
|
}): string;
|
|
38
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* The read-only tools an agent explores a repository with: `listFiles`, `searchText`,
|
|
58
|
+
* `readFile` and `readFiles`.
|
|
59
|
+
*/
|
|
39
60
|
export declare function createWorkspaceTools(workspace: Workspace): Tool[];
|
|
61
|
+
export {};
|
|
@@ -5,6 +5,8 @@ import { cutLine, maxLineLength } from "../domain/lineLength.js";
|
|
|
5
5
|
const maxFiles = 200;
|
|
6
6
|
const maxMatches = 100;
|
|
7
7
|
const maxLines = 200;
|
|
8
|
+
// How many files, or ranges, one `readFiles` call may read.
|
|
9
|
+
const maxReads = 10;
|
|
8
10
|
// What one read or one search may bring into the context, whatever its lines: 200 lines of
|
|
9
11
|
// prose can be 50 KB, and the context carries them on every step after.
|
|
10
12
|
export const maxResultBytes = 16 * 1024;
|
|
@@ -68,6 +70,41 @@ export const readFileInput = z.object({
|
|
|
68
70
|
.nullable()
|
|
69
71
|
.describe(`How many lines to read, at most ${String(maxLines)}. Defaults to ${String(maxLines)}.`),
|
|
70
72
|
});
|
|
73
|
+
/** What `readFiles` takes, shared with the harness's own version of it. */
|
|
74
|
+
export const readFilesDescription = `Reads several files, or several ranges of files, in one call: each as readFile reads it, at most ${String(maxReads)}. A file that cannot be read is reported in its place.`;
|
|
75
|
+
export const readFilesInput = z.object({
|
|
76
|
+
files: z.array(readFileInput).describe('What to read, in order: each a file and its range.'),
|
|
77
|
+
});
|
|
78
|
+
export function readFilesSummary({ files }) {
|
|
79
|
+
return files.map(readFileSummary).join(', ');
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Reads each request with `read`, in order, each under its own heading, and reports a file it
|
|
83
|
+
* could not read in its place. Fails only when it read none of them, or was cancelled.
|
|
84
|
+
*/
|
|
85
|
+
export async function readEach(files, read, signal) {
|
|
86
|
+
if (files.length === 0)
|
|
87
|
+
throw new Error('files must name at least one file to read');
|
|
88
|
+
if (files.length > maxReads) {
|
|
89
|
+
throw new Error(`files may name at most ${String(maxReads)} reads at once; got ${String(files.length)}`);
|
|
90
|
+
}
|
|
91
|
+
let failed = 0;
|
|
92
|
+
const results = [];
|
|
93
|
+
for (const file of files) {
|
|
94
|
+
try {
|
|
95
|
+
results.push(`== ${readFileSummary(file)}\n${await read(file)}`);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
signal.throwIfAborted();
|
|
99
|
+
failed++;
|
|
100
|
+
results.push(`== ${file.path}\nCould not read it: ${error instanceof Error ? error.message : String(error)}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const output = results.join('\n\n');
|
|
104
|
+
if (failed === files.length)
|
|
105
|
+
throw new Error(output);
|
|
106
|
+
return output;
|
|
107
|
+
}
|
|
71
108
|
/** The range a `readFile` call asks for, checked: strict mode allows the schema no minimum. */
|
|
72
109
|
export function readRange({ fromLine, lineCount, }) {
|
|
73
110
|
const from = fromLine ?? 1;
|
|
@@ -94,7 +131,10 @@ function assertPositiveInteger(name, value) {
|
|
|
94
131
|
throw new Error(`${name} must be a whole number of at least 1; got ${String(value)}`);
|
|
95
132
|
}
|
|
96
133
|
}
|
|
97
|
-
/**
|
|
134
|
+
/**
|
|
135
|
+
* The read-only tools an agent explores a repository with: `listFiles`, `searchText`,
|
|
136
|
+
* `readFile` and `readFiles`.
|
|
137
|
+
*/
|
|
98
138
|
export function createWorkspaceTools(workspace) {
|
|
99
139
|
return [
|
|
100
140
|
defineTool({
|
|
@@ -159,6 +199,18 @@ export function createWorkspaceTools(workspace) {
|
|
|
159
199
|
return renderLines({ path, lines, from, totalLines, truncated });
|
|
160
200
|
},
|
|
161
201
|
}),
|
|
202
|
+
defineTool({
|
|
203
|
+
name: 'readFiles',
|
|
204
|
+
description: readFilesDescription,
|
|
205
|
+
risk: 'safe',
|
|
206
|
+
input: readFilesInput,
|
|
207
|
+
summarize: readFilesSummary,
|
|
208
|
+
execute: ({ files }, signal) => readEach(files, async ({ path, fromLine, lineCount }) => {
|
|
209
|
+
const { from, count } = readRange({ fromLine, lineCount });
|
|
210
|
+
const { lines, totalLines, truncated } = await workspace.readFile({ path, fromLine: from, lineCount: count }, signal);
|
|
211
|
+
return renderLines({ path, lines, from, totalLines, truncated });
|
|
212
|
+
}, signal),
|
|
213
|
+
}),
|
|
162
214
|
];
|
|
163
215
|
}
|
|
164
216
|
//# sourceMappingURL=workspaceTools.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspaceTools.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/workspaceTools.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { Tool } from '../domain/tool.ts';\nimport type { Workspace } from '../domain/workspace.ts';\nimport { defineTool } from './defineTool.ts';\nimport { cutLine, maxLineLength } from '../domain/lineLength.ts';\n\n// What one call may return. The model can ask for fewer lines, never for more.\nconst maxFiles = 200;\nconst maxMatches = 100;\nconst maxLines = 200;\n// What one read or one search may bring into the context, whatever its lines: 200 lines of\n// prose can be 50 KB, and the context carries them on every step after.\nexport const maxResultBytes = 16 * 1024;\n\n// Strict mode has no optional fields: an argument the model may leave out is nullable.\nconst scope = {\n\tpath: z.string().nullable().describe('A folder or file to stay inside, relative to the root.'),\n\tglob: z\n\t\t.string()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t'Only paths matching this glob, relative to the root. \"*\" stays inside one folder and \"**\" crosses folders: \"**/*.ts\" is every TypeScript file, \"*.ts\" only those at the top.',\n\t\t),\n};\n\n/**\n * Lines of a file as the model reads them: each numbered, a line too long cut, and a notice for\n * what was cut or lies beyond.\n */\nexport function renderLines({\n\tpath,\n\tlines,\n\tfrom,\n\ttotalLines,\n\ttruncated,\n}: {\n\t/** The file, which decides how long a line may be. */\n\tpath: string;\n\tlines: string[];\n\tfrom: number;\n\ttotalLines: number;\n\ttruncated: boolean;\n}): string {\n\t// The file's length tells the model where its range went wrong.\n\tif (lines.length === 0) {\n\t\treturn `No lines from ${String(from)}: the file has ${String(totalLines)} lines.`;\n\t}\n\n\tconst numbered = withinBytes(\n\t\tlines.map((text, index) => `${String(from + index)}: ${cutLine(path, text)}`),\n\t);\n\tconst shown = lines.slice(0, numbered.length);\n\tconst max = maxLineLength(path);\n\tconst cut = shown.filter(text => text.length > max).length;\n\tconst next = from + shown.length;\n\tconst notices = [\n\t\t...(cut > 0\n\t\t\t? [`[${String(cut)} lines were longer than ${String(max)} characters and were cut.]`]\n\t\t\t: []),\n\t\t...(shown.length < lines.length\n\t\t\t? [\n\t\t\t\t\t`[Lines ${String(from)} to ${String(next - 1)} fill the ${String(maxResultBytes / 1024)} KB one read may return. The file has ${String(totalLines)} lines. Read from line ${String(next)} to continue.]`,\n\t\t\t\t]\n\t\t\t: truncated\n\t\t\t\t? [\n\t\t\t\t\t\t`[The file has ${String(totalLines)} lines. Read from line ${String(next)} to continue.]`,\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t];\n\treturn [...numbered, ...notices].join('\\n');\n}\n\n/** The first of `lines` that fit in `maxResultBytes` together, and always the first one. */\nfunction withinBytes(lines: string[]): string[] {\n\tlet bytes = 0;\n\tconst kept: string[] = [];\n\tfor (const line of lines) {\n\t\tbytes += Buffer.byteLength(line, 'utf8') + 1;\n\t\tif (bytes > maxResultBytes && kept.length > 0) break;\n\t\tkept.push(line);\n\t}\n\treturn kept;\n}\n\n/** What `readFile` takes, shared with the harness's own version of it. */\nexport const readFileDescription = 'Reads lines of a file, each prefixed with its number.';\nexport const readFileInput = z.object({\n\tpath: z.string().describe('The file, relative to the root.'),\n\tfromLine: z.number().nullable().describe('The first line to read, from 1. Defaults to 1.'),\n\tlineCount: z\n\t\t.number()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t`How many lines to read, at most ${String(maxLines)}. Defaults to ${String(maxLines)}.`,\n\t\t),\n});\n\n/** The range a `readFile` call asks for, checked: strict mode allows the schema no minimum. */\nexport function readRange({\n\tfromLine,\n\tlineCount,\n}: {\n\tfromLine: number | null;\n\tlineCount: number | null;\n}): { from: number; count: number } {\n\tconst from = fromLine ?? 1;\n\tconst requested = lineCount ?? maxLines;\n\tassertPositiveInteger('fromLine', from);\n\tassertPositiveInteger('lineCount', requested);\n\treturn { from, count: Math.min(requested, maxLines) };\n}\n\n/** A stable description of a read request, using only the range the model supplied. */\nexport function readFileSummary({\n\tpath,\n\tfromLine,\n\tlineCount,\n}: {\n\tpath: string;\n\tfromLine: number | null;\n\tlineCount: number | null;\n}): string {\n\tconst suppliedFrom = fromLine ?? 1;\n\tconst from = Number.isInteger(suppliedFrom) && suppliedFrom > 0 ? suppliedFrom : 1;\n\tconst suppliedCount = lineCount ?? maxLines;\n\tconst requested = Number.isInteger(suppliedCount) && suppliedCount > 0 ? suppliedCount : maxLines;\n\tconst count = Math.min(requested, maxLines);\n\treturn `${path}:${String(from)}-${String(from + count - 1)}`;\n}\n\nfunction scopeSummary(path: string | null, glob: string | null): string {\n\tconst scopePath = path ?? '.';\n\treturn glob ? `${scopePath === '.' ? '' : `${scopePath}/`}${glob}` : scopePath;\n}\n\nfunction assertPositiveInteger(name: string, value: number): void {\n\tif (!Number.isInteger(value) || value < 1) {\n\t\tthrow new Error(`${name} must be a whole number of at least 1; got ${String(value)}`);\n\t}\n}\n\n/** The three read-only tools an agent explores a repository with. */\nexport function createWorkspaceTools(workspace: Workspace): Tool[] {\n\treturn [\n\t\tdefineTool({\n\t\t\tname: 'listFiles',\n\t\t\tdescription:\n\t\t\t\t'Lists the files of the repository, sorted. Ignored files such as dependencies are left out.',\n\t\t\trisk: 'safe',\n\t\t\tinput: z.object(scope),\n\t\t\tsummarize: ({ path, glob }) => scopeSummary(path, glob),\n\t\t\texecute: async ({ path, glob }, signal) => {\n\t\t\t\t// The port takes \"no scope\" as a missing field.\n\t\t\t\tconst { files, total, truncated } = await workspace.listFiles(\n\t\t\t\t\t{ path: path ?? undefined, glob: glob ?? undefined, limit: maxFiles },\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\n\t\t\t\t// Nothing found is an answer too, not an empty string.\n\t\t\t\tif (files.length === 0) {\n\t\t\t\t\treturn 'No files.';\n\t\t\t\t}\n\n\t\t\t\tconst notice = truncated\n\t\t\t\t\t? `\\n[Showing ${String(files.length)} of ${String(total)} files. Narrow the path or glob to see the rest.]`\n\t\t\t\t\t: '';\n\t\t\t\treturn files.join('\\n') + notice;\n\t\t\t},\n\t\t}),\n\n\t\tdefineTool({\n\t\t\tname: 'searchText',\n\t\t\tdescription:\n\t\t\t\t'Searches the repository for a regular expression and returns each matching line as path:line: text.',\n\t\t\trisk: 'safe',\n\t\t\tinput: z.object({\n\t\t\t\tpattern: z.string().describe('A regular expression.'),\n\t\t\t\tignoreCase: z.boolean().nullable().describe('Match regardless of case. Defaults to false.'),\n\t\t\t\t...scope,\n\t\t\t}),\n\t\t\tsummarize: ({ path, glob, pattern }) => `\"${pattern}\" in ${scopeSummary(path, glob)}`,\n\t\t\texecute: async ({ path, glob, ignoreCase, pattern }, signal) => {\n\t\t\t\tconst { matches, total, truncated } = await workspace.searchText(\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: path ?? undefined,\n\t\t\t\t\t\tglob: glob ?? undefined,\n\t\t\t\t\t\tignoreCase: ignoreCase ?? false,\n\t\t\t\t\t\tpattern,\n\t\t\t\t\t\tlimit: maxMatches,\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\n\t\t\t\tif (matches.length === 0) {\n\t\t\t\t\treturn 'No matches.';\n\t\t\t\t}\n\n\t\t\t\tconst results = withinBytes(\n\t\t\t\t\tmatches.map(\n\t\t\t\t\t\tmatch => `${match.path}:${String(match.line)}: ${cutLine(match.path, match.text)}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\tconst notice =\n\t\t\t\t\tresults.length < matches.length\n\t\t\t\t\t\t? `\\n[Showing ${String(results.length)} of ${String(total)} matches, the ${String(maxResultBytes / 1024)} KB one search may return. Narrow the pattern, the path or the glob to see the rest.]`\n\t\t\t\t\t\t: truncated\n\t\t\t\t\t\t\t? `\\n[Showing ${String(results.length)} of ${String(total)} matches. Narrow the path or glob to see the rest.]`\n\t\t\t\t\t\t\t: '';\n\t\t\t\treturn results.join('\\n') + notice;\n\t\t\t},\n\t\t}),\n\n\t\tdefineTool({\n\t\t\tname: 'readFile',\n\t\t\tdescription: readFileDescription,\n\t\t\trisk: 'safe',\n\t\t\tinput: readFileInput,\n\t\t\tsummarize: readFileSummary,\n\t\t\texecute: async ({ fromLine, lineCount, path }, signal) => {\n\t\t\t\t// Checked before reading.\n\t\t\t\tconst { from, count } = readRange({ fromLine, lineCount });\n\t\t\t\tconst { lines, totalLines, truncated } = await workspace.readFile(\n\t\t\t\t\t{ path, fromLine: from, lineCount: count },\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\t\t\t\treturn renderLines({ path, lines, from, totalLines, truncated });\n\t\t\t},\n\t\t}),\n\t];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"workspaceTools.js","sourceRoot":"","sources":["../../../src/tools/infrastructure/workspaceTools.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\nimport type { Tool } from '../domain/tool.ts';\nimport type { Workspace } from '../domain/workspace.ts';\nimport { defineTool } from './defineTool.ts';\nimport { cutLine, maxLineLength } from '../domain/lineLength.ts';\n\n// What one call may return. The model can ask for fewer lines, never for more.\nconst maxFiles = 200;\nconst maxMatches = 100;\nconst maxLines = 200;\n// How many files, or ranges, one `readFiles` call may read.\nconst maxReads = 10;\n// What one read or one search may bring into the context, whatever its lines: 200 lines of\n// prose can be 50 KB, and the context carries them on every step after.\nexport const maxResultBytes = 16 * 1024;\n\n// Strict mode has no optional fields: an argument the model may leave out is nullable.\nconst scope = {\n\tpath: z.string().nullable().describe('A folder or file to stay inside, relative to the root.'),\n\tglob: z\n\t\t.string()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t'Only paths matching this glob, relative to the root. \"*\" stays inside one folder and \"**\" crosses folders: \"**/*.ts\" is every TypeScript file, \"*.ts\" only those at the top.',\n\t\t),\n};\n\n/**\n * Lines of a file as the model reads them: each numbered, a line too long cut, and a notice for\n * what was cut or lies beyond.\n */\nexport function renderLines({\n\tpath,\n\tlines,\n\tfrom,\n\ttotalLines,\n\ttruncated,\n}: {\n\t/** The file, which decides how long a line may be. */\n\tpath: string;\n\tlines: string[];\n\tfrom: number;\n\ttotalLines: number;\n\ttruncated: boolean;\n}): string {\n\t// The file's length tells the model where its range went wrong.\n\tif (lines.length === 0) {\n\t\treturn `No lines from ${String(from)}: the file has ${String(totalLines)} lines.`;\n\t}\n\n\tconst numbered = withinBytes(\n\t\tlines.map((text, index) => `${String(from + index)}: ${cutLine(path, text)}`),\n\t);\n\tconst shown = lines.slice(0, numbered.length);\n\tconst max = maxLineLength(path);\n\tconst cut = shown.filter(text => text.length > max).length;\n\tconst next = from + shown.length;\n\tconst notices = [\n\t\t...(cut > 0\n\t\t\t? [`[${String(cut)} lines were longer than ${String(max)} characters and were cut.]`]\n\t\t\t: []),\n\t\t...(shown.length < lines.length\n\t\t\t? [\n\t\t\t\t\t`[Lines ${String(from)} to ${String(next - 1)} fill the ${String(maxResultBytes / 1024)} KB one read may return. The file has ${String(totalLines)} lines. Read from line ${String(next)} to continue.]`,\n\t\t\t\t]\n\t\t\t: truncated\n\t\t\t\t? [\n\t\t\t\t\t\t`[The file has ${String(totalLines)} lines. Read from line ${String(next)} to continue.]`,\n\t\t\t\t\t]\n\t\t\t\t: []),\n\t];\n\treturn [...numbered, ...notices].join('\\n');\n}\n\n/** The first of `lines` that fit in `maxResultBytes` together, and always the first one. */\nfunction withinBytes(lines: string[]): string[] {\n\tlet bytes = 0;\n\tconst kept: string[] = [];\n\tfor (const line of lines) {\n\t\tbytes += Buffer.byteLength(line, 'utf8') + 1;\n\t\tif (bytes > maxResultBytes && kept.length > 0) break;\n\t\tkept.push(line);\n\t}\n\treturn kept;\n}\n\n/** What `readFile` takes, shared with the harness's own version of it. */\nexport const readFileDescription = 'Reads lines of a file, each prefixed with its number.';\nexport const readFileInput = z.object({\n\tpath: z.string().describe('The file, relative to the root.'),\n\tfromLine: z.number().nullable().describe('The first line to read, from 1. Defaults to 1.'),\n\tlineCount: z\n\t\t.number()\n\t\t.nullable()\n\t\t.describe(\n\t\t\t`How many lines to read, at most ${String(maxLines)}. Defaults to ${String(maxLines)}.`,\n\t\t),\n});\n\n/** What `readFiles` takes, shared with the harness's own version of it. */\nexport const readFilesDescription = `Reads several files, or several ranges of files, in one call: each as readFile reads it, at most ${String(maxReads)}. A file that cannot be read is reported in its place.`;\nexport const readFilesInput = z.object({\n\tfiles: z.array(readFileInput).describe('What to read, in order: each a file and its range.'),\n});\n\ntype ReadRequest = z.infer<typeof readFileInput>;\n\nexport function readFilesSummary({ files }: { files: ReadRequest[] }): string {\n\treturn files.map(readFileSummary).join(', ');\n}\n\n/**\n * Reads each request with `read`, in order, each under its own heading, and reports a file it\n * could not read in its place. Fails only when it read none of them, or was cancelled.\n */\nexport async function readEach(\n\tfiles: ReadRequest[],\n\tread: (file: ReadRequest) => Promise<string>,\n\tsignal: AbortSignal,\n): Promise<string> {\n\tif (files.length === 0) throw new Error('files must name at least one file to read');\n\tif (files.length > maxReads) {\n\t\tthrow new Error(\n\t\t\t`files may name at most ${String(maxReads)} reads at once; got ${String(files.length)}`,\n\t\t);\n\t}\n\tlet failed = 0;\n\tconst results: string[] = [];\n\tfor (const file of files) {\n\t\ttry {\n\t\t\tresults.push(`== ${readFileSummary(file)}\\n${await read(file)}`);\n\t\t} catch (error) {\n\t\t\tsignal.throwIfAborted();\n\t\t\tfailed++;\n\t\t\tresults.push(\n\t\t\t\t`== ${file.path}\\nCould not read it: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t}\n\t}\n\tconst output = results.join('\\n\\n');\n\tif (failed === files.length) throw new Error(output);\n\treturn output;\n}\n\n/** The range a `readFile` call asks for, checked: strict mode allows the schema no minimum. */\nexport function readRange({\n\tfromLine,\n\tlineCount,\n}: {\n\tfromLine: number | null;\n\tlineCount: number | null;\n}): { from: number; count: number } {\n\tconst from = fromLine ?? 1;\n\tconst requested = lineCount ?? maxLines;\n\tassertPositiveInteger('fromLine', from);\n\tassertPositiveInteger('lineCount', requested);\n\treturn { from, count: Math.min(requested, maxLines) };\n}\n\n/** A stable description of a read request, using only the range the model supplied. */\nexport function readFileSummary({\n\tpath,\n\tfromLine,\n\tlineCount,\n}: {\n\tpath: string;\n\tfromLine: number | null;\n\tlineCount: number | null;\n}): string {\n\tconst suppliedFrom = fromLine ?? 1;\n\tconst from = Number.isInteger(suppliedFrom) && suppliedFrom > 0 ? suppliedFrom : 1;\n\tconst suppliedCount = lineCount ?? maxLines;\n\tconst requested = Number.isInteger(suppliedCount) && suppliedCount > 0 ? suppliedCount : maxLines;\n\tconst count = Math.min(requested, maxLines);\n\treturn `${path}:${String(from)}-${String(from + count - 1)}`;\n}\n\nfunction scopeSummary(path: string | null, glob: string | null): string {\n\tconst scopePath = path ?? '.';\n\treturn glob ? `${scopePath === '.' ? '' : `${scopePath}/`}${glob}` : scopePath;\n}\n\nfunction assertPositiveInteger(name: string, value: number): void {\n\tif (!Number.isInteger(value) || value < 1) {\n\t\tthrow new Error(`${name} must be a whole number of at least 1; got ${String(value)}`);\n\t}\n}\n\n/**\n * The read-only tools an agent explores a repository with: `listFiles`, `searchText`,\n * `readFile` and `readFiles`.\n */\nexport function createWorkspaceTools(workspace: Workspace): Tool[] {\n\treturn [\n\t\tdefineTool({\n\t\t\tname: 'listFiles',\n\t\t\tdescription:\n\t\t\t\t'Lists the files of the repository, sorted. Ignored files such as dependencies are left out.',\n\t\t\trisk: 'safe',\n\t\t\tinput: z.object(scope),\n\t\t\tsummarize: ({ path, glob }) => scopeSummary(path, glob),\n\t\t\texecute: async ({ path, glob }, signal) => {\n\t\t\t\t// The port takes \"no scope\" as a missing field.\n\t\t\t\tconst { files, total, truncated } = await workspace.listFiles(\n\t\t\t\t\t{ path: path ?? undefined, glob: glob ?? undefined, limit: maxFiles },\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\n\t\t\t\t// Nothing found is an answer too, not an empty string.\n\t\t\t\tif (files.length === 0) {\n\t\t\t\t\treturn 'No files.';\n\t\t\t\t}\n\n\t\t\t\tconst notice = truncated\n\t\t\t\t\t? `\\n[Showing ${String(files.length)} of ${String(total)} files. Narrow the path or glob to see the rest.]`\n\t\t\t\t\t: '';\n\t\t\t\treturn files.join('\\n') + notice;\n\t\t\t},\n\t\t}),\n\n\t\tdefineTool({\n\t\t\tname: 'searchText',\n\t\t\tdescription:\n\t\t\t\t'Searches the repository for a regular expression and returns each matching line as path:line: text.',\n\t\t\trisk: 'safe',\n\t\t\tinput: z.object({\n\t\t\t\tpattern: z.string().describe('A regular expression.'),\n\t\t\t\tignoreCase: z.boolean().nullable().describe('Match regardless of case. Defaults to false.'),\n\t\t\t\t...scope,\n\t\t\t}),\n\t\t\tsummarize: ({ path, glob, pattern }) => `\"${pattern}\" in ${scopeSummary(path, glob)}`,\n\t\t\texecute: async ({ path, glob, ignoreCase, pattern }, signal) => {\n\t\t\t\tconst { matches, total, truncated } = await workspace.searchText(\n\t\t\t\t\t{\n\t\t\t\t\t\tpath: path ?? undefined,\n\t\t\t\t\t\tglob: glob ?? undefined,\n\t\t\t\t\t\tignoreCase: ignoreCase ?? false,\n\t\t\t\t\t\tpattern,\n\t\t\t\t\t\tlimit: maxMatches,\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\n\t\t\t\tif (matches.length === 0) {\n\t\t\t\t\treturn 'No matches.';\n\t\t\t\t}\n\n\t\t\t\tconst results = withinBytes(\n\t\t\t\t\tmatches.map(\n\t\t\t\t\t\tmatch => `${match.path}:${String(match.line)}: ${cutLine(match.path, match.text)}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\t\tconst notice =\n\t\t\t\t\tresults.length < matches.length\n\t\t\t\t\t\t? `\\n[Showing ${String(results.length)} of ${String(total)} matches, the ${String(maxResultBytes / 1024)} KB one search may return. Narrow the pattern, the path or the glob to see the rest.]`\n\t\t\t\t\t\t: truncated\n\t\t\t\t\t\t\t? `\\n[Showing ${String(results.length)} of ${String(total)} matches. Narrow the path or glob to see the rest.]`\n\t\t\t\t\t\t\t: '';\n\t\t\t\treturn results.join('\\n') + notice;\n\t\t\t},\n\t\t}),\n\n\t\tdefineTool({\n\t\t\tname: 'readFile',\n\t\t\tdescription: readFileDescription,\n\t\t\trisk: 'safe',\n\t\t\tinput: readFileInput,\n\t\t\tsummarize: readFileSummary,\n\t\t\texecute: async ({ fromLine, lineCount, path }, signal) => {\n\t\t\t\t// Checked before reading.\n\t\t\t\tconst { from, count } = readRange({ fromLine, lineCount });\n\t\t\t\tconst { lines, totalLines, truncated } = await workspace.readFile(\n\t\t\t\t\t{ path, fromLine: from, lineCount: count },\n\t\t\t\t\tsignal,\n\t\t\t\t);\n\t\t\t\treturn renderLines({ path, lines, from, totalLines, truncated });\n\t\t\t},\n\t\t}),\n\n\t\tdefineTool({\n\t\t\tname: 'readFiles',\n\t\t\tdescription: readFilesDescription,\n\t\t\trisk: 'safe',\n\t\t\tinput: readFilesInput,\n\t\t\tsummarize: readFilesSummary,\n\t\t\texecute: ({ files }, signal) =>\n\t\t\t\treadEach(\n\t\t\t\t\tfiles,\n\t\t\t\t\tasync ({ path, fromLine, lineCount }) => {\n\t\t\t\t\t\tconst { from, count } = readRange({ fromLine, lineCount });\n\t\t\t\t\t\tconst { lines, totalLines, truncated } = await workspace.readFile(\n\t\t\t\t\t\t\t{ path, fromLine: from, lineCount: count },\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn renderLines({ path, lines, from, totalLines, truncated });\n\t\t\t\t\t},\n\t\t\t\t\tsignal,\n\t\t\t\t),\n\t\t}),\n\t];\n}\n"]}
|