@morphllm/morphsdk 0.2.146 → 0.2.148

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-FYO46OT6.js → chunk-33CVSDM6.js} +2 -2
  2. package/dist/{chunk-SJYAKVSS.js → chunk-44WJK4MN.js} +2 -2
  3. package/dist/{chunk-SJYAKVSS.js.map → chunk-44WJK4MN.js.map} +1 -1
  4. package/dist/{chunk-GJUB3ECP.js → chunk-4FLNLA2D.js} +2 -2
  5. package/dist/{chunk-E4YKEKGW.js → chunk-4K36ATXI.js} +2 -2
  6. package/dist/{chunk-V73GO5AJ.js → chunk-4ZA4INTU.js} +2 -2
  7. package/dist/{chunk-T564HFSH.js → chunk-5R5FVUAL.js} +1 -1
  8. package/dist/{chunk-FBOJJ3UY.js → chunk-6MVJ5J6H.js} +17 -17
  9. package/dist/{chunk-UVNENJ6H.js → chunk-7PZKCKIH.js} +3 -3
  10. package/dist/{chunk-NF2QWJDY.js → chunk-B3AKP3RA.js} +31 -2
  11. package/dist/chunk-B3AKP3RA.js.map +1 -0
  12. package/dist/chunk-CMSHXALI.js +60 -0
  13. package/dist/chunk-CMSHXALI.js.map +1 -0
  14. package/dist/{chunk-I7SFRYTX.js → chunk-DMYFFSLS.js} +2 -2
  15. package/dist/{chunk-Q36MNOFA.js → chunk-E5K4VHMA.js} +2 -2
  16. package/dist/{chunk-OV57JBMB.js → chunk-EGUJNHMV.js} +2 -2
  17. package/dist/{chunk-E45FW5EK.js → chunk-ELU34VFH.js} +2 -2
  18. package/dist/{chunk-BDHKL3MT.js → chunk-F6OUFO25.js} +2 -2
  19. package/dist/{chunk-QRSWXP4K.js → chunk-H6TH3F4V.js} +2 -2
  20. package/dist/{chunk-FIVYDIHX.js → chunk-HYRHI2UL.js} +1 -1
  21. package/dist/{chunk-DKODF3YG.js → chunk-I3J46TSB.js} +5 -4
  22. package/dist/chunk-I3J46TSB.js.map +1 -0
  23. package/dist/{chunk-J2HIK4GB.js → chunk-IL7OLRJP.js} +2 -2
  24. package/dist/{chunk-JSWNBCGS.js → chunk-LJM3R7UZ.js} +2 -2
  25. package/dist/{chunk-NKUSUSVI.js → chunk-N67TYW2Z.js} +3 -3
  26. package/dist/chunk-OBXWT7NJ.js +401 -0
  27. package/dist/chunk-OBXWT7NJ.js.map +1 -0
  28. package/dist/{chunk-MMBQKN4G.js → chunk-OHYA6SUF.js} +2 -2
  29. package/dist/{chunk-EU7OLX4Z.js → chunk-RH2JB76E.js} +2 -2
  30. package/dist/{chunk-KYKRRF7E.js → chunk-SID7EXWK.js} +2 -2
  31. package/dist/{chunk-YIETFYCL.js → chunk-T7HF2TDQ.js} +75 -48
  32. package/dist/chunk-T7HF2TDQ.js.map +1 -0
  33. package/dist/{chunk-VZ6VYRQB.js → chunk-TSUTRT4Q.js} +2 -2
  34. package/dist/{chunk-UYPWKQKV.js → chunk-TVUYMM4J.js} +2 -2
  35. package/dist/{chunk-HZOTLGJH.js → chunk-XNBQJSLI.js} +42 -2
  36. package/dist/chunk-XNBQJSLI.js.map +1 -0
  37. package/dist/{chunk-BIQ7234U.js → chunk-XT7JQAXV.js} +2 -2
  38. package/dist/{chunk-4PBUB77N.js → chunk-YWJHOYEM.js} +2 -2
  39. package/dist/client.cjs +439 -446
  40. package/dist/client.cjs.map +1 -1
  41. package/dist/client.js +26 -27
  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-DBKuo8yj.d.ts → finish-Ddj1MPGt.d.ts} +1 -1
  46. package/dist/index.cjs +458 -446
  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 +435 -442
  56. package/dist/subagents/anthropic.cjs.map +1 -1
  57. package/dist/subagents/anthropic.js +8 -9
  58. package/dist/subagents/vercel.cjs +435 -442
  59. package/dist/subagents/vercel.cjs.map +1 -1
  60. package/dist/subagents/vercel.js +8 -9
  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 +4 -3
  122. package/dist/tools/warp_grep/agent/config.cjs.map +1 -1
  123. package/dist/tools/warp_grep/agent/config.d.ts +2 -1
  124. package/dist/tools/warp_grep/agent/config.js +1 -1
  125. package/dist/tools/warp_grep/agent/parser.cjs +52 -121
  126. package/dist/tools/warp_grep/agent/parser.cjs.map +1 -1
  127. package/dist/tools/warp_grep/agent/parser.d.ts +12 -5
  128. package/dist/tools/warp_grep/agent/parser.js +7 -3
  129. package/dist/tools/warp_grep/agent/runner.cjs +348 -424
  130. package/dist/tools/warp_grep/agent/runner.cjs.map +1 -1
  131. package/dist/tools/warp_grep/agent/runner.d.ts +6 -3
  132. package/dist/tools/warp_grep/agent/runner.js +5 -6
  133. package/dist/tools/warp_grep/agent/types.cjs.map +1 -1
  134. package/dist/tools/warp_grep/agent/types.d.ts +22 -3
  135. package/dist/tools/warp_grep/anthropic.cjs +435 -442
  136. package/dist/tools/warp_grep/anthropic.cjs.map +1 -1
  137. package/dist/tools/warp_grep/anthropic.js +8 -9
  138. package/dist/tools/warp_grep/client.cjs +435 -442
  139. package/dist/tools/warp_grep/client.cjs.map +1 -1
  140. package/dist/tools/warp_grep/client.js +7 -8
  141. package/dist/tools/warp_grep/gemini.cjs +435 -442
  142. package/dist/tools/warp_grep/gemini.cjs.map +1 -1
  143. package/dist/tools/warp_grep/gemini.js +7 -8
  144. package/dist/tools/warp_grep/gemini.js.map +1 -1
  145. package/dist/tools/warp_grep/harness.cjs +168 -180
  146. package/dist/tools/warp_grep/harness.cjs.map +1 -1
  147. package/dist/tools/warp_grep/harness.d.ts +17 -38
  148. package/dist/tools/warp_grep/harness.js +15 -14
  149. package/dist/tools/warp_grep/harness.js.map +1 -1
  150. package/dist/tools/warp_grep/index.cjs +454 -442
  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 +435 -442
  155. package/dist/tools/warp_grep/openai.cjs.map +1 -1
  156. package/dist/tools/warp_grep/openai.js +8 -9
  157. package/dist/tools/warp_grep/providers/local.cjs +43 -2
  158. package/dist/tools/warp_grep/providers/local.cjs.map +1 -1
  159. package/dist/tools/warp_grep/providers/local.d.ts +5 -1
  160. package/dist/tools/warp_grep/providers/local.js +2 -2
  161. package/dist/tools/warp_grep/providers/remote.cjs +32 -2
  162. package/dist/tools/warp_grep/providers/remote.cjs.map +1 -1
  163. package/dist/tools/warp_grep/providers/remote.d.ts +9 -1
  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 +14 -1
  167. package/dist/tools/warp_grep/vercel.cjs +435 -442
  168. package/dist/tools/warp_grep/vercel.cjs.map +1 -1
  169. package/dist/tools/warp_grep/vercel.js +8 -9
  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-DKODF3YG.js.map +0 -1
  175. package/dist/chunk-EUHNJMWL.js +0 -409
  176. package/dist/chunk-EUHNJMWL.js.map +0 -1
  177. package/dist/chunk-HZOTLGJH.js.map +0 -1
  178. package/dist/chunk-NF2QWJDY.js.map +0 -1
  179. package/dist/chunk-VCKJ22DX.js +0 -131
  180. package/dist/chunk-VCKJ22DX.js.map +0 -1
  181. package/dist/chunk-YIETFYCL.js.map +0 -1
  182. /package/dist/{chunk-FYO46OT6.js.map → chunk-33CVSDM6.js.map} +0 -0
  183. /package/dist/{chunk-GJUB3ECP.js.map → chunk-4FLNLA2D.js.map} +0 -0
  184. /package/dist/{chunk-E4YKEKGW.js.map → chunk-4K36ATXI.js.map} +0 -0
  185. /package/dist/{chunk-V73GO5AJ.js.map → chunk-4ZA4INTU.js.map} +0 -0
  186. /package/dist/{chunk-T564HFSH.js.map → chunk-5R5FVUAL.js.map} +0 -0
  187. /package/dist/{chunk-FBOJJ3UY.js.map → chunk-6MVJ5J6H.js.map} +0 -0
  188. /package/dist/{chunk-UVNENJ6H.js.map → chunk-7PZKCKIH.js.map} +0 -0
  189. /package/dist/{chunk-I7SFRYTX.js.map → chunk-DMYFFSLS.js.map} +0 -0
  190. /package/dist/{chunk-Q36MNOFA.js.map → chunk-E5K4VHMA.js.map} +0 -0
  191. /package/dist/{chunk-OV57JBMB.js.map → chunk-EGUJNHMV.js.map} +0 -0
  192. /package/dist/{chunk-E45FW5EK.js.map → chunk-ELU34VFH.js.map} +0 -0
  193. /package/dist/{chunk-BDHKL3MT.js.map → chunk-F6OUFO25.js.map} +0 -0
  194. /package/dist/{chunk-QRSWXP4K.js.map → chunk-H6TH3F4V.js.map} +0 -0
  195. /package/dist/{chunk-FIVYDIHX.js.map → chunk-HYRHI2UL.js.map} +0 -0
  196. /package/dist/{chunk-J2HIK4GB.js.map → chunk-IL7OLRJP.js.map} +0 -0
  197. /package/dist/{chunk-JSWNBCGS.js.map → chunk-LJM3R7UZ.js.map} +0 -0
  198. /package/dist/{chunk-NKUSUSVI.js.map → chunk-N67TYW2Z.js.map} +0 -0
  199. /package/dist/{chunk-MMBQKN4G.js.map → chunk-OHYA6SUF.js.map} +0 -0
  200. /package/dist/{chunk-EU7OLX4Z.js.map → chunk-RH2JB76E.js.map} +0 -0
  201. /package/dist/{chunk-KYKRRF7E.js.map → chunk-SID7EXWK.js.map} +0 -0
  202. /package/dist/{chunk-VZ6VYRQB.js.map → chunk-TSUTRT4Q.js.map} +0 -0
  203. /package/dist/{chunk-UYPWKQKV.js.map → chunk-TVUYMM4J.js.map} +0 -0
  204. /package/dist/{chunk-BIQ7234U.js.map → chunk-XT7JQAXV.js.map} +0 -0
  205. /package/dist/{chunk-4PBUB77N.js.map → chunk-YWJHOYEM.js.map} +0 -0
