@morphllm/morphsdk 0.2.50 → 0.2.51

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 (62) hide show
  1. package/dist/{chunk-KH3RKPPO.js → chunk-2GZMA5OY.js} +4 -4
  2. package/dist/{chunk-DZRBTNQR.js → chunk-3IASMZXX.js} +5 -5
  3. package/dist/{chunk-NRWVMX4O.js → chunk-HBH4FWIZ.js} +18 -11
  4. package/dist/chunk-HBH4FWIZ.js.map +1 -0
  5. package/dist/{chunk-Z2FBMSNE.js → chunk-HQO45BAJ.js} +5 -1
  6. package/dist/chunk-HQO45BAJ.js.map +1 -0
  7. package/dist/{chunk-2TXLSKGU.js → chunk-KMWLHINT.js} +3 -3
  8. package/dist/{chunk-SWQPIKPY.js → chunk-LVPVVLTI.js} +70 -49
  9. package/dist/chunk-LVPVVLTI.js.map +1 -0
  10. package/dist/{chunk-4MELBN55.js → chunk-W3QUAOGV.js} +4 -4
  11. package/dist/{chunk-5ASRBH5I.js → chunk-X3WAPQSV.js} +4 -4
  12. package/dist/{chunk-MLDK7SCW.js → chunk-ZJIIICRA.js} +29 -7
  13. package/dist/chunk-ZJIIICRA.js.map +1 -0
  14. package/dist/client.cjs +116 -68
  15. package/dist/client.cjs.map +1 -1
  16. package/dist/client.js +9 -9
  17. package/dist/index.cjs +116 -68
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.js +9 -9
  20. package/dist/tools/warp_grep/agent/parser.cjs +69 -48
  21. package/dist/tools/warp_grep/agent/parser.cjs.map +1 -1
  22. package/dist/tools/warp_grep/agent/parser.d.ts +2 -0
  23. package/dist/tools/warp_grep/agent/parser.js +1 -1
  24. package/dist/tools/warp_grep/agent/runner.cjs +88 -62
  25. package/dist/tools/warp_grep/agent/runner.cjs.map +1 -1
  26. package/dist/tools/warp_grep/agent/runner.js +3 -3
  27. package/dist/tools/warp_grep/agent/types.cjs.map +1 -1
  28. package/dist/tools/warp_grep/agent/types.d.ts +1 -1
  29. package/dist/tools/warp_grep/anthropic.cjs +116 -68
  30. package/dist/tools/warp_grep/anthropic.cjs.map +1 -1
  31. package/dist/tools/warp_grep/anthropic.js +6 -6
  32. package/dist/tools/warp_grep/index.cjs +116 -68
  33. package/dist/tools/warp_grep/index.cjs.map +1 -1
  34. package/dist/tools/warp_grep/index.js +8 -8
  35. package/dist/tools/warp_grep/openai.cjs +116 -68
  36. package/dist/tools/warp_grep/openai.cjs.map +1 -1
  37. package/dist/tools/warp_grep/openai.js +6 -6
  38. package/dist/tools/warp_grep/providers/local.cjs +28 -6
  39. package/dist/tools/warp_grep/providers/local.cjs.map +1 -1
  40. package/dist/tools/warp_grep/providers/local.js +1 -1
  41. package/dist/tools/warp_grep/providers/types.cjs.map +1 -1
  42. package/dist/tools/warp_grep/providers/types.d.ts +2 -0
  43. package/dist/tools/warp_grep/tools/grep.cjs +3 -0
  44. package/dist/tools/warp_grep/tools/grep.cjs.map +1 -1
  45. package/dist/tools/warp_grep/tools/grep.js +3 -0
  46. package/dist/tools/warp_grep/tools/grep.js.map +1 -1
  47. package/dist/tools/warp_grep/tools/read.cjs +4 -0
  48. package/dist/tools/warp_grep/tools/read.cjs.map +1 -1
  49. package/dist/tools/warp_grep/tools/read.js +1 -1
  50. package/dist/tools/warp_grep/vercel.cjs +116 -68
  51. package/dist/tools/warp_grep/vercel.cjs.map +1 -1
  52. package/dist/tools/warp_grep/vercel.js +6 -6
  53. package/package.json +1 -1
  54. package/dist/chunk-MLDK7SCW.js.map +0 -1
  55. package/dist/chunk-NRWVMX4O.js.map +0 -1
  56. package/dist/chunk-SWQPIKPY.js.map +0 -1
  57. package/dist/chunk-Z2FBMSNE.js.map +0 -1
  58. /package/dist/{chunk-KH3RKPPO.js.map → chunk-2GZMA5OY.js.map} +0 -0
  59. /package/dist/{chunk-DZRBTNQR.js.map → chunk-3IASMZXX.js.map} +0 -0
  60. /package/dist/{chunk-2TXLSKGU.js.map → chunk-KMWLHINT.js.map} +0 -0
  61. /package/dist/{chunk-4MELBN55.js.map → chunk-W3QUAOGV.js.map} +0 -0
  62. /package/dist/{chunk-5ASRBH5I.js.map → chunk-X3WAPQSV.js.map} +0 -0
