@morphllm/morphsdk 0.2.145 → 0.2.146

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 (205) hide show
  1. package/dist/{chunk-HBIW2XV2.js → chunk-4PBUB77N.js} +2 -2
  2. package/dist/{chunk-SUE4GYA2.js → chunk-BDHKL3MT.js} +2 -2
  3. package/dist/{chunk-S54SPKX3.js → chunk-BIQ7234U.js} +2 -2
  4. package/dist/{chunk-I3J46TSB.js → chunk-DKODF3YG.js} +4 -5
  5. package/dist/chunk-DKODF3YG.js.map +1 -0
  6. package/dist/{chunk-MRPASJBX.js → chunk-E45FW5EK.js} +2 -2
  7. package/dist/{chunk-BXRJYLRS.js → chunk-E4YKEKGW.js} +2 -2
  8. package/dist/{chunk-G23BI5CQ.js → chunk-EU7OLX4Z.js} +2 -2
  9. package/dist/chunk-EUHNJMWL.js +409 -0
  10. package/dist/chunk-EUHNJMWL.js.map +1 -0
  11. package/dist/{chunk-HE7K2QNQ.js → chunk-FBOJJ3UY.js} +17 -17
  12. package/dist/{chunk-HYRHI2UL.js → chunk-FIVYDIHX.js} +1 -1
  13. package/dist/{chunk-GXM3G7Z4.js → chunk-FYO46OT6.js} +2 -2
  14. package/dist/{chunk-GHPQYSSF.js → chunk-GJUB3ECP.js} +2 -2
  15. package/dist/{chunk-4Y2NM6JD.js → chunk-HZOTLGJH.js} +2 -42
  16. package/dist/chunk-HZOTLGJH.js.map +1 -0
  17. package/dist/{chunk-MTJ3PR4M.js → chunk-I7SFRYTX.js} +2 -2
  18. package/dist/{chunk-PX7ODEML.js → chunk-J2HIK4GB.js} +2 -2
  19. package/dist/{chunk-RZXS4ADX.js → chunk-JSWNBCGS.js} +2 -2
  20. package/dist/{chunk-GXCWKYGU.js → chunk-KYKRRF7E.js} +2 -2
  21. package/dist/{chunk-N7TTZIBK.js → chunk-MMBQKN4G.js} +2 -2
  22. package/dist/{chunk-B3AKP3RA.js → chunk-NF2QWJDY.js} +2 -31
  23. package/dist/chunk-NF2QWJDY.js.map +1 -0
  24. package/dist/{chunk-JMUAQQJU.js → chunk-NKUSUSVI.js} +3 -3
  25. package/dist/{chunk-VRV5UYTN.js → chunk-OV57JBMB.js} +2 -2
  26. package/dist/{chunk-EPIOAODF.js → chunk-Q36MNOFA.js} +2 -2
  27. package/dist/{chunk-JRBU4UNP.js → chunk-QRSWXP4K.js} +2 -2
  28. package/dist/{chunk-KELRCMA6.js → chunk-SJYAKVSS.js} +2 -2
  29. package/dist/{chunk-KELRCMA6.js.map → chunk-SJYAKVSS.js.map} +1 -1
  30. package/dist/{chunk-IRWHN55G.js → chunk-T564HFSH.js} +1 -1
  31. package/dist/{chunk-6CFKWZK3.js → chunk-UVNENJ6H.js} +3 -3
  32. package/dist/{chunk-5FCXLQJU.js → chunk-UYPWKQKV.js} +2 -2
  33. package/dist/{chunk-BAF33L6C.js → chunk-V73GO5AJ.js} +2 -2
  34. package/dist/chunk-VCKJ22DX.js +131 -0
  35. package/dist/chunk-VCKJ22DX.js.map +1 -0
  36. package/dist/{chunk-XL7R3XN5.js → chunk-VZ6VYRQB.js} +2 -2
  37. package/dist/{chunk-4LWMPKSB.js → chunk-YIETFYCL.js} +44 -71
  38. package/dist/chunk-YIETFYCL.js.map +1 -0
  39. package/dist/client.cjs +438 -426
  40. package/dist/client.cjs.map +1 -1
  41. package/dist/client.js +27 -26
  42. package/dist/edge.cjs +1 -1
  43. package/dist/edge.cjs.map +1 -1
  44. package/dist/edge.js +4 -4
  45. package/dist/{finish-Ddj1MPGt.d.ts → finish-DBKuo8yj.d.ts} +1 -1
  46. package/dist/index.cjs +438 -445
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.js +29 -29
  49. package/dist/modelrouter/core.cjs +1 -1
  50. package/dist/modelrouter/core.cjs.map +1 -1
  51. package/dist/modelrouter/core.js +3 -3
  52. package/dist/modelrouter/index.cjs +1 -1
  53. package/dist/modelrouter/index.cjs.map +1 -1
  54. package/dist/modelrouter/index.js +3 -3
  55. package/dist/subagents/anthropic.cjs +434 -422
  56. package/dist/subagents/anthropic.cjs.map +1 -1
  57. package/dist/subagents/anthropic.js +9 -8
  58. package/dist/subagents/vercel.cjs +434 -422
  59. package/dist/subagents/vercel.cjs.map +1 -1
  60. package/dist/subagents/vercel.js +9 -8
  61. package/dist/tools/browser/anthropic.cjs +1 -1
  62. package/dist/tools/browser/anthropic.cjs.map +1 -1
  63. package/dist/tools/browser/anthropic.js +5 -5
  64. package/dist/tools/browser/core.cjs +1 -1
  65. package/dist/tools/browser/core.cjs.map +1 -1
  66. package/dist/tools/browser/core.js +4 -4
  67. package/dist/tools/browser/index.cjs +1 -1
  68. package/dist/tools/browser/index.cjs.map +1 -1
  69. package/dist/tools/browser/index.js +7 -7
  70. package/dist/tools/browser/openai.cjs +1 -1
  71. package/dist/tools/browser/openai.cjs.map +1 -1
  72. package/dist/tools/browser/openai.js +5 -5
  73. package/dist/tools/browser/profiles/core.cjs +1 -1
  74. package/dist/tools/browser/profiles/core.cjs.map +1 -1
  75. package/dist/tools/browser/profiles/core.js +3 -3
  76. package/dist/tools/browser/profiles/index.cjs +1 -1
  77. package/dist/tools/browser/profiles/index.cjs.map +1 -1
  78. package/dist/tools/browser/profiles/index.js +3 -3
  79. package/dist/tools/browser/vercel.cjs +1 -1
  80. package/dist/tools/browser/vercel.cjs.map +1 -1
  81. package/dist/tools/browser/vercel.js +5 -5
  82. package/dist/tools/codebase_search/anthropic.cjs +1 -1
  83. package/dist/tools/codebase_search/anthropic.cjs.map +1 -1
  84. package/dist/tools/codebase_search/anthropic.js +4 -4
  85. package/dist/tools/codebase_search/core.cjs +1 -1
  86. package/dist/tools/codebase_search/core.cjs.map +1 -1
  87. package/dist/tools/codebase_search/core.js +3 -3
  88. package/dist/tools/codebase_search/index.cjs +1 -1
  89. package/dist/tools/codebase_search/index.cjs.map +1 -1
  90. package/dist/tools/codebase_search/index.js +6 -6
  91. package/dist/tools/codebase_search/openai.cjs +1 -1
  92. package/dist/tools/codebase_search/openai.cjs.map +1 -1
  93. package/dist/tools/codebase_search/openai.js +4 -4
  94. package/dist/tools/codebase_search/vercel.cjs +1 -1
  95. package/dist/tools/codebase_search/vercel.cjs.map +1 -1
  96. package/dist/tools/codebase_search/vercel.js +4 -4
  97. package/dist/tools/fastapply/anthropic.cjs +1 -1
  98. package/dist/tools/fastapply/anthropic.cjs.map +1 -1
  99. package/dist/tools/fastapply/anthropic.js +4 -4
  100. package/dist/tools/fastapply/apply.cjs +1 -1
  101. package/dist/tools/fastapply/apply.cjs.map +1 -1
  102. package/dist/tools/fastapply/apply.js +2 -2
  103. package/dist/tools/fastapply/core.cjs +1 -1
  104. package/dist/tools/fastapply/core.cjs.map +1 -1
  105. package/dist/tools/fastapply/core.js +3 -3
  106. package/dist/tools/fastapply/index.cjs +1 -1
  107. package/dist/tools/fastapply/index.cjs.map +1 -1
  108. package/dist/tools/fastapply/index.js +6 -6
  109. package/dist/tools/fastapply/openai.cjs +1 -1
  110. package/dist/tools/fastapply/openai.cjs.map +1 -1
  111. package/dist/tools/fastapply/openai.js +4 -4
  112. package/dist/tools/fastapply/vercel.cjs +1 -1
  113. package/dist/tools/fastapply/vercel.cjs.map +1 -1
  114. package/dist/tools/fastapply/vercel.js +4 -4
  115. package/dist/tools/index.cjs +1 -1
  116. package/dist/tools/index.cjs.map +1 -1
  117. package/dist/tools/index.js +6 -6
  118. package/dist/tools/utils/resilience.cjs +1 -1
  119. package/dist/tools/utils/resilience.cjs.map +1 -1
  120. package/dist/tools/utils/resilience.js +2 -2
  121. package/dist/tools/warp_grep/agent/config.cjs +3 -4
  122. package/dist/tools/warp_grep/agent/config.cjs.map +1 -1
  123. package/dist/tools/warp_grep/agent/config.d.ts +1 -2
  124. package/dist/tools/warp_grep/agent/config.js +1 -1
  125. package/dist/tools/warp_grep/agent/parser.cjs +121 -52
  126. package/dist/tools/warp_grep/agent/parser.cjs.map +1 -1
  127. package/dist/tools/warp_grep/agent/parser.d.ts +5 -12
  128. package/dist/tools/warp_grep/agent/parser.js +3 -7
  129. package/dist/tools/warp_grep/agent/runner.cjs +416 -335
  130. package/dist/tools/warp_grep/agent/runner.cjs.map +1 -1
  131. package/dist/tools/warp_grep/agent/runner.d.ts +3 -6
  132. package/dist/tools/warp_grep/agent/runner.js +6 -5
  133. package/dist/tools/warp_grep/agent/types.cjs.map +1 -1
  134. package/dist/tools/warp_grep/agent/types.d.ts +3 -22
  135. package/dist/tools/warp_grep/anthropic.cjs +434 -422
  136. package/dist/tools/warp_grep/anthropic.cjs.map +1 -1
  137. package/dist/tools/warp_grep/anthropic.js +9 -8
  138. package/dist/tools/warp_grep/client.cjs +434 -422
  139. package/dist/tools/warp_grep/client.cjs.map +1 -1
  140. package/dist/tools/warp_grep/client.js +8 -7
  141. package/dist/tools/warp_grep/gemini.cjs +434 -422
  142. package/dist/tools/warp_grep/gemini.cjs.map +1 -1
  143. package/dist/tools/warp_grep/gemini.js +8 -7
  144. package/dist/tools/warp_grep/gemini.js.map +1 -1
  145. package/dist/tools/warp_grep/harness.cjs +176 -164
  146. package/dist/tools/warp_grep/harness.cjs.map +1 -1
  147. package/dist/tools/warp_grep/harness.d.ts +38 -17
  148. package/dist/tools/warp_grep/harness.js +14 -15
  149. package/dist/tools/warp_grep/harness.js.map +1 -1
  150. package/dist/tools/warp_grep/index.cjs +434 -441
  151. package/dist/tools/warp_grep/index.cjs.map +1 -1
  152. package/dist/tools/warp_grep/index.d.ts +1 -1
  153. package/dist/tools/warp_grep/index.js +10 -10
  154. package/dist/tools/warp_grep/openai.cjs +434 -422
  155. package/dist/tools/warp_grep/openai.cjs.map +1 -1
  156. package/dist/tools/warp_grep/openai.js +9 -8
  157. package/dist/tools/warp_grep/providers/local.cjs +2 -43
  158. package/dist/tools/warp_grep/providers/local.cjs.map +1 -1
  159. package/dist/tools/warp_grep/providers/local.d.ts +1 -5
  160. package/dist/tools/warp_grep/providers/local.js +2 -2
  161. package/dist/tools/warp_grep/providers/remote.cjs +2 -32
  162. package/dist/tools/warp_grep/providers/remote.cjs.map +1 -1
  163. package/dist/tools/warp_grep/providers/remote.d.ts +1 -9
  164. package/dist/tools/warp_grep/providers/remote.js +2 -2
  165. package/dist/tools/warp_grep/providers/types.cjs.map +1 -1
  166. package/dist/tools/warp_grep/providers/types.d.ts +1 -14
  167. package/dist/tools/warp_grep/vercel.cjs +434 -422
  168. package/dist/tools/warp_grep/vercel.cjs.map +1 -1
  169. package/dist/tools/warp_grep/vercel.js +9 -8
  170. package/dist/version.cjs +1 -1
  171. package/dist/version.cjs.map +1 -1
  172. package/dist/version.js +1 -1
  173. package/package.json +1 -1
  174. package/dist/chunk-4LWMPKSB.js.map +0 -1
  175. package/dist/chunk-4Y2NM6JD.js.map +0 -1
  176. package/dist/chunk-B3AKP3RA.js.map +0 -1
  177. package/dist/chunk-CMSHXALI.js +0 -60
  178. package/dist/chunk-CMSHXALI.js.map +0 -1
  179. package/dist/chunk-I3J46TSB.js.map +0 -1
  180. package/dist/chunk-OPEQQGST.js +0 -396
  181. package/dist/chunk-OPEQQGST.js.map +0 -1
  182. /package/dist/{chunk-HBIW2XV2.js.map → chunk-4PBUB77N.js.map} +0 -0
  183. /package/dist/{chunk-SUE4GYA2.js.map → chunk-BDHKL3MT.js.map} +0 -0
  184. /package/dist/{chunk-S54SPKX3.js.map → chunk-BIQ7234U.js.map} +0 -0
  185. /package/dist/{chunk-MRPASJBX.js.map → chunk-E45FW5EK.js.map} +0 -0
  186. /package/dist/{chunk-BXRJYLRS.js.map → chunk-E4YKEKGW.js.map} +0 -0
  187. /package/dist/{chunk-G23BI5CQ.js.map → chunk-EU7OLX4Z.js.map} +0 -0
  188. /package/dist/{chunk-HE7K2QNQ.js.map → chunk-FBOJJ3UY.js.map} +0 -0
  189. /package/dist/{chunk-HYRHI2UL.js.map → chunk-FIVYDIHX.js.map} +0 -0
  190. /package/dist/{chunk-GXM3G7Z4.js.map → chunk-FYO46OT6.js.map} +0 -0
  191. /package/dist/{chunk-GHPQYSSF.js.map → chunk-GJUB3ECP.js.map} +0 -0
  192. /package/dist/{chunk-MTJ3PR4M.js.map → chunk-I7SFRYTX.js.map} +0 -0
  193. /package/dist/{chunk-PX7ODEML.js.map → chunk-J2HIK4GB.js.map} +0 -0
  194. /package/dist/{chunk-RZXS4ADX.js.map → chunk-JSWNBCGS.js.map} +0 -0
  195. /package/dist/{chunk-GXCWKYGU.js.map → chunk-KYKRRF7E.js.map} +0 -0
  196. /package/dist/{chunk-N7TTZIBK.js.map → chunk-MMBQKN4G.js.map} +0 -0
  197. /package/dist/{chunk-JMUAQQJU.js.map → chunk-NKUSUSVI.js.map} +0 -0
  198. /package/dist/{chunk-VRV5UYTN.js.map → chunk-OV57JBMB.js.map} +0 -0
  199. /package/dist/{chunk-EPIOAODF.js.map → chunk-Q36MNOFA.js.map} +0 -0
  200. /package/dist/{chunk-JRBU4UNP.js.map → chunk-QRSWXP4K.js.map} +0 -0
  201. /package/dist/{chunk-IRWHN55G.js.map → chunk-T564HFSH.js.map} +0 -0
  202. /package/dist/{chunk-6CFKWZK3.js.map → chunk-UVNENJ6H.js.map} +0 -0
  203. /package/dist/{chunk-5FCXLQJU.js.map → chunk-UYPWKQKV.js.map} +0 -0
  204. /package/dist/{chunk-BAF33L6C.js.map → chunk-V73GO5AJ.js.map} +0 -0
  205. /package/dist/{chunk-XL7R3XN5.js.map → chunk-VZ6VYRQB.js.map} +0 -0
