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