@@ -42,11 +42,12 @@ var parseEnvTimeout = (envValue, defaultMs) => {
42
42
  return isNaN(parsed) || parsed <= 0 ? defaultMs : parsed;
43
43
  };
44
44
  var AGENT_CONFIG = {
45
- MAX_TURNS: 4,
45
+ MAX_TURNS: 6,
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: 54e4,
48
+ MAX_CONTEXT_CHARS: 321600,
49
49
  MAX_OUTPUT_LINES: 200,
50
+ MAX_LIST_RESULTS: 500,
50
51
  MAX_READ_LINES: 800,
51
52
  MAX_LIST_DEPTH: 3,
52
53
  LIST_TIMEOUT_MS: 2e3
@@ -131,134 +132,61 @@ var BUILTIN_EXCLUDES = [
131
132
  ".*"
132
133
  ];
133
134
  var DEFAULT_EXCLUDES = (process.env.MORPH_WARP_GREP_EXCLUDE || "").split(",").map((s) => s.trim()).filter(Boolean).concat(BUILTIN_EXCLUDES);
134
- var DEFAULT_MODEL = "morph-warp-grep-v2";
135
+ var DEFAULT_MODEL = "morph-warp-grep-v2.1";
135
136
 
136
137
  // tools/warp_grep/agent/parser.ts
137
- var VALID_COMMANDS = ["list_directory", "ripgrep", "read", "finish"];
138
- function isValidCommand(name) {
139
- return VALID_COMMANDS.includes(name);
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 {};
140
153
  }
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();
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;
154
163
  }
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 } });
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]]);
248
179
  }
