@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
|
@@ -120,7 +120,8 @@ function buildLiveUrl(debugUrl, options = {}) {
|
|
|
120
120
|
"debugUrl is required. Ensure your backend returns debugUrl in the task response. Contact support@morphllm.com if you need help."
|
|
121
121
|
);
|
|
122
122
|
}
|
|
123
|
-
const
|
|
123
|
+
const normalized = normalizeLiveUrl(debugUrl);
|
|
124
|
+
const url = new URL(normalized);
|
|
124
125
|
if (options.interactive !== void 0) {
|
|
125
126
|
url.searchParams.set("interactive", String(options.interactive));
|
|
126
127
|
}
|
|
@@ -138,6 +139,29 @@ function buildLiveUrl(debugUrl, options = {}) {
|
|
|
138
139
|
}
|
|
139
140
|
return url.toString();
|
|
140
141
|
}
|
|
142
|
+
function normalizeLiveUrl(debugUrl) {
|
|
143
|
+
const trimmed = debugUrl.trim();
|
|
144
|
+
if (!trimmed) {
|
|
145
|
+
return trimmed;
|
|
146
|
+
}
|
|
147
|
+
if (trimmed.startsWith("wss://") || trimmed.startsWith("ws://")) {
|
|
148
|
+
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
149
|
+
}
|
|
150
|
+
let url;
|
|
151
|
+
try {
|
|
152
|
+
url = new URL(trimmed);
|
|
153
|
+
} catch {
|
|
154
|
+
return trimmed;
|
|
155
|
+
}
|
|
156
|
+
if (url.protocol === "wss:" || url.protocol === "ws:") {
|
|
157
|
+
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
158
|
+
}
|
|
159
|
+
const wssParam = url.searchParams.get("wss");
|
|
160
|
+
if (wssParam && (wssParam.startsWith("wss://") || wssParam.startsWith("ws://"))) {
|
|
161
|
+
url.searchParams.set("wss", wssParam);
|
|
162
|
+
}
|
|
163
|
+
return url.toString();
|
|
164
|
+
}
|
|
141
165
|
function buildLiveIframe(debugUrl, options = {}) {
|
|
142
166
|
const {
|
|
143
167
|
width = "100%",
|
|
@@ -181,6 +205,570 @@ function resolvePreset(optionsOrPreset) {
|
|
|
181
205
|
return optionsOrPreset;
|
|
182
206
|
}
|
|
183
207
|
|
|
208
|
+
// tools/browser/errors.ts
|
|
209
|
+
var MorphError = class extends Error {
|
|
210
|
+
/** Error code for programmatic handling */
|
|
211
|
+
code;
|
|
212
|
+
/** Original cause of the error, if any */
|
|
213
|
+
cause;
|
|
214
|
+
constructor(message, code, cause) {
|
|
215
|
+
super(message);
|
|
216
|
+
this.name = "MorphError";
|
|
217
|
+
this.code = code;
|
|
218
|
+
this.cause = cause;
|
|
219
|
+
if (Error.captureStackTrace) {
|
|
220
|
+
Error.captureStackTrace(this, this.constructor);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Returns a JSON representation of the error for logging.
|
|
225
|
+
*/
|
|
226
|
+
toJSON() {
|
|
227
|
+
return {
|
|
228
|
+
name: this.name,
|
|
229
|
+
message: this.message,
|
|
230
|
+
code: this.code,
|
|
231
|
+
cause: this.cause?.message
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
var MorphValidationError = class extends MorphError {
|
|
236
|
+
/** The field that failed validation */
|
|
237
|
+
field;
|
|
238
|
+
constructor(message, field) {
|
|
239
|
+
super(message, "validation_error");
|
|
240
|
+
this.name = "MorphValidationError";
|
|
241
|
+
this.field = field;
|
|
242
|
+
}
|
|
243
|
+
toJSON() {
|
|
244
|
+
return {
|
|
245
|
+
...super.toJSON(),
|
|
246
|
+
field: this.field
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
var MorphAPIError = class extends MorphError {
|
|
251
|
+
/** HTTP status code */
|
|
252
|
+
statusCode;
|
|
253
|
+
/** Request ID for debugging (if available) */
|
|
254
|
+
requestId;
|
|
255
|
+
/** Raw response body */
|
|
256
|
+
rawResponse;
|
|
257
|
+
constructor(message, code, statusCode, options) {
|
|
258
|
+
super(message, code, options?.cause);
|
|
259
|
+
this.name = "MorphAPIError";
|
|
260
|
+
this.statusCode = statusCode;
|
|
261
|
+
this.requestId = options?.requestId;
|
|
262
|
+
this.rawResponse = options?.rawResponse;
|
|
263
|
+
}
|
|
264
|
+
toJSON() {
|
|
265
|
+
return {
|
|
266
|
+
...super.toJSON(),
|
|
267
|
+
statusCode: this.statusCode,
|
|
268
|
+
requestId: this.requestId
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
var MorphAuthenticationError = class extends MorphAPIError {
|
|
273
|
+
constructor(message = "Authentication required. Please provide a valid API key.") {
|
|
274
|
+
super(message, "authentication_required", 401);
|
|
275
|
+
this.name = "MorphAuthenticationError";
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
var MorphRateLimitError = class extends MorphAPIError {
|
|
279
|
+
/** When the rate limit resets (Unix timestamp) */
|
|
280
|
+
resetAt;
|
|
281
|
+
/** Number of seconds until reset */
|
|
282
|
+
retryAfter;
|
|
283
|
+
constructor(message = "Rate limit exceeded. Please retry later.", options) {
|
|
284
|
+
super(message, "rate_limit_exceeded", 429, { requestId: options?.requestId });
|
|
285
|
+
this.name = "MorphRateLimitError";
|
|
286
|
+
this.resetAt = options?.resetAt;
|
|
287
|
+
this.retryAfter = options?.retryAfter;
|
|
288
|
+
}
|
|
289
|
+
toJSON() {
|
|
290
|
+
return {
|
|
291
|
+
...super.toJSON(),
|
|
292
|
+
resetAt: this.resetAt,
|
|
293
|
+
retryAfter: this.retryAfter
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
var MorphNotFoundError = class extends MorphAPIError {
|
|
298
|
+
/** The type of resource that was not found */
|
|
299
|
+
resourceType;
|
|
300
|
+
/** The ID of the resource that was not found */
|
|
301
|
+
resourceId;
|
|
302
|
+
constructor(resourceType, resourceId) {
|
|
303
|
+
const message = resourceId ? `${resourceType} '${resourceId}' not found` : `${resourceType} not found`;
|
|
304
|
+
super(message, "resource_not_found", 404);
|
|
305
|
+
this.name = "MorphNotFoundError";
|
|
306
|
+
this.resourceType = resourceType;
|
|
307
|
+
this.resourceId = resourceId;
|
|
308
|
+
}
|
|
309
|
+
toJSON() {
|
|
310
|
+
return {
|
|
311
|
+
...super.toJSON(),
|
|
312
|
+
resourceType: this.resourceType,
|
|
313
|
+
resourceId: this.resourceId
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
var MorphProfileLimitError = class extends MorphAPIError {
|
|
318
|
+
/** Current number of profiles */
|
|
319
|
+
currentCount;
|
|
320
|
+
/** Maximum allowed profiles for the plan */
|
|
321
|
+
maxAllowed;
|
|
322
|
+
constructor(message = "Profile limit exceeded for your plan.", options) {
|
|
323
|
+
super(message, "profile_limit_exceeded", 403, { requestId: options?.requestId });
|
|
324
|
+
this.name = "MorphProfileLimitError";
|
|
325
|
+
this.currentCount = options?.currentCount;
|
|
326
|
+
this.maxAllowed = options?.maxAllowed;
|
|
327
|
+
}
|
|
328
|
+
toJSON() {
|
|
329
|
+
return {
|
|
330
|
+
...super.toJSON(),
|
|
331
|
+
currentCount: this.currentCount,
|
|
332
|
+
maxAllowed: this.maxAllowed
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
function parseAPIError(statusCode, responseText, requestId) {
|
|
337
|
+
let errorData = {};
|
|
338
|
+
try {
|
|
339
|
+
errorData = JSON.parse(responseText);
|
|
340
|
+
} catch {
|
|
341
|
+
}
|
|
342
|
+
const message = errorData.detail || errorData.message || responseText || "Unknown error";
|
|
343
|
+
const code = errorData.code;
|
|
344
|
+
switch (statusCode) {
|
|
345
|
+
case 401:
|
|
346
|
+
return new MorphAuthenticationError(message);
|
|
347
|
+
case 403:
|
|
348
|
+
if (code === "profile_limit_exceeded" || message.toLowerCase().includes("limit")) {
|
|
349
|
+
return new MorphProfileLimitError(message, { requestId });
|
|
350
|
+
}
|
|
351
|
+
return new MorphAPIError(message, "insufficient_permissions", statusCode, { requestId, rawResponse: responseText });
|
|
352
|
+
case 404:
|
|
353
|
+
if (message.toLowerCase().includes("profile")) {
|
|
354
|
+
return new MorphNotFoundError("Profile", void 0);
|
|
355
|
+
}
|
|
356
|
+
if (message.toLowerCase().includes("session")) {
|
|
357
|
+
return new MorphNotFoundError("Session", void 0);
|
|
358
|
+
}
|
|
359
|
+
return new MorphAPIError(message, "resource_not_found", statusCode, { requestId, rawResponse: responseText });
|
|
360
|
+
case 429:
|
|
361
|
+
return new MorphRateLimitError(message, { requestId });
|
|
362
|
+
case 422:
|
|
363
|
+
return new MorphAPIError(message, "validation_error", statusCode, { requestId, rawResponse: responseText });
|
|
364
|
+
case 500:
|
|
365
|
+
case 502:
|
|
366
|
+
case 503:
|
|
367
|
+
case 504:
|
|
368
|
+
return new MorphAPIError(message, "service_unavailable", statusCode, { requestId, rawResponse: responseText });
|
|
369
|
+
default:
|
|
370
|
+
return new MorphAPIError(message, "network_error", statusCode, { requestId, rawResponse: responseText });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// tools/browser/profiles/types.ts
|
|
375
|
+
function transformProfile(api) {
|
|
376
|
+
return {
|
|
377
|
+
id: api.id,
|
|
378
|
+
name: api.name,
|
|
379
|
+
repoId: api.repo_id,
|
|
380
|
+
cookieDomains: api.cookie_domains,
|
|
381
|
+
lastUsedAt: api.last_used_at,
|
|
382
|
+
createdAt: api.created_at,
|
|
383
|
+
updatedAt: api.updated_at
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
function transformCreateInput(input) {
|
|
387
|
+
return {
|
|
388
|
+
name: input.name,
|
|
389
|
+
repo_id: input.repoId
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function transformSession(api) {
|
|
393
|
+
return {
|
|
394
|
+
sessionId: api.session_id,
|
|
395
|
+
debugUrl: api.debug_url || ""
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
function transformSaveInput(input) {
|
|
399
|
+
return {
|
|
400
|
+
session_id: input.sessionId,
|
|
401
|
+
profile_id: input.profileId
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function transformStateResponse(api) {
|
|
405
|
+
return {
|
|
406
|
+
profileId: api.profile_id,
|
|
407
|
+
stateUrl: api.state_url,
|
|
408
|
+
expiresIn: api.expires_in
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// tools/browser/profiles/core.ts
|
|
413
|
+
var DEFAULT_API_URL = process.env.MORPH_ENVIRONMENT === "DEV" ? "http://localhost:8000" : "https://browser.morphllm.com";
|
|
414
|
+
var ProfilesClient = class {
|
|
415
|
+
config;
|
|
416
|
+
constructor(config) {
|
|
417
|
+
this.config = config;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Create a new browser profile and immediately start a live session.
|
|
421
|
+
*
|
|
422
|
+
* @param input - Profile creation parameters
|
|
423
|
+
* @returns Profile setup handle with live URL + save()
|
|
424
|
+
* @throws {MorphValidationError} If input validation fails
|
|
425
|
+
* @throws {MorphProfileLimitError} If profile limit is exceeded
|
|
426
|
+
* @throws {MorphAuthenticationError} If API key is missing or invalid
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* ```typescript
|
|
430
|
+
* const setup = await morph.browser.profiles.createProfile({
|
|
431
|
+
* name: 'LinkedIn Production',
|
|
432
|
+
* repoId: 'owner/repo'
|
|
433
|
+
* });
|
|
434
|
+
* console.log(setup.session.debugUrl);
|
|
435
|
+
* await setup.save();
|
|
436
|
+
* ```
|
|
437
|
+
*/
|
|
438
|
+
async createProfile(input) {
|
|
439
|
+
return createProfile(input, this.config);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* List all profiles for the authenticated user.
|
|
443
|
+
*
|
|
444
|
+
* @param repoId - Optional repository ID to filter by
|
|
445
|
+
* @returns Array of profiles
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* ```typescript
|
|
449
|
+
* // List all profiles
|
|
450
|
+
* const allProfiles = await morph.browser.profiles.listProfiles();
|
|
451
|
+
*
|
|
452
|
+
* // List profiles for a specific repo
|
|
453
|
+
* const repoProfiles = await morph.browser.profiles.listProfiles('owner/repo');
|
|
454
|
+
* ```
|
|
455
|
+
*/
|
|
456
|
+
async listProfiles(repoId) {
|
|
457
|
+
return listProfiles(this.config, repoId);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Get a profile by ID with convenience methods.
|
|
461
|
+
*
|
|
462
|
+
* @param id - Profile ID
|
|
463
|
+
* @returns Profile with attached methods
|
|
464
|
+
* @throws {MorphNotFoundError} If profile is not found
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* ```typescript
|
|
468
|
+
* const profile = await morph.browser.profiles.getProfile('profile-id');
|
|
469
|
+
* const state = await profile.getState();
|
|
470
|
+
* await profile.delete();
|
|
471
|
+
* ```
|
|
472
|
+
*/
|
|
473
|
+
async getProfile(id) {
|
|
474
|
+
return getProfile(id, this.config);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Update a profile by opening a live session (no rename).
|
|
478
|
+
*
|
|
479
|
+
* @param id - Profile ID
|
|
480
|
+
* @returns Profile setup handle with live URL + save()
|
|
481
|
+
*/
|
|
482
|
+
async updateProfile(id) {
|
|
483
|
+
return updateProfile(id, this.config);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Delete a profile.
|
|
487
|
+
*
|
|
488
|
+
* @param id - Profile ID
|
|
489
|
+
* @throws {MorphNotFoundError} If profile is not found
|
|
490
|
+
*/
|
|
491
|
+
async deleteProfile(id) {
|
|
492
|
+
return deleteProfile(id, this.config);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Start a browser session for profile setup.
|
|
496
|
+
*
|
|
497
|
+
* Returns a live URL where the user can sign into accounts.
|
|
498
|
+
* After signing in, call `saveSession` to persist the state.
|
|
499
|
+
*
|
|
500
|
+
* @param input - Optional session parameters
|
|
501
|
+
* @returns Session with debug URL
|
|
502
|
+
*
|
|
503
|
+
* @example
|
|
504
|
+
* ```typescript
|
|
505
|
+
* const session = await morph.browser.profiles.startSession();
|
|
506
|
+
* console.log('Sign in at:', session.debugUrl);
|
|
507
|
+
* // Open debugUrl in browser, user signs in...
|
|
508
|
+
* await morph.browser.profiles.saveSession(session.sessionId, profile.id);
|
|
509
|
+
* ```
|
|
510
|
+
*/
|
|
511
|
+
async startSession(input) {
|
|
512
|
+
return startProfileSession(this.config, input);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Save browser state from a session to a profile.
|
|
516
|
+
*
|
|
517
|
+
* Call this after the user is done signing into accounts.
|
|
518
|
+
* Extracts cookies, localStorage, and sessionStorage.
|
|
519
|
+
*
|
|
520
|
+
* @param sessionId - Browser session ID from startSession
|
|
521
|
+
* @param profileId - Profile ID to save state to
|
|
522
|
+
* @returns Updated profile with cookie domains
|
|
523
|
+
*/
|
|
524
|
+
async saveSession(sessionId, profileId) {
|
|
525
|
+
return saveProfileSession({ sessionId, profileId }, this.config);
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* List available repo IDs (discovery).
|
|
529
|
+
*
|
|
530
|
+
* @returns Repo summaries with profile counts
|
|
531
|
+
*/
|
|
532
|
+
async listRepos() {
|
|
533
|
+
return listRepos(this.config);
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Get the presigned URL for a profile's state.
|
|
537
|
+
*
|
|
538
|
+
* Use this to download the raw state JSON for debugging
|
|
539
|
+
* or to restore state manually.
|
|
540
|
+
*
|
|
541
|
+
* @param profileId - Profile ID
|
|
542
|
+
* @returns State URL with expiry information
|
|
543
|
+
*/
|
|
544
|
+
async getProfileState(profileId) {
|
|
545
|
+
return getProfileState(profileId, this.config);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
function validateCreateInput(input) {
|
|
549
|
+
if (!input.name || typeof input.name !== "string") {
|
|
550
|
+
throw new MorphValidationError("name is required", "name");
|
|
551
|
+
}
|
|
552
|
+
const trimmedName = input.name.trim();
|
|
553
|
+
if (trimmedName.length === 0) {
|
|
554
|
+
throw new MorphValidationError("name cannot be empty", "name");
|
|
555
|
+
}
|
|
556
|
+
if (trimmedName.length > 100) {
|
|
557
|
+
throw new MorphValidationError("name must be 100 characters or less", "name");
|
|
558
|
+
}
|
|
559
|
+
if (!input.repoId || typeof input.repoId !== "string") {
|
|
560
|
+
throw new MorphValidationError("repoId is required", "repoId");
|
|
561
|
+
}
|
|
562
|
+
if (input.repoId.trim().length === 0) {
|
|
563
|
+
throw new MorphValidationError("repoId cannot be empty", "repoId");
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
function validateId(id, fieldName) {
|
|
567
|
+
if (!id || typeof id !== "string") {
|
|
568
|
+
throw new MorphValidationError(`${fieldName} is required`, fieldName);
|
|
569
|
+
}
|
|
570
|
+
if (id.trim().length === 0) {
|
|
571
|
+
throw new MorphValidationError(`${fieldName} cannot be empty`, fieldName);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
async function createProfile(input, config = {}) {
|
|
575
|
+
validateCreateInput(input);
|
|
576
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
577
|
+
const headers = buildHeaders(config);
|
|
578
|
+
const response = await fetchWithRetry(
|
|
579
|
+
`${apiUrl}/profiles`,
|
|
580
|
+
{
|
|
581
|
+
method: "POST",
|
|
582
|
+
headers,
|
|
583
|
+
body: JSON.stringify(transformCreateInput(input))
|
|
584
|
+
},
|
|
585
|
+
config.retryConfig
|
|
586
|
+
);
|
|
587
|
+
if (!response.ok) {
|
|
588
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
589
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
590
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
591
|
+
}
|
|
592
|
+
const apiProfile = await response.json();
|
|
593
|
+
const profile = transformProfile(apiProfile);
|
|
594
|
+
const session = await startProfileSession(config, { profileId: profile.id });
|
|
595
|
+
return buildProfileSetup(profile, session, config);
|
|
596
|
+
}
|
|
597
|
+
async function listProfiles(config = {}, repoId) {
|
|
598
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
599
|
+
const headers = buildHeaders(config);
|
|
600
|
+
const url = repoId ? `${apiUrl}/profiles?repo_id=${encodeURIComponent(repoId)}` : `${apiUrl}/profiles`;
|
|
601
|
+
const response = await fetchWithRetry(
|
|
602
|
+
url,
|
|
603
|
+
{ method: "GET", headers },
|
|
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 data = await response.json();
|
|
612
|
+
return data.profiles.map(transformProfile);
|
|
613
|
+
}
|
|
614
|
+
async function getProfile(id, config = {}) {
|
|
615
|
+
validateId(id, "id");
|
|
616
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
617
|
+
const headers = buildHeaders(config);
|
|
618
|
+
const response = await fetchWithRetry(
|
|
619
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
620
|
+
{ method: "GET", headers },
|
|
621
|
+
config.retryConfig
|
|
622
|
+
);
|
|
623
|
+
if (!response.ok) {
|
|
624
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
625
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
626
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
627
|
+
}
|
|
628
|
+
const apiProfile = await response.json();
|
|
629
|
+
const profile = transformProfile(apiProfile);
|
|
630
|
+
return {
|
|
631
|
+
...profile,
|
|
632
|
+
getState: () => getProfileState(id, config),
|
|
633
|
+
delete: () => deleteProfile(id, config)
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
async function updateProfile(id, config = {}) {
|
|
637
|
+
validateId(id, "id");
|
|
638
|
+
const profile = await fetchProfile(id, config);
|
|
639
|
+
const session = await startProfileSession(config, { profileId: profile.id });
|
|
640
|
+
return buildProfileSetup(profile, session, config);
|
|
641
|
+
}
|
|
642
|
+
async function deleteProfile(id, config = {}) {
|
|
643
|
+
validateId(id, "id");
|
|
644
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
645
|
+
const headers = buildHeaders(config);
|
|
646
|
+
const response = await fetchWithRetry(
|
|
647
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
648
|
+
{ method: "DELETE", headers },
|
|
649
|
+
config.retryConfig
|
|
650
|
+
);
|
|
651
|
+
if (!response.ok) {
|
|
652
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
653
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
654
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
async function startProfileSession(config = {}, input) {
|
|
658
|
+
if (!config.apiKey) {
|
|
659
|
+
throw new MorphAuthenticationError();
|
|
660
|
+
}
|
|
661
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
662
|
+
const headers = buildHeaders(config);
|
|
663
|
+
const body = input?.profileId ? { profile_id: input.profileId } : {};
|
|
664
|
+
const response = await fetchWithRetry(
|
|
665
|
+
`${apiUrl}/profiles/session/start`,
|
|
666
|
+
{
|
|
667
|
+
method: "POST",
|
|
668
|
+
headers,
|
|
669
|
+
body: JSON.stringify(body)
|
|
670
|
+
},
|
|
671
|
+
config.retryConfig
|
|
672
|
+
);
|
|
673
|
+
if (!response.ok) {
|
|
674
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
675
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
676
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
677
|
+
}
|
|
678
|
+
const apiSession = await response.json();
|
|
679
|
+
return transformSession(apiSession);
|
|
680
|
+
}
|
|
681
|
+
async function saveProfileSession(input, config = {}) {
|
|
682
|
+
validateId(input.sessionId, "sessionId");
|
|
683
|
+
validateId(input.profileId, "profileId");
|
|
684
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
685
|
+
const headers = buildHeaders(config);
|
|
686
|
+
const response = await fetchWithRetry(
|
|
687
|
+
`${apiUrl}/profiles/session/save`,
|
|
688
|
+
{
|
|
689
|
+
method: "POST",
|
|
690
|
+
headers,
|
|
691
|
+
body: JSON.stringify(transformSaveInput(input))
|
|
692
|
+
},
|
|
693
|
+
config.retryConfig
|
|
694
|
+
);
|
|
695
|
+
if (!response.ok) {
|
|
696
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
697
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
698
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
699
|
+
}
|
|
700
|
+
const apiProfile = await response.json();
|
|
701
|
+
return transformProfile(apiProfile);
|
|
702
|
+
}
|
|
703
|
+
async function listRepos(config = {}) {
|
|
704
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
705
|
+
const headers = buildHeaders(config);
|
|
706
|
+
const response = await fetchWithRetry(
|
|
707
|
+
`${apiUrl}/repos`,
|
|
708
|
+
{ method: "GET", headers },
|
|
709
|
+
config.retryConfig
|
|
710
|
+
);
|
|
711
|
+
if (!response.ok) {
|
|
712
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
713
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
714
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
715
|
+
}
|
|
716
|
+
const data = await response.json();
|
|
717
|
+
const repos = Array.isArray(data?.repos) ? data.repos : [];
|
|
718
|
+
return repos.map((repo) => ({
|
|
719
|
+
repoId: repo.repo_id,
|
|
720
|
+
repoFullName: repo.repo_full_name,
|
|
721
|
+
profileCount: repo.profile_count ?? 0
|
|
722
|
+
}));
|
|
723
|
+
}
|
|
724
|
+
async function getProfileState(profileId, config = {}) {
|
|
725
|
+
validateId(profileId, "profileId");
|
|
726
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
727
|
+
const headers = buildHeaders(config);
|
|
728
|
+
const response = await fetchWithRetry(
|
|
729
|
+
`${apiUrl}/profiles/${encodeURIComponent(profileId)}/state`,
|
|
730
|
+
{ method: "GET", headers },
|
|
731
|
+
config.retryConfig
|
|
732
|
+
);
|
|
733
|
+
if (!response.ok) {
|
|
734
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
735
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
736
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
737
|
+
}
|
|
738
|
+
const apiState = await response.json();
|
|
739
|
+
return transformStateResponse(apiState);
|
|
740
|
+
}
|
|
741
|
+
function buildHeaders(config) {
|
|
742
|
+
const headers = { "Content-Type": "application/json" };
|
|
743
|
+
if (config.apiKey) {
|
|
744
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
745
|
+
}
|
|
746
|
+
return headers;
|
|
747
|
+
}
|
|
748
|
+
async function fetchProfile(id, config) {
|
|
749
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
750
|
+
const headers = buildHeaders(config);
|
|
751
|
+
const response = await fetchWithRetry(
|
|
752
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
753
|
+
{ method: "GET", headers },
|
|
754
|
+
config.retryConfig
|
|
755
|
+
);
|
|
756
|
+
if (!response.ok) {
|
|
757
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
758
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
759
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
760
|
+
}
|
|
761
|
+
const apiProfile = await response.json();
|
|
762
|
+
return transformProfile(apiProfile);
|
|
763
|
+
}
|
|
764
|
+
function buildProfileSetup(profile, session, config) {
|
|
765
|
+
return {
|
|
766
|
+
profile,
|
|
767
|
+
session,
|
|
768
|
+
save: () => saveProfileSession({ sessionId: session.sessionId, profileId: profile.id }, config)
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
184
772
|
// tools/browser/core.ts
|
|
185
773
|
var DEFAULT_CONFIG = {
|
|
186
774
|
apiUrl: process.env.MORPH_ENVIRONMENT === "DEV" ? "http://localhost:8000" : "https://browser.morphllm.com",
|
|
@@ -190,11 +778,16 @@ var DEFAULT_CONFIG = {
|
|
|
190
778
|
};
|
|
191
779
|
var BrowserClient = class {
|
|
192
780
|
config;
|
|
781
|
+
/**
|
|
782
|
+
* Profile management - create and manage browser profiles for storing login state.
|
|
783
|
+
*/
|
|
784
|
+
profiles;
|
|
193
785
|
constructor(config = {}) {
|
|
194
786
|
this.config = {
|
|
195
787
|
...DEFAULT_CONFIG,
|
|
196
788
|
...config
|
|
197
789
|
};
|
|
790
|
+
this.profiles = new ProfilesClient(this.config);
|
|
198
791
|
}
|
|
199
792
|
/**
|
|
200
793
|
* Execute a browser automation task
|
|
@@ -217,19 +810,21 @@ var BrowserClient = class {
|
|
|
217
810
|
body: JSON.stringify({
|
|
218
811
|
task: input.task,
|
|
219
812
|
url: input.url,
|
|
220
|
-
max_steps: input.
|
|
813
|
+
max_steps: input.maxSteps ?? 10,
|
|
221
814
|
model: input.model ?? "morph-computer-use-v0",
|
|
222
|
-
viewport_width: input.
|
|
223
|
-
viewport_height: input.
|
|
224
|
-
external_id: input.
|
|
225
|
-
repo_id: input.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
815
|
+
viewport_width: input.viewportWidth ?? 1280,
|
|
816
|
+
viewport_height: input.viewportHeight ?? 720,
|
|
817
|
+
external_id: input.externalId,
|
|
818
|
+
repo_id: input.repoId,
|
|
819
|
+
repo_full_name: input.repoFullName,
|
|
820
|
+
commit_id: input.commitId,
|
|
821
|
+
record_video: input.recordVideo ?? false,
|
|
822
|
+
video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
|
|
823
|
+
video_height: input.videoHeight ?? input.viewportHeight ?? 720,
|
|
824
|
+
allow_resizing: input.allowResizing ?? false,
|
|
231
825
|
structured_output: "schema" in input ? stringifyStructuredOutput(input.schema) : void 0,
|
|
232
|
-
auth: input.auth
|
|
826
|
+
auth: input.auth,
|
|
827
|
+
profile_id: input.profileId
|
|
233
828
|
})
|
|
234
829
|
});
|
|
235
830
|
if (!response.ok) {
|
|
@@ -237,9 +832,10 @@ var BrowserClient = class {
|
|
|
237
832
|
if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);
|
|
238
833
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
239
834
|
}
|
|
240
|
-
const result = await response.json();
|
|
835
|
+
const result = mapTaskResult(await response.json());
|
|
241
836
|
if (debug) {
|
|
242
|
-
|
|
837
|
+
const debugUrl = result.debugUrl;
|
|
838
|
+
console.log(`[Browser] \u2705 Task created: recordingId=${result.recordingId ?? "none"} debugUrl=${debugUrl ? "available" : "none"}`);
|
|
243
839
|
}
|
|
244
840
|
if ("schema" in input) {
|
|
245
841
|
return wrapTaskResponseWithSchema(result, this.config, input.schema);
|
|
@@ -294,15 +890,15 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
294
890
|
error: 'Task description is required. Example: "Go to example.com and click the login button"'
|
|
295
891
|
};
|
|
296
892
|
}
|
|
297
|
-
if (input.
|
|
893
|
+
if (input.maxSteps !== void 0 && (input.maxSteps < 1 || input.maxSteps > 50)) {
|
|
298
894
|
return {
|
|
299
895
|
success: false,
|
|
300
|
-
error: "
|
|
896
|
+
error: "maxSteps must be between 1 and 50. Use more steps for complex multi-page flows."
|
|
301
897
|
};
|
|
302
898
|
}
|
|
303
899
|
if (debug) {
|
|
304
|
-
console.log(`[Browser] Task: "${input.task.slice(0, 60)}..." url=${input.url || "none"} maxSteps=${input.
|
|
305
|
-
console.log(`[Browser] Recording: ${input.
|
|
900
|
+
console.log(`[Browser] Task: "${input.task.slice(0, 60)}..." url=${input.url || "none"} maxSteps=${input.maxSteps ?? 10}`);
|
|
901
|
+
console.log(`[Browser] Recording: ${input.recordVideo ? "yes" : "no"} | Calling ${apiUrl}/browser-task`);
|
|
306
902
|
}
|
|
307
903
|
const startTime = Date.now();
|
|
308
904
|
try {
|
|
@@ -316,19 +912,20 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
316
912
|
body: JSON.stringify({
|
|
317
913
|
task: input.task,
|
|
318
914
|
url: input.url,
|
|
319
|
-
max_steps: input.
|
|
915
|
+
max_steps: input.maxSteps ?? 10,
|
|
320
916
|
model: input.model ?? "morph-computer-use-v0",
|
|
321
|
-
viewport_width: input.
|
|
322
|
-
viewport_height: input.
|
|
323
|
-
external_id: input.
|
|
324
|
-
repo_id: input.
|
|
325
|
-
commit_id: input.
|
|
326
|
-
record_video: input.
|
|
327
|
-
video_width: input.
|
|
328
|
-
video_height: input.
|
|
329
|
-
allow_resizing: input.
|
|
330
|
-
structured_output: input.
|
|
331
|
-
auth: input.auth
|
|
917
|
+
viewport_width: input.viewportWidth ?? 1280,
|
|
918
|
+
viewport_height: input.viewportHeight ?? 720,
|
|
919
|
+
external_id: input.externalId,
|
|
920
|
+
repo_id: input.repoId,
|
|
921
|
+
commit_id: input.commitId,
|
|
922
|
+
record_video: input.recordVideo ?? false,
|
|
923
|
+
video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
|
|
924
|
+
video_height: input.videoHeight ?? input.viewportHeight ?? 720,
|
|
925
|
+
allow_resizing: input.allowResizing ?? false,
|
|
926
|
+
structured_output: input.structuredOutput,
|
|
927
|
+
auth: input.auth,
|
|
928
|
+
profile_id: input.profileId
|
|
332
929
|
})
|
|
333
930
|
},
|
|
334
931
|
config.retryConfig
|
|
@@ -336,17 +933,17 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
336
933
|
const response = await withTimeout(
|
|
337
934
|
fetchPromise,
|
|
338
935
|
timeout,
|
|
339
|
-
`Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing
|
|
936
|
+
`Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing maxSteps.`
|
|
340
937
|
);
|
|
341
938
|
if (!response.ok) {
|
|
342
939
|
const errorText = await response.text().catch(() => response.statusText);
|
|
343
940
|
if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);
|
|
344
941
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
345
942
|
}
|
|
346
|
-
const result = await response.json();
|
|
943
|
+
const result = mapTaskResult(await response.json());
|
|
347
944
|
const elapsed = Date.now() - startTime;
|
|
348
945
|
if (debug) {
|
|
349
|
-
console.log(`[Browser] \u2705 ${result.success ? "Success" : "Failed"} in ${elapsed}ms | steps=${result.
|
|
946
|
+
console.log(`[Browser] \u2705 ${result.success ? "Success" : "Failed"} in ${elapsed}ms | steps=${result.stepsTaken ?? 0} recordingId=${result.recordingId ?? "none"}`);
|
|
350
947
|
}
|
|
351
948
|
return result;
|
|
352
949
|
} catch (error) {
|
|
@@ -384,7 +981,7 @@ async function getRecording(recordingId, config = {}) {
|
|
|
384
981
|
if (debug) console.error(`[Browser] getRecording error: ${response.status} - ${errorText}`);
|
|
385
982
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
386
983
|
}
|
|
387
|
-
const data = await response.json();
|
|
984
|
+
const data = mapRecordingStatus(await response.json());
|
|
388
985
|
if (debug) console.log(`[Browser] Recording status: ${data.status}`);
|
|
389
986
|
return {
|
|
390
987
|
...data,
|
|
@@ -407,10 +1004,10 @@ async function waitForRecording(recordingId, config = {}, options = {}) {
|
|
|
407
1004
|
}
|
|
408
1005
|
async function executeWithRecording(input, config = {}) {
|
|
409
1006
|
const taskResult = await executeBrowserTask(input, config);
|
|
410
|
-
if (taskResult.
|
|
1007
|
+
if (taskResult.recordingId) {
|
|
411
1008
|
try {
|
|
412
1009
|
const recording = await waitForRecording(
|
|
413
|
-
taskResult.
|
|
1010
|
+
taskResult.recordingId,
|
|
414
1011
|
config,
|
|
415
1012
|
{ timeout: 6e4, pollInterval: 2e3 }
|
|
416
1013
|
);
|
|
@@ -420,12 +1017,12 @@ async function executeWithRecording(input, config = {}) {
|
|
|
420
1017
|
};
|
|
421
1018
|
} catch (error) {
|
|
422
1019
|
const errorRecording = {
|
|
423
|
-
id: taskResult.
|
|
1020
|
+
id: taskResult.recordingId,
|
|
424
1021
|
status: "ERROR",
|
|
425
1022
|
error: error instanceof Error ? error.message : String(error),
|
|
426
|
-
|
|
427
|
-
getWebp: (options) => getWebp(taskResult.
|
|
428
|
-
getErrors: () => getErrors(taskResult.
|
|
1023
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1024
|
+
getWebp: (options) => getWebp(taskResult.recordingId, config, options),
|
|
1025
|
+
getErrors: () => getErrors(taskResult.recordingId, config)
|
|
429
1026
|
};
|
|
430
1027
|
return {
|
|
431
1028
|
...taskResult,
|
|
@@ -451,8 +1048,8 @@ async function getErrors(recordingId, config = {}) {
|
|
|
451
1048
|
if (debug) console.error(`[Browser] getErrors error: ${response.status} - ${errorText}`);
|
|
452
1049
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
453
1050
|
}
|
|
454
|
-
const errors = await response.json();
|
|
455
|
-
if (debug) console.log(`[Browser] Found ${errors.
|
|
1051
|
+
const errors = mapErrorsResponse(await response.json());
|
|
1052
|
+
if (debug) console.log(`[Browser] Found ${errors.totalErrors} errors`);
|
|
456
1053
|
return errors;
|
|
457
1054
|
}
|
|
458
1055
|
function stringifyStructuredOutput(schema) {
|
|
@@ -485,6 +1082,84 @@ function parseStructuredTaskOutput(result, schema) {
|
|
|
485
1082
|
throw error;
|
|
486
1083
|
}
|
|
487
1084
|
}
|
|
1085
|
+
function mapTaskResult(api) {
|
|
1086
|
+
if (!api || typeof api !== "object") {
|
|
1087
|
+
return api;
|
|
1088
|
+
}
|
|
1089
|
+
return {
|
|
1090
|
+
success: api.success,
|
|
1091
|
+
result: api.result,
|
|
1092
|
+
error: api.error,
|
|
1093
|
+
stepsTaken: api.steps_taken,
|
|
1094
|
+
executionTimeMs: api.execution_time_ms,
|
|
1095
|
+
urls: api.urls,
|
|
1096
|
+
actionNames: api.action_names,
|
|
1097
|
+
errors: api.errors,
|
|
1098
|
+
modelActions: api.model_actions,
|
|
1099
|
+
isDone: api.is_done,
|
|
1100
|
+
actionHistory: api.action_history,
|
|
1101
|
+
actionResults: api.action_results,
|
|
1102
|
+
hasErrors: api.has_errors,
|
|
1103
|
+
numberOfSteps: api.number_of_steps,
|
|
1104
|
+
judgement: api.judgement,
|
|
1105
|
+
isValidated: api.is_validated,
|
|
1106
|
+
replayId: api.replay_id,
|
|
1107
|
+
replayUrl: api.replay_url,
|
|
1108
|
+
recordingId: api.recording_id,
|
|
1109
|
+
recordingStatus: api.recording_status,
|
|
1110
|
+
taskId: api.task_id,
|
|
1111
|
+
status: api.status,
|
|
1112
|
+
output: api.output,
|
|
1113
|
+
debugUrl: api.debug_url
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
function mapRecordingStatus(api) {
|
|
1117
|
+
return {
|
|
1118
|
+
id: api.id,
|
|
1119
|
+
status: api.status,
|
|
1120
|
+
replayUrl: api.replay_url,
|
|
1121
|
+
networkUrl: api.network_url,
|
|
1122
|
+
consoleUrl: api.console_url,
|
|
1123
|
+
videoUrl: api.video_url,
|
|
1124
|
+
totalEvents: api.total_events,
|
|
1125
|
+
fileSize: api.file_size,
|
|
1126
|
+
duration: api.duration,
|
|
1127
|
+
error: api.error,
|
|
1128
|
+
createdAt: api.created_at
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
function mapBrowserError(api) {
|
|
1132
|
+
return {
|
|
1133
|
+
type: api.type,
|
|
1134
|
+
message: api.message,
|
|
1135
|
+
url: api.url,
|
|
1136
|
+
timestamp: api.timestamp,
|
|
1137
|
+
screenshotUrl: api.screenshot_url,
|
|
1138
|
+
capturedAt: api.captured_at,
|
|
1139
|
+
status: api.status
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
function mapErrorsResponse(api) {
|
|
1143
|
+
return {
|
|
1144
|
+
recordingId: api.recording_id,
|
|
1145
|
+
totalErrors: api.total_errors,
|
|
1146
|
+
errors: Array.isArray(api.errors) ? api.errors.map(mapBrowserError) : []
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
function mapWebpResponse(api) {
|
|
1150
|
+
return {
|
|
1151
|
+
webpUrl: api.webp_url,
|
|
1152
|
+
cached: api.cached,
|
|
1153
|
+
width: api.width,
|
|
1154
|
+
fps: api.fps,
|
|
1155
|
+
maxDuration: api.max_duration,
|
|
1156
|
+
fileSize: api.file_size,
|
|
1157
|
+
maxSizeMb: api.max_size_mb,
|
|
1158
|
+
budgetMet: api.budget_met,
|
|
1159
|
+
qualityUsed: api.quality_used,
|
|
1160
|
+
attempts: api.attempts
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
488
1163
|
async function getTaskStatus(taskId, config) {
|
|
489
1164
|
const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;
|
|
490
1165
|
const debug = config.debug || false;
|
|
@@ -500,7 +1175,7 @@ async function getTaskStatus(taskId, config) {
|
|
|
500
1175
|
if (debug) console.error(`[Browser] getTaskStatus error: ${response.status} - ${errorText}`);
|
|
501
1176
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
502
1177
|
}
|
|
503
|
-
const result = await response.json();
|
|
1178
|
+
const result = mapTaskResult(await response.json());
|
|
504
1179
|
if (debug) console.log(`[Browser] Task status: ${result.status}`);
|
|
505
1180
|
return result;
|
|
506
1181
|
}
|
|
@@ -523,42 +1198,44 @@ async function pollTaskUntilComplete(taskId, config, pollConfig = {}) {
|
|
|
523
1198
|
throw new Error(`Task polling timeout after ${timeout}ms`);
|
|
524
1199
|
}
|
|
525
1200
|
function wrapTaskResponse(result, config) {
|
|
1201
|
+
const debugUrl = result.debugUrl ?? "";
|
|
526
1202
|
const wrapped = {
|
|
527
1203
|
...result,
|
|
528
|
-
|
|
529
|
-
|
|
1204
|
+
debugUrl,
|
|
1205
|
+
taskId: result.taskId || "",
|
|
1206
|
+
liveUrl: result.taskId ? generateLiveUrl(result.taskId, config) : debugUrl,
|
|
530
1207
|
complete: async (pollConfig) => {
|
|
531
|
-
if (result.
|
|
532
|
-
return pollTaskUntilComplete(result.
|
|
1208
|
+
if (result.taskId) {
|
|
1209
|
+
return pollTaskUntilComplete(result.taskId, config, pollConfig);
|
|
533
1210
|
}
|
|
534
|
-
if (result.
|
|
1211
|
+
if (result.recordingId) {
|
|
535
1212
|
const recording = await waitForRecording(
|
|
536
|
-
result.
|
|
1213
|
+
result.recordingId,
|
|
537
1214
|
config,
|
|
538
1215
|
pollConfig
|
|
539
1216
|
);
|
|
540
1217
|
return {
|
|
541
1218
|
...result,
|
|
542
|
-
|
|
1219
|
+
recordingStatus: recording.status
|
|
543
1220
|
};
|
|
544
1221
|
}
|
|
545
|
-
throw new Error("Cannot poll completion: no
|
|
1222
|
+
throw new Error("Cannot poll completion: no taskId or recordingId available");
|
|
546
1223
|
},
|
|
547
1224
|
// Add Steel live session helpers - either functional or error-throwing
|
|
548
|
-
getLiveUrl:
|
|
1225
|
+
getLiveUrl: debugUrl ? (options) => buildLiveUrl(debugUrl, options) : () => {
|
|
549
1226
|
throw new Error(
|
|
550
1227
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
|
|
551
1228
|
);
|
|
552
1229
|
},
|
|
553
|
-
getLiveIframe:
|
|
1230
|
+
getLiveIframe: debugUrl ? (optionsOrPreset) => {
|
|
554
1231
|
const options = resolvePreset(optionsOrPreset);
|
|
555
|
-
return buildLiveIframe(
|
|
1232
|
+
return buildLiveIframe(debugUrl, options);
|
|
556
1233
|
} : () => {
|
|
557
1234
|
throw new Error(
|
|
558
1235
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
|
|
559
1236
|
);
|
|
560
1237
|
},
|
|
561
|
-
getEmbedCode:
|
|
1238
|
+
getEmbedCode: debugUrl ? () => buildEmbedCode(debugUrl) : () => {
|
|
562
1239
|
throw new Error(
|
|
563
1240
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
|
|
564
1241
|
);
|
|
@@ -567,44 +1244,46 @@ function wrapTaskResponse(result, config) {
|
|
|
567
1244
|
return wrapped;
|
|
568
1245
|
}
|
|
569
1246
|
function wrapTaskResponseWithSchema(result, config, schema) {
|
|
1247
|
+
const debugUrl = result.debugUrl ?? "";
|
|
570
1248
|
const parsed = result.output ? parseStructuredTaskOutput(result, schema) : { ...result, parsed: null };
|
|
571
1249
|
const wrapped = {
|
|
572
1250
|
...parsed,
|
|
573
|
-
|
|
574
|
-
|
|
1251
|
+
debugUrl,
|
|
1252
|
+
taskId: result.taskId || "",
|
|
1253
|
+
liveUrl: result.taskId ? generateLiveUrl(result.taskId, config) : debugUrl,
|
|
575
1254
|
complete: async (pollConfig) => {
|
|
576
|
-
if (result.
|
|
577
|
-
const finalResult = await pollTaskUntilComplete(result.
|
|
1255
|
+
if (result.taskId) {
|
|
1256
|
+
const finalResult = await pollTaskUntilComplete(result.taskId, config, pollConfig);
|
|
578
1257
|
return parseStructuredTaskOutput(finalResult, schema);
|
|
579
1258
|
}
|
|
580
|
-
if (result.
|
|
1259
|
+
if (result.recordingId) {
|
|
581
1260
|
const recording = await waitForRecording(
|
|
582
|
-
result.
|
|
1261
|
+
result.recordingId,
|
|
583
1262
|
config,
|
|
584
1263
|
pollConfig
|
|
585
1264
|
);
|
|
586
1265
|
return {
|
|
587
1266
|
...parsed,
|
|
588
|
-
|
|
1267
|
+
recordingStatus: recording.status
|
|
589
1268
|
};
|
|
590
1269
|
}
|
|
591
|
-
throw new Error("Cannot poll completion: no
|
|
1270
|
+
throw new Error("Cannot poll completion: no taskId or recordingId available");
|
|
592
1271
|
},
|
|
593
1272
|
// Add Steel live session helpers - either functional or error-throwing
|
|
594
|
-
getLiveUrl:
|
|
1273
|
+
getLiveUrl: debugUrl ? (options) => buildLiveUrl(debugUrl, options) : () => {
|
|
595
1274
|
throw new Error(
|
|
596
1275
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions. "
|
|
597
1276
|
);
|
|
598
1277
|
},
|
|
599
|
-
getLiveIframe:
|
|
1278
|
+
getLiveIframe: debugUrl ? (optionsOrPreset) => {
|
|
600
1279
|
const options = resolvePreset(optionsOrPreset);
|
|
601
|
-
return buildLiveIframe(
|
|
1280
|
+
return buildLiveIframe(debugUrl, options);
|
|
602
1281
|
} : () => {
|
|
603
1282
|
throw new Error(
|
|
604
1283
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions."
|
|
605
1284
|
);
|
|
606
1285
|
},
|
|
607
|
-
getEmbedCode:
|
|
1286
|
+
getEmbedCode: debugUrl ? () => buildEmbedCode(debugUrl) : () => {
|
|
608
1287
|
throw new Error(
|
|
609
1288
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions."
|
|
610
1289
|
);
|
|
@@ -619,10 +1298,11 @@ async function getWebp(recordingId, config = {}, options = {}) {
|
|
|
619
1298
|
throw new Error("API key required for getWebp");
|
|
620
1299
|
}
|
|
621
1300
|
const params = new URLSearchParams();
|
|
622
|
-
if (options.
|
|
1301
|
+
if (options.maxDuration !== void 0) params.set("max_duration", String(options.maxDuration));
|
|
623
1302
|
if (options.fps !== void 0) params.set("fps", String(options.fps));
|
|
624
1303
|
if (options.width !== void 0) params.set("width", String(options.width));
|
|
625
1304
|
if (options.quality !== void 0) params.set("quality", String(options.quality));
|
|
1305
|
+
if (options.maxSizeMb !== void 0) params.set("max_size_mb", String(options.maxSizeMb));
|
|
626
1306
|
const url = `${apiUrl}/recordings/${recordingId}/webp${params.toString() ? "?" + params.toString() : ""}`;
|
|
627
1307
|
if (debug) console.log(`[Browser] getWebp: ${url}`);
|
|
628
1308
|
const response = await fetch(url, {
|
|
@@ -634,8 +1314,8 @@ async function getWebp(recordingId, config = {}, options = {}) {
|
|
|
634
1314
|
if (debug) console.error(`[Browser] getWebp error: ${response.status} - ${errorText}`);
|
|
635
1315
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
636
1316
|
}
|
|
637
|
-
const result = await response.json();
|
|
638
|
-
if (debug) console.log(`[Browser] WebP ready: ${result.
|
|
1317
|
+
const result = mapWebpResponse(await response.json());
|
|
1318
|
+
if (debug) console.log(`[Browser] WebP ready: ${result.webpUrl} (cached: ${result.cached})`);
|
|
639
1319
|
return result;
|
|
640
1320
|
}
|
|
641
1321
|
async function checkHealth(config = {}) {
|