@@ -42,12 +42,11 @@ var parseEnvTimeout = (envValue, defaultMs) => {
42
42
  return isNaN(parsed) || parsed <= 0 ? defaultMs : parsed;
43
43
  };
44
44
  var AGENT_CONFIG = {
45
- MAX_TURNS: 6,
45
+ MAX_TURNS: 4,
46
46
  /** Default timeout for model calls. Can be overridden via MORPH_WARP_GREP_TIMEOUT env var (in ms) */
47
47
  TIMEOUT_MS: parseEnvTimeout(process.env.MORPH_WARP_GREP_TIMEOUT, 3e4),
48
- MAX_CONTEXT_CHARS: 321600,
48
+ MAX_CONTEXT_CHARS: 54e4,
49
49
  MAX_OUTPUT_LINES: 200,
50
- MAX_LIST_RESULTS: 500,
51
50
  MAX_READ_LINES: 800,
52
51
  MAX_LIST_DEPTH: 3,
53
52
  LIST_TIMEOUT_MS: 2e3
@@ -132,61 +131,134 @@ var BUILTIN_EXCLUDES = [
132
131
  ".*"
133
132
  ];
134
133
  var DEFAULT_EXCLUDES = (process.env.MORPH_WARP_GREP_EXCLUDE || "").split(",").map((s) => s.trim()).filter(Boolean).concat(BUILTIN_EXCLUDES);
135
- var DEFAULT_MODEL = "morph-warp-grep-v2.1";
134
+ var DEFAULT_MODEL = "morph-warp-grep-v2";
136
135
 
137
136
  // tools/warp_grep/agent/parser.ts
138
- function parseReadLines(linesStr) {
139
- const ranges = [];
140
- for (const rangeStr of linesStr.split(",")) {
141
- const trimmed = rangeStr.trim();
142
- if (!trimmed) continue;
143
- const parts = trimmed.split("-").map((v) => parseInt(v.trim(), 10));
144
- if (parts.length >= 2 && Number.isFinite(parts[0]) && Number.isFinite(parts[1])) {
145
- ranges.push([parts[0], parts[1]]);
146
- } else if (Number.isFinite(parts[0])) {
147
- ranges.push([parts[0], parts[0]]);
148
- }
149
- }
150
- if (ranges.length === 1) return { start: ranges[0][0], end: ranges[0][1] };
151
- if (ranges.length > 1) return { lines: ranges };
152
- return {};
137
+ var VALID_COMMANDS = ["list_directory", "ripgrep", "read", "finish"];
138
+ function isValidCommand(name) {
139
+ return VALID_COMMANDS.includes(name);
153
140
  }
154
- function parseFinishFiles(filesStr) {
155
- const files = [];
156
- for (const line of filesStr.trim().split(/\s+/)) {
157
- const trimmed = line.trim();
158
- if (!trimmed) continue;
159
- const colonIdx = trimmed.indexOf(":");
160
- if (colonIdx === -1) {
161
- files.push({ path: trimmed, lines: "*" });
162
- continue;
141
+ function parseQwen3ToolCalls(text) {
142
+ const tools = [];
143
+ const toolCallRegex = /<tool_call>\s*<function=([a-z_][a-z0-9_]*)>([\s\S]*?)<\/function>\s*<\/tool_call>/gi;
144
+ let match;
145
+ while ((match = toolCallRegex.exec(text)) !== null) {
146
+ const funcName = match[1].toLowerCase();
147
+ const body = match[2];
148
+ if (!isValidCommand(funcName)) continue;
149
+ const params = {};
150
+ const paramRegex = /<parameter=([a-z_][a-z0-9_]*)>([\s\S]*?)<\/parameter>/gi;
151
+ let paramMatch;
152
+ while ((paramMatch = paramRegex.exec(body)) !== null) {
153
+ params[paramMatch[1].toLowerCase()] = paramMatch[2].trim();
163
154
  }
164
- const filePath = trimmed.slice(0, colonIdx);
165
- const rangesPart = trimmed.slice(colonIdx + 1);
166
- if (!rangesPart.trim() || rangesPart.trim() === "*") {
167
- files.push({ path: filePath, lines: "*" });
168
- continue;
169
- }
170
- const ranges = [];
171
- for (const rangeStr of rangesPart.split(",")) {
172
- const rt = rangeStr.trim();
173
- if (!rt) continue;
174
- const parts = rt.split("-").map((v) => parseInt(v.trim(), 10));
175
- if (parts.length >= 2 && Number.isFinite(parts[0]) && Number.isFinite(parts[1])) {
176
- ranges.push([parts[0], parts[1]]);
177
- } else if (Number.isFinite(parts[0])) {
178
- ranges.push([parts[0], parts[0]]);
155
+ if (funcName === "ripgrep") {
156
+ const pattern = params.pattern;
157
+ if (!pattern) continue;
158
+ const args = {
159
+ pattern,
160
+ path: params.path || ".",
161
+ ...params.glob && { glob: params.glob },
162
+ ...params.context_lines && { context_lines: parseInt(params.context_lines, 10) },
163
+ ...params.case_sensitive && { case_sensitive: params.case_sensitive === "true" }
164
+ };
165
+ tools.push({ name: "grep", arguments: args });
166
+ } else if (funcName === "list_directory") {
167
+ const command = params.command;
168
+ const directPath = params.path;
169
+ let dirPath = directPath || ".";
170
+ if (!directPath && command) {
171
+ const tokens = command.trim().split(/\s+/);
172
+ const pathTokens = tokens.slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("|") && !t.startsWith("\\("));
173
+ if (pathTokens.length > 0) {
174
+ dirPath = pathTokens[0];
175
+ }
176
+ }
177
+ tools.push({ name: "list_directory", arguments: { path: dirPath, pattern: params.pattern || null } });
178
+ } else if (funcName === "read") {
179
+ const filePath = params.path;
180
+ if (!filePath) continue;
181
+ const args = { path: filePath };
182
+ const linesStr = params.lines;
183
+ if (linesStr) {
184
+ const ranges = [];
185
+ for (const rangeStr of linesStr.split(",")) {
186
+ const trimmed = rangeStr.trim();
187
+ if (!trimmed) continue;
188
+ const [s, e] = trimmed.split("-").map((v) => parseInt(v.trim(), 10));
189
+ if (Number.isFinite(s) && Number.isFinite(e)) {
190
+ ranges.push([s, e]);
191
+ } else if (Number.isFinite(s)) {
192
+ ranges.push([s, s]);
193
+ }
194
+ }
195
+ if (ranges.length === 1) {
196
+ args.start = ranges[0][0];
197
+ args.end = ranges[0][1];
198
+ } else if (ranges.length > 1) {
199
+ args.lines = ranges;
200
+ }
201
+ }
202
+ tools.push({ name: "read", arguments: args });
203
+ } else if (funcName === "finish") {
204
+ if (params.result && !params.files) {
205
+ tools.push({ name: "finish", arguments: { files: [], textResult: params.result } });
206
+ continue;
207
+ }
208
+ const filesStr = params.files;
209
+ if (!filesStr) {
210
+ tools.push({ name: "finish", arguments: { files: [], textResult: "No relevant code found." } });
211
+ continue;
212
+ }
213
+ const files = [];
214
+ for (const line of filesStr.split("\n")) {
215
+ const trimmed = line.trim();
216
+ if (!trimmed) continue;
217
+ const colonIdx = trimmed.indexOf(":");
218
+ if (colonIdx === -1) {
219
+ files.push({ path: trimmed, lines: "*" });
220
+ } else {
221
+ const filePath = trimmed.slice(0, colonIdx);
222
+ const rangesPart = trimmed.slice(colonIdx + 1);
223
+ const ranges = [];
224
+ for (const rangeStr of rangesPart.split(",")) {
225
+ const rt = rangeStr.trim();
226
+ if (!rt || rt === "*") {
227
+ files.push({ path: filePath, lines: "*" });
228
+ break;
229
+ }
230
+ const [s, e] = rt.split("-").map((v) => parseInt(v.trim(), 10));
231
+ if (Number.isFinite(s) && Number.isFinite(e)) {
232
+ ranges.push([s, e]);
233
+ } else if (Number.isFinite(s)) {
234
+ ranges.push([s, s]);
235
+ }
236
+ }
237
+ if (ranges.length > 0) {
238
+ files.push({ path: filePath, lines: ranges });
239
+ } else if (!files.some((f) => f.path === filePath)) {
240
+ files.push({ path: filePath, lines: "*" });
241
+ }
242
+ }
243
+ }
244
+ if (files.length > 0) {
245
+ tools.push({ name: "finish", arguments: { files } });
246
+ } else {
247
+ tools.push({ name: "finish", arguments: { files: [], textResult: filesStr } });
179
248
  }
180
249
  }
181
- files.push({ path: filePath, lines: ranges.length > 0 ? ranges : "*" });
182
250
  }
183
- return files;
184
- }
185
- function extractPathFromCommand(command) {
186
- const tokens = command.trim().split(/\s+/);
187
- const pathTokens = tokens.slice(1).filter((t) => !t.startsWith("-") && !t.startsWith("|") && !t.startsWith("\\("));
188
- return pathTokens[0] || ".";
251
+ return tools;
189
252
  }
253
+ var LLMResponseParser = class {
254
+ parse(text) {
255
+ if (typeof text !== "string") {
256
+ throw new TypeError("Command text must be a string.");
257
+ }
258
+ const withoutThink = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
259
+ return parseQwen3ToolCalls(withoutThink);
260
+ }
261
+ };
190
262
 
191
263
  // tools/warp_grep/agent/tools/grep.ts
192
264
  async function toolGrep(provider, args) {
@@ -236,42 +308,29 @@ async function toolRead(provider, args) {
236
308
  }
237
309
 
238
310
  // tools/warp_grep/agent/tools/list_directory.ts
239
- var import_path = __toESM(require("path"), 1);
240
- async function toolListDirectory(provider, args, repoRoot) {
241
- const maxResults = args.maxResults ?? AGENT_CONFIG.MAX_LIST_RESULTS;
242
- const maxDepth = args.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
243
- const entries = await provider.listDirectory({
244
- path: args.path,
245
- pattern: args.pattern ?? null,
246
- maxResults,
247
- maxDepth
248
- });
249
- if (!entries.length) return "empty";
250
- if (repoRoot) {
251
- const absRoot = import_path.default.resolve(repoRoot);
252
- const lines = entries.map((e) => import_path.default.join(absRoot, e.path));
253
- return lines.join("\n");
254
- }
255
- return entries.map((e) => e.path).join("\n");
256
- }
257
-
258
- // tools/warp_grep/agent/tools/glob.ts
259
- async function toolGlob(provider, args) {
260
- const res = await provider.glob(args);
261
- if (res.error) {
262
- return res.error;
263
- }
264
- if (!res.files.length) {
265
- return "no matches";
311
+ async function toolListDirectory(provider, args) {
312
+ const maxResults = args.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES;
313
+ const initialDepth = args.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
314
+ async function getListRecursive(currentDepth) {
315
+ const entries = await provider.listDirectory({
316
+ path: args.path,
317
+ pattern: args.pattern ?? null,
318
+ maxResults,
319
+ maxDepth: currentDepth
320
+ });
321
+ if (entries.length >= maxResults && currentDepth > 0) {
322
+ return getListRecursive(currentDepth - 1);
323
+ }
324
+ return { entries };
266
325
  }
267
- const header = `Found ${res.totalFound} file(s) matching "${args.pattern}" within ${res.searchDir}, sorted by modification time (newest first):`;
268
- const body = res.files.join("\n");
269
- const truncated = res.totalFound > res.files.length ? `
270
- [${res.totalFound - res.files.length} files truncated]` : "";
271
- return `${header}
272
- ---
273
- ${body}
274
- ---${truncated}`;
326
+ const { entries: list } = await getListRecursive(initialDepth);
327
+ if (!list.length) return "empty";
328
+ const tree = list.map((e) => {
329
+ const indent = " ".repeat(e.depth);
330
+ const name = e.type === "dir" ? `${e.name}/` : e.name;
331
+ return `${indent}${name}`;
332
+ }).join("\n");
333
+ return tree;
275
334
  }
276
335
 
277
336
  // tools/warp_grep/agent/tools/finish.ts
@@ -332,20 +391,31 @@ function mergeRanges(ranges) {
332
391
  return merged;
333
392
  }
334
393
 
335
- // tools/warp_grep/agent/helpers.ts
336
- var import_path2 = __toESM(require("path"), 1);
337
- var TRUNCATED_MARKER = "[truncated for context limit]";
338
- function getMessageSize(m) {
339
- if (m.role === "tool") return m.content.length;
340
- if (m.role === "assistant") {
341
- let size = typeof m.content === "string" ? m.content.length : 0;
342
- if (m.tool_calls) {
343
- size += m.tool_calls.reduce((s, tc) => s + tc.function.name.length + tc.function.arguments.length, 0);
394
+ // tools/warp_grep/agent/formatter.ts
395
+ var ToolOutputFormatter = class {
396
+ format(toolName, _args, output, options = {}) {
397
+ const name = (toolName ?? "").trim();
398
+ if (!name) {
399
+ return "";
400
+ }
401
+ const payload = output?.toString?.()?.trim?.() ?? "";
402
+ const isError = Boolean(options.isError);
403
+ if (!payload && !isError) {
404
+ return "";
344
405
  }
345
- return size;
406
+ return `<tool_response>
407
+ ${payload}
408
+ </tool_response>`;
346
409
  }
347
- return m.content.length;
410
+ };
411
+ var sharedFormatter = new ToolOutputFormatter();
412
+ function formatAgentToolOutput(toolName, args, output, options = {}) {
413
+ return sharedFormatter.format(toolName, args, output, options);
348
414
  }
415
+
416
+ // tools/warp_grep/agent/helpers.ts
417
+ var import_path = __toESM(require("path"), 1);
418
+ var TRUNCATED_MARKER = "[truncated for context limit]";
349
419
  function formatTurnMessage(turnsUsed, maxTurns) {
350
420
  const turnsRemaining = maxTurns - turnsUsed;
351
421
  if (turnsRemaining === 1) {
@@ -356,7 +426,7 @@ You have used ${turnsUsed} turns, you only have 1 turn remaining. You have run o
356
426
  You have used ${turnsUsed} turn${turnsUsed === 1 ? "" : "s"} and have ${turnsRemaining} remaining`;
357
427
  }
358
428
  function calculateContextBudget(messages) {
359
- const totalChars = messages.reduce((sum, m) => sum + getMessageSize(m), 0);
429
+ const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0);
360
430
  const maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS;
361
431
  const percent = Math.round(totalChars / maxChars * 100);
362
432
  const usedK = Math.round(totalChars / 1e3);
@@ -365,21 +435,24 @@ function calculateContextBudget(messages) {
365
435
  }
366
436
  async function buildInitialState(repoRoot, searchTerm, provider, options) {
367
437
  const budget = calculateContextBudget([]);
368
- const turnTag = `You have used 0 turns and have ${AGENT_CONFIG.MAX_TURNS} remaining`;
438
+ const turnTag = `Turn 0/${AGENT_CONFIG.MAX_TURNS}`;
369
439
  const treeDepth = options?.search_type === "node_modules" ? 1 : 2;
370
- const absRoot = import_path2.default.resolve(repoRoot);
371
440
  try {
372
441
  const entries = await provider.listDirectory({
373
442
  path: ".",
374
443
  maxResults: AGENT_CONFIG.MAX_OUTPUT_LINES,
375
444
  maxDepth: treeDepth
376
445
  });
377
- const lines = [absRoot];
378
- for (const e of entries) {
379
- lines.push(import_path2.default.join(absRoot, e.path));
380
- }
446
+ const treeLines = entries.map((e) => {
447
+ const indent = " ".repeat(e.depth);
448
+ const name = e.type === "dir" ? `${e.name}/` : e.name;
449
+ return `${indent}${name}`;
450
+ });
451
+ const repoName = import_path.default.basename(repoRoot);
452
+ const treeOutput = treeLines.length > 0 ? `${repoName}/
453
+ ${treeLines.join("\n")}` : `${repoName}/`;
381
454
  return `<repo_structure>
382
- ${lines.join("\n")}
455
+ ${treeOutput}
383
456
  </repo_structure>
384
457
 
385
458
  <search_string>
@@ -388,8 +461,9 @@ ${searchTerm}
388
461
  ${budget}
389
462
  ${turnTag}`;
390
463
  } catch {
464
+ const repoName = import_path.default.basename(repoRoot);
391
465
  return `<repo_structure>
392
- ${absRoot}
466
+ ${repoName}/
393
467
  </repo_structure>
394
468
 
395
469
  <search_string>
@@ -400,32 +474,26 @@ ${turnTag}`;
400
474
  }
401
475
  }
402
476
  function enforceContextLimit(messages, maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS) {
403
- const getTotalChars = () => messages.reduce((sum, m) => sum + getMessageSize(m), 0);
477
+ const getTotalChars = () => messages.reduce((sum, m) => sum + m.content.length, 0);
404
478
  if (getTotalChars() <= maxChars) {
405
479
  return messages;
406
480
  }
407
- const truncatableIndices = [];
481
+ const userIndices = [];
408
482
  let firstUserSkipped = false;
409
483
  for (let i = 0; i < messages.length; i++) {
410
- const m = messages[i];
411
- if (m.role === "tool") {
412
- truncatableIndices.push(i);
413
- } else if (m.role === "user") {
484
+ if (messages[i].role === "user") {
414
485
  if (!firstUserSkipped) {
415
486
  firstUserSkipped = true;
416
487
  continue;
417
488
  }
418
- truncatableIndices.push(i);
489
+ userIndices.push(i);
419
490
  }
420
491
  }
421
- for (const idx of truncatableIndices) {
492
+ for (const idx of userIndices) {
422
493
  if (getTotalChars() <= maxChars) {
423
494
  break;
424
495
  }
425
- const m = messages[idx];
426
- if (m.role === "tool" && m.content !== TRUNCATED_MARKER) {
427
- messages[idx] = { role: "tool", tool_call_id: m.tool_call_id, content: TRUNCATED_MARKER };
428
- } else if (m.role === "user" && m.content !== TRUNCATED_MARKER) {
496
+ if (messages[idx].content !== TRUNCATED_MARKER) {
429
497
  messages[idx] = { role: "user", content: TRUNCATED_MARKER };
430
498
  }
431
499
  }
@@ -438,7 +506,7 @@ var import_openai = __toESM(require("openai"), 1);
438
506
  // package.json
439
507
  var package_default = {
440
508
  name: "@morphllm/morphsdk",
441
- version: "0.2.145",
509
+ version: "0.2.146",
442
510
  description: "TypeScript SDK and CLI for Morph Fast Apply integration",
443
511
  type: "module",
444
512
  main: "./dist/index.cjs",
@@ -677,115 +745,9 @@ var package_default = {
677
745
  var SDK_VERSION = package_default.version;
678
746
 
679
747
  // tools/warp_grep/agent/runner.ts
680
- var import_path3 = __toESM(require("path"), 1);
748
+ var import_path2 = __toESM(require("path"), 1);
749
+ var parser = new LLMResponseParser();
681
750
  var DEFAULT_API_URL = "https://api.morphllm.com";
682
- var TOOL_SPECS = [
683
- {
684
- type: "function",
685
- function: {
686
- name: "list_directory",
687
- description: "Execute ls or find commands to explore directory structure. Max 500 results. Common junk directories are excluded automatically.",
688
- parameters: {
689
- type: "object",
690
- properties: {
691
- command: {
692
- type: "string",
693
- description: "Full ls or find command (e.g. ls -la src/, find . -maxdepth 2 -type f -name '*.py', find . -type d, ls -d */)."
694
- }
695
- },
696
- required: ["command"]
697
- }
698
- }
699
- },
700
- {
701
- type: "function",
702
- function: {
703
- name: "grep_search",
704
- description: "Search for a regex pattern in file contents. Returns matching lines with file paths and line numbers. Case-insensitive. Respects .gitignore.",
705
- parameters: {
706
- type: "object",
707
- properties: {
708
- pattern: {
709
- type: "string",
710
- description: "Regex pattern to search for in file contents (e.g. 'class\\s+\\w+Error', 'import|require|from', 'def (get|set|update)_user')."
711
- },
712
- path: {
713
- type: "string",
714
- description: "File or directory to search in. Defaults to current working directory."
715
- },
716
- glob: {
717
- type: "string",
718
- description: "Glob pattern to filter files (e.g. '*.py', '*.{ts,tsx,js,jsx,py,go}', 'src/**/*.go', '!*.test.*')."
719
- },
720
- limit: {
721
- type: "integer",
722
- description: "Limit output to first N matching lines. Shows all matches if not specified."
723
- }
724
- },
725
- required: ["pattern"]
726
- }
727
- }
728
- },
729
- {
730
- type: "function",
731
- function: {
732
- name: "glob",
733
- description: "Find files by name/extension using glob patterns. Returns absolute paths sorted by modification time (newest first). Respects .gitignore. Max 100 results.",
734
- parameters: {
735
- type: "object",
736
- properties: {
737
- pattern: {
738
- type: "string",
739
- description: "Glob pattern to match files (e.g. '*.py', 'src/**/*.js', '*.{ts,tsx}', 'test_*.py')."
740
- },
741
- path: {
742
- type: "string",
743
- description: "Directory to search in. Defaults to repository root."
744
- }
745
- },
746
- required: ["pattern"]
747
- }
748
- }
749
- },
750
- {
751
- type: "function",
752
- function: {
753
- name: "read",
754
- description: "Read entire files or specific line ranges using absolute paths.",
755
- parameters: {
756
- type: "object",
757
- properties: {
758
- path: {
759
- type: "string",
760
- description: "File path to read, using absolute path (e.g. '/home/ubuntu/repo/src/main.py' or windows path)."
761
- },
762
- lines: {
763
- type: "string",
764
- description: "Optional line range (e.g. '1-50' or '1-20,45-80'). Omit to read entire file."
765
- }
766
- },
767
- required: ["path"]
768
- }
769
- }
770
- },
771
- {
772
- type: "function",
773
- function: {
774
- name: "finish",
775
- description: "Submit final answer with all relevant code locations. Include imports and over-include rather than miss context.",
776
- parameters: {
777
- type: "object",
778
- properties: {
779
- files: {
780
- type: "string",
781
- description: "One file per line as path:lines (e.g. 'src/auth.py:1-15,25-50\\nsrc/user.py'). Omit line range to include entire file."
782
- }
783
- },
784
- required: ["files"]
785
- }
786
- }
787
- }
788
- ];
789
751
  async function callModel(messages, model, options = {}) {
790
752
  const baseUrl = options.morphApiUrl || DEFAULT_API_URL;
791
753
  const apiKey = options.morphApiKey || process.env.MORPH_API_KEY || "";
@@ -806,9 +768,8 @@ async function callModel(messages, model, options = {}) {
806
768
  data = await client.chat.completions.create({
807
769
  model,
808
770
  temperature: 0,
809
- max_tokens: 2048,
771
+ max_tokens: 1024,
810
772
  messages,
811
- tools: TOOL_SPECS,
812
773
  ...options.search_type ? { search_type: options.search_type } : {}
813
774
  });
814
775
  } catch (error) {
@@ -820,87 +781,187 @@ async function callModel(messages, model, options = {}) {
820
781
  throw error;
821
782
  }
822
783
  const choice = data?.choices?.[0];
823
- const message = choice?.message;
824
- if (!message) {
825
- if (attempt === MAX_EMPTY_RETRIES) {
826
- throw new Error("Invalid response from model: no message in response");
827
- }
828
- await new Promise((resolve) => setTimeout(resolve, 200));
829
- continue;
830
- }
831
- const toolCalls = (message.tool_calls || []).map((tc) => ({
832
- id: tc.id,
833
- type: "function",
834
- function: { name: tc.function.name, arguments: tc.function.arguments }
835
- }));
836
- if (message.content || toolCalls.length > 0) {
837
- return { content: message.content ?? null, tool_calls: toolCalls };
784
+ const content = choice?.message?.content;
785
+ if (content && typeof content === "string") {
786
+ return content;
838
787
  }
839
788
  if (attempt === MAX_EMPTY_RETRIES) {
840
789
  const finishReason = choice?.finish_reason ?? "unknown";
790
+ const hasToolCalls = Array.isArray(choice?.message?.tool_calls) && choice.message.tool_calls.length > 0;
791
+ const choicesLen = data?.choices?.length ?? 0;
792
+ const contentType = content === null ? "null" : content === void 0 ? "undefined" : typeof content;
841
793
  throw new Error(
842
- `Invalid response from model: no content and no tool_calls, finish_reason=${finishReason}`
794
+ `Invalid response from model: content=${contentType}, finish_reason=${finishReason}, has_tool_calls=${hasToolCalls}, choices_length=${choicesLen}`
843
795
  );
844
796
  }
845
797
  await new Promise((resolve) => setTimeout(resolve, 200));
846
798
  }
847
799
  throw new Error("Invalid response from model");
848
800
  }
849
- function safeParseJSON(s) {
850
- try {
851
- return JSON.parse(s);
852
- } catch {
853
- return {};
854
- }
855
- }
856
- async function executeTool(provider, name, args, repoRoot) {
857
- switch (name) {
858
- case "grep_search": {
859
- const grepArgs = {
860
- pattern: args.pattern,
861
- path: args.path || "."
862
- };
863
- if (args.glob) grepArgs.glob = args.glob;
864
- if (args.case_sensitive !== void 0) grepArgs.case_sensitive = args.case_sensitive;
865
- const result = await toolGrep(provider, grepArgs);
866
- let output = result.output;
867
- if (args.limit && typeof args.limit === "number") {
868
- const lines = output.split("\n");
869
- if (lines.length > args.limit) {
870
- output = lines.slice(0, args.limit).join("\n") + `
871
- ... (truncated at ${args.limit} lines)`;
872
- }
873
- }
874
- return output;
801
+ async function runWarpGrep(config) {
802
+ const totalStart = Date.now();
803
+ const timeoutMs = config.timeout ?? AGENT_CONFIG.TIMEOUT_MS;
804
+ const timings = { turns: [], timeout_ms: timeoutMs };
805
+ const repoRoot = import_path2.default.resolve(config.repoRoot || process.cwd());
806
+ const model = config.model || DEFAULT_MODEL;
807
+ const messages = [];
808
+ const maxTurns = AGENT_CONFIG.MAX_TURNS;
809
+ const initialStateStart = Date.now();
810
+ const initialState = await buildInitialState(repoRoot, config.searchTerm, config.provider, { search_type: config.search_type });
811
+ timings.initial_state_ms = Date.now() - initialStateStart;
812
+ messages.push({ role: "user", content: initialState });
813
+ const provider = config.provider;
814
+ const errors = [];
815
+ let finishMeta;
816
+ let terminationReason = "terminated";
817
+ for (let turn = 1; turn <= maxTurns; turn += 1) {
818
+ const turnMetrics = { turn, morph_api_ms: 0, local_tools_ms: 0 };
819
+ enforceContextLimit(messages);
820
+ const modelCallStart = Date.now();
821
+ const assistantContent = await callModel(messages, model, {
822
+ morphApiKey: config.morphApiKey,
823
+ morphApiUrl: config.morphApiUrl,
824
+ retryConfig: config.retryConfig,
825
+ timeout: timeoutMs,
826
+ search_type: config.search_type
827
+ }).catch((e) => {
828
+ const errMsg = e instanceof Error ? e.message : String(e);
829
+ console.error(`[warp_grep] Morph API call failed on turn ${turn}:`, errMsg);
830
+ errors.push({ message: errMsg });
831
+ return "";
832
+ });
833
+ turnMetrics.morph_api_ms = Date.now() - modelCallStart;
834
+ if (!assistantContent) {
835
+ console.error(`[warp_grep] Empty response from Morph API on turn ${turn}. Errors so far:`, errors);
836
+ timings.turns.push(turnMetrics);
837
+ break;
875
838
  }
876
- case "glob": {
877
- return toolGlob(provider, {
878
- pattern: args.pattern,
879
- path: args.path
880
- });
839
+ messages.push({ role: "assistant", content: assistantContent });
840
+ const toolCalls = parser.parse(assistantContent);
841
+ if (toolCalls.length === 0) {
842
+ console.error(`[warp_grep] No tool calls parsed on turn ${turn}. Assistant content (first 500 chars):`, assistantContent.slice(0, 500));
843
+ errors.push({ message: "No tool calls produced by the model. Your MCP is likely out of date! Update it by running: rm -rf ~/.npm/_npx && npm cache clean --force && npx -y @morphllm/morphmcp@latest" });
844
+ terminationReason = "terminated";
845
+ timings.turns.push(turnMetrics);
846
+ break;
881
847
  }
882
- case "list_directory": {
883
- const dirPath = extractPathFromCommand(args.command || ".");
884
- return toolListDirectory(provider, { path: dirPath }, repoRoot);
848
+ const finishCalls = toolCalls.filter((c) => c.name === "finish");
849
+ const grepCalls = toolCalls.filter((c) => c.name === "grep");
850
+ const listDirCalls = toolCalls.filter((c) => c.name === "list_directory");
851
+ const readCalls = toolCalls.filter((c) => c.name === "read");
852
+ const skipCalls = toolCalls.filter((c) => c.name === "_skip");
853
+ const formatted = [];
854
+ for (const c of skipCalls) {
855
+ const msg = c.arguments?.message || "Command skipped due to parsing error";
856
+ formatted.push(msg);
885
857
  }
886
- case "read": {
887
- const readArgs = {
888
- path: args.path
889
- };
890
- if (args.lines && typeof args.lines === "string") {
891
- Object.assign(readArgs, parseReadLines(args.lines));
858
+ const allPromises = [];
859
+ for (const c of grepCalls) {
860
+ const args = c.arguments ?? {};
861
+ allPromises.push(
862
+ toolGrep(provider, args).then(
863
+ ({ output }) => formatAgentToolOutput("grep", args, output),
864
+ (err) => formatAgentToolOutput("grep", args, String(err), { isError: true })
865
+ )
866
+ );
867
+ }
868
+ for (const c of listDirCalls) {
869
+ const args = c.arguments ?? {};
870
+ allPromises.push(
871
+ toolListDirectory(provider, args).then(
872
+ (p) => formatAgentToolOutput("list_directory", args, p),
873
+ (err) => formatAgentToolOutput("list_directory", args, String(err), { isError: true })
874
+ )
875
+ );
876
+ }
877
+ for (const c of readCalls) {
878
+ const args = c.arguments ?? {};
879
+ allPromises.push(
880
+ toolRead(provider, args).then(
881
+ (p) => formatAgentToolOutput("read", args, p),
882
+ (err) => formatAgentToolOutput("read", args, String(err), { isError: true })
883
+ )
884
+ );
885
+ }
886
+ const toolExecStart = Date.now();
887
+ const allResults = await Promise.all(allPromises);
888
+ turnMetrics.local_tools_ms = Date.now() - toolExecStart;
889
+ for (const result of allResults) {
890
+ formatted.push(result);
891
+ }
892
+ if (formatted.length > 0) {
893
+ const turnMessage = formatTurnMessage(turn, maxTurns);
894
+ const contextBudget = calculateContextBudget(messages);
895
+ messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
896
+ }
897
+ timings.turns.push(turnMetrics);
898
+ if (finishCalls.length) {
899
+ const fc = finishCalls[0];
900
+ const files = fc.arguments?.files ?? [];
901
+ const textResult = fc.arguments?.textResult;
902
+ finishMeta = { files };
903
+ terminationReason = "completed";
904
+ if (files.length === 0) {
905
+ const payload2 = textResult || "No relevant code found.";
906
+ timings.turns.push(turnMetrics);
907
+ timings.total_ms = Date.now() - totalStart;
908
+ return {
909
+ terminationReason: "completed",
910
+ messages,
911
+ finish: { payload: payload2, metadata: finishMeta },
912
+ timings
913
+ };
914
+ }
915
+ break;
916
+ }
917
+ }
918
+ if (terminationReason !== "completed" || !finishMeta) {
919
+ timings.total_ms = Date.now() - totalStart;
920
+ return { terminationReason, messages, errors, timings };
921
+ }
922
+ const parts = ["Relevant context found:"];
923
+ for (const f of finishMeta.files) {
924
+ const ranges = f.lines === "*" ? "*" : Array.isArray(f.lines) ? f.lines.map(([s, e]) => `${s}-${e}`).join(", ") : "*";
925
+ parts.push(`- ${f.path}: ${ranges}`);
926
+ }
927
+ const payload = parts.join("\n");
928
+ const finishResolutionStart = Date.now();
929
+ const fileReadErrors = [];
930
+ const resolved = await readFinishFiles(
931
+ repoRoot,
932
+ finishMeta.files,
933
+ async (p, s, e) => {
934
+ try {
935
+ const rr = await provider.read({ path: p, start: s, end: e });
936
+ return rr.lines.map((l) => {
937
+ const idx = l.indexOf("|");
938
+ return idx >= 0 ? l.slice(idx + 1) : l;
939
+ });
940
+ } catch (err) {
941
+ const errorMsg = err instanceof Error ? err.message : String(err);
942
+ fileReadErrors.push({ path: p, error: errorMsg });
943
+ console.error(`[warp_grep] Failed to read file: ${p} - ${errorMsg}`);
944
+ return [`[couldn't find: ${p}]`];
892
945
  }
893
- return toolRead(provider, readArgs);
894
946
  }
895
- default:
896
- return `Unknown tool: ${name}`;
947
+ );
948
+ timings.finish_resolution_ms = Date.now() - finishResolutionStart;
949
+ if (fileReadErrors.length > 0) {
950
+ errors.push(...fileReadErrors.map((e) => ({ message: `File read error: ${e.path} - ${e.error}` })));
897
951
  }
952
+ timings.total_ms = Date.now() - totalStart;
953
+ return {
954
+ terminationReason: "completed",
955
+ messages,
956
+ finish: { payload, metadata: finishMeta, resolved },
957
+ timings
958
+ };
898
959
  }
899
960
  async function* runWarpGrepStreaming(config) {
900
961
  const totalStart = Date.now();
901
962
  const timeoutMs = config.timeout ?? AGENT_CONFIG.TIMEOUT_MS;
902
963
  const timings = { turns: [], timeout_ms: timeoutMs };
903
- const repoRoot = import_path3.default.resolve(config.repoRoot || process.cwd());
964
+ const repoRoot = import_path2.default.resolve(config.repoRoot || process.cwd());
904
965
  const model = config.model || DEFAULT_MODEL;
905
966
  const messages = [];
906
967
  const maxTurns = AGENT_CONFIG.MAX_TURNS;
@@ -916,7 +977,7 @@ async function* runWarpGrepStreaming(config) {
916
977
  const turnMetrics = { turn, morph_api_ms: 0, local_tools_ms: 0 };
917
978
  enforceContextLimit(messages);
918
979
  const modelCallStart = Date.now();
919
- const response = await callModel(messages, model, {
980
+ const assistantContent = await callModel(messages, model, {
920
981
  morphApiKey: config.morphApiKey,
921
982
  morphApiUrl: config.morphApiUrl,
922
983
  retryConfig: config.retryConfig,
@@ -924,45 +985,90 @@ async function* runWarpGrepStreaming(config) {
924
985
  search_type: config.search_type
925
986
  }).catch((e) => {
926
987
  const errMsg = e instanceof Error ? e.message : String(e);
927
- console.error(`[warp_grep] Morph API call failed on turn ${turn}:`, errMsg);
988
+ console.error(`[warp_grep:stream] Morph API call failed on turn ${turn}:`, errMsg);
928
989
  errors.push({ message: errMsg });
929
- return null;
990
+ return "";
930
991
  });
931
992
  turnMetrics.morph_api_ms = Date.now() - modelCallStart;
932
- if (!response) {
993
+ if (!assistantContent) {
994
+ console.error(`[warp_grep:stream] Empty response from Morph API on turn ${turn}. Errors so far:`, errors);
933
995
  timings.turns.push(turnMetrics);
934
996
  break;
935
997
  }
936
- const toolCalls = response.tool_calls;
937
- messages.push({
938
- role: "assistant",
939
- content: response.content,
940
- ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
941
- });
998
+ messages.push({ role: "assistant", content: assistantContent });
999
+ const toolCalls = parser.parse(assistantContent);
942
1000
  if (toolCalls.length === 0) {
943
- console.error(`[warp_grep] No tool calls on turn ${turn}. Content: ${(response.content || "").slice(0, 500)}`);
944
- errors.push({ message: "No tool calls produced by the model." });
1001
+ console.error(`[warp_grep:stream] No tool calls parsed on turn ${turn}. Assistant content (first 500 chars):`, assistantContent.slice(0, 500));
1002
+ errors.push({ message: "No tool calls produced by the model. Your MCP is likely out of date! Update it by running: rm -rf ~/.npm/_npx && npm cache clean --force && npx -y @morphllm/morphmcp@latest" });
945
1003
  terminationReason = "terminated";
946
1004
  timings.turns.push(turnMetrics);
947
1005
  break;
948
1006
  }
949
1007
  yield {
950
1008
  turn,
951
- toolCalls: toolCalls.map((tc) => ({
952
- name: tc.function.name,
953
- arguments: safeParseJSON(tc.function.arguments)
1009
+ toolCalls: toolCalls.map((c) => ({
1010
+ name: c.name,
1011
+ arguments: c.arguments ?? {}
954
1012
  }))
955
1013
  };
956
- const finishCall = toolCalls.find((tc) => tc.function.name === "finish");
957
- if (finishCall) {
958
- const args = safeParseJSON(finishCall.function.arguments);
959
- const filesStr = args.files || "";
960
- const files = parseFinishFiles(filesStr);
1014
+ const finishCalls = toolCalls.filter((c) => c.name === "finish");
1015
+ const grepCalls = toolCalls.filter((c) => c.name === "grep");
1016
+ const listDirCalls = toolCalls.filter((c) => c.name === "list_directory");
1017
+ const readCalls = toolCalls.filter((c) => c.name === "read");
1018
+ const skipCalls = toolCalls.filter((c) => c.name === "_skip");
1019
+ const formatted = [];
1020
+ for (const c of skipCalls) {
1021
+ const msg = c.arguments?.message || "Command skipped due to parsing error";
1022
+ formatted.push(msg);
1023
+ }
1024
+ const allPromises = [];
1025
+ for (const c of grepCalls) {
1026
+ const args = c.arguments ?? {};
1027
+ allPromises.push(
1028
+ toolGrep(provider, args).then(
1029
+ ({ output }) => formatAgentToolOutput("grep", args, output),
1030
+ (err) => formatAgentToolOutput("grep", args, String(err), { isError: true })
1031
+ )
1032
+ );
1033
+ }
1034
+ for (const c of listDirCalls) {
1035
+ const args = c.arguments ?? {};
1036
+ allPromises.push(
1037
+ toolListDirectory(provider, args).then(
1038
+ (p) => formatAgentToolOutput("list_directory", args, p),
1039
+ (err) => formatAgentToolOutput("list_directory", args, String(err), { isError: true })
1040
+ )
1041
+ );
1042
+ }
1043
+ for (const c of readCalls) {
1044
+ const args = c.arguments ?? {};
1045
+ allPromises.push(
1046
+ toolRead(provider, args).then(
1047
+ (p) => formatAgentToolOutput("read", args, p),
1048
+ (err) => formatAgentToolOutput("read", args, String(err), { isError: true })
1049
+ )
1050
+ );
1051
+ }
1052
+ const toolExecStart = Date.now();
1053
+ const allResults = await Promise.all(allPromises);
1054
+ turnMetrics.local_tools_ms = Date.now() - toolExecStart;
1055
+ for (const result of allResults) {
1056
+ formatted.push(result);
1057
+ }
1058
+ if (formatted.length > 0) {
1059
+ const turnMessage = formatTurnMessage(turn, maxTurns);
1060
+ const contextBudget = calculateContextBudget(messages);
1061
+ messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
1062
+ }
1063
+ timings.turns.push(turnMetrics);
1064
+ if (finishCalls.length) {
1065
+ const fc = finishCalls[0];
1066
+ const files = fc.arguments?.files ?? [];
1067
+ const textResult = fc.arguments?.textResult;
961
1068
  finishMeta = { files };
962
1069
  terminationReason = "completed";
963
1070
  if (files.length === 0) {
964
- const payload2 = filesStr || "No relevant code found.";
965
- timings.turns.push(turnMetrics);
1071
+ const payload2 = textResult || "No relevant code found.";
966
1072
  timings.total_ms = Date.now() - totalStart;
967
1073
  return {
968
1074
  terminationReason: "completed",
@@ -971,25 +1077,8 @@ async function* runWarpGrepStreaming(config) {
971
1077
  timings
972
1078
  };
973
1079
  }
974
- timings.turns.push(turnMetrics);
975
1080
  break;
976
1081
  }
977
- const toolExecStart = Date.now();
978
- const results = await Promise.all(
979
- toolCalls.map(async (tc) => {
980
- const args = safeParseJSON(tc.function.arguments);
981
- const output = await executeTool(provider, tc.function.name, args, repoRoot).catch((err) => String(err));
982
- return { tool_call_id: tc.id, content: output };
983
- })
984
- );
985
- turnMetrics.local_tools_ms = Date.now() - toolExecStart;
986
- for (const result of results) {
987
- messages.push({ role: "tool", tool_call_id: result.tool_call_id, content: result.content });
988
- }
989
- const turnMsg = formatTurnMessage(turn, maxTurns);
990
- const budget = calculateContextBudget(messages);
991
- messages.push({ role: "user", content: turnMsg + "\n" + budget });
992
- timings.turns.push(turnMetrics);
993
1082
  }
994
1083
  if (terminationReason !== "completed" || !finishMeta) {
995
1084
  timings.total_ms = Date.now() - totalStart;
@@ -1033,14 +1122,6 @@ async function* runWarpGrepStreaming(config) {
1033
1122
  timings
1034
1123
  };
1035
1124
  }
1036
- async function runWarpGrep(config) {
1037
- const gen = runWarpGrepStreaming(config);
1038
- let result = await gen.next();
1039
- while (!result.done) {
1040
- result = await gen.next();
1041
- }
1042
- return result.value;
1043
- }
1044
1125
  // Annotate the CommonJS export names for ESM import in node:
1045
1126
  0 && (module.exports = {
1046
1127
  runWarpGrep,