249
180
  }
181
+ files.push({ path: filePath, lines: ranges.length > 0 ? ranges : "*" });
250
182
  }
251
- return tools;
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] || ".";
252
189
  }
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
- };
262
190
 
263
191
  // tools/warp_grep/agent/tools/grep.ts
264
192
  async function toolGrep(provider, args) {
@@ -308,29 +236,42 @@ async function toolRead(provider, args) {
308
236
  }
309
237
 
310
238
  // tools/warp_grep/agent/tools/list_directory.ts
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 };
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");
325
254
  }
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;
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";
266
+ }
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}`;
334
275
  }
335
276
 
336
277
  // tools/warp_grep/agent/tools/finish.ts
@@ -391,31 +332,20 @@ function mergeRanges(ranges) {
391
332
  return merged;
392
333
  }
393
334
 
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 "";
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);
405
344
  }
406
- return `<tool_response>
407
- ${payload}
408
- </tool_response>`;
345
+ return size;
409
346
  }
410
- };
411
- var sharedFormatter = new ToolOutputFormatter();
412
- function formatAgentToolOutput(toolName, args, output, options = {}) {
413
- return sharedFormatter.format(toolName, args, output, options);
347
+ return m.content.length;
414
348
  }
415
-
416
- // tools/warp_grep/agent/helpers.ts
417
- var import_path = __toESM(require("path"), 1);
418
- var TRUNCATED_MARKER = "[truncated for context limit]";
419
349
  function formatTurnMessage(turnsUsed, maxTurns) {
420
350
  const turnsRemaining = maxTurns - turnsUsed;
421
351
  if (turnsRemaining === 1) {
@@ -426,33 +356,30 @@ You have used ${turnsUsed} turns, you only have 1 turn remaining. You have run o
426
356
  You have used ${turnsUsed} turn${turnsUsed === 1 ? "" : "s"} and have ${turnsRemaining} remaining`;
