actoviq-agent-sdk 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-zh.md +19 -3
- package/README.md +19 -3
- package/bin/actoviq-interactive-agent.js +5 -0
- package/bin/actoviq-link-runtime.js +74 -0
- package/bin/actoviq-react.js +5 -0
- package/dist/src/cli/actoviq-react.d.ts +3 -0
- package/dist/src/cli/actoviq-react.d.ts.map +1 -0
- package/dist/src/cli/actoviq-react.js +286 -0
- package/dist/src/cli/actoviq-react.js.map +1 -0
- package/dist/src/cli/bridge-interactive-agent.d.ts +2 -0
- package/dist/src/cli/bridge-interactive-agent.d.ts.map +1 -0
- package/dist/src/cli/bridge-interactive-agent.js +147 -0
- package/dist/src/cli/bridge-interactive-agent.js.map +1 -0
- package/dist/src/config/anthropicEnvMapping.d.ts +18 -0
- package/dist/src/config/anthropicEnvMapping.d.ts.map +1 -0
- package/dist/src/config/anthropicEnvMapping.js +40 -0
- package/dist/src/config/anthropicEnvMapping.js.map +1 -0
- package/dist/src/config/resolveRuntimeConfig.d.ts.map +1 -1
- package/dist/src/config/resolveRuntimeConfig.js +44 -23
- package/dist/src/config/resolveRuntimeConfig.js.map +1 -1
- package/dist/src/errors.d.ts +9 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +9 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +23 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +21 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/memory/actoviqMemory.d.ts.map +1 -1
- package/dist/src/memory/actoviqMemory.js +10 -6
- package/dist/src/memory/actoviqMemory.js.map +1 -1
- package/dist/src/memory/actoviqTranscriptLogger.d.ts.map +1 -1
- package/dist/src/memory/actoviqTranscriptLogger.js +2 -2
- package/dist/src/memory/actoviqTranscriptLogger.js.map +1 -1
- package/dist/src/parity/actoviqBridgeEvents.d.ts +7 -0
- package/dist/src/parity/actoviqBridgeEvents.d.ts.map +1 -0
- package/dist/src/parity/actoviqBridgeEvents.js +135 -0
- package/dist/src/parity/actoviqBridgeEvents.js.map +1 -0
- package/dist/src/parity/actoviqBridgeSdk.d.ts +179 -0
- package/dist/src/parity/actoviqBridgeSdk.d.ts.map +1 -0
- package/dist/src/parity/actoviqBridgeSdk.js +1275 -0
- package/dist/src/parity/actoviqBridgeSdk.js.map +1 -0
- package/dist/src/parity/actoviqCleanBridgeCompatSdk.d.ts +277 -0
- package/dist/src/parity/actoviqCleanBridgeCompatSdk.d.ts.map +1 -0
- package/dist/src/parity/actoviqCleanBridgeCompatSdk.js +1391 -0
- package/dist/src/parity/actoviqCleanBridgeCompatSdk.js.map +1 -0
- package/dist/src/parity/actoviqTranscripts.d.ts +44 -0
- package/dist/src/parity/actoviqTranscripts.d.ts.map +1 -0
- package/dist/src/parity/actoviqTranscripts.js +282 -0
- package/dist/src/parity/actoviqTranscripts.js.map +1 -0
- package/dist/src/parity/portableSessions.d.ts +35 -0
- package/dist/src/parity/portableSessions.d.ts.map +1 -0
- package/dist/src/parity/portableSessions.js +237 -0
- package/dist/src/parity/portableSessions.js.map +1 -0
- package/dist/src/prompts/systemPrompt.d.ts +15 -0
- package/dist/src/prompts/systemPrompt.d.ts.map +1 -0
- package/dist/src/prompts/systemPrompt.js +73 -0
- package/dist/src/prompts/systemPrompt.js.map +1 -0
- package/dist/src/provider/client.d.ts +4 -0
- package/dist/src/provider/client.d.ts.map +1 -1
- package/dist/src/provider/client.js +82 -10
- package/dist/src/provider/client.js.map +1 -1
- package/dist/src/provider/json-parse.d.ts +7 -0
- package/dist/src/provider/json-parse.d.ts.map +1 -0
- package/dist/src/provider/json-parse.js +69 -0
- package/dist/src/provider/json-parse.js.map +1 -0
- package/dist/src/provider/openai-client.d.ts.map +1 -1
- package/dist/src/provider/openai-client.js +45 -5
- package/dist/src/provider/openai-client.js.map +1 -1
- package/dist/src/provider/openai-model-api.d.ts.map +1 -1
- package/dist/src/provider/openai-model-api.js +2 -7
- package/dist/src/provider/openai-model-api.js.map +1 -1
- package/dist/src/runtime/actoviqAgents.d.ts +6 -0
- package/dist/src/runtime/actoviqAgents.d.ts.map +1 -1
- package/dist/src/runtime/actoviqAgents.js +110 -14
- package/dist/src/runtime/actoviqAgents.js.map +1 -1
- package/dist/src/runtime/actoviqApiMicrocompact.d.ts +14 -0
- package/dist/src/runtime/actoviqApiMicrocompact.d.ts.map +1 -1
- package/dist/src/runtime/actoviqApiMicrocompact.js +111 -16
- package/dist/src/runtime/actoviqApiMicrocompact.js.map +1 -1
- package/dist/src/runtime/actoviqBackgroundTasks.d.ts +1 -0
- package/dist/src/runtime/actoviqBackgroundTasks.d.ts.map +1 -1
- package/dist/src/runtime/actoviqBackgroundTasks.js +1 -0
- package/dist/src/runtime/actoviqBackgroundTasks.js.map +1 -1
- package/dist/src/runtime/actoviqCompact.d.ts +43 -0
- package/dist/src/runtime/actoviqCompact.d.ts.map +1 -1
- package/dist/src/runtime/actoviqCompact.js +290 -8
- package/dist/src/runtime/actoviqCompact.js.map +1 -1
- package/dist/src/runtime/actoviqPermissions.d.ts.map +1 -1
- package/dist/src/runtime/actoviqPermissions.js +121 -55
- package/dist/src/runtime/actoviqPermissions.js.map +1 -1
- package/dist/src/runtime/agentClient.d.ts +11 -0
- package/dist/src/runtime/agentClient.d.ts.map +1 -1
- package/dist/src/runtime/agentClient.js +332 -6
- package/dist/src/runtime/agentClient.js.map +1 -1
- package/dist/src/runtime/conversationEngine.d.ts.map +1 -1
- package/dist/src/runtime/conversationEngine.js +585 -33
- package/dist/src/runtime/conversationEngine.js.map +1 -1
- package/dist/src/runtime/defaultActoviqAgents.d.ts +3 -0
- package/dist/src/runtime/defaultActoviqAgents.d.ts.map +1 -0
- package/dist/src/runtime/defaultActoviqAgents.js +44 -0
- package/dist/src/runtime/defaultActoviqAgents.js.map +1 -0
- package/dist/src/runtime/denialTracking.d.ts +10 -0
- package/dist/src/runtime/denialTracking.d.ts.map +1 -0
- package/dist/src/runtime/denialTracking.js +27 -0
- package/dist/src/runtime/denialTracking.js.map +1 -0
- package/dist/src/runtime/safetyChecks.d.ts +12 -0
- package/dist/src/runtime/safetyChecks.d.ts.map +1 -0
- package/dist/src/runtime/safetyChecks.js +68 -0
- package/dist/src/runtime/safetyChecks.js.map +1 -0
- package/dist/src/runtime/sessionManager.d.ts.map +1 -1
- package/dist/src/runtime/sessionManager.js +10 -5
- package/dist/src/runtime/sessionManager.js.map +1 -1
- package/dist/src/runtime/tools.d.ts.map +1 -1
- package/dist/src/runtime/tools.js +62 -3
- package/dist/src/runtime/tools.js.map +1 -1
- package/dist/src/scheduling/cron.d.ts +14 -0
- package/dist/src/scheduling/cron.d.ts.map +1 -0
- package/dist/src/scheduling/cron.js +159 -0
- package/dist/src/scheduling/cron.js.map +1 -0
- package/dist/src/scheduling/index.d.ts +3 -0
- package/dist/src/scheduling/index.d.ts.map +1 -0
- package/dist/src/scheduling/index.js +3 -0
- package/dist/src/scheduling/index.js.map +1 -0
- package/dist/src/scheduling/scheduler.d.ts +32 -0
- package/dist/src/scheduling/scheduler.d.ts.map +1 -0
- package/dist/src/scheduling/scheduler.js +196 -0
- package/dist/src/scheduling/scheduler.js.map +1 -0
- package/dist/src/storage/backgroundTaskStore.d.ts.map +1 -1
- package/dist/src/storage/backgroundTaskStore.js +3 -2
- package/dist/src/storage/backgroundTaskStore.js.map +1 -1
- package/dist/src/storage/mailboxStore.d.ts.map +1 -1
- package/dist/src/storage/mailboxStore.js +3 -3
- package/dist/src/storage/mailboxStore.js.map +1 -1
- package/dist/src/storage/pathSafety.d.ts +4 -0
- package/dist/src/storage/pathSafety.d.ts.map +1 -0
- package/dist/src/storage/pathSafety.js +28 -0
- package/dist/src/storage/pathSafety.js.map +1 -0
- package/dist/src/storage/sessionStore.d.ts +2 -2
- package/dist/src/storage/sessionStore.d.ts.map +1 -1
- package/dist/src/storage/sessionStore.js +23 -8
- package/dist/src/storage/sessionStore.js.map +1 -1
- package/dist/src/storage/teammateStore.d.ts.map +1 -1
- package/dist/src/storage/teammateStore.js +3 -2
- package/dist/src/storage/teammateStore.js.map +1 -1
- package/dist/src/tools/actoviqCoreTools.d.ts +28 -0
- package/dist/src/tools/actoviqCoreTools.d.ts.map +1 -0
- package/dist/src/tools/actoviqCoreTools.js +32 -0
- package/dist/src/tools/actoviqCoreTools.js.map +1 -0
- package/dist/src/tools/actoviqFileTools.d.ts.map +1 -1
- package/dist/src/tools/actoviqFileTools.js +349 -373
- package/dist/src/tools/actoviqFileTools.js.map +1 -1
- package/dist/src/tools/actoviqMiscTools.d.ts +11 -0
- package/dist/src/tools/actoviqMiscTools.d.ts.map +1 -0
- package/dist/src/tools/actoviqMiscTools.js +89 -0
- package/dist/src/tools/actoviqMiscTools.js.map +1 -0
- package/dist/src/tools/actoviqNotebookEdit.d.ts +4 -0
- package/dist/src/tools/actoviqNotebookEdit.d.ts.map +1 -0
- package/dist/src/tools/actoviqNotebookEdit.js +140 -0
- package/dist/src/tools/actoviqNotebookEdit.js.map +1 -0
- package/dist/src/tools/actoviqShellTools.d.ts +5 -0
- package/dist/src/tools/actoviqShellTools.d.ts.map +1 -0
- package/dist/src/tools/actoviqShellTools.js +64 -0
- package/dist/src/tools/actoviqShellTools.js.map +1 -0
- package/dist/src/tools/actoviqTaskTools.d.ts +12 -0
- package/dist/src/tools/actoviqTaskTools.d.ts.map +1 -0
- package/dist/src/tools/actoviqTaskTools.js +108 -0
- package/dist/src/tools/actoviqTaskTools.js.map +1 -0
- package/dist/src/tools/actoviqWebTools.d.ts +15 -0
- package/dist/src/tools/actoviqWebTools.d.ts.map +1 -0
- package/dist/src/tools/actoviqWebTools.js +427 -0
- package/dist/src/tools/actoviqWebTools.js.map +1 -0
- package/dist/src/tools/askUserQuestion/AskUserQuestionTool.d.ts +6 -0
- package/dist/src/tools/askUserQuestion/AskUserQuestionTool.d.ts.map +1 -0
- package/dist/src/tools/askUserQuestion/AskUserQuestionTool.js +43 -0
- package/dist/src/tools/askUserQuestion/AskUserQuestionTool.js.map +1 -0
- package/dist/src/tools/bash/BashTool.d.ts +14 -0
- package/dist/src/tools/bash/BashTool.d.ts.map +1 -0
- package/dist/src/tools/bash/BashTool.js +60 -0
- package/dist/src/tools/bash/BashTool.js.map +1 -0
- package/dist/src/tools/bash/prompt.d.ts +3 -0
- package/dist/src/tools/bash/prompt.d.ts.map +1 -0
- package/dist/src/tools/bash/prompt.js +63 -0
- package/dist/src/tools/bash/prompt.js.map +1 -0
- package/dist/src/tools/prompts/fileEditPrompt.d.ts +3 -0
- package/dist/src/tools/prompts/fileEditPrompt.d.ts.map +1 -0
- package/dist/src/tools/prompts/fileEditPrompt.js +14 -0
- package/dist/src/tools/prompts/fileEditPrompt.js.map +1 -0
- package/dist/src/tools/prompts/fileReadPrompt.d.ts +3 -0
- package/dist/src/tools/prompts/fileReadPrompt.d.ts.map +1 -0
- package/dist/src/tools/prompts/fileReadPrompt.js +18 -0
- package/dist/src/tools/prompts/fileReadPrompt.js.map +1 -0
- package/dist/src/tools/prompts/fileSearchPrompt.d.ts +2 -0
- package/dist/src/tools/prompts/fileSearchPrompt.d.ts.map +1 -0
- package/dist/src/tools/prompts/fileSearchPrompt.js +21 -0
- package/dist/src/tools/prompts/fileSearchPrompt.js.map +1 -0
- package/dist/src/tools/prompts/fileWritePrompt.d.ts +3 -0
- package/dist/src/tools/prompts/fileWritePrompt.d.ts.map +1 -0
- package/dist/src/tools/prompts/fileWritePrompt.js +13 -0
- package/dist/src/tools/prompts/fileWritePrompt.js.map +1 -0
- package/dist/src/tools/prompts/webFetchPrompt.d.ts +3 -0
- package/dist/src/tools/prompts/webFetchPrompt.d.ts.map +1 -0
- package/dist/src/tools/prompts/webFetchPrompt.js +13 -0
- package/dist/src/tools/prompts/webFetchPrompt.js.map +1 -0
- package/dist/src/tools/prompts/webSearchPrompt.d.ts +3 -0
- package/dist/src/tools/prompts/webSearchPrompt.d.ts.map +1 -0
- package/dist/src/tools/prompts/webSearchPrompt.js +17 -0
- package/dist/src/tools/prompts/webSearchPrompt.js.map +1 -0
- package/dist/src/tools/todo/TodoWriteTool.d.ts +20 -0
- package/dist/src/tools/todo/TodoWriteTool.d.ts.map +1 -0
- package/dist/src/tools/todo/TodoWriteTool.js +105 -0
- package/dist/src/tools/todo/TodoWriteTool.js.map +1 -0
- package/dist/src/types.d.ts +425 -3
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js.map +1 -1
- package/dist/src/workflow/workflowEngine.js +26 -3
- package/dist/src/workflow/workflowEngine.js.map +1 -1
- package/dist/src/workspace/actoviqWorkspace.d.ts.map +1 -1
- package/dist/src/workspace/actoviqWorkspace.js +25 -2
- package/dist/src/workspace/actoviqWorkspace.js.map +1 -1
- package/package.json +59 -11
- package/vendor/actoviq-runtime/cli.js +54 -0
|
@@ -1,426 +1,402 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Actoviq File Tools — Read, Write, Edit, Glob, Grep
|
|
3
|
+
*
|
|
4
|
+
* Schemas, descriptions, and prompts match Claude Code exactly.
|
|
5
|
+
* Execution logic is adapted for the Actoviq SDK environment.
|
|
6
|
+
*/
|
|
7
|
+
import { readFile, writeFile, mkdir, stat as fsStat } from 'node:fs/promises';
|
|
2
8
|
import os from 'node:os';
|
|
3
9
|
import path from 'node:path';
|
|
4
10
|
import { glob } from 'glob';
|
|
5
11
|
import { z } from 'zod';
|
|
6
12
|
import { ToolExecutionError } from '../errors.js';
|
|
7
13
|
import { tool } from '../runtime/tools.js';
|
|
14
|
+
import { fileReadPrompt, FILE_READ_TOOL_NAME } from './prompts/fileReadPrompt.js';
|
|
15
|
+
import { fileWritePrompt, FILE_WRITE_TOOL_NAME } from './prompts/fileWritePrompt.js';
|
|
16
|
+
import { fileEditPrompt, FILE_EDIT_TOOL_NAME } from './prompts/fileEditPrompt.js';
|
|
17
|
+
import { fileSearchPrompt } from './prompts/fileSearchPrompt.js';
|
|
8
18
|
const DEFAULT_MAX_READ_LINES = 2000;
|
|
9
19
|
const DEFAULT_GLOB_LIMIT = 100;
|
|
10
20
|
const DEFAULT_GREP_LIMIT = 250;
|
|
11
21
|
const BINARY_ZERO_BYTE = 0;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
22
|
+
const PDF_MAX_PAGES = 20;
|
|
23
|
+
const Read = (opts) => tool({
|
|
24
|
+
name: FILE_READ_TOOL_NAME,
|
|
25
|
+
description: 'Reads a file from the local filesystem. You can access any file directly by using this tool.\n' +
|
|
26
|
+
'Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid.\n\n' +
|
|
27
|
+
'Usage:\n' +
|
|
28
|
+
'- The file_path parameter must be an absolute path, not a relative path\n' +
|
|
29
|
+
`- By default, it reads up to ${opts.maxReadLines} lines starting from the beginning of the file\n` +
|
|
30
|
+
'- When you already know which part of the file you need, only read that part.\n' +
|
|
31
|
+
'- Results are returned using cat -n format, with line numbers starting at 1\n' +
|
|
32
|
+
'- This tool can read images (PNG, JPG, etc) and PDF files (.pdf)\n' +
|
|
33
|
+
'- This tool can read Jupyter notebooks (.ipynb files) and returns all cells\n' +
|
|
34
|
+
'- This tool can only read files, not directories.\n' +
|
|
35
|
+
'- You will regularly be asked to read screenshots. ALWAYS use this tool to view the file at the path.\n' +
|
|
36
|
+
'- If you read a file that exists but has empty contents you will receive a system reminder.',
|
|
37
|
+
inputSchema: z.strictObject({
|
|
38
|
+
file_path: z.string().describe('The absolute path to the file to read'),
|
|
39
|
+
offset: z.number().int().nonnegative().optional().describe('The line number to start reading from. Only provide if the file is too large to read at once'),
|
|
40
|
+
limit: z.number().int().positive().optional().describe('The number of lines to read. Only provide if the file is too large to read at once.'),
|
|
41
|
+
pages: z.string().optional().describe(`Page range for PDF files (e.g., "1-5", "3", "10-20"). Only applicable to PDF files. Maximum ${PDF_MAX_PAGES} pages per request.`),
|
|
42
|
+
}),
|
|
43
|
+
isReadOnly: () => true,
|
|
44
|
+
prompt: fileReadPrompt,
|
|
45
|
+
}, async (input) => {
|
|
46
|
+
const resolvedPath = resolvePath(input.file_path, opts.cwd);
|
|
47
|
+
let fileStats;
|
|
48
|
+
try {
|
|
49
|
+
fileStats = await fsStat(resolvedPath);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new ToolExecutionError('Read', `File not found: ${resolvedPath}`);
|
|
53
|
+
}
|
|
54
|
+
if (!fileStats.isFile()) {
|
|
55
|
+
throw new ToolExecutionError('Read', `Path is not a file: ${resolvedPath}`);
|
|
56
|
+
}
|
|
57
|
+
// Handle PDF
|
|
58
|
+
if (resolvedPath.toLowerCase().endsWith('.pdf') && input.pages) {
|
|
59
|
+
throw new ToolExecutionError('Read', 'PDF reading requires pdf-parse dependency. Install it to read PDFs.');
|
|
60
|
+
}
|
|
61
|
+
const buffer = await readFile(resolvedPath);
|
|
62
|
+
if (isProbablyBinary(buffer) && !isImagePath(resolvedPath) && !isPDFPath(resolvedPath)) {
|
|
63
|
+
throw new ToolExecutionError('Read', `Cannot read binary file: ${resolvedPath}`);
|
|
64
|
+
}
|
|
65
|
+
const text = buffer.toString('utf-8');
|
|
66
|
+
opts.readState.set(resolvedPath, { mtimeMs: fileStats.mtimeMs });
|
|
67
|
+
const lines = text.split(/\r?\n/);
|
|
68
|
+
const startLine = input.offset ?? 1;
|
|
69
|
+
const startIndex = Math.max(0, startLine - 1);
|
|
70
|
+
const lineCount = input.limit ?? opts.maxReadLines;
|
|
71
|
+
const selected = lines.slice(startIndex, startIndex + lineCount);
|
|
72
|
+
const content = selected.map((l, i) => `${String(startLine + i).padStart(6, ' ')}\t${l}`).join('\n');
|
|
73
|
+
return {
|
|
74
|
+
type: 'text',
|
|
75
|
+
file: {
|
|
53
76
|
filePath: resolvedPath,
|
|
54
|
-
startLine: startIndex + 1,
|
|
55
|
-
endLine: startIndex + selectedLines.length,
|
|
56
|
-
totalLines: lines.length,
|
|
57
|
-
truncated: startIndex + selectedLines.length < lines.length,
|
|
58
77
|
content,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
78
|
+
numLines: selected.length,
|
|
79
|
+
startLine,
|
|
80
|
+
totalLines: lines.length,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
// ── Write tool (matches Claude Code FileWriteTool) ───────────────
|
|
85
|
+
const Write = (opts) => tool({
|
|
86
|
+
name: FILE_WRITE_TOOL_NAME,
|
|
87
|
+
description: 'Writes a file to the local filesystem.\n\n' +
|
|
88
|
+
'Usage:\n' +
|
|
89
|
+
'- This tool will overwrite the existing file if there is one at the provided path.\n' +
|
|
90
|
+
`- If this is an existing file, you MUST use the \`${FILE_READ_TOOL_NAME}\` tool first to read the file's contents.\n` +
|
|
91
|
+
'- Prefer the Edit tool for modifying existing files — it only sends the diff. Only use this tool to create new files or for complete rewrites.\n' +
|
|
92
|
+
'- NEVER create documentation files (*.md) or README files unless explicitly requested by the User.\n' +
|
|
93
|
+
'- Only use emojis if the user explicitly requests it.',
|
|
94
|
+
inputSchema: z.strictObject({
|
|
95
|
+
file_path: z.string().describe('The absolute path to the file to write (must be absolute, not relative)'),
|
|
96
|
+
content: z.string().describe('The content to write to the file'),
|
|
97
|
+
}),
|
|
98
|
+
isDestructive: () => true,
|
|
99
|
+
prompt: fileWritePrompt,
|
|
100
|
+
}, async (input) => {
|
|
101
|
+
const resolvedPath = resolvePath(input.file_path, opts.cwd);
|
|
102
|
+
let existing;
|
|
103
|
+
try {
|
|
104
|
+
existing = await fsStat(resolvedPath);
|
|
105
|
+
}
|
|
106
|
+
catch { /* new file */ }
|
|
107
|
+
if (existing?.isFile()) {
|
|
108
|
+
ensurePreviouslyRead(opts.readState, resolvedPath, existing.mtimeMs, 'Write');
|
|
109
|
+
}
|
|
110
|
+
await mkdir(path.dirname(resolvedPath), { recursive: true });
|
|
111
|
+
await writeFile(resolvedPath, input.content, 'utf-8');
|
|
112
|
+
const finalStats = await fsStat(resolvedPath);
|
|
113
|
+
opts.readState.set(resolvedPath, { mtimeMs: finalStats.mtimeMs });
|
|
114
|
+
return {
|
|
115
|
+
type: existing ? 'update' : 'create',
|
|
116
|
+
filePath: resolvedPath,
|
|
117
|
+
content: input.content,
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
// ── Edit tool (matches Claude Code FileEditTool) ─────────────────
|
|
121
|
+
const Edit = (opts) => tool({
|
|
122
|
+
name: FILE_EDIT_TOOL_NAME,
|
|
123
|
+
description: 'Performs exact string replacements in files.\n\n' +
|
|
124
|
+
'Usage:\n' +
|
|
125
|
+
`- You must use your \`${FILE_READ_TOOL_NAME}\` tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file.\n` +
|
|
126
|
+
'- When editing text from Read tool output, ensure you preserve the exact indentation (tabs/spaces) as it appears AFTER the line number prefix. The line number prefix format is: line number + tab. Everything after that is the actual file content to match.\n' +
|
|
127
|
+
'- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.\n' +
|
|
128
|
+
'- Only use emojis if the user explicitly requests it.\n' +
|
|
129
|
+
'- The edit will FAIL if `old_string` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use `replace_all` to change every instance of `old_string`.\n' +
|
|
130
|
+
'- Use `replace_all` for replacing and renaming strings across the file.',
|
|
131
|
+
inputSchema: z.strictObject({
|
|
132
|
+
file_path: z.string().describe('The absolute path to the file to modify'),
|
|
133
|
+
old_string: z.string().describe('The text to replace'),
|
|
134
|
+
new_string: z.string().describe('The text to replace it with (must be different from old_string)'),
|
|
135
|
+
replace_all: z.boolean().optional().default(false),
|
|
136
|
+
}),
|
|
137
|
+
isDestructive: () => true,
|
|
138
|
+
prompt: fileEditPrompt,
|
|
139
|
+
}, async (input) => {
|
|
140
|
+
const resolvedPath = resolvePath(input.file_path, opts.cwd);
|
|
141
|
+
let fileStats;
|
|
142
|
+
try {
|
|
143
|
+
fileStats = await fsStat(resolvedPath);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
throw new ToolExecutionError('Edit', `File not found: ${resolvedPath}`);
|
|
147
|
+
}
|
|
148
|
+
if (!fileStats.isFile()) {
|
|
149
|
+
throw new ToolExecutionError('Edit', `Path is not a file: ${resolvedPath}`);
|
|
150
|
+
}
|
|
151
|
+
ensurePreviouslyRead(opts.readState, resolvedPath, fileStats.mtimeMs, 'Edit');
|
|
152
|
+
const originalContent = await readFile(resolvedPath, 'utf-8');
|
|
153
|
+
const occurrences = (originalContent.match(new RegExp(escapeRegex(input.old_string), 'g')) || []).length;
|
|
154
|
+
if (occurrences === 0) {
|
|
155
|
+
throw new ToolExecutionError('Edit', `old_string was not found in ${resolvedPath}`);
|
|
156
|
+
}
|
|
157
|
+
if (!input.replace_all && occurrences > 1) {
|
|
158
|
+
throw new ToolExecutionError('Edit', `old_string matched ${occurrences} locations in ${resolvedPath}. Use replace_all: true or provide a more specific old_string.`);
|
|
159
|
+
}
|
|
160
|
+
const updatedContent = input.replace_all
|
|
161
|
+
? originalContent.split(input.old_string).join(input.new_string)
|
|
162
|
+
: originalContent.replace(input.old_string, input.new_string);
|
|
163
|
+
await writeFile(resolvedPath, updatedContent, 'utf-8');
|
|
164
|
+
const finalStats = await fsStat(resolvedPath);
|
|
165
|
+
opts.readState.set(resolvedPath, { mtimeMs: finalStats.mtimeMs });
|
|
166
|
+
return {
|
|
167
|
+
filePath: resolvedPath,
|
|
168
|
+
replacements: input.replace_all ? occurrences : 1,
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
// ── Glob tool (matches Claude Code GlobTool) ─────────────────────
|
|
172
|
+
const GLOB_TOOL_NAME = 'Glob';
|
|
173
|
+
const defaultGlobExcludes = ['**/.git/**', '**/node_modules/**', '**/.svn/**', '**/.hg/**', '**/.jj/**', '**/.sl/**'];
|
|
174
|
+
const Glob = (opts) => tool({
|
|
175
|
+
name: GLOB_TOOL_NAME,
|
|
176
|
+
description: '- Fast file pattern matching tool that works with any codebase size\n' +
|
|
177
|
+
'- Supports glob patterns like "**/*.js" or "src/**/*.ts"\n' +
|
|
178
|
+
'- Returns matching file paths sorted by modification time\n' +
|
|
179
|
+
'- Use this tool when you need to find files by name patterns\n' +
|
|
180
|
+
'- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead',
|
|
181
|
+
inputSchema: z.strictObject({
|
|
182
|
+
pattern: z.string().describe('The glob pattern to match files against'),
|
|
183
|
+
path: z.string().optional().describe('The directory to search in. Defaults to the current working directory.'),
|
|
184
|
+
limit: z.number().int().positive().optional().describe(`Maximum number of results. Defaults to ${opts.defaultGlobLimit}.`),
|
|
185
|
+
}),
|
|
186
|
+
isReadOnly: () => true,
|
|
187
|
+
prompt: fileSearchPrompt,
|
|
188
|
+
}, async (input) => {
|
|
189
|
+
const searchRoot = resolvePath(input.path ?? opts.cwd, opts.cwd);
|
|
190
|
+
const matches = [];
|
|
191
|
+
const stream = glob.stream(input.pattern, {
|
|
192
|
+
cwd: searchRoot, absolute: true, nodir: true, ignore: defaultGlobExcludes, windowsPathsNoEscape: true,
|
|
147
193
|
});
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
194
|
+
for await (const match of stream) {
|
|
195
|
+
if (typeof match === 'string')
|
|
196
|
+
matches.push(match);
|
|
197
|
+
if (matches.length >= (input.limit ?? opts.defaultGlobLimit))
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
root: searchRoot,
|
|
202
|
+
filenames: matches.slice(0, input.limit ?? opts.defaultGlobLimit),
|
|
203
|
+
numFiles: matches.length,
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
// ── Grep tool (matches Claude Code GrepTool) ─────────────────────
|
|
207
|
+
const GREP_TOOL_NAME = 'Grep';
|
|
208
|
+
const Grep = (opts) => tool({
|
|
209
|
+
name: GREP_TOOL_NAME,
|
|
210
|
+
description: 'A file-content search tool using JavaScript regular expression syntax\n\n' +
|
|
211
|
+
'Usage:\n' +
|
|
212
|
+
'- ALWAYS use Grep for search tasks. NEVER invoke `grep` or `rg` as a Bash command.\n' +
|
|
213
|
+
'- Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")\n' +
|
|
214
|
+
'- Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")\n' +
|
|
215
|
+
'- Output modes: "content" shows matching lines, "files_with_matches" shows file paths (default), "count" shows match counts\n' +
|
|
216
|
+
'- Use Agent tool for open-ended searches requiring multiple rounds\n' +
|
|
217
|
+
'- Pattern syntax: JavaScript RegExp syntax\n' +
|
|
218
|
+
'- Multiline matching: By default patterns match within single lines only. For cross-line patterns use `multiline: true`',
|
|
219
|
+
inputSchema: z.strictObject({
|
|
220
|
+
pattern: z.string().describe('The regular expression pattern to search for in file contents'),
|
|
221
|
+
path: z.string().optional().describe('File or directory to search in. Defaults to current working directory.'),
|
|
222
|
+
glob: z.string().optional().describe('Glob pattern to filter files (e.g. "*.js", "*.{ts,tsx}")'),
|
|
223
|
+
output_mode: z.enum(['content', 'files_with_matches', 'count']).optional().default('files_with_matches'),
|
|
224
|
+
head_limit: z.number().int().nonnegative().optional().describe(`Output limit. Defaults to ${opts.defaultGrepLimit}. Pass 0 for unlimited.`),
|
|
225
|
+
offset: z.number().int().nonnegative().optional().default(0),
|
|
226
|
+
'-i': z.boolean().optional().default(false),
|
|
227
|
+
'-n': z.boolean().optional().default(true),
|
|
228
|
+
'-A': z.number().int().nonnegative().optional(),
|
|
229
|
+
'-B': z.number().int().nonnegative().optional(),
|
|
230
|
+
'-C': z.number().int().nonnegative().optional(),
|
|
231
|
+
context: z.number().int().nonnegative().optional(),
|
|
232
|
+
multiline: z.boolean().optional().default(false),
|
|
233
|
+
}),
|
|
234
|
+
isReadOnly: () => true,
|
|
235
|
+
prompt: fileSearchPrompt,
|
|
236
|
+
}, async (input) => {
|
|
237
|
+
const searchRoot = resolvePath(input.path ?? opts.cwd, opts.cwd);
|
|
238
|
+
const outputMode = input.output_mode ?? 'files_with_matches';
|
|
239
|
+
const limit = input.head_limit ?? opts.defaultGrepLimit;
|
|
240
|
+
const ignoreCase = input['-i'] ?? false;
|
|
241
|
+
const multiline = input.multiline ?? false;
|
|
242
|
+
const matchedFiles = [];
|
|
243
|
+
const contentLines = [];
|
|
244
|
+
const countEntries = [];
|
|
245
|
+
let totalMatches = 0;
|
|
246
|
+
const before = input['-C'] ?? input.context ?? input['-B'] ?? 0;
|
|
247
|
+
const after = input['-C'] ?? input.context ?? input['-A'] ?? 0;
|
|
248
|
+
function buildRegex(pattern, global = false) {
|
|
249
|
+
const f = [ignoreCase ? 'i' : '', multiline ? 's' : '', global ? 'g' : ''].filter(Boolean).join('');
|
|
250
|
+
return new RegExp(pattern, f);
|
|
251
|
+
}
|
|
252
|
+
let matchRegex;
|
|
253
|
+
let globalMatchRegex;
|
|
254
|
+
try {
|
|
255
|
+
matchRegex = buildRegex(input.pattern);
|
|
256
|
+
globalMatchRegex = buildRegex(input.pattern, true);
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
260
|
+
throw new ToolExecutionError('Grep', `Invalid regular expression "${input.pattern}": ${message}`);
|
|
261
|
+
}
|
|
262
|
+
const files = await findFiles(searchRoot, input.glob);
|
|
263
|
+
for (const f of files) {
|
|
264
|
+
try {
|
|
265
|
+
const buffer = await readFile(f);
|
|
266
|
+
if (isProbablyBinary(buffer))
|
|
204
267
|
continue;
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
const relativePath = toDisplayPath(searchRoot, absolutePath);
|
|
268
|
+
const content = buffer.toString('utf-8');
|
|
269
|
+
const relPath = path.relative(searchRoot, f) || f;
|
|
208
270
|
if (outputMode === 'files_with_matches') {
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
multiline: input.multiline ?? false,
|
|
212
|
-
}).test(content)) {
|
|
213
|
-
matchedFiles.push(relativePath);
|
|
214
|
-
}
|
|
215
|
-
continue;
|
|
271
|
+
if (matchRegex.test(content))
|
|
272
|
+
matchedFiles.push(relPath);
|
|
216
273
|
}
|
|
217
|
-
if (outputMode === 'count') {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
274
|
+
else if (outputMode === 'count') {
|
|
275
|
+
globalMatchRegex.lastIndex = 0;
|
|
276
|
+
const count = [...content.matchAll(globalMatchRegex)].length;
|
|
277
|
+
if (count > 0) {
|
|
278
|
+
totalMatches += count;
|
|
279
|
+
countEntries.push({ file: relPath, count });
|
|
222
280
|
}
|
|
223
|
-
continue;
|
|
224
281
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
});
|
|
231
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
232
|
-
if (!matcher.test(lines[index] ?? '')) {
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
const start = Math.max(0, index - beforeContext);
|
|
236
|
-
const end = Math.min(lines.length - 1, index + afterContext);
|
|
237
|
-
for (let lineIndex = start; lineIndex <= end; lineIndex += 1) {
|
|
238
|
-
if (visited.has(lineIndex)) {
|
|
282
|
+
else {
|
|
283
|
+
const lines = content.split(/\r?\n/);
|
|
284
|
+
const visited = new Set();
|
|
285
|
+
for (let i = 0; i < lines.length; i++) {
|
|
286
|
+
if (!matchRegex.test(lines[i]))
|
|
239
287
|
continue;
|
|
288
|
+
for (let j = Math.max(0, i - before); j <= Math.min(lines.length - 1, i + after); j++) {
|
|
289
|
+
if (visited.has(j))
|
|
290
|
+
continue;
|
|
291
|
+
visited.add(j);
|
|
292
|
+
contentLines.push(input['-n'] === false ? `${relPath}:${lines[j]}` : `${relPath}:${j + 1}:${lines[j]}`);
|
|
240
293
|
}
|
|
241
|
-
visited.add(lineIndex);
|
|
242
|
-
const prefix = input['-n'] === false ? `${relativePath}:` : `${relativePath}:${lineIndex + 1}:`;
|
|
243
|
-
contentLines.push(`${prefix}${lines[lineIndex] ?? ''}`);
|
|
244
294
|
}
|
|
245
295
|
}
|
|
246
296
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
totalMatches: matchedFiles.length,
|
|
254
|
-
appliedLimit: paged.appliedLimit,
|
|
255
|
-
appliedOffset: offset,
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
if (outputMode === 'count') {
|
|
259
|
-
const countStrings = countEntries.map((entry) => `${entry.filePath}:${entry.count}`);
|
|
260
|
-
const paged = applyLimit(countStrings, limit, offset);
|
|
261
|
-
return {
|
|
262
|
-
mode: outputMode,
|
|
263
|
-
root: searchRoot,
|
|
264
|
-
filenames: paged.items,
|
|
265
|
-
totalMatches,
|
|
266
|
-
appliedLimit: paged.appliedLimit,
|
|
267
|
-
appliedOffset: offset,
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
const paged = applyLimit(contentLines, limit, offset);
|
|
271
|
-
return {
|
|
272
|
-
mode: outputMode,
|
|
273
|
-
root: searchRoot,
|
|
274
|
-
filenames: paged.items,
|
|
275
|
-
totalMatches: contentLines.length,
|
|
276
|
-
appliedLimit: paged.appliedLimit,
|
|
277
|
-
appliedOffset: offset,
|
|
278
|
-
};
|
|
279
|
-
});
|
|
280
|
-
return [Read, Write, Edit, Glob, Grep];
|
|
281
|
-
}
|
|
282
|
-
function requireAbsolutePath(filePath) {
|
|
283
|
-
if (!path.isAbsolute(filePath) && !filePath.startsWith('~')) {
|
|
284
|
-
throw new ToolExecutionError('filesystem', `Expected an absolute path, received "${filePath}".`);
|
|
297
|
+
catch { /* skip unreadable files */ }
|
|
298
|
+
}
|
|
299
|
+
const off = input.offset ?? 0;
|
|
300
|
+
if (outputMode === 'files_with_matches') {
|
|
301
|
+
const sliced = limit === 0 ? matchedFiles.slice(off) : matchedFiles.slice(off, off + limit);
|
|
302
|
+
return { mode: outputMode, root: searchRoot, filenames: sliced, totalMatches: matchedFiles.length };
|
|
285
303
|
}
|
|
286
|
-
|
|
304
|
+
if (outputMode === 'count') {
|
|
305
|
+
const entries = countEntries.map(e => `${e.file}:${e.count}`);
|
|
306
|
+
const sliced = limit === 0 ? entries.slice(off) : entries.slice(off, off + limit);
|
|
307
|
+
return { mode: outputMode, root: searchRoot, filenames: sliced, totalMatches };
|
|
308
|
+
}
|
|
309
|
+
const sliced = limit === 0 ? contentLines.slice(off) : contentLines.slice(off, off + limit);
|
|
310
|
+
return { mode: outputMode, root: searchRoot, filenames: sliced, totalMatches: contentLines.length };
|
|
311
|
+
});
|
|
312
|
+
// ── Factory ─────────────────────────────────────────────────────
|
|
313
|
+
export function createActoviqFileTools(options = {}) {
|
|
314
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
315
|
+
const maxReadLines = options.maxReadLines ?? DEFAULT_MAX_READ_LINES;
|
|
316
|
+
const defaultGlobLimit = options.defaultGlobLimit ?? DEFAULT_GLOB_LIMIT;
|
|
317
|
+
const defaultGrepLimit = options.defaultGrepLimit ?? DEFAULT_GREP_LIMIT;
|
|
318
|
+
const readState = new Map();
|
|
319
|
+
return [
|
|
320
|
+
Read({ cwd, maxReadLines, readState }),
|
|
321
|
+
Write({ cwd, readState }),
|
|
322
|
+
Edit({ cwd, readState }),
|
|
323
|
+
Glob({ cwd, defaultGlobLimit }),
|
|
324
|
+
Grep({ cwd, defaultGrepLimit }),
|
|
325
|
+
];
|
|
287
326
|
}
|
|
288
|
-
|
|
327
|
+
// ── Helpers ─────────────────────────────────────────────────────
|
|
328
|
+
function resolvePath(filePath, cwd) {
|
|
289
329
|
if (filePath.startsWith('~')) {
|
|
290
330
|
return path.resolve(os.homedir(), filePath.slice(1));
|
|
291
331
|
}
|
|
332
|
+
if (!path.isAbsolute(filePath)) {
|
|
333
|
+
throw new ToolExecutionError('filesystem', `Expected an absolute path, received "${filePath}".`);
|
|
334
|
+
}
|
|
292
335
|
return path.resolve(filePath);
|
|
293
336
|
}
|
|
294
|
-
function
|
|
295
|
-
readState.set(filePath, { mtimeMs });
|
|
296
|
-
}
|
|
297
|
-
function ensurePreviouslyRead(readState, filePath, currentMtimeMs, toolName) {
|
|
337
|
+
function ensurePreviouslyRead(readState, filePath, mtimeMs, toolName) {
|
|
298
338
|
const remembered = readState.get(filePath);
|
|
299
339
|
if (!remembered) {
|
|
300
340
|
throw new ToolExecutionError(toolName, `${toolName} requires the file to be read first: ${filePath}`);
|
|
301
341
|
}
|
|
302
|
-
if (Math.floor(
|
|
342
|
+
if (Math.floor(mtimeMs) > Math.floor(remembered.mtimeMs)) {
|
|
303
343
|
throw new ToolExecutionError(toolName, `The file changed after it was last read. Read it again before using ${toolName}: ${filePath}`);
|
|
304
344
|
}
|
|
305
345
|
}
|
|
306
|
-
async function
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
return buffer.toString('utf8');
|
|
346
|
+
async function findFiles(searchRoot, globPattern) {
|
|
347
|
+
if (!globPattern)
|
|
348
|
+
return listAllFiles(searchRoot);
|
|
349
|
+
return listGlobMatches(globPattern, searchRoot);
|
|
312
350
|
}
|
|
313
|
-
function
|
|
314
|
-
|
|
315
|
-
|
|
351
|
+
async function listGlobMatches(pattern, root) {
|
|
352
|
+
const matches = [];
|
|
353
|
+
for await (const m of glob.stream(pattern, { cwd: root, absolute: true, nodir: true, ignore: defaultGlobExcludes, windowsPathsNoEscape: true })) {
|
|
354
|
+
if (typeof m === 'string')
|
|
355
|
+
matches.push(m);
|
|
316
356
|
}
|
|
317
|
-
return
|
|
318
|
-
.map((line, index) => `${String(startLineNumber + index).padStart(6, ' ')}\t${line}`)
|
|
319
|
-
.join('\n');
|
|
357
|
+
return matches;
|
|
320
358
|
}
|
|
321
|
-
async function
|
|
359
|
+
async function listAllFiles(dir) {
|
|
360
|
+
const results = [];
|
|
322
361
|
try {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
function replaceFirst(content, oldValue, newValue) {
|
|
336
|
-
const index = content.indexOf(oldValue);
|
|
337
|
-
if (index < 0) {
|
|
338
|
-
return content;
|
|
339
|
-
}
|
|
340
|
-
return `${content.slice(0, index)}${newValue}${content.slice(index + oldValue.length)}`;
|
|
341
|
-
}
|
|
342
|
-
function resolveSearchRoot(inputPath, context, fallbackCwd) {
|
|
343
|
-
if (!inputPath) {
|
|
344
|
-
return context.cwd || fallbackCwd;
|
|
345
|
-
}
|
|
346
|
-
if (path.isAbsolute(inputPath)) {
|
|
347
|
-
return path.resolve(inputPath);
|
|
348
|
-
}
|
|
349
|
-
return path.resolve(context.cwd || fallbackCwd, inputPath);
|
|
350
|
-
}
|
|
351
|
-
const defaultGlobExcludes = [
|
|
352
|
-
'**/.git/**',
|
|
353
|
-
'**/node_modules/**',
|
|
354
|
-
'**/.svn/**',
|
|
355
|
-
'**/.hg/**',
|
|
356
|
-
'**/.jj/**',
|
|
357
|
-
'**/.sl/**',
|
|
358
|
-
];
|
|
359
|
-
async function* iterateSearchFiles(searchRoot, globPattern) {
|
|
360
|
-
const pattern = globPattern || '**/*';
|
|
361
|
-
const matches = await listGlobMatches(pattern, searchRoot);
|
|
362
|
-
for (const match of matches) {
|
|
363
|
-
yield match;
|
|
362
|
+
for (const entry of await readdirSafe(dir)) {
|
|
363
|
+
const full = path.join(dir, entry.name);
|
|
364
|
+
if (entry.isDirectory()) {
|
|
365
|
+
if (entry.name === '.git' || entry.name === 'node_modules')
|
|
366
|
+
continue;
|
|
367
|
+
results.push(...await listAllFiles(full));
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
results.push(full);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
364
373
|
}
|
|
374
|
+
catch { /* skip */ }
|
|
375
|
+
return results;
|
|
365
376
|
}
|
|
366
|
-
async function
|
|
367
|
-
const
|
|
368
|
-
cwd: searchRoot,
|
|
369
|
-
absolute: true,
|
|
370
|
-
nodir: true,
|
|
371
|
-
ignore: defaultGlobExcludes,
|
|
372
|
-
windowsPathsNoEscape: true,
|
|
373
|
-
});
|
|
374
|
-
return typeof limit === 'number' ? matches.slice(0, limit) : matches;
|
|
375
|
-
}
|
|
376
|
-
function buildSearchRegex(source, options = {}) {
|
|
377
|
-
const flags = new Set();
|
|
378
|
-
if (options.ignoreCase) {
|
|
379
|
-
flags.add('i');
|
|
380
|
-
}
|
|
381
|
-
if (options.multiline) {
|
|
382
|
-
flags.add('s');
|
|
383
|
-
}
|
|
384
|
-
if (options.global) {
|
|
385
|
-
flags.add('g');
|
|
386
|
-
}
|
|
377
|
+
async function readdirSafe(dir) {
|
|
378
|
+
const fs = await import('node:fs/promises');
|
|
387
379
|
try {
|
|
388
|
-
return
|
|
389
|
-
}
|
|
390
|
-
catch (error) {
|
|
391
|
-
throw new ToolExecutionError('Grep', `Invalid regular expression "${source}": ${String(error)}`);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
function applyLimit(items, limit, offset = 0) {
|
|
395
|
-
if (limit === 0) {
|
|
396
|
-
return { items: items.slice(offset), appliedLimit: undefined };
|
|
397
|
-
}
|
|
398
|
-
const effectiveLimit = limit ?? DEFAULT_GREP_LIMIT;
|
|
399
|
-
return {
|
|
400
|
-
items: items.slice(offset, offset + effectiveLimit),
|
|
401
|
-
appliedLimit: items.length - offset > effectiveLimit ? effectiveLimit : undefined,
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
function serializeGrepOutput(output) {
|
|
405
|
-
if (output.filenames.length === 0) {
|
|
406
|
-
return 'No matches found';
|
|
380
|
+
return (await fs.readdir(dir, { withFileTypes: true }));
|
|
407
381
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
function toDisplayPath(searchRoot, absolutePath) {
|
|
411
|
-
const relativePath = path.relative(searchRoot, absolutePath);
|
|
412
|
-
if (!relativePath || relativePath.startsWith('..')) {
|
|
413
|
-
return absolutePath;
|
|
382
|
+
catch {
|
|
383
|
+
return [];
|
|
414
384
|
}
|
|
415
|
-
return relativePath;
|
|
416
385
|
}
|
|
417
386
|
function isProbablyBinary(buffer) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
if (buffer[index] === BINARY_ZERO_BYTE) {
|
|
387
|
+
for (let i = 0; i < Math.min(buffer.length, 1024); i++) {
|
|
388
|
+
if (buffer[i] === BINARY_ZERO_BYTE)
|
|
421
389
|
return true;
|
|
422
|
-
}
|
|
423
390
|
}
|
|
424
391
|
return false;
|
|
425
392
|
}
|
|
393
|
+
function isImagePath(filePath) {
|
|
394
|
+
return /\.(png|jpe?g|gif|webp|bmp)$/i.test(filePath);
|
|
395
|
+
}
|
|
396
|
+
function isPDFPath(filePath) {
|
|
397
|
+
return filePath.toLowerCase().endsWith('.pdf');
|
|
398
|
+
}
|
|
399
|
+
function escapeRegex(str) {
|
|
400
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
401
|
+
}
|
|
426
402
|
//# sourceMappingURL=actoviqFileTools.js.map
|