@morphllm/morphsdk 0.2.57 → 0.2.59

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.
Files changed (170) hide show
  1. package/dist/anthropic-CaFUHxBW.d.ts +89 -0
  2. package/dist/{chunk-6X5UOY7B.js → chunk-2CASO3ZO.js} +46 -79
  3. package/dist/chunk-2CASO3ZO.js.map +1 -0
  4. package/dist/chunk-374N3GIA.js +118 -0
  5. package/dist/chunk-374N3GIA.js.map +1 -0
  6. package/dist/chunk-3IQIT6MC.js +65 -0
  7. package/dist/chunk-3IQIT6MC.js.map +1 -0
  8. package/dist/chunk-4VGOBA2J.js +57 -0
  9. package/dist/chunk-4VGOBA2J.js.map +1 -0
  10. package/dist/chunk-527P5X2E.js +98 -0
  11. package/dist/chunk-527P5X2E.js.map +1 -0
  12. package/dist/chunk-6N6ZYZYD.js +74 -0
  13. package/dist/chunk-6N6ZYZYD.js.map +1 -0
  14. package/dist/chunk-6Y5JB4JC.js +195 -0
  15. package/dist/chunk-6Y5JB4JC.js.map +1 -0
  16. package/dist/{chunk-TICMYDII.js → chunk-APP75CBN.js} +33 -16
  17. package/dist/chunk-APP75CBN.js.map +1 -0
  18. package/dist/{chunk-QFIHUCTF.js → chunk-FN4EP3WY.js} +19 -19
  19. package/dist/chunk-FN4EP3WY.js.map +1 -0
  20. package/dist/chunk-ILJ3J5IA.js +72 -0
  21. package/dist/chunk-ILJ3J5IA.js.map +1 -0
  22. package/dist/chunk-ISWL67SF.js +1 -0
  23. package/dist/chunk-KW7OEGZK.js +9 -0
  24. package/dist/chunk-KW7OEGZK.js.map +1 -0
  25. package/dist/chunk-Q5AHGIQO.js +205 -0
  26. package/dist/chunk-Q5AHGIQO.js.map +1 -0
  27. package/dist/{chunk-OXHGFHEU.js → chunk-VJU3BRET.js} +3 -3
  28. package/dist/chunk-VJU3BRET.js.map +1 -0
  29. package/dist/{chunk-TJIUA27P.js → chunk-XT5ZO6ES.js} +9 -5
  30. package/dist/chunk-XT5ZO6ES.js.map +1 -0
  31. package/dist/{chunk-LVPVVLTI.js → chunk-YV75OQTE.js} +105 -17
  32. package/dist/chunk-YV75OQTE.js.map +1 -0
  33. package/dist/{chunk-ZJIIICRA.js → chunk-ZO4PPFCZ.js} +60 -29
  34. package/dist/chunk-ZO4PPFCZ.js.map +1 -0
  35. package/dist/{client-CFoR--IU.d.ts → client-CextMMm9.d.ts} +10 -15
  36. package/dist/client.cjs +689 -343
  37. package/dist/client.cjs.map +1 -1
  38. package/dist/client.d.ts +3 -2
  39. package/dist/client.js +15 -15
  40. package/dist/finish-kXAcUJyB.d.ts +33 -0
  41. package/dist/gemini-CE80Pbdy.d.ts +117 -0
  42. package/dist/git/client.cjs +2 -2
  43. package/dist/git/client.cjs.map +1 -1
  44. package/dist/git/client.d.ts +1 -1
  45. package/dist/git/client.js +1 -1
  46. package/dist/git/index.cjs +2 -2
  47. package/dist/git/index.cjs.map +1 -1
  48. package/dist/git/index.js +1 -1
  49. package/dist/git/types.cjs.map +1 -1
  50. package/dist/git/types.d.ts +1 -1
  51. package/dist/index.cjs +702 -343
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.ts +4 -3
  54. package/dist/index.js +17 -16
  55. package/dist/openai-Fvpqln7F.d.ts +89 -0
  56. package/dist/tools/warp_grep/agent/config.cjs +8 -4
  57. package/dist/tools/warp_grep/agent/config.cjs.map +1 -1
  58. package/dist/tools/warp_grep/agent/config.d.ts +7 -2
  59. package/dist/tools/warp_grep/agent/config.js +1 -1
  60. package/dist/tools/warp_grep/agent/formatter.cjs +32 -15
  61. package/dist/tools/warp_grep/agent/formatter.cjs.map +1 -1
  62. package/dist/tools/warp_grep/agent/formatter.d.ts +1 -1
  63. package/dist/tools/warp_grep/agent/formatter.js +1 -1
  64. package/dist/tools/warp_grep/agent/parser.cjs +104 -17
  65. package/dist/tools/warp_grep/agent/parser.cjs.map +1 -1
  66. package/dist/tools/warp_grep/agent/parser.d.ts +3 -5
  67. package/dist/tools/warp_grep/agent/parser.js +1 -3
  68. package/dist/tools/warp_grep/agent/prompt.cjs +132 -56
  69. package/dist/tools/warp_grep/agent/prompt.cjs.map +1 -1
  70. package/dist/tools/warp_grep/agent/prompt.d.ts +1 -1
  71. package/dist/tools/warp_grep/agent/prompt.js +1 -1
  72. package/dist/tools/warp_grep/agent/runner.cjs +459 -192
  73. package/dist/tools/warp_grep/agent/runner.cjs.map +1 -1
  74. package/dist/tools/warp_grep/agent/runner.d.ts +1 -0
  75. package/dist/tools/warp_grep/agent/runner.js +6 -8
  76. package/dist/tools/warp_grep/agent/types.cjs.map +1 -1
  77. package/dist/tools/warp_grep/agent/types.d.ts +9 -2
  78. package/dist/tools/warp_grep/anthropic.cjs +650 -260
  79. package/dist/tools/warp_grep/anthropic.cjs.map +1 -1
  80. package/dist/tools/warp_grep/anthropic.d.ts +4 -74
  81. package/dist/tools/warp_grep/anthropic.js +13 -15
  82. package/dist/tools/warp_grep/client.cjs +1593 -0
  83. package/dist/tools/warp_grep/client.cjs.map +1 -0
  84. package/dist/tools/warp_grep/client.d.ts +87 -0
  85. package/dist/tools/warp_grep/client.js +26 -0
  86. package/dist/tools/warp_grep/gemini.cjs +1587 -0
  87. package/dist/tools/warp_grep/gemini.cjs.map +1 -0
  88. package/dist/tools/warp_grep/gemini.d.ts +7 -0
  89. package/dist/tools/warp_grep/gemini.js +34 -0
  90. package/dist/tools/warp_grep/harness.cjs +556 -220
  91. package/dist/tools/warp_grep/harness.cjs.map +1 -1
  92. package/dist/tools/warp_grep/harness.d.ts +50 -119
  93. package/dist/tools/warp_grep/harness.js +33 -41
  94. package/dist/tools/warp_grep/harness.js.map +1 -1
  95. package/dist/tools/warp_grep/index.cjs +812 -346
  96. package/dist/tools/warp_grep/index.cjs.map +1 -1
  97. package/dist/tools/warp_grep/index.d.ts +11 -6
  98. package/dist/tools/warp_grep/index.js +43 -22
  99. package/dist/tools/warp_grep/openai.cjs +650 -258
  100. package/dist/tools/warp_grep/openai.cjs.map +1 -1
  101. package/dist/tools/warp_grep/openai.d.ts +4 -74
  102. package/dist/tools/warp_grep/openai.js +13 -13
  103. package/dist/tools/warp_grep/providers/local.cjs +66 -27
  104. package/dist/tools/warp_grep/providers/local.cjs.map +1 -1
  105. package/dist/tools/warp_grep/providers/local.d.ts +4 -9
  106. package/dist/tools/warp_grep/providers/local.js +2 -2
  107. package/dist/tools/warp_grep/providers/remote.cjs +211 -0
  108. package/dist/tools/warp_grep/providers/remote.cjs.map +1 -0
  109. package/dist/tools/warp_grep/providers/remote.d.ts +67 -0
  110. package/dist/tools/warp_grep/providers/remote.js +9 -0
  111. package/dist/tools/warp_grep/providers/types.cjs.map +1 -1
  112. package/dist/tools/warp_grep/providers/types.d.ts +7 -15
  113. package/dist/tools/warp_grep/vercel.cjs +662 -277
  114. package/dist/tools/warp_grep/vercel.cjs.map +1 -1
  115. package/dist/tools/warp_grep/vercel.d.ts +4 -51
  116. package/dist/tools/warp_grep/vercel.js +16 -14
  117. package/dist/types-a_hxdPI6.d.ts +144 -0
  118. package/dist/vercel-3yjvfmVB.d.ts +66 -0
  119. package/package.json +12 -2
  120. package/dist/chunk-6X5UOY7B.js.map +0 -1
  121. package/dist/chunk-73RQWOQC.js +0 -16
  122. package/dist/chunk-73RQWOQC.js.map +0 -1
  123. package/dist/chunk-7OQOOB3R.js +0 -1
  124. package/dist/chunk-CFF636UC.js +0 -70
  125. package/dist/chunk-CFF636UC.js.map +0 -1
  126. package/dist/chunk-EK7OQPWD.js +0 -44
  127. package/dist/chunk-EK7OQPWD.js.map +0 -1
  128. package/dist/chunk-GJ5TYNRD.js +0 -107
  129. package/dist/chunk-GJ5TYNRD.js.map +0 -1
  130. package/dist/chunk-HQO45BAJ.js +0 -14
  131. package/dist/chunk-HQO45BAJ.js.map +0 -1
  132. package/dist/chunk-IMYQOKFO.js +0 -83
  133. package/dist/chunk-IMYQOKFO.js.map +0 -1
  134. package/dist/chunk-KBQWGT5L.js +0 -77
  135. package/dist/chunk-KBQWGT5L.js.map +0 -1
  136. package/dist/chunk-LVPVVLTI.js.map +0 -1
  137. package/dist/chunk-OXHGFHEU.js.map +0 -1
  138. package/dist/chunk-QFIHUCTF.js.map +0 -1
  139. package/dist/chunk-TICMYDII.js.map +0 -1
  140. package/dist/chunk-TJIUA27P.js.map +0 -1
  141. package/dist/chunk-WETRQJGU.js +0 -129
  142. package/dist/chunk-WETRQJGU.js.map +0 -1
  143. package/dist/chunk-ZJIIICRA.js.map +0 -1
  144. package/dist/core-CpkYEi_T.d.ts +0 -158
  145. package/dist/tools/warp_grep/tools/analyse.cjs +0 -40
  146. package/dist/tools/warp_grep/tools/analyse.cjs.map +0 -1
  147. package/dist/tools/warp_grep/tools/analyse.d.ts +0 -10
  148. package/dist/tools/warp_grep/tools/analyse.js +0 -8
  149. package/dist/tools/warp_grep/tools/finish.cjs +0 -69
  150. package/dist/tools/warp_grep/tools/finish.cjs.map +0 -1
  151. package/dist/tools/warp_grep/tools/finish.d.ts +0 -10
  152. package/dist/tools/warp_grep/tools/finish.js +0 -10
  153. package/dist/tools/warp_grep/tools/grep.cjs +0 -38
  154. package/dist/tools/warp_grep/tools/grep.cjs.map +0 -1
  155. package/dist/tools/warp_grep/tools/grep.d.ts +0 -8
  156. package/dist/tools/warp_grep/tools/grep.js +0 -15
  157. package/dist/tools/warp_grep/tools/grep.js.map +0 -1
  158. package/dist/tools/warp_grep/tools/read.cjs +0 -38
  159. package/dist/tools/warp_grep/tools/read.cjs.map +0 -1
  160. package/dist/tools/warp_grep/tools/read.d.ts +0 -9
  161. package/dist/tools/warp_grep/tools/read.js +0 -8
  162. package/dist/tools/warp_grep/utils/format.cjs +0 -42
  163. package/dist/tools/warp_grep/utils/format.cjs.map +0 -1
  164. package/dist/tools/warp_grep/utils/format.d.ts +0 -4
  165. package/dist/tools/warp_grep/utils/format.js +0 -18
  166. package/dist/tools/warp_grep/utils/format.js.map +0 -1
  167. /package/dist/{chunk-7OQOOB3R.js.map → chunk-ISWL67SF.js.map} +0 -0
  168. /package/dist/tools/warp_grep/{tools/analyse.js.map → client.js.map} +0 -0
  169. /package/dist/tools/warp_grep/{tools/finish.js.map → gemini.js.map} +0 -0
  170. /package/dist/tools/warp_grep/{tools/read.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
- // Give the model freedom; failsafe cap to prevent infinite loops
40
- MAX_ROUNDS: 10,
41
- TIMEOUT_MS: 3e4
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 query.
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
- <workflow>
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 query specificity)
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 \`finish\` early using fewer turns, but quality over speed.
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
- <tools>
140
- ### \`analyse <path> [pattern]\`
141
- Directory tree or file search. Shows structure of a path, optionally filtered by regex pattern.
142
- - \`path\`: Required. Directory or file path (use \`.\` for repo root)
143
- - \`pattern\`: Optional regex to filter results
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
- analyse .
148
- analyse src/api
149
- analyse . ".*\\.ts$"
150
- analyse src "test.*"
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 <path>[:start-end]\`
154
- Read file contents. Line range is 1-based, inclusive.
170
+ ### \`read\`
171
+ Read file contents. Supports multiple line ranges.
155
172
  - Returns numbered lines for easy reference
156
- - Omit range to read entire file
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 src/main.py
161
- read src/db/conn.py:10-50
162
- read package.json:1-20
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 '<pattern>' <path>\`
166
- Ripgrep search. Finds pattern matches across files.
167
- - \`'<pattern>'\`: Required. Regex pattern wrapped in single quotes
168
- - \`<path>\`: Required. Directory or file to search (use \`.\` for repo root)
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 'class.*Service' src/
173
- grep 'def authenticate' .
174
- grep 'import.*from' src/components/
175
- grep 'TODO' .
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 <file1:ranges> [file2:ranges ...]\`
179
- Submit final answer with all relevant code locations.
180
- - Include generous line ranges\u2014don't be stingy with context
181
- - Ranges are comma-separated: \`file.py:10-30,50-60\`
182
- - ALWAYS include import statements at the top of files (usually lines 1-20)
183
- - If code spans multiple files, include ALL of them
184
- - Small files can be returned in full
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 src/auth.py:1-15,25-50,75-80 src/models/user.py:1-10,20-45
189
- finish src/index.ts:1-100
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 query:**
245
+ **Before your first tool call, classify the search_string:**
195
246
 
196
- | Query Type | Turn 1 Strategy | Early Finish? |
197
- |------------|-----------------|---------------|
198
- | **Specific** (function name, error string, unique identifier) | 8 parallel greps on likely paths | Often by turn 2 |
199
- | **Conceptual** (how does X work, where is Y handled) | analyse + 2-3 broad greps | Rarely early |
200
- | **Exploratory** (find all tests, list API endpoints) | analyse at multiple depths | Usually needs 3 turns |
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 analyse + 7 greps\u2014orient and search simultaneously
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
- - Query classification (specific/conceptual/exploratory)
214
- - Confidence estimate (can I finish in 1-2 turns?)
215
- - This turn's parallel strategy
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 tool calls wrapped in \`<tool_call>...</tool_call>\` tags, one per line.
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 query about authentication. I'll grep for auth-related patterns.
224
- High confidence I can finish in 2 turns if I find the auth module.
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
- <tool_call>grep 'authenticate' src/</tool_call>
228
- <tool_call>grep 'login' src/</tool_call>
229
- <tool_call>analyse src/auth</tool_call>
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 = ["analyse", "grep", "read", "finish"];
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 "analyse":
307
- this.handleAnalyse(parts, line, commands);
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(([path2, ranges]) => ({
326
- path: path2,
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
- handleAnalyse(parts, rawLine, commands) {
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: analyse <path> [pattern]. Example: analyse src/`
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 path2 = parts[1];
541
+ const path3 = parts[1];
366
542
  const pattern = parts[2]?.replace(/^"|"$/g, "") ?? null;