427
357
  }
428
358
  function calculateContextBudget(messages) {
429
- const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0);
359
+ const totalChars = messages.reduce((sum, m) => sum + getMessageSize(m), 0);
430
360
  const maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS;
431
- const percent = Math.round(totalChars / maxChars * 100);
432
- const usedK = Math.round(totalChars / 1e3);
433
- const maxK = Math.round(maxChars / 1e3);
434
- return `<context_budget>${percent}% (${usedK}K/${maxK}K chars used)</context_budget>`;
361
+ const percent = Math.floor(totalChars / maxChars * 100);
362
+ const usedK = Math.floor(totalChars / 1e3);
363
+ const maxK = Math.floor(maxChars / 1e3);
364
+ return `<context_budget>${percent}% (${usedK}K/${maxK}K chars)</context_budget>`;
435
365
  }
436
366
  async function buildInitialState(repoRoot, searchTerm, provider, options) {
437
367
  const budget = calculateContextBudget([]);
438
- const turnTag = `Turn 0/${AGENT_CONFIG.MAX_TURNS}`;
368
+ const turnTag = `You have used 0 turns and have ${AGENT_CONFIG.MAX_TURNS} remaining`;
439
369
  const treeDepth = options?.search_type === "node_modules" ? 1 : 2;
370
+ const absRoot = import_path2.default.resolve(repoRoot);
440
371
  try {
441
372
  const entries = await provider.listDirectory({
442
373
  path: ".",
443
374
  maxResults: AGENT_CONFIG.MAX_OUTPUT_LINES,
444
375
  maxDepth: treeDepth
445
376
  });
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}/`;
377
+ const lines = [absRoot];
378
+ for (const e of entries) {
379
+ lines.push(import_path2.default.join(absRoot, e.path));
380
+ }
454
381
  return `<repo_structure>
455
- ${treeOutput}
382
+ ${lines.join("\n")}
456
383
  </repo_structure>
457
384
 
458
385
  <search_string>
@@ -461,9 +388,8 @@ ${searchTerm}
461
388
  ${budget}
462
389
  ${turnTag}`;
463
390
  } catch {
464
- const repoName = import_path.default.basename(repoRoot);
465
391
  return `<repo_structure>
466
- ${repoName}/
392
+ ${absRoot}
467
393
  </repo_structure>
468
394
 
469
395
  <search_string>
@@ -474,26 +400,32 @@ ${turnTag}`;
474
400
  }
475
401
  }
476
402
  function enforceContextLimit(messages, maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS) {
477
- const getTotalChars = () => messages.reduce((sum, m) => sum + m.content.length, 0);
403
+ const getTotalChars = () => messages.reduce((sum, m) => sum + getMessageSize(m), 0);
478
404
  if (getTotalChars() <= maxChars) {
479
405
  return messages;
480
406
  }
481
- const userIndices = [];
407
+ const truncatableIndices = [];
482
408
  let firstUserSkipped = false;
483
409
  for (let i = 0; i < messages.length; i++) {
484
- if (messages[i].role === "user") {
410
+ const m = messages[i];
411
+ if (m.role === "tool") {
412
+ truncatableIndices.push(i);
413
+ } else if (m.role === "user") {
485
414
  if (!firstUserSkipped) {
486
415
  firstUserSkipped = true;
487
416
  continue;
488
417
  }
489
- userIndices.push(i);
418
+ truncatableIndices.push(i);
490
419
  }
491
420
  }
492
- for (const idx of userIndices) {
421
+ for (const idx of truncatableIndices) {
493
422
  if (getTotalChars() <= maxChars) {
494
423
  break;
495
424
  }
496
- if (messages[idx].content !== TRUNCATED_MARKER) {
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) {
497
429
  messages[idx] = { role: "user", content: TRUNCATED_MARKER };
498
430
  }
499
431
  }
@@ -506,7 +438,7 @@ var import_openai = __toESM(require("openai"), 1);
506
438
  // package.json
507
439
  var package_default = {
508
440
  name: "@morphllm/morphsdk",
509
- version: "0.2.146",
441
+ version: "0.2.148",
510
442
  description: "TypeScript SDK and CLI for Morph Fast Apply integration",
511
443
  type: "module",
512
444
  main: "./dist/index.cjs",
@@ -745,9 +677,115 @@ var package_default = {
745
677
  var SDK_VERSION = package_default.version;
746
678
 
747
679
  // tools/warp_grep/agent/runner.ts
748
- var import_path2 = __toESM(require("path"), 1);
749
- var parser = new LLMResponseParser();
680
+ var import_path3 = __toESM(require("path"), 1);
750
681
  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
+ ];
751
789
  async function callModel(messages, model, options = {}) {
752
790
  const baseUrl = options.morphApiUrl || DEFAULT_API_URL;
753
791
  const apiKey = options.morphApiKey || process.env.MORPH_API_KEY || "";
@@ -768,8 +806,9 @@ async function callModel(messages, model, options = {}) {
768
806
  data = await client.chat.completions.create({
769
807
  model,
770
808
  temperature: 0,
771
- max_tokens: 1024,
809
+ max_tokens: 2048,
772
810
  messages,
811
+ tools: TOOL_SPECS,
773
812
  ...options.search_type ? { search_type: options.search_type } : {}
774
813
  });
775
814
  } catch (error) {
@@ -781,187 +820,87 @@ async function callModel(messages, model, options = {}) {
781
820
  throw error;
782
821
  }
783
822
  const choice = data?.choices?.[0];
784
- const content = choice?.message?.content;
785
- if (content && typeof content === "string") {
786
- return content;
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 };
787
838
  }
788
839
  if (attempt === MAX_EMPTY_RETRIES) {
789
840
  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;
793
841
  throw new Error(
794
- `Invalid response from model: content=${contentType}, finish_reason=${finishReason}, has_tool_calls=${hasToolCalls}, choices_length=${choicesLen}`
842
+ `Invalid response from model: no content and no tool_calls, finish_reason=${finishReason}`
795
843
  );
796
844
  }
797
845
  await new Promise((resolve) => setTimeout(resolve, 200));
798
846
  }
799
847
  throw new Error("Invalid response from model");
800
848
  }
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;
838
- }
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;
847
- }
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);
857
- }
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
- );
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;
885
875
  }
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);
876
+ case "glob": {
877
+ return toolGlob(provider, {
878
+ pattern: args.pattern,
879
+ path: args.path
880
+ });
891
881
  }
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 });
882
+ case "list_directory": {
883
+ const dirPath = extractPathFromCommand(args.command || ".");
884
+ return toolListDirectory(provider, { path: dirPath }, repoRoot);
896
885
  }
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}]`];
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));
945
892
  }
