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