@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
package/dist/client.cjs
CHANGED
|
@@ -440,7 +440,8 @@ function buildLiveUrl(debugUrl, options = {}) {
|
|
|
440
440
|
"debugUrl is required. Ensure your backend returns debugUrl in the task response. Contact support@morphllm.com if you need help."
|
|
441
441
|
);
|
|
442
442
|
}
|
|
443
|
-
const
|
|
443
|
+
const normalized = normalizeLiveUrl(debugUrl);
|
|
444
|
+
const url = new URL(normalized);
|
|
444
445
|
if (options.interactive !== void 0) {
|
|
445
446
|
url.searchParams.set("interactive", String(options.interactive));
|
|
446
447
|
}
|
|
@@ -458,6 +459,29 @@ function buildLiveUrl(debugUrl, options = {}) {
|
|
|
458
459
|
}
|
|
459
460
|
return url.toString();
|
|
460
461
|
}
|
|
462
|
+
function normalizeLiveUrl(debugUrl) {
|
|
463
|
+
const trimmed = debugUrl.trim();
|
|
464
|
+
if (!trimmed) {
|
|
465
|
+
return trimmed;
|
|
466
|
+
}
|
|
467
|
+
if (trimmed.startsWith("wss://") || trimmed.startsWith("ws://")) {
|
|
468
|
+
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
469
|
+
}
|
|
470
|
+
let url;
|
|
471
|
+
try {
|
|
472
|
+
url = new URL(trimmed);
|
|
473
|
+
} catch {
|
|
474
|
+
return trimmed;
|
|
475
|
+
}
|
|
476
|
+
if (url.protocol === "wss:" || url.protocol === "ws:") {
|
|
477
|
+
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
478
|
+
}
|
|
479
|
+
const wssParam = url.searchParams.get("wss");
|
|
480
|
+
if (wssParam && (wssParam.startsWith("wss://") || wssParam.startsWith("ws://"))) {
|
|
481
|
+
url.searchParams.set("wss", wssParam);
|
|
482
|
+
}
|
|
483
|
+
return url.toString();
|
|
484
|
+
}
|
|
461
485
|
function buildLiveIframe(debugUrl, options = {}) {
|
|
462
486
|
const {
|
|
463
487
|
width = "100%",
|
|
@@ -501,6 +525,570 @@ function resolvePreset(optionsOrPreset) {
|
|
|
501
525
|
return optionsOrPreset;
|
|
502
526
|
}
|
|
503
527
|
|
|
528
|
+
// tools/browser/errors.ts
|
|
529
|
+
var MorphError = class extends Error {
|
|
530
|
+
/** Error code for programmatic handling */
|
|
531
|
+
code;
|
|
532
|
+
/** Original cause of the error, if any */
|
|
533
|
+
cause;
|
|
534
|
+
constructor(message, code, cause) {
|
|
535
|
+
super(message);
|
|
536
|
+
this.name = "MorphError";
|
|
537
|
+
this.code = code;
|
|
538
|
+
this.cause = cause;
|
|
539
|
+
if (Error.captureStackTrace) {
|
|
540
|
+
Error.captureStackTrace(this, this.constructor);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Returns a JSON representation of the error for logging.
|
|
545
|
+
*/
|
|
546
|
+
toJSON() {
|
|
547
|
+
return {
|
|
548
|
+
name: this.name,
|
|
549
|
+
message: this.message,
|
|
550
|
+
code: this.code,
|
|
551
|
+
cause: this.cause?.message
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
var MorphValidationError = class extends MorphError {
|
|
556
|
+
/** The field that failed validation */
|
|
557
|
+
field;
|
|
558
|
+
constructor(message, field) {
|
|
559
|
+
super(message, "validation_error");
|
|
560
|
+
this.name = "MorphValidationError";
|
|
561
|
+
this.field = field;
|
|
562
|
+
}
|
|
563
|
+
toJSON() {
|
|
564
|
+
return {
|
|
565
|
+
...super.toJSON(),
|
|
566
|
+
field: this.field
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
var MorphAPIError = class extends MorphError {
|
|
571
|
+
/** HTTP status code */
|
|
572
|
+
statusCode;
|
|
573
|
+
/** Request ID for debugging (if available) */
|
|
574
|
+
requestId;
|
|
575
|
+
/** Raw response body */
|
|
576
|
+
rawResponse;
|
|
577
|
+
constructor(message, code, statusCode, options) {
|
|
578
|
+
super(message, code, options?.cause);
|
|
579
|
+
this.name = "MorphAPIError";
|
|
580
|
+
this.statusCode = statusCode;
|
|
581
|
+
this.requestId = options?.requestId;
|
|
582
|
+
this.rawResponse = options?.rawResponse;
|
|
583
|
+
}
|
|
584
|
+
toJSON() {
|
|
585
|
+
return {
|
|
586
|
+
...super.toJSON(),
|
|
587
|
+
statusCode: this.statusCode,
|
|
588
|
+
requestId: this.requestId
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
var MorphAuthenticationError = class extends MorphAPIError {
|
|
593
|
+
constructor(message = "Authentication required. Please provide a valid API key.") {
|
|
594
|
+
super(message, "authentication_required", 401);
|
|
595
|
+
this.name = "MorphAuthenticationError";
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
var MorphRateLimitError = class extends MorphAPIError {
|
|
599
|
+
/** When the rate limit resets (Unix timestamp) */
|
|
600
|
+
resetAt;
|
|
601
|
+
/** Number of seconds until reset */
|
|
602
|
+
retryAfter;
|
|
603
|
+
constructor(message = "Rate limit exceeded. Please retry later.", options) {
|
|
604
|
+
super(message, "rate_limit_exceeded", 429, { requestId: options?.requestId });
|
|
605
|
+
this.name = "MorphRateLimitError";
|
|
606
|
+
this.resetAt = options?.resetAt;
|
|
607
|
+
this.retryAfter = options?.retryAfter;
|
|
608
|
+
}
|
|
609
|
+
toJSON() {
|
|
610
|
+
return {
|
|
611
|
+
...super.toJSON(),
|
|
612
|
+
resetAt: this.resetAt,
|
|
613
|
+
retryAfter: this.retryAfter
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
var MorphNotFoundError = class extends MorphAPIError {
|
|
618
|
+
/** The type of resource that was not found */
|
|
619
|
+
resourceType;
|
|
620
|
+
/** The ID of the resource that was not found */
|
|
621
|
+
resourceId;
|
|
622
|
+
constructor(resourceType, resourceId) {
|
|
623
|
+
const message = resourceId ? `${resourceType} '${resourceId}' not found` : `${resourceType} not found`;
|
|
624
|
+
super(message, "resource_not_found", 404);
|
|
625
|
+
this.name = "MorphNotFoundError";
|
|
626
|
+
this.resourceType = resourceType;
|
|
627
|
+
this.resourceId = resourceId;
|
|
628
|
+
}
|
|
629
|
+
toJSON() {
|
|
630
|
+
return {
|
|
631
|
+
...super.toJSON(),
|
|
632
|
+
resourceType: this.resourceType,
|
|
633
|
+
resourceId: this.resourceId
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
var MorphProfileLimitError = class extends MorphAPIError {
|
|
638
|
+
/** Current number of profiles */
|
|
639
|
+
currentCount;
|
|
640
|
+
/** Maximum allowed profiles for the plan */
|
|
641
|
+
maxAllowed;
|
|
642
|
+
constructor(message = "Profile limit exceeded for your plan.", options) {
|
|
643
|
+
super(message, "profile_limit_exceeded", 403, { requestId: options?.requestId });
|
|
644
|
+
this.name = "MorphProfileLimitError";
|
|
645
|
+
this.currentCount = options?.currentCount;
|
|
646
|
+
this.maxAllowed = options?.maxAllowed;
|
|
647
|
+
}
|
|
648
|
+
toJSON() {
|
|
649
|
+
return {
|
|
650
|
+
...super.toJSON(),
|
|
651
|
+
currentCount: this.currentCount,
|
|
652
|
+
maxAllowed: this.maxAllowed
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
function parseAPIError(statusCode, responseText, requestId) {
|
|
657
|
+
let errorData = {};
|
|
658
|
+
try {
|
|
659
|
+
errorData = JSON.parse(responseText);
|
|
660
|
+
} catch {
|
|
661
|
+
}
|
|
662
|
+
const message = errorData.detail || errorData.message || responseText || "Unknown error";
|
|
663
|
+
const code = errorData.code;
|
|
664
|
+
switch (statusCode) {
|
|
665
|
+
case 401:
|
|
666
|
+
return new MorphAuthenticationError(message);
|
|
667
|
+
case 403:
|
|
668
|
+
if (code === "profile_limit_exceeded" || message.toLowerCase().includes("limit")) {
|
|
669
|
+
return new MorphProfileLimitError(message, { requestId });
|
|
670
|
+
}
|
|
671
|
+
return new MorphAPIError(message, "insufficient_permissions", statusCode, { requestId, rawResponse: responseText });
|
|
672
|
+
case 404:
|
|
673
|
+
if (message.toLowerCase().includes("profile")) {
|
|
674
|
+
return new MorphNotFoundError("Profile", void 0);
|
|
675
|
+
}
|
|
676
|
+
if (message.toLowerCase().includes("session")) {
|
|
677
|
+
return new MorphNotFoundError("Session", void 0);
|
|
678
|
+
}
|
|
679
|
+
return new MorphAPIError(message, "resource_not_found", statusCode, { requestId, rawResponse: responseText });
|
|
680
|
+
case 429:
|
|
681
|
+
return new MorphRateLimitError(message, { requestId });
|
|
682
|
+
case 422:
|
|
683
|
+
return new MorphAPIError(message, "validation_error", statusCode, { requestId, rawResponse: responseText });
|
|
684
|
+
case 500:
|
|
685
|
+
case 502:
|
|
686
|
+
case 503:
|
|
687
|
+
case 504:
|
|
688
|
+
return new MorphAPIError(message, "service_unavailable", statusCode, { requestId, rawResponse: responseText });
|
|
689
|
+
default:
|
|
690
|
+
return new MorphAPIError(message, "network_error", statusCode, { requestId, rawResponse: responseText });
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// tools/browser/profiles/types.ts
|
|
695
|
+
function transformProfile(api) {
|
|
696
|
+
return {
|
|
697
|
+
id: api.id,
|
|
698
|
+
name: api.name,
|
|
699
|
+
repoId: api.repo_id,
|
|
700
|
+
cookieDomains: api.cookie_domains,
|
|
701
|
+
lastUsedAt: api.last_used_at,
|
|
702
|
+
createdAt: api.created_at,
|
|
703
|
+
updatedAt: api.updated_at
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
function transformCreateInput(input) {
|
|
707
|
+
return {
|
|
708
|
+
name: input.name,
|
|
709
|
+
repo_id: input.repoId
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function transformSession(api) {
|
|
713
|
+
return {
|
|
714
|
+
sessionId: api.session_id,
|
|
715
|
+
debugUrl: api.debug_url || ""
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
function transformSaveInput(input) {
|
|
719
|
+
return {
|
|
720
|
+
session_id: input.sessionId,
|
|
721
|
+
profile_id: input.profileId
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
function transformStateResponse(api) {
|
|
725
|
+
return {
|
|
726
|
+
profileId: api.profile_id,
|
|
727
|
+
stateUrl: api.state_url,
|
|
728
|
+
expiresIn: api.expires_in
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// tools/browser/profiles/core.ts
|
|
733
|
+
var DEFAULT_API_URL = process.env.MORPH_ENVIRONMENT === "DEV" ? "http://localhost:8000" : "https://browser.morphllm.com";
|
|
734
|
+
var ProfilesClient = class {
|
|
735
|
+
config;
|
|
736
|
+
constructor(config) {
|
|
737
|
+
this.config = config;
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Create a new browser profile and immediately start a live session.
|
|
741
|
+
*
|
|
742
|
+
* @param input - Profile creation parameters
|
|
743
|
+
* @returns Profile setup handle with live URL + save()
|
|
744
|
+
* @throws {MorphValidationError} If input validation fails
|
|
745
|
+
* @throws {MorphProfileLimitError} If profile limit is exceeded
|
|
746
|
+
* @throws {MorphAuthenticationError} If API key is missing or invalid
|
|
747
|
+
*
|
|
748
|
+
* @example
|
|
749
|
+
* ```typescript
|
|
750
|
+
* const setup = await morph.browser.profiles.createProfile({
|
|
751
|
+
* name: 'LinkedIn Production',
|
|
752
|
+
* repoId: 'owner/repo'
|
|
753
|
+
* });
|
|
754
|
+
* console.log(setup.session.debugUrl);
|
|
755
|
+
* await setup.save();
|
|
756
|
+
* ```
|
|
757
|
+
*/
|
|
758
|
+
async createProfile(input) {
|
|
759
|
+
return createProfile(input, this.config);
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* List all profiles for the authenticated user.
|
|
763
|
+
*
|
|
764
|
+
* @param repoId - Optional repository ID to filter by
|
|
765
|
+
* @returns Array of profiles
|
|
766
|
+
*
|
|
767
|
+
* @example
|
|
768
|
+
* ```typescript
|
|
769
|
+
* // List all profiles
|
|
770
|
+
* const allProfiles = await morph.browser.profiles.listProfiles();
|
|
771
|
+
*
|
|
772
|
+
* // List profiles for a specific repo
|
|
773
|
+
* const repoProfiles = await morph.browser.profiles.listProfiles('owner/repo');
|
|
774
|
+
* ```
|
|
775
|
+
*/
|
|
776
|
+
async listProfiles(repoId) {
|
|
777
|
+
return listProfiles(this.config, repoId);
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Get a profile by ID with convenience methods.
|
|
781
|
+
*
|
|
782
|
+
* @param id - Profile ID
|
|
783
|
+
* @returns Profile with attached methods
|
|
784
|
+
* @throws {MorphNotFoundError} If profile is not found
|
|
785
|
+
*
|
|
786
|
+
* @example
|
|
787
|
+
* ```typescript
|
|
788
|
+
* const profile = await morph.browser.profiles.getProfile('profile-id');
|
|
789
|
+
* const state = await profile.getState();
|
|
790
|
+
* await profile.delete();
|
|
791
|
+
* ```
|
|
792
|
+
*/
|
|
793
|
+
async getProfile(id) {
|
|
794
|
+
return getProfile(id, this.config);
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Update a profile by opening a live session (no rename).
|
|
798
|
+
*
|
|
799
|
+
* @param id - Profile ID
|
|
800
|
+
* @returns Profile setup handle with live URL + save()
|
|
801
|
+
*/
|
|
802
|
+
async updateProfile(id) {
|
|
803
|
+
return updateProfile(id, this.config);
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Delete a profile.
|
|
807
|
+
*
|
|
808
|
+
* @param id - Profile ID
|
|
809
|
+
* @throws {MorphNotFoundError} If profile is not found
|
|
810
|
+
*/
|
|
811
|
+
async deleteProfile(id) {
|
|
812
|
+
return deleteProfile(id, this.config);
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Start a browser session for profile setup.
|
|
816
|
+
*
|
|
817
|
+
* Returns a live URL where the user can sign into accounts.
|
|
818
|
+
* After signing in, call `saveSession` to persist the state.
|
|
819
|
+
*
|
|
820
|
+
* @param input - Optional session parameters
|
|
821
|
+
* @returns Session with debug URL
|
|
822
|
+
*
|
|
823
|
+
* @example
|
|
824
|
+
* ```typescript
|
|
825
|
+
* const session = await morph.browser.profiles.startSession();
|
|
826
|
+
* console.log('Sign in at:', session.debugUrl);
|
|
827
|
+
* // Open debugUrl in browser, user signs in...
|
|
828
|
+
* await morph.browser.profiles.saveSession(session.sessionId, profile.id);
|
|
829
|
+
* ```
|
|
830
|
+
*/
|
|
831
|
+
async startSession(input) {
|
|
832
|
+
return startProfileSession(this.config, input);
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Save browser state from a session to a profile.
|
|
836
|
+
*
|
|
837
|
+
* Call this after the user is done signing into accounts.
|
|
838
|
+
* Extracts cookies, localStorage, and sessionStorage.
|
|
839
|
+
*
|
|
840
|
+
* @param sessionId - Browser session ID from startSession
|
|
841
|
+
* @param profileId - Profile ID to save state to
|
|
842
|
+
* @returns Updated profile with cookie domains
|
|
843
|
+
*/
|
|
844
|
+
async saveSession(sessionId, profileId) {
|
|
845
|
+
return saveProfileSession({ sessionId, profileId }, this.config);
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* List available repo IDs (discovery).
|
|
849
|
+
*
|
|
850
|
+
* @returns Repo summaries with profile counts
|
|
851
|
+
*/
|
|
852
|
+
async listRepos() {
|
|
853
|
+
return listRepos(this.config);
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Get the presigned URL for a profile's state.
|
|
857
|
+
*
|
|
858
|
+
* Use this to download the raw state JSON for debugging
|
|
859
|
+
* or to restore state manually.
|
|
860
|
+
*
|
|
861
|
+
* @param profileId - Profile ID
|
|
862
|
+
* @returns State URL with expiry information
|
|
863
|
+
*/
|
|
864
|
+
async getProfileState(profileId) {
|
|
865
|
+
return getProfileState(profileId, this.config);
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
function validateCreateInput(input) {
|
|
869
|
+
if (!input.name || typeof input.name !== "string") {
|
|
870
|
+
throw new MorphValidationError("name is required", "name");
|
|
871
|
+
}
|
|
872
|
+
const trimmedName = input.name.trim();
|
|
873
|
+
if (trimmedName.length === 0) {
|
|
874
|
+
throw new MorphValidationError("name cannot be empty", "name");
|
|
875
|
+
}
|
|
876
|
+
if (trimmedName.length > 100) {
|
|
877
|
+
throw new MorphValidationError("name must be 100 characters or less", "name");
|
|
878
|
+
}
|
|
879
|
+
if (!input.repoId || typeof input.repoId !== "string") {
|
|
880
|
+
throw new MorphValidationError("repoId is required", "repoId");
|
|
881
|
+
}
|
|
882
|
+
if (input.repoId.trim().length === 0) {
|
|
883
|
+
throw new MorphValidationError("repoId cannot be empty", "repoId");
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
function validateId(id, fieldName) {
|
|
887
|
+
if (!id || typeof id !== "string") {
|
|
888
|
+
throw new MorphValidationError(`${fieldName} is required`, fieldName);
|
|
889
|
+
}
|
|
890
|
+
if (id.trim().length === 0) {
|
|
891
|
+
throw new MorphValidationError(`${fieldName} cannot be empty`, fieldName);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
async function createProfile(input, config = {}) {
|
|
895
|
+
validateCreateInput(input);
|
|
896
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
897
|
+
const headers = buildHeaders(config);
|
|
898
|
+
const response = await fetchWithRetry(
|
|
899
|
+
`${apiUrl}/profiles`,
|
|
900
|
+
{
|
|
901
|
+
method: "POST",
|
|
902
|
+
headers,
|
|
903
|
+
body: JSON.stringify(transformCreateInput(input))
|
|
904
|
+
},
|
|
905
|
+
config.retryConfig
|
|
906
|
+
);
|
|
907
|
+
if (!response.ok) {
|
|
908
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
909
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
910
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
911
|
+
}
|
|
912
|
+
const apiProfile = await response.json();
|
|
913
|
+
const profile = transformProfile(apiProfile);
|
|
914
|
+
const session = await startProfileSession(config, { profileId: profile.id });
|
|
915
|
+
return buildProfileSetup(profile, session, config);
|
|
916
|
+
}
|
|
917
|
+
async function listProfiles(config = {}, repoId) {
|
|
918
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
919
|
+
const headers = buildHeaders(config);
|
|
920
|
+
const url = repoId ? `${apiUrl}/profiles?repo_id=${encodeURIComponent(repoId)}` : `${apiUrl}/profiles`;
|
|
921
|
+
const response = await fetchWithRetry(
|
|
922
|
+
url,
|
|
923
|
+
{ method: "GET", headers },
|
|
924
|
+
config.retryConfig
|
|
925
|
+
);
|
|
926
|
+
if (!response.ok) {
|
|
927
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
928
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
929
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
930
|
+
}
|
|
931
|
+
const data = await response.json();
|
|
932
|
+
return data.profiles.map(transformProfile);
|
|
933
|
+
}
|
|
934
|
+
async function getProfile(id, config = {}) {
|
|
935
|
+
validateId(id, "id");
|
|
936
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
937
|
+
const headers = buildHeaders(config);
|
|
938
|
+
const response = await fetchWithRetry(
|
|
939
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
940
|
+
{ method: "GET", headers },
|
|
941
|
+
config.retryConfig
|
|
942
|
+
);
|
|
943
|
+
if (!response.ok) {
|
|
944
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
945
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
946
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
947
|
+
}
|
|
948
|
+
const apiProfile = await response.json();
|
|
949
|
+
const profile = transformProfile(apiProfile);
|
|
950
|
+
return {
|
|
951
|
+
...profile,
|
|
952
|
+
getState: () => getProfileState(id, config),
|
|
953
|
+
delete: () => deleteProfile(id, config)
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
async function updateProfile(id, config = {}) {
|
|
957
|
+
validateId(id, "id");
|
|
958
|
+
const profile = await fetchProfile(id, config);
|
|
959
|
+
const session = await startProfileSession(config, { profileId: profile.id });
|
|
960
|
+
return buildProfileSetup(profile, session, config);
|
|
961
|
+
}
|
|
962
|
+
async function deleteProfile(id, config = {}) {
|
|
963
|
+
validateId(id, "id");
|
|
964
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
965
|
+
const headers = buildHeaders(config);
|
|
966
|
+
const response = await fetchWithRetry(
|
|
967
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
968
|
+
{ method: "DELETE", headers },
|
|
969
|
+
config.retryConfig
|
|
970
|
+
);
|
|
971
|
+
if (!response.ok) {
|
|
972
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
973
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
974
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
async function startProfileSession(config = {}, input) {
|
|
978
|
+
if (!config.apiKey) {
|
|
979
|
+
throw new MorphAuthenticationError();
|
|
980
|
+
}
|
|
981
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
982
|
+
const headers = buildHeaders(config);
|
|
983
|
+
const body = input?.profileId ? { profile_id: input.profileId } : {};
|
|
984
|
+
const response = await fetchWithRetry(
|
|
985
|
+
`${apiUrl}/profiles/session/start`,
|
|
986
|
+
{
|
|
987
|
+
method: "POST",
|
|
988
|
+
headers,
|
|
989
|
+
body: JSON.stringify(body)
|
|
990
|
+
},
|
|
991
|
+
config.retryConfig
|
|
992
|
+
);
|
|
993
|
+
if (!response.ok) {
|
|
994
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
995
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
996
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
997
|
+
}
|
|
998
|
+
const apiSession = await response.json();
|
|
999
|
+
return transformSession(apiSession);
|
|
1000
|
+
}
|
|
1001
|
+
async function saveProfileSession(input, config = {}) {
|
|
1002
|
+
validateId(input.sessionId, "sessionId");
|
|
1003
|
+
validateId(input.profileId, "profileId");
|
|
1004
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
1005
|
+
const headers = buildHeaders(config);
|
|
1006
|
+
const response = await fetchWithRetry(
|
|
1007
|
+
`${apiUrl}/profiles/session/save`,
|
|
1008
|
+
{
|
|
1009
|
+
method: "POST",
|
|
1010
|
+
headers,
|
|
1011
|
+
body: JSON.stringify(transformSaveInput(input))
|
|
1012
|
+
},
|
|
1013
|
+
config.retryConfig
|
|
1014
|
+
);
|
|
1015
|
+
if (!response.ok) {
|
|
1016
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1017
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1018
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1019
|
+
}
|
|
1020
|
+
const apiProfile = await response.json();
|
|
1021
|
+
return transformProfile(apiProfile);
|
|
1022
|
+
}
|
|
1023
|
+
async function listRepos(config = {}) {
|
|
1024
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
1025
|
+
const headers = buildHeaders(config);
|
|
1026
|
+
const response = await fetchWithRetry(
|
|
1027
|
+
`${apiUrl}/repos`,
|
|
1028
|
+
{ method: "GET", headers },
|
|
1029
|
+
config.retryConfig
|
|
1030
|
+
);
|
|
1031
|
+
if (!response.ok) {
|
|
1032
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1033
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1034
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1035
|
+
}
|
|
1036
|
+
const data = await response.json();
|
|
1037
|
+
const repos = Array.isArray(data?.repos) ? data.repos : [];
|
|
1038
|
+
return repos.map((repo) => ({
|
|
1039
|
+
repoId: repo.repo_id,
|
|
1040
|
+
repoFullName: repo.repo_full_name,
|
|
1041
|
+
profileCount: repo.profile_count ?? 0
|
|
1042
|
+
}));
|
|
1043
|
+
}
|
|
1044
|
+
async function getProfileState(profileId, config = {}) {
|
|
1045
|
+
validateId(profileId, "profileId");
|
|
1046
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
1047
|
+
const headers = buildHeaders(config);
|
|
1048
|
+
const response = await fetchWithRetry(
|
|
1049
|
+
`${apiUrl}/profiles/${encodeURIComponent(profileId)}/state`,
|
|
1050
|
+
{ method: "GET", headers },
|
|
1051
|
+
config.retryConfig
|
|
1052
|
+
);
|
|
1053
|
+
if (!response.ok) {
|
|
1054
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1055
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1056
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1057
|
+
}
|
|
1058
|
+
const apiState = await response.json();
|
|
1059
|
+
return transformStateResponse(apiState);
|
|
1060
|
+
}
|
|
1061
|
+
function buildHeaders(config) {
|
|
1062
|
+
const headers = { "Content-Type": "application/json" };
|
|
1063
|
+
if (config.apiKey) {
|
|
1064
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1065
|
+
}
|
|
1066
|
+
return headers;
|
|
1067
|
+
}
|
|
1068
|
+
async function fetchProfile(id, config) {
|
|
1069
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
1070
|
+
const headers = buildHeaders(config);
|
|
1071
|
+
const response = await fetchWithRetry(
|
|
1072
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
1073
|
+
{ method: "GET", headers },
|
|
1074
|
+
config.retryConfig
|
|
1075
|
+
);
|
|
1076
|
+
if (!response.ok) {
|
|
1077
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1078
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1079
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1080
|
+
}
|
|
1081
|
+
const apiProfile = await response.json();
|
|
1082
|
+
return transformProfile(apiProfile);
|
|
1083
|
+
}
|
|
1084
|
+
function buildProfileSetup(profile, session, config) {
|
|
1085
|
+
return {
|
|
1086
|
+
profile,
|
|
1087
|
+
session,
|
|
1088
|
+
save: () => saveProfileSession({ sessionId: session.sessionId, profileId: profile.id }, config)
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
|
|
504
1092
|
// tools/browser/core.ts
|
|
505
1093
|
var DEFAULT_CONFIG2 = {
|
|
506
1094
|
apiUrl: process.env.MORPH_ENVIRONMENT === "DEV" ? "http://localhost:8000" : "https://browser.morphllm.com",
|
|
@@ -510,11 +1098,16 @@ var DEFAULT_CONFIG2 = {
|
|
|
510
1098
|
};
|
|
511
1099
|
var BrowserClient = class {
|
|
512
1100
|
config;
|
|
1101
|
+
/**
|
|
1102
|
+
* Profile management - create and manage browser profiles for storing login state.
|
|
1103
|
+
*/
|
|
1104
|
+
profiles;
|
|
513
1105
|
constructor(config = {}) {
|
|
514
1106
|
this.config = {
|
|
515
1107
|
...DEFAULT_CONFIG2,
|
|
516
1108
|
...config
|
|
517
1109
|
};
|
|
1110
|
+
this.profiles = new ProfilesClient(this.config);
|
|
518
1111
|
}
|
|
519
1112
|
/**
|
|
520
1113
|
* Execute a browser automation task
|
|
@@ -537,19 +1130,21 @@ var BrowserClient = class {
|
|
|
537
1130
|
body: JSON.stringify({
|
|
538
1131
|
task: input.task,
|
|
539
1132
|
url: input.url,
|
|
540
|
-
max_steps: input.
|
|
1133
|
+
max_steps: input.maxSteps ?? 10,
|
|
541
1134
|
model: input.model ?? "morph-computer-use-v0",
|
|
542
|
-
viewport_width: input.
|
|
543
|
-
viewport_height: input.
|
|
544
|
-
external_id: input.
|
|
545
|
-
repo_id: input.
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
1135
|
+
viewport_width: input.viewportWidth ?? 1280,
|
|
1136
|
+
viewport_height: input.viewportHeight ?? 720,
|
|
1137
|
+
external_id: input.externalId,
|
|
1138
|
+
repo_id: input.repoId,
|
|
1139
|
+
repo_full_name: input.repoFullName,
|
|
1140
|
+
commit_id: input.commitId,
|
|
1141
|
+
record_video: input.recordVideo ?? false,
|
|
1142
|
+
video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
|
|
1143
|
+
video_height: input.videoHeight ?? input.viewportHeight ?? 720,
|
|
1144
|
+
allow_resizing: input.allowResizing ?? false,
|
|
551
1145
|
structured_output: "schema" in input ? stringifyStructuredOutput(input.schema) : void 0,
|
|
552
|
-
auth: input.auth
|
|
1146
|
+
auth: input.auth,
|
|
1147
|
+
profile_id: input.profileId
|
|
553
1148
|
})
|
|
554
1149
|
});
|
|
555
1150
|
if (!response.ok) {
|
|
@@ -557,9 +1152,10 @@ var BrowserClient = class {
|
|
|
557
1152
|
if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);
|
|
558
1153
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
559
1154
|
}
|
|
560
|
-
const result = await response.json();
|
|
1155
|
+
const result = mapTaskResult(await response.json());
|
|
561
1156
|
if (debug) {
|
|
562
|
-
|
|
1157
|
+
const debugUrl = result.debugUrl;
|
|
1158
|
+
console.log(`[Browser] \u2705 Task created: recordingId=${result.recordingId ?? "none"} debugUrl=${debugUrl ? "available" : "none"}`);
|
|
563
1159
|
}
|
|
564
1160
|
if ("schema" in input) {
|
|
565
1161
|
return wrapTaskResponseWithSchema(result, this.config, input.schema);
|
|
@@ -614,15 +1210,15 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
614
1210
|
error: 'Task description is required. Example: "Go to example.com and click the login button"'
|
|
615
1211
|
};
|
|
616
1212
|
}
|
|
617
|
-
if (input.
|
|
1213
|
+
if (input.maxSteps !== void 0 && (input.maxSteps < 1 || input.maxSteps > 50)) {
|
|
618
1214
|
return {
|
|
619
1215
|
success: false,
|
|
620
|
-
error: "
|
|
1216
|
+
error: "maxSteps must be between 1 and 50. Use more steps for complex multi-page flows."
|
|
621
1217
|
};
|
|
622
1218
|
}
|
|
623
1219
|
if (debug) {
|
|
624
|
-
console.log(`[Browser] Task: "${input.task.slice(0, 60)}..." url=${input.url || "none"} maxSteps=${input.
|
|
625
|
-
console.log(`[Browser] Recording: ${input.
|
|
1220
|
+
console.log(`[Browser] Task: "${input.task.slice(0, 60)}..." url=${input.url || "none"} maxSteps=${input.maxSteps ?? 10}`);
|
|
1221
|
+
console.log(`[Browser] Recording: ${input.recordVideo ? "yes" : "no"} | Calling ${apiUrl}/browser-task`);
|
|
626
1222
|
}
|
|
627
1223
|
const startTime = Date.now();
|
|
628
1224
|
try {
|
|
@@ -636,19 +1232,20 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
636
1232
|
body: JSON.stringify({
|
|
637
1233
|
task: input.task,
|
|
638
1234
|
url: input.url,
|
|
639
|
-
max_steps: input.
|
|
1235
|
+
max_steps: input.maxSteps ?? 10,
|
|
640
1236
|
model: input.model ?? "morph-computer-use-v0",
|
|
641
|
-
viewport_width: input.
|
|
642
|
-
viewport_height: input.
|
|
643
|
-
external_id: input.
|
|
644
|
-
repo_id: input.
|
|
645
|
-
commit_id: input.
|
|
646
|
-
record_video: input.
|
|
647
|
-
video_width: input.
|
|
648
|
-
video_height: input.
|
|
649
|
-
allow_resizing: input.
|
|
650
|
-
structured_output: input.
|
|
651
|
-
auth: input.auth
|
|
1237
|
+
viewport_width: input.viewportWidth ?? 1280,
|
|
1238
|
+
viewport_height: input.viewportHeight ?? 720,
|
|
1239
|
+
external_id: input.externalId,
|
|
1240
|
+
repo_id: input.repoId,
|
|
1241
|
+
commit_id: input.commitId,
|
|
1242
|
+
record_video: input.recordVideo ?? false,
|
|
1243
|
+
video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
|
|
1244
|
+
video_height: input.videoHeight ?? input.viewportHeight ?? 720,
|
|
1245
|
+
allow_resizing: input.allowResizing ?? false,
|
|
1246
|
+
structured_output: input.structuredOutput,
|
|
1247
|
+
auth: input.auth,
|
|
1248
|
+
profile_id: input.profileId
|
|
652
1249
|
})
|
|
653
1250
|
},
|
|
654
1251
|
config.retryConfig
|
|
@@ -656,17 +1253,17 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
656
1253
|
const response = await withTimeout(
|
|
657
1254
|
fetchPromise,
|
|
658
1255
|
timeout,
|
|
659
|
-
`Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing
|
|
1256
|
+
`Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing maxSteps.`
|
|
660
1257
|
);
|
|
661
1258
|
if (!response.ok) {
|
|
662
1259
|
const errorText = await response.text().catch(() => response.statusText);
|
|
663
1260
|
if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);
|
|
664
1261
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
665
1262
|
}
|
|
666
|
-
const result = await response.json();
|
|
1263
|
+
const result = mapTaskResult(await response.json());
|
|
667
1264
|
const elapsed = Date.now() - startTime;
|
|
668
1265
|
if (debug) {
|
|
669
|
-
console.log(`[Browser] \u2705 ${result.success ? "Success" : "Failed"} in ${elapsed}ms | steps=${result.
|
|
1266
|
+
console.log(`[Browser] \u2705 ${result.success ? "Success" : "Failed"} in ${elapsed}ms | steps=${result.stepsTaken ?? 0} recordingId=${result.recordingId ?? "none"}`);
|
|
670
1267
|
}
|
|
671
1268
|
return result;
|
|
672
1269
|
} catch (error) {
|
|
@@ -704,7 +1301,7 @@ async function getRecording(recordingId, config = {}) {
|
|
|
704
1301
|
if (debug) console.error(`[Browser] getRecording error: ${response.status} - ${errorText}`);
|
|
705
1302
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
706
1303
|
}
|
|
707
|
-
const data = await response.json();
|
|
1304
|
+
const data = mapRecordingStatus(await response.json());
|
|
708
1305
|
if (debug) console.log(`[Browser] Recording status: ${data.status}`);
|
|
709
1306
|
return {
|
|
710
1307
|
...data,
|
|
@@ -727,10 +1324,10 @@ async function waitForRecording(recordingId, config = {}, options = {}) {
|
|
|
727
1324
|
}
|
|
728
1325
|
async function executeWithRecording(input, config = {}) {
|
|
729
1326
|
const taskResult = await executeBrowserTask(input, config);
|
|
730
|
-
if (taskResult.
|
|
1327
|
+
if (taskResult.recordingId) {
|
|
731
1328
|
try {
|
|
732
1329
|
const recording = await waitForRecording(
|
|
733
|
-
taskResult.
|
|
1330
|
+
taskResult.recordingId,
|
|
734
1331
|
config,
|
|
735
1332
|
{ timeout: 6e4, pollInterval: 2e3 }
|
|
736
1333
|
);
|
|
@@ -740,12 +1337,12 @@ async function executeWithRecording(input, config = {}) {
|
|
|
740
1337
|
};
|
|
741
1338
|
} catch (error) {
|
|
742
1339
|
const errorRecording = {
|
|
743
|
-
id: taskResult.
|
|
1340
|
+
id: taskResult.recordingId,
|
|
744
1341
|
status: "ERROR",
|
|
745
1342
|
error: error instanceof Error ? error.message : String(error),
|
|
746
|
-
|
|
747
|
-
getWebp: (options) => getWebp(taskResult.
|
|
748
|
-
getErrors: () => getErrors(taskResult.
|
|
1343
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1344
|
+
getWebp: (options) => getWebp(taskResult.recordingId, config, options),
|
|
1345
|
+
getErrors: () => getErrors(taskResult.recordingId, config)
|
|
749
1346
|
};
|
|
750
1347
|
return {
|
|
751
1348
|
...taskResult,
|
|
@@ -771,8 +1368,8 @@ async function getErrors(recordingId, config = {}) {
|
|
|
771
1368
|
if (debug) console.error(`[Browser] getErrors error: ${response.status} - ${errorText}`);
|
|
772
1369
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
773
1370
|
}
|
|
774
|
-
const errors = await response.json();
|
|
775
|
-
if (debug) console.log(`[Browser] Found ${errors.
|
|
1371
|
+
const errors = mapErrorsResponse(await response.json());
|
|
1372
|
+
if (debug) console.log(`[Browser] Found ${errors.totalErrors} errors`);
|
|
776
1373
|
return errors;
|
|
777
1374
|
}
|
|
778
1375
|
function stringifyStructuredOutput(schema) {
|
|
@@ -805,6 +1402,84 @@ function parseStructuredTaskOutput(result, schema) {
|
|
|
805
1402
|
throw error;
|
|
806
1403
|
}
|
|
807
1404
|
}
|
|
1405
|
+
function mapTaskResult(api) {
|
|
1406
|
+
if (!api || typeof api !== "object") {
|
|
1407
|
+
return api;
|
|
1408
|
+
}
|
|
1409
|
+
return {
|
|
1410
|
+
success: api.success,
|
|
1411
|
+
result: api.result,
|
|
1412
|
+
error: api.error,
|
|
1413
|
+
stepsTaken: api.steps_taken,
|
|
1414
|
+
executionTimeMs: api.execution_time_ms,
|
|
1415
|
+
urls: api.urls,
|
|
1416
|
+
actionNames: api.action_names,
|
|
1417
|
+
errors: api.errors,
|
|
1418
|
+
modelActions: api.model_actions,
|
|
1419
|
+
isDone: api.is_done,
|
|
1420
|
+
actionHistory: api.action_history,
|
|
1421
|
+
actionResults: api.action_results,
|
|
1422
|
+
hasErrors: api.has_errors,
|
|
1423
|
+
numberOfSteps: api.number_of_steps,
|
|
1424
|
+
judgement: api.judgement,
|
|
1425
|
+
isValidated: api.is_validated,
|
|
1426
|
+
replayId: api.replay_id,
|
|
1427
|
+
replayUrl: api.replay_url,
|
|
1428
|
+
recordingId: api.recording_id,
|
|
1429
|
+
recordingStatus: api.recording_status,
|
|
1430
|
+
taskId: api.task_id,
|
|
1431
|
+
status: api.status,
|
|
1432
|
+
output: api.output,
|
|
1433
|
+
debugUrl: api.debug_url
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
function mapRecordingStatus(api) {
|
|
1437
|
+
return {
|
|
1438
|
+
id: api.id,
|
|
1439
|
+
status: api.status,
|
|
1440
|
+
replayUrl: api.replay_url,
|
|
1441
|
+
networkUrl: api.network_url,
|
|
1442
|
+
consoleUrl: api.console_url,
|
|
1443
|
+
videoUrl: api.video_url,
|
|
1444
|
+
totalEvents: api.total_events,
|
|
1445
|
+
fileSize: api.file_size,
|
|
1446
|
+
duration: api.duration,
|
|
1447
|
+
error: api.error,
|
|
1448
|
+
createdAt: api.created_at
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
function mapBrowserError(api) {
|
|
1452
|
+
return {
|
|
1453
|
+
type: api.type,
|
|
1454
|
+
message: api.message,
|
|
1455
|
+
url: api.url,
|
|
1456
|
+
timestamp: api.timestamp,
|
|
1457
|
+
screenshotUrl: api.screenshot_url,
|
|
1458
|
+
capturedAt: api.captured_at,
|
|
1459
|
+
status: api.status
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
function mapErrorsResponse(api) {
|
|
1463
|
+
return {
|
|
1464
|
+
recordingId: api.recording_id,
|
|
1465
|
+
totalErrors: api.total_errors,
|
|
1466
|
+
errors: Array.isArray(api.errors) ? api.errors.map(mapBrowserError) : []
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
function mapWebpResponse(api) {
|
|
1470
|
+
return {
|
|
1471
|
+
webpUrl: api.webp_url,
|
|
1472
|
+
cached: api.cached,
|
|
1473
|
+
width: api.width,
|
|
1474
|
+
fps: api.fps,
|
|
1475
|
+
maxDuration: api.max_duration,
|
|
1476
|
+
fileSize: api.file_size,
|
|
1477
|
+
maxSizeMb: api.max_size_mb,
|
|
1478
|
+
budgetMet: api.budget_met,
|
|
1479
|
+
qualityUsed: api.quality_used,
|
|
1480
|
+
attempts: api.attempts
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
808
1483
|
async function getTaskStatus(taskId, config) {
|
|
809
1484
|
const apiUrl = config.apiUrl || DEFAULT_CONFIG2.apiUrl;
|
|
810
1485
|
const debug = config.debug || false;
|
|
@@ -820,7 +1495,7 @@ async function getTaskStatus(taskId, config) {
|
|
|
820
1495
|
if (debug) console.error(`[Browser] getTaskStatus error: ${response.status} - ${errorText}`);
|
|
821
1496
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
822
1497
|
}
|
|
823
|
-
const result = await response.json();
|
|
1498
|
+
const result = mapTaskResult(await response.json());
|
|
824
1499
|
if (debug) console.log(`[Browser] Task status: ${result.status}`);
|
|
825
1500
|
return result;
|
|
826
1501
|
}
|
|
@@ -843,42 +1518,44 @@ async function pollTaskUntilComplete(taskId, config, pollConfig = {}) {
|
|
|
843
1518
|
throw new Error(`Task polling timeout after ${timeout}ms`);
|
|
844
1519
|
}
|
|
845
1520
|
function wrapTaskResponse(result, config) {
|
|
1521
|
+
const debugUrl = result.debugUrl ?? "";
|
|
846
1522
|
const wrapped = {
|
|
847
1523
|
...result,
|
|
848
|
-
|
|
849
|
-
|
|
1524
|
+
debugUrl,
|
|
1525
|
+
taskId: result.taskId || "",
|
|
1526
|
+
liveUrl: result.taskId ? generateLiveUrl(result.taskId, config) : debugUrl,
|
|
850
1527
|
complete: async (pollConfig) => {
|
|
851
|
-
if (result.
|
|
852
|
-
return pollTaskUntilComplete(result.
|
|
1528
|
+
if (result.taskId) {
|
|
1529
|
+
return pollTaskUntilComplete(result.taskId, config, pollConfig);
|
|
853
1530
|
}
|
|
854
|
-
if (result.
|
|
1531
|
+
if (result.recordingId) {
|
|
855
1532
|
const recording = await waitForRecording(
|
|
856
|
-
result.
|
|
1533
|
+
result.recordingId,
|
|
857
1534
|
config,
|
|
858
1535
|
pollConfig
|
|
859
1536
|
);
|
|
860
1537
|
return {
|
|
861
1538
|
...result,
|
|
862
|
-
|
|
1539
|
+
recordingStatus: recording.status
|
|
863
1540
|
};
|
|
864
1541
|
}
|
|
865
|
-
throw new Error("Cannot poll completion: no
|
|
1542
|
+
throw new Error("Cannot poll completion: no taskId or recordingId available");
|
|
866
1543
|
},
|
|
867
1544
|
// Add Steel live session helpers - either functional or error-throwing
|
|
868
|
-
getLiveUrl:
|
|
1545
|
+
getLiveUrl: debugUrl ? (options) => buildLiveUrl(debugUrl, options) : () => {
|
|
869
1546
|
throw new Error(
|
|
870
1547
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
|
|
871
1548
|
);
|
|
872
1549
|
},
|
|
873
|
-
getLiveIframe:
|
|
1550
|
+
getLiveIframe: debugUrl ? (optionsOrPreset) => {
|
|
874
1551
|
const options = resolvePreset(optionsOrPreset);
|
|
875
|
-
return buildLiveIframe(
|
|
1552
|
+
return buildLiveIframe(debugUrl, options);
|
|
876
1553
|
} : () => {
|
|
877
1554
|
throw new Error(
|
|
878
1555
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
|
|
879
1556
|
);
|
|
880
1557
|
},
|
|
881
|
-
getEmbedCode:
|
|
1558
|
+
getEmbedCode: debugUrl ? () => buildEmbedCode(debugUrl) : () => {
|
|
882
1559
|
throw new Error(
|
|
883
1560
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
|
|
884
1561
|
);
|
|
@@ -887,44 +1564,46 @@ function wrapTaskResponse(result, config) {
|
|
|
887
1564
|
return wrapped;
|
|
888
1565
|
}
|
|
889
1566
|
function wrapTaskResponseWithSchema(result, config, schema) {
|
|
1567
|
+
const debugUrl = result.debugUrl ?? "";
|
|
890
1568
|
const parsed = result.output ? parseStructuredTaskOutput(result, schema) : { ...result, parsed: null };
|
|
891
1569
|
const wrapped = {
|
|
892
1570
|
...parsed,
|
|
893
|
-
|
|
894
|
-
|
|
1571
|
+
debugUrl,
|
|
1572
|
+
taskId: result.taskId || "",
|
|
1573
|
+
liveUrl: result.taskId ? generateLiveUrl(result.taskId, config) : debugUrl,
|
|
895
1574
|
complete: async (pollConfig) => {
|
|
896
|
-
if (result.
|
|
897
|
-
const finalResult = await pollTaskUntilComplete(result.
|
|
1575
|
+
if (result.taskId) {
|
|
1576
|
+
const finalResult = await pollTaskUntilComplete(result.taskId, config, pollConfig);
|
|
898
1577
|
return parseStructuredTaskOutput(finalResult, schema);
|
|
899
1578
|
}
|
|
900
|
-
if (result.
|
|
1579
|
+
if (result.recordingId) {
|
|
901
1580
|
const recording = await waitForRecording(
|
|
902
|
-
result.
|
|
1581
|
+
result.recordingId,
|
|
903
1582
|
config,
|
|
904
1583
|
pollConfig
|
|
905
1584
|
);
|
|
906
1585
|
return {
|
|
907
1586
|
...parsed,
|
|
908
|
-
|
|
1587
|
+
recordingStatus: recording.status
|
|
909
1588
|
};
|
|
910
1589
|
}
|
|
911
|
-
throw new Error("Cannot poll completion: no
|
|
1590
|
+
throw new Error("Cannot poll completion: no taskId or recordingId available");
|
|
912
1591
|
},
|
|
913
1592
|
// Add Steel live session helpers - either functional or error-throwing
|
|
914
|
-
getLiveUrl:
|
|
1593
|
+
getLiveUrl: debugUrl ? (options) => buildLiveUrl(debugUrl, options) : () => {
|
|
915
1594
|
throw new Error(
|
|
916
1595
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions. "
|
|
917
1596
|
);
|
|
918
1597
|
},
|
|
919
|
-
getLiveIframe:
|
|
1598
|
+
getLiveIframe: debugUrl ? (optionsOrPreset) => {
|
|
920
1599
|
const options = resolvePreset(optionsOrPreset);
|
|
921
|
-
return buildLiveIframe(
|
|
1600
|
+
return buildLiveIframe(debugUrl, options);
|
|
922
1601
|
} : () => {
|
|
923
1602
|
throw new Error(
|
|
924
1603
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions."
|
|
925
1604
|
);
|
|
926
1605
|
},
|
|
927
|
-
getEmbedCode:
|
|
1606
|
+
getEmbedCode: debugUrl ? () => buildEmbedCode(debugUrl) : () => {
|
|
928
1607
|
throw new Error(
|
|
929
1608
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions."
|
|
930
1609
|
);
|
|
@@ -939,10 +1618,11 @@ async function getWebp(recordingId, config = {}, options = {}) {
|
|
|
939
1618
|
throw new Error("API key required for getWebp");
|
|
940
1619
|
}
|
|
941
1620
|
const params = new URLSearchParams();
|
|
942
|
-
if (options.
|
|
1621
|
+
if (options.maxDuration !== void 0) params.set("max_duration", String(options.maxDuration));
|
|
943
1622
|
if (options.fps !== void 0) params.set("fps", String(options.fps));
|
|
944
1623
|
if (options.width !== void 0) params.set("width", String(options.width));
|
|
945
1624
|
if (options.quality !== void 0) params.set("quality", String(options.quality));
|
|
1625
|
+
if (options.maxSizeMb !== void 0) params.set("max_size_mb", String(options.maxSizeMb));
|
|
946
1626
|
const url = `${apiUrl}/recordings/${recordingId}/webp${params.toString() ? "?" + params.toString() : ""}`;
|
|
947
1627
|
if (debug) console.log(`[Browser] getWebp: ${url}`);
|
|
948
1628
|
const response = await fetch(url, {
|
|
@@ -954,8 +1634,8 @@ async function getWebp(recordingId, config = {}, options = {}) {
|
|
|
954
1634
|
if (debug) console.error(`[Browser] getWebp error: ${response.status} - ${errorText}`);
|
|
955
1635
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
956
1636
|
}
|
|
957
|
-
const result = await response.json();
|
|
958
|
-
if (debug) console.log(`[Browser] WebP ready: ${result.
|
|
1637
|
+
const result = mapWebpResponse(await response.json());
|
|
1638
|
+
if (debug) console.log(`[Browser] WebP ready: ${result.webpUrl} (cached: ${result.cached})`);
|
|
959
1639
|
return result;
|
|
960
1640
|
}
|
|
961
1641
|
async function checkHealth(config = {}) {
|
|
@@ -1851,42 +2531,36 @@ function enforceContextLimit(messages, maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS
|
|
|
1851
2531
|
}
|
|
1852
2532
|
|
|
1853
2533
|
// tools/warp_grep/agent/runner.ts
|
|
2534
|
+
var import_openai = __toESM(require("openai"), 1);
|
|
1854
2535
|
var import_path3 = __toESM(require("path"), 1);
|
|
1855
2536
|
var parser = new LLMResponseParser();
|
|
1856
|
-
var
|
|
2537
|
+
var DEFAULT_API_URL2 = "https://api.morphllm.com";
|
|
1857
2538
|
async function callModel(messages, model, options = {}) {
|
|
1858
|
-
const baseUrl = options.morphApiUrl ||
|
|
2539
|
+
const baseUrl = options.morphApiUrl || DEFAULT_API_URL2;
|
|
1859
2540
|
const apiKey = options.morphApiKey || process.env.MORPH_API_KEY || "";
|
|
1860
|
-
const fetchPromise = fetchWithRetry(
|
|
1861
|
-
`${baseUrl}/v1/chat/completions`,
|
|
1862
|
-
{
|
|
1863
|
-
method: "POST",
|
|
1864
|
-
headers: {
|
|
1865
|
-
"Content-Type": "application/json",
|
|
1866
|
-
Authorization: `Bearer ${apiKey}`
|
|
1867
|
-
},
|
|
1868
|
-
body: JSON.stringify({
|
|
1869
|
-
model,
|
|
1870
|
-
temperature: 0,
|
|
1871
|
-
max_tokens: 1024,
|
|
1872
|
-
repetition_penalty: 1.05,
|
|
1873
|
-
messages
|
|
1874
|
-
})
|
|
1875
|
-
},
|
|
1876
|
-
options.retryConfig
|
|
1877
|
-
);
|
|
1878
2541
|
const timeoutMs = options.timeout ?? AGENT_CONFIG.TIMEOUT_MS;
|
|
1879
|
-
const
|
|
1880
|
-
|
|
1881
|
-
|
|
2542
|
+
const client = new import_openai.default({
|
|
2543
|
+
apiKey,
|
|
2544
|
+
baseURL: baseUrl,
|
|
2545
|
+
maxRetries: options.retryConfig?.maxRetries,
|
|
2546
|
+
timeout: timeoutMs
|
|
2547
|
+
});
|
|
2548
|
+
let data;
|
|
2549
|
+
try {
|
|
2550
|
+
data = await client.chat.completions.create({
|
|
2551
|
+
model,
|
|
2552
|
+
temperature: 0,
|
|
2553
|
+
max_tokens: 1024,
|
|
2554
|
+
messages
|
|
2555
|
+
});
|
|
2556
|
+
} catch (error) {
|
|
2557
|
+
if (error instanceof import_openai.default.APIError && error.status === 404) {
|
|
1882
2558
|
throw new Error(
|
|
1883
2559
|
"The endpoint you are trying to call is likely deprecated. Please update with: npm cache clean --force && npx -y @morphllm/morphmcp@latest or visit: https://morphllm.com/mcp"
|
|
1884
2560
|
);
|
|
1885
2561
|
}
|
|
1886
|
-
|
|
1887
|
-
throw new Error(`morph-warp-grep error ${resp.status}: ${t}`);
|
|
2562
|
+
throw error;
|
|
1888
2563
|
}
|
|
1889
|
-
const data = await resp.json();
|
|
1890
2564
|
const content = data?.choices?.[0]?.message?.content;
|
|
1891
2565
|
if (!content || typeof content !== "string") {
|
|
1892
2566
|
throw new Error("Invalid response from model");
|
|
@@ -3842,13 +4516,6 @@ function formatResult3(result) {
|
|
|
3842
4516
|
changes.linesRemoved && `-${changes.linesRemoved} lines`,
|
|
3843
4517
|
changes.linesModified && `~${changes.linesModified} lines modified`
|
|
3844
4518
|
].filter(Boolean).join(", ");
|
|
3845
|
-
if (result.udiff) {
|
|
3846
|
-
return `Successfully applied changes to ${result.filepath}:
|
|
3847
|
-
|
|
3848
|
-
${result.udiff}
|
|
3849
|
-
|
|
3850
|
-
Summary: ${summary}`;
|
|
3851
|
-
}
|
|
3852
4519
|
return `Successfully applied changes to ${result.filepath}. ${summary}`;
|
|
3853
4520
|
}
|
|
3854
4521
|
function createEditFileTool(config = {}) {
|
|
@@ -4049,13 +4716,6 @@ function formatResult5(result) {
|
|
|
4049
4716
|
changes.linesRemoved && `-${changes.linesRemoved} lines`,
|
|
4050
4717
|
changes.linesModified && `~${changes.linesModified} lines modified`
|
|
4051
4718
|
].filter(Boolean).join(", ");
|
|
4052
|
-
if (result.udiff) {
|
|
4053
|
-
return `Successfully applied changes to ${result.filepath}:
|
|
4054
|
-
|
|
4055
|
-
${result.udiff}
|
|
4056
|
-
|
|
4057
|
-
Summary: ${summary}`;
|
|
4058
|
-
}
|
|
4059
4719
|
return `Successfully applied changes to ${result.filepath}. ${summary}`;
|
|
4060
4720
|
}
|
|
4061
4721
|
function createEditFileTool2(config = {}) {
|