@morphllm/morphsdk 0.2.93 → 0.2.95
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-2AMEQAO2.js +46 -0
- package/dist/chunk-2AMEQAO2.js.map +1 -0
- package/dist/{chunk-EI4UKP24.js → chunk-2HMEZZKK.js} +2 -2
- package/dist/{chunk-EI4UKP24.js.map → chunk-2HMEZZKK.js.map} +1 -1
- package/dist/chunk-2VERUKO2.js +177 -0
- package/dist/chunk-2VERUKO2.js.map +1 -0
- package/dist/{chunk-VHOWYK66.js → chunk-43LQLGP6.js} +23 -33
- package/dist/chunk-43LQLGP6.js.map +1 -0
- package/dist/{chunk-LMUZ3NGC.js → chunk-73RV6EXR.js} +2 -2
- package/dist/{chunk-PBLPZ6AU.js → chunk-7D6TXC7X.js} +2 -2
- package/dist/{chunk-GU6DACME.js → chunk-O7LDZA52.js} +2 -2
- package/dist/{chunk-5QIWYEHJ.js → chunk-PE4KGDA6.js} +1 -8
- package/dist/chunk-PE4KGDA6.js.map +1 -0
- package/dist/{chunk-SQN4DUQS.js → chunk-Q6Y4R236.js} +26 -2
- package/dist/chunk-Q6Y4R236.js.map +1 -0
- package/dist/{chunk-PUGIOVSP.js → chunk-QAT5UVPX.js} +2 -2
- package/dist/{chunk-MIIJWDOQ.js → chunk-QJP62BXH.js} +166 -71
- package/dist/chunk-QJP62BXH.js.map +1 -0
- package/dist/{chunk-EYGBUH2R.js → chunk-R7IQWNSA.js} +8 -8
- package/dist/chunk-R7IQWNSA.js.map +1 -0
- package/dist/chunk-SI2CKRKJ.js +389 -0
- package/dist/chunk-SI2CKRKJ.js.map +1 -0
- package/dist/{chunk-4WLGDYWQ.js → chunk-TSENDJQI.js} +6 -6
- package/dist/chunk-TSENDJQI.js.map +1 -0
- package/dist/{chunk-IUG2FHNN.js → chunk-XH7P7HVT.js} +1 -8
- package/dist/chunk-XH7P7HVT.js.map +1 -0
- package/dist/{chunk-FNLNDMIX.js → chunk-YZ5NCWO2.js} +6 -6
- package/dist/chunk-YZ5NCWO2.js.map +1 -0
- package/dist/{chunk-IJ54DTJ3.js → chunk-ZYTAKEBW.js} +13 -13
- package/dist/client.cjs +770 -110
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.ts +2 -0
- package/dist/client.js +16 -13
- package/dist/index.cjs +770 -110
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +16 -13
- package/dist/tools/browser/anthropic.cjs +58 -23
- package/dist/tools/browser/anthropic.cjs.map +1 -1
- package/dist/tools/browser/anthropic.js +7 -4
- package/dist/tools/browser/core.cjs +750 -70
- package/dist/tools/browser/core.cjs.map +1 -1
- package/dist/tools/browser/core.d.ts +30 -24
- package/dist/tools/browser/core.js +5 -2
- package/dist/tools/browser/errors.cjs +208 -0
- package/dist/tools/browser/errors.cjs.map +1 -0
- package/dist/tools/browser/errors.d.ts +158 -0
- package/dist/tools/browser/errors.js +22 -0
- package/dist/tools/browser/errors.js.map +1 -0
- package/dist/tools/browser/index.cjs +783 -85
- package/dist/tools/browser/index.cjs.map +1 -1
- package/dist/tools/browser/index.d.ts +5 -2
- package/dist/tools/browser/index.js +32 -9
- package/dist/tools/browser/index.js.map +1 -1
- package/dist/tools/browser/live.cjs +25 -1
- package/dist/tools/browser/live.cjs.map +1 -1
- package/dist/tools/browser/live.js +1 -1
- package/dist/tools/browser/openai.cjs +58 -23
- package/dist/tools/browser/openai.cjs.map +1 -1
- package/dist/tools/browser/openai.js +7 -4
- package/dist/tools/browser/profiles/core.cjs +670 -0
- package/dist/tools/browser/profiles/core.cjs.map +1 -0
- package/dist/tools/browser/profiles/core.d.ts +187 -0
- package/dist/tools/browser/profiles/core.js +29 -0
- package/dist/tools/browser/profiles/core.js.map +1 -0
- package/dist/tools/browser/profiles/index.cjs +670 -0
- package/dist/tools/browser/profiles/index.cjs.map +1 -0
- package/dist/tools/browser/profiles/index.d.ts +4 -0
- package/dist/tools/browser/profiles/index.js +29 -0
- package/dist/tools/browser/profiles/index.js.map +1 -0
- package/dist/tools/browser/profiles/types.cjs +74 -0
- package/dist/tools/browser/profiles/types.cjs.map +1 -0
- package/dist/tools/browser/profiles/types.d.ts +195 -0
- package/dist/tools/browser/profiles/types.js +16 -0
- package/dist/tools/browser/profiles/types.js.map +1 -0
- package/dist/tools/browser/prompts.cjs +1 -1
- package/dist/tools/browser/prompts.cjs.map +1 -1
- package/dist/tools/browser/prompts.d.ts +1 -1
- package/dist/tools/browser/prompts.js +1 -1
- package/dist/tools/browser/types.cjs.map +1 -1
- package/dist/tools/browser/types.d.ts +55 -51
- package/dist/tools/browser/vercel.cjs +60 -25
- package/dist/tools/browser/vercel.cjs.map +1 -1
- package/dist/tools/browser/vercel.d.ts +1 -1
- package/dist/tools/browser/vercel.js +7 -4
- package/dist/tools/fastapply/anthropic.cjs +0 -7
- package/dist/tools/fastapply/anthropic.cjs.map +1 -1
- package/dist/tools/fastapply/anthropic.js +1 -1
- package/dist/tools/fastapply/index.cjs +0 -14
- package/dist/tools/fastapply/index.cjs.map +1 -1
- package/dist/tools/fastapply/index.js +5 -5
- package/dist/tools/fastapply/openai.cjs +0 -7
- package/dist/tools/fastapply/openai.cjs.map +1 -1
- package/dist/tools/fastapply/openai.js +1 -1
- package/dist/tools/index.cjs +0 -14
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.js +5 -5
- package/dist/tools/warp_grep/agent/runner.cjs +18 -98
- package/dist/tools/warp_grep/agent/runner.cjs.map +1 -1
- package/dist/tools/warp_grep/agent/runner.js +2 -3
- package/dist/tools/warp_grep/anthropic.cjs +18 -98
- package/dist/tools/warp_grep/anthropic.cjs.map +1 -1
- package/dist/tools/warp_grep/anthropic.js +8 -9
- package/dist/tools/warp_grep/client.cjs +18 -98
- package/dist/tools/warp_grep/client.cjs.map +1 -1
- package/dist/tools/warp_grep/client.js +5 -6
- package/dist/tools/warp_grep/gemini.cjs +18 -98
- package/dist/tools/warp_grep/gemini.cjs.map +1 -1
- package/dist/tools/warp_grep/gemini.js +7 -8
- package/dist/tools/warp_grep/gemini.js.map +1 -1
- package/dist/tools/warp_grep/harness.js +10 -10
- package/dist/tools/warp_grep/index.cjs +18 -98
- package/dist/tools/warp_grep/index.cjs.map +1 -1
- package/dist/tools/warp_grep/index.js +8 -9
- package/dist/tools/warp_grep/openai.cjs +18 -98
- package/dist/tools/warp_grep/openai.cjs.map +1 -1
- package/dist/tools/warp_grep/openai.js +8 -9
- package/dist/tools/warp_grep/vercel.cjs +18 -98
- package/dist/tools/warp_grep/vercel.cjs.map +1 -1
- package/dist/tools/warp_grep/vercel.js +8 -9
- package/dist/{vercel-CsnNSdze.d.ts → vercel-CVF27qFK.d.ts} +10 -10
- package/package.json +7 -2
- package/dist/chunk-4WLGDYWQ.js.map +0 -1
- package/dist/chunk-5QIWYEHJ.js.map +0 -1
- package/dist/chunk-EYGBUH2R.js.map +0 -1
- package/dist/chunk-FNLNDMIX.js.map +0 -1
- package/dist/chunk-IUG2FHNN.js.map +0 -1
- package/dist/chunk-MIIJWDOQ.js.map +0 -1
- package/dist/chunk-SQN4DUQS.js.map +0 -1
- package/dist/chunk-VHOWYK66.js.map +0 -1
- /package/dist/{chunk-LMUZ3NGC.js.map → chunk-73RV6EXR.js.map} +0 -0
- /package/dist/{chunk-PBLPZ6AU.js.map → chunk-7D6TXC7X.js.map} +0 -0
- /package/dist/{chunk-GU6DACME.js.map → chunk-O7LDZA52.js.map} +0 -0
- /package/dist/{chunk-PUGIOVSP.js.map → chunk-QAT5UVPX.js.map} +0 -0
- /package/dist/{chunk-IJ54DTJ3.js.map → chunk-ZYTAKEBW.js.map} +0 -0
|
@@ -24,6 +24,14 @@ __export(browser_exports, {
|
|
|
24
24
|
BROWSER_TOOL_DESCRIPTION: () => BROWSER_TOOL_DESCRIPTION,
|
|
25
25
|
BrowserClient: () => BrowserClient,
|
|
26
26
|
LIVE_PRESETS: () => LIVE_PRESETS,
|
|
27
|
+
MorphAPIError: () => MorphAPIError,
|
|
28
|
+
MorphAuthenticationError: () => MorphAuthenticationError,
|
|
29
|
+
MorphError: () => MorphError,
|
|
30
|
+
MorphNotFoundError: () => MorphNotFoundError,
|
|
31
|
+
MorphProfileLimitError: () => MorphProfileLimitError,
|
|
32
|
+
MorphRateLimitError: () => MorphRateLimitError,
|
|
33
|
+
MorphValidationError: () => MorphValidationError,
|
|
34
|
+
ProfilesClient: () => ProfilesClient,
|
|
27
35
|
anthropic: () => anthropic_exports,
|
|
28
36
|
buildEmbedCode: () => buildEmbedCode,
|
|
29
37
|
buildLiveIframe: () => buildLiveIframe,
|
|
@@ -36,6 +44,7 @@ __export(browser_exports, {
|
|
|
36
44
|
getRecording: () => getRecording,
|
|
37
45
|
getWebp: () => getWebp,
|
|
38
46
|
openai: () => openai_exports,
|
|
47
|
+
parseAPIError: () => parseAPIError,
|
|
39
48
|
vercel: () => vercel_exports,
|
|
40
49
|
waitForRecording: () => waitForRecording
|
|
41
50
|
});
|
|
@@ -130,7 +139,8 @@ function buildLiveUrl(debugUrl, options = {}) {
|
|
|
130
139
|
"debugUrl is required. Ensure your backend returns debugUrl in the task response. Contact support@morphllm.com if you need help."
|
|
131
140
|
);
|
|
132
141
|
}
|
|
133
|
-
const
|
|
142
|
+
const normalized = normalizeLiveUrl(debugUrl);
|
|
143
|
+
const url = new URL(normalized);
|
|
134
144
|
if (options.interactive !== void 0) {
|
|
135
145
|
url.searchParams.set("interactive", String(options.interactive));
|
|
136
146
|
}
|
|
@@ -148,6 +158,29 @@ function buildLiveUrl(debugUrl, options = {}) {
|
|
|
148
158
|
}
|
|
149
159
|
return url.toString();
|
|
150
160
|
}
|
|
161
|
+
function normalizeLiveUrl(debugUrl) {
|
|
162
|
+
const trimmed = debugUrl.trim();
|
|
163
|
+
if (!trimmed) {
|
|
164
|
+
return trimmed;
|
|
165
|
+
}
|
|
166
|
+
if (trimmed.startsWith("wss://") || trimmed.startsWith("ws://")) {
|
|
167
|
+
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
168
|
+
}
|
|
169
|
+
let url;
|
|
170
|
+
try {
|
|
171
|
+
url = new URL(trimmed);
|
|
172
|
+
} catch {
|
|
173
|
+
return trimmed;
|
|
174
|
+
}
|
|
175
|
+
if (url.protocol === "wss:" || url.protocol === "ws:") {
|
|
176
|
+
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
177
|
+
}
|
|
178
|
+
const wssParam = url.searchParams.get("wss");
|
|
179
|
+
if (wssParam && (wssParam.startsWith("wss://") || wssParam.startsWith("ws://"))) {
|
|
180
|
+
url.searchParams.set("wss", wssParam);
|
|
181
|
+
}
|
|
182
|
+
return url.toString();
|
|
183
|
+
}
|
|
151
184
|
function buildLiveIframe(debugUrl, options = {}) {
|
|
152
185
|
const {
|
|
153
186
|
width = "100%",
|
|
@@ -191,6 +224,570 @@ function resolvePreset(optionsOrPreset) {
|
|
|
191
224
|
return optionsOrPreset;
|
|
192
225
|
}
|
|
193
226
|
|
|
227
|
+
// tools/browser/errors.ts
|
|
228
|
+
var MorphError = class extends Error {
|
|
229
|
+
/** Error code for programmatic handling */
|
|
230
|
+
code;
|
|
231
|
+
/** Original cause of the error, if any */
|
|
232
|
+
cause;
|
|
233
|
+
constructor(message, code, cause) {
|
|
234
|
+
super(message);
|
|
235
|
+
this.name = "MorphError";
|
|
236
|
+
this.code = code;
|
|
237
|
+
this.cause = cause;
|
|
238
|
+
if (Error.captureStackTrace) {
|
|
239
|
+
Error.captureStackTrace(this, this.constructor);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Returns a JSON representation of the error for logging.
|
|
244
|
+
*/
|
|
245
|
+
toJSON() {
|
|
246
|
+
return {
|
|
247
|
+
name: this.name,
|
|
248
|
+
message: this.message,
|
|
249
|
+
code: this.code,
|
|
250
|
+
cause: this.cause?.message
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
var MorphValidationError = class extends MorphError {
|
|
255
|
+
/** The field that failed validation */
|
|
256
|
+
field;
|
|
257
|
+
constructor(message, field) {
|
|
258
|
+
super(message, "validation_error");
|
|
259
|
+
this.name = "MorphValidationError";
|
|
260
|
+
this.field = field;
|
|
261
|
+
}
|
|
262
|
+
toJSON() {
|
|
263
|
+
return {
|
|
264
|
+
...super.toJSON(),
|
|
265
|
+
field: this.field
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
var MorphAPIError = class extends MorphError {
|
|
270
|
+
/** HTTP status code */
|
|
271
|
+
statusCode;
|
|
272
|
+
/** Request ID for debugging (if available) */
|
|
273
|
+
requestId;
|
|
274
|
+
/** Raw response body */
|
|
275
|
+
rawResponse;
|
|
276
|
+
constructor(message, code, statusCode, options) {
|
|
277
|
+
super(message, code, options?.cause);
|
|
278
|
+
this.name = "MorphAPIError";
|
|
279
|
+
this.statusCode = statusCode;
|
|
280
|
+
this.requestId = options?.requestId;
|
|
281
|
+
this.rawResponse = options?.rawResponse;
|
|
282
|
+
}
|
|
283
|
+
toJSON() {
|
|
284
|
+
return {
|
|
285
|
+
...super.toJSON(),
|
|
286
|
+
statusCode: this.statusCode,
|
|
287
|
+
requestId: this.requestId
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
var MorphAuthenticationError = class extends MorphAPIError {
|
|
292
|
+
constructor(message = "Authentication required. Please provide a valid API key.") {
|
|
293
|
+
super(message, "authentication_required", 401);
|
|
294
|
+
this.name = "MorphAuthenticationError";
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
var MorphRateLimitError = class extends MorphAPIError {
|
|
298
|
+
/** When the rate limit resets (Unix timestamp) */
|
|
299
|
+
resetAt;
|
|
300
|
+
/** Number of seconds until reset */
|
|
301
|
+
retryAfter;
|
|
302
|
+
constructor(message = "Rate limit exceeded. Please retry later.", options) {
|
|
303
|
+
super(message, "rate_limit_exceeded", 429, { requestId: options?.requestId });
|
|
304
|
+
this.name = "MorphRateLimitError";
|
|
305
|
+
this.resetAt = options?.resetAt;
|
|
306
|
+
this.retryAfter = options?.retryAfter;
|
|
307
|
+
}
|
|
308
|
+
toJSON() {
|
|
309
|
+
return {
|
|
310
|
+
...super.toJSON(),
|
|
311
|
+
resetAt: this.resetAt,
|
|
312
|
+
retryAfter: this.retryAfter
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
var MorphNotFoundError = class extends MorphAPIError {
|
|
317
|
+
/** The type of resource that was not found */
|
|
318
|
+
resourceType;
|
|
319
|
+
/** The ID of the resource that was not found */
|
|
320
|
+
resourceId;
|
|
321
|
+
constructor(resourceType, resourceId) {
|
|
322
|
+
const message = resourceId ? `${resourceType} '${resourceId}' not found` : `${resourceType} not found`;
|
|
323
|
+
super(message, "resource_not_found", 404);
|
|
324
|
+
this.name = "MorphNotFoundError";
|
|
325
|
+
this.resourceType = resourceType;
|
|
326
|
+
this.resourceId = resourceId;
|
|
327
|
+
}
|
|
328
|
+
toJSON() {
|
|
329
|
+
return {
|
|
330
|
+
...super.toJSON(),
|
|
331
|
+
resourceType: this.resourceType,
|
|
332
|
+
resourceId: this.resourceId
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
var MorphProfileLimitError = class extends MorphAPIError {
|
|
337
|
+
/** Current number of profiles */
|
|
338
|
+
currentCount;
|
|
339
|
+
/** Maximum allowed profiles for the plan */
|
|
340
|
+
maxAllowed;
|
|
341
|
+
constructor(message = "Profile limit exceeded for your plan.", options) {
|
|
342
|
+
super(message, "profile_limit_exceeded", 403, { requestId: options?.requestId });
|
|
343
|
+
this.name = "MorphProfileLimitError";
|
|
344
|
+
this.currentCount = options?.currentCount;
|
|
345
|
+
this.maxAllowed = options?.maxAllowed;
|
|
346
|
+
}
|
|
347
|
+
toJSON() {
|
|
348
|
+
return {
|
|
349
|
+
...super.toJSON(),
|
|
350
|
+
currentCount: this.currentCount,
|
|
351
|
+
maxAllowed: this.maxAllowed
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
function parseAPIError(statusCode, responseText, requestId) {
|
|
356
|
+
let errorData = {};
|
|
357
|
+
try {
|
|
358
|
+
errorData = JSON.parse(responseText);
|
|
359
|
+
} catch {
|
|
360
|
+
}
|
|
361
|
+
const message = errorData.detail || errorData.message || responseText || "Unknown error";
|
|
362
|
+
const code = errorData.code;
|
|
363
|
+
switch (statusCode) {
|
|
364
|
+
case 401:
|
|
365
|
+
return new MorphAuthenticationError(message);
|
|
366
|
+
case 403:
|
|
367
|
+
if (code === "profile_limit_exceeded" || message.toLowerCase().includes("limit")) {
|
|
368
|
+
return new MorphProfileLimitError(message, { requestId });
|
|
369
|
+
}
|
|
370
|
+
return new MorphAPIError(message, "insufficient_permissions", statusCode, { requestId, rawResponse: responseText });
|
|
371
|
+
case 404:
|
|
372
|
+
if (message.toLowerCase().includes("profile")) {
|
|
373
|
+
return new MorphNotFoundError("Profile", void 0);
|
|
374
|
+
}
|
|
375
|
+
if (message.toLowerCase().includes("session")) {
|
|
376
|
+
return new MorphNotFoundError("Session", void 0);
|
|
377
|
+
}
|
|
378
|
+
return new MorphAPIError(message, "resource_not_found", statusCode, { requestId, rawResponse: responseText });
|
|
379
|
+
case 429:
|
|
380
|
+
return new MorphRateLimitError(message, { requestId });
|
|
381
|
+
case 422:
|
|
382
|
+
return new MorphAPIError(message, "validation_error", statusCode, { requestId, rawResponse: responseText });
|
|
383
|
+
case 500:
|
|
384
|
+
case 502:
|
|
385
|
+
case 503:
|
|
386
|
+
case 504:
|
|
387
|
+
return new MorphAPIError(message, "service_unavailable", statusCode, { requestId, rawResponse: responseText });
|
|
388
|
+
default:
|
|
389
|
+
return new MorphAPIError(message, "network_error", statusCode, { requestId, rawResponse: responseText });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// tools/browser/profiles/types.ts
|
|
394
|
+
function transformProfile(api) {
|
|
395
|
+
return {
|
|
396
|
+
id: api.id,
|
|
397
|
+
name: api.name,
|
|
398
|
+
repoId: api.repo_id,
|
|
399
|
+
cookieDomains: api.cookie_domains,
|
|
400
|
+
lastUsedAt: api.last_used_at,
|
|
401
|
+
createdAt: api.created_at,
|
|
402
|
+
updatedAt: api.updated_at
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function transformCreateInput(input) {
|
|
406
|
+
return {
|
|
407
|
+
name: input.name,
|
|
408
|
+
repo_id: input.repoId
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function transformSession(api) {
|
|
412
|
+
return {
|
|
413
|
+
sessionId: api.session_id,
|
|
414
|
+
debugUrl: api.debug_url || ""
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function transformSaveInput(input) {
|
|
418
|
+
return {
|
|
419
|
+
session_id: input.sessionId,
|
|
420
|
+
profile_id: input.profileId
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function transformStateResponse(api) {
|
|
424
|
+
return {
|
|
425
|
+
profileId: api.profile_id,
|
|
426
|
+
stateUrl: api.state_url,
|
|
427
|
+
expiresIn: api.expires_in
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// tools/browser/profiles/core.ts
|
|
432
|
+
var DEFAULT_API_URL = process.env.MORPH_ENVIRONMENT === "DEV" ? "http://localhost:8000" : "https://browser.morphllm.com";
|
|
433
|
+
var ProfilesClient = class {
|
|
434
|
+
config;
|
|
435
|
+
constructor(config) {
|
|
436
|
+
this.config = config;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Create a new browser profile and immediately start a live session.
|
|
440
|
+
*
|
|
441
|
+
* @param input - Profile creation parameters
|
|
442
|
+
* @returns Profile setup handle with live URL + save()
|
|
443
|
+
* @throws {MorphValidationError} If input validation fails
|
|
444
|
+
* @throws {MorphProfileLimitError} If profile limit is exceeded
|
|
445
|
+
* @throws {MorphAuthenticationError} If API key is missing or invalid
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* ```typescript
|
|
449
|
+
* const setup = await morph.browser.profiles.createProfile({
|
|
450
|
+
* name: 'LinkedIn Production',
|
|
451
|
+
* repoId: 'owner/repo'
|
|
452
|
+
* });
|
|
453
|
+
* console.log(setup.session.debugUrl);
|
|
454
|
+
* await setup.save();
|
|
455
|
+
* ```
|
|
456
|
+
*/
|
|
457
|
+
async createProfile(input) {
|
|
458
|
+
return createProfile(input, this.config);
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* List all profiles for the authenticated user.
|
|
462
|
+
*
|
|
463
|
+
* @param repoId - Optional repository ID to filter by
|
|
464
|
+
* @returns Array of profiles
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* ```typescript
|
|
468
|
+
* // List all profiles
|
|
469
|
+
* const allProfiles = await morph.browser.profiles.listProfiles();
|
|
470
|
+
*
|
|
471
|
+
* // List profiles for a specific repo
|
|
472
|
+
* const repoProfiles = await morph.browser.profiles.listProfiles('owner/repo');
|
|
473
|
+
* ```
|
|
474
|
+
*/
|
|
475
|
+
async listProfiles(repoId) {
|
|
476
|
+
return listProfiles(this.config, repoId);
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Get a profile by ID with convenience methods.
|
|
480
|
+
*
|
|
481
|
+
* @param id - Profile ID
|
|
482
|
+
* @returns Profile with attached methods
|
|
483
|
+
* @throws {MorphNotFoundError} If profile is not found
|
|
484
|
+
*
|
|
485
|
+
* @example
|
|
486
|
+
* ```typescript
|
|
487
|
+
* const profile = await morph.browser.profiles.getProfile('profile-id');
|
|
488
|
+
* const state = await profile.getState();
|
|
489
|
+
* await profile.delete();
|
|
490
|
+
* ```
|
|
491
|
+
*/
|
|
492
|
+
async getProfile(id) {
|
|
493
|
+
return getProfile(id, this.config);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Update a profile by opening a live session (no rename).
|
|
497
|
+
*
|
|
498
|
+
* @param id - Profile ID
|
|
499
|
+
* @returns Profile setup handle with live URL + save()
|
|
500
|
+
*/
|
|
501
|
+
async updateProfile(id) {
|
|
502
|
+
return updateProfile(id, this.config);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Delete a profile.
|
|
506
|
+
*
|
|
507
|
+
* @param id - Profile ID
|
|
508
|
+
* @throws {MorphNotFoundError} If profile is not found
|
|
509
|
+
*/
|
|
510
|
+
async deleteProfile(id) {
|
|
511
|
+
return deleteProfile(id, this.config);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Start a browser session for profile setup.
|
|
515
|
+
*
|
|
516
|
+
* Returns a live URL where the user can sign into accounts.
|
|
517
|
+
* After signing in, call `saveSession` to persist the state.
|
|
518
|
+
*
|
|
519
|
+
* @param input - Optional session parameters
|
|
520
|
+
* @returns Session with debug URL
|
|
521
|
+
*
|
|
522
|
+
* @example
|
|
523
|
+
* ```typescript
|
|
524
|
+
* const session = await morph.browser.profiles.startSession();
|
|
525
|
+
* console.log('Sign in at:', session.debugUrl);
|
|
526
|
+
* // Open debugUrl in browser, user signs in...
|
|
527
|
+
* await morph.browser.profiles.saveSession(session.sessionId, profile.id);
|
|
528
|
+
* ```
|
|
529
|
+
*/
|
|
530
|
+
async startSession(input) {
|
|
531
|
+
return startProfileSession(this.config, input);
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Save browser state from a session to a profile.
|
|
535
|
+
*
|
|
536
|
+
* Call this after the user is done signing into accounts.
|
|
537
|
+
* Extracts cookies, localStorage, and sessionStorage.
|
|
538
|
+
*
|
|
539
|
+
* @param sessionId - Browser session ID from startSession
|
|
540
|
+
* @param profileId - Profile ID to save state to
|
|
541
|
+
* @returns Updated profile with cookie domains
|
|
542
|
+
*/
|
|
543
|
+
async saveSession(sessionId, profileId) {
|
|
544
|
+
return saveProfileSession({ sessionId, profileId }, this.config);
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* List available repo IDs (discovery).
|
|
548
|
+
*
|
|
549
|
+
* @returns Repo summaries with profile counts
|
|
550
|
+
*/
|
|
551
|
+
async listRepos() {
|
|
552
|
+
return listRepos(this.config);
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Get the presigned URL for a profile's state.
|
|
556
|
+
*
|
|
557
|
+
* Use this to download the raw state JSON for debugging
|
|
558
|
+
* or to restore state manually.
|
|
559
|
+
*
|
|
560
|
+
* @param profileId - Profile ID
|
|
561
|
+
* @returns State URL with expiry information
|
|
562
|
+
*/
|
|
563
|
+
async getProfileState(profileId) {
|
|
564
|
+
return getProfileState(profileId, this.config);
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
function validateCreateInput(input) {
|
|
568
|
+
if (!input.name || typeof input.name !== "string") {
|
|
569
|
+
throw new MorphValidationError("name is required", "name");
|
|
570
|
+
}
|
|
571
|
+
const trimmedName = input.name.trim();
|
|
572
|
+
if (trimmedName.length === 0) {
|
|
573
|
+
throw new MorphValidationError("name cannot be empty", "name");
|
|
574
|
+
}
|
|
575
|
+
if (trimmedName.length > 100) {
|
|
576
|
+
throw new MorphValidationError("name must be 100 characters or less", "name");
|
|
577
|
+
}
|
|
578
|
+
if (!input.repoId || typeof input.repoId !== "string") {
|
|
579
|
+
throw new MorphValidationError("repoId is required", "repoId");
|
|
580
|
+
}
|
|
581
|
+
if (input.repoId.trim().length === 0) {
|
|
582
|
+
throw new MorphValidationError("repoId cannot be empty", "repoId");
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function validateId(id, fieldName) {
|
|
586
|
+
if (!id || typeof id !== "string") {
|
|
587
|
+
throw new MorphValidationError(`${fieldName} is required`, fieldName);
|
|
588
|
+
}
|
|
589
|
+
if (id.trim().length === 0) {
|
|
590
|
+
throw new MorphValidationError(`${fieldName} cannot be empty`, fieldName);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
async function createProfile(input, config = {}) {
|
|
594
|
+
validateCreateInput(input);
|
|
595
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
596
|
+
const headers = buildHeaders(config);
|
|
597
|
+
const response = await fetchWithRetry(
|
|
598
|
+
`${apiUrl}/profiles`,
|
|
599
|
+
{
|
|
600
|
+
method: "POST",
|
|
601
|
+
headers,
|
|
602
|
+
body: JSON.stringify(transformCreateInput(input))
|
|
603
|
+
},
|
|
604
|
+
config.retryConfig
|
|
605
|
+
);
|
|
606
|
+
if (!response.ok) {
|
|
607
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
608
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
609
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
610
|
+
}
|
|
611
|
+
const apiProfile = await response.json();
|
|
612
|
+
const profile = transformProfile(apiProfile);
|
|
613
|
+
const session = await startProfileSession(config, { profileId: profile.id });
|
|
614
|
+
return buildProfileSetup(profile, session, config);
|
|
615
|
+
}
|
|
616
|
+
async function listProfiles(config = {}, repoId) {
|
|
617
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
618
|
+
const headers = buildHeaders(config);
|
|
619
|
+
const url = repoId ? `${apiUrl}/profiles?repo_id=${encodeURIComponent(repoId)}` : `${apiUrl}/profiles`;
|
|
620
|
+
const response = await fetchWithRetry(
|
|
621
|
+
url,
|
|
622
|
+
{ method: "GET", headers },
|
|
623
|
+
config.retryConfig
|
|
624
|
+
);
|
|
625
|
+
if (!response.ok) {
|
|
626
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
627
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
628
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
629
|
+
}
|
|
630
|
+
const data = await response.json();
|
|
631
|
+
return data.profiles.map(transformProfile);
|
|
632
|
+
}
|
|
633
|
+
async function getProfile(id, config = {}) {
|
|
634
|
+
validateId(id, "id");
|
|
635
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
636
|
+
const headers = buildHeaders(config);
|
|
637
|
+
const response = await fetchWithRetry(
|
|
638
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
639
|
+
{ method: "GET", headers },
|
|
640
|
+
config.retryConfig
|
|
641
|
+
);
|
|
642
|
+
if (!response.ok) {
|
|
643
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
644
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
645
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
646
|
+
}
|
|
647
|
+
const apiProfile = await response.json();
|
|
648
|
+
const profile = transformProfile(apiProfile);
|
|
649
|
+
return {
|
|
650
|
+
...profile,
|
|
651
|
+
getState: () => getProfileState(id, config),
|
|
652
|
+
delete: () => deleteProfile(id, config)
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
async function updateProfile(id, config = {}) {
|
|
656
|
+
validateId(id, "id");
|
|
657
|
+
const profile = await fetchProfile(id, config);
|
|
658
|
+
const session = await startProfileSession(config, { profileId: profile.id });
|
|
659
|
+
return buildProfileSetup(profile, session, config);
|
|
660
|
+
}
|
|
661
|
+
async function deleteProfile(id, config = {}) {
|
|
662
|
+
validateId(id, "id");
|
|
663
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
664
|
+
const headers = buildHeaders(config);
|
|
665
|
+
const response = await fetchWithRetry(
|
|
666
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
667
|
+
{ method: "DELETE", headers },
|
|
668
|
+
config.retryConfig
|
|
669
|
+
);
|
|
670
|
+
if (!response.ok) {
|
|
671
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
672
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
673
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
async function startProfileSession(config = {}, input) {
|
|
677
|
+
if (!config.apiKey) {
|
|
678
|
+
throw new MorphAuthenticationError();
|
|
679
|
+
}
|
|
680
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
681
|
+
const headers = buildHeaders(config);
|
|
682
|
+
const body = input?.profileId ? { profile_id: input.profileId } : {};
|
|
683
|
+
const response = await fetchWithRetry(
|
|
684
|
+
`${apiUrl}/profiles/session/start`,
|
|
685
|
+
{
|
|
686
|
+
method: "POST",
|
|
687
|
+
headers,
|
|
688
|
+
body: JSON.stringify(body)
|
|
689
|
+
},
|
|
690
|
+
config.retryConfig
|
|
691
|
+
);
|
|
692
|
+
if (!response.ok) {
|
|
693
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
694
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
695
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
696
|
+
}
|
|
697
|
+
const apiSession = await response.json();
|
|
698
|
+
return transformSession(apiSession);
|
|
699
|
+
}
|
|
700
|
+
async function saveProfileSession(input, config = {}) {
|
|
701
|
+
validateId(input.sessionId, "sessionId");
|
|
702
|
+
validateId(input.profileId, "profileId");
|
|
703
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
704
|
+
const headers = buildHeaders(config);
|
|
705
|
+
const response = await fetchWithRetry(
|
|
706
|
+
`${apiUrl}/profiles/session/save`,
|
|
707
|
+
{
|
|
708
|
+
method: "POST",
|
|
709
|
+
headers,
|
|
710
|
+
body: JSON.stringify(transformSaveInput(input))
|
|
711
|
+
},
|
|
712
|
+
config.retryConfig
|
|
713
|
+
);
|
|
714
|
+
if (!response.ok) {
|
|
715
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
716
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
717
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
718
|
+
}
|
|
719
|
+
const apiProfile = await response.json();
|
|
720
|
+
return transformProfile(apiProfile);
|
|
721
|
+
}
|
|
722
|
+
async function listRepos(config = {}) {
|
|
723
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
724
|
+
const headers = buildHeaders(config);
|
|
725
|
+
const response = await fetchWithRetry(
|
|
726
|
+
`${apiUrl}/repos`,
|
|
727
|
+
{ method: "GET", headers },
|
|
728
|
+
config.retryConfig
|
|
729
|
+
);
|
|
730
|
+
if (!response.ok) {
|
|
731
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
732
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
733
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
734
|
+
}
|
|
735
|
+
const data = await response.json();
|
|
736
|
+
const repos = Array.isArray(data?.repos) ? data.repos : [];
|
|
737
|
+
return repos.map((repo) => ({
|
|
738
|
+
repoId: repo.repo_id,
|
|
739
|
+
repoFullName: repo.repo_full_name,
|
|
740
|
+
profileCount: repo.profile_count ?? 0
|
|
741
|
+
}));
|
|
742
|
+
}
|
|
743
|
+
async function getProfileState(profileId, config = {}) {
|
|
744
|
+
validateId(profileId, "profileId");
|
|
745
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
746
|
+
const headers = buildHeaders(config);
|
|
747
|
+
const response = await fetchWithRetry(
|
|
748
|
+
`${apiUrl}/profiles/${encodeURIComponent(profileId)}/state`,
|
|
749
|
+
{ method: "GET", headers },
|
|
750
|
+
config.retryConfig
|
|
751
|
+
);
|
|
752
|
+
if (!response.ok) {
|
|
753
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
754
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
755
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
756
|
+
}
|
|
757
|
+
const apiState = await response.json();
|
|
758
|
+
return transformStateResponse(apiState);
|
|
759
|
+
}
|
|
760
|
+
function buildHeaders(config) {
|
|
761
|
+
const headers = { "Content-Type": "application/json" };
|
|
762
|
+
if (config.apiKey) {
|
|
763
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
764
|
+
}
|
|
765
|
+
return headers;
|
|
766
|
+
}
|
|
767
|
+
async function fetchProfile(id, config) {
|
|
768
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
769
|
+
const headers = buildHeaders(config);
|
|
770
|
+
const response = await fetchWithRetry(
|
|
771
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
772
|
+
{ method: "GET", headers },
|
|
773
|
+
config.retryConfig
|
|
774
|
+
);
|
|
775
|
+
if (!response.ok) {
|
|
776
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
777
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
778
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
779
|
+
}
|
|
780
|
+
const apiProfile = await response.json();
|
|
781
|
+
return transformProfile(apiProfile);
|
|
782
|
+
}
|
|
783
|
+
function buildProfileSetup(profile, session, config) {
|
|
784
|
+
return {
|
|
785
|
+
profile,
|
|
786
|
+
session,
|
|
787
|
+
save: () => saveProfileSession({ sessionId: session.sessionId, profileId: profile.id }, config)
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
194
791
|
// tools/browser/core.ts
|
|
195
792
|
var DEFAULT_CONFIG = {
|
|
196
793
|
apiUrl: process.env.MORPH_ENVIRONMENT === "DEV" ? "http://localhost:8000" : "https://browser.morphllm.com",
|
|
@@ -200,11 +797,16 @@ var DEFAULT_CONFIG = {
|
|
|
200
797
|
};
|
|
201
798
|
var BrowserClient = class {
|
|
202
799
|
config;
|
|
800
|
+
/**
|
|
801
|
+
* Profile management - create and manage browser profiles for storing login state.
|
|
802
|
+
*/
|
|
803
|
+
profiles;
|
|
203
804
|
constructor(config = {}) {
|
|
204
805
|
this.config = {
|
|
205
806
|
...DEFAULT_CONFIG,
|
|
206
807
|
...config
|
|
207
808
|
};
|
|
809
|
+
this.profiles = new ProfilesClient(this.config);
|
|
208
810
|
}
|
|
209
811
|
/**
|
|
210
812
|
* Execute a browser automation task
|
|
@@ -227,19 +829,21 @@ var BrowserClient = class {
|
|
|
227
829
|
body: JSON.stringify({
|
|
228
830
|
task: input.task,
|
|
229
831
|
url: input.url,
|
|
230
|
-
max_steps: input.
|
|
832
|
+
max_steps: input.maxSteps ?? 10,
|
|
231
833
|
model: input.model ?? "morph-computer-use-v0",
|
|
232
|
-
viewport_width: input.
|
|
233
|
-
viewport_height: input.
|
|
234
|
-
external_id: input.
|
|
235
|
-
repo_id: input.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
834
|
+
viewport_width: input.viewportWidth ?? 1280,
|
|
835
|
+
viewport_height: input.viewportHeight ?? 720,
|
|
836
|
+
external_id: input.externalId,
|
|
837
|
+
repo_id: input.repoId,
|
|
838
|
+
repo_full_name: input.repoFullName,
|
|
839
|
+
commit_id: input.commitId,
|
|
840
|
+
record_video: input.recordVideo ?? false,
|
|
841
|
+
video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
|
|
842
|
+
video_height: input.videoHeight ?? input.viewportHeight ?? 720,
|
|
843
|
+
allow_resizing: input.allowResizing ?? false,
|
|
241
844
|
structured_output: "schema" in input ? stringifyStructuredOutput(input.schema) : void 0,
|
|
242
|
-
auth: input.auth
|
|
845
|
+
auth: input.auth,
|
|
846
|
+
profile_id: input.profileId
|
|
243
847
|
})
|
|
244
848
|
});
|
|
245
849
|
if (!response.ok) {
|
|
@@ -247,9 +851,10 @@ var BrowserClient = class {
|
|
|
247
851
|
if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);
|
|
248
852
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
249
853
|
}
|
|
250
|
-
const result = await response.json();
|
|
854
|
+
const result = mapTaskResult(await response.json());
|
|
251
855
|
if (debug) {
|
|
252
|
-
|
|
856
|
+
const debugUrl = result.debugUrl;
|
|
857
|
+
console.log(`[Browser] \u2705 Task created: recordingId=${result.recordingId ?? "none"} debugUrl=${debugUrl ? "available" : "none"}`);
|
|
253
858
|
}
|
|
254
859
|
if ("schema" in input) {
|
|
255
860
|
return wrapTaskResponseWithSchema(result, this.config, input.schema);
|
|
@@ -304,15 +909,15 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
304
909
|
error: 'Task description is required. Example: "Go to example.com and click the login button"'
|
|
305
910
|
};
|
|
306
911
|
}
|
|
307
|
-
if (input.
|
|
912
|
+
if (input.maxSteps !== void 0 && (input.maxSteps < 1 || input.maxSteps > 50)) {
|
|
308
913
|
return {
|
|
309
914
|
success: false,
|
|
310
|
-
error: "
|
|
915
|
+
error: "maxSteps must be between 1 and 50. Use more steps for complex multi-page flows."
|
|
311
916
|
};
|
|
312
917
|
}
|
|
313
918
|
if (debug) {
|
|
314
|
-
console.log(`[Browser] Task: "${input.task.slice(0, 60)}..." url=${input.url || "none"} maxSteps=${input.
|
|
315
|
-
console.log(`[Browser] Recording: ${input.
|
|
919
|
+
console.log(`[Browser] Task: "${input.task.slice(0, 60)}..." url=${input.url || "none"} maxSteps=${input.maxSteps ?? 10}`);
|
|
920
|
+
console.log(`[Browser] Recording: ${input.recordVideo ? "yes" : "no"} | Calling ${apiUrl}/browser-task`);
|
|
316
921
|
}
|
|
317
922
|
const startTime = Date.now();
|
|
318
923
|
try {
|
|
@@ -326,19 +931,20 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
326
931
|
body: JSON.stringify({
|
|
327
932
|
task: input.task,
|
|
328
933
|
url: input.url,
|
|
329
|
-
max_steps: input.
|
|
934
|
+
max_steps: input.maxSteps ?? 10,
|
|
330
935
|
model: input.model ?? "morph-computer-use-v0",
|
|
331
|
-
viewport_width: input.
|
|
332
|
-
viewport_height: input.
|
|
333
|
-
external_id: input.
|
|
334
|
-
repo_id: input.
|
|
335
|
-
commit_id: input.
|
|
336
|
-
record_video: input.
|
|
337
|
-
video_width: input.
|
|
338
|
-
video_height: input.
|
|
339
|
-
allow_resizing: input.
|
|
340
|
-
structured_output: input.
|
|
341
|
-
auth: input.auth
|
|
936
|
+
viewport_width: input.viewportWidth ?? 1280,
|
|
937
|
+
viewport_height: input.viewportHeight ?? 720,
|
|
938
|
+
external_id: input.externalId,
|
|
939
|
+
repo_id: input.repoId,
|
|
940
|
+
commit_id: input.commitId,
|
|
941
|
+
record_video: input.recordVideo ?? false,
|
|
942
|
+
video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
|
|
943
|
+
video_height: input.videoHeight ?? input.viewportHeight ?? 720,
|
|
944
|
+
allow_resizing: input.allowResizing ?? false,
|
|
945
|
+
structured_output: input.structuredOutput,
|
|
946
|
+
auth: input.auth,
|
|
947
|
+
profile_id: input.profileId
|
|
342
948
|
})
|
|
343
949
|
},
|
|
344
950
|
config.retryConfig
|
|
@@ -346,17 +952,17 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
346
952
|
const response = await withTimeout(
|
|
347
953
|
fetchPromise,
|
|
348
954
|
timeout,
|
|
349
|
-
`Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing
|
|
955
|
+
`Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing maxSteps.`
|
|
350
956
|
);
|
|
351
957
|
if (!response.ok) {
|
|
352
958
|
const errorText = await response.text().catch(() => response.statusText);
|
|
353
959
|
if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);
|
|
354
960
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
355
961
|
}
|
|
356
|
-
const result = await response.json();
|
|
962
|
+
const result = mapTaskResult(await response.json());
|
|
357
963
|
const elapsed = Date.now() - startTime;
|
|
358
964
|
if (debug) {
|
|
359
|
-
console.log(`[Browser] \u2705 ${result.success ? "Success" : "Failed"} in ${elapsed}ms | steps=${result.
|
|
965
|
+
console.log(`[Browser] \u2705 ${result.success ? "Success" : "Failed"} in ${elapsed}ms | steps=${result.stepsTaken ?? 0} recordingId=${result.recordingId ?? "none"}`);
|
|
360
966
|
}
|
|
361
967
|
return result;
|
|
362
968
|
} catch (error) {
|
|
@@ -394,7 +1000,7 @@ async function getRecording(recordingId, config = {}) {
|
|
|
394
1000
|
if (debug) console.error(`[Browser] getRecording error: ${response.status} - ${errorText}`);
|
|
395
1001
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
396
1002
|
}
|
|
397
|
-
const data = await response.json();
|
|
1003
|
+
const data = mapRecordingStatus(await response.json());
|
|
398
1004
|
if (debug) console.log(`[Browser] Recording status: ${data.status}`);
|
|
399
1005
|
return {
|
|
400
1006
|
...data,
|
|
@@ -417,10 +1023,10 @@ async function waitForRecording(recordingId, config = {}, options = {}) {
|
|
|
417
1023
|
}
|
|
418
1024
|
async function executeWithRecording(input, config = {}) {
|
|
419
1025
|
const taskResult = await executeBrowserTask(input, config);
|
|
420
|
-
if (taskResult.
|
|
1026
|
+
if (taskResult.recordingId) {
|
|
421
1027
|
try {
|
|
422
1028
|
const recording = await waitForRecording(
|
|
423
|
-
taskResult.
|
|
1029
|
+
taskResult.recordingId,
|
|
424
1030
|
config,
|
|
425
1031
|
{ timeout: 6e4, pollInterval: 2e3 }
|
|
426
1032
|
);
|
|
@@ -430,12 +1036,12 @@ async function executeWithRecording(input, config = {}) {
|
|
|
430
1036
|
};
|
|
431
1037
|
} catch (error) {
|
|
432
1038
|
const errorRecording = {
|
|
433
|
-
id: taskResult.
|
|
1039
|
+
id: taskResult.recordingId,
|
|
434
1040
|
status: "ERROR",
|
|
435
1041
|
error: error instanceof Error ? error.message : String(error),
|
|
436
|
-
|
|
437
|
-
getWebp: (options) => getWebp(taskResult.
|
|
438
|
-
getErrors: () => getErrors(taskResult.
|
|
1042
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1043
|
+
getWebp: (options) => getWebp(taskResult.recordingId, config, options),
|
|
1044
|
+
getErrors: () => getErrors(taskResult.recordingId, config)
|
|
439
1045
|
};
|
|
440
1046
|
return {
|
|
441
1047
|
...taskResult,
|
|
@@ -461,8 +1067,8 @@ async function getErrors(recordingId, config = {}) {
|
|
|
461
1067
|
if (debug) console.error(`[Browser] getErrors error: ${response.status} - ${errorText}`);
|
|
462
1068
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
463
1069
|
}
|
|
464
|
-
const errors = await response.json();
|
|
465
|
-
if (debug) console.log(`[Browser] Found ${errors.
|
|
1070
|
+
const errors = mapErrorsResponse(await response.json());
|
|
1071
|
+
if (debug) console.log(`[Browser] Found ${errors.totalErrors} errors`);
|
|
466
1072
|
return errors;
|
|
467
1073
|
}
|
|
468
1074
|
function stringifyStructuredOutput(schema) {
|
|
@@ -495,6 +1101,84 @@ function parseStructuredTaskOutput(result, schema) {
|
|
|
495
1101
|
throw error;
|
|
496
1102
|
}
|
|
497
1103
|
}
|
|
1104
|
+
function mapTaskResult(api) {
|
|
1105
|
+
if (!api || typeof api !== "object") {
|
|
1106
|
+
return api;
|
|
1107
|
+
}
|
|
1108
|
+
return {
|
|
1109
|
+
success: api.success,
|
|
1110
|
+
result: api.result,
|
|
1111
|
+
error: api.error,
|
|
1112
|
+
stepsTaken: api.steps_taken,
|
|
1113
|
+
executionTimeMs: api.execution_time_ms,
|
|
1114
|
+
urls: api.urls,
|
|
1115
|
+
actionNames: api.action_names,
|
|
1116
|
+
errors: api.errors,
|
|
1117
|
+
modelActions: api.model_actions,
|
|
1118
|
+
isDone: api.is_done,
|
|
1119
|
+
actionHistory: api.action_history,
|
|
1120
|
+
actionResults: api.action_results,
|
|
1121
|
+
hasErrors: api.has_errors,
|
|
1122
|
+
numberOfSteps: api.number_of_steps,
|
|
1123
|
+
judgement: api.judgement,
|
|
1124
|
+
isValidated: api.is_validated,
|
|
1125
|
+
replayId: api.replay_id,
|
|
1126
|
+
replayUrl: api.replay_url,
|
|
1127
|
+
recordingId: api.recording_id,
|
|
1128
|
+
recordingStatus: api.recording_status,
|
|
1129
|
+
taskId: api.task_id,
|
|
1130
|
+
status: api.status,
|
|
1131
|
+
output: api.output,
|
|
1132
|
+
debugUrl: api.debug_url
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
function mapRecordingStatus(api) {
|
|
1136
|
+
return {
|
|
1137
|
+
id: api.id,
|
|
1138
|
+
status: api.status,
|
|
1139
|
+
replayUrl: api.replay_url,
|
|
1140
|
+
networkUrl: api.network_url,
|
|
1141
|
+
consoleUrl: api.console_url,
|
|
1142
|
+
videoUrl: api.video_url,
|
|
1143
|
+
totalEvents: api.total_events,
|
|
1144
|
+
fileSize: api.file_size,
|
|
1145
|
+
duration: api.duration,
|
|
1146
|
+
error: api.error,
|
|
1147
|
+
createdAt: api.created_at
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
function mapBrowserError(api) {
|
|
1151
|
+
return {
|
|
1152
|
+
type: api.type,
|
|
1153
|
+
message: api.message,
|
|
1154
|
+
url: api.url,
|
|
1155
|
+
timestamp: api.timestamp,
|
|
1156
|
+
screenshotUrl: api.screenshot_url,
|
|
1157
|
+
capturedAt: api.captured_at,
|
|
1158
|
+
status: api.status
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function mapErrorsResponse(api) {
|
|
1162
|
+
return {
|
|
1163
|
+
recordingId: api.recording_id,
|
|
1164
|
+
totalErrors: api.total_errors,
|
|
1165
|
+
errors: Array.isArray(api.errors) ? api.errors.map(mapBrowserError) : []
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
function mapWebpResponse(api) {
|
|
1169
|
+
return {
|
|
1170
|
+
webpUrl: api.webp_url,
|
|
1171
|
+
cached: api.cached,
|
|
1172
|
+
width: api.width,
|
|
1173
|
+
fps: api.fps,
|
|
1174
|
+
maxDuration: api.max_duration,
|
|
1175
|
+
fileSize: api.file_size,
|
|
1176
|
+
maxSizeMb: api.max_size_mb,
|
|
1177
|
+
budgetMet: api.budget_met,
|
|
1178
|
+
qualityUsed: api.quality_used,
|
|
1179
|
+
attempts: api.attempts
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
498
1182
|
async function getTaskStatus(taskId, config) {
|
|
499
1183
|
const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;
|
|
500
1184
|
const debug = config.debug || false;
|
|
@@ -510,7 +1194,7 @@ async function getTaskStatus(taskId, config) {
|
|
|
510
1194
|
if (debug) console.error(`[Browser] getTaskStatus error: ${response.status} - ${errorText}`);
|
|
511
1195
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
512
1196
|
}
|
|
513
|
-
const result = await response.json();
|
|
1197
|
+
const result = mapTaskResult(await response.json());
|
|
514
1198
|
if (debug) console.log(`[Browser] Task status: ${result.status}`);
|
|
515
1199
|
return result;
|
|
516
1200
|
}
|
|
@@ -533,42 +1217,44 @@ async function pollTaskUntilComplete(taskId, config, pollConfig = {}) {
|
|
|
533
1217
|
throw new Error(`Task polling timeout after ${timeout}ms`);
|
|
534
1218
|
}
|
|
535
1219
|
function wrapTaskResponse(result, config) {
|
|
1220
|
+
const debugUrl = result.debugUrl ?? "";
|
|
536
1221
|
const wrapped = {
|
|
537
1222
|
...result,
|
|
538
|
-
|
|
539
|
-
|
|
1223
|
+
debugUrl,
|
|
1224
|
+
taskId: result.taskId || "",
|
|
1225
|
+
liveUrl: result.taskId ? generateLiveUrl(result.taskId, config) : debugUrl,
|
|
540
1226
|
complete: async (pollConfig) => {
|
|
541
|
-
if (result.
|
|
542
|
-
return pollTaskUntilComplete(result.
|
|
1227
|
+
if (result.taskId) {
|
|
1228
|
+
return pollTaskUntilComplete(result.taskId, config, pollConfig);
|
|
543
1229
|
}
|
|
544
|
-
if (result.
|
|
1230
|
+
if (result.recordingId) {
|
|
545
1231
|
const recording = await waitForRecording(
|
|
546
|
-
result.
|
|
1232
|
+
result.recordingId,
|
|
547
1233
|
config,
|
|
548
1234
|
pollConfig
|
|
549
1235
|
);
|
|
550
1236
|
return {
|
|
551
1237
|
...result,
|
|
552
|
-
|
|
1238
|
+
recordingStatus: recording.status
|
|
553
1239
|
};
|
|
554
1240
|
}
|
|
555
|
-
throw new Error("Cannot poll completion: no
|
|
1241
|
+
throw new Error("Cannot poll completion: no taskId or recordingId available");
|
|
556
1242
|
},
|
|
557
1243
|
// Add Steel live session helpers - either functional or error-throwing
|
|
558
|
-
getLiveUrl:
|
|
1244
|
+
getLiveUrl: debugUrl ? (options) => buildLiveUrl(debugUrl, options) : () => {
|
|
559
1245
|
throw new Error(
|
|
560
1246
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
|
|
561
1247
|
);
|
|
562
1248
|
},
|
|
563
|
-
getLiveIframe:
|
|
1249
|
+
getLiveIframe: debugUrl ? (optionsOrPreset) => {
|
|
564
1250
|
const options = resolvePreset(optionsOrPreset);
|
|
565
|
-
return buildLiveIframe(
|
|
1251
|
+
return buildLiveIframe(debugUrl, options);
|
|
566
1252
|
} : () => {
|
|
567
1253
|
throw new Error(
|
|
568
1254
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
|
|
569
1255
|
);
|
|
570
1256
|
},
|
|
571
|
-
getEmbedCode:
|
|
1257
|
+
getEmbedCode: debugUrl ? () => buildEmbedCode(debugUrl) : () => {
|
|
572
1258
|
throw new Error(
|
|
573
1259
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
|
|
574
1260
|
);
|
|
@@ -577,44 +1263,46 @@ function wrapTaskResponse(result, config) {
|
|
|
577
1263
|
return wrapped;
|
|
578
1264
|
}
|
|
579
1265
|
function wrapTaskResponseWithSchema(result, config, schema) {
|
|
1266
|
+
const debugUrl = result.debugUrl ?? "";
|
|
580
1267
|
const parsed = result.output ? parseStructuredTaskOutput(result, schema) : { ...result, parsed: null };
|
|
581
1268
|
const wrapped = {
|
|
582
1269
|
...parsed,
|
|
583
|
-
|
|
584
|
-
|
|
1270
|
+
debugUrl,
|
|
1271
|
+
taskId: result.taskId || "",
|
|
1272
|
+
liveUrl: result.taskId ? generateLiveUrl(result.taskId, config) : debugUrl,
|
|
585
1273
|
complete: async (pollConfig) => {
|
|
586
|
-
if (result.
|
|
587
|
-
const finalResult = await pollTaskUntilComplete(result.
|
|
1274
|
+
if (result.taskId) {
|
|
1275
|
+
const finalResult = await pollTaskUntilComplete(result.taskId, config, pollConfig);
|
|
588
1276
|
return parseStructuredTaskOutput(finalResult, schema);
|
|
589
1277
|
}
|
|
590
|
-
if (result.
|
|
1278
|
+
if (result.recordingId) {
|
|
591
1279
|
const recording = await waitForRecording(
|
|
592
|
-
result.
|
|
1280
|
+
result.recordingId,
|
|
593
1281
|
config,
|
|
594
1282
|
pollConfig
|
|
595
1283
|
);
|
|
596
1284
|
return {
|
|
597
1285
|
...parsed,
|
|
598
|
-
|
|
1286
|
+
recordingStatus: recording.status
|
|
599
1287
|
};
|
|
600
1288
|
}
|
|
601
|
-
throw new Error("Cannot poll completion: no
|
|
1289
|
+
throw new Error("Cannot poll completion: no taskId or recordingId available");
|
|
602
1290
|
},
|
|
603
1291
|
// Add Steel live session helpers - either functional or error-throwing
|
|
604
|
-
getLiveUrl:
|
|
1292
|
+
getLiveUrl: debugUrl ? (options) => buildLiveUrl(debugUrl, options) : () => {
|
|
605
1293
|
throw new Error(
|
|
606
1294
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions. "
|
|
607
1295
|
);
|
|
608
1296
|
},
|
|
609
|
-
getLiveIframe:
|
|
1297
|
+
getLiveIframe: debugUrl ? (optionsOrPreset) => {
|
|
610
1298
|
const options = resolvePreset(optionsOrPreset);
|
|
611
|
-
return buildLiveIframe(
|
|
1299
|
+
return buildLiveIframe(debugUrl, options);
|
|
612
1300
|
} : () => {
|
|
613
1301
|
throw new Error(
|
|
614
1302
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions."
|
|
615
1303
|
);
|
|
616
1304
|
},
|
|
617
|
-
getEmbedCode:
|
|
1305
|
+
getEmbedCode: debugUrl ? () => buildEmbedCode(debugUrl) : () => {
|
|
618
1306
|
throw new Error(
|
|
619
1307
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions."
|
|
620
1308
|
);
|
|
@@ -629,10 +1317,11 @@ async function getWebp(recordingId, config = {}, options = {}) {
|
|
|
629
1317
|
throw new Error("API key required for getWebp");
|
|
630
1318
|
}
|
|
631
1319
|
const params = new URLSearchParams();
|
|
632
|
-
if (options.
|
|
1320
|
+
if (options.maxDuration !== void 0) params.set("max_duration", String(options.maxDuration));
|
|
633
1321
|
if (options.fps !== void 0) params.set("fps", String(options.fps));
|
|
634
1322
|
if (options.width !== void 0) params.set("width", String(options.width));
|
|
635
1323
|
if (options.quality !== void 0) params.set("quality", String(options.quality));
|
|
1324
|
+
if (options.maxSizeMb !== void 0) params.set("max_size_mb", String(options.maxSizeMb));
|
|
636
1325
|
const url = `${apiUrl}/recordings/${recordingId}/webp${params.toString() ? "?" + params.toString() : ""}`;
|
|
637
1326
|
if (debug) console.log(`[Browser] getWebp: ${url}`);
|
|
638
1327
|
const response = await fetch(url, {
|
|
@@ -644,8 +1333,8 @@ async function getWebp(recordingId, config = {}, options = {}) {
|
|
|
644
1333
|
if (debug) console.error(`[Browser] getWebp error: ${response.status} - ${errorText}`);
|
|
645
1334
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
646
1335
|
}
|
|
647
|
-
const result = await response.json();
|
|
648
|
-
if (debug) console.log(`[Browser] WebP ready: ${result.
|
|
1336
|
+
const result = mapWebpResponse(await response.json());
|
|
1337
|
+
if (debug) console.log(`[Browser] WebP ready: ${result.webpUrl} (cached: ${result.cached})`);
|
|
649
1338
|
return result;
|
|
650
1339
|
}
|
|
651
1340
|
async function checkHealth(config = {}) {
|
|
@@ -712,7 +1401,7 @@ Include verification steps:
|
|
|
712
1401
|
## Requirements
|
|
713
1402
|
- **URL**: Must be publicly accessible (use tunnels like ngrok, Cloudflare, or deploy to staging)
|
|
714
1403
|
- **Timing**: Use this after implementation, not during coding
|
|
715
|
-
- **Complexity**: Set
|
|
1404
|
+
- **Complexity**: Set maxSteps higher (20-30) for multi-step user workflows`;
|
|
716
1405
|
var BROWSER_SYSTEM_PROMPT = `You are an AI agent designed to automate browser tasks to accomplish the <user_request>. Respond with a valid JSON object in the format: {"thinking": "Reason step-by-step about your current state, history, and the user request to decide your next goal and action. Analyze the browser state and screenshot to confirm the outcome of your last action.", "evaluation_previous_goal": "A concise, one-sentence evaluation of your last action's outcome (e.g., Success, Failure, or Uncertain).", "memory": "1-3 sentences summarizing key information and progress so far. This helps you track progress across multiple steps (e.g., items collected, pages visited).", "next_goal": "A clear, one-sentence description of your immediate next objective.", "action": [{"action_name": {"parameter": "value"}}]}`;
|
|
717
1406
|
|
|
718
1407
|
// tools/browser/anthropic.ts
|
|
@@ -735,7 +1424,7 @@ var browserTool = {
|
|
|
735
1424
|
type: "string",
|
|
736
1425
|
description: "Starting URL (e.g., https://3000-xyz.e2b.dev). Required if navigating to a specific page."
|
|
737
1426
|
},
|
|
738
|
-
|
|
1427
|
+
maxSteps: {
|
|
739
1428
|
type: "number",
|
|
740
1429
|
description: "Maximum number of browser actions to take (1-50). Default: 10. Use 15-30 for complex flows.",
|
|
741
1430
|
default: 10
|
|
@@ -754,8 +1443,8 @@ function formatResult(result) {
|
|
|
754
1443
|
if (result.success) {
|
|
755
1444
|
const parts = [
|
|
756
1445
|
"\u2705 Browser task completed successfully",
|
|
757
|
-
`Steps taken: ${result.
|
|
758
|
-
result.
|
|
1446
|
+
`Steps taken: ${result.stepsTaken ?? 0}`,
|
|
1447
|
+
result.executionTimeMs ? `Execution time: ${result.executionTimeMs}ms` : null,
|
|
759
1448
|
"",
|
|
760
1449
|
"Result:",
|
|
761
1450
|
result.result || "Task completed"
|
|
@@ -803,7 +1492,7 @@ var browserTool2 = {
|
|
|
803
1492
|
type: "string",
|
|
804
1493
|
description: "Starting URL (e.g., https://3000-xyz.e2b.dev). Required if navigating to a specific page."
|
|
805
1494
|
},
|
|
806
|
-
|
|
1495
|
+
maxSteps: {
|
|
807
1496
|
type: "number",
|
|
808
1497
|
description: "Maximum number of browser actions to take (1-50). Default: 10. Use 15-30 for complex flows.",
|
|
809
1498
|
default: 10
|
|
@@ -827,8 +1516,8 @@ function formatResult2(result) {
|
|
|
827
1516
|
if (result.success) {
|
|
828
1517
|
const parts = [
|
|
829
1518
|
"\u2705 Browser task completed successfully",
|
|
830
|
-
`Steps taken: ${result.
|
|
831
|
-
result.
|
|
1519
|
+
`Steps taken: ${result.stepsTaken ?? 0}`,
|
|
1520
|
+
result.executionTimeMs ? `Execution time: ${result.executionTimeMs}ms` : null,
|
|
832
1521
|
"",
|
|
833
1522
|
"Result:",
|
|
834
1523
|
result.result || "Task completed"
|
|
@@ -866,24 +1555,24 @@ function createBrowserTool3(config) {
|
|
|
866
1555
|
const schema = import_zod.z.object({
|
|
867
1556
|
task: import_zod.z.string().describe('Natural language description of what to do (e.g., "Test checkout flow for buying a pineapple")'),
|
|
868
1557
|
url: import_zod.z.string().optional().describe("Starting URL (e.g., https://3000-xyz.e2b.dev)"),
|
|
869
|
-
|
|
1558
|
+
maxSteps: import_zod.z.number().min(1).max(50).default(10).describe("Maximum number of browser actions to take"),
|
|
870
1559
|
region: import_zod.z.enum(["sfo", "lon"]).default("sfo").describe("Browserless region: sfo (US West) or lon (Europe)")
|
|
871
1560
|
});
|
|
872
1561
|
return (0, import_ai.tool)({
|
|
873
1562
|
description: BROWSER_TOOL_DESCRIPTION,
|
|
874
1563
|
inputSchema: schema,
|
|
875
1564
|
execute: async (params) => {
|
|
876
|
-
const { task, url,
|
|
1565
|
+
const { task, url, maxSteps, region } = params;
|
|
877
1566
|
const result = await executeBrowserTask(
|
|
878
|
-
{ task, url,
|
|
1567
|
+
{ task, url, maxSteps, region },
|
|
879
1568
|
config
|
|
880
1569
|
);
|
|
881
1570
|
if (result.success) {
|
|
882
1571
|
return {
|
|
883
1572
|
success: true,
|
|
884
1573
|
result: result.result,
|
|
885
|
-
|
|
886
|
-
|
|
1574
|
+
stepsTaken: result.stepsTaken,
|
|
1575
|
+
executionTimeMs: result.executionTimeMs
|
|
887
1576
|
};
|
|
888
1577
|
}
|
|
889
1578
|
return {
|
|
@@ -918,7 +1607,7 @@ var TOOL_PARAMETERS = {
|
|
|
918
1607
|
type: import_generative_ai.SchemaType.STRING,
|
|
919
1608
|
description: "Starting URL (e.g., https://3000-xyz.e2b.dev). Required if navigating to a specific page."
|
|
920
1609
|
},
|
|
921
|
-
|
|
1610
|
+
maxSteps: {
|
|
922
1611
|
type: import_generative_ai.SchemaType.NUMBER,
|
|
923
1612
|
description: "Maximum number of browser actions to take (1-50). Default: 10. Use 15-30 for complex flows."
|
|
924
1613
|
},
|
|
@@ -942,8 +1631,8 @@ function formatResult3(result) {
|
|
|
942
1631
|
if (result.success) {
|
|
943
1632
|
const parts = [
|
|
944
1633
|
"\u2705 Browser task completed successfully",
|
|
945
|
-
`Steps taken: ${result.
|
|
946
|
-
result.
|
|
1634
|
+
`Steps taken: ${result.stepsTaken ?? 0}`,
|
|
1635
|
+
result.executionTimeMs ? `Execution time: ${result.executionTimeMs}ms` : null,
|
|
947
1636
|
"",
|
|
948
1637
|
"Result:",
|
|
949
1638
|
result.result || "Task completed"
|
|
@@ -981,6 +1670,14 @@ var gemini_default = browserFunctionDeclaration;
|
|
|
981
1670
|
BROWSER_TOOL_DESCRIPTION,
|
|
982
1671
|
BrowserClient,
|
|
983
1672
|
LIVE_PRESETS,
|
|
1673
|
+
MorphAPIError,
|
|
1674
|
+
MorphAuthenticationError,
|
|
1675
|
+
MorphError,
|
|
1676
|
+
MorphNotFoundError,
|
|
1677
|
+
MorphProfileLimitError,
|
|
1678
|
+
MorphRateLimitError,
|
|
1679
|
+
MorphValidationError,
|
|
1680
|
+
ProfilesClient,
|
|
984
1681
|
anthropic,
|
|
985
1682
|
buildEmbedCode,
|
|
986
1683
|
buildLiveIframe,
|
|
@@ -993,6 +1690,7 @@ var gemini_default = browserFunctionDeclaration;
|
|
|
993
1690
|
getRecording,
|
|
994
1691
|
getWebp,
|
|
995
1692
|
openai,
|
|
1693
|
+
parseAPIError,
|
|
996
1694
|
vercel,
|
|
997
1695
|
waitForRecording
|
|
998
1696
|
});
|