@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.
Files changed (135) hide show
  1. package/dist/chunk-2AMEQAO2.js +46 -0
  2. package/dist/chunk-2AMEQAO2.js.map +1 -0
  3. package/dist/{chunk-EI4UKP24.js → chunk-2HMEZZKK.js} +2 -2
  4. package/dist/{chunk-EI4UKP24.js.map → chunk-2HMEZZKK.js.map} +1 -1
  5. package/dist/chunk-2VERUKO2.js +177 -0
  6. package/dist/chunk-2VERUKO2.js.map +1 -0
  7. package/dist/{chunk-VHOWYK66.js → chunk-43LQLGP6.js} +23 -33
  8. package/dist/chunk-43LQLGP6.js.map +1 -0
  9. package/dist/{chunk-LMUZ3NGC.js → chunk-73RV6EXR.js} +2 -2
  10. package/dist/{chunk-PBLPZ6AU.js → chunk-7D6TXC7X.js} +2 -2
  11. package/dist/{chunk-GU6DACME.js → chunk-O7LDZA52.js} +2 -2
  12. package/dist/{chunk-5QIWYEHJ.js → chunk-PE4KGDA6.js} +1 -8
  13. package/dist/chunk-PE4KGDA6.js.map +1 -0
  14. package/dist/{chunk-SQN4DUQS.js → chunk-Q6Y4R236.js} +26 -2
  15. package/dist/chunk-Q6Y4R236.js.map +1 -0
  16. package/dist/{chunk-PUGIOVSP.js → chunk-QAT5UVPX.js} +2 -2
  17. package/dist/{chunk-MIIJWDOQ.js → chunk-QJP62BXH.js} +166 -71
  18. package/dist/chunk-QJP62BXH.js.map +1 -0
  19. package/dist/{chunk-EYGBUH2R.js → chunk-R7IQWNSA.js} +8 -8
  20. package/dist/chunk-R7IQWNSA.js.map +1 -0
  21. package/dist/chunk-SI2CKRKJ.js +389 -0
  22. package/dist/chunk-SI2CKRKJ.js.map +1 -0
  23. package/dist/{chunk-4WLGDYWQ.js → chunk-TSENDJQI.js} +6 -6
  24. package/dist/chunk-TSENDJQI.js.map +1 -0
  25. package/dist/{chunk-IUG2FHNN.js → chunk-XH7P7HVT.js} +1 -8
  26. package/dist/chunk-XH7P7HVT.js.map +1 -0
  27. package/dist/{chunk-FNLNDMIX.js → chunk-YZ5NCWO2.js} +6 -6
  28. package/dist/chunk-YZ5NCWO2.js.map +1 -0
  29. package/dist/{chunk-IJ54DTJ3.js → chunk-ZYTAKEBW.js} +13 -13
  30. package/dist/client.cjs +770 -110
  31. package/dist/client.cjs.map +1 -1
  32. package/dist/client.d.ts +2 -0
  33. package/dist/client.js +16 -13
  34. package/dist/index.cjs +770 -110
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.js +16 -13
  38. package/dist/tools/browser/anthropic.cjs +58 -23
  39. package/dist/tools/browser/anthropic.cjs.map +1 -1
  40. package/dist/tools/browser/anthropic.js +7 -4
  41. package/dist/tools/browser/core.cjs +750 -70
  42. package/dist/tools/browser/core.cjs.map +1 -1
  43. package/dist/tools/browser/core.d.ts +30 -24
  44. package/dist/tools/browser/core.js +5 -2
  45. package/dist/tools/browser/errors.cjs +208 -0
  46. package/dist/tools/browser/errors.cjs.map +1 -0
  47. package/dist/tools/browser/errors.d.ts +158 -0
  48. package/dist/tools/browser/errors.js +22 -0
  49. package/dist/tools/browser/errors.js.map +1 -0
  50. package/dist/tools/browser/index.cjs +783 -85
  51. package/dist/tools/browser/index.cjs.map +1 -1
  52. package/dist/tools/browser/index.d.ts +5 -2
  53. package/dist/tools/browser/index.js +32 -9
  54. package/dist/tools/browser/index.js.map +1 -1
  55. package/dist/tools/browser/live.cjs +25 -1
  56. package/dist/tools/browser/live.cjs.map +1 -1
  57. package/dist/tools/browser/live.js +1 -1
  58. package/dist/tools/browser/openai.cjs +58 -23
  59. package/dist/tools/browser/openai.cjs.map +1 -1
  60. package/dist/tools/browser/openai.js +7 -4
  61. package/dist/tools/browser/profiles/core.cjs +670 -0
  62. package/dist/tools/browser/profiles/core.cjs.map +1 -0
  63. package/dist/tools/browser/profiles/core.d.ts +187 -0
  64. package/dist/tools/browser/profiles/core.js +29 -0
  65. package/dist/tools/browser/profiles/core.js.map +1 -0
  66. package/dist/tools/browser/profiles/index.cjs +670 -0
  67. package/dist/tools/browser/profiles/index.cjs.map +1 -0
  68. package/dist/tools/browser/profiles/index.d.ts +4 -0
  69. package/dist/tools/browser/profiles/index.js +29 -0
  70. package/dist/tools/browser/profiles/index.js.map +1 -0
  71. package/dist/tools/browser/profiles/types.cjs +74 -0
  72. package/dist/tools/browser/profiles/types.cjs.map +1 -0
  73. package/dist/tools/browser/profiles/types.d.ts +195 -0
  74. package/dist/tools/browser/profiles/types.js +16 -0
  75. package/dist/tools/browser/profiles/types.js.map +1 -0
  76. package/dist/tools/browser/prompts.cjs +1 -1
  77. package/dist/tools/browser/prompts.cjs.map +1 -1
  78. package/dist/tools/browser/prompts.d.ts +1 -1
  79. package/dist/tools/browser/prompts.js +1 -1
  80. package/dist/tools/browser/types.cjs.map +1 -1
  81. package/dist/tools/browser/types.d.ts +55 -51
  82. package/dist/tools/browser/vercel.cjs +60 -25
  83. package/dist/tools/browser/vercel.cjs.map +1 -1
  84. package/dist/tools/browser/vercel.d.ts +1 -1
  85. package/dist/tools/browser/vercel.js +7 -4
  86. package/dist/tools/fastapply/anthropic.cjs +0 -7
  87. package/dist/tools/fastapply/anthropic.cjs.map +1 -1
  88. package/dist/tools/fastapply/anthropic.js +1 -1
  89. package/dist/tools/fastapply/index.cjs +0 -14
  90. package/dist/tools/fastapply/index.cjs.map +1 -1
  91. package/dist/tools/fastapply/index.js +5 -5
  92. package/dist/tools/fastapply/openai.cjs +0 -7
  93. package/dist/tools/fastapply/openai.cjs.map +1 -1
  94. package/dist/tools/fastapply/openai.js +1 -1
  95. package/dist/tools/index.cjs +0 -14
  96. package/dist/tools/index.cjs.map +1 -1
  97. package/dist/tools/index.js +5 -5
  98. package/dist/tools/warp_grep/agent/runner.cjs +18 -98
  99. package/dist/tools/warp_grep/agent/runner.cjs.map +1 -1
  100. package/dist/tools/warp_grep/agent/runner.js +2 -3
  101. package/dist/tools/warp_grep/anthropic.cjs +18 -98
  102. package/dist/tools/warp_grep/anthropic.cjs.map +1 -1
  103. package/dist/tools/warp_grep/anthropic.js +8 -9
  104. package/dist/tools/warp_grep/client.cjs +18 -98
  105. package/dist/tools/warp_grep/client.cjs.map +1 -1
  106. package/dist/tools/warp_grep/client.js +5 -6
  107. package/dist/tools/warp_grep/gemini.cjs +18 -98
  108. package/dist/tools/warp_grep/gemini.cjs.map +1 -1
  109. package/dist/tools/warp_grep/gemini.js +7 -8
  110. package/dist/tools/warp_grep/gemini.js.map +1 -1
  111. package/dist/tools/warp_grep/harness.js +10 -10
  112. package/dist/tools/warp_grep/index.cjs +18 -98
  113. package/dist/tools/warp_grep/index.cjs.map +1 -1
  114. package/dist/tools/warp_grep/index.js +8 -9
  115. package/dist/tools/warp_grep/openai.cjs +18 -98
  116. package/dist/tools/warp_grep/openai.cjs.map +1 -1
  117. package/dist/tools/warp_grep/openai.js +8 -9
  118. package/dist/tools/warp_grep/vercel.cjs +18 -98
  119. package/dist/tools/warp_grep/vercel.cjs.map +1 -1
  120. package/dist/tools/warp_grep/vercel.js +8 -9
  121. package/dist/{vercel-CsnNSdze.d.ts → vercel-CVF27qFK.d.ts} +10 -10
  122. package/package.json +7 -2
  123. package/dist/chunk-4WLGDYWQ.js.map +0 -1
  124. package/dist/chunk-5QIWYEHJ.js.map +0 -1
  125. package/dist/chunk-EYGBUH2R.js.map +0 -1
  126. package/dist/chunk-FNLNDMIX.js.map +0 -1
  127. package/dist/chunk-IUG2FHNN.js.map +0 -1
  128. package/dist/chunk-MIIJWDOQ.js.map +0 -1
  129. package/dist/chunk-SQN4DUQS.js.map +0 -1
  130. package/dist/chunk-VHOWYK66.js.map +0 -1
  131. /package/dist/{chunk-LMUZ3NGC.js.map → chunk-73RV6EXR.js.map} +0 -0
  132. /package/dist/{chunk-PBLPZ6AU.js.map → chunk-7D6TXC7X.js.map} +0 -0
  133. /package/dist/{chunk-GU6DACME.js.map → chunk-O7LDZA52.js.map} +0 -0
  134. /package/dist/{chunk-PUGIOVSP.js.map → chunk-QAT5UVPX.js.map} +0 -0
  135. /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 url = new URL(debugUrl);
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.max_steps ?? 10,
813
+ max_steps: input.maxSteps ?? 10,
221
814
  model: input.model ?? "morph-computer-use-v0",
