@morphllm/morphsdk 0.2.102 → 0.2.103
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-2MAUPERG.js +129 -0
- package/dist/chunk-2MAUPERG.js.map +1 -0
- package/dist/{chunk-QH4BSXOD.js → chunk-4CZPXV6R.js} +8 -7
- package/dist/chunk-4CZPXV6R.js.map +1 -0
- package/dist/{chunk-AIXF4GQC.js → chunk-AFLEE2PO.js} +2 -2
- package/dist/{chunk-CP4NZGRY.js → chunk-EMMSRY32.js} +3 -3
- package/dist/chunk-EMMSRY32.js.map +1 -0
- package/dist/{chunk-QZNGKOCZ.js → chunk-HDRLLCAD.js} +4 -4
- package/dist/{chunk-BKIM7SNY.js → chunk-I3IN742Q.js} +4 -4
- package/dist/{chunk-7UYDS6OX.js → chunk-KQP6ZPYB.js} +4 -4
- package/dist/{chunk-OTPYEYMZ.js → chunk-MY4OU4ON.js} +2 -2
- package/dist/{chunk-UBX7QYBD.js → chunk-O5DA5V5S.js} +4 -4
- package/dist/{chunk-GJU7UOFL.js → chunk-OUEJ6XEO.js} +4 -4
- package/dist/{chunk-L5WXPMCH.js → chunk-QUIGATZE.js} +2 -2
- package/dist/chunk-TLC3QKE6.js +114 -0
- package/dist/chunk-TLC3QKE6.js.map +1 -0
- package/dist/{chunk-4J6NACK2.js → chunk-Y2OTK5WC.js} +15 -15
- package/dist/{chunk-4KMBU6T3.js → chunk-YJZP5ZL5.js} +4 -4
- package/dist/{chunk-76DJEQEP.js → chunk-ZRLEAPZV.js} +4 -4
- package/dist/{chunk-BGEEES52.js → chunk-ZROQPUCQ.js} +7 -7
- package/dist/client.cjs +2922 -2840
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.js +18 -21
- package/dist/edge.cjs +437 -0
- package/dist/edge.cjs.map +1 -0
- package/dist/edge.d.ts +5 -0
- package/dist/edge.js +25 -0
- package/dist/edge.js.map +1 -0
- package/dist/index.cjs +1981 -1888
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +28 -26
- package/dist/modelrouter/core.cjs +2 -2
- package/dist/modelrouter/core.cjs.map +1 -1
- package/dist/modelrouter/core.js +1 -1
- package/dist/modelrouter/index.cjs +2 -2
- package/dist/modelrouter/index.cjs.map +1 -1
- package/dist/modelrouter/index.js +1 -1
- package/dist/tools/browser/index.js +3 -3
- package/dist/tools/codebase_search/anthropic.js +2 -2
- package/dist/tools/codebase_search/index.js +6 -6
- package/dist/tools/codebase_search/openai.js +2 -2
- package/dist/tools/codebase_search/vercel.js +2 -2
- package/dist/tools/fastapply/anthropic.cjs +109 -40
- package/dist/tools/fastapply/anthropic.cjs.map +1 -1
- package/dist/tools/fastapply/anthropic.js +3 -2
- package/dist/tools/fastapply/apply.cjs +227 -0
- package/dist/tools/fastapply/apply.cjs.map +1 -0
- package/dist/tools/fastapply/apply.d.ts +59 -0
- package/dist/tools/fastapply/apply.js +15 -0
- package/dist/tools/fastapply/apply.js.map +1 -0
- package/dist/tools/fastapply/core.cjs +156 -116
- package/dist/tools/fastapply/core.cjs.map +1 -1
- package/dist/tools/fastapply/core.d.ts +5 -41
- package/dist/tools/fastapply/core.js +6 -2
- package/dist/tools/fastapply/index.cjs +113 -76
- package/dist/tools/fastapply/index.cjs.map +1 -1
- package/dist/tools/fastapply/index.d.ts +2 -1
- package/dist/tools/fastapply/index.js +11 -9
- package/dist/tools/fastapply/openai.cjs +110 -41
- package/dist/tools/fastapply/openai.cjs.map +1 -1
- package/dist/tools/fastapply/openai.js +3 -2
- package/dist/tools/fastapply/vercel.cjs +110 -41
- package/dist/tools/fastapply/vercel.cjs.map +1 -1
- package/dist/tools/fastapply/vercel.js +3 -2
- package/dist/tools/index.cjs +113 -76
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +11 -9
- package/dist/tools/warp_grep/agent/runner.js +3 -3
- package/dist/tools/warp_grep/anthropic.cjs +549 -500
- package/dist/tools/warp_grep/anthropic.cjs.map +1 -1
- package/dist/tools/warp_grep/anthropic.js +5 -9
- package/dist/tools/warp_grep/client.cjs +550 -501
- package/dist/tools/warp_grep/client.cjs.map +1 -1
- package/dist/tools/warp_grep/client.js +4 -8
- package/dist/tools/warp_grep/gemini.cjs +549 -500
- package/dist/tools/warp_grep/gemini.cjs.map +1 -1
- package/dist/tools/warp_grep/gemini.js +4 -8
- package/dist/tools/warp_grep/gemini.js.map +1 -1
- package/dist/tools/warp_grep/harness.js +12 -12
- package/dist/tools/warp_grep/index.cjs +559 -501
- package/dist/tools/warp_grep/index.cjs.map +1 -1
- package/dist/tools/warp_grep/index.js +12 -12
- package/dist/tools/warp_grep/openai.cjs +549 -500
- package/dist/tools/warp_grep/openai.cjs.map +1 -1
- package/dist/tools/warp_grep/openai.js +5 -9
- package/dist/tools/warp_grep/providers/local.js +2 -2
- package/dist/tools/warp_grep/vercel.cjs +549 -500
- package/dist/tools/warp_grep/vercel.cjs.map +1 -1
- package/dist/tools/warp_grep/vercel.js +5 -9
- package/package.json +7 -2
- package/dist/chunk-CKTA4AXM.js +0 -233
- package/dist/chunk-CKTA4AXM.js.map +0 -1
- package/dist/chunk-CP4NZGRY.js.map +0 -1
- package/dist/chunk-QH4BSXOD.js.map +0 -1
- /package/dist/{chunk-AIXF4GQC.js.map → chunk-AFLEE2PO.js.map} +0 -0
- /package/dist/{chunk-QZNGKOCZ.js.map → chunk-HDRLLCAD.js.map} +0 -0
- /package/dist/{chunk-BKIM7SNY.js.map → chunk-I3IN742Q.js.map} +0 -0
- /package/dist/{chunk-7UYDS6OX.js.map → chunk-KQP6ZPYB.js.map} +0 -0
- /package/dist/{chunk-OTPYEYMZ.js.map → chunk-MY4OU4ON.js.map} +0 -0
- /package/dist/{chunk-UBX7QYBD.js.map → chunk-O5DA5V5S.js.map} +0 -0
- /package/dist/{chunk-GJU7UOFL.js.map → chunk-OUEJ6XEO.js.map} +0 -0
- /package/dist/{chunk-L5WXPMCH.js.map → chunk-QUIGATZE.js.map} +0 -0
- /package/dist/{chunk-4J6NACK2.js.map → chunk-Y2OTK5WC.js.map} +0 -0
- /package/dist/{chunk-4KMBU6T3.js.map → chunk-YJZP5ZL5.js.map} +0 -0
- /package/dist/{chunk-76DJEQEP.js.map → chunk-ZRLEAPZV.js.map} +0 -0
- /package/dist/{chunk-BGEEES52.js.map → chunk-ZROQPUCQ.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,40 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
30
|
));
|
|
28
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
32
|
|
|
30
|
-
// index.ts
|
|
31
|
-
var index_exports = {};
|
|
32
|
-
__export(index_exports, {
|
|
33
|
-
AnthropicRouter: () => AnthropicRouter,
|
|
34
|
-
AnthropicToolFactory: () => AnthropicToolFactory,
|
|
35
|
-
BrowserClient: () => BrowserClient,
|
|
36
|
-
CodebaseSearchClient: () => CodebaseSearchClient,
|
|
37
|
-
FastApplyClient: () => FastApplyClient,
|
|
38
|
-
GeminiRouter: () => GeminiRouter,
|
|
39
|
-
LocalRipgrepProvider: () => LocalRipgrepProvider,
|
|
40
|
-
MorphClient: () => MorphClient,
|
|
41
|
-
MorphGit: () => MorphGit,
|
|
42
|
-
OpenAIRouter: () => OpenAIRouter,
|
|
43
|
-
OpenAIToolFactory: () => OpenAIToolFactory,
|
|
44
|
-
RawRouter: () => RawRouter,
|
|
45
|
-
VercelToolFactory: () => VercelToolFactory,
|
|
46
|
-
WarpGrepClient: () => WarpGrepClient,
|
|
47
|
-
applyEdit: () => applyEdit
|
|
48
|
-
});
|
|
49
|
-
module.exports = __toCommonJS(index_exports);
|
|
50
|
-
|
|
51
|
-
// tools/fastapply/core.ts
|
|
52
|
-
var import_promises = require("fs/promises");
|
|
53
|
-
var import_path = require("path");
|
|
54
|
-
var import_diff = require("diff");
|
|
55
|
-
|
|
56
33
|
// tools/utils/resilience.ts
|
|
57
|
-
var DEFAULT_RETRY_CONFIG = {
|
|
58
|
-
maxRetries: 3,
|
|
59
|
-
initialDelay: 1e3,
|
|
60
|
-
maxDelay: 3e4,
|
|
61
|
-
backoffMultiplier: 2,
|
|
62
|
-
retryableErrors: ["ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND"]
|
|
63
|
-
};
|
|
64
34
|
async function fetchWithRetry(url, options, retryConfig = {}) {
|
|
65
35
|
const {
|
|
66
36
|
maxRetries = DEFAULT_RETRY_CONFIG.maxRetries,
|
|
@@ -126,63 +96,28 @@ async function withTimeout(promise, timeoutMs, errorMessage) {
|
|
|
126
96
|
function sleep(ms) {
|
|
127
97
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
128
98
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
var FastApplyClient = class {
|
|
140
|
-
config;
|
|
141
|
-
constructor(config = {}) {
|
|
142
|
-
this.config = {
|
|
143
|
-
morphApiKey: config.apiKey,
|
|
144
|
-
morphApiUrl: DEFAULT_CONFIG.morphApiUrl,
|
|
145
|
-
debug: config.debug,
|
|
146
|
-
timeout: config.timeout || DEFAULT_CONFIG.timeout,
|
|
147
|
-
retryConfig: config.retryConfig,
|
|
148
|
-
generateUdiff: DEFAULT_CONFIG.generateUdiff,
|
|
149
|
-
autoWrite: DEFAULT_CONFIG.autoWrite
|
|
99
|
+
var DEFAULT_RETRY_CONFIG;
|
|
100
|
+
var init_resilience = __esm({
|
|
101
|
+
"tools/utils/resilience.ts"() {
|
|
102
|
+
"use strict";
|
|
103
|
+
DEFAULT_RETRY_CONFIG = {
|
|
104
|
+
maxRetries: 3,
|
|
105
|
+
initialDelay: 1e3,
|
|
106
|
+
maxDelay: 3e4,
|
|
107
|
+
backoffMultiplier: 2,
|
|
108
|
+
retryableErrors: ["ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND"]
|
|
150
109
|
};
|
|
151
110
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Apply an edit to code directly without file I/O
|
|
164
|
-
*
|
|
165
|
-
* Useful for sandbox environments or when you manage your own file system.
|
|
166
|
-
* Compatible with the earlier OpenAI client API contract.
|
|
167
|
-
*
|
|
168
|
-
* @param input - Code and edit parameters
|
|
169
|
-
* @param overrides - Optional config overrides for this operation
|
|
170
|
-
* @returns Result with merged code
|
|
171
|
-
*
|
|
172
|
-
* @example
|
|
173
|
-
* ```typescript
|
|
174
|
-
* const result = await client.applyEdit({
|
|
175
|
-
* originalCode: 'function hello() { return "world"; }',
|
|
176
|
-
* codeEdit: 'function hello() { return "universe"; }',
|
|
177
|
-
* instructions: 'Change return value'
|
|
178
|
-
* });
|
|
179
|
-
* console.log(result.mergedCode);
|
|
180
|
-
* ```
|
|
181
|
-
*/
|
|
182
|
-
async applyEdit(input, overrides) {
|
|
183
|
-
return applyEdit(input, { ...this.config, ...overrides });
|
|
184
|
-
}
|
|
185
|
-
};
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// tools/fastapply/apply.ts
|
|
114
|
+
var apply_exports = {};
|
|
115
|
+
__export(apply_exports, {
|
|
116
|
+
applyEdit: () => applyEdit,
|
|
117
|
+
callMorphAPI: () => callMorphAPI,
|
|
118
|
+
countChanges: () => countChanges,
|
|
119
|
+
generateUdiff: () => generateUdiff
|
|
120
|
+
});
|
|
186
121
|
function generateUdiff(original, modified, filepath) {
|
|
187
122
|
return (0, import_diff.createTwoFilesPatch)(
|
|
188
123
|
filepath,
|
|
@@ -213,9 +148,9 @@ function countChanges(original, modified) {
|
|
|
213
148
|
};
|
|
214
149
|
}
|
|
215
150
|
async function callMorphAPI(originalCode, codeEdit, instructions, filepath, config) {
|
|
216
|
-
const apiKey = config.morphApiKey || process.env
|
|
217
|
-
const apiUrl = config.morphApiUrl ||
|
|
218
|
-
const timeout = config.timeout ||
|
|
151
|
+
const apiKey = config.morphApiKey || (typeof process !== "undefined" ? process.env?.MORPH_API_KEY : void 0);
|
|
152
|
+
const apiUrl = config.morphApiUrl || DEFAULT_API_URL;
|
|
153
|
+
const timeout = config.timeout || DEFAULT_TIMEOUT;
|
|
219
154
|
const debug = config.debug || false;
|
|
220
155
|
if (!apiKey) {
|
|
221
156
|
throw new Error(
|
|
@@ -263,54 +198,6 @@ async function callMorphAPI(originalCode, codeEdit, instructions, filepath, conf
|
|
|
263
198
|
}
|
|
264
199
|
return data.choices[0].message.content;
|
|
265
200
|
}
|
|
266
|
-
async function executeEditFile(input, config = {}) {
|
|
267
|
-
const baseDir = config.baseDir || DEFAULT_CONFIG.baseDir;
|
|
268
|
-
const fullPath = (0, import_path.resolve)((0, import_path.join)(baseDir, input.target_filepath));
|
|
269
|
-
const debug = config.debug || false;
|
|
270
|
-
const relativePath = (0, import_path.relative)(baseDir, fullPath);
|
|
271
|
-
if (relativePath.startsWith("..") || fullPath === baseDir) {
|
|
272
|
-
return {
|
|
273
|
-
success: false,
|
|
274
|
-
filepath: input.target_filepath,
|
|
275
|
-
changes: { linesAdded: 0, linesRemoved: 0, linesModified: 0 },
|
|
276
|
-
error: `Invalid filepath: '${input.target_filepath}' is outside baseDir`
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
try {
|
|
280
|
-
if (debug) console.log(`[FastApply] Reading file: ${input.target_filepath}`);
|
|
281
|
-
let originalCode = "";
|
|
282
|
-
try {
|
|
283
|
-
originalCode = await (0, import_promises.readFile)(fullPath, "utf-8");
|
|
284
|
-
} catch (error) {
|
|
285
|
-
if (error.code !== "ENOENT") {
|
|
286
|
-
throw error;
|
|
287
|
-
}
|
|
288
|
-
if (debug) console.log(`[FastApply] File doesn't exist, will create new file`);
|
|
289
|
-
}
|
|
290
|
-
const mergedCode = await callMorphAPI(originalCode, input.code_edit, input.instructions, input.target_filepath, config);
|
|
291
|
-
const udiff = config.generateUdiff !== false ? generateUdiff(originalCode, mergedCode, input.target_filepath) : void 0;
|
|
292
|
-
if (config.autoWrite !== false) {
|
|
293
|
-
await (0, import_promises.writeFile)(fullPath, mergedCode, "utf-8");
|
|
294
|
-
if (debug) console.log(`[FastApply] Wrote ${mergedCode.length} chars to ${input.target_filepath}`);
|
|
295
|
-
}
|
|
296
|
-
const changes = countChanges(originalCode, mergedCode);
|
|
297
|
-
return {
|
|
298
|
-
success: true,
|
|
299
|
-
filepath: input.target_filepath,
|
|
300
|
-
udiff,
|
|
301
|
-
changes
|
|
302
|
-
};
|
|
303
|
-
} catch (error) {
|
|
304
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
305
|
-
if (debug) console.error(`[FastApply] Error: ${errorMessage}`);
|
|
306
|
-
return {
|
|
307
|
-
success: false,
|
|
308
|
-
filepath: input.target_filepath,
|
|
309
|
-
changes: { linesAdded: 0, linesRemoved: 0, linesModified: 0 },
|
|
310
|
-
error: errorMessage
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
201
|
async function applyEdit(input, config = {}) {
|
|
315
202
|
const debug = config.debug || false;
|
|
316
203
|
const filepath = input.filepath || "file";
|
|
@@ -343,1198 +230,1880 @@ async function applyEdit(input, config = {}) {
|
|
|
343
230
|
};
|
|
344
231
|
}
|
|
345
232
|
}
|
|
233
|
+
var import_diff, DEFAULT_API_URL, DEFAULT_TIMEOUT;
|
|
234
|
+
var init_apply = __esm({
|
|
235
|
+
"tools/fastapply/apply.ts"() {
|
|
236
|
+
"use strict";
|
|
237
|
+
import_diff = require("diff");
|
|
238
|
+
init_resilience();
|
|
239
|
+
DEFAULT_API_URL = "https://api.morphllm.com";
|
|
240
|
+
DEFAULT_TIMEOUT = 3e4;
|
|
241
|
+
}
|
|
242
|
+
});
|
|
346
243
|
|
|
347
|
-
// tools/
|
|
348
|
-
var
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
retryConfig: config.retryConfig
|
|
244
|
+
// tools/warp_grep/agent/config.ts
|
|
245
|
+
var parseEnvTimeout, AGENT_CONFIG, BUILTIN_EXCLUDES, DEFAULT_EXCLUDES, DEFAULT_MODEL;
|
|
246
|
+
var init_config = __esm({
|
|
247
|
+
"tools/warp_grep/agent/config.ts"() {
|
|
248
|
+
"use strict";
|
|
249
|
+
parseEnvTimeout = (envValue, defaultMs) => {
|
|
250
|
+
if (!envValue) return defaultMs;
|
|
251
|
+
const parsed = parseInt(envValue, 10);
|
|
252
|
+
return isNaN(parsed) || parsed <= 0 ? defaultMs : parsed;
|
|
357
253
|
};
|
|
254
|
+
AGENT_CONFIG = {
|
|
255
|
+
MAX_TURNS: 4,
|
|
256
|
+
/** Default timeout for model calls. Can be overridden via MORPH_WARP_GREP_TIMEOUT env var (in ms) */
|
|
257
|
+
TIMEOUT_MS: parseEnvTimeout(process.env.MORPH_WARP_GREP_TIMEOUT, 3e4),
|
|
258
|
+
MAX_CONTEXT_CHARS: 54e4,
|
|
259
|
+
MAX_OUTPUT_LINES: 200,
|
|
260
|
+
MAX_READ_LINES: 800,
|
|
261
|
+
MAX_LIST_DEPTH: 3,
|
|
262
|
+
LIST_TIMEOUT_MS: 2e3
|
|
263
|
+
};
|
|
264
|
+
BUILTIN_EXCLUDES = [
|
|
265
|
+
// Version control
|
|
266
|
+
".git",
|
|
267
|
+
".svn",
|
|
268
|
+
".hg",
|
|
269
|
+
".bzr",
|
|
270
|
+
// Dependencies
|
|
271
|
+
"node_modules",
|
|
272
|
+
"bower_components",
|
|
273
|
+
".pnpm",
|
|
274
|
+
".yarn",
|
|
275
|
+
"vendor",
|
|
276
|
+
"packages",
|
|
277
|
+
"Pods",
|
|
278
|
+
".bundle",
|
|
279
|
+
// Python
|
|
280
|
+
"__pycache__",
|
|
281
|
+
".pytest_cache",
|
|
282
|
+
".mypy_cache",
|
|
283
|
+
".ruff_cache",
|
|
284
|
+
".venv",
|
|
285
|
+
"venv",
|
|
286
|
+
".tox",
|
|
287
|
+
".nox",
|
|
288
|
+
".eggs",
|
|
289
|
+
"*.egg-info",
|
|
290
|
+
// Build outputs
|
|
291
|
+
"dist",
|
|
292
|
+
"build",
|
|
293
|
+
"out",
|
|
294
|
+
"output",
|
|
295
|
+
"target",
|
|
296
|
+
"_build",
|
|
297
|
+
".next",
|
|
298
|
+
".nuxt",
|
|
299
|
+
".output",
|
|
300
|
+
".vercel",
|
|
301
|
+
".netlify",
|
|
302
|
+
// Cache directories
|
|
303
|
+
".cache",
|
|
304
|
+
".parcel-cache",
|
|
305
|
+
".turbo",
|
|
306
|
+
".nx",
|
|
307
|
+
".gradle",
|
|
308
|
+
// IDE/Editor
|
|
309
|
+
".idea",
|
|
310
|
+
".vscode",
|
|
311
|
+
".vs",
|
|
312
|
+
// Coverage
|
|
313
|
+
"coverage",
|
|
314
|
+
".coverage",
|
|
315
|
+
"htmlcov",
|
|
316
|
+
".nyc_output",
|
|
317
|
+
// Temporary
|
|
318
|
+
"tmp",
|
|
319
|
+
"temp",
|
|
320
|
+
".tmp",
|
|
321
|
+
".temp",
|
|
322
|
+
// Lock files
|
|
323
|
+
"package-lock.json",
|
|
324
|
+
"yarn.lock",
|
|
325
|
+
"pnpm-lock.yaml",
|
|
326
|
+
"bun.lockb",
|
|
327
|
+
"Cargo.lock",
|
|
328
|
+
"Gemfile.lock",
|
|
329
|
+
"poetry.lock",
|
|
330
|
+
// Binary/minified
|
|
331
|
+
"*.min.js",
|
|
332
|
+
"*.min.css",
|
|
333
|
+
"*.bundle.js",
|
|
334
|
+
"*.wasm",
|
|
335
|
+
"*.so",
|
|
336
|
+
"*.dll",
|
|
337
|
+
"*.pyc",
|
|
338
|
+
"*.map",
|
|
339
|
+
"*.js.map",
|
|
340
|
+
// Hidden directories catch-all
|
|
341
|
+
".*"
|
|
342
|
+
];
|
|
343
|
+
DEFAULT_EXCLUDES = (process.env.MORPH_WARP_GREP_EXCLUDE || "").split(",").map((s) => s.trim()).filter(Boolean).concat(BUILTIN_EXCLUDES);
|
|
344
|
+
DEFAULT_MODEL = "morph-warp-grep-v1";
|
|
358
345
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
{
|
|
375
|
-
);
|
|
376
|
-
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// tools/warp_grep/utils/ripgrep.ts
|
|
349
|
+
function spawnRg(rgBinary, args, opts) {
|
|
350
|
+
return new Promise((resolve2) => {
|
|
351
|
+
const child = (0, import_child_process.spawn)(rgBinary, args, {
|
|
352
|
+
cwd: opts?.cwd,
|
|
353
|
+
env: { ...process.env, ...opts?.env || {} },
|
|
354
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
355
|
+
});
|
|
356
|
+
let stdout = "";
|
|
357
|
+
let stderr = "";
|
|
358
|
+
child.stdout.on("data", (d) => stdout += d.toString());
|
|
359
|
+
child.stderr.on("data", (d) => stderr += d.toString());
|
|
360
|
+
child.on("close", (code) => {
|
|
361
|
+
resolve2({ stdout, stderr, exitCode: typeof code === "number" ? code : -1 });
|
|
362
|
+
});
|
|
363
|
+
child.on("error", () => {
|
|
364
|
+
resolve2({ stdout: "", stderr: "Failed to spawn ripgrep.", exitCode: -1 });
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
function isBinaryFailure(result) {
|
|
369
|
+
if (result.exitCode === -1) return true;
|
|
370
|
+
if (result.stderr.includes("jemalloc") || result.stderr.includes("Unsupported system page size")) return true;
|
|
371
|
+
if (result.exitCode === 134) return true;
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
async function runRipgrep(args, opts) {
|
|
375
|
+
if (rgPathChecked && resolvedRgPath) {
|
|
376
|
+
return spawnRg(resolvedRgPath, args, opts);
|
|
389
377
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
headers: {
|
|
397
|
-
"Content-Type": "application/json",
|
|
398
|
-
"Authorization": `Bearer ${apiKey}`
|
|
399
|
-
},
|
|
400
|
-
body: JSON.stringify({
|
|
401
|
-
query: input.query,
|
|
402
|
-
repoId: config.repoId,
|
|
403
|
-
targetDirectories: input.target_directories || [],
|
|
404
|
-
limit: input.limit || 10,
|
|
405
|
-
candidateLimit: 50
|
|
406
|
-
})
|
|
407
|
-
},
|
|
408
|
-
config.retryConfig
|
|
409
|
-
);
|
|
410
|
-
const response = await withTimeout(fetchPromise, timeout, `Codebase search timed out after ${timeout}ms`);
|
|
411
|
-
if (!response.ok) {
|
|
412
|
-
const errorText = await response.text();
|
|
413
|
-
if (debug) console.error(`[CodebaseSearch] Error: ${response.status} - ${errorText}`);
|
|
414
|
-
return {
|
|
415
|
-
success: false,
|
|
416
|
-
results: [],
|
|
417
|
-
stats: { totalResults: 0, candidatesRetrieved: 0, searchTimeMs: 0 },
|
|
418
|
-
error: `Search failed (${response.status}): ${errorText}`
|
|
419
|
-
};
|
|
378
|
+
if (!rgPathChecked) {
|
|
379
|
+
const result = await spawnRg(import_ripgrep.rgPath, args, opts);
|
|
380
|
+
if (!isBinaryFailure(result)) {
|
|
381
|
+
resolvedRgPath = import_ripgrep.rgPath;
|
|
382
|
+
rgPathChecked = true;
|
|
383
|
+
return result;
|
|
420
384
|
}
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
385
|
+
const fallbackResult = await spawnRg("rg", args, opts);
|
|
386
|
+
if (!isBinaryFailure(fallbackResult)) {
|
|
387
|
+
resolvedRgPath = "rg";
|
|
388
|
+
rgPathChecked = true;
|
|
389
|
+
return fallbackResult;
|
|
425
390
|
}
|
|
391
|
+
rgPathChecked = true;
|
|
426
392
|
return {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
};
|
|
431
|
-
} catch (error) {
|
|
432
|
-
if (debug) console.error(`[CodebaseSearch] Exception: ${error instanceof Error ? error.message : error}`);
|
|
433
|
-
return {
|
|
434
|
-
success: false,
|
|
435
|
-
results: [],
|
|
436
|
-
stats: { totalResults: 0, candidatesRetrieved: 0, searchTimeMs: 0 },
|
|
437
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
393
|
+
stdout: "",
|
|
394
|
+
stderr: "Failed to spawn ripgrep. Neither bundled nor system rg is available.",
|
|
395
|
+
exitCode: -1
|
|
438
396
|
};
|
|
439
397
|
}
|
|
398
|
+
return {
|
|
399
|
+
stdout: "",
|
|
400
|
+
stderr: "Failed to spawn ripgrep. Neither bundled nor system rg is available.",
|
|
401
|
+
exitCode: -1
|
|
402
|
+
};
|
|
440
403
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
monitoring: { interactive: false, showControls: false }
|
|
450
|
-
};
|
|
451
|
-
function buildLiveUrl(debugUrl, options = {}) {
|
|
452
|
-
if (!debugUrl) {
|
|
453
|
-
throw new Error(
|
|
454
|
-
"debugUrl is required. Ensure your backend returns debugUrl in the task response. Contact support@morphllm.com if you need help."
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
const normalized = normalizeLiveUrl(debugUrl);
|
|
458
|
-
const url = new URL(normalized);
|
|
459
|
-
if (options.interactive !== void 0) {
|
|
460
|
-
url.searchParams.set("interactive", String(options.interactive));
|
|
461
|
-
}
|
|
462
|
-
if (options.theme) {
|
|
463
|
-
url.searchParams.set("theme", options.theme);
|
|
464
|
-
}
|
|
465
|
-
if (options.showControls !== void 0) {
|
|
466
|
-
url.searchParams.set("showControls", String(options.showControls));
|
|
467
|
-
}
|
|
468
|
-
if (options.pageId) {
|
|
469
|
-
url.searchParams.set("pageId", options.pageId);
|
|
404
|
+
var import_child_process, import_ripgrep, resolvedRgPath, rgPathChecked;
|
|
405
|
+
var init_ripgrep = __esm({
|
|
406
|
+
"tools/warp_grep/utils/ripgrep.ts"() {
|
|
407
|
+
"use strict";
|
|
408
|
+
import_child_process = require("child_process");
|
|
409
|
+
import_ripgrep = require("@vscode/ripgrep");
|
|
410
|
+
resolvedRgPath = null;
|
|
411
|
+
rgPathChecked = false;
|
|
470
412
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// tools/warp_grep/utils/paths.ts
|
|
416
|
+
function resolveUnderRepo(repoRoot, targetPath) {
|
|
417
|
+
const absRoot = import_path4.default.resolve(repoRoot);
|
|
418
|
+
const resolved = import_path4.default.resolve(absRoot, targetPath);
|
|
419
|
+
ensureWithinRepo(absRoot, resolved);
|
|
420
|
+
return resolved;
|
|
475
421
|
}
|
|
476
|
-
function
|
|
477
|
-
const
|
|
478
|
-
if (
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
if (trimmed.startsWith("wss://") || trimmed.startsWith("ws://")) {
|
|
482
|
-
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
422
|
+
function ensureWithinRepo(repoRoot, absTarget) {
|
|
423
|
+
const rel = import_path4.default.relative(import_path4.default.resolve(repoRoot), import_path4.default.resolve(absTarget));
|
|
424
|
+
if (rel.startsWith("..") || import_path4.default.isAbsolute(rel)) {
|
|
425
|
+
throw new Error(`Path outside repository root: ${absTarget}`);
|
|
483
426
|
}
|
|
484
|
-
|
|
427
|
+
}
|
|
428
|
+
function toRepoRelative(repoRoot, absPath) {
|
|
429
|
+
return import_path4.default.relative(import_path4.default.resolve(repoRoot), import_path4.default.resolve(absPath));
|
|
430
|
+
}
|
|
431
|
+
function isSymlink(p) {
|
|
485
432
|
try {
|
|
486
|
-
|
|
433
|
+
const st = import_fs.default.lstatSync(p);
|
|
434
|
+
return st.isSymbolicLink();
|
|
487
435
|
} catch {
|
|
488
|
-
return
|
|
489
|
-
}
|
|
490
|
-
if (url.protocol === "wss:" || url.protocol === "ws:") {
|
|
491
|
-
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
492
|
-
}
|
|
493
|
-
const wssParam = url.searchParams.get("wss");
|
|
494
|
-
if (wssParam && (wssParam.startsWith("wss://") || wssParam.startsWith("ws://"))) {
|
|
495
|
-
url.searchParams.set("wss", wssParam);
|
|
496
|
-
}
|
|
497
|
-
return url.toString();
|
|
498
|
-
}
|
|
499
|
-
function buildLiveIframe(debugUrl, options = {}) {
|
|
500
|
-
const {
|
|
501
|
-
width = "100%",
|
|
502
|
-
height = "600px",
|
|
503
|
-
style = "",
|
|
504
|
-
className = "",
|
|
505
|
-
...sessionOptions
|
|
506
|
-
} = options;
|
|
507
|
-
const src = buildLiveUrl(debugUrl, sessionOptions);
|
|
508
|
-
const widthStr = typeof width === "number" ? `${width}px` : width;
|
|
509
|
-
const heightStr = typeof height === "number" ? `${height}px` : height;
|
|
510
|
-
const baseStyle = `width: ${widthStr}; height: ${heightStr}; border: none;`;
|
|
511
|
-
const fullStyle = style ? `${baseStyle} ${style}` : baseStyle;
|
|
512
|
-
const attributes = [
|
|
513
|
-
`src="${src}"`,
|
|
514
|
-
`style="${fullStyle}"`
|
|
515
|
-
];
|
|
516
|
-
if (className) {
|
|
517
|
-
attributes.push(`class="${className}"`);
|
|
436
|
+
return false;
|
|
518
437
|
}
|
|
519
|
-
return `<iframe ${attributes.join(" ")}></iframe>`;
|
|
520
438
|
}
|
|
521
|
-
function
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (!preset) {
|
|
533
|
-
throw new Error(
|
|
534
|
-
`Unknown preset: ${optionsOrPreset}. Available presets: ${Object.keys(LIVE_PRESETS).join(", ")}`
|
|
535
|
-
);
|
|
439
|
+
function fixPathRepetition(fullPath) {
|
|
440
|
+
const segments = fullPath.split(import_path4.default.sep).filter(Boolean);
|
|
441
|
+
if (segments.length < 2) return null;
|
|
442
|
+
for (let len = Math.floor(segments.length / 2); len >= 1; len--) {
|
|
443
|
+
for (let i = 0; i <= segments.length - 2 * len; i++) {
|
|
444
|
+
const first = segments.slice(i, i + len);
|
|
445
|
+
const second = segments.slice(i + len, i + 2 * len);
|
|
446
|
+
if (first.every((seg, idx) => seg === second[idx])) {
|
|
447
|
+
const fixed = [...segments.slice(0, i), ...segments.slice(i + len)];
|
|
448
|
+
return import_path4.default.sep + fixed.join(import_path4.default.sep);
|
|
449
|
+
}
|
|
536
450
|
}
|
|
537
|
-
return preset;
|
|
538
451
|
}
|
|
539
|
-
return
|
|
452
|
+
return null;
|
|
540
453
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
if (Error.captureStackTrace) {
|
|
554
|
-
Error.captureStackTrace(this, this.constructor);
|
|
454
|
+
function isTextualFile(filePath, maxBytes = 2e6) {
|
|
455
|
+
try {
|
|
456
|
+
const st = import_fs.default.statSync(filePath);
|
|
457
|
+
if (!st.isFile()) return false;
|
|
458
|
+
if (st.size > maxBytes) return false;
|
|
459
|
+
const fd = import_fs.default.openSync(filePath, "r");
|
|
460
|
+
const buf = Buffer.alloc(512);
|
|
461
|
+
const read = import_fs.default.readSync(fd, buf, 0, buf.length, 0);
|
|
462
|
+
import_fs.default.closeSync(fd);
|
|
463
|
+
for (let i = 0; i < read; i++) {
|
|
464
|
+
const c = buf[i];
|
|
465
|
+
if (c === 0) return false;
|
|
555
466
|
}
|
|
467
|
+
return true;
|
|
468
|
+
} catch {
|
|
469
|
+
return false;
|
|
556
470
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
code: this.code,
|
|
565
|
-
cause: this.cause?.message
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
};
|
|
569
|
-
var MorphValidationError = class extends MorphError {
|
|
570
|
-
/** The field that failed validation */
|
|
571
|
-
field;
|
|
572
|
-
constructor(message, field) {
|
|
573
|
-
super(message, "validation_error");
|
|
574
|
-
this.name = "MorphValidationError";
|
|
575
|
-
this.field = field;
|
|
471
|
+
}
|
|
472
|
+
var import_fs, import_path4;
|
|
473
|
+
var init_paths = __esm({
|
|
474
|
+
"tools/warp_grep/utils/paths.ts"() {
|
|
475
|
+
"use strict";
|
|
476
|
+
import_fs = __toESM(require("fs"), 1);
|
|
477
|
+
import_path4 = __toESM(require("path"), 1);
|
|
576
478
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// tools/warp_grep/utils/files.ts
|
|
482
|
+
async function readAllLines(filePath) {
|
|
483
|
+
const content = await import_promises.default.readFile(filePath, "utf8");
|
|
484
|
+
return content.split(/\r?\n/);
|
|
485
|
+
}
|
|
486
|
+
var import_promises;
|
|
487
|
+
var init_files = __esm({
|
|
488
|
+
"tools/warp_grep/utils/files.ts"() {
|
|
489
|
+
"use strict";
|
|
490
|
+
import_promises = __toESM(require("fs/promises"), 1);
|
|
582
491
|
}
|
|
583
|
-
};
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
this.requestId = options?.requestId;
|
|
596
|
-
this.rawResponse = options?.rawResponse;
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// tools/warp_grep/providers/local.ts
|
|
495
|
+
var local_exports = {};
|
|
496
|
+
__export(local_exports, {
|
|
497
|
+
LocalRipgrepProvider: () => LocalRipgrepProvider
|
|
498
|
+
});
|
|
499
|
+
function shouldSkip2(name) {
|
|
500
|
+
if (SKIP_NAMES2.has(name)) return true;
|
|
501
|
+
if (name.startsWith(".")) return true;
|
|
502
|
+
for (const ext of SKIP_EXTENSIONS2) {
|
|
503
|
+
if (name.endsWith(ext)) return true;
|
|
597
504
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
var import_promises2, import_path5, SKIP_NAMES2, SKIP_EXTENSIONS2, LocalRipgrepProvider;
|
|
508
|
+
var init_local = __esm({
|
|
509
|
+
"tools/warp_grep/providers/local.ts"() {
|
|
510
|
+
"use strict";
|
|
511
|
+
import_promises2 = __toESM(require("fs/promises"), 1);
|
|
512
|
+
import_path5 = __toESM(require("path"), 1);
|
|
513
|
+
init_ripgrep();
|
|
514
|
+
init_paths();
|
|
515
|
+
init_files();
|
|
516
|
+
init_config();
|
|
517
|
+
SKIP_NAMES2 = /* @__PURE__ */ new Set([
|
|
518
|
+
// Version control
|
|
519
|
+
".git",
|
|
520
|
+
".svn",
|
|
521
|
+
".hg",
|
|
522
|
+
".bzr",
|
|
523
|
+
// Dependencies
|
|
524
|
+
"node_modules",
|
|
525
|
+
"bower_components",
|
|
526
|
+
".pnpm",
|
|
527
|
+
".yarn",
|
|
528
|
+
"vendor",
|
|
529
|
+
"Pods",
|
|
530
|
+
".bundle",
|
|
531
|
+
// Python
|
|
532
|
+
"__pycache__",
|
|
533
|
+
".pytest_cache",
|
|
534
|
+
".mypy_cache",
|
|
535
|
+
".ruff_cache",
|
|
536
|
+
".venv",
|
|
537
|
+
"venv",
|
|
538
|
+
".tox",
|
|
539
|
+
".nox",
|
|
540
|
+
".eggs",
|
|
541
|
+
// Build outputs
|
|
542
|
+
"dist",
|
|
543
|
+
"build",
|
|
544
|
+
"out",
|
|
545
|
+
"output",
|
|
546
|
+
"target",
|
|
547
|
+
"_build",
|
|
548
|
+
".next",
|
|
549
|
+
".nuxt",
|
|
550
|
+
".output",
|
|
551
|
+
".vercel",
|
|
552
|
+
".netlify",
|
|
553
|
+
// Cache
|
|
554
|
+
".cache",
|
|
555
|
+
".parcel-cache",
|
|
556
|
+
".turbo",
|
|
557
|
+
".nx",
|
|
558
|
+
".gradle",
|
|
559
|
+
// IDE
|
|
560
|
+
".idea",
|
|
561
|
+
".vscode",
|
|
562
|
+
".vs",
|
|
563
|
+
// Coverage
|
|
564
|
+
"coverage",
|
|
565
|
+
".coverage",
|
|
566
|
+
"htmlcov",
|
|
567
|
+
".nyc_output",
|
|
568
|
+
// Temp
|
|
569
|
+
"tmp",
|
|
570
|
+
"temp",
|
|
571
|
+
".tmp",
|
|
572
|
+
".temp",
|
|
573
|
+
// Lock files
|
|
574
|
+
"package-lock.json",
|
|
575
|
+
"yarn.lock",
|
|
576
|
+
"pnpm-lock.yaml",
|
|
577
|
+
"bun.lockb",
|
|
578
|
+
"Cargo.lock",
|
|
579
|
+
"Gemfile.lock",
|
|
580
|
+
"poetry.lock"
|
|
581
|
+
]);
|
|
582
|
+
SKIP_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
583
|
+
".min.js",
|
|
584
|
+
".min.css",
|
|
585
|
+
".bundle.js",
|
|
586
|
+
".wasm",
|
|
587
|
+
".so",
|
|
588
|
+
".dll",
|
|
589
|
+
".pyc",
|
|
590
|
+
".map",
|
|
591
|
+
".js.map"
|
|
592
|
+
]);
|
|
593
|
+
LocalRipgrepProvider = class {
|
|
594
|
+
constructor(repoRoot, excludes = DEFAULT_EXCLUDES) {
|
|
595
|
+
this.repoRoot = repoRoot;
|
|
596
|
+
this.excludes = excludes;
|
|
597
|
+
}
|
|
598
|
+
async grep(params) {
|
|
599
|
+
let abs;
|
|
600
|
+
try {
|
|
601
|
+
abs = resolveUnderRepo(this.repoRoot, params.path);
|
|
602
|
+
} catch (err) {
|
|
603
|
+
return {
|
|
604
|
+
lines: [],
|
|
605
|
+
error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
const stat = await import_promises2.default.stat(abs).catch(() => null);
|
|
609
|
+
if (!stat) return { lines: [] };
|
|
610
|
+
const targetArg = abs === import_path5.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
|
|
611
|
+
const args = [
|
|
612
|
+
"--no-config",
|
|
613
|
+
"--no-heading",
|
|
614
|
+
"--with-filename",
|
|
615
|
+
"--line-number",
|
|
616
|
+
"--color=never",
|
|
617
|
+
"--trim",
|
|
618
|
+
"--max-columns=400",
|
|
619
|
+
"-C",
|
|
620
|
+
"1",
|
|
621
|
+
...params.glob ? ["--glob", params.glob] : [],
|
|
622
|
+
...this.excludes.flatMap((e) => ["-g", `!${e}`]),
|
|
623
|
+
params.pattern,
|
|
624
|
+
targetArg || "."
|
|
625
|
+
];
|
|
626
|
+
const res = await runRipgrep(args, { cwd: this.repoRoot });
|
|
627
|
+
if (res.exitCode === -1) {
|
|
628
|
+
return {
|
|
629
|
+
lines: [],
|
|
630
|
+
error: `[RIPGREP NOT AVAILABLE] ripgrep (rg) is required but failed to execute. Please install it:
|
|
631
|
+
\u2022 macOS: brew install ripgrep
|
|
632
|
+
\u2022 Ubuntu/Debian: apt install ripgrep
|
|
633
|
+
\u2022 Windows: choco install ripgrep
|
|
634
|
+
\u2022 Or visit: https://github.com/BurntSushi/ripgrep#installation
|
|
635
|
+
Exit code: ${res.exitCode}${res.stderr ? `
|
|
636
|
+
Details: ${res.stderr}` : ""}`
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
if (res.exitCode !== 0 && res.exitCode !== 1) {
|
|
640
|
+
return {
|
|
641
|
+
lines: [],
|
|
642
|
+
error: `[RIPGREP ERROR] grep failed with exit code ${res.exitCode}${res.stderr ? `: ${res.stderr}` : ""}`
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
const lines = (res.stdout || "").trim().split(/\r?\n/).filter((l) => l.length > 0);
|
|
646
|
+
if (lines.length > AGENT_CONFIG.MAX_OUTPUT_LINES) {
|
|
647
|
+
return {
|
|
648
|
+
lines: [],
|
|
649
|
+
error: "query not specific enough, tool tried to return too much context and failed"
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
return { lines };
|
|
653
|
+
}
|
|
654
|
+
async read(params) {
|
|
655
|
+
let abs;
|
|
656
|
+
try {
|
|
657
|
+
abs = resolveUnderRepo(this.repoRoot, params.path);
|
|
658
|
+
} catch (err) {
|
|
659
|
+
return {
|
|
660
|
+
lines: [],
|
|
661
|
+
error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
let stat = await import_promises2.default.stat(abs).catch(() => null);
|
|
665
|
+
if (!stat || !stat.isFile()) {
|
|
666
|
+
const fixedPath = fixPathRepetition(abs);
|
|
667
|
+
if (fixedPath) {
|
|
668
|
+
const fixedStat = await import_promises2.default.stat(fixedPath).catch(() => null);
|
|
669
|
+
if (fixedStat?.isFile()) {
|
|
670
|
+
abs = fixedPath;
|
|
671
|
+
stat = fixedStat;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (!stat || !stat.isFile()) {
|
|
676
|
+
return {
|
|
677
|
+
lines: [],
|
|
678
|
+
error: `[FILE NOT FOUND] You tried to read "${params.path}" but there is no file at this path. Double-check the path exists and is spelled correctly.`
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
if (isSymlink(abs)) {
|
|
682
|
+
return {
|
|
683
|
+
lines: [],
|
|
684
|
+
error: `[SYMLINK] You tried to read "${params.path}" but this is a symlink. Try reading the actual file it points to instead.`
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
if (!isTextualFile(abs)) {
|
|
688
|
+
return {
|
|
689
|
+
lines: [],
|
|
690
|
+
error: `[UNREADABLE FILE] You tried to read "${params.path}" but this file is either too large or not a text file, so it cannot be read. Try a different file.`
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
let lines;
|
|
694
|
+
try {
|
|
695
|
+
lines = await readAllLines(abs);
|
|
696
|
+
} catch (err) {
|
|
697
|
+
return {
|
|
698
|
+
lines: [],
|
|
699
|
+
error: `[READ ERROR] Failed to read "${params.path}": ${err instanceof Error ? err.message : String(err)}`
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
const total = lines.length;
|
|
703
|
+
const rawStart = params.start;
|
|
704
|
+
const rawEnd = params.end;
|
|
705
|
+
let s = 1;
|
|
706
|
+
let e = total;
|
|
707
|
+
const startValid = rawStart === void 0 || Number.isFinite(rawStart) && rawStart > 0;
|
|
708
|
+
const endValid = rawEnd === void 0 || Number.isFinite(rawEnd) && rawEnd > 0;
|
|
709
|
+
if (startValid && endValid) {
|
|
710
|
+
s = rawStart ?? 1;
|
|
711
|
+
e = Math.min(rawEnd ?? total, total);
|
|
712
|
+
if (s > total && total > 0 || s > e) {
|
|
713
|
+
s = 1;
|
|
714
|
+
e = total;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
const out = [];
|
|
718
|
+
for (let i = s; i <= e; i += 1) {
|
|
719
|
+
const content = lines[i - 1] ?? "";
|
|
720
|
+
out.push(`${i}|${content}`);
|
|
721
|
+
}
|
|
722
|
+
if (out.length > AGENT_CONFIG.MAX_READ_LINES) {
|
|
723
|
+
const truncated = out.slice(0, AGENT_CONFIG.MAX_READ_LINES);
|
|
724
|
+
truncated.push(`... [truncated: showing ${AGENT_CONFIG.MAX_READ_LINES} of ${out.length} lines]`);
|
|
725
|
+
return { lines: truncated };
|
|
726
|
+
}
|
|
727
|
+
return { lines: out };
|
|
728
|
+
}
|
|
729
|
+
async listDirectory(params) {
|
|
730
|
+
let abs;
|
|
731
|
+
try {
|
|
732
|
+
abs = resolveUnderRepo(this.repoRoot, params.path);
|
|
733
|
+
} catch {
|
|
734
|
+
return [];
|
|
735
|
+
}
|
|
736
|
+
const stat = await import_promises2.default.stat(abs).catch(() => null);
|
|
737
|
+
if (!stat || !stat.isDirectory()) {
|
|
738
|
+
return [];
|
|
739
|
+
}
|
|
740
|
+
const maxResults = params.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES;
|
|
741
|
+
const maxDepth = params.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
|
|
742
|
+
const regex = params.pattern ? new RegExp(params.pattern) : null;
|
|
743
|
+
const repoRoot = this.repoRoot;
|
|
744
|
+
const results = [];
|
|
745
|
+
let timedOut = false;
|
|
746
|
+
const startTime = Date.now();
|
|
747
|
+
async function walk(dir, depth) {
|
|
748
|
+
if (Date.now() - startTime > AGENT_CONFIG.LIST_TIMEOUT_MS) {
|
|
749
|
+
timedOut = true;
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (depth > maxDepth || results.length >= maxResults) return;
|
|
753
|
+
const entries = await import_promises2.default.readdir(dir, { withFileTypes: true });
|
|
754
|
+
for (const entry of entries) {
|
|
755
|
+
if (timedOut || results.length >= maxResults) break;
|
|
756
|
+
if (shouldSkip2(entry.name)) continue;
|
|
757
|
+
if (regex && !regex.test(entry.name)) continue;
|
|
758
|
+
const full = import_path5.default.join(dir, entry.name);
|
|
759
|
+
const isDir = entry.isDirectory();
|
|
760
|
+
results.push({
|
|
761
|
+
name: entry.name,
|
|
762
|
+
path: toRepoRelative(repoRoot, full),
|
|
763
|
+
type: isDir ? "dir" : "file",
|
|
764
|
+
depth
|
|
765
|
+
});
|
|
766
|
+
if (isDir) {
|
|
767
|
+
await walk(full, depth + 1);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
await walk(abs, 0);
|
|
772
|
+
return results;
|
|
773
|
+
}
|
|
603
774
|
};
|
|
604
775
|
}
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
// index.ts
|
|
779
|
+
var index_exports = {};
|
|
780
|
+
__export(index_exports, {
|
|
781
|
+
AnthropicRouter: () => AnthropicRouter,
|
|
782
|
+
AnthropicToolFactory: () => AnthropicToolFactory,
|
|
783
|
+
BrowserClient: () => BrowserClient,
|
|
784
|
+
CodebaseSearchClient: () => CodebaseSearchClient,
|
|
785
|
+
FastApplyClient: () => FastApplyClient,
|
|
786
|
+
GeminiRouter: () => GeminiRouter,
|
|
787
|
+
LocalRipgrepProvider: () => LocalRipgrepProvider,
|
|
788
|
+
MorphClient: () => MorphClient,
|
|
789
|
+
MorphGit: () => MorphGit,
|
|
790
|
+
OpenAIRouter: () => OpenAIRouter,
|
|
791
|
+
OpenAIToolFactory: () => OpenAIToolFactory,
|
|
792
|
+
RawRouter: () => RawRouter,
|
|
793
|
+
VercelToolFactory: () => VercelToolFactory,
|
|
794
|
+
WarpGrepClient: () => WarpGrepClient,
|
|
795
|
+
applyEdit: () => applyEdit
|
|
796
|
+
});
|
|
797
|
+
module.exports = __toCommonJS(index_exports);
|
|
798
|
+
|
|
799
|
+
// tools/fastapply/core.ts
|
|
800
|
+
var import_path = require("path");
|
|
801
|
+
init_apply();
|
|
802
|
+
var DEFAULT_CONFIG = {
|
|
803
|
+
morphApiUrl: "https://api.morphllm.com",
|
|
804
|
+
baseDir: process.cwd(),
|
|
805
|
+
generateUdiff: true,
|
|
806
|
+
autoWrite: true,
|
|
807
|
+
timeout: 3e4,
|
|
808
|
+
debug: false
|
|
605
809
|
};
|
|
606
|
-
var
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
this.
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
constructor(message = "Rate limit exceeded. Please retry later.", options) {
|
|
618
|
-
super(message, "rate_limit_exceeded", 429, { requestId: options?.requestId });
|
|
619
|
-
this.name = "MorphRateLimitError";
|
|
620
|
-
this.resetAt = options?.resetAt;
|
|
621
|
-
this.retryAfter = options?.retryAfter;
|
|
622
|
-
}
|
|
623
|
-
toJSON() {
|
|
624
|
-
return {
|
|
625
|
-
...super.toJSON(),
|
|
626
|
-
resetAt: this.resetAt,
|
|
627
|
-
retryAfter: this.retryAfter
|
|
810
|
+
var FastApplyClient = class {
|
|
811
|
+
config;
|
|
812
|
+
constructor(config = {}) {
|
|
813
|
+
this.config = {
|
|
814
|
+
morphApiKey: config.apiKey,
|
|
815
|
+
morphApiUrl: DEFAULT_CONFIG.morphApiUrl,
|
|
816
|
+
debug: config.debug,
|
|
817
|
+
timeout: config.timeout || DEFAULT_CONFIG.timeout,
|
|
818
|
+
retryConfig: config.retryConfig,
|
|
819
|
+
generateUdiff: DEFAULT_CONFIG.generateUdiff,
|
|
820
|
+
autoWrite: DEFAULT_CONFIG.autoWrite
|
|
628
821
|
};
|
|
629
822
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
this.name = "MorphNotFoundError";
|
|
640
|
-
this.resourceType = resourceType;
|
|
641
|
-
this.resourceId = resourceId;
|
|
823
|
+
/**
|
|
824
|
+
* Execute a file edit operation
|
|
825
|
+
*
|
|
826
|
+
* @param input - Edit parameters including filepath, instructions, and code_edit
|
|
827
|
+
* @param overrides - Optional config overrides for this operation
|
|
828
|
+
* @returns Edit result with success status and changes
|
|
829
|
+
*/
|
|
830
|
+
async execute(input, overrides) {
|
|
831
|
+
return executeEditFile(input, { ...this.config, ...overrides });
|
|
642
832
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
833
|
+
/**
|
|
834
|
+
* Apply an edit to code directly without file I/O
|
|
835
|
+
*
|
|
836
|
+
* Useful for sandbox environments or when you manage your own file system.
|
|
837
|
+
* Compatible with the earlier OpenAI client API contract.
|
|
838
|
+
*
|
|
839
|
+
* @param input - Code and edit parameters
|
|
840
|
+
* @param overrides - Optional config overrides for this operation
|
|
841
|
+
* @returns Result with merged code
|
|
842
|
+
*
|
|
843
|
+
* @example
|
|
844
|
+
* ```typescript
|
|
845
|
+
* const result = await client.applyEdit({
|
|
846
|
+
* originalCode: 'function hello() { return "world"; }',
|
|
847
|
+
* codeEdit: 'function hello() { return "universe"; }',
|
|
848
|
+
* instructions: 'Change return value'
|
|
849
|
+
* });
|
|
850
|
+
* console.log(result.mergedCode);
|
|
851
|
+
* ```
|
|
852
|
+
*/
|
|
853
|
+
async applyEdit(input, overrides) {
|
|
854
|
+
const { applyEdit: applyEdit2 } = await Promise.resolve().then(() => (init_apply(), apply_exports));
|
|
855
|
+
return applyEdit2(input, { ...this.config, ...overrides });
|
|
649
856
|
}
|
|
650
857
|
};
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
super(message, "profile_limit_exceeded", 403, { requestId: options?.requestId });
|
|
658
|
-
this.name = "MorphProfileLimitError";
|
|
659
|
-
this.currentCount = options?.currentCount;
|
|
660
|
-
this.maxAllowed = options?.maxAllowed;
|
|
661
|
-
}
|
|
662
|
-
toJSON() {
|
|
858
|
+
async function executeEditFile(input, config = {}) {
|
|
859
|
+
const baseDir = config.baseDir || DEFAULT_CONFIG.baseDir;
|
|
860
|
+
const fullPath = (0, import_path.resolve)((0, import_path.join)(baseDir, input.target_filepath));
|
|
861
|
+
const debug = config.debug || false;
|
|
862
|
+
const relativePath = (0, import_path.relative)(baseDir, fullPath);
|
|
863
|
+
if (relativePath.startsWith("..") || fullPath === baseDir) {
|
|
663
864
|
return {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
865
|
+
success: false,
|
|
866
|
+
filepath: input.target_filepath,
|
|
867
|
+
changes: { linesAdded: 0, linesRemoved: 0, linesModified: 0 },
|
|
868
|
+
error: `Invalid filepath: '${input.target_filepath}' is outside baseDir`
|
|
667
869
|
};
|
|
668
870
|
}
|
|
669
|
-
};
|
|
670
|
-
function parseAPIError(statusCode, responseText, requestId) {
|
|
671
|
-
let errorData = {};
|
|
672
871
|
try {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
if (code === "profile_limit_exceeded" || message.toLowerCase().includes("limit")) {
|
|
683
|
-
return new MorphProfileLimitError(message, { requestId });
|
|
684
|
-
}
|
|
685
|
-
return new MorphAPIError(message, "insufficient_permissions", statusCode, { requestId, rawResponse: responseText });
|
|
686
|
-
case 404:
|
|
687
|
-
if (message.toLowerCase().includes("profile")) {
|
|
688
|
-
return new MorphNotFoundError("Profile", void 0);
|
|
689
|
-
}
|
|
690
|
-
if (message.toLowerCase().includes("session")) {
|
|
691
|
-
return new MorphNotFoundError("Session", void 0);
|
|
872
|
+
if (debug) console.log(`[FastApply] Reading file: ${input.target_filepath}`);
|
|
873
|
+
const { readFile, writeFile } = await import("fs/promises");
|
|
874
|
+
const { callMorphAPI: callMorphAPI2, generateUdiff: generateUdiff2, countChanges: countChanges2 } = await Promise.resolve().then(() => (init_apply(), apply_exports));
|
|
875
|
+
let originalCode = "";
|
|
876
|
+
try {
|
|
877
|
+
originalCode = await readFile(fullPath, "utf-8");
|
|
878
|
+
} catch (error) {
|
|
879
|
+
if (error.code !== "ENOENT") {
|
|
880
|
+
throw error;
|
|
692
881
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
882
|
+
if (debug) console.log(`[FastApply] File doesn't exist, will create new file`);
|
|
883
|
+
}
|
|
884
|
+
const mergedCode = await callMorphAPI2(originalCode, input.code_edit, input.instructions, input.target_filepath, config);
|
|
885
|
+
const udiff = config.generateUdiff !== false ? generateUdiff2(originalCode, mergedCode, input.target_filepath) : void 0;
|
|
886
|
+
if (config.autoWrite !== false) {
|
|
887
|
+
await writeFile(fullPath, mergedCode, "utf-8");
|
|
888
|
+
if (debug) console.log(`[FastApply] Wrote ${mergedCode.length} chars to ${input.target_filepath}`);
|
|
889
|
+
}
|
|
890
|
+
const changes = countChanges2(originalCode, mergedCode);
|
|
891
|
+
return {
|
|
892
|
+
success: true,
|
|
893
|
+
filepath: input.target_filepath,
|
|
894
|
+
udiff,
|
|
895
|
+
changes
|
|
896
|
+
};
|
|
897
|
+
} catch (error) {
|
|
898
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
899
|
+
if (debug) console.error(`[FastApply] Error: ${errorMessage}`);
|
|
900
|
+
return {
|
|
901
|
+
success: false,
|
|
902
|
+
filepath: input.target_filepath,
|
|
903
|
+
changes: { linesAdded: 0, linesRemoved: 0, linesModified: 0 },
|
|
904
|
+
error: errorMessage
|
|
905
|
+
};
|
|
705
906
|
}
|
|
706
907
|
}
|
|
707
908
|
|
|
708
|
-
// tools/
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
id: api.id,
|
|
712
|
-
name: api.name,
|
|
713
|
-
repoId: api.repo_id,
|
|
714
|
-
cookieDomains: api.cookie_domains,
|
|
715
|
-
lastUsedAt: api.last_used_at,
|
|
716
|
-
createdAt: api.created_at,
|
|
717
|
-
updatedAt: api.updated_at
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
function transformCreateInput(input) {
|
|
721
|
-
return {
|
|
722
|
-
name: input.name,
|
|
723
|
-
repo_id: input.repoId
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
function transformSession(api) {
|
|
727
|
-
return {
|
|
728
|
-
sessionId: api.session_id,
|
|
729
|
-
debugUrl: api.debug_url || ""
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
function transformSaveInput(input) {
|
|
733
|
-
return {
|
|
734
|
-
session_id: input.sessionId,
|
|
735
|
-
profile_id: input.profileId
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
function transformStateResponse(api) {
|
|
739
|
-
return {
|
|
740
|
-
profileId: api.profile_id,
|
|
741
|
-
stateUrl: api.state_url,
|
|
742
|
-
expiresIn: api.expires_in
|
|
743
|
-
};
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// tools/browser/profiles/core.ts
|
|
747
|
-
var DEFAULT_API_URL = process.env.MORPH_ENVIRONMENT === "DEV" ? "http://localhost:8000" : "https://browser.morphllm.com";
|
|
748
|
-
var ProfilesClient = class {
|
|
909
|
+
// tools/codebase_search/core.ts
|
|
910
|
+
init_resilience();
|
|
911
|
+
var CodebaseSearchClient = class {
|
|
749
912
|
config;
|
|
750
|
-
constructor(config) {
|
|
751
|
-
this.config =
|
|
913
|
+
constructor(config = {}) {
|
|
914
|
+
this.config = {
|
|
915
|
+
apiKey: config.apiKey,
|
|
916
|
+
searchUrl: process.env.MORPH_SEARCH_URL || "https://repos.morphllm.com",
|
|
917
|
+
debug: config.debug,
|
|
918
|
+
timeout: config.timeout || 3e4,
|
|
919
|
+
retryConfig: config.retryConfig
|
|
920
|
+
};
|
|
752
921
|
}
|
|
753
922
|
/**
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
* @throws {MorphProfileLimitError} If profile limit is exceeded
|
|
760
|
-
* @throws {MorphAuthenticationError} If API key is missing or invalid
|
|
761
|
-
*
|
|
762
|
-
* @example
|
|
763
|
-
* ```typescript
|
|
764
|
-
* const setup = await morph.browser.profiles.createProfile({
|
|
765
|
-
* name: 'LinkedIn Production',
|
|
766
|
-
* repoId: 'owner/repo'
|
|
767
|
-
* });
|
|
768
|
-
* console.log(setup.session.debugUrl);
|
|
769
|
-
* await setup.save();
|
|
770
|
-
* ```
|
|
771
|
-
*/
|
|
772
|
-
async createProfile(input) {
|
|
773
|
-
return createProfile(input, this.config);
|
|
774
|
-
}
|
|
775
|
-
/**
|
|
776
|
-
* List all profiles for the authenticated user.
|
|
777
|
-
*
|
|
778
|
-
* @param repoId - Optional repository ID to filter by
|
|
779
|
-
* @returns Array of profiles
|
|
780
|
-
*
|
|
781
|
-
* @example
|
|
782
|
-
* ```typescript
|
|
783
|
-
* // List all profiles
|
|
784
|
-
* const allProfiles = await morph.browser.profiles.listProfiles();
|
|
785
|
-
*
|
|
786
|
-
* // List profiles for a specific repo
|
|
787
|
-
* const repoProfiles = await morph.browser.profiles.listProfiles('owner/repo');
|
|
788
|
-
* ```
|
|
923
|
+
* Execute a semantic code search
|
|
924
|
+
*
|
|
925
|
+
* @param input - Search parameters including query, repoId, and target directories
|
|
926
|
+
* @param overrides - Optional config overrides for this operation
|
|
927
|
+
* @returns Search results with ranked code matches
|
|
789
928
|
*/
|
|
790
|
-
async
|
|
791
|
-
return
|
|
929
|
+
async search(input, overrides) {
|
|
930
|
+
return executeCodebaseSearch(
|
|
931
|
+
{
|
|
932
|
+
query: input.query,
|
|
933
|
+
target_directories: input.target_directories,
|
|
934
|
+
explanation: input.explanation,
|
|
935
|
+
limit: input.limit
|
|
936
|
+
},
|
|
937
|
+
{ ...this.config, repoId: input.repoId, ...overrides }
|
|
938
|
+
);
|
|
792
939
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
* @throws {MorphNotFoundError} If profile is not found
|
|
799
|
-
*
|
|
800
|
-
* @example
|
|
801
|
-
* ```typescript
|
|
802
|
-
* const profile = await morph.browser.profiles.getProfile('profile-id');
|
|
803
|
-
* const state = await profile.getState();
|
|
804
|
-
* await profile.delete();
|
|
805
|
-
* ```
|
|
806
|
-
*/
|
|
807
|
-
async getProfile(id) {
|
|
808
|
-
return getProfile(id, this.config);
|
|
940
|
+
};
|
|
941
|
+
async function executeCodebaseSearch(input, config) {
|
|
942
|
+
const apiKey = config.apiKey || process.env.MORPH_API_KEY;
|
|
943
|
+
if (!apiKey) {
|
|
944
|
+
throw new Error("MORPH_API_KEY not found. Set environment variable or pass in config");
|
|
809
945
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
async updateProfile(id) {
|
|
817
|
-
return updateProfile(id, this.config);
|
|
946
|
+
const searchUrl = config.searchUrl || process.env.MORPH_SEARCH_URL || "https://repos.morphllm.com";
|
|
947
|
+
const timeout = config.timeout || 3e4;
|
|
948
|
+
const debug = config.debug || false;
|
|
949
|
+
if (debug) {
|
|
950
|
+
console.log(`[CodebaseSearch] Query: "${input.query.slice(0, 60)}..." repo=${config.repoId}`);
|
|
951
|
+
console.log(`[CodebaseSearch] URL: ${searchUrl}/v1/codebase_search`);
|
|
818
952
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
953
|
+
const startTime = Date.now();
|
|
954
|
+
try {
|
|
955
|
+
const fetchPromise = fetchWithRetry(
|
|
956
|
+
`${searchUrl}/v1/codebase_search`,
|
|
957
|
+
{
|
|
958
|
+
method: "POST",
|
|
959
|
+
headers: {
|
|
960
|
+
"Content-Type": "application/json",
|
|
961
|
+
"Authorization": `Bearer ${apiKey}`
|
|
962
|
+
},
|
|
963
|
+
body: JSON.stringify({
|
|
964
|
+
query: input.query,
|
|
965
|
+
repoId: config.repoId,
|
|
966
|
+
targetDirectories: input.target_directories || [],
|
|
967
|
+
limit: input.limit || 10,
|
|
968
|
+
candidateLimit: 50
|
|
969
|
+
})
|
|
970
|
+
},
|
|
971
|
+
config.retryConfig
|
|
972
|
+
);
|
|
973
|
+
const response = await withTimeout(fetchPromise, timeout, `Codebase search timed out after ${timeout}ms`);
|
|
974
|
+
if (!response.ok) {
|
|
975
|
+
const errorText = await response.text();
|
|
976
|
+
if (debug) console.error(`[CodebaseSearch] Error: ${response.status} - ${errorText}`);
|
|
977
|
+
return {
|
|
978
|
+
success: false,
|
|
979
|
+
results: [],
|
|
980
|
+
stats: { totalResults: 0, candidatesRetrieved: 0, searchTimeMs: 0 },
|
|
981
|
+
error: `Search failed (${response.status}): ${errorText}`
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
const data = await response.json();
|
|
985
|
+
const elapsed = Date.now() - startTime;
|
|
986
|
+
if (debug) {
|
|
987
|
+
console.log(`[CodebaseSearch] \u2705 ${data.results?.length || 0} results in ${elapsed}ms`);
|
|
988
|
+
}
|
|
989
|
+
return {
|
|
990
|
+
success: true,
|
|
991
|
+
results: data.results || [],
|
|
992
|
+
stats: data.stats || { totalResults: 0, candidatesRetrieved: 0, searchTimeMs: elapsed }
|
|
993
|
+
};
|
|
994
|
+
} catch (error) {
|
|
995
|
+
if (debug) console.error(`[CodebaseSearch] Exception: ${error instanceof Error ? error.message : error}`);
|
|
996
|
+
return {
|
|
997
|
+
success: false,
|
|
998
|
+
results: [],
|
|
999
|
+
stats: { totalResults: 0, candidatesRetrieved: 0, searchTimeMs: 0 },
|
|
1000
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
1001
|
+
};
|
|
827
1002
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// tools/browser/core.ts
|
|
1006
|
+
init_resilience();
|
|
1007
|
+
|
|
1008
|
+
// tools/browser/live.ts
|
|
1009
|
+
var LIVE_PRESETS = {
|
|
1010
|
+
/** Read-only monitoring (no interaction) */
|
|
1011
|
+
readonly: { interactive: false },
|
|
1012
|
+
/** Interactive control (human-in-the-loop) */
|
|
1013
|
+
interactive: { interactive: true },
|
|
1014
|
+
/** Watch-only without controls */
|
|
1015
|
+
monitoring: { interactive: false, showControls: false }
|
|
1016
|
+
};
|
|
1017
|
+
function buildLiveUrl(debugUrl, options = {}) {
|
|
1018
|
+
if (!debugUrl) {
|
|
1019
|
+
throw new Error(
|
|
1020
|
+
"debugUrl is required. Ensure your backend returns debugUrl in the task response. Contact support@morphllm.com if you need help."
|
|
1021
|
+
);
|
|
847
1022
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
* Extracts cookies, localStorage, and sessionStorage.
|
|
853
|
-
*
|
|
854
|
-
* @param sessionId - Browser session ID from startSession
|
|
855
|
-
* @param profileId - Profile ID to save state to
|
|
856
|
-
* @returns Updated profile with cookie domains
|
|
857
|
-
*/
|
|
858
|
-
async saveSession(sessionId, profileId) {
|
|
859
|
-
return saveProfileSession({ sessionId, profileId }, this.config);
|
|
1023
|
+
const normalized = normalizeLiveUrl(debugUrl);
|
|
1024
|
+
const url = new URL(normalized);
|
|
1025
|
+
if (options.interactive !== void 0) {
|
|
1026
|
+
url.searchParams.set("interactive", String(options.interactive));
|
|
860
1027
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
*
|
|
864
|
-
* @returns Repo summaries with profile counts
|
|
865
|
-
*/
|
|
866
|
-
async listRepos() {
|
|
867
|
-
return listRepos(this.config);
|
|
1028
|
+
if (options.theme) {
|
|
1029
|
+
url.searchParams.set("theme", options.theme);
|
|
868
1030
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
*
|
|
872
|
-
* Use this to download the raw state JSON for debugging
|
|
873
|
-
* or to restore state manually.
|
|
874
|
-
*
|
|
875
|
-
* @param profileId - Profile ID
|
|
876
|
-
* @returns State URL with expiry information
|
|
877
|
-
*/
|
|
878
|
-
async getProfileState(profileId) {
|
|
879
|
-
return getProfileState(profileId, this.config);
|
|
1031
|
+
if (options.showControls !== void 0) {
|
|
1032
|
+
url.searchParams.set("showControls", String(options.showControls));
|
|
880
1033
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
if (!input.name || typeof input.name !== "string") {
|
|
884
|
-
throw new MorphValidationError("name is required", "name");
|
|
1034
|
+
if (options.pageId) {
|
|
1035
|
+
url.searchParams.set("pageId", options.pageId);
|
|
885
1036
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
throw new MorphValidationError("name cannot be empty", "name");
|
|
1037
|
+
if (options.pageIndex) {
|
|
1038
|
+
url.searchParams.set("pageIndex", options.pageIndex);
|
|
889
1039
|
}
|
|
890
|
-
|
|
891
|
-
|
|
1040
|
+
return url.toString();
|
|
1041
|
+
}
|
|
1042
|
+
function normalizeLiveUrl(debugUrl) {
|
|
1043
|
+
const trimmed = debugUrl.trim();
|
|
1044
|
+
if (!trimmed) {
|
|
1045
|
+
return trimmed;
|
|
892
1046
|
}
|
|
893
|
-
if (
|
|
894
|
-
|
|
1047
|
+
if (trimmed.startsWith("wss://") || trimmed.startsWith("ws://")) {
|
|
1048
|
+
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
895
1049
|
}
|
|
896
|
-
|
|
897
|
-
|
|
1050
|
+
let url;
|
|
1051
|
+
try {
|
|
1052
|
+
url = new URL(trimmed);
|
|
1053
|
+
} catch {
|
|
1054
|
+
return trimmed;
|
|
898
1055
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
if (!id || typeof id !== "string") {
|
|
902
|
-
throw new MorphValidationError(`${fieldName} is required`, fieldName);
|
|
1056
|
+
if (url.protocol === "wss:" || url.protocol === "ws:") {
|
|
1057
|
+
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
903
1058
|
}
|
|
904
|
-
|
|
905
|
-
|
|
1059
|
+
const wssParam = url.searchParams.get("wss");
|
|
1060
|
+
if (wssParam && (wssParam.startsWith("wss://") || wssParam.startsWith("ws://"))) {
|
|
1061
|
+
url.searchParams.set("wss", wssParam);
|
|
906
1062
|
}
|
|
1063
|
+
return url.toString();
|
|
907
1064
|
}
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1065
|
+
function buildLiveIframe(debugUrl, options = {}) {
|
|
1066
|
+
const {
|
|
1067
|
+
width = "100%",
|
|
1068
|
+
height = "600px",
|
|
1069
|
+
style = "",
|
|
1070
|
+
className = "",
|
|
1071
|
+
...sessionOptions
|
|
1072
|
+
} = options;
|
|
1073
|
+
const src = buildLiveUrl(debugUrl, sessionOptions);
|
|
1074
|
+
const widthStr = typeof width === "number" ? `${width}px` : width;
|
|
1075
|
+
const heightStr = typeof height === "number" ? `${height}px` : height;
|
|
1076
|
+
const baseStyle = `width: ${widthStr}; height: ${heightStr}; border: none;`;
|
|
1077
|
+
const fullStyle = style ? `${baseStyle} ${style}` : baseStyle;
|
|
1078
|
+
const attributes = [
|
|
1079
|
+
`src="${src}"`,
|
|
1080
|
+
`style="${fullStyle}"`
|
|
1081
|
+
];
|
|
1082
|
+
if (className) {
|
|
1083
|
+
attributes.push(`class="${className}"`);
|
|
925
1084
|
}
|
|
926
|
-
|
|
927
|
-
const profile = transformProfile(apiProfile);
|
|
928
|
-
const session = await startProfileSession(config, { profileId: profile.id });
|
|
929
|
-
return buildProfileSetup(profile, session, config);
|
|
1085
|
+
return `<iframe ${attributes.join(" ")}></iframe>`;
|
|
930
1086
|
}
|
|
931
|
-
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
const response = await fetchWithRetry(
|
|
936
|
-
url,
|
|
937
|
-
{ method: "GET", headers },
|
|
938
|
-
config.retryConfig
|
|
939
|
-
);
|
|
940
|
-
if (!response.ok) {
|
|
941
|
-
const errorText = await response.text().catch(() => response.statusText);
|
|
942
|
-
const requestId = response.headers.get("x-request-id") || void 0;
|
|
943
|
-
throw parseAPIError(response.status, errorText, requestId);
|
|
944
|
-
}
|
|
945
|
-
const data = await response.json();
|
|
946
|
-
return data.profiles.map(transformProfile);
|
|
1087
|
+
function buildEmbedCode(debugUrl, options = {}) {
|
|
1088
|
+
const iframe = buildLiveIframe(debugUrl, options);
|
|
1089
|
+
return `<!-- Embed Morph Live Session -->
|
|
1090
|
+
${iframe}`;
|
|
947
1091
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
const headers = buildHeaders(config);
|
|
952
|
-
const response = await fetchWithRetry(
|
|
953
|
-
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
954
|
-
{ method: "GET", headers },
|
|
955
|
-
config.retryConfig
|
|
956
|
-
);
|
|
957
|
-
if (!response.ok) {
|
|
958
|
-
const errorText = await response.text().catch(() => response.statusText);
|
|
959
|
-
const requestId = response.headers.get("x-request-id") || void 0;
|
|
960
|
-
throw parseAPIError(response.status, errorText, requestId);
|
|
1092
|
+
function resolvePreset(optionsOrPreset) {
|
|
1093
|
+
if (!optionsOrPreset) {
|
|
1094
|
+
return {};
|
|
961
1095
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
async function updateProfile(id, config = {}) {
|
|
971
|
-
validateId(id, "id");
|
|
972
|
-
const profile = await fetchProfile(id, config);
|
|
973
|
-
const session = await startProfileSession(config, { profileId: profile.id });
|
|
974
|
-
return buildProfileSetup(profile, session, config);
|
|
975
|
-
}
|
|
976
|
-
async function deleteProfile(id, config = {}) {
|
|
977
|
-
validateId(id, "id");
|
|
978
|
-
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
979
|
-
const headers = buildHeaders(config);
|
|
980
|
-
const response = await fetchWithRetry(
|
|
981
|
-
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
982
|
-
{ method: "DELETE", headers },
|
|
983
|
-
config.retryConfig
|
|
984
|
-
);
|
|
985
|
-
if (!response.ok) {
|
|
986
|
-
const errorText = await response.text().catch(() => response.statusText);
|
|
987
|
-
const requestId = response.headers.get("x-request-id") || void 0;
|
|
988
|
-
throw parseAPIError(response.status, errorText, requestId);
|
|
1096
|
+
if (typeof optionsOrPreset === "string") {
|
|
1097
|
+
const preset = LIVE_PRESETS[optionsOrPreset];
|
|
1098
|
+
if (!preset) {
|
|
1099
|
+
throw new Error(
|
|
1100
|
+
`Unknown preset: ${optionsOrPreset}. Available presets: ${Object.keys(LIVE_PRESETS).join(", ")}`
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
return preset;
|
|
989
1104
|
}
|
|
1105
|
+
return optionsOrPreset;
|
|
990
1106
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1107
|
+
|
|
1108
|
+
// tools/browser/profiles/core.ts
|
|
1109
|
+
init_resilience();
|
|
1110
|
+
|
|
1111
|
+
// tools/browser/errors.ts
|
|
1112
|
+
var MorphError = class extends Error {
|
|
1113
|
+
/** Error code for programmatic handling */
|
|
1114
|
+
code;
|
|
1115
|
+
/** Original cause of the error, if any */
|
|
1116
|
+
cause;
|
|
1117
|
+
constructor(message, code, cause) {
|
|
1118
|
+
super(message);
|
|
1119
|
+
this.name = "MorphError";
|
|
1120
|
+
this.code = code;
|
|
1121
|
+
this.cause = cause;
|
|
1122
|
+
if (Error.captureStackTrace) {
|
|
1123
|
+
Error.captureStackTrace(this, this.constructor);
|
|
1124
|
+
}
|
|
994
1125
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
}
|
|
1005
|
-
config.retryConfig
|
|
1006
|
-
);
|
|
1007
|
-
if (!response.ok) {
|
|
1008
|
-
const errorText = await response.text().catch(() => response.statusText);
|
|
1009
|
-
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1010
|
-
throw parseAPIError(response.status, errorText, requestId);
|
|
1126
|
+
/**
|
|
1127
|
+
* Returns a JSON representation of the error for logging.
|
|
1128
|
+
*/
|
|
1129
|
+
toJSON() {
|
|
1130
|
+
return {
|
|
1131
|
+
name: this.name,
|
|
1132
|
+
message: this.message,
|
|
1133
|
+
code: this.code,
|
|
1134
|
+
cause: this.cause?.message
|
|
1135
|
+
};
|
|
1011
1136
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
const response = await fetchWithRetry(
|
|
1021
|
-
`${apiUrl}/profiles/session/save`,
|
|
1022
|
-
{
|
|
1023
|
-
method: "POST",
|
|
1024
|
-
headers,
|
|
1025
|
-
body: JSON.stringify(transformSaveInput(input))
|
|
1026
|
-
},
|
|
1027
|
-
config.retryConfig
|
|
1028
|
-
);
|
|
1029
|
-
if (!response.ok) {
|
|
1030
|
-
const errorText = await response.text().catch(() => response.statusText);
|
|
1031
|
-
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1032
|
-
throw parseAPIError(response.status, errorText, requestId);
|
|
1137
|
+
};
|
|
1138
|
+
var MorphValidationError = class extends MorphError {
|
|
1139
|
+
/** The field that failed validation */
|
|
1140
|
+
field;
|
|
1141
|
+
constructor(message, field) {
|
|
1142
|
+
super(message, "validation_error");
|
|
1143
|
+
this.name = "MorphValidationError";
|
|
1144
|
+
this.field = field;
|
|
1033
1145
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
const headers = buildHeaders(config);
|
|
1040
|
-
const response = await fetchWithRetry(
|
|
1041
|
-
`${apiUrl}/repos`,
|
|
1042
|
-
{ method: "GET", headers },
|
|
1043
|
-
config.retryConfig
|
|
1044
|
-
);
|
|
1045
|
-
if (!response.ok) {
|
|
1046
|
-
const errorText = await response.text().catch(() => response.statusText);
|
|
1047
|
-
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1048
|
-
throw parseAPIError(response.status, errorText, requestId);
|
|
1146
|
+
toJSON() {
|
|
1147
|
+
return {
|
|
1148
|
+
...super.toJSON(),
|
|
1149
|
+
field: this.field
|
|
1150
|
+
};
|
|
1049
1151
|
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
{ method: "GET", headers },
|
|
1065
|
-
config.retryConfig
|
|
1066
|
-
);
|
|
1067
|
-
if (!response.ok) {
|
|
1068
|
-
const errorText = await response.text().catch(() => response.statusText);
|
|
1069
|
-
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1070
|
-
throw parseAPIError(response.status, errorText, requestId);
|
|
1152
|
+
};
|
|
1153
|
+
var MorphAPIError = class extends MorphError {
|
|
1154
|
+
/** HTTP status code */
|
|
1155
|
+
statusCode;
|
|
1156
|
+
/** Request ID for debugging (if available) */
|
|
1157
|
+
requestId;
|
|
1158
|
+
/** Raw response body */
|
|
1159
|
+
rawResponse;
|
|
1160
|
+
constructor(message, code, statusCode, options) {
|
|
1161
|
+
super(message, code, options?.cause);
|
|
1162
|
+
this.name = "MorphAPIError";
|
|
1163
|
+
this.statusCode = statusCode;
|
|
1164
|
+
this.requestId = options?.requestId;
|
|
1165
|
+
this.rawResponse = options?.rawResponse;
|
|
1071
1166
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1167
|
+
toJSON() {
|
|
1168
|
+
return {
|
|
1169
|
+
...super.toJSON(),
|
|
1170
|
+
statusCode: this.statusCode,
|
|
1171
|
+
requestId: this.requestId
|
|
1172
|
+
};
|
|
1079
1173
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1174
|
+
};
|
|
1175
|
+
var MorphAuthenticationError = class extends MorphAPIError {
|
|
1176
|
+
constructor(message = "Authentication required. Please provide a valid API key.") {
|
|
1177
|
+
super(message, "authentication_required", 401);
|
|
1178
|
+
this.name = "MorphAuthenticationError";
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
var MorphRateLimitError = class extends MorphAPIError {
|
|
1182
|
+
/** When the rate limit resets (Unix timestamp) */
|
|
1183
|
+
resetAt;
|
|
1184
|
+
/** Number of seconds until reset */
|
|
1185
|
+
retryAfter;
|
|
1186
|
+
constructor(message = "Rate limit exceeded. Please retry later.", options) {
|
|
1187
|
+
super(message, "rate_limit_exceeded", 429, { requestId: options?.requestId });
|
|
1188
|
+
this.name = "MorphRateLimitError";
|
|
1189
|
+
this.resetAt = options?.resetAt;
|
|
1190
|
+
this.retryAfter = options?.retryAfter;
|
|
1191
|
+
}
|
|
1192
|
+
toJSON() {
|
|
1193
|
+
return {
|
|
1194
|
+
...super.toJSON(),
|
|
1195
|
+
resetAt: this.resetAt,
|
|
1196
|
+
retryAfter: this.retryAfter
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
1200
|
+
var MorphNotFoundError = class extends MorphAPIError {
|
|
1201
|
+
/** The type of resource that was not found */
|
|
1202
|
+
resourceType;
|
|
1203
|
+
/** The ID of the resource that was not found */
|
|
1204
|
+
resourceId;
|
|
1205
|
+
constructor(resourceType, resourceId) {
|
|
1206
|
+
const message = resourceId ? `${resourceType} '${resourceId}' not found` : `${resourceType} not found`;
|
|
1207
|
+
super(message, "resource_not_found", 404);
|
|
1208
|
+
this.name = "MorphNotFoundError";
|
|
1209
|
+
this.resourceType = resourceType;
|
|
1210
|
+
this.resourceId = resourceId;
|
|
1211
|
+
}
|
|
1212
|
+
toJSON() {
|
|
1213
|
+
return {
|
|
1214
|
+
...super.toJSON(),
|
|
1215
|
+
resourceType: this.resourceType,
|
|
1216
|
+
resourceId: this.resourceId
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
var MorphProfileLimitError = class extends MorphAPIError {
|
|
1221
|
+
/** Current number of profiles */
|
|
1222
|
+
currentCount;
|
|
1223
|
+
/** Maximum allowed profiles for the plan */
|
|
1224
|
+
maxAllowed;
|
|
1225
|
+
constructor(message = "Profile limit exceeded for your plan.", options) {
|
|
1226
|
+
super(message, "profile_limit_exceeded", 403, { requestId: options?.requestId });
|
|
1227
|
+
this.name = "MorphProfileLimitError";
|
|
1228
|
+
this.currentCount = options?.currentCount;
|
|
1229
|
+
this.maxAllowed = options?.maxAllowed;
|
|
1230
|
+
}
|
|
1231
|
+
toJSON() {
|
|
1232
|
+
return {
|
|
1233
|
+
...super.toJSON(),
|
|
1234
|
+
currentCount: this.currentCount,
|
|
1235
|
+
maxAllowed: this.maxAllowed
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
function parseAPIError(statusCode, responseText, requestId) {
|
|
1240
|
+
let errorData = {};
|
|
1241
|
+
try {
|
|
1242
|
+
errorData = JSON.parse(responseText);
|
|
1243
|
+
} catch {
|
|
1244
|
+
}
|
|
1245
|
+
const message = errorData.detail || errorData.message || responseText || "Unknown error";
|
|
1246
|
+
const code = errorData.code;
|
|
1247
|
+
switch (statusCode) {
|
|
1248
|
+
case 401:
|
|
1249
|
+
return new MorphAuthenticationError(message);
|
|
1250
|
+
case 403:
|
|
1251
|
+
if (code === "profile_limit_exceeded" || message.toLowerCase().includes("limit")) {
|
|
1252
|
+
return new MorphProfileLimitError(message, { requestId });
|
|
1253
|
+
}
|
|
1254
|
+
return new MorphAPIError(message, "insufficient_permissions", statusCode, { requestId, rawResponse: responseText });
|
|
1255
|
+
case 404:
|
|
1256
|
+
if (message.toLowerCase().includes("profile")) {
|
|
1257
|
+
return new MorphNotFoundError("Profile", void 0);
|
|
1258
|
+
}
|
|
1259
|
+
if (message.toLowerCase().includes("session")) {
|
|
1260
|
+
return new MorphNotFoundError("Session", void 0);
|
|
1261
|
+
}
|
|
1262
|
+
return new MorphAPIError(message, "resource_not_found", statusCode, { requestId, rawResponse: responseText });
|
|
1263
|
+
case 429:
|
|
1264
|
+
return new MorphRateLimitError(message, { requestId });
|
|
1265
|
+
case 422:
|
|
1266
|
+
return new MorphAPIError(message, "validation_error", statusCode, { requestId, rawResponse: responseText });
|
|
1267
|
+
case 500:
|
|
1268
|
+
case 502:
|
|
1269
|
+
case 503:
|
|
1270
|
+
case 504:
|
|
1271
|
+
return new MorphAPIError(message, "service_unavailable", statusCode, { requestId, rawResponse: responseText });
|
|
1272
|
+
default:
|
|
1273
|
+
return new MorphAPIError(message, "network_error", statusCode, { requestId, rawResponse: responseText });
|
|
1094
1274
|
}
|
|
1095
|
-
const apiProfile = await response.json();
|
|
1096
|
-
return transformProfile(apiProfile);
|
|
1097
1275
|
}
|
|
1098
|
-
|
|
1276
|
+
|
|
1277
|
+
// tools/browser/profiles/types.ts
|
|
1278
|
+
function transformProfile(api) {
|
|
1099
1279
|
return {
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1280
|
+
id: api.id,
|
|
1281
|
+
name: api.name,
|
|
1282
|
+
repoId: api.repo_id,
|
|
1283
|
+
cookieDomains: api.cookie_domains,
|
|
1284
|
+
lastUsedAt: api.last_used_at,
|
|
1285
|
+
createdAt: api.created_at,
|
|
1286
|
+
updatedAt: api.updated_at
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
function transformCreateInput(input) {
|
|
1290
|
+
return {
|
|
1291
|
+
name: input.name,
|
|
1292
|
+
repo_id: input.repoId
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
function transformSession(api) {
|
|
1296
|
+
return {
|
|
1297
|
+
sessionId: api.session_id,
|
|
1298
|
+
debugUrl: api.debug_url || ""
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
function transformSaveInput(input) {
|
|
1302
|
+
return {
|
|
1303
|
+
session_id: input.sessionId,
|
|
1304
|
+
profile_id: input.profileId
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
function transformStateResponse(api) {
|
|
1308
|
+
return {
|
|
1309
|
+
profileId: api.profile_id,
|
|
1310
|
+
stateUrl: api.state_url,
|
|
1311
|
+
expiresIn: api.expires_in
|
|
1103
1312
|
};
|
|
1104
1313
|
}
|
|
1105
1314
|
|
|
1106
|
-
// tools/browser/core.ts
|
|
1107
|
-
var
|
|
1108
|
-
|
|
1109
|
-
timeout: 1e6,
|
|
1110
|
-
// 10 minutes for complex tasks
|
|
1111
|
-
debug: false
|
|
1112
|
-
};
|
|
1113
|
-
var BrowserClient = class {
|
|
1315
|
+
// tools/browser/profiles/core.ts
|
|
1316
|
+
var DEFAULT_API_URL2 = process.env.MORPH_ENVIRONMENT === "DEV" ? "http://localhost:8000" : "https://browser.morphllm.com";
|
|
1317
|
+
var ProfilesClient = class {
|
|
1114
1318
|
config;
|
|
1319
|
+
constructor(config) {
|
|
1320
|
+
this.config = config;
|
|
1321
|
+
}
|
|
1115
1322
|
/**
|
|
1116
|
-
|
|
1323
|
+
* Create a new browser profile and immediately start a live session.
|
|
1324
|
+
*
|
|
1325
|
+
* @param input - Profile creation parameters
|
|
1326
|
+
* @returns Profile setup handle with live URL + save()
|
|
1327
|
+
* @throws {MorphValidationError} If input validation fails
|
|
1328
|
+
* @throws {MorphProfileLimitError} If profile limit is exceeded
|
|
1329
|
+
* @throws {MorphAuthenticationError} If API key is missing or invalid
|
|
1330
|
+
*
|
|
1331
|
+
* @example
|
|
1332
|
+
* ```typescript
|
|
1333
|
+
* const setup = await morph.browser.profiles.createProfile({
|
|
1334
|
+
* name: 'LinkedIn Production',
|
|
1335
|
+
* repoId: 'owner/repo'
|
|
1336
|
+
* });
|
|
1337
|
+
* console.log(setup.session.debugUrl);
|
|
1338
|
+
* await setup.save();
|
|
1339
|
+
* ```
|
|
1340
|
+
*/
|
|
1341
|
+
async createProfile(input) {
|
|
1342
|
+
return createProfile(input, this.config);
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* List all profiles for the authenticated user.
|
|
1346
|
+
*
|
|
1347
|
+
* @param repoId - Optional repository ID to filter by
|
|
1348
|
+
* @returns Array of profiles
|
|
1349
|
+
*
|
|
1350
|
+
* @example
|
|
1351
|
+
* ```typescript
|
|
1352
|
+
* // List all profiles
|
|
1353
|
+
* const allProfiles = await morph.browser.profiles.listProfiles();
|
|
1354
|
+
*
|
|
1355
|
+
* // List profiles for a specific repo
|
|
1356
|
+
* const repoProfiles = await morph.browser.profiles.listProfiles('owner/repo');
|
|
1357
|
+
* ```
|
|
1117
1358
|
*/
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
this.config = {
|
|
1121
|
-
...DEFAULT_CONFIG2,
|
|
1122
|
-
...config
|
|
1123
|
-
};
|
|
1124
|
-
this.profiles = new ProfilesClient(this.config);
|
|
1359
|
+
async listProfiles(repoId) {
|
|
1360
|
+
return listProfiles(this.config, repoId);
|
|
1125
1361
|
}
|
|
1126
1362
|
/**
|
|
1127
|
-
*
|
|
1363
|
+
* Get a profile by ID with convenience methods.
|
|
1364
|
+
*
|
|
1365
|
+
* @param id - Profile ID
|
|
1366
|
+
* @returns Profile with attached methods
|
|
1367
|
+
* @throws {MorphNotFoundError} If profile is not found
|
|
1368
|
+
*
|
|
1369
|
+
* @example
|
|
1370
|
+
* ```typescript
|
|
1371
|
+
* const profile = await morph.browser.profiles.getProfile('profile-id');
|
|
1372
|
+
* const state = await profile.getState();
|
|
1373
|
+
* await profile.delete();
|
|
1374
|
+
* ```
|
|
1128
1375
|
*/
|
|
1129
|
-
async
|
|
1130
|
-
return
|
|
1131
|
-
}
|
|
1132
|
-
async createTask(input) {
|
|
1133
|
-
const apiUrl = this.config.apiUrl || DEFAULT_CONFIG2.apiUrl;
|
|
1134
|
-
const debug = this.config.debug || false;
|
|
1135
|
-
const hasTask = typeof input.task === "string" && input.task.trim().length > 0;
|
|
1136
|
-
const hasDiff = typeof input.diff === "string" && input.diff.trim().length > 0;
|
|
1137
|
-
if (!hasTask && !hasDiff) {
|
|
1138
|
-
throw new Error('Browser task requires either "task" (natural language) or "diff" (PR-review planning)');
|
|
1139
|
-
}
|
|
1140
|
-
if (debug) {
|
|
1141
|
-
const preview = (input.task ?? "").slice(0, 60);
|
|
1142
|
-
console.log(`[Browser] createTask: "${preview}..." url=${input.url || "none"}`);
|
|
1143
|
-
console.log(`[Browser] Calling async endpoint: ${apiUrl}/browser-task/async`);
|
|
1144
|
-
}
|
|
1145
|
-
const headers = { "Content-Type": "application/json" };
|
|
1146
|
-
if (this.config.apiKey) headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
1147
|
-
const response = await fetch(`${apiUrl}/browser-task/async`, {
|
|
1148
|
-
method: "POST",
|
|
1149
|
-
headers,
|
|
1150
|
-
body: JSON.stringify({
|
|
1151
|
-
task: input.task,
|
|
1152
|
-
diff: input.diff,
|
|
1153
|
-
url: input.url,
|
|
1154
|
-
max_steps: input.maxSteps ?? 10,
|
|
1155
|
-
model: input.model ?? "morph-computer-use-v0",
|
|
1156
|
-
viewport_width: input.viewportWidth ?? 1280,
|
|
1157
|
-
viewport_height: input.viewportHeight ?? 720,
|
|
1158
|
-
external_id: input.externalId,
|
|
1159
|
-
repo_id: input.repoId,
|
|
1160
|
-
repo_full_name: input.repoFullName,
|
|
1161
|
-
commit_id: input.commitId,
|
|
1162
|
-
record_video: input.recordVideo ?? false,
|
|
1163
|
-
video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
|
|
1164
|
-
video_height: input.videoHeight ?? input.viewportHeight ?? 720,
|
|
1165
|
-
allow_resizing: input.allowResizing ?? false,
|
|
1166
|
-
structured_output: "schema" in input ? stringifyStructuredOutput(input.schema) : void 0,
|
|
1167
|
-
auth: input.auth,
|
|
1168
|
-
profile_id: input.profileId,
|
|
1169
|
-
force_query_params: input.forceQueryParams
|
|
1170
|
-
})
|
|
1171
|
-
});
|
|
1172
|
-
if (!response.ok) {
|
|
1173
|
-
const errorText = await response.text().catch(() => response.statusText);
|
|
1174
|
-
if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);
|
|
1175
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
1176
|
-
}
|
|
1177
|
-
const result = mapTaskResult(await response.json());
|
|
1178
|
-
if (debug) {
|
|
1179
|
-
const debugUrl = result.debugUrl;
|
|
1180
|
-
console.log(`[Browser] \u2705 Task created: recordingId=${result.recordingId ?? "none"} debugUrl=${debugUrl ? "available" : "none"}`);
|
|
1181
|
-
}
|
|
1182
|
-
if ("schema" in input) {
|
|
1183
|
-
return wrapTaskResponseWithSchema(result, this.config, input.schema);
|
|
1184
|
-
} else {
|
|
1185
|
-
return wrapTaskResponse(result, this.config);
|
|
1186
|
-
}
|
|
1376
|
+
async getProfile(id) {
|
|
1377
|
+
return getProfile(id, this.config);
|
|
1187
1378
|
}
|
|
1188
1379
|
/**
|
|
1189
|
-
*
|
|
1380
|
+
* Update a profile by opening a live session (no rename).
|
|
1381
|
+
*
|
|
1382
|
+
* @param id - Profile ID
|
|
1383
|
+
* @returns Profile setup handle with live URL + save()
|
|
1190
1384
|
*/
|
|
1191
|
-
async
|
|
1192
|
-
return
|
|
1385
|
+
async updateProfile(id) {
|
|
1386
|
+
return updateProfile(id, this.config);
|
|
1193
1387
|
}
|
|
1194
1388
|
/**
|
|
1195
|
-
*
|
|
1389
|
+
* Delete a profile.
|
|
1390
|
+
*
|
|
1391
|
+
* @param id - Profile ID
|
|
1392
|
+
* @throws {MorphNotFoundError} If profile is not found
|
|
1196
1393
|
*/
|
|
1197
|
-
async
|
|
1198
|
-
return
|
|
1394
|
+
async deleteProfile(id) {
|
|
1395
|
+
return deleteProfile(id, this.config);
|
|
1199
1396
|
}
|
|
1200
1397
|
/**
|
|
1201
|
-
*
|
|
1398
|
+
* Start a browser session for profile setup.
|
|
1399
|
+
*
|
|
1400
|
+
* Returns a live URL where the user can sign into accounts.
|
|
1401
|
+
* After signing in, call `saveSession` to persist the state.
|
|
1402
|
+
*
|
|
1403
|
+
* @param input - Optional session parameters
|
|
1404
|
+
* @returns Session with debug URL
|
|
1405
|
+
*
|
|
1406
|
+
* @example
|
|
1407
|
+
* ```typescript
|
|
1408
|
+
* const session = await morph.browser.profiles.startSession();
|
|
1409
|
+
* console.log('Sign in at:', session.debugUrl);
|
|
1410
|
+
* // Open debugUrl in browser, user signs in...
|
|
1411
|
+
* await morph.browser.profiles.saveSession(session.sessionId, profile.id);
|
|
1412
|
+
* ```
|
|
1202
1413
|
*/
|
|
1203
|
-
async
|
|
1204
|
-
return
|
|
1414
|
+
async startSession(input) {
|
|
1415
|
+
return startProfileSession(this.config, input);
|
|
1205
1416
|
}
|
|
1206
1417
|
/**
|
|
1207
|
-
*
|
|
1418
|
+
* Save browser state from a session to a profile.
|
|
1419
|
+
*
|
|
1420
|
+
* Call this after the user is done signing into accounts.
|
|
1421
|
+
* Extracts cookies, localStorage, and sessionStorage.
|
|
1422
|
+
*
|
|
1423
|
+
* @param sessionId - Browser session ID from startSession
|
|
1424
|
+
* @param profileId - Profile ID to save state to
|
|
1425
|
+
* @returns Updated profile with cookie domains
|
|
1208
1426
|
*/
|
|
1209
|
-
async
|
|
1210
|
-
return
|
|
1427
|
+
async saveSession(sessionId, profileId) {
|
|
1428
|
+
return saveProfileSession({ sessionId, profileId }, this.config);
|
|
1211
1429
|
}
|
|
1212
1430
|
/**
|
|
1213
|
-
*
|
|
1431
|
+
* List available repo IDs (discovery).
|
|
1432
|
+
*
|
|
1433
|
+
* @returns Repo summaries with profile counts
|
|
1214
1434
|
*/
|
|
1215
|
-
async
|
|
1216
|
-
return
|
|
1435
|
+
async listRepos() {
|
|
1436
|
+
return listRepos(this.config);
|
|
1217
1437
|
}
|
|
1218
1438
|
/**
|
|
1219
|
-
*
|
|
1439
|
+
* Get the presigned URL for a profile's state.
|
|
1440
|
+
*
|
|
1441
|
+
* Use this to download the raw state JSON for debugging
|
|
1442
|
+
* or to restore state manually.
|
|
1443
|
+
*
|
|
1444
|
+
* @param profileId - Profile ID
|
|
1445
|
+
* @returns State URL with expiry information
|
|
1220
1446
|
*/
|
|
1221
|
-
async
|
|
1222
|
-
return
|
|
1447
|
+
async getProfileState(profileId) {
|
|
1448
|
+
return getProfileState(profileId, this.config);
|
|
1223
1449
|
}
|
|
1224
1450
|
};
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
const debug = config.debug || false;
|
|
1229
|
-
if (!input.task || input.task.trim().length === 0) {
|
|
1230
|
-
return {
|
|
1231
|
-
success: false,
|
|
1232
|
-
error: 'Task description is required. Example: "Go to example.com and click the login button"'
|
|
1233
|
-
};
|
|
1451
|
+
function validateCreateInput(input) {
|
|
1452
|
+
if (!input.name || typeof input.name !== "string") {
|
|
1453
|
+
throw new MorphValidationError("name is required", "name");
|
|
1234
1454
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
error: "maxSteps must be between 1 and 50. Use more steps for complex multi-page flows."
|
|
1239
|
-
};
|
|
1455
|
+
const trimmedName = input.name.trim();
|
|
1456
|
+
if (trimmedName.length === 0) {
|
|
1457
|
+
throw new MorphValidationError("name cannot be empty", "name");
|
|
1240
1458
|
}
|
|
1241
|
-
if (
|
|
1242
|
-
|
|
1243
|
-
console.log(`[Browser] Recording: ${input.recordVideo ? "yes" : "no"} | Calling ${apiUrl}/browser-task`);
|
|
1459
|
+
if (trimmedName.length > 100) {
|
|
1460
|
+
throw new MorphValidationError("name must be 100 characters or less", "name");
|
|
1244
1461
|
}
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
`${apiUrl}/browser-task`,
|
|
1251
|
-
{
|
|
1252
|
-
method: "POST",
|
|
1253
|
-
headers,
|
|
1254
|
-
body: JSON.stringify({
|
|
1255
|
-
task: input.task,
|
|
1256
|
-
url: input.url,
|
|
1257
|
-
max_steps: input.maxSteps ?? 10,
|
|
1258
|
-
model: input.model ?? "morph-computer-use-v0",
|
|
1259
|
-
viewport_width: input.viewportWidth ?? 1280,
|
|
1260
|
-
viewport_height: input.viewportHeight ?? 720,
|
|
1261
|
-
external_id: input.externalId,
|
|
1262
|
-
repo_id: input.repoId,
|
|
1263
|
-
commit_id: input.commitId,
|
|
1264
|
-
record_video: input.recordVideo ?? false,
|
|
1265
|
-
video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
|
|
1266
|
-
video_height: input.videoHeight ?? input.viewportHeight ?? 720,
|
|
1267
|
-
allow_resizing: input.allowResizing ?? false,
|
|
1268
|
-
structured_output: input.structuredOutput,
|
|
1269
|
-
auth: input.auth,
|
|
1270
|
-
profile_id: input.profileId,
|
|
1271
|
-
force_query_params: input.forceQueryParams
|
|
1272
|
-
})
|
|
1273
|
-
},
|
|
1274
|
-
config.retryConfig
|
|
1275
|
-
);
|
|
1276
|
-
const response = await withTimeout(
|
|
1277
|
-
fetchPromise,
|
|
1278
|
-
timeout,
|
|
1279
|
-
`Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing maxSteps.`
|
|
1280
|
-
);
|
|
1281
|
-
if (!response.ok) {
|
|
1282
|
-
const errorText = await response.text().catch(() => response.statusText);
|
|
1283
|
-
if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);
|
|
1284
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
1285
|
-
}
|
|
1286
|
-
const result = mapTaskResult(await response.json());
|
|
1287
|
-
const elapsed = Date.now() - startTime;
|
|
1288
|
-
if (debug) {
|
|
1289
|
-
console.log(`[Browser] \u2705 ${result.success ? "Success" : "Failed"} in ${elapsed}ms | steps=${result.stepsTaken ?? 0} recordingId=${result.recordingId ?? "none"}`);
|
|
1290
|
-
}
|
|
1291
|
-
return result;
|
|
1292
|
-
} catch (error) {
|
|
1293
|
-
if (error instanceof Error) {
|
|
1294
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
1295
|
-
return {
|
|
1296
|
-
success: false,
|
|
1297
|
-
error: `Cannot connect to browser worker at ${apiUrl}. Ensure the service is running and accessible. For local dev, set MORPH_ENVIRONMENT=DEV.`
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
return {
|
|
1301
|
-
success: false,
|
|
1302
|
-
error: error.message
|
|
1303
|
-
};
|
|
1304
|
-
}
|
|
1305
|
-
return {
|
|
1306
|
-
success: false,
|
|
1307
|
-
error: String(error)
|
|
1308
|
-
};
|
|
1462
|
+
if (!input.repoId || typeof input.repoId !== "string") {
|
|
1463
|
+
throw new MorphValidationError("repoId is required", "repoId");
|
|
1464
|
+
}
|
|
1465
|
+
if (input.repoId.trim().length === 0) {
|
|
1466
|
+
throw new MorphValidationError("repoId cannot be empty", "repoId");
|
|
1309
1467
|
}
|
|
1310
1468
|
}
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
if (!config.apiKey) {
|
|
1315
|
-
throw new Error("API key required for getRecording");
|
|
1469
|
+
function validateId(id, fieldName) {
|
|
1470
|
+
if (!id || typeof id !== "string") {
|
|
1471
|
+
throw new MorphValidationError(`${fieldName} is required`, fieldName);
|
|
1316
1472
|
}
|
|
1317
|
-
if (
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1473
|
+
if (id.trim().length === 0) {
|
|
1474
|
+
throw new MorphValidationError(`${fieldName} cannot be empty`, fieldName);
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
async function createProfile(input, config = {}) {
|
|
1478
|
+
validateCreateInput(input);
|
|
1479
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL2;
|
|
1480
|
+
const headers = buildHeaders(config);
|
|
1481
|
+
const response = await fetchWithRetry(
|
|
1482
|
+
`${apiUrl}/profiles`,
|
|
1483
|
+
{
|
|
1484
|
+
method: "POST",
|
|
1485
|
+
headers,
|
|
1486
|
+
body: JSON.stringify(transformCreateInput(input))
|
|
1487
|
+
},
|
|
1488
|
+
config.retryConfig
|
|
1489
|
+
);
|
|
1322
1490
|
if (!response.ok) {
|
|
1323
1491
|
const errorText = await response.text().catch(() => response.statusText);
|
|
1324
|
-
|
|
1325
|
-
throw
|
|
1492
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1493
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1326
1494
|
}
|
|
1327
|
-
const
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
getWebp: (options) => getWebp(recordingId, config, options),
|
|
1332
|
-
getErrors: () => getErrors(recordingId, config)
|
|
1333
|
-
};
|
|
1495
|
+
const apiProfile = await response.json();
|
|
1496
|
+
const profile = transformProfile(apiProfile);
|
|
1497
|
+
const session = await startProfileSession(config, { profileId: profile.id });
|
|
1498
|
+
return buildProfileSetup(profile, session, config);
|
|
1334
1499
|
}
|
|
1335
|
-
async function
|
|
1336
|
-
const
|
|
1337
|
-
const
|
|
1338
|
-
const
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1500
|
+
async function listProfiles(config = {}, repoId) {
|
|
1501
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL2;
|
|
1502
|
+
const headers = buildHeaders(config);
|
|
1503
|
+
const url = repoId ? `${apiUrl}/profiles?repo_id=${encodeURIComponent(repoId)}` : `${apiUrl}/profiles`;
|
|
1504
|
+
const response = await fetchWithRetry(
|
|
1505
|
+
url,
|
|
1506
|
+
{ method: "GET", headers },
|
|
1507
|
+
config.retryConfig
|
|
1508
|
+
);
|
|
1509
|
+
if (!response.ok) {
|
|
1510
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1511
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1512
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1345
1513
|
}
|
|
1346
|
-
|
|
1514
|
+
const data = await response.json();
|
|
1515
|
+
return data.profiles.map(transformProfile);
|
|
1347
1516
|
}
|
|
1348
|
-
async function
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
} catch (error) {
|
|
1362
|
-
const errorRecording = {
|
|
1363
|
-
id: taskResult.recordingId,
|
|
1364
|
-
status: "ERROR",
|
|
1365
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1366
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1367
|
-
getWebp: (options) => getWebp(taskResult.recordingId, config, options),
|
|
1368
|
-
getErrors: () => getErrors(taskResult.recordingId, config)
|
|
1369
|
-
};
|
|
1370
|
-
return {
|
|
1371
|
-
...taskResult,
|
|
1372
|
-
recording: errorRecording
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1517
|
+
async function getProfile(id, config = {}) {
|
|
1518
|
+
validateId(id, "id");
|
|
1519
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL2;
|
|
1520
|
+
const headers = buildHeaders(config);
|
|
1521
|
+
const response = await fetchWithRetry(
|
|
1522
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
1523
|
+
{ method: "GET", headers },
|
|
1524
|
+
config.retryConfig
|
|
1525
|
+
);
|
|
1526
|
+
if (!response.ok) {
|
|
1527
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1528
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1529
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1375
1530
|
}
|
|
1376
|
-
|
|
1531
|
+
const apiProfile = await response.json();
|
|
1532
|
+
const profile = transformProfile(apiProfile);
|
|
1533
|
+
return {
|
|
1534
|
+
...profile,
|
|
1535
|
+
getState: () => getProfileState(id, config),
|
|
1536
|
+
delete: () => deleteProfile(id, config)
|
|
1537
|
+
};
|
|
1377
1538
|
}
|
|
1378
|
-
async function
|
|
1379
|
-
|
|
1380
|
-
const
|
|
1539
|
+
async function updateProfile(id, config = {}) {
|
|
1540
|
+
validateId(id, "id");
|
|
1541
|
+
const profile = await fetchProfile(id, config);
|
|
1542
|
+
const session = await startProfileSession(config, { profileId: profile.id });
|
|
1543
|
+
return buildProfileSetup(profile, session, config);
|
|
1544
|
+
}
|
|
1545
|
+
async function deleteProfile(id, config = {}) {
|
|
1546
|
+
validateId(id, "id");
|
|
1547
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL2;
|
|
1548
|
+
const headers = buildHeaders(config);
|
|
1549
|
+
const response = await fetchWithRetry(
|
|
1550
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
1551
|
+
{ method: "DELETE", headers },
|
|
1552
|
+
config.retryConfig
|
|
1553
|
+
);
|
|
1554
|
+
if (!response.ok) {
|
|
1555
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1556
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1557
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
async function startProfileSession(config = {}, input) {
|
|
1381
1561
|
if (!config.apiKey) {
|
|
1382
|
-
throw new
|
|
1562
|
+
throw new MorphAuthenticationError();
|
|
1383
1563
|
}
|
|
1384
|
-
|
|
1385
|
-
const
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1564
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL2;
|
|
1565
|
+
const headers = buildHeaders(config);
|
|
1566
|
+
const body = input?.profileId ? { profile_id: input.profileId } : {};
|
|
1567
|
+
const response = await fetchWithRetry(
|
|
1568
|
+
`${apiUrl}/profiles/session/start`,
|
|
1569
|
+
{
|
|
1570
|
+
method: "POST",
|
|
1571
|
+
headers,
|
|
1572
|
+
body: JSON.stringify(body)
|
|
1573
|
+
},
|
|
1574
|
+
config.retryConfig
|
|
1575
|
+
);
|
|
1389
1576
|
if (!response.ok) {
|
|
1390
1577
|
const errorText = await response.text().catch(() => response.statusText);
|
|
1391
|
-
|
|
1392
|
-
throw
|
|
1578
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1579
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1393
1580
|
}
|
|
1394
|
-
const
|
|
1395
|
-
|
|
1396
|
-
return errors;
|
|
1581
|
+
const apiSession = await response.json();
|
|
1582
|
+
return transformSession(apiSession);
|
|
1397
1583
|
}
|
|
1398
|
-
function
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1584
|
+
async function saveProfileSession(input, config = {}) {
|
|
1585
|
+
validateId(input.sessionId, "sessionId");
|
|
1586
|
+
validateId(input.profileId, "profileId");
|
|
1587
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL2;
|
|
1588
|
+
const headers = buildHeaders(config);
|
|
1589
|
+
const response = await fetchWithRetry(
|
|
1590
|
+
`${apiUrl}/profiles/session/save`,
|
|
1591
|
+
{
|
|
1592
|
+
method: "POST",
|
|
1593
|
+
headers,
|
|
1594
|
+
body: JSON.stringify(transformSaveInput(input))
|
|
1595
|
+
},
|
|
1596
|
+
config.retryConfig
|
|
1597
|
+
);
|
|
1598
|
+
if (!response.ok) {
|
|
1599
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1600
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1601
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1411
1602
|
}
|
|
1603
|
+
const apiProfile = await response.json();
|
|
1604
|
+
return transformProfile(apiProfile);
|
|
1412
1605
|
}
|
|
1413
|
-
function
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
throw error;
|
|
1606
|
+
async function listRepos(config = {}) {
|
|
1607
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL2;
|
|
1608
|
+
const headers = buildHeaders(config);
|
|
1609
|
+
const response = await fetchWithRetry(
|
|
1610
|
+
`${apiUrl}/repos`,
|
|
1611
|
+
{ method: "GET", headers },
|
|
1612
|
+
config.retryConfig
|
|
1613
|
+
);
|
|
1614
|
+
if (!response.ok) {
|
|
1615
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1616
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1617
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1426
1618
|
}
|
|
1619
|
+
const data = await response.json();
|
|
1620
|
+
const repos = Array.isArray(data?.repos) ? data.repos : [];
|
|
1621
|
+
return repos.map((repo) => ({
|
|
1622
|
+
repoId: repo.repo_id,
|
|
1623
|
+
repoFullName: repo.repo_full_name,
|
|
1624
|
+
profileCount: repo.profile_count ?? 0
|
|
1625
|
+
}));
|
|
1427
1626
|
}
|
|
1428
|
-
function
|
|
1429
|
-
|
|
1430
|
-
|
|
1627
|
+
async function getProfileState(profileId, config = {}) {
|
|
1628
|
+
validateId(profileId, "profileId");
|
|
1629
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL2;
|
|
1630
|
+
const headers = buildHeaders(config);
|
|
1631
|
+
const response = await fetchWithRetry(
|
|
1632
|
+
`${apiUrl}/profiles/${encodeURIComponent(profileId)}/state`,
|
|
1633
|
+
{ method: "GET", headers },
|
|
1634
|
+
config.retryConfig
|
|
1635
|
+
);
|
|
1636
|
+
if (!response.ok) {
|
|
1637
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1638
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1639
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1431
1640
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
result: api.result,
|
|
1435
|
-
error: api.error,
|
|
1436
|
-
stepsTaken: api.steps_taken,
|
|
1437
|
-
executionTimeMs: api.execution_time_ms,
|
|
1438
|
-
urls: api.urls,
|
|
1439
|
-
actionNames: api.action_names,
|
|
1440
|
-
errors: api.errors,
|
|
1441
|
-
modelActions: api.model_actions,
|
|
1442
|
-
isDone: api.is_done,
|
|
1443
|
-
actionHistory: api.action_history,
|
|
1444
|
-
actionResults: api.action_results,
|
|
1445
|
-
hasErrors: api.has_errors,
|
|
1446
|
-
numberOfSteps: api.number_of_steps,
|
|
1447
|
-
judgement: api.judgement,
|
|
1448
|
-
isValidated: api.is_validated,
|
|
1449
|
-
replayId: api.replay_id,
|
|
1450
|
-
replayUrl: api.replay_url,
|
|
1451
|
-
recordingId: api.recording_id,
|
|
1452
|
-
recordingStatus: api.recording_status,
|
|
1453
|
-
taskId: api.task_id,
|
|
1454
|
-
status: api.status,
|
|
1455
|
-
output: api.output,
|
|
1456
|
-
debugUrl: api.debug_url
|
|
1457
|
-
};
|
|
1641
|
+
const apiState = await response.json();
|
|
1642
|
+
return transformStateResponse(apiState);
|
|
1458
1643
|
}
|
|
1459
|
-
function
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
consoleUrl: api.console_url,
|
|
1466
|
-
videoUrl: api.video_url,
|
|
1467
|
-
result: api.result,
|
|
1468
|
-
totalEvents: api.total_events,
|
|
1469
|
-
fileSize: api.file_size,
|
|
1470
|
-
duration: api.duration,
|
|
1471
|
-
error: api.error,
|
|
1472
|
-
createdAt: api.created_at
|
|
1473
|
-
};
|
|
1644
|
+
function buildHeaders(config) {
|
|
1645
|
+
const headers = { "Content-Type": "application/json" };
|
|
1646
|
+
if (config.apiKey) {
|
|
1647
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1648
|
+
}
|
|
1649
|
+
return headers;
|
|
1474
1650
|
}
|
|
1475
|
-
function
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1651
|
+
async function fetchProfile(id, config) {
|
|
1652
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL2;
|
|
1653
|
+
const headers = buildHeaders(config);
|
|
1654
|
+
const response = await fetchWithRetry(
|
|
1655
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
1656
|
+
{ method: "GET", headers },
|
|
1657
|
+
config.retryConfig
|
|
1658
|
+
);
|
|
1659
|
+
if (!response.ok) {
|
|
1660
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1661
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1662
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1663
|
+
}
|
|
1664
|
+
const apiProfile = await response.json();
|
|
1665
|
+
return transformProfile(apiProfile);
|
|
1485
1666
|
}
|
|
1486
|
-
function
|
|
1667
|
+
function buildProfileSetup(profile, session, config) {
|
|
1487
1668
|
return {
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1669
|
+
profile,
|
|
1670
|
+
session,
|
|
1671
|
+
save: () => saveProfileSession({ sessionId: session.sessionId, profileId: profile.id }, config)
|
|
1491
1672
|
};
|
|
1492
1673
|
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1674
|
+
|
|
1675
|
+
// tools/browser/core.ts
|
|
1676
|
+
var DEFAULT_CONFIG2 = {
|
|
1677
|
+
apiUrl: process.env.MORPH_ENVIRONMENT === "DEV" ? "http://localhost:8000" : "https://browser.morphllm.com",
|
|
1678
|
+
timeout: 1e6,
|
|
1679
|
+
// 10 minutes for complex tasks
|
|
1680
|
+
debug: false
|
|
1681
|
+
};
|
|
1682
|
+
var BrowserClient = class {
|
|
1683
|
+
config;
|
|
1684
|
+
/**
|
|
1685
|
+
* Profile management - create and manage browser profiles for storing login state.
|
|
1686
|
+
*/
|
|
1687
|
+
profiles;
|
|
1688
|
+
constructor(config = {}) {
|
|
1689
|
+
this.config = {
|
|
1690
|
+
...DEFAULT_CONFIG2,
|
|
1691
|
+
...config
|
|
1692
|
+
};
|
|
1693
|
+
this.profiles = new ProfilesClient(this.config);
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Execute a browser automation task
|
|
1697
|
+
*/
|
|
1698
|
+
async execute(input) {
|
|
1699
|
+
return executeBrowserTask(input, this.config);
|
|
1700
|
+
}
|
|
1701
|
+
async createTask(input) {
|
|
1702
|
+
const apiUrl = this.config.apiUrl || DEFAULT_CONFIG2.apiUrl;
|
|
1703
|
+
const debug = this.config.debug || false;
|
|
1704
|
+
const hasTask = typeof input.task === "string" && input.task.trim().length > 0;
|
|
1705
|
+
const hasDiff = typeof input.diff === "string" && input.diff.trim().length > 0;
|
|
1706
|
+
if (!hasTask && !hasDiff) {
|
|
1707
|
+
throw new Error('Browser task requires either "task" (natural language) or "diff" (PR-review planning)');
|
|
1708
|
+
}
|
|
1709
|
+
if (debug) {
|
|
1710
|
+
const preview = (input.task ?? "").slice(0, 60);
|
|
1711
|
+
console.log(`[Browser] createTask: "${preview}..." url=${input.url || "none"}`);
|
|
1712
|
+
console.log(`[Browser] Calling async endpoint: ${apiUrl}/browser-task/async`);
|
|
1713
|
+
}
|
|
1714
|
+
const headers = { "Content-Type": "application/json" };
|
|
1715
|
+
if (this.config.apiKey) headers["Authorization"] = `Bearer ${this.config.apiKey}`;
|
|
1716
|
+
const response = await fetch(`${apiUrl}/browser-task/async`, {
|
|
1717
|
+
method: "POST",
|
|
1718
|
+
headers,
|
|
1719
|
+
body: JSON.stringify({
|
|
1720
|
+
task: input.task,
|
|
1721
|
+
diff: input.diff,
|
|
1722
|
+
url: input.url,
|
|
1723
|
+
max_steps: input.maxSteps ?? 10,
|
|
1724
|
+
model: input.model ?? "morph-computer-use-v0",
|
|
1725
|
+
viewport_width: input.viewportWidth ?? 1280,
|
|
1726
|
+
viewport_height: input.viewportHeight ?? 720,
|
|
1727
|
+
external_id: input.externalId,
|
|
1728
|
+
repo_id: input.repoId,
|
|
1729
|
+
repo_full_name: input.repoFullName,
|
|
1730
|
+
commit_id: input.commitId,
|
|
1731
|
+
record_video: input.recordVideo ?? false,
|
|
1732
|
+
video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
|
|
1733
|
+
video_height: input.videoHeight ?? input.viewportHeight ?? 720,
|
|
1734
|
+
allow_resizing: input.allowResizing ?? false,
|
|
1735
|
+
structured_output: "schema" in input ? stringifyStructuredOutput(input.schema) : void 0,
|
|
1736
|
+
auth: input.auth,
|
|
1737
|
+
profile_id: input.profileId,
|
|
1738
|
+
force_query_params: input.forceQueryParams
|
|
1739
|
+
})
|
|
1740
|
+
});
|
|
1741
|
+
if (!response.ok) {
|
|
1742
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1743
|
+
if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);
|
|
1744
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
1745
|
+
}
|
|
1746
|
+
const result = mapTaskResult(await response.json());
|
|
1747
|
+
if (debug) {
|
|
1748
|
+
const debugUrl = result.debugUrl;
|
|
1749
|
+
console.log(`[Browser] \u2705 Task created: recordingId=${result.recordingId ?? "none"} debugUrl=${debugUrl ? "available" : "none"}`);
|
|
1750
|
+
}
|
|
1751
|
+
if ("schema" in input) {
|
|
1752
|
+
return wrapTaskResponseWithSchema(result, this.config, input.schema);
|
|
1753
|
+
} else {
|
|
1754
|
+
return wrapTaskResponse(result, this.config);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Execute task with recording and wait for video to be ready
|
|
1759
|
+
*/
|
|
1760
|
+
async executeWithRecording(input) {
|
|
1761
|
+
return executeWithRecording(input, this.config);
|
|
1762
|
+
}
|
|
1763
|
+
/**
|
|
1764
|
+
* Get recording status and URLs
|
|
1765
|
+
*/
|
|
1766
|
+
async getRecording(recordingId) {
|
|
1767
|
+
return getRecording(recordingId, this.config);
|
|
1768
|
+
}
|
|
1769
|
+
/**
|
|
1770
|
+
* Wait for recording to complete with automatic polling
|
|
1771
|
+
*/
|
|
1772
|
+
async waitForRecording(recordingId, options) {
|
|
1773
|
+
return waitForRecording(recordingId, this.config, options);
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Get errors from recording with screenshots
|
|
1777
|
+
*/
|
|
1778
|
+
async getErrors(recordingId) {
|
|
1779
|
+
return getErrors(recordingId, this.config);
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Get animated WebP preview of recording
|
|
1783
|
+
*/
|
|
1784
|
+
async getWebp(recordingId, options) {
|
|
1785
|
+
return getWebp(recordingId, this.config, options);
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Check if browser worker service is healthy
|
|
1789
|
+
*/
|
|
1790
|
+
async checkHealth() {
|
|
1791
|
+
return checkHealth(this.config);
|
|
1792
|
+
}
|
|
1793
|
+
};
|
|
1794
|
+
async function executeBrowserTask(input, config = {}) {
|
|
1795
|
+
const apiUrl = config.apiUrl || DEFAULT_CONFIG2.apiUrl;
|
|
1796
|
+
const timeout = config.timeout || DEFAULT_CONFIG2.timeout;
|
|
1797
|
+
const debug = config.debug || false;
|
|
1798
|
+
if (!input.task || input.task.trim().length === 0) {
|
|
1799
|
+
return {
|
|
1800
|
+
success: false,
|
|
1801
|
+
error: 'Task description is required. Example: "Go to example.com and click the login button"'
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
if (input.maxSteps !== void 0 && (input.maxSteps < 1 || input.maxSteps > 50)) {
|
|
1805
|
+
return {
|
|
1806
|
+
success: false,
|
|
1807
|
+
error: "maxSteps must be between 1 and 50. Use more steps for complex multi-page flows."
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
if (debug) {
|
|
1811
|
+
console.log(`[Browser] Task: "${input.task.slice(0, 60)}..." url=${input.url || "none"} maxSteps=${input.maxSteps ?? 10}`);
|
|
1812
|
+
console.log(`[Browser] Recording: ${input.recordVideo ? "yes" : "no"} | Calling ${apiUrl}/browser-task`);
|
|
1813
|
+
}
|
|
1814
|
+
const startTime = Date.now();
|
|
1815
|
+
try {
|
|
1816
|
+
const headers = { "Content-Type": "application/json" };
|
|
1817
|
+
if (config.apiKey) headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1818
|
+
const fetchPromise = fetchWithRetry(
|
|
1819
|
+
`${apiUrl}/browser-task`,
|
|
1820
|
+
{
|
|
1821
|
+
method: "POST",
|
|
1822
|
+
headers,
|
|
1823
|
+
body: JSON.stringify({
|
|
1824
|
+
task: input.task,
|
|
1825
|
+
url: input.url,
|
|
1826
|
+
max_steps: input.maxSteps ?? 10,
|
|
1827
|
+
model: input.model ?? "morph-computer-use-v0",
|
|
1828
|
+
viewport_width: input.viewportWidth ?? 1280,
|
|
1829
|
+
viewport_height: input.viewportHeight ?? 720,
|
|
1830
|
+
external_id: input.externalId,
|
|
1831
|
+
repo_id: input.repoId,
|
|
1832
|
+
commit_id: input.commitId,
|
|
1833
|
+
record_video: input.recordVideo ?? false,
|
|
1834
|
+
video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
|
|
1835
|
+
video_height: input.videoHeight ?? input.viewportHeight ?? 720,
|
|
1836
|
+
allow_resizing: input.allowResizing ?? false,
|
|
1837
|
+
structured_output: input.structuredOutput,
|
|
1838
|
+
auth: input.auth,
|
|
1839
|
+
profile_id: input.profileId,
|
|
1840
|
+
force_query_params: input.forceQueryParams
|
|
1841
|
+
})
|
|
1842
|
+
},
|
|
1843
|
+
config.retryConfig
|
|
1844
|
+
);
|
|
1845
|
+
const response = await withTimeout(
|
|
1846
|
+
fetchPromise,
|
|
1847
|
+
timeout,
|
|
1848
|
+
`Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing maxSteps.`
|
|
1849
|
+
);
|
|
1850
|
+
if (!response.ok) {
|
|
1851
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1852
|
+
if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);
|
|
1853
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
1854
|
+
}
|
|
1855
|
+
const result = mapTaskResult(await response.json());
|
|
1856
|
+
const elapsed = Date.now() - startTime;
|
|
1857
|
+
if (debug) {
|
|
1858
|
+
console.log(`[Browser] \u2705 ${result.success ? "Success" : "Failed"} in ${elapsed}ms | steps=${result.stepsTaken ?? 0} recordingId=${result.recordingId ?? "none"}`);
|
|
1859
|
+
}
|
|
1860
|
+
return result;
|
|
1861
|
+
} catch (error) {
|
|
1862
|
+
if (error instanceof Error) {
|
|
1863
|
+
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
1864
|
+
return {
|
|
1865
|
+
success: false,
|
|
1866
|
+
error: `Cannot connect to browser worker at ${apiUrl}. Ensure the service is running and accessible. For local dev, set MORPH_ENVIRONMENT=DEV.`
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
return {
|
|
1870
|
+
success: false,
|
|
1871
|
+
error: error.message
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
return {
|
|
1875
|
+
success: false,
|
|
1876
|
+
error: String(error)
|
|
1877
|
+
};
|
|
1878
|
+
}
|
|
1506
1879
|
}
|
|
1507
|
-
async function
|
|
1880
|
+
async function getRecording(recordingId, config = {}) {
|
|
1508
1881
|
const apiUrl = config.apiUrl || DEFAULT_CONFIG2.apiUrl;
|
|
1509
1882
|
const debug = config.debug || false;
|
|
1510
|
-
if (
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1883
|
+
if (!config.apiKey) {
|
|
1884
|
+
throw new Error("API key required for getRecording");
|
|
1885
|
+
}
|
|
1886
|
+
if (debug) console.log(`[Browser] getRecording: ${recordingId}`);
|
|
1887
|
+
const response = await fetch(`${apiUrl}/recordings/${recordingId}`, {
|
|
1514
1888
|
method: "GET",
|
|
1515
|
-
headers
|
|
1889
|
+
headers: { "Authorization": `Bearer ${config.apiKey}` }
|
|
1516
1890
|
});
|
|
1517
1891
|
if (!response.ok) {
|
|
1518
1892
|
const errorText = await response.text().catch(() => response.statusText);
|
|
1519
|
-
if (debug) console.error(`[Browser]
|
|
1893
|
+
if (debug) console.error(`[Browser] getRecording error: ${response.status} - ${errorText}`);
|
|
1520
1894
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
1521
1895
|
}
|
|
1522
|
-
const
|
|
1523
|
-
if (debug) console.log(`[Browser]
|
|
1524
|
-
return
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
return `${baseUrl}/tasks/${taskId}/live`;
|
|
1896
|
+
const data = mapRecordingStatus(await response.json());
|
|
1897
|
+
if (debug) console.log(`[Browser] Recording status: ${data.status}`);
|
|
1898
|
+
return {
|
|
1899
|
+
...data,
|
|
1900
|
+
getWebp: (options) => getWebp(recordingId, config, options),
|
|
1901
|
+
getErrors: () => getErrors(recordingId, config)
|
|
1902
|
+
};
|
|
1530
1903
|
}
|
|
1531
|
-
async function
|
|
1532
|
-
const
|
|
1533
|
-
const
|
|
1904
|
+
async function waitForRecording(recordingId, config = {}, options = {}) {
|
|
1905
|
+
const timeout = options.timeout ?? 6e4;
|
|
1906
|
+
const pollInterval = options.pollInterval ?? 2e3;
|
|
1534
1907
|
const startTime = Date.now();
|
|
1535
1908
|
while (Date.now() - startTime < timeout) {
|
|
1536
|
-
const status = await
|
|
1537
|
-
if (status.status === "
|
|
1909
|
+
const status = await getRecording(recordingId, config);
|
|
1910
|
+
if (status.status === "COMPLETED" || status.status === "ERROR") {
|
|
1911
|
+
return status;
|
|
1912
|
+
}
|
|
1913
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
1914
|
+
}
|
|
1915
|
+
throw new Error(`Recording timeout after ${timeout}ms - status still pending`);
|
|
1916
|
+
}
|
|
1917
|
+
async function executeWithRecording(input, config = {}) {
|
|
1918
|
+
const taskResult = await executeBrowserTask(input, config);
|
|
1919
|
+
if (taskResult.recordingId) {
|
|
1920
|
+
try {
|
|
1921
|
+
const recording = await waitForRecording(
|
|
1922
|
+
taskResult.recordingId,
|
|
1923
|
+
config,
|
|
1924
|
+
{ timeout: 6e4, pollInterval: 2e3 }
|
|
1925
|
+
);
|
|
1926
|
+
return {
|
|
1927
|
+
...taskResult,
|
|
1928
|
+
recording
|
|
1929
|
+
};
|
|
1930
|
+
} catch (error) {
|
|
1931
|
+
const errorRecording = {
|
|
1932
|
+
id: taskResult.recordingId,
|
|
1933
|
+
status: "ERROR",
|
|
1934
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1935
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1936
|
+
getWebp: (options) => getWebp(taskResult.recordingId, config, options),
|
|
1937
|
+
getErrors: () => getErrors(taskResult.recordingId, config)
|
|
1938
|
+
};
|
|
1939
|
+
return {
|
|
1940
|
+
...taskResult,
|
|
1941
|
+
recording: errorRecording
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
return taskResult;
|
|
1946
|
+
}
|
|
1947
|
+
async function getErrors(recordingId, config = {}) {
|
|
1948
|
+
const apiUrl = config.apiUrl || DEFAULT_CONFIG2.apiUrl;
|
|
1949
|
+
const debug = config.debug || false;
|
|
1950
|
+
if (!config.apiKey) {
|
|
1951
|
+
throw new Error("API key required for getErrors");
|
|
1952
|
+
}
|
|
1953
|
+
if (debug) console.log(`[Browser] getErrors: ${recordingId}`);
|
|
1954
|
+
const response = await fetch(`${apiUrl}/recordings/${recordingId}/errors`, {
|
|
1955
|
+
method: "GET",
|
|
1956
|
+
headers: { "Authorization": `Bearer ${config.apiKey}` }
|
|
1957
|
+
});
|
|
1958
|
+
if (!response.ok) {
|
|
1959
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1960
|
+
if (debug) console.error(`[Browser] getErrors error: ${response.status} - ${errorText}`);
|
|
1961
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
1962
|
+
}
|
|
1963
|
+
const errors = mapErrorsResponse(await response.json());
|
|
1964
|
+
if (debug) console.log(`[Browser] Found ${errors.totalErrors} errors`);
|
|
1965
|
+
return errors;
|
|
1966
|
+
}
|
|
1967
|
+
function stringifyStructuredOutput(schema) {
|
|
1968
|
+
try {
|
|
1969
|
+
return JSON.stringify({
|
|
1970
|
+
type: "object",
|
|
1971
|
+
description: "Zod schema definition (Zod v3)",
|
|
1972
|
+
zodDef: schema._def
|
|
1973
|
+
});
|
|
1974
|
+
} catch (error) {
|
|
1975
|
+
console.warn("[Browser] Failed to serialize Zod schema:", error);
|
|
1976
|
+
return JSON.stringify({
|
|
1977
|
+
type: "object",
|
|
1978
|
+
description: "Schema serialization failed"
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
function parseStructuredTaskOutput(result, schema) {
|
|
1983
|
+
if (!result.output) {
|
|
1984
|
+
return { ...result, parsed: null };
|
|
1985
|
+
}
|
|
1986
|
+
try {
|
|
1987
|
+
const parsed = JSON.parse(result.output);
|
|
1988
|
+
const validated = schema.parse(parsed);
|
|
1989
|
+
return { ...result, parsed: validated };
|
|
1990
|
+
} catch (error) {
|
|
1991
|
+
if (error instanceof SyntaxError) {
|
|
1992
|
+
return { ...result, parsed: null };
|
|
1993
|
+
}
|
|
1994
|
+
throw error;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
function mapTaskResult(api) {
|
|
1998
|
+
if (!api || typeof api !== "object") {
|
|
1999
|
+
return api;
|
|
2000
|
+
}
|
|
2001
|
+
return {
|
|
2002
|
+
success: api.success,
|
|
2003
|
+
result: api.result,
|
|
2004
|
+
error: api.error,
|
|
2005
|
+
stepsTaken: api.steps_taken,
|
|
2006
|
+
executionTimeMs: api.execution_time_ms,
|
|
2007
|
+
urls: api.urls,
|
|
2008
|
+
actionNames: api.action_names,
|
|
2009
|
+
errors: api.errors,
|
|
2010
|
+
modelActions: api.model_actions,
|
|
2011
|
+
isDone: api.is_done,
|
|
2012
|
+
actionHistory: api.action_history,
|
|
2013
|
+
actionResults: api.action_results,
|
|
2014
|
+
hasErrors: api.has_errors,
|
|
2015
|
+
numberOfSteps: api.number_of_steps,
|
|
2016
|
+
judgement: api.judgement,
|
|
2017
|
+
isValidated: api.is_validated,
|
|
2018
|
+
replayId: api.replay_id,
|
|
2019
|
+
replayUrl: api.replay_url,
|
|
2020
|
+
recordingId: api.recording_id,
|
|
2021
|
+
recordingStatus: api.recording_status,
|
|
2022
|
+
taskId: api.task_id,
|
|
2023
|
+
status: api.status,
|
|
2024
|
+
output: api.output,
|
|
2025
|
+
debugUrl: api.debug_url
|
|
2026
|
+
};
|
|
2027
|
+
}
|
|
2028
|
+
function mapRecordingStatus(api) {
|
|
2029
|
+
return {
|
|
2030
|
+
id: api.id,
|
|
2031
|
+
status: api.status,
|
|
2032
|
+
replayUrl: api.replay_url,
|
|
2033
|
+
networkUrl: api.network_url,
|
|
2034
|
+
consoleUrl: api.console_url,
|
|
2035
|
+
videoUrl: api.video_url,
|
|
2036
|
+
result: api.result,
|
|
2037
|
+
totalEvents: api.total_events,
|
|
2038
|
+
fileSize: api.file_size,
|
|
2039
|
+
duration: api.duration,
|
|
2040
|
+
error: api.error,
|
|
2041
|
+
createdAt: api.created_at
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
function mapBrowserError(api) {
|
|
2045
|
+
return {
|
|
2046
|
+
type: api.type,
|
|
2047
|
+
message: api.message,
|
|
2048
|
+
url: api.url,
|
|
2049
|
+
timestamp: api.timestamp,
|
|
2050
|
+
screenshotUrl: api.screenshot_url,
|
|
2051
|
+
capturedAt: api.captured_at,
|
|
2052
|
+
status: api.status
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
function mapErrorsResponse(api) {
|
|
2056
|
+
return {
|
|
2057
|
+
recordingId: api.recording_id,
|
|
2058
|
+
totalErrors: api.total_errors,
|
|
2059
|
+
errors: Array.isArray(api.errors) ? api.errors.map(mapBrowserError) : []
|
|
2060
|
+
};
|
|
2061
|
+
}
|
|
2062
|
+
function mapWebpResponse(api) {
|
|
2063
|
+
return {
|
|
2064
|
+
webpUrl: api.webp_url,
|
|
2065
|
+
cached: api.cached,
|
|
2066
|
+
width: api.width,
|
|
2067
|
+
fps: api.fps,
|
|
2068
|
+
maxDuration: api.max_duration,
|
|
2069
|
+
fileSize: api.file_size,
|
|
2070
|
+
maxSizeMb: api.max_size_mb,
|
|
2071
|
+
budgetMet: api.budget_met,
|
|
2072
|
+
qualityUsed: api.quality_used,
|
|
2073
|
+
attempts: api.attempts
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
async function getTaskStatus(taskId, config) {
|
|
2077
|
+
const apiUrl = config.apiUrl || DEFAULT_CONFIG2.apiUrl;
|
|
2078
|
+
const debug = config.debug || false;
|
|
2079
|
+
if (debug) console.log(`[Browser] getTaskStatus: ${taskId}`);
|
|
2080
|
+
const headers = {};
|
|
2081
|
+
if (config.apiKey) headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
2082
|
+
const response = await fetch(`${apiUrl}/tasks/${taskId}`, {
|
|
2083
|
+
method: "GET",
|
|
2084
|
+
headers
|
|
2085
|
+
});
|
|
2086
|
+
if (!response.ok) {
|
|
2087
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
2088
|
+
if (debug) console.error(`[Browser] getTaskStatus error: ${response.status} - ${errorText}`);
|
|
2089
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
2090
|
+
}
|
|
2091
|
+
const result = mapTaskResult(await response.json());
|
|
2092
|
+
if (debug) console.log(`[Browser] Task status: ${result.status}`);
|
|
2093
|
+
return result;
|
|
2094
|
+
}
|
|
2095
|
+
function generateLiveUrl(taskId, config) {
|
|
2096
|
+
const apiUrl = config.apiUrl || DEFAULT_CONFIG2.apiUrl;
|
|
2097
|
+
const baseUrl = apiUrl.replace("/api", "");
|
|
2098
|
+
return `${baseUrl}/tasks/${taskId}/live`;
|
|
2099
|
+
}
|
|
2100
|
+
async function pollTaskUntilComplete(taskId, config, pollConfig = {}) {
|
|
2101
|
+
const interval = pollConfig.interval ?? 2e3;
|
|
2102
|
+
const timeout = pollConfig.timeout ?? 3e5;
|
|
2103
|
+
const startTime = Date.now();
|
|
2104
|
+
while (Date.now() - startTime < timeout) {
|
|
2105
|
+
const status = await getTaskStatus(taskId, config);
|
|
2106
|
+
if (status.status === "completed" || status.status === "failed") {
|
|
1538
2107
|
return status;
|
|
1539
2108
|
}
|
|
1540
2109
|
await new Promise((resolve2) => setTimeout(resolve2, interval));
|
|
@@ -1690,103 +2259,8 @@ async function checkHealth(config = {}) {
|
|
|
1690
2259
|
}
|
|
1691
2260
|
}
|
|
1692
2261
|
|
|
1693
|
-
// tools/warp_grep/agent/
|
|
1694
|
-
|
|
1695
|
-
if (!envValue) return defaultMs;
|
|
1696
|
-
const parsed = parseInt(envValue, 10);
|
|
1697
|
-
return isNaN(parsed) || parsed <= 0 ? defaultMs : parsed;
|
|
1698
|
-
};
|
|
1699
|
-
var AGENT_CONFIG = {
|
|
1700
|
-
MAX_TURNS: 4,
|
|
1701
|
-
/** Default timeout for model calls. Can be overridden via MORPH_WARP_GREP_TIMEOUT env var (in ms) */
|
|
1702
|
-
TIMEOUT_MS: parseEnvTimeout(process.env.MORPH_WARP_GREP_TIMEOUT, 3e4),
|
|
1703
|
-
MAX_CONTEXT_CHARS: 54e4,
|
|
1704
|
-
MAX_OUTPUT_LINES: 200,
|
|
1705
|
-
MAX_READ_LINES: 800,
|
|
1706
|
-
MAX_LIST_DEPTH: 3,
|
|
1707
|
-
LIST_TIMEOUT_MS: 2e3
|
|
1708
|
-
};
|
|
1709
|
-
var BUILTIN_EXCLUDES = [
|
|
1710
|
-
// Version control
|
|
1711
|
-
".git",
|
|
1712
|
-
".svn",
|
|
1713
|
-
".hg",
|
|
1714
|
-
".bzr",
|
|
1715
|
-
// Dependencies
|
|
1716
|
-
"node_modules",
|
|
1717
|
-
"bower_components",
|
|
1718
|
-
".pnpm",
|
|
1719
|
-
".yarn",
|
|
1720
|
-
"vendor",
|
|
1721
|
-
"packages",
|
|
1722
|
-
"Pods",
|
|
1723
|
-
".bundle",
|
|
1724
|
-
// Python
|
|
1725
|
-
"__pycache__",
|
|
1726
|
-
".pytest_cache",
|
|
1727
|
-
".mypy_cache",
|
|
1728
|
-
".ruff_cache",
|
|
1729
|
-
".venv",
|
|
1730
|
-
"venv",
|
|
1731
|
-
".tox",
|
|
1732
|
-
".nox",
|
|
1733
|
-
".eggs",
|
|
1734
|
-
"*.egg-info",
|
|
1735
|
-
// Build outputs
|
|
1736
|
-
"dist",
|
|
1737
|
-
"build",
|
|
1738
|
-
"out",
|
|
1739
|
-
"output",
|
|
1740
|
-
"target",
|
|
1741
|
-
"_build",
|
|
1742
|
-
".next",
|
|
1743
|
-
".nuxt",
|
|
1744
|
-
".output",
|
|
1745
|
-
".vercel",
|
|
1746
|
-
".netlify",
|
|
1747
|
-
// Cache directories
|
|
1748
|
-
".cache",
|
|
1749
|
-
".parcel-cache",
|
|
1750
|
-
".turbo",
|
|
1751
|
-
".nx",
|
|
1752
|
-
".gradle",
|
|
1753
|
-
// IDE/Editor
|
|
1754
|
-
".idea",
|
|
1755
|
-
".vscode",
|
|
1756
|
-
".vs",
|
|
1757
|
-
// Coverage
|
|
1758
|
-
"coverage",
|
|
1759
|
-
".coverage",
|
|
1760
|
-
"htmlcov",
|
|
1761
|
-
".nyc_output",
|
|
1762
|
-
// Temporary
|
|
1763
|
-
"tmp",
|
|
1764
|
-
"temp",
|
|
1765
|
-
".tmp",
|
|
1766
|
-
".temp",
|
|
1767
|
-
// Lock files
|
|
1768
|
-
"package-lock.json",
|
|
1769
|
-
"yarn.lock",
|
|
1770
|
-
"pnpm-lock.yaml",
|
|
1771
|
-
"bun.lockb",
|
|
1772
|
-
"Cargo.lock",
|
|
1773
|
-
"Gemfile.lock",
|
|
1774
|
-
"poetry.lock",
|
|
1775
|
-
// Binary/minified
|
|
1776
|
-
"*.min.js",
|
|
1777
|
-
"*.min.css",
|
|
1778
|
-
"*.bundle.js",
|
|
1779
|
-
"*.wasm",
|
|
1780
|
-
"*.so",
|
|
1781
|
-
"*.dll",
|
|
1782
|
-
"*.pyc",
|
|
1783
|
-
"*.map",
|
|
1784
|
-
"*.js.map",
|
|
1785
|
-
// Hidden directories catch-all
|
|
1786
|
-
".*"
|
|
1787
|
-
];
|
|
1788
|
-
var DEFAULT_EXCLUDES = (process.env.MORPH_WARP_GREP_EXCLUDE || "").split(",").map((s) => s.trim()).filter(Boolean).concat(BUILTIN_EXCLUDES);
|
|
1789
|
-
var DEFAULT_MODEL = "morph-warp-grep-v1";
|
|
2262
|
+
// tools/warp_grep/agent/runner.ts
|
|
2263
|
+
init_config();
|
|
1790
2264
|
|
|
1791
2265
|
// tools/warp_grep/agent/prompt.ts
|
|
1792
2266
|
var SYSTEM_PROMPT = `You are a code search agent. Your task is to find all relevant code for a given search_string.
|
|
@@ -2302,6 +2776,7 @@ async function toolRead(provider, args) {
|
|
|
2302
2776
|
}
|
|
2303
2777
|
|
|
2304
2778
|
// tools/warp_grep/agent/tools/list_directory.ts
|
|
2779
|
+
init_config();
|
|
2305
2780
|
async function toolListDirectory(provider, args) {
|
|
2306
2781
|
const maxResults = args.maxResults ?? AGENT_CONFIG.MAX_OUTPUT_LINES;
|
|
2307
2782
|
const initialDepth = args.maxDepth ?? AGENT_CONFIG.MAX_LIST_DEPTH;
|
|
@@ -2476,6 +2951,7 @@ function formatAgentToolOutput(toolName, args, output, options = {}) {
|
|
|
2476
2951
|
|
|
2477
2952
|
// tools/warp_grep/agent/helpers.ts
|
|
2478
2953
|
var import_path2 = __toESM(require("path"), 1);
|
|
2954
|
+
init_config();
|
|
2479
2955
|
var TRUNCATED_MARKER = "[truncated for context limit]";
|
|
2480
2956
|
function formatTurnMessage(turnsUsed, maxTurns) {
|
|
2481
2957
|
const turnsRemaining = maxTurns - turnsUsed;
|
|
@@ -2558,9 +3034,9 @@ function enforceContextLimit(messages, maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS
|
|
|
2558
3034
|
var import_openai = __toESM(require("openai"), 1);
|
|
2559
3035
|
var import_path3 = __toESM(require("path"), 1);
|
|
2560
3036
|
var parser = new LLMResponseParser();
|
|
2561
|
-
var
|
|
3037
|
+
var DEFAULT_API_URL3 = "https://api.morphllm.com";
|
|
2562
3038
|
async function callModel(messages, model, options = {}) {
|
|
2563
|
-
const baseUrl = options.morphApiUrl ||
|
|
3039
|
+
const baseUrl = options.morphApiUrl || DEFAULT_API_URL3;
|
|
2564
3040
|
const apiKey = options.morphApiKey || process.env.MORPH_API_KEY || "";
|
|
2565
3041
|
const timeoutMs = options.timeout ?? AGENT_CONFIG.TIMEOUT_MS;
|
|
2566
3042
|
const client = new import_openai.default({
|
|
@@ -2669,624 +3145,225 @@ async function runWarpGrep(config) {
|
|
|
2669
3145
|
toolRead(provider, args).then(
|
|
2670
3146
|
(p) => formatAgentToolOutput("read", args, p, { isError: false }),
|
|
2671
3147
|
(err) => formatAgentToolOutput("read", args, String(err), { isError: true })
|
|
2672
|
-
)
|
|
2673
|
-
);
|
|
2674
|
-
}
|
|
2675
|
-
const toolExecStart = Date.now();
|
|
2676
|
-
const allResults = await Promise.all(allPromises);
|
|
2677
|
-
turnMetrics.local_tools_ms = Date.now() - toolExecStart;
|
|
2678
|
-
for (const result of allResults) {
|
|
2679
|
-
formatted.push(result);
|
|
2680
|
-
}
|
|
2681
|
-
if (formatted.length > 0) {
|
|
2682
|
-
const turnMessage = formatTurnMessage(turn, maxTurns);
|
|
2683
|
-
const contextBudget = calculateContextBudget(messages);
|
|
2684
|
-
messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
|
|
2685
|
-
}
|
|
2686
|
-
timings.turns.push(turnMetrics);
|
|
2687
|
-
if (finishCalls.length) {
|
|
2688
|
-
const fc = finishCalls[0];
|
|
2689
|
-
const files = fc.arguments?.files ?? [];
|
|
2690
|
-
finishMeta = { files };
|
|
2691
|
-
terminationReason = "completed";
|
|
2692
|
-
break;
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
if (terminationReason !== "completed" || !finishMeta) {
|
|
2696
|
-
timings.total_ms = Date.now() - totalStart;
|
|
2697
|
-
return { terminationReason, messages, errors, timings };
|
|
2698
|
-
}
|
|
2699
|
-
const parts = ["Relevant context found:"];
|
|
2700
|
-
for (const f of finishMeta.files) {
|
|
2701
|
-
const ranges = f.lines === "*" ? "*" : Array.isArray(f.lines) ? f.lines.map(([s, e]) => `${s}-${e}`).join(", ") : "*";
|
|
2702
|
-
parts.push(`- ${f.path}: ${ranges}`);
|
|
2703
|
-
}
|
|
2704
|
-
const payload = parts.join("\n");
|
|
2705
|
-
const finishResolutionStart = Date.now();
|
|
2706
|
-
const fileReadErrors = [];
|
|
2707
|
-
const resolved = await readFinishFiles(
|
|
2708
|
-
repoRoot,
|
|
2709
|
-
finishMeta.files,
|
|
2710
|
-
async (p, s, e) => {
|
|
2711
|
-
try {
|
|
2712
|
-
const rr = await provider.read({ path: p, start: s, end: e });
|
|
2713
|
-
return rr.lines.map((l) => {
|
|
2714
|
-
const idx = l.indexOf("|");
|
|
2715
|
-
return idx >= 0 ? l.slice(idx + 1) : l;
|
|
2716
|
-
});
|
|
2717
|
-
} catch (err) {
|
|
2718
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2719
|
-
fileReadErrors.push({ path: p, error: errorMsg });
|
|
2720
|
-
console.error(`[warp_grep] Failed to read file: ${p} - ${errorMsg}`);
|
|
2721
|
-
return [`[couldn't find: ${p}]`];
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
|
-
);
|
|
2725
|
-
timings.finish_resolution_ms = Date.now() - finishResolutionStart;
|
|
2726
|
-
if (fileReadErrors.length > 0) {
|
|
2727
|
-
errors.push(...fileReadErrors.map((e) => ({ message: `File read error: ${e.path} - ${e.error}` })));
|
|
2728
|
-
}
|
|
2729
|
-
timings.total_ms = Date.now() - totalStart;
|
|
2730
|
-
return {
|
|
2731
|
-
terminationReason: "completed",
|
|
2732
|
-
messages,
|
|
2733
|
-
finish: { payload, metadata: finishMeta, resolved },
|
|
2734
|
-
timings
|
|
2735
|
-
};
|
|
2736
|
-
}
|
|
2737
|
-
async function* runWarpGrepStreaming(config) {
|
|
2738
|
-
const totalStart = Date.now();
|
|
2739
|
-
const timeoutMs = config.timeout ?? AGENT_CONFIG.TIMEOUT_MS;
|
|
2740
|
-
const timings = { turns: [], timeout_ms: timeoutMs };
|
|
2741
|
-
const repoRoot = import_path3.default.resolve(config.repoRoot || process.cwd());
|
|
2742
|
-
const messages = [];
|
|
2743
|
-
messages.push({ role: "system", content: getSystemPrompt() });
|
|
2744
|
-
const initialStateStart = Date.now();
|
|
2745
|
-
const initialState = await buildInitialState(repoRoot, config.query, config.provider);
|
|
2746
|
-
timings.initial_state_ms = Date.now() - initialStateStart;
|
|
2747
|
-
messages.push({ role: "user", content: initialState });
|
|
2748
|
-
const maxTurns = AGENT_CONFIG.MAX_TURNS;
|
|
2749
|
-
const model = config.model || DEFAULT_MODEL;
|
|
2750
|
-
const provider = config.provider;
|
|
2751
|
-
const errors = [];
|
|
2752
|
-
let finishMeta;
|
|
2753
|
-
let terminationReason = "terminated";
|
|
2754
|
-
for (let turn = 1; turn <= maxTurns; turn += 1) {
|
|
2755
|
-
const turnMetrics = { turn, morph_api_ms: 0, local_tools_ms: 0 };
|
|
2756
|
-
enforceContextLimit(messages);
|
|
2757
|
-
const modelCallStart = Date.now();
|
|
2758
|
-
const assistantContent = await callModel(messages, model, {
|
|
2759
|
-
morphApiKey: config.morphApiKey,
|
|
2760
|
-
morphApiUrl: config.morphApiUrl,
|
|
2761
|
-
retryConfig: config.retryConfig,
|
|
2762
|
-
timeout: timeoutMs
|
|
2763
|
-
}).catch((e) => {
|
|
2764
|
-
errors.push({ message: e instanceof Error ? e.message : String(e) });
|
|
2765
|
-
return "";
|
|
2766
|
-
});
|
|
2767
|
-
turnMetrics.morph_api_ms = Date.now() - modelCallStart;
|
|
2768
|
-
if (!assistantContent) {
|
|
2769
|
-
timings.turns.push(turnMetrics);
|
|
2770
|
-
break;
|
|
2771
|
-
}
|
|
2772
|
-
messages.push({ role: "assistant", content: assistantContent });
|
|
2773
|
-
const toolCalls = parser.parse(assistantContent);
|
|
2774
|
-
if (toolCalls.length === 0) {
|
|
2775
|
-
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" });
|
|
2776
|
-
terminationReason = "terminated";
|
|
2777
|
-
timings.turns.push(turnMetrics);
|
|
2778
|
-
break;
|
|
2779
|
-
}
|
|
2780
|
-
yield {
|
|
2781
|
-
turn,
|
|
2782
|
-
toolCalls: toolCalls.map((c) => ({
|
|
2783
|
-
name: c.name,
|
|
2784
|
-
arguments: c.arguments ?? {}
|
|
2785
|
-
}))
|
|
2786
|
-
};
|
|
2787
|
-
const finishCalls = toolCalls.filter((c) => c.name === "finish");
|
|
2788
|
-
const grepCalls = toolCalls.filter((c) => c.name === "grep");
|
|
2789
|
-
const listDirCalls = toolCalls.filter((c) => c.name === "list_directory");
|
|
2790
|
-
const readCalls = toolCalls.filter((c) => c.name === "read");
|
|
2791
|
-
const skipCalls = toolCalls.filter((c) => c.name === "_skip");
|
|
2792
|
-
const formatted = [];
|
|
2793
|
-
for (const c of skipCalls) {
|
|
2794
|
-
const msg = c.arguments?.message || "Command skipped due to parsing error";
|
|
2795
|
-
formatted.push(msg);
|
|
2796
|
-
}
|
|
2797
|
-
const allPromises = [];
|
|
2798
|
-
for (const c of grepCalls) {
|
|
2799
|
-
const args = c.arguments ?? {};
|
|
2800
|
-
allPromises.push(
|
|
2801
|
-
toolGrep(provider, args).then(
|
|
2802
|
-
({ output }) => formatAgentToolOutput("grep", args, output, { isError: false }),
|
|
2803
|
-
(err) => formatAgentToolOutput("grep", args, String(err), { isError: true })
|
|
2804
|
-
)
|
|
2805
|
-
);
|
|
2806
|
-
}
|
|
2807
|
-
for (const c of listDirCalls) {
|
|
2808
|
-
const args = c.arguments ?? {};
|
|
2809
|
-
allPromises.push(
|
|
2810
|
-
toolListDirectory(provider, args).then(
|
|
2811
|
-
(p) => formatAgentToolOutput("list_directory", args, p, { isError: false }),
|
|
2812
|
-
(err) => formatAgentToolOutput("list_directory", args, String(err), { isError: true })
|
|
2813
|
-
)
|
|
2814
|
-
);
|
|
2815
|
-
}
|
|
2816
|
-
for (const c of readCalls) {
|
|
2817
|
-
const args = c.arguments ?? {};
|
|
2818
|
-
allPromises.push(
|
|
2819
|
-
toolRead(provider, args).then(
|
|
2820
|
-
(p) => formatAgentToolOutput("read", args, p, { isError: false }),
|
|
2821
|
-
(err) => formatAgentToolOutput("read", args, String(err), { isError: true })
|
|
2822
|
-
)
|
|
2823
|
-
);
|
|
2824
|
-
}
|
|
2825
|
-
const toolExecStart = Date.now();
|
|
2826
|
-
const allResults = await Promise.all(allPromises);
|
|
2827
|
-
turnMetrics.local_tools_ms = Date.now() - toolExecStart;
|
|
2828
|
-
for (const result of allResults) {
|
|
2829
|
-
formatted.push(result);
|
|
2830
|
-
}
|
|
2831
|
-
if (formatted.length > 0) {
|
|
2832
|
-
const turnMessage = formatTurnMessage(turn, maxTurns);
|
|
2833
|
-
const contextBudget = calculateContextBudget(messages);
|
|
2834
|
-
messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
|
|
2835
|
-
}
|
|
2836
|
-
timings.turns.push(turnMetrics);
|
|
2837
|
-
if (finishCalls.length) {
|
|
2838
|
-
const fc = finishCalls[0];
|
|
2839
|
-
const files = fc.arguments?.files ?? [];
|
|
2840
|
-
finishMeta = { files };
|
|
2841
|
-
terminationReason = "completed";
|
|
2842
|
-
break;
|
|
2843
|
-
}
|
|
2844
|
-
}
|
|
2845
|
-
if (terminationReason !== "completed" || !finishMeta) {
|
|
2846
|
-
timings.total_ms = Date.now() - totalStart;
|
|
2847
|
-
return { terminationReason, messages, errors, timings };
|
|
2848
|
-
}
|
|
2849
|
-
const parts = ["Relevant context found:"];
|
|
2850
|
-
for (const f of finishMeta.files) {
|
|
2851
|
-
const ranges = f.lines === "*" ? "*" : Array.isArray(f.lines) ? f.lines.map(([s, e]) => `${s}-${e}`).join(", ") : "*";
|
|
2852
|
-
parts.push(`- ${f.path}: ${ranges}`);
|
|
2853
|
-
}
|
|
2854
|
-
const payload = parts.join("\n");
|
|
2855
|
-
const finishResolutionStart = Date.now();
|
|
2856
|
-
const fileReadErrors = [];
|
|
2857
|
-
const resolved = await readFinishFiles(
|
|
2858
|
-
repoRoot,
|
|
2859
|
-
finishMeta.files,
|
|
2860
|
-
async (p, s, e) => {
|
|
2861
|
-
try {
|
|
2862
|
-
const rr = await provider.read({ path: p, start: s, end: e });
|
|
2863
|
-
return rr.lines.map((l) => {
|
|
2864
|
-
const idx = l.indexOf("|");
|
|
2865
|
-
return idx >= 0 ? l.slice(idx + 1) : l;
|
|
2866
|
-
});
|
|
2867
|
-
} catch (err) {
|
|
2868
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
2869
|
-
fileReadErrors.push({ path: p, error: errorMsg });
|
|
2870
|
-
console.error(`[warp_grep] Failed to read file: ${p} - ${errorMsg}`);
|
|
2871
|
-
return [`[couldn't find: ${p}]`];
|
|
2872
|
-
}
|
|
2873
|
-
}
|
|
2874
|
-
);
|
|
2875
|
-
timings.finish_resolution_ms = Date.now() - finishResolutionStart;
|
|
2876
|
-
if (fileReadErrors.length > 0) {
|
|
2877
|
-
errors.push(...fileReadErrors.map((e) => ({ message: `File read error: ${e.path} - ${e.error}` })));
|
|
2878
|
-
}
|
|
2879
|
-
timings.total_ms = Date.now() - totalStart;
|
|
2880
|
-
return {
|
|
2881
|
-
terminationReason: "completed",
|
|
2882
|
-
messages,
|
|
2883
|
-
finish: { payload, metadata: finishMeta, resolved },
|
|
2884
|
-
timings
|
|
2885
|
-
};
|
|
2886
|
-
}
|
|
2887
|
-
|
|
2888
|
-
// tools/warp_grep/providers/local.ts
|
|
2889
|
-
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
2890
|
-
var import_path5 = __toESM(require("path"), 1);
|
|
2891
|
-
|
|
2892
|
-
// tools/warp_grep/utils/ripgrep.ts
|
|
2893
|
-
var import_child_process = require("child_process");
|
|
2894
|
-
var import_ripgrep = require("@vscode/ripgrep");
|
|
2895
|
-
var resolvedRgPath = null;
|
|
2896
|
-
var rgPathChecked = false;
|
|
2897
|
-
function spawnRg(rgBinary, args, opts) {
|
|
2898
|
-
return new Promise((resolve2) => {
|
|
2899
|
-
const child = (0, import_child_process.spawn)(rgBinary, args, {
|
|
2900
|
-
cwd: opts?.cwd,
|
|
2901
|
-
env: { ...process.env, ...opts?.env || {} },
|
|
2902
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2903
|
-
});
|
|
2904
|
-
let stdout = "";
|
|
2905
|
-
let stderr = "";
|
|
2906
|
-
child.stdout.on("data", (d) => stdout += d.toString());
|
|
2907
|
-
child.stderr.on("data", (d) => stderr += d.toString());
|
|
2908
|
-
child.on("close", (code) => {
|
|
2909
|
-
resolve2({ stdout, stderr, exitCode: typeof code === "number" ? code : -1 });
|
|
2910
|
-
});
|
|
2911
|
-
child.on("error", () => {
|
|
2912
|
-
resolve2({ stdout: "", stderr: "Failed to spawn ripgrep.", exitCode: -1 });
|
|
2913
|
-
});
|
|
2914
|
-
});
|
|
2915
|
-
}
|
|
2916
|
-
function isBinaryFailure(result) {
|
|
2917
|
-
if (result.exitCode === -1) return true;
|
|
2918
|
-
if (result.stderr.includes("jemalloc") || result.stderr.includes("Unsupported system page size")) return true;
|
|
2919
|
-
if (result.exitCode === 134) return true;
|
|
2920
|
-
return false;
|
|
2921
|
-
}
|
|
2922
|
-
async function runRipgrep(args, opts) {
|
|
2923
|
-
if (rgPathChecked && resolvedRgPath) {
|
|
2924
|
-
return spawnRg(resolvedRgPath, args, opts);
|
|
2925
|
-
}
|
|
2926
|
-
if (!rgPathChecked) {
|
|
2927
|
-
const result = await spawnRg(import_ripgrep.rgPath, args, opts);
|
|
2928
|
-
if (!isBinaryFailure(result)) {
|
|
2929
|
-
resolvedRgPath = import_ripgrep.rgPath;
|
|
2930
|
-
rgPathChecked = true;
|
|
2931
|
-
return result;
|
|
2932
|
-
}
|
|
2933
|
-
const fallbackResult = await spawnRg("rg", args, opts);
|
|
2934
|
-
if (!isBinaryFailure(fallbackResult)) {
|
|
2935
|
-
resolvedRgPath = "rg";
|
|
2936
|
-
rgPathChecked = true;
|
|
2937
|
-
return fallbackResult;
|
|
2938
|
-
}
|
|
2939
|
-
rgPathChecked = true;
|
|
2940
|
-
return {
|
|
2941
|
-
stdout: "",
|
|
2942
|
-
stderr: "Failed to spawn ripgrep. Neither bundled nor system rg is available.",
|
|
2943
|
-
exitCode: -1
|
|
2944
|
-
};
|
|
2945
|
-
}
|
|
2946
|
-
return {
|
|
2947
|
-
stdout: "",
|
|
2948
|
-
stderr: "Failed to spawn ripgrep. Neither bundled nor system rg is available.",
|
|
2949
|
-
exitCode: -1
|
|
2950
|
-
};
|
|
2951
|
-
}
|
|
2952
|
-
|
|
2953
|
-
// tools/warp_grep/utils/paths.ts
|
|
2954
|
-
var import_fs = __toESM(require("fs"), 1);
|
|
2955
|
-
var import_path4 = __toESM(require("path"), 1);
|
|
2956
|
-
function resolveUnderRepo(repoRoot, targetPath) {
|
|
2957
|
-
const absRoot = import_path4.default.resolve(repoRoot);
|
|
2958
|
-
const resolved = import_path4.default.resolve(absRoot, targetPath);
|
|
2959
|
-
ensureWithinRepo(absRoot, resolved);
|
|
2960
|
-
return resolved;
|
|
2961
|
-
}
|
|
2962
|
-
function ensureWithinRepo(repoRoot, absTarget) {
|
|
2963
|
-
const rel = import_path4.default.relative(import_path4.default.resolve(repoRoot), import_path4.default.resolve(absTarget));
|
|
2964
|
-
if (rel.startsWith("..") || import_path4.default.isAbsolute(rel)) {
|
|
2965
|
-
throw new Error(`Path outside repository root: ${absTarget}`);
|
|
2966
|
-
}
|
|
2967
|
-
}
|
|
2968
|
-
function toRepoRelative(repoRoot, absPath) {
|
|
2969
|
-
return import_path4.default.relative(import_path4.default.resolve(repoRoot), import_path4.default.resolve(absPath));
|
|
2970
|
-
}
|
|
2971
|
-
function isSymlink(p) {
|
|
2972
|
-
try {
|
|
2973
|
-
const st = import_fs.default.lstatSync(p);
|
|
2974
|
-
return st.isSymbolicLink();
|
|
2975
|
-
} catch {
|
|
2976
|
-
return false;
|
|
2977
|
-
}
|
|
2978
|
-
}
|
|
2979
|
-
function fixPathRepetition(fullPath) {
|
|
2980
|
-
const segments = fullPath.split(import_path4.default.sep).filter(Boolean);
|
|
2981
|
-
if (segments.length < 2) return null;
|
|
2982
|
-
for (let len = Math.floor(segments.length / 2); len >= 1; len--) {
|
|
2983
|
-
for (let i = 0; i <= segments.length - 2 * len; i++) {
|
|
2984
|
-
const first = segments.slice(i, i + len);
|
|
2985
|
-
const second = segments.slice(i + len, i + 2 * len);
|
|
2986
|
-
if (first.every((seg, idx) => seg === second[idx])) {
|
|
2987
|
-
const fixed = [...segments.slice(0, i), ...segments.slice(i + len)];
|
|
2988
|
-
return import_path4.default.sep + fixed.join(import_path4.default.sep);
|
|
2989
|
-
}
|
|
2990
|
-
}
|
|
2991
|
-
}
|
|
2992
|
-
return null;
|
|
2993
|
-
}
|
|
2994
|
-
function isTextualFile(filePath, maxBytes = 2e6) {
|
|
2995
|
-
try {
|
|
2996
|
-
const st = import_fs.default.statSync(filePath);
|
|
2997
|
-
if (!st.isFile()) return false;
|
|
2998
|
-
if (st.size > maxBytes) return false;
|
|
2999
|
-
const fd = import_fs.default.openSync(filePath, "r");
|
|
3000
|
-
const buf = Buffer.alloc(512);
|
|
3001
|
-
const read = import_fs.default.readSync(fd, buf, 0, buf.length, 0);
|
|
3002
|
-
import_fs.default.closeSync(fd);
|
|
3003
|
-
for (let i = 0; i < read; i++) {
|
|
3004
|
-
const c = buf[i];
|
|
3005
|
-
if (c === 0) return false;
|
|
3006
|
-
}
|
|
3007
|
-
return true;
|
|
3008
|
-
} catch {
|
|
3009
|
-
return false;
|
|
3010
|
-
}
|
|
3011
|
-
}
|
|
3012
|
-
|
|
3013
|
-
// tools/warp_grep/utils/files.ts
|
|
3014
|
-
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
3015
|
-
async function readAllLines(filePath) {
|
|
3016
|
-
const content = await import_promises2.default.readFile(filePath, "utf8");
|
|
3017
|
-
return content.split(/\r?\n/);
|
|
3018
|
-
}
|
|
3019
|
-
|
|
3020
|
-
// tools/warp_grep/providers/local.ts
|
|
3021
|
-
var SKIP_NAMES = /* @__PURE__ */ new Set([
|
|
3022
|
-
// Version control
|
|
3023
|
-
".git",
|
|
3024
|
-
".svn",
|
|
3025
|
-
".hg",
|
|
3026
|
-
".bzr",
|
|
3027
|
-
// Dependencies
|
|
3028
|
-
"node_modules",
|
|
3029
|
-
"bower_components",
|
|
3030
|
-
".pnpm",
|
|
3031
|
-
".yarn",
|
|
3032
|
-
"vendor",
|
|
3033
|
-
"Pods",
|
|
3034
|
-
".bundle",
|
|
3035
|
-
// Python
|
|
3036
|
-
"__pycache__",
|
|
3037
|
-
".pytest_cache",
|
|
3038
|
-
".mypy_cache",
|
|
3039
|
-
".ruff_cache",
|
|
3040
|
-
".venv",
|
|
3041
|
-
"venv",
|
|
3042
|
-
".tox",
|
|
3043
|
-
".nox",
|
|
3044
|
-
".eggs",
|
|
3045
|
-
// Build outputs
|
|
3046
|
-
"dist",
|
|
3047
|
-
"build",
|
|
3048
|
-
"out",
|
|
3049
|
-
"output",
|
|
3050
|
-
"target",
|
|
3051
|
-
"_build",
|
|
3052
|
-
".next",
|
|
3053
|
-
".nuxt",
|
|
3054
|
-
".output",
|
|
3055
|
-
".vercel",
|
|
3056
|
-
".netlify",
|
|
3057
|
-
// Cache
|
|
3058
|
-
".cache",
|
|
3059
|
-
".parcel-cache",
|
|
3060
|
-
".turbo",
|
|
3061
|
-
".nx",
|
|
3062
|
-
".gradle",
|
|
3063
|
-
// IDE
|
|
3064
|
-
".idea",
|
|
3065
|
-
".vscode",
|
|
3066
|
-
".vs",
|
|
3067
|
-
// Coverage
|
|
3068
|
-
"coverage",
|
|
3069
|
-
".coverage",
|
|
3070
|
-
"htmlcov",
|
|
3071
|
-
".nyc_output",
|
|
3072
|
-
// Temp
|
|
3073
|
-
"tmp",
|
|
3074
|
-
"temp",
|
|
3075
|
-
".tmp",
|
|
3076
|
-
".temp",
|
|
3077
|
-
// Lock files
|
|
3078
|
-
"package-lock.json",
|
|
3079
|
-
"yarn.lock",
|
|
3080
|
-
"pnpm-lock.yaml",
|
|
3081
|
-
"bun.lockb",
|
|
3082
|
-
"Cargo.lock",
|
|
3083
|
-
"Gemfile.lock",
|
|
3084
|
-
"poetry.lock"
|
|
3085
|
-
]);
|
|
3086
|
-
var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3087
|
-
".min.js",
|
|
3088
|
-
".min.css",
|
|
3089
|
-
".bundle.js",
|
|
3090
|
-
".wasm",
|
|
3091
|
-
".so",
|
|
3092
|
-
".dll",
|
|
3093
|
-
".pyc",
|
|
3094
|
-
".map",
|
|
3095
|
-
".js.map"
|
|
3096
|
-
]);
|
|
3097
|
-
function shouldSkip(name) {
|
|
3098
|
-
if (SKIP_NAMES.has(name)) return true;
|
|
3099
|
-
if (name.startsWith(".")) return true;
|
|
3100
|
-
for (const ext of SKIP_EXTENSIONS) {
|
|
3101
|
-
if (name.endsWith(ext)) return true;
|
|
3102
|
-
}
|
|
3103
|
-
return false;
|
|
3104
|
-
}
|
|
3105
|
-
var LocalRipgrepProvider = class {
|
|
3106
|
-
constructor(repoRoot, excludes = DEFAULT_EXCLUDES) {
|
|
3107
|
-
this.repoRoot = repoRoot;
|
|
3108
|
-
this.excludes = excludes;
|
|
3109
|
-
}
|
|
3110
|
-
async grep(params) {
|
|
3111
|
-
let abs;
|
|
3112
|
-
try {
|
|
3113
|
-
abs = resolveUnderRepo(this.repoRoot, params.path);
|
|
3114
|
-
} catch (err) {
|
|
3115
|
-
return {
|
|
3116
|
-
lines: [],
|
|
3117
|
-
error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
|
|
3118
|
-
};
|
|
3119
|
-
}
|
|
3120
|
-
const stat = await import_promises3.default.stat(abs).catch(() => null);
|
|
3121
|
-
if (!stat) return { lines: [] };
|
|
3122
|
-
const targetArg = abs === import_path5.default.resolve(this.repoRoot) ? "." : toRepoRelative(this.repoRoot, abs);
|
|
3123
|
-
const args = [
|
|
3124
|
-
"--no-config",
|
|
3125
|
-
"--no-heading",
|
|
3126
|
-
"--with-filename",
|
|
3127
|
-
"--line-number",
|
|
3128
|
-
"--color=never",
|
|
3129
|
-
"--trim",
|
|
3130
|
-
"--max-columns=400",
|
|
3131
|
-
"-C",
|
|
3132
|
-
"1",
|
|
3133
|
-
...params.glob ? ["--glob", params.glob] : [],
|
|
3134
|
-
...this.excludes.flatMap((e) => ["-g", `!${e}`]),
|
|
3135
|
-
params.pattern,
|
|
3136
|
-
targetArg || "."
|
|
3137
|
-
];
|
|
3138
|
-
const res = await runRipgrep(args, { cwd: this.repoRoot });
|
|
3139
|
-
if (res.exitCode === -1) {
|
|
3140
|
-
return {
|
|
3141
|
-
lines: [],
|
|
3142
|
-
error: `[RIPGREP NOT AVAILABLE] ripgrep (rg) is required but failed to execute. Please install it:
|
|
3143
|
-
\u2022 macOS: brew install ripgrep
|
|
3144
|
-
\u2022 Ubuntu/Debian: apt install ripgrep
|
|
3145
|
-
\u2022 Windows: choco install ripgrep
|
|
3146
|
-
\u2022 Or visit: https://github.com/BurntSushi/ripgrep#installation
|
|
3147
|
-
Exit code: ${res.exitCode}${res.stderr ? `
|
|
3148
|
-
Details: ${res.stderr}` : ""}`
|
|
3149
|
-
};
|
|
3148
|
+
)
|
|
3149
|
+
);
|
|
3150
3150
|
}
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3151
|
+
const toolExecStart = Date.now();
|
|
3152
|
+
const allResults = await Promise.all(allPromises);
|
|
3153
|
+
turnMetrics.local_tools_ms = Date.now() - toolExecStart;
|
|
3154
|
+
for (const result of allResults) {
|
|
3155
|
+
formatted.push(result);
|
|
3156
3156
|
}
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
error: "query not specific enough, tool tried to return too much context and failed"
|
|
3162
|
-
};
|
|
3157
|
+
if (formatted.length > 0) {
|
|
3158
|
+
const turnMessage = formatTurnMessage(turn, maxTurns);
|
|
3159
|
+
const contextBudget = calculateContextBudget(messages);
|
|
3160
|
+
messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
|
|
3163
3161
|
}
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
return {
|
|
3172
|
-
lines: [],
|
|
3173
|
-
error: `[PATH ERROR] ${err instanceof Error ? err.message : String(err)}`
|
|
3174
|
-
};
|
|
3162
|
+
timings.turns.push(turnMetrics);
|
|
3163
|
+
if (finishCalls.length) {
|
|
3164
|
+
const fc = finishCalls[0];
|
|
3165
|
+
const files = fc.arguments?.files ?? [];
|
|
3166
|
+
finishMeta = { files };
|
|
3167
|
+
terminationReason = "completed";
|
|
3168
|
+
break;
|
|
3175
3169
|
}
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3170
|
+
}
|
|
3171
|
+
if (terminationReason !== "completed" || !finishMeta) {
|
|
3172
|
+
timings.total_ms = Date.now() - totalStart;
|
|
3173
|
+
return { terminationReason, messages, errors, timings };
|
|
3174
|
+
}
|
|
3175
|
+
const parts = ["Relevant context found:"];
|
|
3176
|
+
for (const f of finishMeta.files) {
|
|
3177
|
+
const ranges = f.lines === "*" ? "*" : Array.isArray(f.lines) ? f.lines.map(([s, e]) => `${s}-${e}`).join(", ") : "*";
|
|
3178
|
+
parts.push(`- ${f.path}: ${ranges}`);
|
|
3179
|
+
}
|
|
3180
|
+
const payload = parts.join("\n");
|
|
3181
|
+
const finishResolutionStart = Date.now();
|
|
3182
|
+
const fileReadErrors = [];
|
|
3183
|
+
const resolved = await readFinishFiles(
|
|
3184
|
+
repoRoot,
|
|
3185
|
+
finishMeta.files,
|
|
3186
|
+
async (p, s, e) => {
|
|
3187
|
+
try {
|
|
3188
|
+
const rr = await provider.read({ path: p, start: s, end: e });
|
|
3189
|
+
return rr.lines.map((l) => {
|
|
3190
|
+
const idx = l.indexOf("|");
|
|
3191
|
+
return idx >= 0 ? l.slice(idx + 1) : l;
|
|
3192
|
+
});
|
|
3193
|
+
} catch (err) {
|
|
3194
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3195
|
+
fileReadErrors.push({ path: p, error: errorMsg });
|
|
3196
|
+
console.error(`[warp_grep] Failed to read file: ${p} - ${errorMsg}`);
|
|
3197
|
+
return [`[couldn't find: ${p}]`];
|
|
3185
3198
|
}
|
|
3186
3199
|
}
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3200
|
+
);
|
|
3201
|
+
timings.finish_resolution_ms = Date.now() - finishResolutionStart;
|
|
3202
|
+
if (fileReadErrors.length > 0) {
|
|
3203
|
+
errors.push(...fileReadErrors.map((e) => ({ message: `File read error: ${e.path} - ${e.error}` })));
|
|
3204
|
+
}
|
|
3205
|
+
timings.total_ms = Date.now() - totalStart;
|
|
3206
|
+
return {
|
|
3207
|
+
terminationReason: "completed",
|
|
3208
|
+
messages,
|
|
3209
|
+
finish: { payload, metadata: finishMeta, resolved },
|
|
3210
|
+
timings
|
|
3211
|
+
};
|
|
3212
|
+
}
|
|
3213
|
+
async function* runWarpGrepStreaming(config) {
|
|
3214
|
+
const totalStart = Date.now();
|
|
3215
|
+
const timeoutMs = config.timeout ?? AGENT_CONFIG.TIMEOUT_MS;
|
|
3216
|
+
const timings = { turns: [], timeout_ms: timeoutMs };
|
|
3217
|
+
const repoRoot = import_path3.default.resolve(config.repoRoot || process.cwd());
|
|
3218
|
+
const messages = [];
|
|
3219
|
+
messages.push({ role: "system", content: getSystemPrompt() });
|
|
3220
|
+
const initialStateStart = Date.now();
|
|
3221
|
+
const initialState = await buildInitialState(repoRoot, config.query, config.provider);
|
|
3222
|
+
timings.initial_state_ms = Date.now() - initialStateStart;
|
|
3223
|
+
messages.push({ role: "user", content: initialState });
|
|
3224
|
+
const maxTurns = AGENT_CONFIG.MAX_TURNS;
|
|
3225
|
+
const model = config.model || DEFAULT_MODEL;
|
|
3226
|
+
const provider = config.provider;
|
|
3227
|
+
const errors = [];
|
|
3228
|
+
let finishMeta;
|
|
3229
|
+
let terminationReason = "terminated";
|
|
3230
|
+
for (let turn = 1; turn <= maxTurns; turn += 1) {
|
|
3231
|
+
const turnMetrics = { turn, morph_api_ms: 0, local_tools_ms: 0 };
|
|
3232
|
+
enforceContextLimit(messages);
|
|
3233
|
+
const modelCallStart = Date.now();
|
|
3234
|
+
const assistantContent = await callModel(messages, model, {
|
|
3235
|
+
morphApiKey: config.morphApiKey,
|
|
3236
|
+
morphApiUrl: config.morphApiUrl,
|
|
3237
|
+
retryConfig: config.retryConfig,
|
|
3238
|
+
timeout: timeoutMs
|
|
3239
|
+
}).catch((e) => {
|
|
3240
|
+
errors.push({ message: e instanceof Error ? e.message : String(e) });
|
|
3241
|
+
return "";
|
|
3242
|
+
});
|
|
3243
|
+
turnMetrics.morph_api_ms = Date.now() - modelCallStart;
|
|
3244
|
+
if (!assistantContent) {
|
|
3245
|
+
timings.turns.push(turnMetrics);
|
|
3246
|
+
break;
|
|
3192
3247
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3248
|
+
messages.push({ role: "assistant", content: assistantContent });
|
|
3249
|
+
const toolCalls = parser.parse(assistantContent);
|
|
3250
|
+
if (toolCalls.length === 0) {
|
|
3251
|
+
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" });
|
|
3252
|
+
terminationReason = "terminated";
|
|
3253
|
+
timings.turns.push(turnMetrics);
|
|
3254
|
+
break;
|
|
3198
3255
|
}
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3256
|
+
yield {
|
|
3257
|
+
turn,
|
|
3258
|
+
toolCalls: toolCalls.map((c) => ({
|
|
3259
|
+
name: c.name,
|
|
3260
|
+
arguments: c.arguments ?? {}
|
|
3261
|
+
}))
|
|
3262
|
+
};
|
|
3263
|
+
const finishCalls = toolCalls.filter((c) => c.name === "finish");
|
|
3264
|
+
const grepCalls = toolCalls.filter((c) => c.name === "grep");
|
|
3265
|
+
const listDirCalls = toolCalls.filter((c) => c.name === "list_directory");
|
|
3266
|
+
const readCalls = toolCalls.filter((c) => c.name === "read");
|
|
3267
|
+
const skipCalls = toolCalls.filter((c) => c.name === "_skip");
|
|
3268
|
+
const formatted = [];
|
|
3269
|
+
for (const c of skipCalls) {
|
|
3270
|
+
const msg = c.arguments?.message || "Command skipped due to parsing error";
|
|
3271
|
+
formatted.push(msg);
|
|
3204
3272
|
}
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3273
|
+
const allPromises = [];
|
|
3274
|
+
for (const c of grepCalls) {
|
|
3275
|
+
const args = c.arguments ?? {};
|
|
3276
|
+
allPromises.push(
|
|
3277
|
+
toolGrep(provider, args).then(
|
|
3278
|
+
({ output }) => formatAgentToolOutput("grep", args, output, { isError: false }),
|
|
3279
|
+
(err) => formatAgentToolOutput("grep", args, String(err), { isError: true })
|
|
3280
|
+
)
|
|
3281
|
+
);
|
|
3213
3282
|
}
|
|
3214
|
-
const
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
s = rawStart ?? 1;
|
|
3223
|
-
e = Math.min(rawEnd ?? total, total);
|
|
3224
|
-
if (s > total && total > 0 || s > e) {
|
|
3225
|
-
s = 1;
|
|
3226
|
-
e = total;
|
|
3227
|
-
}
|
|
3283
|
+
for (const c of listDirCalls) {
|
|
3284
|
+
const args = c.arguments ?? {};
|
|
3285
|
+
allPromises.push(
|
|
3286
|
+
toolListDirectory(provider, args).then(
|
|
3287
|
+
(p) => formatAgentToolOutput("list_directory", args, p, { isError: false }),
|
|
3288
|
+
(err) => formatAgentToolOutput("list_directory", args, String(err), { isError: true })
|
|
3289
|
+
)
|
|
3290
|
+
);
|
|
3228
3291
|
}
|
|
3229
|
-
const
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3292
|
+
for (const c of readCalls) {
|
|
3293
|
+
const args = c.arguments ?? {};
|
|
3294
|
+
allPromises.push(
|
|
3295
|
+
toolRead(provider, args).then(
|
|
3296
|
+
(p) => formatAgentToolOutput("read", args, p, { isError: false }),
|
|
3297
|
+
(err) => formatAgentToolOutput("read", args, String(err), { isError: true })
|
|
3298
|
+
)
|
|
3299
|
+
);
|
|
3233
3300
|
}
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3301
|
+
const toolExecStart = Date.now();
|
|
3302
|
+
const allResults = await Promise.all(allPromises);
|
|
3303
|
+
turnMetrics.local_tools_ms = Date.now() - toolExecStart;
|
|
3304
|
+
for (const result of allResults) {
|
|
3305
|
+
formatted.push(result);
|
|
3238
3306
|
}
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
try {
|
|
3244
|
-
abs = resolveUnderRepo(this.repoRoot, params.path);
|
|
3245
|
-
} catch {
|
|
3246
|
-
return [];
|
|
3307
|
+
if (formatted.length > 0) {
|
|
3308
|
+
const turnMessage = formatTurnMessage(turn, maxTurns);
|
|
3309
|
+
const contextBudget = calculateContextBudget(messages);
|
|
3310
|
+
messages.push({ role: "user", content: formatted.join("\n") + turnMessage + "\n" + contextBudget });
|
|
3247
3311
|
}
|
|
3248
|
-
|
|
3249
|
-
if (
|
|
3250
|
-
|
|
3312
|
+
timings.turns.push(turnMetrics);
|
|
3313
|
+
if (finishCalls.length) {
|
|
3314
|
+
const fc = finishCalls[0];
|
|
3315
|
+
const files = fc.arguments?.files ?? [];
|
|
3316
|
+
finishMeta = { files };
|
|
3317
|
+
terminationReason = "completed";
|
|
3318
|
+
break;
|
|
3251
3319
|
}
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
const
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
path: toRepoRelative(repoRoot, full),
|
|
3275
|
-
type: isDir ? "dir" : "file",
|
|
3276
|
-
depth
|
|
3320
|
+
}
|
|
3321
|
+
if (terminationReason !== "completed" || !finishMeta) {
|
|
3322
|
+
timings.total_ms = Date.now() - totalStart;
|
|
3323
|
+
return { terminationReason, messages, errors, timings };
|
|
3324
|
+
}
|
|
3325
|
+
const parts = ["Relevant context found:"];
|
|
3326
|
+
for (const f of finishMeta.files) {
|
|
3327
|
+
const ranges = f.lines === "*" ? "*" : Array.isArray(f.lines) ? f.lines.map(([s, e]) => `${s}-${e}`).join(", ") : "*";
|
|
3328
|
+
parts.push(`- ${f.path}: ${ranges}`);
|
|
3329
|
+
}
|
|
3330
|
+
const payload = parts.join("\n");
|
|
3331
|
+
const finishResolutionStart = Date.now();
|
|
3332
|
+
const fileReadErrors = [];
|
|
3333
|
+
const resolved = await readFinishFiles(
|
|
3334
|
+
repoRoot,
|
|
3335
|
+
finishMeta.files,
|
|
3336
|
+
async (p, s, e) => {
|
|
3337
|
+
try {
|
|
3338
|
+
const rr = await provider.read({ path: p, start: s, end: e });
|
|
3339
|
+
return rr.lines.map((l) => {
|
|
3340
|
+
const idx = l.indexOf("|");
|
|
3341
|
+
return idx >= 0 ? l.slice(idx + 1) : l;
|
|
3277
3342
|
});
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
}
|
|
3343
|
+
} catch (err) {
|
|
3344
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
3345
|
+
fileReadErrors.push({ path: p, error: errorMsg });
|
|
3346
|
+
console.error(`[warp_grep] Failed to read file: ${p} - ${errorMsg}`);
|
|
3347
|
+
return [`[couldn't find: ${p}]`];
|
|
3281
3348
|
}
|
|
3282
3349
|
}
|
|
3283
|
-
|
|
3284
|
-
|
|
3350
|
+
);
|
|
3351
|
+
timings.finish_resolution_ms = Date.now() - finishResolutionStart;
|
|
3352
|
+
if (fileReadErrors.length > 0) {
|
|
3353
|
+
errors.push(...fileReadErrors.map((e) => ({ message: `File read error: ${e.path} - ${e.error}` })));
|
|
3285
3354
|
}
|
|
3286
|
-
|
|
3355
|
+
timings.total_ms = Date.now() - totalStart;
|
|
3356
|
+
return {
|
|
3357
|
+
terminationReason: "completed",
|
|
3358
|
+
messages,
|
|
3359
|
+
finish: { payload, metadata: finishMeta, resolved },
|
|
3360
|
+
timings
|
|
3361
|
+
};
|
|
3362
|
+
}
|
|
3287
3363
|
|
|
3288
3364
|
// tools/warp_grep/providers/remote.ts
|
|
3289
|
-
|
|
3365
|
+
init_config();
|
|
3366
|
+
var SKIP_NAMES = /* @__PURE__ */ new Set([
|
|
3290
3367
|
// Version control
|
|
3291
3368
|
".git",
|
|
3292
3369
|
".svn",
|
|
@@ -3351,7 +3428,7 @@ var SKIP_NAMES2 = /* @__PURE__ */ new Set([
|
|
|
3351
3428
|
"Gemfile.lock",
|
|
3352
3429
|
"poetry.lock"
|
|
3353
3430
|
]);
|
|
3354
|
-
var
|
|
3431
|
+
var SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3355
3432
|
".min.js",
|
|
3356
3433
|
".min.css",
|
|
3357
3434
|
".bundle.js",
|
|
@@ -3362,10 +3439,10 @@ var SKIP_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
|
3362
3439
|
".map",
|
|
3363
3440
|
".js.map"
|
|
3364
3441
|
]);
|
|
3365
|
-
function
|
|
3366
|
-
if (
|
|
3442
|
+
function shouldSkip(name) {
|
|
3443
|
+
if (SKIP_NAMES.has(name)) return true;
|
|
3367
3444
|
if (name.startsWith(".")) return true;
|
|
3368
|
-
for (const ext of
|
|
3445
|
+
for (const ext of SKIP_EXTENSIONS) {
|
|
3369
3446
|
if (name.endsWith(ext)) return true;
|
|
3370
3447
|
}
|
|
3371
3448
|
return false;
|
|
@@ -3441,7 +3518,7 @@ var RemoteCommandsProvider = class {
|
|
|
3441
3518
|
for (const fullPath of paths) {
|
|
3442
3519
|
if (fullPath === params.path || fullPath === this.repoRoot) continue;
|
|
3443
3520
|
const name = fullPath.split("/").pop() || "";
|
|
3444
|
-
if (
|
|
3521
|
+
if (shouldSkip(name)) continue;
|
|
3445
3522
|
if (regex && !regex.test(name)) continue;
|
|
3446
3523
|
let relativePath = fullPath;
|
|
3447
3524
|
if (fullPath.startsWith(this.repoRoot)) {
|
|
@@ -3466,6 +3543,10 @@ var RemoteCommandsProvider = class {
|
|
|
3466
3543
|
};
|
|
3467
3544
|
|
|
3468
3545
|
// tools/warp_grep/client.ts
|
|
3546
|
+
async function getLocalProvider(repoRoot, excludes) {
|
|
3547
|
+
const { LocalRipgrepProvider: LocalRipgrepProvider2 } = await Promise.resolve().then(() => (init_local(), local_exports));
|
|
3548
|
+
return new LocalRipgrepProvider2(repoRoot, excludes);
|
|
3549
|
+
}
|
|
3469
3550
|
var WarpGrepClient = class {
|
|
3470
3551
|
config;
|
|
3471
3552
|
constructor(config = {}) {
|
|
@@ -3498,7 +3579,7 @@ var WarpGrepClient = class {
|
|
|
3498
3579
|
};
|
|
3499
3580
|
async function executeToolCall(input, config) {
|
|
3500
3581
|
const parsed = typeof input === "string" ? JSON.parse(input) : input;
|
|
3501
|
-
const provider = config.remoteCommands ? new RemoteCommandsProvider(config.repoRoot, config.remoteCommands) : config.provider ??
|
|
3582
|
+
const provider = config.remoteCommands ? new RemoteCommandsProvider(config.repoRoot, config.remoteCommands) : config.provider ?? await getLocalProvider(config.repoRoot, config.excludes);
|
|
3502
3583
|
const result = await runWarpGrep({
|
|
3503
3584
|
query: parsed.query,
|
|
3504
3585
|
repoRoot: config.repoRoot,
|
|
@@ -3534,7 +3615,7 @@ function processAgentResult(result) {
|
|
|
3534
3615
|
}
|
|
3535
3616
|
async function* executeToolCallStreaming(input, config) {
|
|
3536
3617
|
const parsed = typeof input === "string" ? JSON.parse(input) : input;
|
|
3537
|
-
const provider = config.remoteCommands ? new RemoteCommandsProvider(config.repoRoot, config.remoteCommands) : config.provider ??
|
|
3618
|
+
const provider = config.remoteCommands ? new RemoteCommandsProvider(config.repoRoot, config.remoteCommands) : config.provider ?? await getLocalProvider(config.repoRoot, config.excludes);
|
|
3538
3619
|
const generator = runWarpGrepStreaming({
|
|
3539
3620
|
query: parsed.query,
|
|
3540
3621
|
repoRoot: config.repoRoot,
|
|
@@ -4126,6 +4207,7 @@ var import_isomorphic_git2 = __toESM(require("isomorphic-git"), 1);
|
|
|
4126
4207
|
var import_node2 = __toESM(require("isomorphic-git/http/node"), 1);
|
|
4127
4208
|
|
|
4128
4209
|
// modelrouter/core.ts
|
|
4210
|
+
init_resilience();
|
|
4129
4211
|
var DEFAULT_CONFIG3 = {
|
|
4130
4212
|
apiUrl: "https://api.morphllm.com",
|
|
4131
4213
|
timeout: 5e3,
|
|
@@ -4150,7 +4232,7 @@ var BaseRouter = class {
|
|
|
4150
4232
|
*/
|
|
4151
4233
|
async selectModel(input) {
|
|
4152
4234
|
const mode = input.mode || "balanced";
|
|
4153
|
-
const apiKey = this.config.apiKey || process.env
|
|
4235
|
+
const apiKey = this.config.apiKey || (typeof process !== "undefined" ? process.env?.MORPH_API_KEY : void 0);
|
|
4154
4236
|
if (!apiKey) {
|
|
4155
4237
|
throw new Error(
|
|
4156
4238
|
"Morph API key is required. Set MORPH_API_KEY environment variable or pass apiKey in config."
|
|
@@ -4261,7 +4343,7 @@ var RawRouter = class extends BaseRouter {
|
|
|
4261
4343
|
*/
|
|
4262
4344
|
async classify(input) {
|
|
4263
4345
|
const mode = input.mode || "balanced";
|
|
4264
|
-
const apiKey = this.config.apiKey || process.env
|
|
4346
|
+
const apiKey = this.config.apiKey || (typeof process !== "undefined" ? process.env?.MORPH_API_KEY : void 0);
|
|
4265
4347
|
if (!apiKey) {
|
|
4266
4348
|
throw new Error(
|
|
4267
4349
|
"Morph API key is required. Set MORPH_API_KEY environment variable or pass apiKey in config."
|
|
@@ -5060,6 +5142,17 @@ var MorphClient = class {
|
|
|
5060
5142
|
|
|
5061
5143
|
// tools/warp_grep/index.ts
|
|
5062
5144
|
var import_zod4 = require("zod");
|
|
5145
|
+
|
|
5146
|
+
// tools/warp_grep/providers/index.ts
|
|
5147
|
+
init_local();
|
|
5148
|
+
|
|
5149
|
+
// tools/warp_grep/index.ts
|
|
5150
|
+
init_paths();
|
|
5151
|
+
|
|
5152
|
+
// tools/warp_grep/agent/index.ts
|
|
5153
|
+
init_config();
|
|
5154
|
+
|
|
5155
|
+
// tools/warp_grep/index.ts
|
|
5063
5156
|
var warpGrepInputSchema = import_zod4.z.object({
|
|
5064
5157
|
query: import_zod4.z.string().describe("Free-form repository question")
|
|
5065
5158
|
});
|