893
+ return toolRead(provider, readArgs);
946
894
  }
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}` })));
895
+ default:
896
+ return `Unknown tool: ${name}`;
951
897
  }
952
- timings.total_ms = Date.now() - totalStart;
953
- return {
954
- terminationReason: "completed",
955
- messages,
956
- finish: { payload, metadata: finishMeta, resolved },
957
- timings
958
- };
959
898
  }
960
899
  async function* runWarpGrepStreaming(config) {
961
900
  const totalStart = Date.now();
962
901
  const timeoutMs = config.timeout ?? AGENT_CONFIG.TIMEOUT_MS;
963
902
  const timings = { turns: [], timeout_ms: timeoutMs };
964
- const repoRoot = import_path2.default.resolve(config.repoRoot || process.cwd());
903
+ const repoRoot = import_path3.default.resolve(config.repoRoot || process.cwd());
965
904
  const model = config.model || DEFAULT_MODEL;
966
905
  const messages = [];
967
906
  const maxTurns = AGENT_CONFIG.MAX_TURNS;
@@ -977,7 +916,7 @@ async function* runWarpGrepStreaming(config) {
977
916
  const turnMetrics = { turn, morph_api_ms: 0, local_tools_ms: 0 };
978
917
  enforceContextLimit(messages);
979
918
  const modelCallStart = Date.now();
980
- const assistantContent = await callModel(messages, model, {
919
+ const response = await callModel(messages, model, {
981
920
  morphApiKey: config.morphApiKey,
982
921
  morphApiUrl: config.morphApiUrl,
983
922
  retryConfig: config.retryConfig,
@@ -985,90 +924,45 @@ async function* runWarpGrepStreaming(config) {
985
924
  search_type: config.search_type
986
925
  }).catch((e) => {
987
926
  const errMsg = e instanceof Error ? e.message : String(e);
988
- console.error(`[warp_grep:stream] Morph API call failed on turn ${turn}:`, errMsg);
927
+ console.error(`[warp_grep] Morph API call failed on turn ${turn}:`, errMsg);
989
928
  errors.push({ message: errMsg });
990
- return "";
929
+ return null;
991
930
  });
992
931
  turnMetrics.morph_api_ms = Date.now() - modelCallStart;
993
- if (!assistantContent) {
994
- console.error(`[warp_grep:stream] Empty response from Morph API on turn ${turn}. Errors so far:`, errors);
932
+ if (!response) {
995
933
  timings.turns.push(turnMetrics);
996
934
  break;
997
935
  }
998
- messages.push({ role: "assistant", content: assistantContent });
999
- const toolCalls = parser.parse(assistantContent);
936
+ const toolCalls = response.tool_calls;
937
+ messages.push({
938
+ role: "assistant",
939
+ content: response.content,
940
+ ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
941
+ });
1000
942
  if (toolCalls.length === 0) {
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" });
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." });
1003
945
  terminationReason = "terminated";
1004
946
  timings.turns.push(turnMetrics);
1005
947
  break;
1006
948
  }
1007
949
  yield {
1008
950
  turn,
1009
- toolCalls: toolCalls.map((c) => ({
1010
- name: c.name,
1011
- arguments: c.arguments ?? {}
951
+ toolCalls: toolCalls.map((tc) => ({
952
+ name: tc.function.name,
953
+ arguments: safeParseJSON(tc.function.arguments)
1012
954
  }))
1013
955
  };
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;
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);
1068
961
  finishMeta = { files };
1069
962
  terminationReason = "completed";
1070
963
  if (files.length === 0) {
1071
- const payload2 = textResult || "No relevant code found.";
964
+ const payload2 = filesStr || "No relevant code found.";
965
+ timings.turns.push(turnMetrics);
1072
966
  timings.total_ms = Date.now() - totalStart;
1073
967
  return {
1074
968
  terminationReason: "completed",
@@ -1077,8 +971,25 @@ async function* runWarpGrepStreaming(config) {
1077
971
  timings
1078
972
  };
1079
973
  }
974
+ timings.turns.push(turnMetrics);
1080
975
  break;
1081
976
  }
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);
1082
993
  }
1083
994
  if (terminationReason !== "completed" || !finishMeta) {
1084
995
  timings.total_ms = Date.now() - totalStart;
@@ -1096,17 +1007,22 @@ async function* runWarpGrepStreaming(config) {
1096
1007
  repoRoot,
1097
1008
  finishMeta.files,
1098
1009
  async (p, s, e) => {
1010
+ let resolvedPath = p;
1011
+ if (!p.startsWith(repoRoot)) {
1012
+ const relative = p.startsWith("/") ? p.slice(1) : p;
1013
+ resolvedPath = import_path3.default.join(repoRoot, relative);
1014
+ }
1099
1015
  try {
1100
- const rr = await provider.read({ path: p, start: s, end: e });
1016
+ const rr = await provider.read({ path: resolvedPath, start: s, end: e });
1101
1017
  return rr.lines.map((l) => {
1102
1018
  const idx = l.indexOf("|");
1103
1019
  return idx >= 0 ? l.slice(idx + 1) : l;
1104
1020
  });
1105
1021
  } catch (err) {
1106
1022
  const errorMsg = err instanceof Error ? err.message : String(err);
1107
- fileReadErrors.push({ path: p, error: errorMsg });
1108
- console.error(`[warp_grep] Failed to read file: ${p} - ${errorMsg}`);
1109
- return [`[couldn't find: ${p}]`];
1023
+ fileReadErrors.push({ path: resolvedPath, error: errorMsg });
1024
+ console.error(`[warp_grep] Failed to read file: ${resolvedPath} - ${errorMsg}`);
1025
+ return [`[couldn't find: ${resolvedPath}]`];
1110
1026
  }
1111
1027
  }
1112
1028
  );
@@ -1122,6 +1038,14 @@ async function* runWarpGrepStreaming(config) {
1122
1038
  timings
1123
1039
  };
1124
1040
  }
1041
+ async function runWarpGrep(config) {
1042
+ const gen = runWarpGrepStreaming(config);
1043
+ let result = await gen.next();
1044
+ while (!result.done) {
1045
+ result = await gen.next();
1046
+ }
1047
+ return result.value;
1048
+ }
1125
1049
  // Annotate the CommonJS export names for ESM import in node:
1126
1050
  0 && (module.exports = {
1127
1051
  runWarpGrep,