222
- viewport_width: input.viewport_width ?? 1280,
223
- viewport_height: input.viewport_height ?? 720,
224
- external_id: input.external_id,
225
- repo_id: input.repo_id,
226
- commit_id: input.commit_id,
227
- record_video: input.record_video ?? false,
228
- video_width: input.video_width ?? input.viewport_width ?? 1280,
229
- video_height: input.video_height ?? input.viewport_height ?? 720,
230
- allow_resizing: input.allow_resizing ?? false,
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
- console.log(`[Browser] \u2705 Task created: recording_id=${result.recording_id ?? "none"} debug_url=${result.debugUrl ? "available" : "none"}`);
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.max_steps !== void 0 && (input.max_steps < 1 || input.max_steps > 50)) {
893
+ if (input.maxSteps !== void 0 && (input.maxSteps < 1 || input.maxSteps > 50)) {
298
894
  return {
299
895
  success: false,
300
- error: "max_steps must be between 1 and 50. Use more steps for complex multi-page flows."
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.max_steps ?? 10}`);
305
- console.log(`[Browser] Recording: ${input.record_video ? "yes" : "no"} | Calling ${apiUrl}/browser-task`);
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.max_steps ?? 10,
915
+ max_steps: input.maxSteps ?? 10,
320
916
  model: input.model ?? "morph-computer-use-v0",
321
- viewport_width: input.viewport_width ?? 1280,
322
- viewport_height: input.viewport_height ?? 720,
323
- external_id: input.external_id,
324
- repo_id: input.repo_id,
325
- commit_id: input.commit_id,
326
- record_video: input.record_video ?? false,
327
- video_width: input.video_width ?? input.viewport_width ?? 1280,
328
- video_height: input.video_height ?? input.viewport_height ?? 720,
329
- allow_resizing: input.allow_resizing ?? false,
330
- structured_output: input.structured_output,
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 max_steps.`
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.steps_taken ?? 0} recordingId=${result.recording_id ?? "none"}`);
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.recording_id) {
1007
+ if (taskResult.recordingId) {
411
1008
  try {
412
1009
  const recording = await waitForRecording(
413
- taskResult.recording_id,
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.recording_id,
1020
+ id: taskResult.recordingId,
424
1021
  status: "ERROR",
425
1022
  error: error instanceof Error ? error.message : String(error),
426
- created_at: (/* @__PURE__ */ new Date()).toISOString(),
427
- getWebp: (options) => getWebp(taskResult.recording_id, config, options),
428
- getErrors: () => getErrors(taskResult.recording_id, config)
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.total_errors} 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
- task_id: result.task_id || "",
529
- liveUrl: result.task_id ? generateLiveUrl(result.task_id, config) : result.debugUrl || "",
1204
+ debugUrl,
1205
+ taskId: result.taskId || "",
1206
+ liveUrl: result.taskId ? generateLiveUrl(result.taskId, config) : debugUrl,
530
1207
  complete: async (pollConfig) => {
531
- if (result.task_id) {
532
- return pollTaskUntilComplete(result.task_id, config, pollConfig);
1208
+ if (result.taskId) {
1209
+ return pollTaskUntilComplete(result.taskId, config, pollConfig);
533
1210
  }
534
- if (result.recording_id) {
1211
+ if (result.recordingId) {
535
1212
  const recording = await waitForRecording(
536
- result.recording_id,
1213
+ result.recordingId,
537
1214
  config,
538
1215
  pollConfig
539
1216
  );
540
1217
  return {
541
1218
  ...result,
542
- recording_status: recording.status
1219
+ recordingStatus: recording.status
543
1220
  };
544
1221
  }
545
- throw new Error("Cannot poll completion: no task_id or recording_id available");
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: result.debugUrl ? (options) => buildLiveUrl(result.debugUrl, options) : () => {
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: result.debugUrl ? (optionsOrPreset) => {
1230
+ getLiveIframe: debugUrl ? (optionsOrPreset) => {
554
1231
  const options = resolvePreset(optionsOrPreset);
555
- return buildLiveIframe(result.debugUrl, options);
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: result.debugUrl ? () => buildEmbedCode(result.debugUrl) : () => {
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
- task_id: result.task_id || "",
574
- liveUrl: result.task_id ? generateLiveUrl(result.task_id, config) : result.debugUrl || "",
1251
+ debugUrl,
1252
+ taskId: result.taskId || "",
1253
+ liveUrl: result.taskId ? generateLiveUrl(result.taskId, config) : debugUrl,
575
1254
  complete: async (pollConfig) => {
576
- if (result.task_id) {
577
- const finalResult = await pollTaskUntilComplete(result.task_id, config, pollConfig);
1255
+ if (result.taskId) {
1256
+ const finalResult = await pollTaskUntilComplete(result.taskId, config, pollConfig);
578
1257
  return parseStructuredTaskOutput(finalResult, schema);
579
1258
  }
580
- if (result.recording_id) {
1259
+ if (result.recordingId) {
581
1260
  const recording = await waitForRecording(
582
- result.recording_id,
1261
+ result.recordingId,
583
1262
  config,
584
1263
  pollConfig
585
1264
  );
586
1265
  return {
587
1266
  ...parsed,
588
- recording_status: recording.status
1267
+ recordingStatus: recording.status
589
1268
  };
590
1269
  }
591
- throw new Error("Cannot poll completion: no task_id or recording_id available");
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: result.debugUrl ? (options) => buildLiveUrl(result.debugUrl, options) : () => {
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: result.debugUrl ? (optionsOrPreset) => {
1278
+ getLiveIframe: debugUrl ? (optionsOrPreset) => {
600
1279
  const options = resolvePreset(optionsOrPreset);
601
- return buildLiveIframe(result.debugUrl, options);
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: result.debugUrl ? () => buildEmbedCode(result.debugUrl) : () => {
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.max_duration !== void 0) params.set("max_duration", String(options.max_duration));
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.webp_url} (cached: ${result.cached})`);
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 = {}) {