@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
|
@@ -36,9 +36,13 @@ module.exports = __toCommonJS(runner_exports);
|
|
|
36
36
|
|
|
37
37
|
// tools/warp_grep/agent/config.ts
|
|
38
38
|
var AGENT_CONFIG = {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
MAX_TURNS: 4,
|
|
40
|
+
TIMEOUT_MS: 3e4,
|
|
41
|
+
MAX_CONTEXT_CHARS: 54e4,
|
|
42
|
+
MAX_OUTPUT_LINES: 200,
|
|
43
|
+
MAX_READ_LINES: 800,
|
|
44
|
+
MAX_LIST_DEPTH: 3,
|
|
45
|
+
LIST_TIMEOUT_MS: 2e3
|
|
42
46
|
};
|
|
43
47
|
var BUILTIN_EXCLUDES = [
|
|
44
48
|
// Version control
|
|
@@ -120,113 +124,191 @@ var BUILTIN_EXCLUDES = [
|
|
|
120
124
|
".*"
|
|
121
125
|
];
|
|
122
126
|
var DEFAULT_EXCLUDES = (process.env.MORPH_WARP_GREP_EXCLUDE || "").split(",").map((s) => s.trim()).filter(Boolean).concat(BUILTIN_EXCLUDES);
|
|
123
|
-
var DEFAULT_MODEL = "morph-warp-grep";
|
|
127
|
+
var DEFAULT_MODEL = "morph-warp-grep-v1";
|
|
124
128
|
|
|
125
129
|
// tools/warp_grep/agent/prompt.ts
|
|
126
|
-
var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given
|
|
130
|
+
var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given search_string.
|
|
127
131
|
|
|
128
|
-
|
|
132
|
+
### workflow
|
|
129
133
|
You have exactly 4 turns. The 4th turn MUST be a \`finish\` call. Each turn allows up to 8 parallel tool calls.
|
|
130
134
|
|
|
131
|
-
- Turn 1: Map the territory OR dive deep (based on
|
|
135
|
+
- Turn 1: Map the territory OR dive deep (based on search_string specificity)
|
|
132
136
|
- Turn 2-3: Refine based on findings
|
|
133
137
|
- Turn 4: MUST call \`finish\` with all relevant code locations
|
|
134
138
|
- You MAY call \`finish\` early if confident\u2014but never before at least 1 search turn.
|
|
139
|
+
- The user strongly prefers if you can call the finish tool early, but you must be correct
|
|
135
140
|
|
|
136
|
-
Remember, if the task feels easy to you, it is strongly desirable to call
|
|
137
|
-
</workflow>
|
|
141
|
+
Remember, if the task feels easy to you, it is strongly desirable to call 'finish' early using fewer turns, but quality over speed
|
|
138
142
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
143
|
+
### tools
|
|
144
|
+
Tool calls use nested XML elements:
|
|
145
|
+
\`\`\`xml
|
|
146
|
+
<tool_name>
|
|
147
|
+
<parameter>value</parameter>
|
|
148
|
+
</tool_name>
|
|
149
|
+
\`\`\`
|
|
150
|
+
|
|
151
|
+
### \`list_directory\`
|
|
152
|
+
Directory tree view. Shows structure of a path, optionally filtered by regex pattern.
|
|
153
|
+
|
|
154
|
+
Elements:
|
|
155
|
+
- \`<path>\` (required): Directory path to list (use \`.\` for repo root)
|
|
156
|
+
- \`<pattern>\` (optional): Regex to filter results
|
|
144
157
|
|
|
145
158
|
Examples:
|
|
146
159
|
\`\`\`
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
160
|
+
<list_directory>
|
|
161
|
+
<path>src/services</path>
|
|
162
|
+
</list_directory>
|
|
163
|
+
|
|
164
|
+
<list_directory>
|
|
165
|
+
<path>lib/utils</path>
|
|
166
|
+
<pattern>.*\\.(ts|js)$</pattern>
|
|
167
|
+
</list_directory>
|
|
151
168
|
\`\`\`
|
|
152
169
|
|
|
153
|
-
### \`read
|
|
154
|
-
Read file contents.
|
|
170
|
+
### \`read\`
|
|
171
|
+
Read file contents. Supports multiple line ranges.
|
|
155
172
|
- Returns numbered lines for easy reference
|
|
156
|
-
-
|
|
173
|
+
- ALWAYS include import statements (usually lines 1-20). Better to over-include than miss context.
|
|
174
|
+
|
|
175
|
+
Elements:
|
|
176
|
+
- \`<path>\` (required): File path to read
|
|
177
|
+
- \`<lines>\` (optional): Line ranges like "1-50,75-80,100-120" (omit to read entire file)
|
|
157
178
|
|
|
158
179
|
Examples:
|
|
159
180
|
\`\`\`
|
|
160
|
-
read
|
|
161
|
-
|
|
162
|
-
read
|
|
181
|
+
<read>
|
|
182
|
+
<path>src/main.py</path>
|
|
183
|
+
</read>
|
|
184
|
+
|
|
185
|
+
<read>
|
|
186
|
+
<path>src/auth.py</path>
|
|
187
|
+
<lines>1-20,45-80,150-200</lines>
|
|
188
|
+
</read>
|
|
163
189
|
\`\`\`
|
|
164
190
|
|
|
165
|
-
### \`grep
|
|
166
|
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
191
|
+
### \`grep\`
|
|
192
|
+
Search for pattern matches across files. Returns matches with 1 line of context above and below.
|
|
193
|
+
- Match lines use \`:\` separator \u2192 \`filepath:linenum:content\`
|
|
194
|
+
- Context lines use \`-\` separator \u2192 \`filepath-linenum-content\`
|
|
195
|
+
|
|
196
|
+
Elements:
|
|
197
|
+
- \`<pattern>\` (required): Search pattern (regex). Use \`(a|b)\` for OR patterns.
|
|
198
|
+
- \`<sub_dir>\` (optional): Subdirectory to search in (defaults to \`.\`)
|
|
199
|
+
- \`<glob>\` (optional): File pattern filter like \`*.py\` or \`*.{ts,tsx}\`
|
|
169
200
|
|
|
170
201
|
Examples:
|
|
171
202
|
\`\`\`
|
|
172
|
-
grep
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
grep
|
|
203
|
+
<grep>
|
|
204
|
+
<pattern>(authenticate|authorize|login)</pattern>
|
|
205
|
+
<sub_dir>src/auth/</sub_dir>
|
|
206
|
+
</grep>
|
|
207
|
+
|
|
208
|
+
<grep>
|
|
209
|
+
<pattern>class.*(Service|Controller)</pattern>
|
|
210
|
+
<glob>*.{ts,js}</glob>
|
|
211
|
+
</grep>
|
|
212
|
+
|
|
213
|
+
<grep>
|
|
214
|
+
<pattern>(DB_HOST|DATABASE_URL|connection)</pattern>
|
|
215
|
+
<glob>*.{py,yaml,env}</glob>
|
|
216
|
+
<sub_dir>lib/</sub_dir>
|
|
217
|
+
</grep>
|
|
176
218
|
\`\`\`
|
|
177
219
|
|
|
178
|
-
### \`finish
|
|
179
|
-
Submit final answer with all relevant code locations.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
|
|
220
|
+
### \`finish\`
|
|
221
|
+
Submit final answer with all relevant code locations. Uses nested \`<file>\` elements.
|
|
222
|
+
|
|
223
|
+
File elements:
|
|
224
|
+
- \`<path>\` (required): File path
|
|
225
|
+
- \`<lines>\` (optional): Line ranges like "1-50,75-80" (\`*\` for entire file)
|
|
226
|
+
|
|
227
|
+
ALWAYS include import statements (usually lines 1-20). Better to over-include than miss context.
|
|
185
228
|
|
|
186
229
|
Examples:
|
|
187
230
|
\`\`\`
|
|
188
|
-
finish
|
|
189
|
-
|
|
231
|
+
<finish>
|
|
232
|
+
<file>
|
|
233
|
+
<path>src/auth.py</path>
|
|
234
|
+
<lines>1-15,25-50,75-80</lines>
|
|
235
|
+
</file>
|
|
236
|
+
<file>
|
|
237
|
+
<path>src/models/user.py</path>
|
|
238
|
+
<lines>*</lines>
|
|
239
|
+
</file>
|
|
240
|
+
</finish>
|
|
190
241
|
\`\`\`
|
|
191
242
|
</tools>
|
|
192
243
|
|
|
193
244
|
<strategy>
|
|
194
|
-
**Before your first tool call, classify the
|
|
245
|
+
**Before your first tool call, classify the search_string:**
|
|
195
246
|
|
|
196
|
-
|
|
|
197
|
-
|
|
198
|
-
| **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by
|
|
199
|
-
| **Conceptual** (how does X work, where is Y handled) |
|
|
200
|
-
| **Exploratory** (find all tests, list API endpoints) |
|
|
247
|
+
| Search_string Type | Round 1 Strategy | Early Finish? |
|
|
248
|
+
|------------|------------------|---------------|
|
|
249
|
+
| **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by round 2 |
|
|
250
|
+
| **Conceptual** (how does X work, where is Y handled) | list_directory + 2-3 broad greps | Rarely early |
|
|
251
|
+
| **Exploratory** (find all tests, list API endpoints) | list_directory at multiple depths | Usually needs 3 rounds |
|
|
201
252
|
|
|
202
253
|
**Parallel call patterns:**
|
|
203
254
|
- **Shotgun grep**: Same pattern, 8 different directories\u2014fast coverage
|
|
204
255
|
- **Variant grep**: 8 pattern variations (synonyms, naming conventions)\u2014catches inconsistent codebases
|
|
205
|
-
- **Funnel**: 1
|
|
256
|
+
- **Funnel**: 1 list_directory + 7 greps\u2014orient and search simultaneously
|
|
206
257
|
- **Deep read**: 8 reads on files you already identified\u2014gather full context fast
|
|
258
|
+
|
|
259
|
+
**Tool call expectations:**
|
|
260
|
+
- 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.
|
|
261
|
+
- 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.
|
|
262
|
+
- 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.
|
|
263
|
+
- 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.
|
|
207
264
|
</strategy>
|
|
208
265
|
|
|
209
266
|
<output_format>
|
|
210
267
|
EVERY response MUST follow this exact format:
|
|
211
268
|
|
|
212
269
|
1. First, wrap your reasoning in \`<think>...</think>\` tags containing:
|
|
213
|
-
-
|
|
214
|
-
- Confidence estimate (can I finish in 1-2
|
|
215
|
-
- This
|
|
270
|
+
- Search_string classification (specific/conceptual/exploratory)
|
|
271
|
+
- Confidence estimate (can I finish in 1-2 rounds?)
|
|
272
|
+
- This round's parallel strategy
|
|
216
273
|
- What signals would let me finish early?
|
|
217
274
|
|
|
218
|
-
2. Then, output
|
|
275
|
+
2. Then, output up to 8 tool calls using nested XML elements.
|
|
219
276
|
|
|
220
277
|
Example:
|
|
221
278
|
\`\`\`
|
|
222
279
|
<think>
|
|
223
|
-
This is a specific
|
|
224
|
-
High confidence I can finish in 2
|
|
280
|
+
This is a specific search_string about authentication. I'll grep for auth-related patterns.
|
|
281
|
+
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
|
|
225
282
|
Strategy: Shotgun grep across likely directories.
|
|
226
283
|
</think>
|
|
227
|
-
<
|
|
228
|
-
<
|
|
229
|
-
<
|
|
284
|
+
<grep>
|
|
285
|
+
<pattern>(authenticate|login|session)</pattern>
|
|
286
|
+
<sub_dir>src/auth/</sub_dir>
|
|
287
|
+
</grep>
|
|
288
|
+
<grep>
|
|
289
|
+
<pattern>(middleware|interceptor)</pattern>
|
|
290
|
+
<glob>*.{ts,js}</glob>
|
|
291
|
+
</grep>
|
|
292
|
+
<list_directory>
|
|
293
|
+
<path>src/auth</path>
|
|
294
|
+
</list_directory>
|
|
295
|
+
\`\`\`
|
|
296
|
+
|
|
297
|
+
Finishing example:
|
|
298
|
+
\`\`\`
|
|
299
|
+
<think>
|
|
300
|
+
I think I have a rough idea, but this is my last turn so I must call the finish tool regardless.
|
|
301
|
+
</think>
|
|
302
|
+
<finish>
|
|
303
|
+
<file>
|
|
304
|
+
<path>src/auth/login.py</path>
|
|
305
|
+
<lines>1-50</lines>
|
|
306
|
+
</file>
|
|
307
|
+
<file>
|
|
308
|
+
<path>src/middleware/session.py</path>
|
|
309
|
+
<lines>10-80</lines>
|
|
310
|
+
</file>
|
|
311
|
+
</finish>
|
|
230
312
|
\`\`\`
|
|
231
313
|
|
|
232
314
|
No commentary outside \`<think>\`. No explanations after tool calls.
|
|
@@ -239,17 +321,111 @@ When calling \`finish\`:
|
|
|
239
321
|
- Include any type definitions, interfaces, or constants used
|
|
240
322
|
- Better to over-include than leave the user missing context
|
|
241
323
|
- If unsure about boundaries, include more rather than less
|
|
242
|
-
</finishing_requirements
|
|
243
|
-
|
|
244
|
-
Begin your exploration now to find code relevant to the query.`;
|
|
324
|
+
</finishing_requirements>`;
|
|
245
325
|
function getSystemPrompt() {
|
|
246
326
|
return SYSTEM_PROMPT;
|
|
247
327
|
}
|
|
248
328
|
|
|
249
329
|
// tools/warp_grep/agent/parser.ts
|
|
250
|
-
var VALID_COMMANDS = ["
|
|
330
|
+
var VALID_COMMANDS = ["list_directory", "grep", "read", "finish"];
|
|
331
|
+
function isValidCommand(name) {
|
|
332
|
+
return VALID_COMMANDS.includes(name);
|
|
333
|
+
}
|
|
334
|
+
function getXmlElementText(xml, tagName) {
|
|
335
|
+
const regex = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`, "i");
|
|
336
|
+
const match = xml.match(regex);
|
|
337
|
+
return match ? match[1].trim() : null;
|
|
338
|
+
}
|
|
339
|
+
function parseNestedXmlTools(text) {
|
|
340
|
+
const tools = [];
|
|
341
|
+
const toolRegex = /<([a-z_][a-z0-9_]*)>([\s\S]*?)<\/\1>/gi;
|
|
342
|
+
let match;
|
|
343
|
+
while ((match = toolRegex.exec(text)) !== null) {
|
|
344
|
+
const rawToolName = match[1].toLowerCase();
|
|
345
|
+
const content = match[2];
|
|
346
|
+
if (!isValidCommand(rawToolName)) continue;
|
|
347
|
+
const toolName = rawToolName;
|
|
348
|
+
if (toolName === "list_directory") {
|
|
349
|
+
const path3 = getXmlElementText(content, "path");
|
|
350
|
+
const pattern = getXmlElementText(content, "pattern");
|
|
351
|
+
if (path3) {
|
|
352
|
+
tools.push({ name: "list_directory", arguments: { path: path3, pattern } });
|
|
353
|
+
}
|
|
354
|
+
} else if (toolName === "grep") {
|
|
355
|
+
const pattern = getXmlElementText(content, "pattern");
|
|
356
|
+
const subDir = getXmlElementText(content, "sub_dir");
|
|
357
|
+
const glob = getXmlElementText(content, "glob");
|
|
358
|
+
if (pattern) {
|
|
359
|
+
tools.push({
|
|
360
|
+
name: "grep",
|
|
361
|
+
arguments: {
|
|
362
|
+
pattern,
|
|
363
|
+
path: subDir || ".",
|
|
364
|
+
...glob && { glob }
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
} else if (toolName === "read") {
|
|
369
|
+
const path3 = getXmlElementText(content, "path");
|
|
370
|
+
const linesStr = getXmlElementText(content, "lines");
|
|
371
|
+
if (path3) {
|
|
372
|
+
const args = { path: path3 };
|
|
373
|
+
if (linesStr) {
|
|
374
|
+
const ranges = [];
|
|
375
|
+
for (const rangeStr of linesStr.split(",")) {
|
|
376
|
+
const trimmed = rangeStr.trim();
|
|
377
|
+
if (!trimmed) continue;
|
|
378
|
+
const [s, e] = trimmed.split("-").map((v) => parseInt(v.trim(), 10));
|
|
379
|
+
if (Number.isFinite(s) && Number.isFinite(e)) {
|
|
380
|
+
ranges.push([s, e]);
|
|
381
|
+
} else if (Number.isFinite(s)) {
|
|
382
|
+
ranges.push([s, s]);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (ranges.length === 1) {
|
|
386
|
+
args.start = ranges[0][0];
|
|
387
|
+
args.end = ranges[0][1];
|
|
388
|
+
} else if (ranges.length > 1) {
|
|
389
|
+
args.lines = ranges;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
tools.push({ name: "read", arguments: args });
|
|
393
|
+
}
|
|
394
|
+
} else if (toolName === "finish") {
|
|
395
|
+
const fileRegex = /<file>([\s\S]*?)<\/file>/gi;
|
|
396
|
+
const files = [];
|
|
397
|
+
let fileMatch;
|
|
398
|
+
while ((fileMatch = fileRegex.exec(content)) !== null) {
|
|
399
|
+
const fileContent = fileMatch[1];
|
|
400
|
+
const filePath = getXmlElementText(fileContent, "path");
|
|
401
|
+
const linesStr = getXmlElementText(fileContent, "lines");
|
|
402
|
+
if (filePath && linesStr) {
|
|
403
|
+
const ranges = [];
|
|
404
|
+
for (const rangeStr of linesStr.split(",")) {
|
|
405
|
+
if (rangeStr.trim() === "*") {
|
|
406
|
+
ranges.push([1, 999999]);
|
|
407
|
+
} else {
|
|
408
|
+
const [s, e] = rangeStr.split("-").map((v) => parseInt(v.trim(), 10));
|
|
409
|
+
if (Number.isFinite(s) && Number.isFinite(e)) {
|
|
410
|
+
ranges.push([s, e]);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (ranges.length > 0) {
|
|
415
|
+
files.push({ path: filePath, lines: ranges });
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (files.length > 0) {
|
|
420
|
+
tools.push({ name: "finish", arguments: { files } });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return tools;
|
|
425
|
+
}
|
|
251
426
|
function preprocessText(text) {
|
|
252
427
|
let processed = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
|
|
428
|
+
const nestedTools = parseNestedXmlTools(processed);
|
|
253
429
|
const openingTagRegex = /<tool_call>|<tool>/gi;
|
|
254
430
|
const closingTagRegex = /<\/tool_call>|<\/tool>/gi;
|
|
255
431
|
const openingMatches = processed.match(openingTagRegex) || [];
|
|
@@ -286,7 +462,7 @@ function preprocessText(text) {
|
|
|
286
462
|
}
|
|
287
463
|
}
|
|
288
464
|
}
|
|
289
|
-
return toolCallLines;
|
|
465
|
+
return { lines: toolCallLines, nestedTools };
|
|
290
466
|
}
|
|
291
467
|
var LLMResponseParser = class {
|
|
292
468
|
finishSpecSplitRe = /,(?=[^,\s]+:)/;
|
|
@@ -294,8 +470,8 @@ var LLMResponseParser = class {
|
|
|
294
470
|
if (typeof text !== "string") {
|
|
295
471
|
throw new TypeError("Command text must be a string.");
|
|
296
472
|
}
|
|
297
|
-
const lines = preprocessText(text);
|
|
298
|
-
const commands = [];
|
|
473
|
+
const { lines, nestedTools } = preprocessText(text);
|
|
474
|
+
const commands = [...nestedTools];
|
|
299
475
|
let finishAccumulator = null;
|
|
300
476
|
lines.forEach((line) => {
|
|
301
477
|
if (!line || line.startsWith("#")) return;
|
|
@@ -303,8 +479,8 @@ var LLMResponseParser = class {
|
|
|
303
479
|
if (parts.length === 0) return;
|
|
304
480
|
const cmd = parts[0];
|
|
305
481
|
switch (cmd) {
|
|
306
|
-
case "
|
|
307
|
-
this.
|
|
482
|
+
case "list_directory":
|
|
483
|
+
this.handleListDirectory(parts, line, commands);
|
|
308
484
|
break;
|
|
309
485
|
case "grep":
|
|
310
486
|
this.handleGrep(parts, line, commands);
|
|
@@ -322,8 +498,8 @@ var LLMResponseParser = class {
|
|
|
322
498
|
if (finishAccumulator) {
|
|
323
499
|
const map = finishAccumulator;
|
|
324
500
|
const entries = [...map.entries()];
|
|
325
|
-
const filesPayload = entries.map(([
|
|
326
|
-
path:
|
|
501
|
+
const filesPayload = entries.map(([path3, ranges]) => ({
|
|
502
|
+
path: path3,
|
|
327
503
|
lines: [...ranges].sort((a, b) => a[0] - b[0])
|
|
328
504
|
}));
|
|
329
505
|
commands.push({ name: "finish", arguments: { files: filesPayload } });
|
|
@@ -355,18 +531,17 @@ var LLMResponseParser = class {
|
|
|
355
531
|
skip(message) {
|
|
356
532
|
return { name: "_skip", arguments: { message } };
|
|
357
533
|
}
|
|
358
|
-
|
|
534
|
+
handleListDirectory(parts, rawLine, commands) {
|
|
359
535
|
if (parts.length < 2) {
|
|
360
536
|
commands.push(this.skip(
|
|
361
|
-
`[SKIPPED] Your command "${rawLine}" is missing a path. Correct format:
|
|
537
|
+
`[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: list_directory <path> [pattern]. Example: list_directory src/`
|
|
362
538
|
));
|
|
363
539
|
return;
|
|
364
540
|
}
|
|
365
|
-
const
|
|
541
|
+
const path3 = parts[1];
|
|
366
542
|
const pattern = parts[2]?.replace(/^"|"$/g, "") ?? null;
|
|
367
|
-
commands.push({ name: "
|
|
543
|
+
commands.push({ name: "list_directory", arguments: { path: path3, pattern } });
|
|
368
544
|
}
|
|
369
|
-
// no glob tool in MCP
|
|
370
545
|
handleGrep(parts, rawLine, commands) {
|
|
371
546
|
if (parts.length < 3) {
|
|
372
547
|
commands.push(this.skip(
|
|
@@ -437,8 +612,30 @@ var LLMResponseParser = class {
|
|
|
437
612
|
}
|
|
438
613
|
};
|
|
439
614
|
|
|
440
|
-
// tools/warp_grep/tools/
|
|
615
|
+
// tools/warp_grep/agent/tools/grep.ts
|
|
616
|
+
async function toolGrep(provider, args) {
|
|
617
|
+
const res = await provider.grep(args);
|
|
618
|
+
if (res.error) {
|
|
619
|
+
return { output: res.error };
|
|
620
|
+
}
|
|
621
|
+
if (!res.lines.length) {
|
|
622
|
+
return { output: "no matches" };
|
|
623
|
+
}
|
|
624
|
+
return { output: res.lines.join("\n") };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// tools/warp_grep/agent/tools/read.ts
|
|
441
628
|
async function toolRead(provider, args) {
|
|
629
|
+
if (args.lines && args.lines.length > 0) {
|
|
630
|
+
const chunks = [];
|
|
631
|
+
for (const [start, end] of args.lines) {
|
|
632
|
+
const res2 = await provider.read({ path: args.path, start, end });
|
|
633
|
+
if (res2.error) return res2.error;
|
|
634
|
+
chunks.push(res2.lines.join("\n"));
|
|
635
|
+
}
|
|
636
|
+
if (chunks.every((c) => c === "")) return "(empty file)";
|
|
637
|
+
return chunks.join("\n...\n");
|
|
638
|
+
}
|
|
442
639
|
const res = await provider.read({ path: args.path, start: args.start, end: args.end });
|
|
443
640
|
if (res.error) {
|
|
444
641
|
return res.error;
|
|
@@ -447,16 +644,56 @@ async function toolRead(provider, args) {
|
|
|
447
644
|
return res.lines.join("\n");
|
|
448
645
|
}
|
|
449
646
|
|
|
450
|
-
// tools/warp_grep/tools/
|
|
451
|
-
async function
|
|
452
|
-
const list = await provider.
|
|
647
|
+
// tools/warp_grep/agent/tools/list_directory.ts
|
|
648
|
+
async function toolListDirectory(provider, args) {
|
|
649
|
+
const list = await provider.listDirectory({
|
|
453
650
|
path: args.path,
|
|
454
651
|
pattern: args.pattern ?? null,
|
|
455
|
-
maxResults: args.maxResults ??
|
|
456
|
-
maxDepth: args.maxDepth ??
|
|
652
|
+
maxResults: args.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES,
|
|
653
|
+
maxDepth: args.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH
|
|
457
654
|
});
|
|
458
655
|
if (!list.length) return "empty";
|
|
459
|
-
|
|
656
|
+
if (list.length >= AGENT_CONFIG.MAX_OUTPUT_LINES) {
|
|
657
|
+
return "query not specific enough, tool called tried to return too much context and failed";
|
|
658
|
+
}
|
|
659
|
+
return list.map((e) => {
|
|
660
|
+
const indent = " ".repeat(e.depth);
|
|
661
|
+
const name = e.type === "dir" ? `${e.name}/` : e.name;
|
|
662
|
+
return `${indent}${name}`;
|
|
663
|
+
}).join("\n");
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// tools/warp_grep/agent/tools/finish.ts
|
|
667
|
+
async function readFinishFiles(repoRoot, files, reader) {
|
|
668
|
+
const out = [];
|
|
669
|
+
for (const f of files) {
|
|
670
|
+
const ranges = mergeRanges(f.lines);
|
|
671
|
+
const chunks = [];
|
|
672
|
+
for (const [s, e] of ranges) {
|
|
673
|
+
const lines = await reader(f.path, s, e);
|
|
674
|
+
chunks.push(lines.join("\n"));
|
|
675
|
+
}
|
|
676
|
+
out.push({ path: f.path, ranges, content: chunks.join("\n") });
|
|
677
|
+
}
|
|
678
|
+
return out;
|
|
679
|
+
}
|
|
680
|
+
function mergeRanges(ranges) {
|
|
681
|
+
if (!ranges.length) return [];
|
|
682
|
+
const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
|
|
683
|
+
const merged = [];
|
|
684
|
+
let [cs, ce] = sorted[0];
|
|
685
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
686
|
+
const [s, e] = sorted[i];
|
|
687
|
+
if (s <= ce + 1) {
|
|
688
|
+
ce = Math.max(ce, e);
|
|
689
|
+
} else {
|
|
690
|
+
merged.push([cs, ce]);
|
|
691
|
+
cs = s;
|
|
692
|
+
ce = e;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
merged.push([cs, ce]);
|
|
696
|
+
return merged;
|
|
460
697
|
}
|
|
461
698
|
|
|
462
699
|
// tools/utils/resilience.ts
|
|
@@ -549,8 +786,8 @@ var ToolOutputFormatter = class {
|
|
|
549
786
|
switch (name) {
|
|
550
787
|
case "read":
|
|
551
788
|
return this.formatRead(safeArgs, payload, isError);
|
|
552
|
-
case "
|
|
553
|
-
return this.
|
|
789
|
+
case "list_directory":
|
|
790
|
+
return this.formatListDirectory(safeArgs, payload, isError);
|
|
554
791
|
case "grep":
|
|
555
792
|
return this.formatGrep(safeArgs, payload, isError);
|
|
556
793
|
default:
|
|
@@ -563,39 +800,56 @@ ${payload}
|
|
|
563
800
|
if (isError) {
|
|
564
801
|
return payload;
|
|
565
802
|
}
|
|
566
|
-
const
|
|
567
|
-
|
|
803
|
+
const path3 = this.asString(args.path) || "...";
|
|
804
|
+
const start = args.start;
|
|
805
|
+
const end = args.end;
|
|
806
|
+
const linesArray = args.lines;
|
|
807
|
+
const attributes = [`path="${path3}"`];
|
|
808
|
+
if (linesArray && linesArray.length > 0) {
|
|
809
|
+
const rangeStr = linesArray.map(([s, e]) => `${s}-${e}`).join(",");
|
|
810
|
+
attributes.push(`lines="${rangeStr}"`);
|
|
811
|
+
} else if (start !== void 0 && end !== void 0) {
|
|
812
|
+
attributes.push(`lines="${start}-${end}"`);
|
|
813
|
+
}
|
|
814
|
+
return `<read ${attributes.join(" ")}>
|
|
568
815
|
${payload}
|
|
569
|
-
</
|
|
816
|
+
</read>`;
|
|
570
817
|
}
|
|
571
|
-
|
|
572
|
-
const
|
|
818
|
+
formatListDirectory(args, payload, isError) {
|
|
819
|
+
const path3 = this.asString(args.path) || ".";
|
|
820
|
+
const pattern = this.asString(args.pattern);
|
|
821
|
+
const attributes = [`path="${path3}"`];
|
|
822
|
+
if (pattern) {
|
|
823
|
+
attributes.push(`pattern="${pattern}"`);
|
|
824
|
+
}
|
|
573
825
|
if (isError) {
|
|
574
|
-
|
|
575
|
-
${payload}
|
|
576
|
-
</analyse_results>`;
|
|
826
|
+
attributes.push('status="error"');
|
|
577
827
|
}
|
|
578
|
-
return `<
|
|
828
|
+
return `<list_directory ${attributes.join(" ")}>
|
|
579
829
|
${payload}
|
|
580
|
-
</
|
|
830
|
+
</list_directory>`;
|
|
581
831
|
}
|
|
582
832
|
formatGrep(args, payload, isError) {
|
|
583
833
|
const pattern = this.asString(args.pattern);
|
|
584
|
-
const
|
|
834
|
+
const subDir = this.asString(args.path);
|
|
835
|
+
const glob = this.asString(args.glob);
|
|
585
836
|
const attributes = [];
|
|
586
837
|
if (pattern !== void 0) {
|
|
587
838
|
attributes.push(`pattern="${pattern}"`);
|
|
588
839
|
}
|
|
589
|
-
if (
|
|
590
|
-
attributes.push(`
|
|
840
|
+
if (subDir !== void 0) {
|
|
841
|
+
attributes.push(`sub_dir="${subDir}"`);
|
|
842
|
+
}
|
|
843
|
+
if (glob !== void 0) {
|
|
844
|
+
attributes.push(`glob="${glob}"`);
|
|
591
845
|
}
|
|
592
846
|
if (isError) {
|
|
593
847
|
attributes.push('status="error"');
|
|
594
848
|
}
|
|
595
849
|
const attrText = attributes.length ? ` ${attributes.join(" ")}` : "";
|
|
596
|
-
return `<
|
|
850
|
+
return `<grep${attrText}>
|
|
597
851
|
${payload}
|
|
598
|
-
</
|
|
852
|
+
</grep>`;
|
|
599
853
|
}
|
|
600
854
|
asString(value) {
|
|
601
855
|
if (value === null || value === void 0) {
|
|
@@ -609,180 +863,100 @@ function formatAgentToolOutput(toolName, args, output, options = {}) {
|
|
|
609
863
|
return sharedFormatter.format(toolName, args, output, options);
|
|
610
864
|
}
|
|
611
865
|
|
|
612
|
-
// tools/warp_grep/agent/
|
|
613
|
-
var
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
this.seenLines.add(this.makeKey(path2, lineNumber));
|
|
621
|
-
}
|
|
622
|
-
makeKey(path2, lineNumber) {
|
|
623
|
-
return `${path2}:${lineNumber}`;
|
|
866
|
+
// tools/warp_grep/agent/helpers.ts
|
|
867
|
+
var import_path = __toESM(require("path"), 1);
|
|
868
|
+
var TRUNCATED_MARKER = "[truncated for context limit]";
|
|
869
|
+
function formatTurnMessage(turnsUsed, maxTurns) {
|
|
870
|
+
const turnsRemaining = maxTurns - turnsUsed;
|
|
871
|
+
if (turnsRemaining === 1) {
|
|
872
|
+
return `
|
|
873
|
+
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`;
|
|
624
874
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
function extractMatchFields(payload) {
|
|
628
|
-
const text = payload.replace(/\r?\n$/, "");
|
|
629
|
-
if (!text || text.startsWith("[error]")) {
|
|
630
|
-
return null;
|
|
631
|
-
}
|
|
632
|
-
const firstSep = text.indexOf(":");
|
|
633
|
-
if (firstSep === -1) {
|
|
634
|
-
return null;
|
|
635
|
-
}
|
|
636
|
-
let filePath = text.slice(0, firstSep).trim();
|
|
637
|
-
if (!filePath) {
|
|
638
|
-
return null;
|
|
639
|
-
}
|
|
640
|
-
if (filePath.startsWith("./") || filePath.startsWith(".\\")) {
|
|
641
|
-
filePath = filePath.slice(2);
|
|
642
|
-
}
|
|
643
|
-
const remainder = text.slice(firstSep + 1);
|
|
644
|
-
const secondSep = remainder.indexOf(":");
|
|
645
|
-
if (secondSep === -1) {
|
|
646
|
-
return null;
|
|
647
|
-
}
|
|
648
|
-
const linePart = remainder.slice(0, secondSep);
|
|
649
|
-
const lineNumber = Number.parseInt(linePart, 10);
|
|
650
|
-
if (!Number.isInteger(lineNumber) || lineNumber <= 0) {
|
|
651
|
-
return null;
|
|
652
|
-
}
|
|
653
|
-
let contentSegment = remainder.slice(secondSep + 1);
|
|
654
|
-
const columnSep = contentSegment.indexOf(":");
|
|
655
|
-
if (columnSep !== -1 && /^\d+$/.test(contentSegment.slice(0, columnSep))) {
|
|
656
|
-
contentSegment = contentSegment.slice(columnSep + 1);
|
|
657
|
-
}
|
|
658
|
-
const content = contentSegment.trim();
|
|
659
|
-
if (!content) {
|
|
660
|
-
return null;
|
|
661
|
-
}
|
|
662
|
-
return { path: filePath, lineNumber, content };
|
|
875
|
+
return `
|
|
876
|
+
You have used ${turnsUsed} turn${turnsUsed === 1 ? "" : "s"} and have ${turnsRemaining} remaining`;
|
|
663
877
|
}
|
|
664
|
-
function
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
if (!fields) {
|
|
672
|
-
continue;
|
|
673
|
-
}
|
|
674
|
-
if (state.isNew(fields.path, fields.lineNumber)) {
|
|
675
|
-
matches.push(fields);
|
|
676
|
-
state.add(fields.path, fields.lineNumber);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
return matches;
|
|
878
|
+
function calculateContextBudget(messages) {
|
|
879
|
+
const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0);
|
|
880
|
+
const maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS;
|
|
881
|
+
const percent = Math.round(totalChars / maxChars * 100);
|
|
882
|
+
const usedK = Math.round(totalChars / 1e3);
|
|
883
|
+
const maxK = Math.round(maxChars / 1e3);
|
|
884
|
+
return `<context_budget>${percent}% (${usedK}K/${maxK}K chars)</context_budget>`;
|
|
680
885
|
}
|
|
681
|
-
function
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
886
|
+
async function buildInitialState(repoRoot, query, provider) {
|
|
887
|
+
try {
|
|
888
|
+
const entries = await provider.listDirectory({
|
|
889
|
+
path: ".",
|
|
890
|
+
maxResults: AGENT_CONFIG.MAX_OUTPUT_LINES,
|
|
891
|
+
maxDepth: 2
|
|
892
|
+
});
|
|
893
|
+
const treeLines = entries.map((e) => {
|
|
894
|
+
const indent = " ".repeat(e.depth);
|
|
895
|
+
const name = e.type === "dir" ? `${e.name}/` : e.name;
|
|
896
|
+
return `${indent}${name}`;
|
|
897
|
+
});
|
|
898
|
+
const repoName = import_path.default.basename(repoRoot);
|
|
899
|
+
const treeOutput = treeLines.length > 0 ? `${repoName}/
|
|
900
|
+
${treeLines.join("\n")}` : `${repoName}/`;
|
|
901
|
+
return `<repo_structure>
|
|
902
|
+
${treeOutput}
|
|
903
|
+
</repo_structure>
|
|
904
|
+
|
|
905
|
+
<search_string>
|
|
906
|
+
${query}
|
|
907
|
+
</search_string>`;
|
|
908
|
+
} catch {
|
|
909
|
+
const repoName = import_path.default.basename(repoRoot);
|
|
910
|
+
return `<repo_structure>
|
|
911
|
+
${repoName}/
|
|
912
|
+
</repo_structure>
|
|
913
|
+
|
|
914
|
+
<search_string>
|
|
915
|
+
${query}
|
|
916
|
+
</search_string>`;
|
|
917
|
+
}
|
|
699
918
|
}
|
|
700
|
-
function
|
|
701
|
-
|
|
702
|
-
|
|
919
|
+
function enforceContextLimit(messages, maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS) {
|
|
920
|
+
const getTotalChars = () => messages.reduce((sum, m) => sum + m.content.length, 0);
|
|
921
|
+
if (getTotalChars() <= maxChars) {
|
|
922
|
+
return messages;
|
|
703
923
|
}
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
924
|
+
const userIndices = [];
|
|
925
|
+
let firstUserSkipped = false;
|
|
926
|
+
for (let i = 0; i < messages.length; i++) {
|
|
927
|
+
if (messages[i].role === "user") {
|
|
928
|
+
if (!firstUserSkipped) {
|
|
929
|
+
firstUserSkipped = true;
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
userIndices.push(i);
|
|
708
933
|
}
|
|
709
|
-
matchesByFile.get(match.path).push(match);
|
|
710
934
|
}
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
if (index > 0) {
|
|
715
|
-
lines.push("");
|
|
716
|
-
}
|
|
717
|
-
lines.push(filePath);
|
|
718
|
-
const sortedMatches = matchesByFile.get(filePath).slice().sort((a, b) => a.lineNumber - b.lineNumber);
|
|
719
|
-
for (const match of sortedMatches) {
|
|
720
|
-
lines.push(`${match.lineNumber}:${match.content}`);
|
|
721
|
-
}
|
|
722
|
-
});
|
|
723
|
-
return truncateOutput(lines.join("\n"), maxChars);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// tools/warp_grep/tools/finish.ts
|
|
727
|
-
async function readFinishFiles(repoRoot, files, reader) {
|
|
728
|
-
const out = [];
|
|
729
|
-
for (const f of files) {
|
|
730
|
-
const ranges = mergeRanges(f.lines);
|
|
731
|
-
const chunks = [];
|
|
732
|
-
for (const [s, e] of ranges) {
|
|
733
|
-
const lines = await reader(f.path, s, e);
|
|
734
|
-
chunks.push(lines.join("\n"));
|
|
935
|
+
for (const idx of userIndices) {
|
|
936
|
+
if (getTotalChars() <= maxChars) {
|
|
937
|
+
break;
|
|
735
938
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
return out;
|
|
739
|
-
}
|
|
740
|
-
function mergeRanges(ranges) {
|
|
741
|
-
if (!ranges.length) return [];
|
|
742
|
-
const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
|
|
743
|
-
const merged = [];
|
|
744
|
-
let [cs, ce] = sorted[0];
|
|
745
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
746
|
-
const [s, e] = sorted[i];
|
|
747
|
-
if (s <= ce + 1) {
|
|
748
|
-
ce = Math.max(ce, e);
|
|
749
|
-
} else {
|
|
750
|
-
merged.push([cs, ce]);
|
|
751
|
-
cs = s;
|
|
752
|
-
ce = e;
|
|
939
|
+
if (messages[idx].content !== TRUNCATED_MARKER) {
|
|
940
|
+
messages[idx] = { role: "user", content: TRUNCATED_MARKER };
|
|
753
941
|
}
|
|
754
942
|
}
|
|
755
|
-
|
|
756
|
-
return merged;
|
|
943
|
+
return messages;
|
|
757
944
|
}
|
|
758
945
|
|
|
759
946
|
// tools/warp_grep/agent/runner.ts
|
|
760
|
-
var
|
|
947
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
761
948
|
var parser = new LLMResponseParser();
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
const files = entries.filter((e) => e.type === "file").map((f) => f.name).slice(0, 50);
|
|
767
|
-
const parts = [
|
|
768
|
-
`<repo_root>${repoRoot}</repo_root>`,
|
|
769
|
-
`<top_dirs>${dirs.join(", ")}</top_dirs>`,
|
|
770
|
-
`<top_files>${files.join(", ")}</top_files>`
|
|
771
|
-
];
|
|
772
|
-
return parts.join("\n");
|
|
773
|
-
} catch {
|
|
774
|
-
return `<repo_root>${repoRoot}</repo_root>`;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
async function callModel(messages, model, apiKey) {
|
|
778
|
-
const api = "https://api.morphllm.com/v1/chat/completions";
|
|
949
|
+
var DEFAULT_API_URL = "https://api.morphllm.com";
|
|
950
|
+
async function callModel(messages, model, options = {}) {
|
|
951
|
+
const baseUrl = DEFAULT_API_URL;
|
|
952
|
+
const apiKey = options.morphApiKey || process.env.MORPH_API_KEY || "";
|
|
779
953
|
const fetchPromise = fetchWithRetry(
|
|
780
|
-
|
|
954
|
+
`${baseUrl}/v1/chat/completions`,
|
|
781
955
|
{
|
|
782
956
|
method: "POST",
|
|
783
957
|
headers: {
|
|
784
958
|
"Content-Type": "application/json",
|
|
785
|
-
Authorization: `Bearer ${apiKey
|
|
959
|
+
Authorization: `Bearer ${apiKey}`
|
|
786
960
|
},
|
|
787
961
|
body: JSON.stringify({
|
|
788
962
|
model,
|
|
@@ -791,10 +965,15 @@ async function callModel(messages, model, apiKey) {
|
|
|
791
965
|
messages
|
|
792
966
|
})
|
|
793
967
|
},
|
|
794
|
-
|
|
968
|
+
options.retryConfig
|
|
795
969
|
);
|
|
796
970
|
const resp = await withTimeout(fetchPromise, AGENT_CONFIG.TIMEOUT_MS, "morph-warp-grep request timed out");
|
|
797
971
|
if (!resp.ok) {
|
|
972
|
+
if (resp.status === 404) {
|
|
973
|
+
throw new Error(
|
|
974
|
+
"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"
|
|
975
|
+
);
|
|
976
|
+
}
|
|
798
977
|
const t = await resp.text();
|
|
799
978
|
throw new Error(`morph-warp-grep error ${resp.status}: ${t}`);
|
|
800
979
|
}
|
|
@@ -806,23 +985,24 @@ async function callModel(messages, model, apiKey) {
|
|
|
806
985
|
return content;
|
|
807
986
|
}
|
|
808
987
|
async function runWarpGrep(config) {
|
|
809
|
-
const repoRoot =
|
|
988
|
+
const repoRoot = import_path2.default.resolve(config.repoRoot || process.cwd());
|
|
810
989
|
const messages = [];
|
|
811
|
-
|
|
812
|
-
messages.push(systemMessage);
|
|
813
|
-
const queryContent = `<query>${config.query}</query>`;
|
|
814
|
-
messages.push({ role: "user", content: queryContent });
|
|
990
|
+
messages.push({ role: "system", content: getSystemPrompt() });
|
|
815
991
|
const initialState = await buildInitialState(repoRoot, config.query, config.provider);
|
|
816
992
|
messages.push({ role: "user", content: initialState });
|
|
817
|
-
const
|
|
993
|
+
const maxTurns = AGENT_CONFIG.MAX_TURNS;
|
|
818
994
|
const model = config.model || DEFAULT_MODEL;
|
|
819
995
|
const provider = config.provider;
|
|
820
996
|
const errors = [];
|
|
821
|
-
const grepState = new GrepState();
|
|
822
997
|
let finishMeta;
|
|
823
998
|
let terminationReason = "terminated";
|
|
824
|
-
for (let
|
|
825
|
-
|
|
999
|
+
for (let turn = 1; turn <= maxTurns; turn += 1) {
|
|
1000
|
+
enforceContextLimit(messages);
|
|
1001
|
+
const assistantContent = await callModel(messages, model, {
|
|
1002
|
+
morphApiKey: config.morphApiKey,
|
|
1003
|
+
morphApiUrl: config.morphApiUrl,
|
|
1004
|
+
retryConfig: config.retryConfig
|
|
1005
|
+
}).catch((e) => {
|
|
826
1006
|
errors.push({ message: e instanceof Error ? e.message : String(e) });
|
|
827
1007
|
return "";
|
|
828
1008
|
});
|
|
@@ -830,13 +1010,13 @@ async function runWarpGrep(config) {
|
|
|
830
1010
|
messages.push({ role: "assistant", content: assistantContent });
|
|
831
1011
|
const toolCalls = parser.parse(assistantContent);
|
|
832
1012
|
if (toolCalls.length === 0) {
|
|
833
|
-
errors.push({ message: "No tool calls produced by the model." });
|
|
1013
|
+
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" });
|
|
834
1014
|
terminationReason = "terminated";
|
|
835
1015
|
break;
|
|
836
1016
|
}
|
|
837
1017
|
const finishCalls = toolCalls.filter((c) => c.name === "finish");
|
|
838
1018
|
const grepCalls = toolCalls.filter((c) => c.name === "grep");
|
|
839
|
-
const
|
|
1019
|
+
const listDirCalls = toolCalls.filter((c) => c.name === "list_directory");
|
|
840
1020
|
const readCalls = toolCalls.filter((c) => c.name === "read");
|
|
841
1021
|
const skipCalls = toolCalls.filter((c) => c.name === "_skip");
|
|
842
1022
|
const formatted = [];
|
|
@@ -844,69 +1024,42 @@ async function runWarpGrep(config) {
|
|
|
844
1024
|
const msg = c.arguments?.message || "Command skipped due to parsing error";
|
|
845
1025
|
formatted.push(msg);
|
|
846
1026
|
}
|
|
847
|
-
const
|
|
848
|
-
for (const c of
|
|
1027
|
+
const allPromises = [];
|
|
1028
|
+
for (const c of grepCalls) {
|
|
1029
|
+
const args = c.arguments ?? {};
|
|
1030
|
+
allPromises.push(
|
|
1031
|
+
toolGrep(provider, args).then(
|
|
1032
|
+
({ output }) => formatAgentToolOutput("grep", args, output, { isError: false }),
|
|
1033
|
+
(err) => formatAgentToolOutput("grep", args, String(err), { isError: true })
|
|
1034
|
+
)
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
for (const c of listDirCalls) {
|
|
849
1038
|
const args = c.arguments ?? {};
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
(p) => formatAgentToolOutput("
|
|
853
|
-
(err) => formatAgentToolOutput("
|
|
1039
|
+
allPromises.push(
|
|
1040
|
+
toolListDirectory(provider, args).then(
|
|
1041
|
+
(p) => formatAgentToolOutput("list_directory", args, p, { isError: false }),
|
|
1042
|
+
(err) => formatAgentToolOutput("list_directory", args, String(err), { isError: true })
|
|
854
1043
|
)
|
|
855
1044
|
);
|
|
856
1045
|
}
|
|
857
1046
|
for (const c of readCalls) {
|
|
858
1047
|
const args = c.arguments ?? {};
|
|
859
|
-
|
|
1048
|
+
allPromises.push(
|
|
860
1049
|
toolRead(provider, args).then(
|
|
861
1050
|
(p) => formatAgentToolOutput("read", args, p, { isError: false }),
|
|
862
1051
|
(err) => formatAgentToolOutput("read", args, String(err), { isError: true })
|
|
863
1052
|
)
|
|
864
1053
|
);
|
|
865
1054
|
}
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
const args = c.arguments ?? {};
|
|
870
|
-
try {
|
|
871
|
-
const grepRes = await provider.grep({ pattern: args.pattern, path: args.path });
|
|
872
|
-
if (grepRes.error) {
|
|
873
|
-
errors.push({ message: grepRes.error });
|
|
874
|
-
terminationReason = "terminated";
|
|
875
|
-
return {
|
|
876
|
-
terminationReason: "terminated",
|
|
877
|
-
messages,
|
|
878
|
-
errors
|
|
879
|
-
};
|
|
880
|
-
}
|
|
881
|
-
const rawOutput = Array.isArray(grepRes.lines) ? grepRes.lines.join("\n") : "";
|
|
882
|
-
const newMatches = parseAndFilterGrepOutput(rawOutput, grepState);
|
|
883
|
-
let formattedPayload = formatTurnGrepOutput(newMatches);
|
|
884
|
-
if (formattedPayload === "No new matches found.") {
|
|
885
|
-
formattedPayload = "no new matches";
|
|
886
|
-
}
|
|
887
|
-
formatted.push(formatAgentToolOutput("grep", args, formattedPayload, { isError: false }));
|
|
888
|
-
} catch (err) {
|
|
889
|
-
formatted.push(formatAgentToolOutput("grep", args, String(err), { isError: true }));
|
|
890
|
-
}
|
|
1055
|
+
const allResults = await Promise.all(allPromises);
|
|
1056
|
+
for (const result of allResults) {
|
|
1057
|
+
formatted.push(result);
|
|
891
1058
|
}
|
|
892
1059
|
if (formatted.length > 0) {
|
|
893
|
-
const
|
|
894
|
-
const
|
|
895
|
-
|
|
896
|
-
if (turnsRemaining === 0) {
|
|
897
|
-
turnMessage = `
|
|
898
|
-
|
|
899
|
-
[Turn ${turnsUsed}/4] This is your LAST turn. You MUST call the finish tool now.`;
|
|
900
|
-
} else if (turnsRemaining === 1) {
|
|
901
|
-
turnMessage = `
|
|
902
|
-
|
|
903
|
-
[Turn ${turnsUsed}/4] You have 1 turn remaining. Next turn you MUST call the finish tool.`;
|
|
904
|
-
} else {
|
|
905
|
-
turnMessage = `
|
|
906
|
-
|
|
907
|
-
[Turn ${turnsUsed}/4] You have ${turnsRemaining} turns remaining.`;
|
|
908
|
-
}
|
|
909
|
-
messages.push({ role: "user", content: formatted.join("\n") + turnMessage });
|
|
1060
|
+
const turnMessage = formatTurnMessage(turn, maxTurns);
|
|
1061
|
+
const contextBudget = calculateContextBudget(messages);
|
|
1062
|
+
messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
|
|
910
1063
|
}
|
|
911
1064
|
if (finishCalls.length) {
|
|
912
1065
|
const fc = finishCalls[0];
|