forked from onlook-dev/onlook
-
Notifications
You must be signed in to change notification settings - Fork 25
Expand file tree
/
Copy pathindex.ts
More file actions
185 lines (175 loc) · 6.98 KB
/
index.ts
File metadata and controls
185 lines (175 loc) · 6.98 KB
1
2
3
4
5
6
7
8
9
10
11
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
53
54
55
56
57
58
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
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
147
148
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
import { anthropic } from '@ai-sdk/anthropic';
import { tool, type ToolSet } from 'ai';
import { readFile } from 'fs/promises';
import { z } from 'zod';
import { ONLOOK_PROMPT } from '../prompt/onlook';
import { getAllFiles } from './helpers';
import { LintingService } from './lint';
export const listFilesTool = tool({
description: 'List all files in the current directory, including subdirectories',
parameters: z.object({
path: z
.string()
.describe(
'The absolute path to the directory to get files from. This should be the root directory of the project.',
),
}),
execute: async ({ path }) => {
const res = await getAllFiles(path);
if (!res.success) {
return { error: res.error };
}
return res.files;
},
});
export const readFilesTool = tool({
description: 'Read the contents of files',
parameters: z.object({
paths: z.array(z.string()).describe('The absolute paths to the files to read'),
}),
execute: async ({ paths }) => {
try {
const files = await Promise.all(
paths.map(async (path) => {
const file = await readFile(path, 'utf8');
return { path, content: file };
}),
);
return files;
} catch (error) {
return `Error: ${error instanceof Error ? error.message : error}`;
}
},
});
export const onlookInstructionsTool = tool({
description: 'Get the instructions for the Onlook AI',
parameters: z.object({}),
execute: async () => {
return ONLOOK_PROMPT;
},
});
export const lintTool = tool({
description: 'Analyze code quality and optionally fix issues in TypeScript/JavaScript files',
parameters: z.object({
path: z.string().describe('The absolute path to the file or directory to lint'),
fix: z.boolean().optional().default(false).describe('Whether to auto-fix the issues'),
detailed: z
.boolean()
.optional()
.default(false)
.describe('Whether to return detailed rule-based statistics'),
}),
execute: async ({ path, fix = false, detailed = false }) => {
try {
const lintingService = LintingService.getInstance(fix);
const result = await lintingService.lintProject(path);
if (!detailed) {
return {
summary: {
totalFiles: result.totalFiles,
totalErrors: result.totalErrors,
totalWarnings: result.totalWarnings,
fixedFiles: fix ? result.fixedFiles : undefined,
},
fileResults: result.results.map((r) => ({
file: r.filePath,
errors: r.messages.filter((m) => m.severity === 2).length,
warnings: r.messages.filter((m) => m.severity === 1).length,
...(fix && { fixed: r.fixed }),
})),
};
}
return result;
} catch (error) {
return {
error:
error instanceof Error
? error.message
: 'Unknown error occurred during linting',
success: false,
};
}
},
});
// https://docs.anthropic.com/en/docs/agents-and-tools/computer-use#understand-anthropic-defined-tools
// https://sdk.vercel.ai/docs/guides/computer-use#get-started-with-computer-use
// We currently can't use this because it doens't support streaming
interface FileOperationHandlers {
readFile: (path: string) => Promise<string>;
writeFile: (path: string, content: string) => Promise<boolean>;
undoEdit?: () => Promise<boolean>;
}
export const getStrReplaceEditorTool = (handlers: FileOperationHandlers) => {
const strReplaceEditorTool = anthropic.tools.textEditor_20250124({
execute: async ({
command,
path,
file_text,
insert_line,
new_str,
old_str,
view_range,
}) => {
try {
switch (command) {
case 'view': {
const content = await handlers.readFile(path);
if (view_range) {
const lines = content.split('\n');
const [start, end] = view_range;
return lines.slice(start - 1, end).join('\n');
}
return content;
}
case 'create': {
if (!file_text) {
throw new Error('file_text is required for create command');
}
await handlers.writeFile(path, file_text);
return `File created successfully at ${path}`;
}
case 'str_replace': {
if (!old_str) {
throw new Error('old_str is required for str_replace command');
}
const content = await handlers.readFile(path);
const newContent = content.replace(old_str, new_str || '');
await handlers.writeFile(path, newContent);
return `String replaced successfully in ${path}`;
}
case 'insert': {
if (!new_str || insert_line === undefined) {
throw new Error(
'new_str and insert_line are required for insert command',
);
}
const content = await handlers.readFile(path);
const lines = content.split('\n');
lines.splice(insert_line, 0, new_str);
await handlers.writeFile(path, lines.join('\n'));
return `Content inserted successfully at line ${insert_line} in ${path}`;
}
case 'undo_edit': {
if (handlers.undoEdit) {
await handlers.undoEdit();
return 'Edit undone successfully';
}
return 'Undo operation not implemented';
}
default: {
throw new Error(`Unknown command: ${command}`);
}
}
} catch (error) {
return `Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
}
},
});
return strReplaceEditorTool;
};
export const chatToolSet: ToolSet = {
list_files: listFilesTool,
read_files: readFilesTool,
onlook_instructions: onlookInstructionsTool,
lint: lintTool,
};