@morphllm/morphsdk 0.2.56 → 0.2.58
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/anthropic-CaFUHxBW.d.ts +89 -0
- package/dist/{chunk-SALJ2K6S.js → chunk-2CASO3ZO.js} +60 -98
- package/dist/chunk-2CASO3ZO.js.map +1 -0
- package/dist/chunk-374N3GIA.js +118 -0
- package/dist/chunk-374N3GIA.js.map +1 -0
- package/dist/chunk-3IQIT6MC.js +65 -0
- package/dist/chunk-3IQIT6MC.js.map +1 -0
- package/dist/chunk-4VGOBA2J.js +57 -0
- package/dist/chunk-4VGOBA2J.js.map +1 -0
- package/dist/chunk-527P5X2E.js +98 -0
- package/dist/chunk-527P5X2E.js.map +1 -0
- package/dist/chunk-6N6ZYZYD.js +74 -0
- package/dist/chunk-6N6ZYZYD.js.map +1 -0
- package/dist/chunk-6Y5JB4JC.js +195 -0
- package/dist/chunk-6Y5JB4JC.js.map +1 -0
- package/dist/{chunk-WSSSSBWU.js → chunk-7EIHYJSG.js} +18 -18
- package/dist/chunk-7EIHYJSG.js.map +1 -0
- package/dist/{chunk-TICMYDII.js → chunk-APP75CBN.js} +33 -16
- package/dist/chunk-APP75CBN.js.map +1 -0
- package/dist/chunk-ILJ3J5IA.js +72 -0
- package/dist/chunk-ILJ3J5IA.js.map +1 -0
- package/dist/chunk-ISWL67SF.js +1 -0
- package/dist/chunk-KW7OEGZK.js +9 -0
- package/dist/chunk-KW7OEGZK.js.map +1 -0
- package/dist/chunk-Q5AHGIQO.js +205 -0
- package/dist/chunk-Q5AHGIQO.js.map +1 -0
- package/dist/{chunk-TJIUA27P.js → chunk-XT5ZO6ES.js} +9 -5
- package/dist/chunk-XT5ZO6ES.js.map +1 -0
- package/dist/{chunk-LVPVVLTI.js → chunk-YV75OQTE.js} +105 -17
- package/dist/chunk-YV75OQTE.js.map +1 -0
- package/dist/{chunk-ZJIIICRA.js → chunk-ZO4PPFCZ.js} +60 -29
- package/dist/chunk-ZO4PPFCZ.js.map +1 -0
- package/dist/{client-CFoR--IU.d.ts → client-CextMMm9.d.ts} +10 -15
- package/dist/client.cjs +698 -466
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.ts +3 -2
- package/dist/client.js +14 -15
- package/dist/finish-kXAcUJyB.d.ts +33 -0
- package/dist/gemini-CE80Pbdy.d.ts +117 -0
- package/dist/index.cjs +711 -466
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +16 -16
- package/dist/openai-Fvpqln7F.d.ts +89 -0
- package/dist/tools/warp_grep/agent/config.cjs +8 -4
- package/dist/tools/warp_grep/agent/config.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/config.d.ts +7 -2
- package/dist/tools/warp_grep/agent/config.js +1 -1
- package/dist/tools/warp_grep/agent/formatter.cjs +32 -15
- package/dist/tools/warp_grep/agent/formatter.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/formatter.d.ts +1 -1
- package/dist/tools/warp_grep/agent/formatter.js +1 -1
- package/dist/tools/warp_grep/agent/parser.cjs +104 -17
- package/dist/tools/warp_grep/agent/parser.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/parser.d.ts +3 -5
- package/dist/tools/warp_grep/agent/parser.js +1 -3
- package/dist/tools/warp_grep/agent/prompt.cjs +132 -56
- package/dist/tools/warp_grep/agent/prompt.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/prompt.d.ts +1 -1
- package/dist/tools/warp_grep/agent/prompt.js +1 -1
- package/dist/tools/warp_grep/agent/runner.cjs +466 -313
- package/dist/tools/warp_grep/agent/runner.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/runner.d.ts +1 -0
- package/dist/tools/warp_grep/agent/runner.js +6 -9
- package/dist/tools/warp_grep/agent/types.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/types.d.ts +9 -2
- package/dist/tools/warp_grep/anthropic.cjs +656 -380
- package/dist/tools/warp_grep/anthropic.cjs.map +1 -1
- package/dist/tools/warp_grep/anthropic.d.ts +4 -74
- package/dist/tools/warp_grep/anthropic.js +13 -16
- package/dist/tools/warp_grep/client.cjs +1593 -0
- package/dist/tools/warp_grep/client.cjs.map +1 -0
- package/dist/tools/warp_grep/client.d.ts +87 -0
- package/dist/tools/warp_grep/client.js +26 -0
- package/dist/tools/warp_grep/gemini.cjs +1587 -0
- package/dist/tools/warp_grep/gemini.cjs.map +1 -0
- package/dist/tools/warp_grep/gemini.d.ts +7 -0
- package/dist/tools/warp_grep/gemini.js +34 -0
- package/dist/tools/warp_grep/harness.cjs +1195 -0
- package/dist/tools/warp_grep/harness.cjs.map +1 -0
- package/dist/tools/warp_grep/harness.d.ts +107 -0
- package/dist/tools/warp_grep/harness.js +68 -0
- package/dist/tools/warp_grep/harness.js.map +1 -0
- package/dist/tools/warp_grep/index.cjs +818 -466
- package/dist/tools/warp_grep/index.cjs.map +1 -1
- package/dist/tools/warp_grep/index.d.ts +11 -6
- package/dist/tools/warp_grep/index.js +43 -23
- package/dist/tools/warp_grep/openai.cjs +656 -378
- package/dist/tools/warp_grep/openai.cjs.map +1 -1
- package/dist/tools/warp_grep/openai.d.ts +4 -74
- package/dist/tools/warp_grep/openai.js +13 -14
- package/dist/tools/warp_grep/providers/local.cjs +66 -27
- package/dist/tools/warp_grep/providers/local.cjs.map +1 -1
- package/dist/tools/warp_grep/providers/local.d.ts +4 -9
- package/dist/tools/warp_grep/providers/local.js +2 -2
- package/dist/tools/warp_grep/providers/remote.cjs +211 -0
- package/dist/tools/warp_grep/providers/remote.cjs.map +1 -0
- package/dist/tools/warp_grep/providers/remote.d.ts +67 -0
- package/dist/tools/warp_grep/providers/remote.js +9 -0
- package/dist/tools/warp_grep/providers/types.cjs.map +1 -1
- package/dist/tools/warp_grep/providers/types.d.ts +7 -15
- package/dist/tools/warp_grep/vercel.cjs +668 -397
- package/dist/tools/warp_grep/vercel.cjs.map +1 -1
- package/dist/tools/warp_grep/vercel.d.ts +4 -51
- package/dist/tools/warp_grep/vercel.js +16 -15
- package/dist/types-a_hxdPI6.d.ts +144 -0
- package/dist/vercel-3yjvfmVB.d.ts +66 -0
- package/package.json +17 -2
- package/dist/chunk-4ZHDBKBY.js +0 -83
- package/dist/chunk-4ZHDBKBY.js.map +0 -1
- package/dist/chunk-73RQWOQC.js +0 -16
- package/dist/chunk-73RQWOQC.js.map +0 -1
- package/dist/chunk-7OQOOB3R.js +0 -1
- package/dist/chunk-EK7OQPWD.js +0 -44
- package/dist/chunk-EK7OQPWD.js.map +0 -1
- package/dist/chunk-GJURLQ3L.js +0 -77
- package/dist/chunk-GJURLQ3L.js.map +0 -1
- package/dist/chunk-HQO45BAJ.js +0 -14
- package/dist/chunk-HQO45BAJ.js.map +0 -1
- package/dist/chunk-LVPVVLTI.js.map +0 -1
- package/dist/chunk-NDZO5IPV.js +0 -121
- package/dist/chunk-NDZO5IPV.js.map +0 -1
- package/dist/chunk-QVRXBAMM.js +0 -107
- package/dist/chunk-QVRXBAMM.js.map +0 -1
- package/dist/chunk-SALJ2K6S.js.map +0 -1
- package/dist/chunk-TICMYDII.js.map +0 -1
- package/dist/chunk-TJIUA27P.js.map +0 -1
- package/dist/chunk-UIRJE422.js +0 -70
- package/dist/chunk-UIRJE422.js.map +0 -1
- package/dist/chunk-WETRQJGU.js +0 -129
- package/dist/chunk-WETRQJGU.js.map +0 -1
- package/dist/chunk-WSSSSBWU.js.map +0 -1
- package/dist/chunk-ZJIIICRA.js.map +0 -1
- package/dist/core-CpkYEi_T.d.ts +0 -158
- package/dist/tools/warp_grep/agent/grep_helpers.cjs +0 -148
- package/dist/tools/warp_grep/agent/grep_helpers.cjs.map +0 -1
- package/dist/tools/warp_grep/agent/grep_helpers.d.ts +0 -16
- package/dist/tools/warp_grep/agent/grep_helpers.js +0 -14
- package/dist/tools/warp_grep/tools/analyse.cjs +0 -40
- package/dist/tools/warp_grep/tools/analyse.cjs.map +0 -1
- package/dist/tools/warp_grep/tools/analyse.d.ts +0 -10
- package/dist/tools/warp_grep/tools/analyse.js +0 -8
- package/dist/tools/warp_grep/tools/finish.cjs +0 -69
- package/dist/tools/warp_grep/tools/finish.cjs.map +0 -1
- package/dist/tools/warp_grep/tools/finish.d.ts +0 -10
- package/dist/tools/warp_grep/tools/finish.js +0 -10
- package/dist/tools/warp_grep/tools/grep.cjs +0 -38
- package/dist/tools/warp_grep/tools/grep.cjs.map +0 -1
- package/dist/tools/warp_grep/tools/grep.d.ts +0 -8
- package/dist/tools/warp_grep/tools/grep.js +0 -15
- package/dist/tools/warp_grep/tools/grep.js.map +0 -1
- package/dist/tools/warp_grep/tools/read.cjs +0 -38
- package/dist/tools/warp_grep/tools/read.cjs.map +0 -1
- package/dist/tools/warp_grep/tools/read.d.ts +0 -9
- package/dist/tools/warp_grep/tools/read.js +0 -8
- package/dist/tools/warp_grep/tools/read.js.map +0 -1
- package/dist/tools/warp_grep/utils/format.cjs +0 -42
- package/dist/tools/warp_grep/utils/format.cjs.map +0 -1
- package/dist/tools/warp_grep/utils/format.d.ts +0 -4
- package/dist/tools/warp_grep/utils/format.js +0 -18
- package/dist/tools/warp_grep/utils/format.js.map +0 -1
- /package/dist/{chunk-7OQOOB3R.js.map → chunk-ISWL67SF.js.map} +0 -0
- /package/dist/tools/warp_grep/{agent/grep_helpers.js.map → client.js.map} +0 -0
- /package/dist/tools/warp_grep/{tools/analyse.js.map → gemini.js.map} +0 -0
- /package/dist/tools/warp_grep/{tools/finish.js.map → providers/remote.js.map} +0 -0
|
@@ -30,7 +30,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// tools/warp_grep/vercel.ts
|
|
31
31
|
var vercel_exports = {};
|
|
32
32
|
__export(vercel_exports, {
|
|
33
|
-
|
|
33
|
+
createWarpGrepTool: () => createWarpGrepTool,
|
|
34
|
+
default: () => vercel_default,
|
|
34
35
|
execute: () => execute,
|
|
35
36
|
formatResult: () => formatResult,
|
|
36
37
|
getSystemPrompt: () => getSystemPrompt
|
|
@@ -41,9 +42,13 @@ var import_zod = require("zod");
|
|
|
41
42
|
|
|
42
43
|
// tools/warp_grep/agent/config.ts
|
|
43
44
|
var AGENT_CONFIG = {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
MAX_TURNS: 4,
|
|
46
|
+
TIMEOUT_MS: 3e4,
|
|
47
|
+
MAX_CONTEXT_CHARS: 54e4,
|
|
48
|
+
MAX_OUTPUT_LINES: 200,
|
|
49
|
+
MAX_READ_LINES: 800,
|
|
50
|
+
MAX_LIST_DEPTH: 3,
|
|
51
|
+
LIST_TIMEOUT_MS: 2e3
|
|
47
52
|
};
|
|
48
53
|
var BUILTIN_EXCLUDES = [
|
|
49
54
|
// Version control
|
|
@@ -125,113 +130,191 @@ var BUILTIN_EXCLUDES = [
|
|
|
125
130
|
".*"
|
|
126
131
|
];
|
|
127
132
|
var DEFAULT_EXCLUDES = (process.env.MORPH_WARP_GREP_EXCLUDE || "").split(",").map((s) => s.trim()).filter(Boolean).concat(BUILTIN_EXCLUDES);
|
|
128
|
-
var DEFAULT_MODEL = "morph-warp-grep";
|
|
133
|
+
var DEFAULT_MODEL = "morph-warp-grep-v1";
|
|
129
134
|
|
|
130
135
|
// tools/warp_grep/agent/prompt.ts
|
|
131
|
-
var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given
|
|
136
|
+
var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given search_string.
|
|
132
137
|
|
|
133
|
-
|
|
138
|
+
### workflow
|
|
134
139
|
You have exactly 4 turns. The 4th turn MUST be a \`finish\` call. Each turn allows up to 8 parallel tool calls.
|
|
135
140
|
|
|
136
|
-
- Turn 1: Map the territory OR dive deep (based on
|
|
141
|
+
- Turn 1: Map the territory OR dive deep (based on search_string specificity)
|
|
137
142
|
- Turn 2-3: Refine based on findings
|
|
138
143
|
- Turn 4: MUST call \`finish\` with all relevant code locations
|
|
139
144
|
- You MAY call \`finish\` early if confident\u2014but never before at least 1 search turn.
|
|
145
|
+
- The user strongly prefers if you can call the finish tool early, but you must be correct
|
|
140
146
|
|
|
141
|
-
Remember, if the task feels easy to you, it is strongly desirable to call
|
|
142
|
-
</workflow>
|
|
147
|
+
Remember, if the task feels easy to you, it is strongly desirable to call 'finish' early using fewer turns, but quality over speed
|
|
143
148
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
### tools
|
|
150
|
+
Tool calls use nested XML elements:
|
|
151
|
+
\`\`\`xml
|
|
152
|
+
<tool_name>
|
|
153
|
+
<parameter>value</parameter>
|
|
154
|
+
</tool_name>
|
|
155
|
+
\`\`\`
|
|
156
|
+
|
|
157
|
+
### \`list_directory\`
|
|
158
|
+
Directory tree view. Shows structure of a path, optionally filtered by regex pattern.
|
|
159
|
+
|
|
160
|
+
Elements:
|
|
161
|
+
- \`<path>\` (required): Directory path to list (use \`.\` for repo root)
|
|
162
|
+
- \`<pattern>\` (optional): Regex to filter results
|
|
149
163
|
|
|
150
164
|
Examples:
|
|
151
165
|
\`\`\`
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
166
|
+
<list_directory>
|
|
167
|
+
<path>src/services</path>
|
|
168
|
+
</list_directory>
|
|
169
|
+
|
|
170
|
+
<list_directory>
|
|
171
|
+
<path>lib/utils</path>
|
|
172
|
+
<pattern>.*\\.(ts|js)$</pattern>
|
|
173
|
+
</list_directory>
|
|
156
174
|
\`\`\`
|
|
157
175
|
|
|
158
|
-
### \`read
|
|
159
|
-
Read file contents.
|
|
176
|
+
### \`read\`
|
|
177
|
+
Read file contents. Supports multiple line ranges.
|
|
160
178
|
- Returns numbered lines for easy reference
|
|
161
|
-
-
|
|
179
|
+
- ALWAYS include import statements (usually lines 1-20). Better to over-include than miss context.
|
|
180
|
+
|
|
181
|
+
Elements:
|
|
182
|
+
- \`<path>\` (required): File path to read
|
|
183
|
+
- \`<lines>\` (optional): Line ranges like "1-50,75-80,100-120" (omit to read entire file)
|
|
162
184
|
|
|
163
185
|
Examples:
|
|
164
186
|
\`\`\`
|
|
165
|
-
read
|
|
166
|
-
|
|
167
|
-
read
|
|
187
|
+
<read>
|
|
188
|
+
<path>src/main.py</path>
|
|
189
|
+
</read>
|
|
190
|
+
|
|
191
|
+
<read>
|
|
192
|
+
<path>src/auth.py</path>
|
|
193
|
+
<lines>1-20,45-80,150-200</lines>
|
|
194
|
+
</read>
|
|
168
195
|
\`\`\`
|
|
169
196
|
|
|
170
|
-
### \`grep
|
|
171
|
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
197
|
+
### \`grep\`
|
|
198
|
+
Search for pattern matches across files. Returns matches with 1 line of context above and below.
|
|
199
|
+
- Match lines use \`:\` separator \u2192 \`filepath:linenum:content\`
|
|
200
|
+
- Context lines use \`-\` separator \u2192 \`filepath-linenum-content\`
|
|
201
|
+
|
|
202
|
+
Elements:
|
|
203
|
+
- \`<pattern>\` (required): Search pattern (regex). Use \`(a|b)\` for OR patterns.
|
|
204
|
+
- \`<sub_dir>\` (optional): Subdirectory to search in (defaults to \`.\`)
|
|
205
|
+
- \`<glob>\` (optional): File pattern filter like \`*.py\` or \`*.{ts,tsx}\`
|
|
174
206
|
|
|
175
207
|
Examples:
|
|
176
208
|
\`\`\`
|
|
177
|
-
grep
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
grep
|
|
209
|
+
<grep>
|
|
210
|
+
<pattern>(authenticate|authorize|login)</pattern>
|
|
211
|
+
<sub_dir>src/auth/</sub_dir>
|
|
212
|
+
</grep>
|
|
213
|
+
|
|
214
|
+
<grep>
|
|
215
|
+
<pattern>class.*(Service|Controller)</pattern>
|
|
216
|
+
<glob>*.{ts,js}</glob>
|
|
217
|
+
</grep>
|
|
218
|
+
|
|
219
|
+
<grep>
|
|
220
|
+
<pattern>(DB_HOST|DATABASE_URL|connection)</pattern>
|
|
221
|
+
<glob>*.{py,yaml,env}</glob>
|
|
222
|
+
<sub_dir>lib/</sub_dir>
|
|
223
|
+
</grep>
|
|
181
224
|
\`\`\`
|
|
182
225
|
|
|
183
|
-
### \`finish
|
|
184
|
-
Submit final answer with all relevant code locations.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
-
|
|
188
|
-
-
|
|
189
|
-
|
|
226
|
+
### \`finish\`
|
|
227
|
+
Submit final answer with all relevant code locations. Uses nested \`<file>\` elements.
|
|
228
|
+
|
|
229
|
+
File elements:
|
|
230
|
+
- \`<path>\` (required): File path
|
|
231
|
+
- \`<lines>\` (optional): Line ranges like "1-50,75-80" (\`*\` for entire file)
|
|
232
|
+
|
|
233
|
+
ALWAYS include import statements (usually lines 1-20). Better to over-include than miss context.
|
|
190
234
|
|
|
191
235
|
Examples:
|
|
192
236
|
\`\`\`
|
|
193
|
-
finish
|
|
194
|
-
|
|
237
|
+
<finish>
|
|
238
|
+
<file>
|
|
239
|
+
<path>src/auth.py</path>
|
|
240
|
+
<lines>1-15,25-50,75-80</lines>
|
|
241
|
+
</file>
|
|
242
|
+
<file>
|
|
243
|
+
<path>src/models/user.py</path>
|
|
244
|
+
<lines>*</lines>
|
|
245
|
+
</file>
|
|
246
|
+
</finish>
|
|
195
247
|
\`\`\`
|
|
196
248
|
</tools>
|
|
197
249
|
|
|
198
250
|
<strategy>
|
|
199
|
-
**Before your first tool call, classify the
|
|
251
|
+
**Before your first tool call, classify the search_string:**
|
|
200
252
|
|
|
201
|
-
|
|
|
202
|
-
|
|
203
|
-
| **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by
|
|
204
|
-
| **Conceptual** (how does X work, where is Y handled) |
|
|
205
|
-
| **Exploratory** (find all tests, list API endpoints) |
|
|
253
|
+
| Search_string Type | Round 1 Strategy | Early Finish? |
|
|
254
|
+
|------------|------------------|---------------|
|
|
255
|
+
| **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by round 2 |
|
|
256
|
+
| **Conceptual** (how does X work, where is Y handled) | list_directory + 2-3 broad greps | Rarely early |
|
|
257
|
+
| **Exploratory** (find all tests, list API endpoints) | list_directory at multiple depths | Usually needs 3 rounds |
|
|
206
258
|
|
|
207
259
|
**Parallel call patterns:**
|
|
208
260
|
- **Shotgun grep**: Same pattern, 8 different directories\u2014fast coverage
|
|
209
261
|
- **Variant grep**: 8 pattern variations (synonyms, naming conventions)\u2014catches inconsistent codebases
|
|
210
|
-
- **Funnel**: 1
|
|
262
|
+
- **Funnel**: 1 list_directory + 7 greps\u2014orient and search simultaneously
|
|
211
263
|
- **Deep read**: 8 reads on files you already identified\u2014gather full context fast
|
|
264
|
+
|
|
265
|
+
**Tool call expectations:**
|
|
266
|
+
- Low quality tool calls are ones that give back sparse information. This either means they are not well thought out and are not educated guesses OR, they are too broad and give back too many results.
|
|
267
|
+
- High quality tool calls strike a balance between complexity in the tool call to exclude results we know we don't want, and how wide the search space is so that we don't miss anything. It is ok to start off with wider search spaces, but is imperative that you use your intuition from there on out and seek high quality tool calls only.
|
|
268
|
+
- You are not starting blind, you have some information about root level repo structure going in, so use that to prevent making trivial repo wide queries.
|
|
269
|
+
- The grep tool shows you which file path and line numbers the pattern was found in, use this information smartly when trying to read the file.
|
|
212
270
|
</strategy>
|
|
213
271
|
|
|
214
272
|
<output_format>
|
|
215
273
|
EVERY response MUST follow this exact format:
|
|
216
274
|
|
|
217
275
|
1. First, wrap your reasoning in \`<think>...</think>\` tags containing:
|
|
218
|
-
-
|
|
219
|
-
- Confidence estimate (can I finish in 1-2
|
|
220
|
-
- This
|
|
276
|
+
- Search_string classification (specific/conceptual/exploratory)
|
|
277
|
+
- Confidence estimate (can I finish in 1-2 rounds?)
|
|
278
|
+
- This round's parallel strategy
|
|
221
279
|
- What signals would let me finish early?
|
|
222
280
|
|
|
223
|
-
2. Then, output
|
|
281
|
+
2. Then, output up to 8 tool calls using nested XML elements.
|
|
224
282
|
|
|
225
283
|
Example:
|
|
226
284
|
\`\`\`
|
|
227
285
|
<think>
|
|
228
|
-
This is a specific
|
|
229
|
-
High confidence I can finish in 2
|
|
286
|
+
This is a specific search_string about authentication. I'll grep for auth-related patterns.
|
|
287
|
+
High confidence I can finish in 2 rounds if I find the auth module. I have already been shown the repo's structure at root
|
|
230
288
|
Strategy: Shotgun grep across likely directories.
|
|
231
289
|
</think>
|
|
232
|
-
<
|
|
233
|
-
<
|
|
234
|
-
<
|
|
290
|
+
<grep>
|
|
291
|
+
<pattern>(authenticate|login|session)</pattern>
|
|
292
|
+
<sub_dir>src/auth/</sub_dir>
|
|
293
|
+
</grep>
|
|
294
|
+
<grep>
|
|
295
|
+
<pattern>(middleware|interceptor)</pattern>
|
|
296
|
+
<glob>*.{ts,js}</glob>
|
|
297
|
+
</grep>
|
|
298
|
+
<list_directory>
|
|
299
|
+
<path>src/auth</path>
|
|
300
|
+
</list_directory>
|
|
301
|
+
\`\`\`
|
|
302
|
+
|
|
303
|
+
Finishing example:
|
|
304
|
+
\`\`\`
|
|
305
|
+
<think>
|
|
306
|
+
I think I have a rough idea, but this is my last turn so I must call the finish tool regardless.
|
|
307
|
+
</think>
|
|
308
|
+
<finish>
|
|
309
|
+
<file>
|
|
310
|
+
<path>src/auth/login.py</path>
|
|
311
|
+
<lines>1-50</lines>
|
|
312
|
+
</file>
|
|
313
|
+
<file>
|
|
314
|
+
<path>src/middleware/session.py</path>
|
|
315
|
+
<lines>10-80</lines>
|
|
316
|
+
</file>
|
|
317
|
+
</finish>
|
|
235
318
|
\`\`\`
|
|
236
319
|
|
|
237
320
|
No commentary outside \`<think>\`. No explanations after tool calls.
|
|
@@ -244,17 +327,111 @@ When calling \`finish\`:
|
|
|
244
327
|
- Include any type definitions, interfaces, or constants used
|
|
245
328
|
- Better to over-include than leave the user missing context
|
|
246
329
|
- If unsure about boundaries, include more rather than less
|
|
247
|
-
</finishing_requirements
|
|
248
|
-
|
|
249
|
-
Begin your exploration now to find code relevant to the query.`;
|
|
330
|
+
</finishing_requirements>`;
|
|
250
331
|
function getSystemPrompt() {
|
|
251
332
|
return SYSTEM_PROMPT;
|
|
252
333
|
}
|
|
253
334
|
|
|
254
335
|
// tools/warp_grep/agent/parser.ts
|
|
255
|
-
var VALID_COMMANDS = ["
|
|
336
|
+
var VALID_COMMANDS = ["list_directory", "grep", "read", "finish"];
|
|
337
|
+
function isValidCommand(name) {
|
|
338
|
+
return VALID_COMMANDS.includes(name);
|
|
339
|
+
}
|
|
340
|
+
function getXmlElementText(xml, tagName) {
|
|
341
|
+
const regex = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`, "i");
|
|
342
|
+
const match = xml.match(regex);
|
|
343
|
+
return match ? match[1].trim() : null;
|
|
344
|
+
}
|
|
345
|
+
function parseNestedXmlTools(text) {
|
|
346
|
+
const tools = [];
|
|
347
|
+
const toolRegex = /<([a-z_][a-z0-9_]*)>([\s\S]*?)<\/\1>/gi;
|
|
348
|
+
let match;
|
|
349
|
+
while ((match = toolRegex.exec(text)) !== null) {
|
|
350
|
+
const rawToolName = match[1].toLowerCase();
|
|
351
|
+
const content = match[2];
|
|
352
|
+
if (!isValidCommand(rawToolName)) continue;
|
|
353
|
+
const toolName = rawToolName;
|
|
354
|
+
if (toolName === "list_directory") {
|
|
355
|
+
const path5 = getXmlElementText(content, "path");
|
|
356
|
+
const pattern = getXmlElementText(content, "pattern");
|
|
357
|
+
if (path5) {
|
|
358
|
+
tools.push({ name: "list_directory", arguments: { path: path5, pattern } });
|
|
359
|
+
}
|
|
360
|
+
} else if (toolName === "grep") {
|
|
361
|
+
const pattern = getXmlElementText(content, "pattern");
|
|
362
|
+
const subDir = getXmlElementText(content, "sub_dir");
|
|
363
|
+
const glob = getXmlElementText(content, "glob");
|
|
364
|
+
if (pattern) {
|
|
365
|
+
tools.push({
|
|
366
|
+
name: "grep",
|
|
367
|
+
arguments: {
|
|
368
|
+
pattern,
|
|
369
|
+
path: subDir || ".",
|
|
370
|
+
...glob && { glob }
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
} else if (toolName === "read") {
|
|
375
|
+
const path5 = getXmlElementText(content, "path");
|
|
376
|
+
const linesStr = getXmlElementText(content, "lines");
|
|
377
|
+
if (path5) {
|
|
378
|
+
const args = { path: path5 };
|
|
379
|
+
if (linesStr) {
|
|
380
|
+
const ranges = [];
|
|
381
|
+
for (const rangeStr of linesStr.split(",")) {
|
|
382
|
+
const trimmed = rangeStr.trim();
|
|
383
|
+
if (!trimmed) continue;
|
|
384
|
+
const [s, e] = trimmed.split("-").map((v) => parseInt(v.trim(), 10));
|
|
385
|
+
if (Number.isFinite(s) && Number.isFinite(e)) {
|
|
386
|
+
ranges.push([s, e]);
|
|
387
|
+
} else if (Number.isFinite(s)) {
|
|
388
|
+
ranges.push([s, s]);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (ranges.length === 1) {
|
|
392
|
+
args.start = ranges[0][0];
|
|
393
|
+
args.end = ranges[0][1];
|
|
394
|
+
} else if (ranges.length > 1) {
|
|
395
|
+
args.lines = ranges;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
tools.push({ name: "read", arguments: args });
|
|
399
|
+
}
|
|
400
|
+
} else if (toolName === "finish") {
|
|
401
|
+
const fileRegex = /<file>([\s\S]*?)<\/file>/gi;
|
|
402
|
+
const files = [];
|
|
403
|
+
let fileMatch;
|
|
404
|
+
while ((fileMatch = fileRegex.exec(content)) !== null) {
|
|
405
|
+
const fileContent = fileMatch[1];
|
|
406
|
+
const filePath = getXmlElementText(fileContent, "path");
|
|
407
|
+
const linesStr = getXmlElementText(fileContent, "lines");
|
|
408
|
+
if (filePath && linesStr) {
|
|
409
|
+
const ranges = [];
|
|
410
|
+
for (const rangeStr of linesStr.split(",")) {
|
|
411
|
+
if (rangeStr.trim() === "*") {
|
|
412
|
+
ranges.push([1, 999999]);
|
|
413
|
+
} else {
|
|
414
|
+
const [s, e] = rangeStr.split("-").map((v) => parseInt(v.trim(), 10));
|
|
415
|
+
if (Number.isFinite(s) && Number.isFinite(e)) {
|
|
416
|
+
ranges.push([s, e]);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (ranges.length > 0) {
|
|
421
|
+
files.push({ path: filePath, lines: ranges });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (files.length > 0) {
|
|
426
|
+
tools.push({ name: "finish", arguments: { files } });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return tools;
|
|
431
|
+
}
|
|
256
432
|
function preprocessText(text) {
|
|
257
433
|
let processed = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
|
|
434
|
+
const nestedTools = parseNestedXmlTools(processed);
|
|
258
435
|
const openingTagRegex = /<tool_call>|<tool>/gi;
|
|
259
436
|
const closingTagRegex = /<\/tool_call>|<\/tool>/gi;
|
|
260
437
|
const openingMatches = processed.match(openingTagRegex) || [];
|
|
@@ -291,7 +468,7 @@ function preprocessText(text) {
|
|
|
291
468
|
}
|
|
292
469
|
}
|
|
293
470
|
}
|
|
294
|
-
return toolCallLines;
|
|
471
|
+
return { lines: toolCallLines, nestedTools };
|
|
295
472
|
}
|
|
296
473
|
var LLMResponseParser = class {
|
|
297
474
|
finishSpecSplitRe = /,(?=[^,\s]+:)/;
|
|
@@ -299,8 +476,8 @@ var LLMResponseParser = class {
|
|
|
299
476
|
if (typeof text !== "string") {
|
|
300
477
|
throw new TypeError("Command text must be a string.");
|
|
301
478
|
}
|
|
302
|
-
const lines = preprocessText(text);
|
|
303
|
-
const commands = [];
|
|
479
|
+
const { lines, nestedTools } = preprocessText(text);
|
|
480
|
+
const commands = [...nestedTools];
|
|
304
481
|
let finishAccumulator = null;
|
|
305
482
|
lines.forEach((line) => {
|
|
306
483
|
if (!line || line.startsWith("#")) return;
|
|
@@ -308,8 +485,8 @@ var LLMResponseParser = class {
|
|
|
308
485
|
if (parts.length === 0) return;
|
|
309
486
|
const cmd = parts[0];
|
|
310
487
|
switch (cmd) {
|
|
311
|
-
case "
|
|
312
|
-
this.
|
|
488
|
+
case "list_directory":
|
|
489
|
+
this.handleListDirectory(parts, line, commands);
|
|
313
490
|
break;
|
|
314
491
|
case "grep":
|
|
315
492
|
this.handleGrep(parts, line, commands);
|
|
@@ -327,8 +504,8 @@ var LLMResponseParser = class {
|
|
|
327
504
|
if (finishAccumulator) {
|
|
328
505
|
const map = finishAccumulator;
|
|
329
506
|
const entries = [...map.entries()];
|
|
330
|
-
const filesPayload = entries.map(([
|
|
331
|
-
path:
|
|
507
|
+
const filesPayload = entries.map(([path5, ranges]) => ({
|
|
508
|
+
path: path5,
|
|
332
509
|
lines: [...ranges].sort((a, b) => a[0] - b[0])
|
|
333
510
|
}));
|
|
334
511
|
commands.push({ name: "finish", arguments: { files: filesPayload } });
|
|
@@ -360,18 +537,17 @@ var LLMResponseParser = class {
|
|
|
360
537
|
skip(message) {
|
|
361
538
|
return { name: "_skip", arguments: { message } };
|
|
362
539
|
}
|
|
363
|
-
|
|
540
|
+
handleListDirectory(parts, rawLine, commands) {
|
|
364
541
|
if (parts.length < 2) {
|
|
365
542
|
commands.push(this.skip(
|
|
366
|
-
`[SKIPPED] Your command "${rawLine}" is missing a path. Correct format:
|
|
543
|
+
`[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: list_directory <path> [pattern]. Example: list_directory src/`
|
|
367
544
|
));
|
|
368
545
|
return;
|
|
369
546
|
}
|
|
370
|
-
const
|
|
547
|
+
const path5 = parts[1];
|
|
371
548
|
const pattern = parts[2]?.replace(/^"|"$/g, "") ?? null;
|
|
372
|
-
commands.push({ name: "
|
|
549
|
+
commands.push({ name: "list_directory", arguments: { path: path5, pattern } });
|
|
373
550
|
}
|
|
374
|
-
// no glob tool in MCP
|
|
375
551
|
handleGrep(parts, rawLine, commands) {
|
|
376
552
|
if (parts.length < 3) {
|
|
377
553
|
commands.push(this.skip(
|
|
@@ -442,8 +618,30 @@ var LLMResponseParser = class {
|
|
|
442
618
|
}
|
|
443
619
|
};
|
|
444
620
|
|
|
445
|
-
// tools/warp_grep/tools/
|
|
621
|
+
// tools/warp_grep/agent/tools/grep.ts
|
|
622
|
+
async function toolGrep(provider, args) {
|
|
623
|
+
const res = await provider.grep(args);
|
|
624
|
+
if (res.error) {
|
|
625
|
+
return { output: res.error };
|
|
626
|
+
}
|
|
627
|
+
if (!res.lines.length) {
|
|
628
|
+
return { output: "no matches" };
|
|
629
|
+
}
|
|
630
|
+
return { output: res.lines.join("\n") };
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// tools/warp_grep/agent/tools/read.ts
|
|
446
634
|
async function toolRead(provider, args) {
|
|
635
|
+
if (args.lines && args.lines.length > 0) {
|
|
636
|
+
const chunks = [];
|
|
637
|
+
for (const [start, end] of args.lines) {
|
|
638
|
+
const res2 = await provider.read({ path: args.path, start, end });
|
|
639
|
+
if (res2.error) return res2.error;
|
|
640
|
+
chunks.push(res2.lines.join("\n"));
|
|
641
|
+
}
|
|
642
|
+
if (chunks.every((c) => c === "")) return "(empty file)";
|
|
643
|
+
return chunks.join("\n...\n");
|
|
644
|
+
}
|
|
447
645
|
const res = await provider.read({ path: args.path, start: args.start, end: args.end });
|
|
448
646
|
if (res.error) {
|
|
449
647
|
return res.error;
|
|
@@ -452,16 +650,56 @@ async function toolRead(provider, args) {
|
|
|
452
650
|
return res.lines.join("\n");
|
|
453
651
|
}
|
|
454
652
|
|
|
455
|
-
// tools/warp_grep/tools/
|
|
456
|
-
async function
|
|
457
|
-
const list = await provider.
|
|
653
|
+
// tools/warp_grep/agent/tools/list_directory.ts
|
|
654
|
+
async function toolListDirectory(provider, args) {
|
|
655
|
+
const list = await provider.listDirectory({
|
|
458
656
|
path: args.path,
|
|
459
657
|
pattern: args.pattern ?? null,
|
|
460
|
-
maxResults: args.maxResults ??
|
|
461
|
-
maxDepth: args.maxDepth ??
|
|
658
|
+
maxResults: args.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES,
|
|
659
|
+
maxDepth: args.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH
|
|
462
660
|
});
|
|
463
661
|
if (!list.length) return "empty";
|
|
464
|
-
|
|
662
|
+
if (list.length >= AGENT_CONFIG.MAX_OUTPUT_LINES) {
|
|
663
|
+
return "query not specific enough, tool called tried to return too much context and failed";
|
|
664
|
+
}
|
|
665
|
+
return list.map((e) => {
|
|
666
|
+
const indent = " ".repeat(e.depth);
|
|
667
|
+
const name = e.type === "dir" ? `${e.name}/` : e.name;
|
|
668
|
+
return `${indent}${name}`;
|
|
669
|
+
}).join("\n");
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// tools/warp_grep/agent/tools/finish.ts
|
|
673
|
+
async function readFinishFiles(repoRoot, files, reader) {
|
|
674
|
+
const out = [];
|
|
675
|
+
for (const f of files) {
|
|
676
|
+
const ranges = mergeRanges(f.lines);
|
|
677
|
+
const chunks = [];
|
|
678
|
+
for (const [s, e] of ranges) {
|
|
679
|
+
const lines = await reader(f.path, s, e);
|
|
680
|
+
chunks.push(lines.join("\n"));
|
|
681
|
+
}
|
|
682
|
+
out.push({ path: f.path, ranges, content: chunks.join("\n") });
|
|
683
|
+
}
|
|
684
|
+
return out;
|
|
685
|
+
}
|
|
686
|
+
function mergeRanges(ranges) {
|
|
687
|
+
if (!ranges.length) return [];
|
|
688
|
+
const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
|
|
689
|
+
const merged = [];
|
|
690
|
+
let [cs, ce] = sorted[0];
|
|
691
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
692
|
+
const [s, e] = sorted[i];
|
|
693
|
+
if (s <= ce + 1) {
|
|
694
|
+
ce = Math.max(ce, e);
|
|
695
|
+
} else {
|
|
696
|
+
merged.push([cs, ce]);
|
|
697
|
+
cs = s;
|
|
698
|
+
ce = e;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
merged.push([cs, ce]);
|
|
702
|
+
return merged;
|
|
465
703
|
}
|
|
466
704
|
|
|
467
705
|
// tools/utils/resilience.ts
|
|
@@ -554,8 +792,8 @@ var ToolOutputFormatter = class {
|
|
|
554
792
|
switch (name) {
|
|
555
793
|
case "read":
|
|
556
794
|
return this.formatRead(safeArgs, payload, isError);
|
|
557
|
-
case "
|
|
558
|
-
return this.
|
|
795
|
+
case "list_directory":
|
|
796
|
+
return this.formatListDirectory(safeArgs, payload, isError);
|
|
559
797
|
case "grep":
|
|
560
798
|
return this.formatGrep(safeArgs, payload, isError);
|
|
561
799
|
default:
|
|
@@ -568,39 +806,56 @@ ${payload}
|
|
|
568
806
|
if (isError) {
|
|
569
807
|
return payload;
|
|
570
808
|
}
|
|
571
|
-
const
|
|
572
|
-
|
|
809
|
+
const path5 = this.asString(args.path) || "...";
|
|
810
|
+
const start = args.start;
|
|
811
|
+
const end = args.end;
|
|
812
|
+
const linesArray = args.lines;
|
|
813
|
+
const attributes = [`path="${path5}"`];
|
|
814
|
+
if (linesArray && linesArray.length > 0) {
|
|
815
|
+
const rangeStr = linesArray.map(([s, e]) => `${s}-${e}`).join(",");
|
|
816
|
+
attributes.push(`lines="${rangeStr}"`);
|
|
817
|
+
} else if (start !== void 0 && end !== void 0) {
|
|
818
|
+
attributes.push(`lines="${start}-${end}"`);
|
|
819
|
+
}
|
|
820
|
+
return `<read ${attributes.join(" ")}>
|
|
573
821
|
${payload}
|
|
574
|
-
</
|
|
822
|
+
</read>`;
|
|
575
823
|
}
|
|
576
|
-
|
|
577
|
-
const
|
|
824
|
+
formatListDirectory(args, payload, isError) {
|
|
825
|
+
const path5 = this.asString(args.path) || ".";
|
|
826
|
+
const pattern = this.asString(args.pattern);
|
|
827
|
+
const attributes = [`path="${path5}"`];
|
|
828
|
+
if (pattern) {
|
|
829
|
+
attributes.push(`pattern="${pattern}"`);
|
|
830
|
+
}
|
|
578
831
|
if (isError) {
|
|
579
|
-
|
|
580
|
-
${payload}
|
|
581
|
-
</analyse_results>`;
|
|
832
|
+
attributes.push('status="error"');
|
|
582
833
|
}
|
|
583
|
-
return `<
|
|
834
|
+
return `<list_directory ${attributes.join(" ")}>
|
|
584
835
|
${payload}
|
|
585
|
-
</
|
|
836
|
+
</list_directory>`;
|
|
586
837
|
}
|
|
587
838
|
formatGrep(args, payload, isError) {
|
|
588
839
|
const pattern = this.asString(args.pattern);
|
|
589
|
-
const
|
|
840
|
+
const subDir = this.asString(args.path);
|
|
841
|
+
const glob = this.asString(args.glob);
|
|
590
842
|
const attributes = [];
|
|
591
843
|
if (pattern !== void 0) {
|
|
592
844
|
attributes.push(`pattern="${pattern}"`);
|
|
593
845
|
}
|
|
594
|
-
if (
|
|
595
|
-
attributes.push(`
|
|
846
|
+
if (subDir !== void 0) {
|
|
847
|
+
attributes.push(`sub_dir="${subDir}"`);
|
|
848
|
+
}
|
|
849
|
+
if (glob !== void 0) {
|
|
850
|
+
attributes.push(`glob="${glob}"`);
|
|
596
851
|
}
|
|
597
852
|
if (isError) {
|
|
598
853
|
attributes.push('status="error"');
|
|
599
854
|
}
|
|
600
855
|
const attrText = attributes.length ? ` ${attributes.join(" ")}` : "";
|
|
601
|
-
return `<
|
|
856
|
+
return `<grep${attrText}>
|
|
602
857
|
${payload}
|
|
603
|
-
</
|
|
858
|
+
</grep>`;
|
|
604
859
|
}
|
|
605
860
|
asString(value) {
|
|
606
861
|
if (value === null || value === void 0) {
|
|
@@ -614,180 +869,100 @@ function formatAgentToolOutput(toolName, args, output, options = {}) {
|
|
|
614
869
|
return sharedFormatter.format(toolName, args, output, options);
|
|
615
870
|
}
|
|
616
871
|
|
|
617
|
-
// tools/warp_grep/agent/
|
|
618
|
-
var
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
this.seenLines.add(this.makeKey(path4, lineNumber));
|
|
626
|
-
}
|
|
627
|
-
makeKey(path4, lineNumber) {
|
|
628
|
-
return `${path4}:${lineNumber}`;
|
|
629
|
-
}
|
|
630
|
-
};
|
|
631
|
-
var MAX_GREP_OUTPUT_CHARS_PER_TURN = 6e4;
|
|
632
|
-
function extractMatchFields(payload) {
|
|
633
|
-
const text = payload.replace(/\r?\n$/, "");
|
|
634
|
-
if (!text || text.startsWith("[error]")) {
|
|
635
|
-
return null;
|
|
636
|
-
}
|
|
637
|
-
const firstSep = text.indexOf(":");
|
|
638
|
-
if (firstSep === -1) {
|
|
639
|
-
return null;
|
|
640
|
-
}
|
|
641
|
-
let filePath = text.slice(0, firstSep).trim();
|
|
642
|
-
if (!filePath) {
|
|
643
|
-
return null;
|
|
644
|
-
}
|
|
645
|
-
if (filePath.startsWith("./") || filePath.startsWith(".\\")) {
|
|
646
|
-
filePath = filePath.slice(2);
|
|
647
|
-
}
|
|
648
|
-
const remainder = text.slice(firstSep + 1);
|
|
649
|
-
const secondSep = remainder.indexOf(":");
|
|
650
|
-
if (secondSep === -1) {
|
|
651
|
-
return null;
|
|
652
|
-
}
|
|
653
|
-
const linePart = remainder.slice(0, secondSep);
|
|
654
|
-
const lineNumber = Number.parseInt(linePart, 10);
|
|
655
|
-
if (!Number.isInteger(lineNumber) || lineNumber <= 0) {
|
|
656
|
-
return null;
|
|
657
|
-
}
|
|
658
|
-
let contentSegment = remainder.slice(secondSep + 1);
|
|
659
|
-
const columnSep = contentSegment.indexOf(":");
|
|
660
|
-
if (columnSep !== -1 && /^\d+$/.test(contentSegment.slice(0, columnSep))) {
|
|
661
|
-
contentSegment = contentSegment.slice(columnSep + 1);
|
|
662
|
-
}
|
|
663
|
-
const content = contentSegment.trim();
|
|
664
|
-
if (!content) {
|
|
665
|
-
return null;
|
|
872
|
+
// tools/warp_grep/agent/helpers.ts
|
|
873
|
+
var import_path = __toESM(require("path"), 1);
|
|
874
|
+
var TRUNCATED_MARKER = "[truncated for context limit]";
|
|
875
|
+
function formatTurnMessage(turnsUsed, maxTurns) {
|
|
876
|
+
const turnsRemaining = maxTurns - turnsUsed;
|
|
877
|
+
if (turnsRemaining === 1) {
|
|
878
|
+
return `
|
|
879
|
+
You have used ${turnsUsed} turns, you only have 1 turn remaining. You have run out of turns to explore the code base and MUST call the finish tool now`;
|
|
666
880
|
}
|
|
667
|
-
return
|
|
881
|
+
return `
|
|
882
|
+
You have used ${turnsUsed} turn${turnsUsed === 1 ? "" : "s"} and have ${turnsRemaining} remaining`;
|
|
668
883
|
}
|
|
669
|
-
function
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
if (!fields) {
|
|
677
|
-
continue;
|
|
678
|
-
}
|
|
679
|
-
if (state.isNew(fields.path, fields.lineNumber)) {
|
|
680
|
-
matches.push(fields);
|
|
681
|
-
state.add(fields.path, fields.lineNumber);
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
return matches;
|
|
884
|
+
function calculateContextBudget(messages) {
|
|
885
|
+
const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0);
|
|
886
|
+
const maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS;
|
|
887
|
+
const percent = Math.round(totalChars / maxChars * 100);
|
|
888
|
+
const usedK = Math.round(totalChars / 1e3);
|
|
889
|
+
const maxK = Math.round(maxChars / 1e3);
|
|
890
|
+
return `<context_budget>${percent}% (${usedK}K/${maxK}K chars)</context_budget>`;
|
|
685
891
|
}
|
|
686
|
-
function
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
892
|
+
async function buildInitialState(repoRoot, query, provider) {
|
|
893
|
+
try {
|
|
894
|
+
const entries = await provider.listDirectory({
|
|
895
|
+
path: ".",
|
|
896
|
+
maxResults: AGENT_CONFIG.MAX_OUTPUT_LINES,
|
|
897
|
+
maxDepth: 2
|
|
898
|
+
});
|
|
899
|
+
const treeLines = entries.map((e) => {
|
|
900
|
+
const indent = " ".repeat(e.depth);
|
|
901
|
+
const name = e.type === "dir" ? `${e.name}/` : e.name;
|
|
902
|
+
return `${indent}${name}`;
|
|
903
|
+
});
|
|
904
|
+
const repoName = import_path.default.basename(repoRoot);
|
|
905
|
+
const treeOutput = treeLines.length > 0 ? `${repoName}/
|
|
906
|
+
${treeLines.join("\n")}` : `${repoName}/`;
|
|
907
|
+
return `<repo_structure>
|
|
908
|
+
${treeOutput}
|
|
909
|
+
</repo_structure>
|
|
910
|
+
|
|
911
|
+
<search_string>
|
|
912
|
+
${query}
|
|
913
|
+
</search_string>`;
|
|
914
|
+
} catch {
|
|
915
|
+
const repoName = import_path.default.basename(repoRoot);
|
|
916
|
+
return `<repo_structure>
|
|
917
|
+
${repoName}/
|
|
918
|
+
</repo_structure>
|
|
919
|
+
|
|
920
|
+
<search_string>
|
|
921
|
+
${query}
|
|
922
|
+
</search_string>`;
|
|
698
923
|
}
|
|
699
|
-
const core = payload.slice(0, Math.max(0, available - 1));
|
|
700
|
-
const trimmed = core.replace(/\n$/, "").replace(/\s+$/, "");
|
|
701
|
-
const snippet = trimmed ? `${trimmed}\u2026` : "\u2026";
|
|
702
|
-
return `${snippet}
|
|
703
|
-
${note}`;
|
|
704
924
|
}
|
|
705
|
-
function
|
|
706
|
-
|
|
707
|
-
|
|
925
|
+
function enforceContextLimit(messages, maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS) {
|
|
926
|
+
const getTotalChars = () => messages.reduce((sum, m) => sum + m.content.length, 0);
|
|
927
|
+
if (getTotalChars() <= maxChars) {
|
|
928
|
+
return messages;
|
|
708
929
|
}
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
930
|
+
const userIndices = [];
|
|
931
|
+
let firstUserSkipped = false;
|
|
932
|
+
for (let i = 0; i < messages.length; i++) {
|
|
933
|
+
if (messages[i].role === "user") {
|
|
934
|
+
if (!firstUserSkipped) {
|
|
935
|
+
firstUserSkipped = true;
|
|
936
|
+
continue;
|
|
937
|
+
}
|
|
938
|
+
userIndices.push(i);
|
|
713
939
|
}
|
|
714
|
-
matchesByFile.get(match.path).push(match);
|
|
715
940
|
}
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
if (index > 0) {
|
|
720
|
-
lines.push("");
|
|
721
|
-
}
|
|
722
|
-
lines.push(filePath);
|
|
723
|
-
const sortedMatches = matchesByFile.get(filePath).slice().sort((a, b) => a.lineNumber - b.lineNumber);
|
|
724
|
-
for (const match of sortedMatches) {
|
|
725
|
-
lines.push(`${match.lineNumber}:${match.content}`);
|
|
726
|
-
}
|
|
727
|
-
});
|
|
728
|
-
return truncateOutput(lines.join("\n"), maxChars);
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
// tools/warp_grep/tools/finish.ts
|
|
732
|
-
async function readFinishFiles(repoRoot, files, reader) {
|
|
733
|
-
const out = [];
|
|
734
|
-
for (const f of files) {
|
|
735
|
-
const ranges = mergeRanges(f.lines);
|
|
736
|
-
const chunks = [];
|
|
737
|
-
for (const [s, e] of ranges) {
|
|
738
|
-
const lines = await reader(f.path, s, e);
|
|
739
|
-
chunks.push(lines.join("\n"));
|
|
941
|
+
for (const idx of userIndices) {
|
|
942
|
+
if (getTotalChars() <= maxChars) {
|
|
943
|
+
break;
|
|
740
944
|
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
return out;
|
|
744
|
-
}
|
|
745
|
-
function mergeRanges(ranges) {
|
|
746
|
-
if (!ranges.length) return [];
|
|
747
|
-
const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
|
|
748
|
-
const merged = [];
|
|
749
|
-
let [cs, ce] = sorted[0];
|
|
750
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
751
|
-
const [s, e] = sorted[i];
|
|
752
|
-
if (s <= ce + 1) {
|
|
753
|
-
ce = Math.max(ce, e);
|
|
754
|
-
} else {
|
|
755
|
-
merged.push([cs, ce]);
|
|
756
|
-
cs = s;
|
|
757
|
-
ce = e;
|
|
945
|
+
if (messages[idx].content !== TRUNCATED_MARKER) {
|
|
946
|
+
messages[idx] = { role: "user", content: TRUNCATED_MARKER };
|
|
758
947
|
}
|
|
759
948
|
}
|
|
760
|
-
|
|
761
|
-
return merged;
|
|
949
|
+
return messages;
|
|
762
950
|
}
|
|
763
951
|
|
|
764
952
|
// tools/warp_grep/agent/runner.ts
|
|
765
|
-
var
|
|
953
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
766
954
|
var parser = new LLMResponseParser();
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
const files = entries.filter((e) => e.type === "file").map((f) => f.name).slice(0, 50);
|
|
772
|
-
const parts = [
|
|
773
|
-
`<repo_root>${repoRoot}</repo_root>`,
|
|
774
|
-
`<top_dirs>${dirs.join(", ")}</top_dirs>`,
|
|
775
|
-
`<top_files>${files.join(", ")}</top_files>`
|
|
776
|
-
];
|
|
777
|
-
return parts.join("\n");
|
|
778
|
-
} catch {
|
|
779
|
-
return `<repo_root>${repoRoot}</repo_root>`;
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
async function callModel(messages, model, apiKey) {
|
|
783
|
-
const api = "https://api.morphllm.com/v1/chat/completions";
|
|
955
|
+
var DEFAULT_API_URL = "https://api.morphllm.com";
|
|
956
|
+
async function callModel(messages, model, options = {}) {
|
|
957
|
+
const baseUrl = DEFAULT_API_URL;
|
|
958
|
+
const apiKey = options.morphApiKey || process.env.MORPH_API_KEY || "";
|
|
784
959
|
const fetchPromise = fetchWithRetry(
|
|
785
|
-
|
|
960
|
+
`${baseUrl}/v1/chat/completions`,
|
|
786
961
|
{
|
|
787
962
|
method: "POST",
|
|
788
963
|
headers: {
|
|
789
964
|
"Content-Type": "application/json",
|
|
790
|
-
Authorization: `Bearer ${apiKey
|
|
965
|
+
Authorization: `Bearer ${apiKey}`
|
|
791
966
|
},
|
|
792
967
|
body: JSON.stringify({
|
|
793
968
|
model,
|
|
@@ -796,10 +971,15 @@ async function callModel(messages, model, apiKey) {
|
|
|
796
971
|
messages
|
|
797
972
|
})
|
|
798
973
|
},
|
|
799
|
-
|
|
974
|
+
options.retryConfig
|
|
800
975
|
);
|
|
801
976
|
const resp = await withTimeout(fetchPromise, AGENT_CONFIG.TIMEOUT_MS, "morph-warp-grep request timed out");
|
|
802
977
|
if (!resp.ok) {
|
|
978
|
+
if (resp.status === 404) {
|
|
979
|
+
throw new Error(
|
|
980
|
+
"The endpoint you are trying to call is likely deprecated. Please update with: npm cache clean --force && npx -y @morphllm/morphmcp@latest or visit: https://morphllm.com/mcp"
|
|
981
|
+
);
|
|
982
|
+
}
|
|
803
983
|
const t = await resp.text();
|
|
804
984
|
throw new Error(`morph-warp-grep error ${resp.status}: ${t}`);
|
|
805
985
|
}
|
|
@@ -811,23 +991,24 @@ async function callModel(messages, model, apiKey) {
|
|
|
811
991
|
return content;
|
|
812
992
|
}
|
|
813
993
|
async function runWarpGrep(config) {
|
|
814
|
-
const repoRoot =
|
|
994
|
+
const repoRoot = import_path2.default.resolve(config.repoRoot || process.cwd());
|
|
815
995
|
const messages = [];
|
|
816
|
-
|
|
817
|
-
messages.push(systemMessage);
|
|
818
|
-
const queryContent = `<query>${config.query}</query>`;
|
|
819
|
-
messages.push({ role: "user", content: queryContent });
|
|
996
|
+
messages.push({ role: "system", content: getSystemPrompt() });
|
|
820
997
|
const initialState = await buildInitialState(repoRoot, config.query, config.provider);
|
|
821
998
|
messages.push({ role: "user", content: initialState });
|
|
822
|
-
const
|
|
999
|
+
const maxTurns = AGENT_CONFIG.MAX_TURNS;
|
|
823
1000
|
const model = config.model || DEFAULT_MODEL;
|
|
824
1001
|
const provider = config.provider;
|
|
825
1002
|
const errors = [];
|
|
826
|
-
const grepState = new GrepState();
|
|
827
1003
|
let finishMeta;
|
|
828
1004
|
let terminationReason = "terminated";
|
|
829
|
-
for (let
|
|
830
|
-
|
|
1005
|
+
for (let turn = 1; turn <= maxTurns; turn += 1) {
|
|
1006
|
+
enforceContextLimit(messages);
|
|
1007
|
+
const assistantContent = await callModel(messages, model, {
|
|
1008
|
+
morphApiKey: config.morphApiKey,
|
|
1009
|
+
morphApiUrl: config.morphApiUrl,
|
|
1010
|
+
retryConfig: config.retryConfig
|
|
1011
|
+
}).catch((e) => {
|
|
831
1012
|
errors.push({ message: e instanceof Error ? e.message : String(e) });
|
|
832
1013
|
return "";
|
|
833
1014
|
});
|
|
@@ -835,13 +1016,13 @@ async function runWarpGrep(config) {
|
|
|
835
1016
|
messages.push({ role: "assistant", content: assistantContent });
|
|
836
1017
|
const toolCalls = parser.parse(assistantContent);
|
|
837
1018
|
if (toolCalls.length === 0) {
|
|
838
|
-
errors.push({ message: "No tool calls produced by the model." });
|
|
1019
|
+
errors.push({ message: "No tool calls produced by the model. Your MCP is likely out of date! Update it by running: npm cache clean --force && npx -y @morphllm/morphmcp@latest" });
|
|
839
1020
|
terminationReason = "terminated";
|
|
840
1021
|
break;
|
|
841
1022
|
}
|
|
842
1023
|
const finishCalls = toolCalls.filter((c) => c.name === "finish");
|
|
843
1024
|
const grepCalls = toolCalls.filter((c) => c.name === "grep");
|
|
844
|
-
const
|
|
1025
|
+
const listDirCalls = toolCalls.filter((c) => c.name === "list_directory");
|
|
845
1026
|
const readCalls = toolCalls.filter((c) => c.name === "read");
|
|
846
1027
|
const skipCalls = toolCalls.filter((c) => c.name === "_skip");
|
|
847
1028
|
const formatted = [];
|
|
@@ -849,69 +1030,42 @@ async function runWarpGrep(config) {
|
|
|
849
1030
|
const msg = c.arguments?.message || "Command skipped due to parsing error";
|
|
850
1031
|
formatted.push(msg);
|
|
851
1032
|
}
|
|
852
|
-
const
|
|
853
|
-
for (const c of
|
|
1033
|
+
const allPromises = [];
|
|
1034
|
+
for (const c of grepCalls) {
|
|
1035
|
+
const args = c.arguments ?? {};
|
|
1036
|
+
allPromises.push(
|
|
1037
|
+
toolGrep(provider, args).then(
|
|
1038
|
+
({ output }) => formatAgentToolOutput("grep", args, output, { isError: false }),
|
|
1039
|
+
(err) => formatAgentToolOutput("grep", args, String(err), { isError: true })
|
|
1040
|
+
)
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
for (const c of listDirCalls) {
|
|
854
1044
|
const args = c.arguments ?? {};
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
(p) => formatAgentToolOutput("
|
|
858
|
-
(err) => formatAgentToolOutput("
|
|
1045
|
+
allPromises.push(
|
|
1046
|
+
toolListDirectory(provider, args).then(
|
|
1047
|
+
(p) => formatAgentToolOutput("list_directory", args, p, { isError: false }),
|
|
1048
|
+
(err) => formatAgentToolOutput("list_directory", args, String(err), { isError: true })
|
|
859
1049
|
)
|
|
860
1050
|
);
|
|
861
1051
|
}
|
|
862
1052
|
for (const c of readCalls) {
|
|
863
1053
|
const args = c.arguments ?? {};
|
|
864
|
-
|
|
1054
|
+
allPromises.push(
|
|
865
1055
|
toolRead(provider, args).then(
|
|
866
1056
|
(p) => formatAgentToolOutput("read", args, p, { isError: false }),
|
|
867
1057
|
(err) => formatAgentToolOutput("read", args, String(err), { isError: true })
|
|
868
1058
|
)
|
|
869
1059
|
);
|
|
870
1060
|
}
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
const args = c.arguments ?? {};
|
|
875
|
-
try {
|
|
876
|
-
const grepRes = await provider.grep({ pattern: args.pattern, path: args.path });
|
|
877
|
-
if (grepRes.error) {
|
|
878
|
-
errors.push({ message: grepRes.error });
|
|
879
|
-
terminationReason = "terminated";
|
|
880
|
-
return {
|
|
881
|
-
terminationReason: "terminated",
|
|
882
|
-
messages,
|
|
883
|
-
errors
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
const rawOutput = Array.isArray(grepRes.lines) ? grepRes.lines.join("\n") : "";
|
|
887
|
-
const newMatches = parseAndFilterGrepOutput(rawOutput, grepState);
|
|
888
|
-
let formattedPayload = formatTurnGrepOutput(newMatches);
|
|
889
|
-
if (formattedPayload === "No new matches found.") {
|
|
890
|
-
formattedPayload = "no new matches";
|
|
891
|
-
}
|
|
892
|
-
formatted.push(formatAgentToolOutput("grep", args, formattedPayload, { isError: false }));
|
|
893
|
-
} catch (err) {
|
|
894
|
-
formatted.push(formatAgentToolOutput("grep", args, String(err), { isError: true }));
|
|
895
|
-
}
|
|
1061
|
+
const allResults = await Promise.all(allPromises);
|
|
1062
|
+
for (const result of allResults) {
|
|
1063
|
+
formatted.push(result);
|
|
896
1064
|
}
|
|
897
1065
|
if (formatted.length > 0) {
|
|
898
|
-
const
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
if (turnsRemaining === 0) {
|
|
902
|
-
turnMessage = `
|
|
903
|
-
|
|
904
|
-
[Turn ${turnsUsed}/4] This is your LAST turn. You MUST call the finish tool now.`;
|
|
905
|
-
} else if (turnsRemaining === 1) {
|
|
906
|
-
turnMessage = `
|
|
907
|
-
|
|
908
|
-
[Turn ${turnsUsed}/4] You have 1 turn remaining. Next turn you MUST call the finish tool.`;
|
|
909
|
-
} else {
|
|
910
|
-
turnMessage = `
|
|
911
|
-
|
|
912
|
-
[Turn ${turnsUsed}/4] You have ${turnsRemaining} turns remaining.`;
|
|
913
|
-
}
|
|
914
|
-
messages.push({ role: "user", content: formatted.join("\n") + turnMessage });
|
|
1066
|
+
const turnMessage = formatTurnMessage(turn, maxTurns);
|
|
1067
|
+
const contextBudget = calculateContextBudget(messages);
|
|
1068
|
+
messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
|
|
915
1069
|
}
|
|
916
1070
|
if (finishCalls.length) {
|
|
917
1071
|
const fc = finishCalls[0];
|
|
@@ -961,7 +1115,7 @@ async function runWarpGrep(config) {
|
|
|
961
1115
|
|
|
962
1116
|
// tools/warp_grep/providers/local.ts
|
|
963
1117
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
964
|
-
var
|
|
1118
|
+
var import_path4 = __toESM(require("path"), 1);
|
|
965
1119
|
|
|
966
1120
|
// tools/warp_grep/utils/ripgrep.ts
|
|
967
1121
|
var import_child_process = require("child_process");
|
|
@@ -1026,21 +1180,21 @@ async function runRipgrep(args, opts) {
|
|
|
1026
1180
|
|
|
1027
1181
|
// tools/warp_grep/utils/paths.ts
|
|
1028
1182
|
var import_fs = __toESM(require("fs"), 1);
|
|
1029
|
-
var
|
|
1183
|
+
var import_path3 = __toESM(require("path"), 1);
|
|
1030
1184
|
function resolveUnderRepo(repoRoot, targetPath) {
|
|
1031
|
-
const absRoot =
|
|
1032
|
-
const resolved =
|
|
1185
|
+
const absRoot = import_path3.default.resolve(repoRoot);
|
|
1186
|
+
const resolved = import_path3.default.resolve(absRoot, targetPath);
|
|
1033
1187
|
ensureWithinRepo(absRoot, resolved);
|
|
1034
1188
|
return resolved;
|
|
1035
1189
|
}
|
|
1036
1190
|
function ensureWithinRepo(repoRoot, absTarget) {
|
|
1037
|
-
const rel =
|
|
1038
|
-
if (rel.startsWith("..") ||
|
|
1191
|
+
const rel = import_path3.default.relative(import_path3.default.resolve(repoRoot), import_path3.default.resolve(absTarget));
|
|
1192
|
+
if (rel.startsWith("..") || import_path3.default.isAbsolute(rel)) {
|
|
1039
1193
|
throw new Error(`Path outside repository root: ${absTarget}`);
|
|
1040
1194
|
}
|
|
1041
1195
|
}
|
|
1042
1196
|
function toRepoRelative(repoRoot, absPath) {
|
|
1043
|
-
return
|
|
1197
|
+
return import_path3.default.relative(import_path3.default.resolve(repoRoot), import_path3.default.resolve(absPath));
|
|
1044
1198
|
}
|
|
1045
1199
|
function isSymlink(p) {
|
|
1046
1200
|
try {
|
|
@@ -1083,10 +1237,18 @@ var LocalRipgrepProvider = class {
|
|
|
1083
1237
|
this.excludes = excludes;
|
|
1084
1238
|
}
|
|
1085
1239
|
async grep(params) {
|
|
1086
|
-
|
|
1240
|
+
let abs;
|
|
1241
|
+
try {
|
|
1242
|
+
abs = resolveUnderRepo(this.repoRoot, params.path);
|
|
1243
|
+
} catch (err) {
|
|
1244
|
+
return {
|
|
1245
|
+
lines: [],
|
|
1246
|
+
error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1087
1249
|
const stat = await import_promises2.default.stat(abs).catch(() => null);
|
|
1088
1250
|
if (!stat) return { lines: [] };
|
|
1089
|
-
const targetArg = abs ===
|
|
1251
|
+
const targetArg = abs === import_path4.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
|
|
1090
1252
|
const args = [
|
|
1091
1253
|
"--no-config",
|
|
1092
1254
|
"--no-heading",
|
|
@@ -1095,6 +1257,9 @@ var LocalRipgrepProvider = class {
|
|
|
1095
1257
|
"--color=never",
|
|
1096
1258
|
"--trim",
|
|
1097
1259
|
"--max-columns=400",
|
|
1260
|
+
"-C",
|
|
1261
|
+
"1",
|
|
1262
|
+
...params.glob ? ["--glob", params.glob] : [],
|
|
1098
1263
|
...this.excludes.flatMap((e) => ["-g", `!${e}`]),
|
|
1099
1264
|
params.pattern,
|
|
1100
1265
|
targetArg || "."
|
|
@@ -1119,29 +1284,24 @@ Details: ${res.stderr}` : ""}`
|
|
|
1119
1284
|
};
|
|
1120
1285
|
}
|
|
1121
1286
|
const lines = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
const args = [
|
|
1128
|
-
"--no-config",
|
|
1129
|
-
"--files",
|
|
1130
|
-
"-g",
|
|
1131
|
-
params.pattern,
|
|
1132
|
-
...this.excludes.flatMap((e) => ["-g", `!${e}`]),
|
|
1133
|
-
targetArg || "."
|
|
1134
|
-
];
|
|
1135
|
-
const res = await runRipgrep(args, { cwd: this.repoRoot });
|
|
1136
|
-
if (res.exitCode === -1) {
|
|
1137
|
-
console.warn(`[warp_grep] ripgrep not available for glob: ${res.stderr || "execution failed"}`);
|
|
1138
|
-
return { files: [] };
|
|
1287
|
+
if (lines.length > AGENT_CONFIG.MAX_OUTPUT_LINES) {
|
|
1288
|
+
return {
|
|
1289
|
+
lines: [],
|
|
1290
|
+
error: "query not specific enough, tool tried to return too much context and failed"
|
|
1291
|
+
};
|
|
1139
1292
|
}
|
|
1140
|
-
|
|
1141
|
-
return { files };
|
|
1293
|
+
return { lines };
|
|
1142
1294
|
}
|
|
1143
1295
|
async read(params) {
|
|
1144
|
-
|
|
1296
|
+
let abs;
|
|
1297
|
+
try {
|
|
1298
|
+
abs = resolveUnderRepo(this.repoRoot, params.path);
|
|
1299
|
+
} catch (err) {
|
|
1300
|
+
return {
|
|
1301
|
+
lines: [],
|
|
1302
|
+
error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1145
1305
|
const stat = await import_promises2.default.stat(abs).catch(() => null);
|
|
1146
1306
|
if (!stat || !stat.isFile()) {
|
|
1147
1307
|
return {
|
|
@@ -1161,7 +1321,15 @@ Details: ${res.stderr}` : ""}`
|
|
|
1161
1321
|
error: `[UNREADABLE FILE] You tried to read "${params.path}" but this file is either too large or not a text file, so it cannot be read. Try a different file.`
|
|
1162
1322
|
};
|
|
1163
1323
|
}
|
|
1164
|
-
|
|
1324
|
+
let lines;
|
|
1325
|
+
try {
|
|
1326
|
+
lines = await readAllLines(abs);
|
|
1327
|
+
} catch (err) {
|
|
1328
|
+
return {
|
|
1329
|
+
lines: [],
|
|
1330
|
+
error: `[READ ERROR] Failed to read "${params.path}": ${err instanceof Error ? err.message : String(err)}`
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1165
1333
|
const total = lines.length;
|
|
1166
1334
|
let s = params.start ?? 1;
|
|
1167
1335
|
let e = Math.min(params.end ?? total, total);
|
|
@@ -1174,30 +1342,46 @@ Details: ${res.stderr}` : ""}`
|
|
|
1174
1342
|
const content = lines[i - 1] ?? "";
|
|
1175
1343
|
out.push(`${i}|${content}`);
|
|
1176
1344
|
}
|
|
1345
|
+
if (out.length > AGENT_CONFIG.MAX_READ_LINES) {
|
|
1346
|
+
const truncated = out.slice(0, AGENT_CONFIG.MAX_READ_LINES);
|
|
1347
|
+
truncated.push(`... [truncated: showing ${AGENT_CONFIG.MAX_READ_LINES} of ${out.length} lines]`);
|
|
1348
|
+
return { lines: truncated };
|
|
1349
|
+
}
|
|
1177
1350
|
return { lines: out };
|
|
1178
1351
|
}
|
|
1179
|
-
async
|
|
1180
|
-
|
|
1352
|
+
async listDirectory(params) {
|
|
1353
|
+
let abs;
|
|
1354
|
+
try {
|
|
1355
|
+
abs = resolveUnderRepo(this.repoRoot, params.path);
|
|
1356
|
+
} catch {
|
|
1357
|
+
return [];
|
|
1358
|
+
}
|
|
1181
1359
|
const stat = await import_promises2.default.stat(abs).catch(() => null);
|
|
1182
1360
|
if (!stat || !stat.isDirectory()) {
|
|
1183
1361
|
return [];
|
|
1184
1362
|
}
|
|
1185
|
-
const maxResults = params.maxResults ??
|
|
1186
|
-
const maxDepth = params.maxDepth ??
|
|
1363
|
+
const maxResults = params.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES;
|
|
1364
|
+
const maxDepth = params.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
|
|
1187
1365
|
const regex = params.pattern ? new RegExp(params.pattern) : null;
|
|
1188
1366
|
const results = [];
|
|
1367
|
+
let timedOut = false;
|
|
1368
|
+
const startTime = Date.now();
|
|
1189
1369
|
async function walk(dir, depth) {
|
|
1370
|
+
if (Date.now() - startTime > AGENT_CONFIG.LIST_TIMEOUT_MS) {
|
|
1371
|
+
timedOut = true;
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1190
1374
|
if (depth > maxDepth || results.length >= maxResults) return;
|
|
1191
1375
|
const entries = await import_promises2.default.readdir(dir, { withFileTypes: true });
|
|
1192
1376
|
for (const entry of entries) {
|
|
1193
|
-
|
|
1377
|
+
if (timedOut || results.length >= maxResults) break;
|
|
1378
|
+
const full = import_path4.default.join(dir, entry.name);
|
|
1194
1379
|
const rel = toRepoRelative(abs, full).replace(/^[.][/\\]?/, "");
|
|
1195
|
-
if (DEFAULT_EXCLUDES.some((ex) => rel.split(
|
|
1380
|
+
if (DEFAULT_EXCLUDES.some((ex) => rel.split(import_path4.default.sep).includes(ex))) continue;
|
|
1196
1381
|
if (regex && !regex.test(entry.name)) continue;
|
|
1197
|
-
if (results.length >= maxResults) break;
|
|
1198
1382
|
results.push({
|
|
1199
1383
|
name: entry.name,
|
|
1200
|
-
path: toRepoRelative(
|
|
1384
|
+
path: toRepoRelative(import_path4.default.resolve(""), full),
|
|
1201
1385
|
// relative display
|
|
1202
1386
|
type: entry.isDirectory() ? "dir" : "file",
|
|
1203
1387
|
depth
|
|
@@ -1212,10 +1396,121 @@ Details: ${res.stderr}` : ""}`
|
|
|
1212
1396
|
}
|
|
1213
1397
|
};
|
|
1214
1398
|
|
|
1215
|
-
// tools/warp_grep/
|
|
1216
|
-
var
|
|
1399
|
+
// tools/warp_grep/providers/remote.ts
|
|
1400
|
+
var RemoteCommandsProvider = class {
|
|
1401
|
+
constructor(repoRoot, commands) {
|
|
1402
|
+
this.repoRoot = repoRoot;
|
|
1403
|
+
this.commands = commands;
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Run grep command and parse ripgrep output
|
|
1407
|
+
*/
|
|
1408
|
+
async grep(params) {
|
|
1409
|
+
try {
|
|
1410
|
+
const stdout = await this.commands.grep(params.pattern, params.path, params.glob);
|
|
1411
|
+
const lines = (stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
|
|
1412
|
+
if (lines.length > AGENT_CONFIG.MAX_OUTPUT_LINES) {
|
|
1413
|
+
return {
|
|
1414
|
+
lines: [],
|
|
1415
|
+
error: "Query not specific enough - too many results returned. Try a more specific pattern."
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
return { lines };
|
|
1419
|
+
} catch (error) {
|
|
1420
|
+
return {
|
|
1421
|
+
lines: [],
|
|
1422
|
+
error: `[GREP ERROR] ${error instanceof Error ? error.message : String(error)}`
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Read file and add line numbers
|
|
1428
|
+
*/
|
|
1429
|
+
async read(params) {
|
|
1430
|
+
const start = params.start ?? 1;
|
|
1431
|
+
const end = params.end ?? 1e6;
|
|
1432
|
+
try {
|
|
1433
|
+
const stdout = await this.commands.read(params.path, start, end);
|
|
1434
|
+
const contentLines = (stdout || "").split("\n");
|
|
1435
|
+
if (contentLines.length > 0 && contentLines[contentLines.length - 1] === "") {
|
|
1436
|
+
contentLines.pop();
|
|
1437
|
+
}
|
|
1438
|
+
const lines = contentLines.map((content, idx) => `${start + idx}|${content}`);
|
|
1439
|
+
if (lines.length > AGENT_CONFIG.MAX_READ_LINES) {
|
|
1440
|
+
const truncated = lines.slice(0, AGENT_CONFIG.MAX_READ_LINES);
|
|
1441
|
+
truncated.push(`... [truncated: showing ${AGENT_CONFIG.MAX_READ_LINES} of ${lines.length} lines]`);
|
|
1442
|
+
return { lines: truncated };
|
|
1443
|
+
}
|
|
1444
|
+
return { lines };
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
return {
|
|
1447
|
+
lines: [],
|
|
1448
|
+
error: `[READ ERROR] ${error instanceof Error ? error.message : String(error)}`
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* List directory and parse find output
|
|
1454
|
+
*/
|
|
1455
|
+
async listDirectory(params) {
|
|
1456
|
+
const maxDepth = params.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
|
|
1457
|
+
const maxResults = params.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES;
|
|
1458
|
+
try {
|
|
1459
|
+
const stdout = await this.commands.listDir(params.path, maxDepth);
|
|
1460
|
+
const paths = (stdout || "").trim().split(/\r?\n/).filter((p) => p.length > 0);
|
|
1461
|
+
const regex = params.pattern ? new RegExp(params.pattern) : null;
|
|
1462
|
+
const entries = [];
|
|
1463
|
+
for (const fullPath of paths) {
|
|
1464
|
+
if (fullPath === params.path || fullPath === this.repoRoot) continue;
|
|
1465
|
+
const name = fullPath.split("/").pop() || "";
|
|
1466
|
+
if (regex && !regex.test(name)) continue;
|
|
1467
|
+
let relativePath = fullPath;
|
|
1468
|
+
if (fullPath.startsWith(this.repoRoot)) {
|
|
1469
|
+
relativePath = fullPath.slice(this.repoRoot.length).replace(/^\//, "");
|
|
1470
|
+
}
|
|
1471
|
+
const depth = relativePath.split("/").filter(Boolean).length - 1;
|
|
1472
|
+
const hasExtension = name.includes(".") && !name.startsWith(".");
|
|
1473
|
+
const type = hasExtension ? "file" : "dir";
|
|
1474
|
+
entries.push({
|
|
1475
|
+
name,
|
|
1476
|
+
path: relativePath,
|
|
1477
|
+
type,
|
|
1478
|
+
depth: Math.max(0, depth)
|
|
1479
|
+
});
|
|
1480
|
+
if (entries.length >= maxResults) break;
|
|
1481
|
+
}
|
|
1482
|
+
return entries;
|
|
1483
|
+
} catch (error) {
|
|
1484
|
+
return [];
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1217
1488
|
|
|
1218
|
-
// tools/warp_grep/
|
|
1489
|
+
// tools/warp_grep/client.ts
|
|
1490
|
+
async function executeToolCall(input, config) {
|
|
1491
|
+
const parsed = typeof input === "string" ? JSON.parse(input) : input;
|
|
1492
|
+
const provider = config.remoteCommands ? new RemoteCommandsProvider(config.repoRoot, config.remoteCommands) : config.provider ?? new LocalRipgrepProvider(config.repoRoot, config.excludes);
|
|
1493
|
+
const result = await runWarpGrep({
|
|
1494
|
+
query: parsed.query,
|
|
1495
|
+
repoRoot: config.repoRoot,
|
|
1496
|
+
provider,
|
|
1497
|
+
excludes: config.excludes,
|
|
1498
|
+
includes: config.includes,
|
|
1499
|
+
debug: config.debug ?? false,
|
|
1500
|
+
morphApiKey: config.morphApiKey,
|
|
1501
|
+
morphApiUrl: config.morphApiUrl,
|
|
1502
|
+
retryConfig: config.retryConfig
|
|
1503
|
+
});
|
|
1504
|
+
const finish = result.finish;
|
|
1505
|
+
if (result.terminationReason !== "completed" || !finish?.metadata) {
|
|
1506
|
+
return { success: false, error: "Search did not complete" };
|
|
1507
|
+
}
|
|
1508
|
+
const contexts = (finish.resolved ?? []).map((r) => ({
|
|
1509
|
+
file: r.path,
|
|
1510
|
+
content: r.content
|
|
1511
|
+
}));
|
|
1512
|
+
return { success: true, contexts, summary: finish.payload };
|
|
1513
|
+
}
|
|
1219
1514
|
function formatResult(result) {
|
|
1220
1515
|
if (!result.success) {
|
|
1221
1516
|
return `Search failed: ${result.error}`;
|
|
@@ -1239,61 +1534,37 @@ function formatResult(result) {
|
|
|
1239
1534
|
return lines.join("\n");
|
|
1240
1535
|
}
|
|
1241
1536
|
|
|
1537
|
+
// tools/warp_grep/prompts.ts
|
|
1538
|
+
var WARP_GREP_DESCRIPTION = 'IMPORTANT: If you need to explore the codebase to gather context, and the task does not involve a single file or function which is provided by name, you should ALWAYS use the warpgrep codebase search tool first instead of running search commands. When the task requires exploration beyond a single known file, invoke warpgrep first with a natural-language query describing the target functionality, bug, or architectural concern. Warp Grep is a fast and accurate tool that can search for all relevant context in a codebase. Keep queries action-oriented (e.g., "Find where billing invoices are generated and emailed"), and after reviewing the summarized results, you may verify important findings with other search tools or direct file reads to ensure completeness. Warp Grep can be used for query types like: find function responsible for <small feature>; find code that does <description>; find code path for <big feature>; Where does <minimal error message> come from?; or any query of that type. When a task requires exploration beyond a single known file, ALWAYS default to warpgrep codebase search before other search mechanisms.';
|
|
1539
|
+
|
|
1242
1540
|
// tools/warp_grep/vercel.ts
|
|
1243
|
-
var warpGrepSchema = import_zod.z.object({
|
|
1244
|
-
query: import_zod.z.string().describe("Free-form repository question")
|
|
1245
|
-
});
|
|
1246
1541
|
async function execute(input, config) {
|
|
1247
|
-
|
|
1248
|
-
const result = await runWarpGrep({
|
|
1249
|
-
query: input.query,
|
|
1250
|
-
repoRoot: config.repoRoot,
|
|
1251
|
-
provider,
|
|
1252
|
-
excludes: config.excludes,
|
|
1253
|
-
includes: config.includes,
|
|
1254
|
-
debug: config.debug ?? false,
|
|
1255
|
-
apiKey: config.apiKey
|
|
1256
|
-
});
|
|
1257
|
-
const finish = result.finish;
|
|
1258
|
-
if (result.terminationReason !== "completed" || !finish?.metadata) {
|
|
1259
|
-
return { success: false, error: "Search did not complete" };
|
|
1260
|
-
}
|
|
1261
|
-
const contexts = (finish.resolved ?? []).map((r) => ({
|
|
1262
|
-
file: r.path,
|
|
1263
|
-
content: r.content
|
|
1264
|
-
}));
|
|
1265
|
-
return { success: true, contexts, summary: finish.payload };
|
|
1542
|
+
return executeToolCall(input, config);
|
|
1266
1543
|
}
|
|
1267
|
-
function
|
|
1544
|
+
function createWarpGrepTool(config) {
|
|
1545
|
+
const schema = import_zod.z.object({
|
|
1546
|
+
query: import_zod.z.string().describe("Free-form repository question")
|
|
1547
|
+
});
|
|
1268
1548
|
return (0, import_ai.tool)({
|
|
1269
1549
|
description: config.description ?? WARP_GREP_DESCRIPTION,
|
|
1270
|
-
inputSchema:
|
|
1550
|
+
inputSchema: schema,
|
|
1271
1551
|
execute: async (params) => {
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
repoRoot: config.repoRoot,
|
|
1276
|
-
provider,
|
|
1277
|
-
excludes: config.excludes,
|
|
1278
|
-
includes: config.includes,
|
|
1279
|
-
debug: config.debug ?? false,
|
|
1280
|
-
apiKey: config.apiKey
|
|
1281
|
-
});
|
|
1282
|
-
const finish = result.finish;
|
|
1283
|
-
if (result.terminationReason !== "completed" || !finish?.metadata) {
|
|
1284
|
-
return { success: false, error: "Search did not complete" };
|
|
1552
|
+
const result = await executeToolCall(params, config);
|
|
1553
|
+
if (!result.success) {
|
|
1554
|
+
throw new Error(`Failed to search codebase: ${result.error}`);
|
|
1285
1555
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1556
|
+
return {
|
|
1557
|
+
success: true,
|
|
1558
|
+
contexts: result.contexts,
|
|
1559
|
+
summary: result.summary
|
|
1560
|
+
};
|
|
1291
1561
|
}
|
|
1292
1562
|
});
|
|
1293
1563
|
}
|
|
1564
|
+
var vercel_default = createWarpGrepTool;
|
|
1294
1565
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1295
1566
|
0 && (module.exports = {
|
|
1296
|
-
|
|
1567
|
+
createWarpGrepTool,
|
|
1297
1568
|
execute,
|
|
1298
1569
|
formatResult,
|
|
1299
1570
|
getSystemPrompt
|