367
- commands.push({ name: "analyse", arguments: { path: path2, pattern } });
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/read.ts
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/analyse.ts
451
- async function toolAnalyse(provider, args) {
452
- const list = await provider.analyse({
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 ?? 100,
456
- maxDepth: args.maxDepth ?? 2
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
- return list.map((e) => `${" ".repeat(e.depth)}- ${e.type === "dir" ? "[D]" : "[F]"} ${e.name}`).join("\n");
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 "analyse":
553
- return this.formatAnalyse(safeArgs, payload, isError);
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 path2 = this.asString(args.path) || "...";
567
- return `<file path="${path2}">
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
- </file>`;
816
+ </read>`;
570
817
  }
571
- formatAnalyse(args, payload, isError) {
572
- const path2 = this.asString(args.path) || ".";
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
- return `<analyse_results path="${path2}" status="error">
575
- ${payload}
576
- </analyse_results>`;
826
+ attributes.push('status="error"');
577
827
  }
578
- return `<analyse_results path="${path2}">
828
+ return `<list_directory ${attributes.join(" ")}>
579
829
  ${payload}
580
- </analyse_results>`;
830
+ </list_directory>`;
581
831
  }
582
832
  formatGrep(args, payload, isError) {
583
833
  const pattern = this.asString(args.pattern);
584
- const path2 = this.asString(args.path);
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 (path2 !== void 0) {
590
- attributes.push(`path="${path2}"`);
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 `<grep_output${attrText}>
850
+ return `<grep${attrText}>
597
851
  ${payload}
598
- </grep_output>`;
852
+ </grep>`;
599
853
  }
600
854
  asString(value) {
601
855
  if (value === null || value === void 0) {
@@ -609,66 +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/tools/finish.ts
613
- async function readFinishFiles(repoRoot, files, reader) {
614
- const out = [];
615
- for (const f of files) {
616
- const ranges = mergeRanges(f.lines);
617
- const chunks = [];
618
- for (const [s, e] of ranges) {
619
- const lines = await reader(f.path, s, e);
620
- chunks.push(lines.join("\n"));
621
- }
622
- out.push({ path: f.path, ranges, content: chunks.join("\n") });
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`;
623
874
  }
624
- return out;
875
+ return `
876
+ You have used ${turnsUsed} turn${turnsUsed === 1 ? "" : "s"} and have ${turnsRemaining} remaining`;
625
877
  }
626
- function mergeRanges(ranges) {
627
- if (!ranges.length) return [];
628
- const sorted = [...ranges].sort((a, b) => a[0] - b[0]);
629
- const merged = [];
630
- let [cs, ce] = sorted[0];
631
- for (let i = 1; i < sorted.length; i++) {
632
- const [s, e] = sorted[i];
633
- if (s <= ce + 1) {
634
- ce = Math.max(ce, e);
635
- } else {
636
- merged.push([cs, ce]);
637
- cs = s;
638
- ce = e;
639
- }
640
- }
641
- merged.push([cs, ce]);
642
- return merged;
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>`;
643
885
  }
644
-
645
- // tools/warp_grep/agent/runner.ts
646
- var import_path = __toESM(require("path"), 1);
647
- var parser = new LLMResponseParser();
648
886
  async function buildInitialState(repoRoot, query, provider) {
649
887
  try {
650
- const entries = await provider.analyse({ path: ".", maxResults: 100 });
651
- const dirs = entries.filter((e) => e.type === "dir").map((d) => d.name).slice(0, 50);
652
- const files = entries.filter((e) => e.type === "file").map((f) => f.name).slice(0, 50);
653
- const parts = [
654
- `<repo_root>${repoRoot}</repo_root>`,
655
- `<top_dirs>${dirs.join(", ")}</top_dirs>`,
656
- `<top_files>${files.join(", ")}</top_files>`
657
- ];
658
- return parts.join("\n");
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>`;
659
908
  } catch {
660
- return `<repo_root>${repoRoot}</repo_root>`;
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
+ }
918
+ }
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;
923
+ }
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);
933
+ }
661
934
  }
