@ridit/lens 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +629 -461
- package/package.json +1 -1
- package/src/components/chat/ChatOverlays.tsx +73 -46
- package/src/components/chat/ChatRunner.tsx +203 -369
- package/src/index.tsx +3 -0
- package/src/prompts/system.ts +79 -102
- package/src/utils/chat.ts +95 -176
- package/src/utils/tools/builtins.ts +324 -0
- package/src/utils/tools/registry.ts +119 -0
package/src/index.tsx
CHANGED
|
@@ -7,6 +7,9 @@ import { ReviewCommand } from "./commands/review";
|
|
|
7
7
|
import { TaskCommand } from "./commands/task";
|
|
8
8
|
import { ChatCommand } from "./commands/chat";
|
|
9
9
|
import { TimelineCommand } from "./commands/timeline";
|
|
10
|
+
import { registerBuiltins } from "./utils/tools/builtins";
|
|
11
|
+
|
|
12
|
+
registerBuiltins();
|
|
10
13
|
|
|
11
14
|
const program = new Command();
|
|
12
15
|
|
package/src/prompts/system.ts
CHANGED
|
@@ -3,63 +3,19 @@ import type { ImportantFile } from "../types/repo";
|
|
|
3
3
|
export function buildSystemPrompt(
|
|
4
4
|
files: ImportantFile[],
|
|
5
5
|
memorySummary = "",
|
|
6
|
+
toolsSection?: string,
|
|
6
7
|
): string {
|
|
7
8
|
const fileList = files
|
|
8
9
|
.map((f) => `### ${f.path}\n\`\`\`\n${f.content.slice(0, 2000)}\n\`\`\``)
|
|
9
10
|
.join("\n\n");
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
You have exactly thirteen tools. To use a tool you MUST wrap it in the exact XML tags shown below — no other format will work.
|
|
16
|
-
|
|
17
|
-
### 1. fetch — load a URL
|
|
18
|
-
<fetch>https://example.com</fetch>
|
|
12
|
+
// If a toolsSection is supplied (e.g. from the plugin registry), use it.
|
|
13
|
+
// Otherwise fall back to the static built-in section.
|
|
14
|
+
const tools = toolsSection ?? BUILTIN_TOOLS_SECTION;
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
<shell>node -v</shell>
|
|
22
|
-
|
|
23
|
-
### 3. read-file — read a file from the repo
|
|
24
|
-
<read-file>src/foo.ts</read-file>
|
|
25
|
-
|
|
26
|
-
### 4. read-folder — list contents of a folder (files + subfolders, one level deep)
|
|
27
|
-
<read-folder>src/components</read-folder>
|
|
28
|
-
|
|
29
|
-
### 5. grep — search for a pattern across files in the repo (cross-platform, no shell needed)
|
|
30
|
-
<grep>
|
|
31
|
-
{"pattern": "ChatRunner", "glob": "src/**/*.tsx"}
|
|
32
|
-
</grep>
|
|
33
|
-
|
|
34
|
-
### 6. write-file — create or overwrite a file
|
|
35
|
-
<write-file>
|
|
36
|
-
{"path": "data/output.csv", "content": "col1,col2\nval1,val2"}
|
|
37
|
-
</write-file>
|
|
38
|
-
|
|
39
|
-
### 7. delete-file — permanently delete a single file
|
|
40
|
-
<delete-file>src/old-component.tsx</delete-file>
|
|
41
|
-
|
|
42
|
-
### 8. delete-folder — permanently delete a folder and all its contents
|
|
43
|
-
<delete-folder>src/legacy</delete-folder>
|
|
44
|
-
|
|
45
|
-
### 9. open-url — open a URL in the user's default browser
|
|
46
|
-
<open-url>https://github.com/owner/repo</open-url>
|
|
47
|
-
|
|
48
|
-
### 10. generate-pdf — generate a PDF file from markdown-style content
|
|
49
|
-
<generate-pdf>
|
|
50
|
-
{"path": "output/report.pdf", "content": "# Title\n\nSome body text.\n\n## Section\n\nMore content."}
|
|
51
|
-
</generate-pdf>
|
|
52
|
-
|
|
53
|
-
### 11. search — search the internet for anything you are unsure about
|
|
54
|
-
<search>how to use React useEffect cleanup function</search>
|
|
55
|
-
|
|
56
|
-
### 12. clone — clone a GitHub repo so you can explore and discuss it
|
|
57
|
-
<clone>https://github.com/owner/repo</clone>
|
|
16
|
+
return `You are an expert software engineer assistant with access to the user's codebase and tools.
|
|
58
17
|
|
|
59
|
-
|
|
60
|
-
<changes>
|
|
61
|
-
{"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}
|
|
62
|
-
</changes>
|
|
18
|
+
${tools}
|
|
63
19
|
|
|
64
20
|
## MEMORY OPERATIONS
|
|
65
21
|
|
|
@@ -109,9 +65,26 @@ You may emit multiple memory operations in a single response alongside normal co
|
|
|
109
65
|
22. When scaffolding a multi-file project, after each write-file succeeds, immediately proceed to writing the NEXT file — NEVER rewrite a file you already wrote in this session. Each file is written ONCE and ONLY ONCE.
|
|
110
66
|
23. For JSX/TSX files always use \`.tsx\` extension and include \`/** @jsxImportSource react */\` or ensure tsconfig has jsx set — bun needs this to parse JSX
|
|
111
67
|
24. When explaining how to use a tool in text, use [tag] bracket notation or a fenced code block — NEVER emit a real XML tool tag as part of an explanation or example
|
|
112
|
-
25. NEVER
|
|
113
|
-
26. NEVER
|
|
114
|
-
|
|
68
|
+
25. NEVER read files, list folders, or run tools that were not asked for in the current user message
|
|
69
|
+
26. NEVER use markdown formatting in plain text responses — no **bold**, no *italics*, no # headings, no bullet points with -, *, or +, no numbered lists, no backtick inline code. Write in plain prose. Only use fenced \`\`\` code blocks when showing actual code.
|
|
70
|
+
|
|
71
|
+
## SCAFFOLDING — CHAINING WRITE-FILE CALLS
|
|
72
|
+
|
|
73
|
+
When creating multiple files (e.g. scaffolding a project or creating 10 files), emit ALL of them
|
|
74
|
+
in a single response by chaining the tags back-to-back with no text between them:
|
|
75
|
+
|
|
76
|
+
<write-file>
|
|
77
|
+
{"path": "test/file1.txt", "content": "File 1 content"}
|
|
78
|
+
</write-file>
|
|
79
|
+
<write-file>
|
|
80
|
+
{"path": "test/file2.txt", "content": "File 2 content"}
|
|
81
|
+
</write-file>
|
|
82
|
+
<write-file>
|
|
83
|
+
{"path": "test/file3.txt", "content": "File 3 content"}
|
|
84
|
+
</write-file>
|
|
85
|
+
|
|
86
|
+
The system processes each tag sequentially and automatically continues to the next one.
|
|
87
|
+
Do NOT wait for a user message between files — emit all tags at once.
|
|
115
88
|
|
|
116
89
|
## CRITICAL: READ BEFORE YOU WRITE
|
|
117
90
|
|
|
@@ -133,58 +106,62 @@ These rules are mandatory whenever you plan to edit or create a file:
|
|
|
133
106
|
- NEVER produce a file that is shorter than the original unless you are explicitly asked to delete things
|
|
134
107
|
- If you catch yourself rewriting a file from scratch, STOP — go back and read the original first
|
|
135
108
|
|
|
136
|
-
## WHEN TO USE read-folder:
|
|
137
|
-
- Before editing files in an unfamiliar directory — list it first to understand the structure
|
|
138
|
-
- When a feature spans multiple files and you are not sure what exists
|
|
139
|
-
- When the user asks you to explore or explain a part of the codebase
|
|
140
|
-
|
|
141
|
-
## SCAFFOLDING A NEW PROJECT (follow this exactly)
|
|
142
|
-
|
|
143
|
-
When the user asks to create a new CLI/app in a subfolder (e.g. "make a todo app called list"):
|
|
144
|
-
1. Create all files first using write-file with paths like \`list/package.json\`, \`list/src/index.tsx\`
|
|
145
|
-
2. Then run \`cd list && bun install\` (or npm/pnpm) in one shell command
|
|
146
|
-
3. Then run the project with \`cd list && bun run index.ts\` or whatever the entry point is
|
|
147
|
-
4. NEVER run \`bun init\` — it is interactive and will hang. Create package.json manually with write-file instead
|
|
148
|
-
5. TSX files need either tsconfig.json with \`"jsx": "react-jsx"\` or \`/** @jsxImportSource react */\` at the top
|
|
149
|
-
|
|
150
|
-
## FETCH → WRITE FLOW (follow this exactly when saving fetched data)
|
|
151
|
-
|
|
152
|
-
1. fetch the URL
|
|
153
|
-
2. Analyze the result — count the rows, identify columns, check completeness
|
|
154
|
-
3. Tell the user what you found: "Found X rows with columns: A, B, C. Writing now."
|
|
155
|
-
4. emit write-file with correctly structured, complete content
|
|
156
|
-
5. After write-file confirms success, emit read-file to verify
|
|
157
|
-
6. Only after read-file confirms content is correct, tell the user it is done
|
|
158
|
-
|
|
159
|
-
## WHEN TO USE TOOLS
|
|
160
|
-
|
|
161
|
-
- User shares any URL → fetch it immediately
|
|
162
|
-
- User asks to run anything → shell it immediately
|
|
163
|
-
- User asks to open a link, open a URL, or visit a website → open-url it immediately, do NOT use fetch
|
|
164
|
-
- User asks to delete a file → delete-file it immediately (requires approval)
|
|
165
|
-
- User asks to delete a folder or directory → delete-folder it immediately (requires approval)
|
|
166
|
-
- User asks to search for a pattern in files, find usages, find where something is defined → grep it immediately, NEVER use shell grep/findstr/Select-String
|
|
167
|
-
- User asks to read a file → read-file it immediately, NEVER use shell cat/type
|
|
168
|
-
- User asks what files are in a folder, or to explore/list a directory → read-folder it immediately, NEVER use shell ls/dir/find/git ls-files
|
|
169
|
-
- User asks to explore a folder or directory → read-folder it immediately
|
|
170
|
-
- User asks to save/create/write a file → write-file it immediately, then read-file to verify
|
|
171
|
-
- User asks to modify/edit/add to an existing file → read-file it FIRST, then emit changes
|
|
172
|
-
- User shares a GitHub URL and wants to clone/explore/discuss it → use clone immediately, NEVER use shell git clone
|
|
173
|
-
- After clone succeeds, you will see context about the clone in the conversation. Wait for the user to ask a specific question before using any tools. Do NOT auto-read files, do NOT emit any tool tags until the user asks.
|
|
174
|
-
- You are unsure about an API, library, error, concept, or piece of code → search it immediately
|
|
175
|
-
- User asks about something recent or that you might not know → search it immediately
|
|
176
|
-
- You are about to say "I'm not sure" or "I don't know" → search instead of guessing
|
|
177
|
-
|
|
178
|
-
## shell IS ONLY FOR:
|
|
179
|
-
- Running code: \`node script.js\`, \`bun run dev\`, \`python main.py\`
|
|
180
|
-
- Installing packages: \`npm install\`, \`pip install\`
|
|
181
|
-
- Building/testing: \`npm run build\`, \`bun test\`
|
|
182
|
-
- Git operations other than clone: \`git status\`, \`git log\`, \`git diff\`
|
|
183
|
-
- Anything that EXECUTES — not reads or lists
|
|
184
|
-
|
|
185
109
|
## CODEBASE
|
|
186
110
|
|
|
187
111
|
${fileList.length > 0 ? fileList : "(no files indexed)"}
|
|
188
112
|
|
|
189
113
|
${memorySummary}`;
|
|
190
114
|
}
|
|
115
|
+
|
|
116
|
+
// ── Static fallback tools section (used when registry is not available) ───────
|
|
117
|
+
|
|
118
|
+
const BUILTIN_TOOLS_SECTION = `## TOOLS
|
|
119
|
+
|
|
120
|
+
You have exactly thirteen tools. To use a tool you MUST wrap it in the exact XML tags shown below — no other format will work.
|
|
121
|
+
|
|
122
|
+
### 1. fetch — load a URL
|
|
123
|
+
<fetch>https://example.com</fetch>
|
|
124
|
+
|
|
125
|
+
### 2. shell — run a terminal command
|
|
126
|
+
<shell>node -v</shell>
|
|
127
|
+
|
|
128
|
+
### 3. read-file — read a file from the repo
|
|
129
|
+
<read-file>src/foo.ts</read-file>
|
|
130
|
+
|
|
131
|
+
### 4. read-folder — list contents of a folder (files + subfolders, one level deep)
|
|
132
|
+
<read-folder>src/components</read-folder>
|
|
133
|
+
|
|
134
|
+
### 5. grep — search for a pattern across files in the repo (cross-platform, no shell needed)
|
|
135
|
+
<grep>
|
|
136
|
+
{"pattern": "ChatRunner", "glob": "src/**/*.tsx"}
|
|
137
|
+
</grep>
|
|
138
|
+
|
|
139
|
+
### 6. write-file — create or overwrite a file
|
|
140
|
+
<write-file>
|
|
141
|
+
{"path": "data/output.csv", "content": "col1,col2\\nval1,val2"}
|
|
142
|
+
</write-file>
|
|
143
|
+
|
|
144
|
+
### 7. delete-file — permanently delete a single file
|
|
145
|
+
<delete-file>src/old-component.tsx</delete-file>
|
|
146
|
+
|
|
147
|
+
### 8. delete-folder — permanently delete a folder and all its contents
|
|
148
|
+
<delete-folder>src/legacy</delete-folder>
|
|
149
|
+
|
|
150
|
+
### 9. open-url — open a URL in the user's default browser
|
|
151
|
+
<open-url>https://github.com/owner/repo</open-url>
|
|
152
|
+
|
|
153
|
+
### 10. generate-pdf — generate a PDF file from markdown-style content
|
|
154
|
+
<generate-pdf>
|
|
155
|
+
{"path": "output/report.pdf", "content": "# Title\\n\\nSome body text.\\n\\n## Section\\n\\nMore content."}
|
|
156
|
+
</generate-pdf>
|
|
157
|
+
|
|
158
|
+
### 11. search — search the internet for anything you are unsure about
|
|
159
|
+
<search>how to use React useEffect cleanup function</search>
|
|
160
|
+
|
|
161
|
+
### 12. clone — clone a GitHub repo so you can explore and discuss it
|
|
162
|
+
<clone>https://github.com/owner/repo</clone>
|
|
163
|
+
|
|
164
|
+
### 13. changes — propose code edits (shown as a diff for user approval)
|
|
165
|
+
<changes>
|
|
166
|
+
{"summary": "what changed and why", "patches": [{"path": "src/foo.ts", "content": "COMPLETE file content", "isNew": false}]}
|
|
167
|
+
</changes>`;
|
package/src/utils/chat.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// ── chat.ts ───────────────────────────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Response parsing and API calls.
|
|
4
|
+
// Tool parsing is now fully driven by the ToolRegistry — adding a new tool
|
|
5
|
+
// to the registry automatically makes it parseable here.
|
|
6
|
+
|
|
1
7
|
export {
|
|
2
8
|
walkDir,
|
|
3
9
|
applyPatches,
|
|
@@ -13,104 +19,87 @@ export { fetchUrl, searchWeb } from "../tools/web";
|
|
|
13
19
|
export { generatePdf } from "../tools/pdf";
|
|
14
20
|
export { buildSystemPrompt, FEW_SHOT_MESSAGES } from "../prompts";
|
|
15
21
|
|
|
16
|
-
import type { FilePatch } from "../components/repo/DiffViewer";
|
|
17
22
|
import type { Message } from "../types/chat";
|
|
18
23
|
import type { Provider } from "../types/config";
|
|
19
24
|
import { FEW_SHOT_MESSAGES } from "../prompts";
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
import { registry } from "./tools/registry";
|
|
26
|
+
import type { FilePatch } from "../components/repo/DiffViewer";
|
|
22
27
|
|
|
23
28
|
export type ParsedResponse =
|
|
24
|
-
| { kind: "text"; content: string }
|
|
25
|
-
| { kind: "changes"; content: string; patches: FilePatch[] }
|
|
26
|
-
| { kind: "shell"; content: string; command: string }
|
|
27
|
-
| { kind: "fetch"; content: string; url: string }
|
|
28
|
-
| { kind: "read-file"; content: string; filePath: string }
|
|
29
|
-
| { kind: "read-folder"; content: string; folderPath: string }
|
|
30
|
-
| { kind: "grep"; content: string; pattern: string; glob: string }
|
|
31
|
-
| { kind: "delete-file"; content: string; filePath: string }
|
|
32
|
-
| { kind: "delete-folder"; content: string; folderPath: string }
|
|
33
|
-
| { kind: "open-url"; content: string; url: string }
|
|
29
|
+
| { kind: "text"; content: string; remainder?: string }
|
|
34
30
|
| {
|
|
35
|
-
kind: "
|
|
31
|
+
kind: "changes";
|
|
36
32
|
content: string;
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
patches: FilePatch[];
|
|
34
|
+
remainder?: string;
|
|
39
35
|
}
|
|
36
|
+
| { kind: "clone"; content: string; repoUrl: string; remainder?: string }
|
|
40
37
|
| {
|
|
41
|
-
kind: "
|
|
38
|
+
kind: "tool";
|
|
39
|
+
toolName: string;
|
|
40
|
+
input: unknown;
|
|
41
|
+
rawInput: string;
|
|
42
42
|
content: string;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
| { kind: "search"; content: string; query: string }
|
|
47
|
-
| { kind: "clone"; content: string; repoUrl: string };
|
|
43
|
+
remainder?: string;
|
|
44
|
+
};
|
|
48
45
|
|
|
49
46
|
export function parseResponse(text: string): ParsedResponse {
|
|
50
47
|
const scanText = text.replace(/```[\s\S]*?```/g, (m) => " ".repeat(m.length));
|
|
51
48
|
|
|
52
49
|
type Candidate = {
|
|
53
50
|
index: number;
|
|
54
|
-
|
|
55
|
-
| "changes"
|
|
56
|
-
| "shell"
|
|
57
|
-
| "fetch"
|
|
58
|
-
| "read-file"
|
|
59
|
-
| "read-folder"
|
|
60
|
-
| "grep"
|
|
61
|
-
| "delete-file"
|
|
62
|
-
| "delete-folder"
|
|
63
|
-
| "open-url"
|
|
64
|
-
| "generate-pdf"
|
|
65
|
-
| "write-file"
|
|
66
|
-
| "search"
|
|
67
|
-
| "clone";
|
|
51
|
+
toolName: string;
|
|
68
52
|
match: RegExpExecArray;
|
|
69
53
|
};
|
|
70
54
|
const candidates: Candidate[] = [];
|
|
71
55
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
{
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
{ kind: "changes", re: /```changes\r?\n([\s\S]*?)\r?\n```/g },
|
|
97
|
-
];
|
|
56
|
+
for (const toolName of registry.names()) {
|
|
57
|
+
const escaped = toolName.replace(/[-]/g, "\\-");
|
|
58
|
+
|
|
59
|
+
// XML tag
|
|
60
|
+
const xmlRe = new RegExp(`<${escaped}>([\\s\\S]*?)<\\/${escaped}>`, "g");
|
|
61
|
+
xmlRe.lastIndex = 0;
|
|
62
|
+
const xmlM = xmlRe.exec(scanText);
|
|
63
|
+
if (xmlM) {
|
|
64
|
+
const orig = new RegExp(xmlRe.source);
|
|
65
|
+
const origM = orig.exec(text.slice(xmlM.index));
|
|
66
|
+
if (origM) {
|
|
67
|
+
candidates.push({
|
|
68
|
+
index: xmlM.index,
|
|
69
|
+
toolName,
|
|
70
|
+
match: Object.assign(
|
|
71
|
+
[
|
|
72
|
+
text.slice(xmlM.index, xmlM.index + origM[0].length),
|
|
73
|
+
origM[1],
|
|
74
|
+
] as unknown as RegExpExecArray,
|
|
75
|
+
{ index: xmlM.index, input: text, groups: undefined },
|
|
76
|
+
),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
98
80
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
81
|
+
// Fenced code block fallback
|
|
82
|
+
const fencedRe = new RegExp(
|
|
83
|
+
`\`\`\`${escaped}\\r?\\n([\\s\\S]*?)\\r?\\n\`\`\``,
|
|
84
|
+
"g",
|
|
85
|
+
);
|
|
86
|
+
fencedRe.lastIndex = 0;
|
|
87
|
+
const fencedM = fencedRe.exec(scanText);
|
|
88
|
+
if (fencedM) {
|
|
89
|
+
const orig = new RegExp(fencedRe.source);
|
|
90
|
+
const origM = orig.exec(text.slice(fencedM.index));
|
|
91
|
+
if (origM) {
|
|
92
|
+
candidates.push({
|
|
93
|
+
index: fencedM.index,
|
|
94
|
+
toolName,
|
|
95
|
+
match: Object.assign(
|
|
96
|
+
[
|
|
97
|
+
text.slice(fencedM.index, fencedM.index + origM[0].length),
|
|
98
|
+
origM[1],
|
|
99
|
+
] as unknown as RegExpExecArray,
|
|
100
|
+
{ index: fencedM.index, input: text, groups: undefined },
|
|
101
|
+
),
|
|
102
|
+
});
|
|
114
103
|
}
|
|
115
104
|
}
|
|
116
105
|
}
|
|
@@ -118,104 +107,56 @@ export function parseResponse(text: string): ParsedResponse {
|
|
|
118
107
|
if (candidates.length === 0) return { kind: "text", content: text.trim() };
|
|
119
108
|
|
|
120
109
|
candidates.sort((a, b) => a.index - b.index);
|
|
121
|
-
const {
|
|
110
|
+
const { toolName, match } = candidates[0]!;
|
|
122
111
|
|
|
123
|
-
const before = text
|
|
124
|
-
.slice(0, match.index)
|
|
125
|
-
.replace(
|
|
126
|
-
/<(fetch|shell|read-file|read-folder|write-file|search|clone|changes)[^>]*>[\s\S]*?<\/\1>/g,
|
|
127
|
-
"",
|
|
128
|
-
)
|
|
129
|
-
.trim();
|
|
112
|
+
const before = text.slice(0, match.index).trim();
|
|
130
113
|
const body = (match[1] ?? "").trim();
|
|
114
|
+
const afterMatch = text.slice(match.index + match[0].length).trim();
|
|
115
|
+
const remainder = afterMatch.length > 0 ? afterMatch : undefined;
|
|
131
116
|
|
|
132
|
-
|
|
117
|
+
// Special UI variants
|
|
118
|
+
if (toolName === "changes") {
|
|
133
119
|
try {
|
|
134
120
|
const parsed = JSON.parse(body) as {
|
|
135
121
|
summary: string;
|
|
136
122
|
patches: FilePatch[];
|
|
137
123
|
};
|
|
138
124
|
const display = [before, parsed.summary].filter(Boolean).join("\n\n");
|
|
139
|
-
return {
|
|
125
|
+
return {
|
|
126
|
+
kind: "changes",
|
|
127
|
+
content: display,
|
|
128
|
+
patches: parsed.patches,
|
|
129
|
+
remainder,
|
|
130
|
+
};
|
|
140
131
|
} catch {
|
|
141
|
-
|
|
132
|
+
return { kind: "text", content: text.trim() };
|
|
142
133
|
}
|
|
143
134
|
}
|
|
144
135
|
|
|
145
|
-
if (
|
|
146
|
-
return { kind: "shell", content: before, command: body };
|
|
147
|
-
if (kind === "fetch")
|
|
148
|
-
return {
|
|
149
|
-
kind: "fetch",
|
|
150
|
-
content: before,
|
|
151
|
-
url: body.replace(/^<|>$/g, "").trim(),
|
|
152
|
-
};
|
|
153
|
-
if (kind === "read-file")
|
|
154
|
-
return { kind: "read-file", content: before, filePath: body };
|
|
155
|
-
if (kind === "read-folder")
|
|
156
|
-
return { kind: "read-folder", content: before, folderPath: body };
|
|
157
|
-
if (kind === "delete-file")
|
|
158
|
-
return { kind: "delete-file", content: before, filePath: body };
|
|
159
|
-
if (kind === "delete-folder")
|
|
160
|
-
return { kind: "delete-folder", content: before, folderPath: body };
|
|
161
|
-
if (kind === "open-url")
|
|
162
|
-
return {
|
|
163
|
-
kind: "open-url",
|
|
164
|
-
content: before,
|
|
165
|
-
url: body.replace(/^<|>$/g, "").trim(),
|
|
166
|
-
};
|
|
167
|
-
if (kind === "search")
|
|
168
|
-
return { kind: "search", content: before, query: body };
|
|
169
|
-
if (kind === "clone")
|
|
136
|
+
if (toolName === "clone") {
|
|
170
137
|
return {
|
|
171
138
|
kind: "clone",
|
|
172
139
|
content: before,
|
|
173
140
|
repoUrl: body.replace(/^<|>$/g, "").trim(),
|
|
141
|
+
remainder,
|
|
174
142
|
};
|
|
175
|
-
|
|
176
|
-
if (kind === "generate-pdf") {
|
|
177
|
-
try {
|
|
178
|
-
const parsed = JSON.parse(body);
|
|
179
|
-
return {
|
|
180
|
-
kind: "generate-pdf",
|
|
181
|
-
content: before,
|
|
182
|
-
filePath: parsed.path ?? parsed.filePath ?? "output.pdf",
|
|
183
|
-
pdfContent: parsed.content ?? "",
|
|
184
|
-
};
|
|
185
|
-
} catch {
|
|
186
|
-
return { kind: "text", content: text };
|
|
187
|
-
}
|
|
188
143
|
}
|
|
189
144
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return {
|
|
194
|
-
kind: "grep",
|
|
195
|
-
content: before,
|
|
196
|
-
pattern: parsed.pattern,
|
|
197
|
-
glob: parsed.glob ?? "**/*",
|
|
198
|
-
};
|
|
199
|
-
} catch {
|
|
200
|
-
return { kind: "grep", content: before, pattern: body, glob: "**/*" };
|
|
201
|
-
}
|
|
202
|
-
}
|
|
145
|
+
// Generic tool
|
|
146
|
+
const tool = registry.get(toolName);
|
|
147
|
+
if (!tool) return { kind: "text", content: text.trim() };
|
|
203
148
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const parsed = JSON.parse(body) as { path: string; content: string };
|
|
207
|
-
return {
|
|
208
|
-
kind: "write-file",
|
|
209
|
-
content: before,
|
|
210
|
-
filePath: parsed.path,
|
|
211
|
-
fileContent: parsed.content,
|
|
212
|
-
};
|
|
213
|
-
} catch {
|
|
214
|
-
/* fall through */
|
|
215
|
-
}
|
|
216
|
-
}
|
|
149
|
+
const input = tool.parseInput(body);
|
|
150
|
+
if (input === null) return { kind: "text", content: text.trim() };
|
|
217
151
|
|
|
218
|
-
return {
|
|
152
|
+
return {
|
|
153
|
+
kind: "tool",
|
|
154
|
+
toolName,
|
|
155
|
+
input,
|
|
156
|
+
rawInput: body,
|
|
157
|
+
content: before,
|
|
158
|
+
remainder,
|
|
159
|
+
};
|
|
219
160
|
}
|
|
220
161
|
|
|
221
162
|
// ── Clone tag helper ──────────────────────────────────────────────────────────
|
|
@@ -251,31 +192,9 @@ function buildApiMessages(
|
|
|
251
192
|
"The tool call was denied by the user. Please respond without using that tool.",
|
|
252
193
|
};
|
|
253
194
|
}
|
|
254
|
-
const label =
|
|
255
|
-
m.toolName === "shell"
|
|
256
|
-
? `shell command \`${m.content}\``
|
|
257
|
-
: m.toolName === "fetch"
|
|
258
|
-
? `fetch of ${m.content}`
|
|
259
|
-
: m.toolName === "read-file"
|
|
260
|
-
? `read-file of ${m.content}`
|
|
261
|
-
: m.toolName === "read-folder"
|
|
262
|
-
? `read-folder of ${m.content}`
|
|
263
|
-
: m.toolName === "grep"
|
|
264
|
-
? `grep for "${m.content}"`
|
|
265
|
-
: m.toolName === "delete-file"
|
|
266
|
-
? `delete-file of ${m.content}`
|
|
267
|
-
: m.toolName === "delete-folder"
|
|
268
|
-
? `delete-folder of ${m.content}`
|
|
269
|
-
: m.toolName === "open-url"
|
|
270
|
-
? `open-url ${m.content}`
|
|
271
|
-
: m.toolName === "generate-pdf"
|
|
272
|
-
? `generate-pdf to ${m.content}`
|
|
273
|
-
: m.toolName === "search"
|
|
274
|
-
? `web search for "${m.content}"`
|
|
275
|
-
: `write-file to ${m.content}`;
|
|
276
195
|
return {
|
|
277
196
|
role: "user",
|
|
278
|
-
content: `Here is the output from the ${
|
|
197
|
+
content: `Here is the output from the ${m.toolName} of ${m.content}:\n\n${m.result}\n\nPlease continue your response based on this output.`,
|
|
279
198
|
};
|
|
280
199
|
}
|
|
281
200
|
return { role: m.role, content: m.content };
|