@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.
- package/dist/{chunk-KH3RKPPO.js → chunk-2GZMA5OY.js} +4 -4
- package/dist/{chunk-DZRBTNQR.js → chunk-3IASMZXX.js} +5 -5
- package/dist/{chunk-NRWVMX4O.js → chunk-HBH4FWIZ.js} +18 -11
- package/dist/chunk-HBH4FWIZ.js.map +1 -0
- package/dist/{chunk-Z2FBMSNE.js → chunk-HQO45BAJ.js} +5 -1
- package/dist/chunk-HQO45BAJ.js.map +1 -0
- package/dist/{chunk-2TXLSKGU.js → chunk-KMWLHINT.js} +3 -3
- package/dist/{chunk-SWQPIKPY.js → chunk-LVPVVLTI.js} +70 -49
- package/dist/chunk-LVPVVLTI.js.map +1 -0
- package/dist/{chunk-4MELBN55.js → chunk-W3QUAOGV.js} +4 -4
- package/dist/{chunk-5ASRBH5I.js → chunk-X3WAPQSV.js} +4 -4
- package/dist/{chunk-MLDK7SCW.js → chunk-ZJIIICRA.js} +29 -7
- package/dist/chunk-ZJIIICRA.js.map +1 -0
- package/dist/client.cjs +116 -68
- package/dist/client.cjs.map +1 -1
- package/dist/client.js +9 -9
- package/dist/index.cjs +116 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +9 -9
- package/dist/tools/warp_grep/agent/parser.cjs +69 -48
- package/dist/tools/warp_grep/agent/parser.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/parser.d.ts +2 -0
- package/dist/tools/warp_grep/agent/parser.js +1 -1
- package/dist/tools/warp_grep/agent/runner.cjs +88 -62
- package/dist/tools/warp_grep/agent/runner.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/runner.js +3 -3
- package/dist/tools/warp_grep/agent/types.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/types.d.ts +1 -1
- package/dist/tools/warp_grep/anthropic.cjs +116 -68
- package/dist/tools/warp_grep/anthropic.cjs.map +1 -1
- package/dist/tools/warp_grep/anthropic.js +6 -6
- package/dist/tools/warp_grep/index.cjs +116 -68
- package/dist/tools/warp_grep/index.cjs.map +1 -1
- package/dist/tools/warp_grep/index.js +8 -8
- package/dist/tools/warp_grep/openai.cjs +116 -68
- package/dist/tools/warp_grep/openai.cjs.map +1 -1
- package/dist/tools/warp_grep/openai.js +6 -6
- package/dist/tools/warp_grep/providers/local.cjs +28 -6
- package/dist/tools/warp_grep/providers/local.cjs.map +1 -1
- package/dist/tools/warp_grep/providers/local.js +1 -1
- package/dist/tools/warp_grep/providers/types.cjs.map +1 -1
- package/dist/tools/warp_grep/providers/types.d.ts +2 -0
- package/dist/tools/warp_grep/tools/grep.cjs +3 -0
- package/dist/tools/warp_grep/tools/grep.cjs.map +1 -1
- package/dist/tools/warp_grep/tools/grep.js +3 -0
- package/dist/tools/warp_grep/tools/grep.js.map +1 -1
- package/dist/tools/warp_grep/tools/read.cjs +4 -0
- package/dist/tools/warp_grep/tools/read.cjs.map +1 -1
- package/dist/tools/warp_grep/tools/read.js +1 -1
- package/dist/tools/warp_grep/vercel.cjs +116 -68
- package/dist/tools/warp_grep/vercel.cjs.map +1 -1
- package/dist/tools/warp_grep/vercel.js +6 -6
- package/package.json +1 -1
- package/dist/chunk-MLDK7SCW.js.map +0 -1
- package/dist/chunk-NRWVMX4O.js.map +0 -1
- package/dist/chunk-SWQPIKPY.js.map +0 -1
- package/dist/chunk-Z2FBMSNE.js.map +0 -1
- /package/dist/{chunk-KH3RKPPO.js.map → chunk-2GZMA5OY.js.map} +0 -0
- /package/dist/{chunk-DZRBTNQR.js.map → chunk-3IASMZXX.js.map} +0 -0
- /package/dist/{chunk-2TXLSKGU.js.map → chunk-KMWLHINT.js.map} +0 -0
- /package/dist/{chunk-4MELBN55.js.map → chunk-W3QUAOGV.js.map} +0 -0
- /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-
|
|
11
|
-
import "./chunk-
|
|
12
|
-
import "./chunk-
|
|
13
|
-
import "./chunk-
|
|
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-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-KMWLHINT.js";
|
|
17
|
+
import "./chunk-HBH4FWIZ.js";
|
|
18
18
|
import "./chunk-EK7OQPWD.js";
|
|
19
|
-
import "./chunk-
|
|
20
|
-
import "./chunk-
|
|
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-
|
|
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
|
|
83
|
+
lines.forEach((line) => {
|
|
84
84
|
if (!line || line.startsWith("#")) return;
|
|
85
|
-
const
|
|
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,
|
|
90
|
+
this.handleAnalyse(parts, line, commands);
|
|
92
91
|
break;
|
|
93
92
|
case "grep":
|
|
94
|
-
this.handleGrep(parts,
|
|
93
|
+
this.handleGrep(parts, line, commands);
|
|
95
94
|
break;
|
|
96
95
|
case "read":
|
|
97
|
-
this.handleRead(parts,
|
|
96
|
+
this.handleRead(parts, line, commands);
|
|
98
97
|
break;
|
|
99
98
|
case "finish":
|
|
100
|
-
finishAccumulator = this.handleFinish(parts,
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
153
|
+
handleGrep(parts, rawLine, commands) {
|
|
152
154
|
if (parts.length < 3) {
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
if (
|
|
157
|
-
|
|
160
|
+
let pat = parts[1];
|
|
161
|
+
if (pat.startsWith("'") && pat.endsWith("'")) {
|
|
162
|
+
pat = pat.slice(1, -1);
|
|
158
163
|
}
|
|
159
|
-
|
|
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,
|
|
172
|
+
handleRead(parts, rawLine, commands) {
|
|
162
173
|
if (parts.length < 2) {
|
|
163
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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 [
|
|
184
|
-
if (!
|
|
185
|
-
|
|
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
|
-
|
|
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(
|
|
214
|
+
const arr = map.get(filePath) ?? [];
|
|
194
215
|
arr.push([s, e]);
|
|
195
|
-
map.set(
|
|
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;
|
|
@@ -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
|
|
300
|
+
lines.forEach((line) => {
|
|
307
301
|
if (!line || line.startsWith("#")) return;
|
|
308
|
-
const
|
|
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,
|
|
307
|
+
this.handleAnalyse(parts, line, commands);
|
|
315
308
|
break;
|
|
316
309
|
case "grep":
|
|
317
|
-
this.handleGrep(parts,
|
|
310
|
+
this.handleGrep(parts, line, commands);
|
|
318
311
|
break;
|
|
319
312
|
case "read":
|
|
320
|
-
this.handleRead(parts,
|
|
313
|
+
this.handleRead(parts, line, commands);
|
|
321
314
|
break;
|
|
322
315
|
case "finish":
|
|
323
|
-
finishAccumulator = this.handleFinish(parts,
|
|
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
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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,
|
|
358
|
+
handleAnalyse(parts, rawLine, commands) {
|
|
366
359
|
if (parts.length < 2) {
|
|
367
|
-
|
|
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,
|
|
370
|
+
handleGrep(parts, rawLine, commands) {
|
|
375
371
|
if (parts.length < 3) {
|
|
376
|
-
|
|
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
|
-
|
|
379
|
-
if (
|
|
380
|
-
|
|
377
|
+
let pat = parts[1];
|
|
378
|
+
if (pat.startsWith("'") && pat.endsWith("'")) {
|
|
379
|
+
pat = pat.slice(1, -1);
|
|
381
380
|
}
|
|
382
|
-
|
|
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,
|
|
389
|
+
handleRead(parts, rawLine, commands) {
|
|
385
390
|
if (parts.length < 2) {
|
|
386
|
-
|
|
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
|
|
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
|
-
|
|
406
|
+
commands.push({ name: "read", arguments: { path: filePath } });
|
|
407
|
+
return;
|
|
399
408
|
}
|
|
400
|
-
commands.push({ name: "read", arguments: { path:
|
|
409
|
+
commands.push({ name: "read", arguments: { path: filePath, start: s, end: e } });
|
|
401
410
|
}
|
|
402
|
-
handleFinish(parts,
|
|
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 [
|
|
407
|
-
if (!
|
|
408
|
-
|
|
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
|
-
|
|
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(
|
|
431
|
+
const arr = map.get(filePath) ?? [];
|
|
417
432
|
arr.push([s, e]);
|
|
418
|
-
map.set(
|
|
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
|
-
|
|
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);
|