935
+ for (const idx of userIndices) {
936
+ if (getTotalChars() <= maxChars) {
937
+ break;
938
+ }
939
+ if (messages[idx].content !== TRUNCATED_MARKER) {
940
+ messages[idx] = { role: "user", content: TRUNCATED_MARKER };
941
+ }
942
+ }
943
+ return messages;
662
944
  }
663
- async function callModel(messages, model, apiKey) {
664
- const api = "https://api.morphllm.com/v1/chat/completions";
945
+
946
+ // tools/warp_grep/agent/runner.ts
947
+ var import_path2 = __toESM(require("path"), 1);
948
+ var parser = new LLMResponseParser();
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 || "";
665
953
  const fetchPromise = fetchWithRetry(
666
- api,
954
+ `${baseUrl}/v1/chat/completions`,
667
955
  {
668
956
  method: "POST",
669
957
  headers: {
670
958
  "Content-Type": "application/json",
671
- Authorization: `Bearer ${apiKey || process.env.MORPH_API_KEY || ""}`
959
+ Authorization: `Bearer ${apiKey}`
672
960
  },
673
961
  body: JSON.stringify({
674
962
  model,
@@ -677,10 +965,15 @@ async function callModel(messages, model, apiKey) {
677
965
  messages
678
966
  })
679
967
  },
680
- {}
968
+ options.retryConfig
681
969
  );
682
970
  const resp = await withTimeout(fetchPromise, AGENT_CONFIG.TIMEOUT_MS, "morph-warp-grep request timed out");
683
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
+ }
684
977
  const t = await resp.text();
685
978
  throw new Error(`morph-warp-grep error ${resp.status}: ${t}`);
686
979
  }