package/dist/index.js CHANGED
@@ -7,21 +7,21 @@ import {
7
7
  MorphClient,
8
8
  OpenAIToolFactory,
9
9
  VercelToolFactory
10
- } from "./chunk-DZRBTNQR.js";
11
- import "./chunk-5ASRBH5I.js";
12
- import "./chunk-KH3RKPPO.js";
13
- import "./chunk-4MELBN55.js";
10
+ } from "./chunk-3IASMZXX.js";
11
+ import "./chunk-X3WAPQSV.js";
12
+ import "./chunk-2GZMA5OY.js";
13
+ import "./chunk-W3QUAOGV.js";
14
14
  import {
15
15
  WarpGrepClient
16
- } from "./chunk-2TXLSKGU.js";
17
- import "./chunk-NRWVMX4O.js";
16
+ } from "./chunk-KMWLHINT.js";
17
+ import "./chunk-HBH4FWIZ.js";
18
18
  import "./chunk-EK7OQPWD.js";
19
- import "./chunk-Z2FBMSNE.js";
20
- import "./chunk-SWQPIKPY.js";
19
+ import "./chunk-HQO45BAJ.js";
20
+ import "./chunk-LVPVVLTI.js";
21
21
  import "./chunk-WETRQJGU.js";
22
22
  import {
23
23
  LocalRipgrepProvider
24
- } from "./chunk-MLDK7SCW.js";
24
+ } from "./chunk-ZJIIICRA.js";
25
25
  import "./chunk-G2RSY56Q.js";
26
26
  import "./chunk-SMGZ6A64.js";
27
27
  import "./chunk-TPP2UGQP.js";