@@ -692,22 +985,24 @@ async function callModel(messages, model, apiKey) {
692
985
  return content;
693
986
  }
694
987
  async function runWarpGrep(config) {
695
- const repoRoot = import_path.default.resolve(config.repoRoot || process.cwd());
988
+ const repoRoot = import_path2.default.resolve(config.repoRoot || process.cwd());
696
989
  const messages = [];
697
- const systemMessage = { role: "system", content: getSystemPrompt() };
698
- messages.push(systemMessage);
699
- const queryContent = `<query>${config.query}</query>`;
700
- messages.push({ role: "user", content: queryContent });
990
+ messages.push({ role: "system", content: getSystemPrompt() });
701
991
  const initialState = await buildInitialState(repoRoot, config.query, config.provider);
702
992
  messages.push({ role: "user", content: initialState });
703
- const maxRounds = AGENT_CONFIG.MAX_ROUNDS;
993
+ const maxTurns = AGENT_CONFIG.MAX_TURNS;
704
994
  const model = config.model || DEFAULT_MODEL;
705
995
  const provider = config.provider;
706
996
  const errors = [];
707
997
  let finishMeta;
708
998
  let terminationReason = "terminated";
709
- for (let round = 1; round <= maxRounds; round += 1) {
710
- const assistantContent = await callModel(messages, model, config.apiKey).catch((e) => {
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) => {
711
1006
  errors.push({ message: e instanceof Error ? e.message : String(e) });
712
1007
  return "";
713
1008
  });
@@ -715,13 +1010,13 @@ async function runWarpGrep(config) {
715
1010
  messages.push({ role: "assistant", content: assistantContent });
716
1011
  const toolCalls = parser.parse(assistantContent);
717
1012
  if (toolCalls.length === 0) {
718
- 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" });
719
1014
  terminationReason = "terminated";
720
1015
  break;
721
1016
  }
722
1017
  const finishCalls = toolCalls.filter((c) => c.name === "finish");
723
1018
  const grepCalls = toolCalls.filter((c) => c.name === "grep");
724
- const analyseCalls = toolCalls.filter((c) => c.name === "analyse");
1019
+ const listDirCalls = toolCalls.filter((c) => c.name === "list_directory");
725
1020
  const readCalls = toolCalls.filter((c) => c.name === "read");
726
1021
  const skipCalls = toolCalls.filter((c) => c.name === "_skip");
727
1022
  const formatted = [];
@@ -733,24 +1028,18 @@ async function runWarpGrep(config) {
733
1028
  for (const c of grepCalls) {
734
1029
  const args = c.arguments ?? {};
735
1030
  allPromises.push(
736
- provider.grep({ pattern: args.pattern, path: args.path }).then(
737
- (grepRes) => {
738
- if (grepRes.error) {
739
- return { terminate: true, error: grepRes.error };
740
- }
741
- const output = grepRes.lines.join("\n") || "no matches";
742
- return formatAgentToolOutput("grep", args, output, { isError: false });
743
- },
1031
+ toolGrep(provider, args).then(
1032
+ ({ output }) => formatAgentToolOutput("grep", args, output, { isError: false }),
744
1033
  (err) => formatAgentToolOutput("grep", args, String(err), { isError: true })
745
1034
  )
746
1035
  );
747
1036
  }
748
- for (const c of analyseCalls) {
1037
+ for (const c of listDirCalls) {
749
1038
  const args = c.arguments ?? {};
750
1039
  allPromises.push(
751
- toolAnalyse(provider, args).then(
752
- (p) => formatAgentToolOutput("analyse", args, p, { isError: false }),
753
- (err) => formatAgentToolOutput("analyse", args, String(err), { isError: true })
1040
+ toolListDirectory(provider, args).then(
1041
+ (p) => formatAgentToolOutput("list_directory", args, p, { isError: false }),
1042
+ (err) => formatAgentToolOutput("list_directory", args, String(err), { isError: true })
754
1043
  )
755
1044
  );
756
1045
  }
@@ -765,34 +1054,12 @@ async function runWarpGrep(config) {
765
1054
  }
766
1055
  const allResults = await Promise.all(allPromises);
767
1056
  for (const result of allResults) {
768
- if (typeof result === "object" && "terminate" in result) {
769
- errors.push({ message: result.error });
770
- return {
771
- terminationReason: "terminated",
772
- messages,
773
- errors
774
- };
775
- }
776
1057
  formatted.push(result);
777
1058
  }
778
1059
  if (formatted.length > 0) {
779
- const turnsUsed = round;
780
- const turnsRemaining = 4 - turnsUsed;
781
- let turnMessage;
782
- if (turnsRemaining === 0) {
783
- turnMessage = `
784
-
785
- [Turn ${turnsUsed}/4] This is your LAST turn. You MUST call the finish tool now.`;
786
- } else if (turnsRemaining === 1) {
787
- turnMessage = `
788
-
789
- [Turn ${turnsUsed}/4] You have 1 turn remaining. Next turn you MUST call the finish tool.`;
790
- } else {
791
- turnMessage = `
792
-
793
- [Turn ${turnsUsed}/4] You have ${turnsRemaining} turns remaining.`;
794
- }
795
- 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 });
796
1063
  }
797
1064
  if (finishCalls.length) {
798
1065
  const fc = finishCalls[0];