@@ -80,24 +80,23 @@ var LLMResponseParser = class {
80
80
  const lines = preprocessText(text);
81
81
  const commands = [];
82
82
  let finishAccumulator = null;
83
- lines.forEach((line, idx) => {
83
+ lines.forEach((line) => {
84
84
  if (!line || line.startsWith("#")) return;
85
- const ctx = { lineNumber: idx + 1, raw: line };
86
- const parts = this.splitLine(line, ctx);
85
+ const parts = this.splitLine(line);
87
86
  if (parts.length === 0) return;
88
87
  const cmd = parts[0];
89
88
  switch (cmd) {
90
89
  case "analyse":
91
- this.handleAnalyse(parts, ctx, commands);
90
+ this.handleAnalyse(parts, line, commands);
92
91
  break;
93
92
  case "grep":
94
- this.handleGrep(parts, ctx, commands);
93
+ this.handleGrep(parts, line, commands);
95
94
  break;
96
95
  case "read":
97
- this.handleRead(parts, ctx, commands);
96
+ this.handleRead(parts, line, commands);
98
97
  break;
99
98
  case "finish":
100
- finishAccumulator = this.handleFinish(parts, ctx, finishAccumulator);
99
+ finishAccumulator = this.handleFinish(parts, line, commands, finishAccumulator);
101
100
  break;
102
101
  default:
103
102
  break;
@@ -114,53 +113,68 @@ var LLMResponseParser = class {
114
113
  }
115
114
  return commands;
116
115
  }
117
- splitLine(line, ctx) {
118
- try {
119
- const parts = [];
120
- let current = "";
121
- let inSingle = false;
122
- for (let i = 0; i < line.length; i++) {
123
- const ch = line[i];
124
- if (ch === "'" && line[i - 1] !== "\\") {
125
- inSingle = !inSingle;
126
- current += ch;
127
- } else if (!inSingle && /\s/.test(ch)) {
128
- if (current) {
129
- parts.push(current);
130
- current = "";
131
- }
132
- } else {
133
- current += ch;
116
+ splitLine(line) {
117
+ const parts = [];
118
+ let current = "";
119
+ let inSingle = false;
120
+ for (let i = 0; i < line.length; i++) {
121
+ const ch = line[i];
122
+ if (ch === "'" && line[i - 1] !== "\\") {
123
+ inSingle = !inSingle;
124
+ current += ch;
125
+ } else if (!inSingle && /\s/.test(ch)) {
126
+ if (current) {
127
+ parts.push(current);
128
+ current = "";
134
129
  }
130
+ } else {
131
+ current += ch;
135
132
  }
136
- if (current) parts.push(current);
137
- return parts;
138
- } catch {
139
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: Unable to parse line.`);
140
133
  }
134
+ if (current) parts.push(current);
135
+ return parts;
141
136
  }
142
- handleAnalyse(parts, ctx, commands) {
137
+ /** Helper to create a _skip tool call with an error message */
138
+ skip(message) {
139
+ return { name: "_skip", arguments: { message } };
140
+ }
141
+ handleAnalyse(parts, rawLine, commands) {
143
142
  if (parts.length < 2) {
144
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: analyse requires <path>`);
143
+ commands.push(this.skip(
144
+ `[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: analyse <path> [pattern]. Example: analyse src/`
145
+ ));
146
+ return;
145
147
  }
146
148
  const path = parts[1];
147
149
  const pattern = parts[2]?.replace(/^"|"$/g, "") ?? null;
148
150
  commands.push({ name: "analyse", arguments: { path, pattern } });
149
151
  }
150
152
  // no glob tool in MCP
151
- handleGrep(parts, ctx, commands) {
153
+ handleGrep(parts, rawLine, commands) {
152
154
  if (parts.length < 3) {
153
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: grep requires '<pattern>' and <path>`);
155
+ commands.push(this.skip(
156
+ `[SKIPPED] Your command "${rawLine}" is missing arguments. Correct format: grep '<pattern>' <path>. Example: grep 'TODO' src/`
157
+ ));
158
+ return;
154
159
  }
155
- const pat = parts[1];
156
- if (!pat.startsWith("'") || !pat.endsWith("'")) {
157
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: grep pattern must be single-quoted`);
160
+ let pat = parts[1];
161
+ if (pat.startsWith("'") && pat.endsWith("'")) {
162
+ pat = pat.slice(1, -1);
158
163
  }
159
- commands.push({ name: "grep", arguments: { pattern: pat.slice(1, -1), path: parts[2] } });
164
+ if (!pat) {
165
+ commands.push(this.skip(
166
+ `[SKIPPED] Your command "${rawLine}" has an empty pattern. Provide a non-empty search pattern. Example: grep 'function' src/`
167
+ ));
168
+ return;
169
+ }
170
+ commands.push({ name: "grep", arguments: { pattern: pat, path: parts[2] } });
160
171
  }
161
- handleRead(parts, ctx, commands) {
172
+ handleRead(parts, rawLine, commands) {
162
173
  if (parts.length < 2) {
163
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: read requires <path> or <path>:<start-end>`);
174
+ commands.push(this.skip(
175
+ `[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: read <path> or read <path>:<start>-<end>. Example: read src/index.ts:1-50`
176
+ ));
177
+ return;
164
178
  }
165
179
  const spec = parts[1];
166
180
  const rangeIdx = spec.indexOf(":");
@@ -168,31 +182,38 @@ var LLMResponseParser = class {
168
182
  commands.push({ name: "read", arguments: { path: spec } });
169
183
  return;
170
184
  }
171
- const path = spec.slice(0, rangeIdx);
185
+ const filePath = spec.slice(0, rangeIdx);
172
186
  const range = spec.slice(rangeIdx + 1);
173
187
  const [s, e] = range.split("-").map((v) => parseInt(v, 10));
174
188
  if (!Number.isFinite(s) || !Number.isFinite(e)) {
175
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: invalid read range '${range}'`);
189
+ commands.push({ name: "read", arguments: { path: filePath } });
190
+ return;
176
191
  }
177
- commands.push({ name: "read", arguments: { path, start: s, end: e } });
192
+ commands.push({ name: "read", arguments: { path: filePath, start: s, end: e } });
178
193
  }
179
- handleFinish(parts, ctx, acc) {
194
+ handleFinish(parts, rawLine, commands, acc) {
180
195
  const map = acc ?? /* @__PURE__ */ new Map();
181
196
  const args = parts.slice(1);
182
197
  for (const token of args) {
183
- const [path, rangesText] = token.split(":", 2);
184
- if (!path || !rangesText) {
185
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: invalid finish token '${token}'`);
198
+ const [filePath, rangesText] = token.split(":", 2);
199
+ if (!filePath || !rangesText) {
200
+ commands.push(this.skip(
201
+ `[SKIPPED] Invalid finish token "${token}". Correct format: finish <path>:<start>-<end>. Example: finish src/index.ts:1-50`
202
+ ));
203
+ continue;
186
204
  }
187
205
  const rangeSpecs = rangesText.split(",").filter(Boolean);
188
206
  for (const spec of rangeSpecs) {
189
207
  const [s, e] = spec.split("-").map((v) => parseInt(v, 10));
190
208
  if (!Number.isFinite(s) || !Number.isFinite(e) || e < s) {
191
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: invalid range '${spec}'`);
209
+ commands.push(this.skip(
210
+ `[SKIPPED] Invalid range "${spec}" in "${token}". Ranges must be <start>-<end> where start <= end. Example: 1-50`
211
+ ));
212
+ continue;
192
213
  }
193
- const arr = map.get(path) ?? [];
214
+ const arr = map.get(filePath) ?? [];
194
215
  arr.push([s, e]);
195
- map.set(path, arr);
216
+ map.set(filePath, arr);
196
217
  }
197
218
  }
198
219
  return map;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../tools/warp_grep/agent/parser.ts"],"sourcesContent":["// Parses assistant lines into structured tool calls\nimport type { ToolCall } from './types.js';\n\nexport class LLMResponseParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LLMResponseParseError';\n }\n}\n\ntype LineContext = { lineNumber: number; raw: string };\n\n// Valid tool command names\nconst VALID_COMMANDS = ['analyse', 'grep', 'read', 'finish'];\n\n/**\n * Preprocesses text to handle XML tags:\n * 1. Removes <think>...</think> blocks entirely\n * 2. Extracts content from <tool>...</tool> or <tool_call>...</tool_call> tags\n * 3. Passes through raw tool calls (lines starting with valid commands)\n * 4. Discards unclosed <tool...> tags\n */\nfunction preprocessText(text: string): string[] {\n // Step 1: Remove <think>...</think> blocks (including multiline)\n let processed = text.replace(/<think>[\\s\\S]*?<\\/think>/gi, '');\n \n // Step 2: Check for unclosed <tool or <tool_call tags and discard them\n // Find all opening tags and their positions\n const openingTagRegex = /<tool_call>|<tool>/gi;\n const closingTagRegex = /<\\/tool_call>|<\\/tool>/gi;\n \n // Count opening and closing tags\n const openingMatches = processed.match(openingTagRegex) || [];\n const closingMatches = processed.match(closingTagRegex) || [];\n \n // If there are more opening than closing tags, we have unclosed tags\n // In that case, only process complete tag pairs\n if (openingMatches.length > closingMatches.length) {\n // Remove any content after the last complete closing tag\n const lastClosingMatch = /<\\/tool_call>|<\\/tool>/gi;\n let lastClosingIndex = -1;\n let match;\n while ((match = lastClosingMatch.exec(processed)) !== null) {\n lastClosingIndex = match.index + match[0].length;\n }\n if (lastClosingIndex > 0) {\n processed = processed.slice(0, lastClosingIndex);\n }\n }\n \n // Step 3: Extract content from <tool_call>...</tool_call> and <tool>...</tool> tags\n const toolCallLines: string[] = [];\n const toolTagRegex = /<tool_call>([\\s\\S]*?)<\\/tool_call>|<tool>([\\s\\S]*?)<\\/tool>/gi;\n let tagMatch;\n \n while ((tagMatch = toolTagRegex.exec(processed)) !== null) {\n const content = (tagMatch[1] || tagMatch[2] || '').trim();\n if (content) {\n // Split content by newlines in case there are multiple tool calls in one tag\n const lines = content.split(/\\r?\\n/).map(l => l.trim()).filter(l => l);\n toolCallLines.push(...lines);\n }\n }\n \n // Step 4: Also extract raw tool calls (lines starting with valid commands)\n // This provides backwards compatibility\n const allLines = processed.split(/\\r?\\n/).map(l => l.trim());\n for (const line of allLines) {\n if (!line) continue;\n \n // Skip lines that are inside XML tags (already processed above)\n if (line.startsWith('<')) continue;\n \n // Check if line starts with a valid command\n const firstWord = line.split(/\\s/)[0];\n if (VALID_COMMANDS.includes(firstWord)) {\n // Avoid duplicates\n if (!toolCallLines.includes(line)) {\n toolCallLines.push(line);\n }\n }\n }\n \n return toolCallLines;\n}\n\nexport class LLMResponseParser {\n private readonly finishSpecSplitRe = /,(?=[^,\\s]+:)/;\n\n parse(text: string): ToolCall[] {\n if (typeof text !== 'string') {\n throw new TypeError('Command text must be a string.');\n }\n \n // Preprocess to handle XML tags\n const lines = preprocessText(text);\n \n const commands: ToolCall[] = [];\n let finishAccumulator: Map<string, number[][]> | null = null;\n\n lines.forEach((line, idx) => {\n if (!line || line.startsWith('#')) return;\n const ctx: LineContext = { lineNumber: idx + 1, raw: line };\n const parts = this.splitLine(line, ctx);\n if (parts.length === 0) return;\n const cmd = parts[0];\n switch (cmd) {\n case 'analyse':\n this.handleAnalyse(parts, ctx, commands);\n break;\n case 'grep':\n this.handleGrep(parts, ctx, commands);\n break;\n case 'read':\n this.handleRead(parts, ctx, commands);\n break;\n case 'finish':\n finishAccumulator = this.handleFinish(parts, ctx, finishAccumulator);\n break;\n default:\n // Silently ignore unknown commands after preprocessing\n // (they might be remnants of XML or other content)\n break;\n }\n });\n\n if (finishAccumulator) {\n const map = finishAccumulator as Map<string, number[][]>;\n const entries = [...map.entries()];\n const filesPayload = entries.map(([path, ranges]) => ({\n path,\n lines: [...ranges].sort((a, b) => a[0] - b[0]) as Array<[number, number]>,\n }));\n commands.push({ name: 'finish', arguments: { files: filesPayload } });\n }\n return commands;\n }\n\n private splitLine(line: string, ctx: LineContext): string[] {\n try {\n // Split by whitespace but keep quoted blocks as one\n const parts: string[] = [];\n let current = '';\n let inSingle = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (ch === \"'\" && line[i - 1] !== '\\\\') {\n inSingle = !inSingle;\n current += ch;\n } else if (!inSingle && /\\s/.test(ch)) {\n if (current) {\n parts.push(current);\n current = '';\n }\n } else {\n current += ch;\n }\n }\n if (current) parts.push(current);\n return parts;\n } catch {\n throw new LLMResponseParseError(`Line ${ctx.lineNumber}: Unable to parse line.`);\n }\n }\n\n private handleAnalyse(parts: string[], ctx: LineContext, commands: ToolCall[]) {\n // analyse <path> [pattern]\n if (parts.length < 2) {\n throw new LLMResponseParseError(`Line ${ctx.lineNumber}: analyse requires <path>`);\n }\n const path = parts[1];\n const pattern = parts[2]?.replace(/^\"|\"$/g, '') ?? null;\n commands.push({ name: 'analyse', arguments: { path, pattern } });\n }\n\n // no glob tool in MCP\n\n private handleGrep(parts: string[], ctx: LineContext, commands: ToolCall[]) {\n // grep '<pattern>' <path>\n if (parts.length < 3) {\n throw new LLMResponseParseError(`Line ${ctx.lineNumber}: grep requires '<pattern>' and <path>`);\n }\n const pat = parts[1];\n if (!pat.startsWith(\"'\") || !pat.endsWith(\"'\")) {\n throw new LLMResponseParseError(`Line ${ctx.lineNumber}: grep pattern must be single-quoted`);\n }\n commands.push({ name: 'grep', arguments: { pattern: pat.slice(1, -1), path: parts[2] } });\n }\n\n private handleRead(parts: string[], ctx: LineContext, commands: ToolCall[]) {\n // read <path>[:start-end]\n if (parts.length < 2) {\n throw new LLMResponseParseError(`Line ${ctx.lineNumber}: read requires <path> or <path>:<start-end>`);\n }\n const spec = parts[1];\n const rangeIdx = spec.indexOf(':');\n if (rangeIdx === -1) {\n commands.push({ name: 'read', arguments: { path: spec } });\n return;\n }\n const path = spec.slice(0, rangeIdx);\n const range = spec.slice(rangeIdx + 1);\n const [s, e] = range.split('-').map(v => parseInt(v, 10));\n if (!Number.isFinite(s) || !Number.isFinite(e)) {\n throw new LLMResponseParseError(`Line ${ctx.lineNumber}: invalid read range '${range}'`);\n }\n commands.push({ name: 'read', arguments: { path, start: s, end: e } });\n }\n\n private handleFinish(parts: string[], ctx: LineContext, acc: Map<string, number[][]> | null) {\n // finish file1:1-10,20-30 file2:5-7\n const map = acc ?? new Map<string, number[][]>();\n const args = parts.slice(1);\n for (const token of args) {\n const [path, rangesText] = token.split(':', 2);\n if (!path || !rangesText) {\n throw new LLMResponseParseError(`Line ${ctx.lineNumber}: invalid finish token '${token}'`);\n }\n const rangeSpecs = rangesText.split(',').filter(Boolean);\n for (const spec of rangeSpecs) {\n const [s, e] = spec.split('-').map(v => parseInt(v, 10));\n if (!Number.isFinite(s) || !Number.isFinite(e) || e < s) {\n throw new LLMResponseParseError(`Line ${ctx.lineNumber}: invalid range '${spec}'`);\n }\n const arr = map.get(path) ?? [];\n arr.push([s, e]);\n map.set(path, arr);\n }\n }\n return map;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../tools/warp_grep/agent/parser.ts"],"sourcesContent":["// Parses assistant lines into structured tool calls\nimport type { ToolCall } from './types.js';\n\n// Keep for backwards compatibility - no longer thrown, but exported for tests\nexport class LLMResponseParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LLMResponseParseError';\n }\n}\n\n// Valid tool command names\nconst VALID_COMMANDS = ['analyse', 'grep', 'read', 'finish'];\n\n/**\n * Preprocesses text to handle XML tags:\n * 1. Removes <think>...</think> blocks entirely\n * 2. Extracts content from <tool>...</tool> or <tool_call>...</tool_call> tags\n * 3. Passes through raw tool calls (lines starting with valid commands)\n * 4. Discards unclosed <tool...> tags\n */\nfunction preprocessText(text: string): string[] {\n // Step 1: Remove <think>...</think> blocks (including multiline)\n let processed = text.replace(/<think>[\\s\\S]*?<\\/think>/gi, '');\n \n // Step 2: Check for unclosed <tool or <tool_call tags and discard them\n // Find all opening tags and their positions\n const openingTagRegex = /<tool_call>|<tool>/gi;\n const closingTagRegex = /<\\/tool_call>|<\\/tool>/gi;\n \n // Count opening and closing tags\n const openingMatches = processed.match(openingTagRegex) || [];\n const closingMatches = processed.match(closingTagRegex) || [];\n \n // If there are more opening than closing tags, we have unclosed tags\n // In that case, only process complete tag pairs\n if (openingMatches.length > closingMatches.length) {\n // Remove any content after the last complete closing tag\n const lastClosingMatch = /<\\/tool_call>|<\\/tool>/gi;\n let lastClosingIndex = -1;\n let match;\n while ((match = lastClosingMatch.exec(processed)) !== null) {\n lastClosingIndex = match.index + match[0].length;\n }\n if (lastClosingIndex > 0) {\n processed = processed.slice(0, lastClosingIndex);\n }\n }\n \n // Step 3: Extract content from <tool_call>...</tool_call> and <tool>...</tool> tags\n const toolCallLines: string[] = [];\n const toolTagRegex = /<tool_call>([\\s\\S]*?)<\\/tool_call>|<tool>([\\s\\S]*?)<\\/tool>/gi;\n let tagMatch;\n \n while ((tagMatch = toolTagRegex.exec(processed)) !== null) {\n const content = (tagMatch[1] || tagMatch[2] || '').trim();\n if (content) {\n // Split content by newlines in case there are multiple tool calls in one tag\n const lines = content.split(/\\r?\\n/).map(l => l.trim()).filter(l => l);\n toolCallLines.push(...lines);\n }\n }\n \n // Step 4: Also extract raw tool calls (lines starting with valid commands)\n // This provides backwards compatibility\n const allLines = processed.split(/\\r?\\n/).map(l => l.trim());\n for (const line of allLines) {\n if (!line) continue;\n \n // Skip lines that are inside XML tags (already processed above)\n if (line.startsWith('<')) continue;\n \n // Check if line starts with a valid command\n const firstWord = line.split(/\\s/)[0];\n if (VALID_COMMANDS.includes(firstWord)) {\n // Avoid duplicates\n if (!toolCallLines.includes(line)) {\n toolCallLines.push(line);\n }\n }\n }\n \n return toolCallLines;\n}\n\nexport class LLMResponseParser {\n private readonly finishSpecSplitRe = /,(?=[^,\\s]+:)/;\n\n parse(text: string): ToolCall[] {\n if (typeof text !== 'string') {\n // no way we hit this, but sure, we can throw here\n throw new TypeError('Command text must be a string.');\n }\n \n // Preprocess to handle XML tags\n const lines = preprocessText(text);\n \n const commands: ToolCall[] = [];\n let finishAccumulator: Map<string, number[][]> | null = null;\n\n lines.forEach((line) => {\n if (!line || line.startsWith('#')) return;\n const parts = this.splitLine(line);\n if (parts.length === 0) return;\n const cmd = parts[0];\n switch (cmd) {\n case 'analyse':\n this.handleAnalyse(parts, line, commands);\n break;\n case 'grep':\n this.handleGrep(parts, line, commands);\n break;\n case 'read':\n this.handleRead(parts, line, commands);\n break;\n case 'finish':\n finishAccumulator = this.handleFinish(parts, line, commands, finishAccumulator);\n break;\n default:\n // Silently ignore unknown commands after preprocessing\n // (they might be remnants of XML or other content)\n break;\n }\n });\n\n if (finishAccumulator) {\n const map = finishAccumulator as Map<string, number[][]>;\n const entries = [...map.entries()];\n const filesPayload = entries.map(([path, ranges]) => ({\n path,\n lines: [...ranges].sort((a, b) => a[0] - b[0]) as Array<[number, number]>,\n }));\n commands.push({ name: 'finish', arguments: { files: filesPayload } });\n }\n return commands;\n }\n\n private splitLine(line: string): string[] {\n // Split by whitespace but keep quoted blocks as one\n const parts: string[] = [];\n let current = '';\n let inSingle = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (ch === \"'\" && line[i - 1] !== '\\\\') {\n inSingle = !inSingle;\n current += ch;\n } else if (!inSingle && /\\s/.test(ch)) {\n if (current) {\n parts.push(current);\n current = '';\n }\n } else {\n current += ch;\n }\n }\n if (current) parts.push(current);\n return parts;\n }\n\n /** Helper to create a _skip tool call with an error message */\n private skip(message: string): ToolCall {\n return { name: '_skip', arguments: { message } };\n }\n\n private handleAnalyse(parts: string[], rawLine: string, commands: ToolCall[]) {\n // analyse <path> [pattern]\n if (parts.length < 2) {\n commands.push(this.skip(\n `[SKIPPED] Your command \"${rawLine}\" is missing a path. ` +\n `Correct format: analyse <path> [pattern]. Example: analyse src/`\n ));\n return;\n }\n const path = parts[1];\n const pattern = parts[2]?.replace(/^\"|\"$/g, '') ?? null;\n commands.push({ name: 'analyse', arguments: { path, pattern } });\n }\n\n // no glob tool in MCP\n\n private handleGrep(parts: string[], rawLine: string, commands: ToolCall[]) {\n // grep '<pattern>' <path>\n if (parts.length < 3) {\n commands.push(this.skip(\n `[SKIPPED] Your command \"${rawLine}\" is missing arguments. ` +\n `Correct format: grep '<pattern>' <path>. Example: grep 'TODO' src/`\n ));\n return;\n }\n let pat = parts[1];\n // Be lenient: accept unquoted patterns by treating the first arg as the pattern\n if (pat.startsWith(\"'\") && pat.endsWith(\"'\")) {\n pat = pat.slice(1, -1);\n }\n // If pattern is empty after processing, skip\n if (!pat) {\n commands.push(this.skip(\n `[SKIPPED] Your command \"${rawLine}\" has an empty pattern. ` +\n `Provide a non-empty search pattern. Example: grep 'function' src/`\n ));\n return;\n }\n commands.push({ name: 'grep', arguments: { pattern: pat, path: parts[2] } });\n }\n\n private handleRead(parts: string[], rawLine: string, commands: ToolCall[]) {\n // read <path>[:start-end]\n if (parts.length < 2) {\n commands.push(this.skip(\n `[SKIPPED] Your command \"${rawLine}\" is missing a path. ` +\n `Correct format: read <path> or read <path>:<start>-<end>. Example: read src/index.ts:1-50`\n ));\n return;\n }\n const spec = parts[1];\n const rangeIdx = spec.indexOf(':');\n if (rangeIdx === -1) {\n commands.push({ name: 'read', arguments: { path: spec } });\n return;\n }\n const filePath = spec.slice(0, rangeIdx);\n const range = spec.slice(rangeIdx + 1);\n const [s, e] = range.split('-').map(v => parseInt(v, 10));\n // If range is invalid, fallback to reading the whole file\n if (!Number.isFinite(s) || !Number.isFinite(e)) {\n commands.push({ name: 'read', arguments: { path: filePath } });\n return;\n }\n commands.push({ name: 'read', arguments: { path: filePath, start: s, end: e } });\n }\n\n private handleFinish(parts: string[], rawLine: string, commands: ToolCall[], acc: Map<string, number[][]> | null) {\n // finish file1:1-10,20-30 file2:5-7\n const map = acc ?? new Map<string, number[][]>();\n const args = parts.slice(1);\n for (const token of args) {\n const [filePath, rangesText] = token.split(':', 2);\n if (!filePath || !rangesText) {\n // Skip this malformed token, continue processing others\n commands.push(this.skip(\n `[SKIPPED] Invalid finish token \"${token}\". ` +\n `Correct format: finish <path>:<start>-<end>. Example: finish src/index.ts:1-50`\n ));\n continue;\n }\n const rangeSpecs = rangesText.split(',').filter(Boolean);\n for (const spec of rangeSpecs) {\n const [s, e] = spec.split('-').map(v => parseInt(v, 10));\n if (!Number.isFinite(s) || !Number.isFinite(e) || e < s) {\n // Skip this invalid range, continue with others\n commands.push(this.skip(\n `[SKIPPED] Invalid range \"${spec}\" in \"${token}\". ` +\n `Ranges must be <start>-<end> where start <= end. Example: 1-50`\n ));\n continue;\n }\n const arr = map.get(filePath) ?? [];\n arr.push([s, e]);\n map.set(filePath, arr);\n }\n }\n return map;\n }\n}\n"],"mappings":"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","names":[]}
@@ -7,6 +7,8 @@ declare class LLMResponseParser {
7
7
  private readonly finishSpecSplitRe;
8
8
  parse(text: string): ToolCall[];
9
9
  private splitLine;
10
+ /** Helper to create a _skip tool call with an error message */
11
+ private skip;
10
12
  private handleAnalyse;
11
13
  private handleGrep;
12
14
  private handleRead;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  LLMResponseParseError,
3
3
  LLMResponseParser
4
- } from "../../../chunk-SWQPIKPY.js";
4
+ } from "../../../chunk-LVPVVLTI.js";
5
5
  import "../../../chunk-PZ5AY32C.js";
6
6
  export {
7
7
  LLMResponseParseError,
@@ -247,12 +247,6 @@ function getSystemPrompt() {
247
247
  }
248
248
 
249
249
  // tools/warp_grep/agent/parser.ts
250
- var LLMResponseParseError = class extends Error {
251
- constructor(message) {
252
- super(message);
253
- this.name = "LLMResponseParseError";
254
- }
255
- };
256
250
  var VALID_COMMANDS = ["analyse", "grep", "read", "finish"];
257
251
  function preprocessText(text) {
258
252
  let processed = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
@@ -303,24 +297,23 @@ var LLMResponseParser = class {
303
297
  const lines = preprocessText(text);
304
298
  const commands = [];
305
299
  let finishAccumulator = null;
306
- lines.forEach((line, idx) => {
300
+ lines.forEach((line) => {
307
301
  if (!line || line.startsWith("#")) return;
308
- const ctx = { lineNumber: idx + 1, raw: line };
309
- const parts = this.splitLine(line, ctx);
302
+ const parts = this.splitLine(line);
310
303
  if (parts.length === 0) return;
311
304
  const cmd = parts[0];
312
305
  switch (cmd) {
313
306
  case "analyse":
314
- this.handleAnalyse(parts, ctx, commands);
307
+ this.handleAnalyse(parts, line, commands);
315
308
  break;
316
309
  case "grep":
317
- this.handleGrep(parts, ctx, commands);
310
+ this.handleGrep(parts, line, commands);
318
311
  break;
319
312
  case "read":
320
- this.handleRead(parts, ctx, commands);
313
+ this.handleRead(parts, line, commands);
321
314
  break;
322
315
  case "finish":
323
- finishAccumulator = this.handleFinish(parts, ctx, finishAccumulator);
316
+ finishAccumulator = this.handleFinish(parts, line, commands, finishAccumulator);
324
317
  break;
325
318
  default:
326
319
  break;
@@ -337,53 +330,68 @@ var LLMResponseParser = class {
337
330
  }
338
331
  return commands;
339
332
  }
340
- splitLine(line, ctx) {
341
- try {
342
- const parts = [];
343
- let current = "";
344
- let inSingle = false;
345
- for (let i = 0; i < line.length; i++) {
346
- const ch = line[i];
347
- if (ch === "'" && line[i - 1] !== "\\") {
348
- inSingle = !inSingle;
349
- current += ch;
350
- } else if (!inSingle && /\s/.test(ch)) {
351
- if (current) {
352
- parts.push(current);
353
- current = "";
354
- }
355
- } else {
356
- current += ch;
333
+ splitLine(line) {
334
+ const parts = [];
335
+ let current = "";
336
+ let inSingle = false;
337
+ for (let i = 0; i < line.length; i++) {
338
+ const ch = line[i];
339
+ if (ch === "'" && line[i - 1] !== "\\") {
340
+ inSingle = !inSingle;
341
+ current += ch;
342
+ } else if (!inSingle && /\s/.test(ch)) {
343
+ if (current) {
344
+ parts.push(current);
345
+ current = "";
357
346
  }
347
+ } else {
348
+ current += ch;
358
349
  }
359
- if (current) parts.push(current);
360
- return parts;
361
- } catch {
362
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: Unable to parse line.`);
363
350
  }
351
+ if (current) parts.push(current);
352
+ return parts;
353
+ }
354
+ /** Helper to create a _skip tool call with an error message */
355
+ skip(message) {
356
+ return { name: "_skip", arguments: { message } };
364
357
  }
365
- handleAnalyse(parts, ctx, commands) {
358
+ handleAnalyse(parts, rawLine, commands) {
366
359
  if (parts.length < 2) {
367
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: analyse requires <path>`);
360
+ commands.push(this.skip(
361
+ `[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: analyse <path> [pattern]. Example: analyse src/`
362
+ ));
363
+ return;
368
364
  }
369
365
  const path2 = parts[1];
370
366
  const pattern = parts[2]?.replace(/^"|"$/g, "") ?? null;
371
367
  commands.push({ name: "analyse", arguments: { path: path2, pattern } });
372
368
  }
373
369
  // no glob tool in MCP
374
- handleGrep(parts, ctx, commands) {
370
+ handleGrep(parts, rawLine, commands) {
375
371
  if (parts.length < 3) {
376
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: grep requires '<pattern>' and <path>`);
372
+ commands.push(this.skip(
373
+ `[SKIPPED] Your command "${rawLine}" is missing arguments. Correct format: grep '<pattern>' <path>. Example: grep 'TODO' src/`
374
+ ));
375
+ return;
377
376
  }
378
- const pat = parts[1];
379
- if (!pat.startsWith("'") || !pat.endsWith("'")) {
380
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: grep pattern must be single-quoted`);
377
+ let pat = parts[1];
378
+ if (pat.startsWith("'") && pat.endsWith("'")) {
379
+ pat = pat.slice(1, -1);
381
380
  }
382
- commands.push({ name: "grep", arguments: { pattern: pat.slice(1, -1), path: parts[2] } });
381
+ if (!pat) {
382
+ commands.push(this.skip(
383
+ `[SKIPPED] Your command "${rawLine}" has an empty pattern. Provide a non-empty search pattern. Example: grep 'function' src/`
384
+ ));
385
+ return;
386
+ }
387
+ commands.push({ name: "grep", arguments: { pattern: pat, path: parts[2] } });
383
388
  }
384
- handleRead(parts, ctx, commands) {
389
+ handleRead(parts, rawLine, commands) {
385
390
  if (parts.length < 2) {
386
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: read requires <path> or <path>:<start-end>`);
391
+ commands.push(this.skip(
392
+ `[SKIPPED] Your command "${rawLine}" is missing a path. Correct format: read <path> or read <path>:<start>-<end>. Example: read src/index.ts:1-50`
393
+ ));
394
+ return;
387
395
  }
388
396
  const spec = parts[1];
389
397
  const rangeIdx = spec.indexOf(":");
@@ -391,31 +399,38 @@ var LLMResponseParser = class {
391
399
  commands.push({ name: "read", arguments: { path: spec } });
392
400
  return;
393
401
  }
394
- const path2 = spec.slice(0, rangeIdx);
402
+ const filePath = spec.slice(0, rangeIdx);
395
403
  const range = spec.slice(rangeIdx + 1);
396
404
  const [s, e] = range.split("-").map((v) => parseInt(v, 10));
397
405
  if (!Number.isFinite(s) || !Number.isFinite(e)) {
398
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: invalid read range '${range}'`);
406
+ commands.push({ name: "read", arguments: { path: filePath } });
407
+ return;
399
408
  }
400
- commands.push({ name: "read", arguments: { path: path2, start: s, end: e } });
409
+ commands.push({ name: "read", arguments: { path: filePath, start: s, end: e } });
401
410
  }
402
- handleFinish(parts, ctx, acc) {
411
+ handleFinish(parts, rawLine, commands, acc) {
403
412
  const map = acc ?? /* @__PURE__ */ new Map();
404
413
  const args = parts.slice(1);
405
414
  for (const token of args) {
406
- const [path2, rangesText] = token.split(":", 2);
407
- if (!path2 || !rangesText) {
408
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: invalid finish token '${token}'`);
415
+ const [filePath, rangesText] = token.split(":", 2);
416
+ if (!filePath || !rangesText) {
417
+ commands.push(this.skip(
418
+ `[SKIPPED] Invalid finish token "${token}". Correct format: finish <path>:<start>-<end>. Example: finish src/index.ts:1-50`
419
+ ));
420
+ continue;
409
421
  }
410
422
  const rangeSpecs = rangesText.split(",").filter(Boolean);
411
423
  for (const spec of rangeSpecs) {
412
424
  const [s, e] = spec.split("-").map((v) => parseInt(v, 10));
413
425
  if (!Number.isFinite(s) || !Number.isFinite(e) || e < s) {
414
- throw new LLMResponseParseError(`Line ${ctx.lineNumber}: invalid range '${spec}'`);
426
+ commands.push(this.skip(
427
+ `[SKIPPED] Invalid range "${spec}" in "${token}". Ranges must be <start>-<end> where start <= end. Example: 1-50`
428
+ ));
429
+ continue;
415
430
  }
416
- const arr = map.get(path2) ?? [];
431
+ const arr = map.get(filePath) ?? [];
417
432
  arr.push([s, e]);
418
- map.set(path2, arr);
433
+ map.set(filePath, arr);
419
434
  }
420
435
  }
421
436
  return map;
@@ -425,6 +440,10 @@ var LLMResponseParser = class {
425
440
  // tools/warp_grep/tools/read.ts
426
441
  async function toolRead(provider, args) {
427
442
  const res = await provider.read({ path: args.path, start: args.start, end: args.end });
443
+ if (res.error) {
444
+ return res.error;
445
+ }
446
+ if (!res.lines.length) return "(empty file)";
428
447
  return res.lines.join("\n");
429
448
  }
430
449
 
@@ -810,14 +829,7 @@ async function runWarpGrep(config) {
810
829
  });
811
830
  if (!assistantContent) break;
812
831
  messages.push({ role: "assistant", content: assistantContent });
813
- let toolCalls = [];
814
- try {
815
- toolCalls = parser.parse(assistantContent);
816
- } catch (e) {
817
- errors.push({ message: e instanceof Error ? e.message : String(e) });
818
- terminationReason = "terminated";
819
- break;
820
- }
832
+ const toolCalls = parser.parse(assistantContent);
821
833
  if (toolCalls.length === 0) {
822
834
  errors.push({ message: "No tool calls produced by the model." });
823
835
  terminationReason = "terminated";
@@ -827,7 +839,12 @@ async function runWarpGrep(config) {
827
839
  const grepCalls = toolCalls.filter((c) => c.name === "grep");
828
840
  const analyseCalls = toolCalls.filter((c) => c.name === "analyse");
829
841
  const readCalls = toolCalls.filter((c) => c.name === "read");
842
+ const skipCalls = toolCalls.filter((c) => c.name === "_skip");
830
843
  const formatted = [];
844
+ for (const c of skipCalls) {
845
+ const msg = c.arguments?.message || "Command skipped due to parsing error";
846
+ formatted.push(msg);
847
+ }
831
848
  const otherPromises = [];
832
849
  for (const c of analyseCalls) {
833
850
  const args = c.arguments ?? {};
@@ -853,6 +870,15 @@ async function runWarpGrep(config) {
853
870
  const args = c.arguments ?? {};
854
871
  try {
855
872
  const grepRes = await provider.grep({ pattern: args.pattern, path: args.path });
873
+ if (grepRes.error) {
874
+ errors.push({ message: grepRes.error });
875
+ terminationReason = "terminated";
876
+ return {
877
+ terminationReason: "terminated",
878
+ messages,
879
+ errors
880
+ };
881
+ }
856
882
  const rawOutput = Array.isArray(grepRes.lines) ? grepRes.lines.join("\n") : "";
857
883
  const newMatches = parseAndFilterGrepOutput(rawOutput, grepState);
858
884
  let formattedPayload = formatTurnGrepOutput(newMatches);