@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/index.cjs
CHANGED
|
@@ -454,7 +454,8 @@ function buildLiveUrl(debugUrl, options = {}) {
|
|
|
454
454
|
"debugUrl is required. Ensure your backend returns debugUrl in the task response. Contact support@morphllm.com if you need help."
|
|
455
455
|
);
|
|
456
456
|
}
|
|
457
|
-
const
|
|
457
|
+
const normalized = normalizeLiveUrl(debugUrl);
|
|
458
|
+
const url = new URL(normalized);
|
|
458
459
|
if (options.interactive !== void 0) {
|
|
459
460
|
url.searchParams.set("interactive", String(options.interactive));
|
|
460
461
|
}
|
|
@@ -472,6 +473,29 @@ function buildLiveUrl(debugUrl, options = {}) {
|
|
|
472
473
|
}
|
|
473
474
|
return url.toString();
|
|
474
475
|
}
|
|
476
|
+
function normalizeLiveUrl(debugUrl) {
|
|
477
|
+
const trimmed = debugUrl.trim();
|
|
478
|
+
if (!trimmed) {
|
|
479
|
+
return trimmed;
|
|
480
|
+
}
|
|
481
|
+
if (trimmed.startsWith("wss://") || trimmed.startsWith("ws://")) {
|
|
482
|
+
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
483
|
+
}
|
|
484
|
+
let url;
|
|
485
|
+
try {
|
|
486
|
+
url = new URL(trimmed);
|
|
487
|
+
} catch {
|
|
488
|
+
return trimmed;
|
|
489
|
+
}
|
|
490
|
+
if (url.protocol === "wss:" || url.protocol === "ws:") {
|
|
491
|
+
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
492
|
+
}
|
|
493
|
+
const wssParam = url.searchParams.get("wss");
|
|
494
|
+
if (wssParam && (wssParam.startsWith("wss://") || wssParam.startsWith("ws://"))) {
|
|
495
|
+
url.searchParams.set("wss", wssParam);
|
|
496
|
+
}
|
|
497
|
+
return url.toString();
|
|
498
|
+
}
|
|
475
499
|
function buildLiveIframe(debugUrl, options = {}) {
|
|
476
500
|
const {
|
|
477
501
|
width = "100%",
|
|
@@ -515,6 +539,570 @@ function resolvePreset(optionsOrPreset) {
|
|
|
515
539
|
return optionsOrPreset;
|
|
516
540
|
}
|
|
517
541
|
|
|
542
|
+
// tools/browser/errors.ts
|
|
543
|
+
var MorphError = class extends Error {
|
|
544
|
+
/** Error code for programmatic handling */
|
|
545
|
+
code;
|
|
546
|
+
/** Original cause of the error, if any */
|
|
547
|
+
cause;
|
|
548
|
+
constructor(message, code, cause) {
|
|
549
|
+
super(message);
|
|
550
|
+
this.name = "MorphError";
|
|
551
|
+
this.code = code;
|
|
552
|
+
this.cause = cause;
|
|
553
|
+
if (Error.captureStackTrace) {
|
|
554
|
+
Error.captureStackTrace(this, this.constructor);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Returns a JSON representation of the error for logging.
|
|
559
|
+
*/
|
|
560
|
+
toJSON() {
|
|
561
|
+
return {
|
|
562
|
+
name: this.name,
|
|
563
|
+
message: this.message,
|
|
564
|
+
code: this.code,
|
|
565
|
+
cause: this.cause?.message
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
var MorphValidationError = class extends MorphError {
|
|
570
|
+
/** The field that failed validation */
|
|
571
|
+
field;
|
|
572
|
+
constructor(message, field) {
|
|
573
|
+
super(message, "validation_error");
|
|
574
|
+
this.name = "MorphValidationError";
|
|
575
|
+
this.field = field;
|
|
576
|
+
}
|
|
577
|
+
toJSON() {
|
|
578
|
+
return {
|
|
579
|
+
...super.toJSON(),
|
|
580
|
+
field: this.field
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
var MorphAPIError = class extends MorphError {
|
|
585
|
+
/** HTTP status code */
|
|
586
|
+
statusCode;
|
|
587
|
+
/** Request ID for debugging (if available) */
|
|
588
|
+
requestId;
|
|
589
|
+
/** Raw response body */
|
|
590
|
+
rawResponse;
|
|
591
|
+
constructor(message, code, statusCode, options) {
|
|
592
|
+
super(message, code, options?.cause);
|
|
593
|
+
this.name = "MorphAPIError";
|
|
594
|
+
this.statusCode = statusCode;
|
|
595
|
+
this.requestId = options?.requestId;
|
|
596
|
+
this.rawResponse = options?.rawResponse;
|
|
597
|
+
}
|
|
598
|
+
toJSON() {
|
|
599
|
+
return {
|
|
600
|
+
...super.toJSON(),
|
|
601
|
+
statusCode: this.statusCode,
|
|
602
|
+
requestId: this.requestId
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
var MorphAuthenticationError = class extends MorphAPIError {
|
|
607
|
+
constructor(message = "Authentication required. Please provide a valid API key.") {
|
|
608
|
+
super(message, "authentication_required", 401);
|
|
609
|
+
this.name = "MorphAuthenticationError";
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
var MorphRateLimitError = class extends MorphAPIError {
|
|
613
|
+
/** When the rate limit resets (Unix timestamp) */
|
|
614
|
+
resetAt;
|
|
615
|
+
/** Number of seconds until reset */
|
|
616
|
+
retryAfter;
|
|
617
|
+
constructor(message = "Rate limit exceeded. Please retry later.", options) {
|
|
618
|
+
super(message, "rate_limit_exceeded", 429, { requestId: options?.requestId });
|
|
619
|
+
this.name = "MorphRateLimitError";
|
|
620
|
+
this.resetAt = options?.resetAt;
|
|
621
|
+
this.retryAfter = options?.retryAfter;
|
|
622
|
+
}
|
|
623
|
+
toJSON() {
|
|
624
|
+
return {
|
|
625
|
+
...super.toJSON(),
|
|
626
|
+
resetAt: this.resetAt,
|
|
627
|
+
retryAfter: this.retryAfter
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
var MorphNotFoundError = class extends MorphAPIError {
|
|
632
|
+
/** The type of resource that was not found */
|
|
633
|
+
resourceType;
|
|
634
|
+
/** The ID of the resource that was not found */
|
|
635
|
+
resourceId;
|
|
636
|
+
constructor(resourceType, resourceId) {
|
|
637
|
+
const message = resourceId ? `${resourceType} '${resourceId}' not found` : `${resourceType} not found`;
|
|
638
|
+
super(message, "resource_not_found", 404);
|
|
639
|
+
this.name = "MorphNotFoundError";
|
|
640
|
+
this.resourceType = resourceType;
|
|
641
|
+
this.resourceId = resourceId;
|
|
642
|
+
}
|
|
643
|
+
toJSON() {
|
|
644
|
+
return {
|
|
645
|
+
...super.toJSON(),
|
|
646
|
+
resourceType: this.resourceType,
|
|
647
|
+
resourceId: this.resourceId
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
var MorphProfileLimitError = class extends MorphAPIError {
|
|
652
|
+
/** Current number of profiles */
|
|
653
|
+
currentCount;
|
|
654
|
+
/** Maximum allowed profiles for the plan */
|
|
655
|
+
maxAllowed;
|
|
656
|
+
constructor(message = "Profile limit exceeded for your plan.", options) {
|
|
657
|
+
super(message, "profile_limit_exceeded", 403, { requestId: options?.requestId });
|
|
658
|
+
this.name = "MorphProfileLimitError";
|
|
659
|
+
this.currentCount = options?.currentCount;
|
|
660
|
+
this.maxAllowed = options?.maxAllowed;
|
|
661
|
+
}
|
|
662
|
+
toJSON() {
|
|
663
|
+
return {
|
|
664
|
+
...super.toJSON(),
|
|
665
|
+
currentCount: this.currentCount,
|
|
666
|
+
maxAllowed: this.maxAllowed
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
};
|
|
670
|
+
function parseAPIError(statusCode, responseText, requestId) {
|
|
671
|
+
let errorData = {};
|
|
672
|
+
try {
|
|
673
|
+
errorData = JSON.parse(responseText);
|
|
674
|
+
} catch {
|
|
675
|
+
}
|
|
676
|
+
const message = errorData.detail || errorData.message || responseText || "Unknown error";
|
|
677
|
+
const code = errorData.code;
|
|
678
|
+
switch (statusCode) {
|
|
679
|
+
case 401:
|
|
680
|
+
return new MorphAuthenticationError(message);
|
|
681
|
+
case 403:
|
|
682
|
+
if (code === "profile_limit_exceeded" || message.toLowerCase().includes("limit")) {
|
|
683
|
+
return new MorphProfileLimitError(message, { requestId });
|
|
684
|
+
}
|
|
685
|
+
return new MorphAPIError(message, "insufficient_permissions", statusCode, { requestId, rawResponse: responseText });
|
|
686
|
+
case 404:
|
|
687
|
+
if (message.toLowerCase().includes("profile")) {
|
|
688
|
+
return new MorphNotFoundError("Profile", void 0);
|
|
689
|
+
}
|
|
690
|
+
if (message.toLowerCase().includes("session")) {
|
|
691
|
+
return new MorphNotFoundError("Session", void 0);
|
|
692
|
+
}
|
|
693
|
+
return new MorphAPIError(message, "resource_not_found", statusCode, { requestId, rawResponse: responseText });
|
|
694
|
+
case 429:
|
|
695
|
+
return new MorphRateLimitError(message, { requestId });
|
|
696
|
+
case 422:
|
|
697
|
+
return new MorphAPIError(message, "validation_error", statusCode, { requestId, rawResponse: responseText });
|
|
698
|
+
case 500:
|
|
699
|
+
case 502:
|
|
700
|
+
case 503:
|
|
701
|
+
case 504:
|
|
702
|
+
return new MorphAPIError(message, "service_unavailable", statusCode, { requestId, rawResponse: responseText });
|
|
703
|
+
default:
|
|
704
|
+
return new MorphAPIError(message, "network_error", statusCode, { requestId, rawResponse: responseText });
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// tools/browser/profiles/types.ts
|
|
709
|
+
function transformProfile(api) {
|
|
710
|
+
return {
|
|
711
|
+
id: api.id,
|
|
712
|
+
name: api.name,
|
|
713
|
+
repoId: api.repo_id,
|
|
714
|
+
cookieDomains: api.cookie_domains,
|
|
715
|
+
lastUsedAt: api.last_used_at,
|
|
716
|
+
createdAt: api.created_at,
|
|
717
|
+
updatedAt: api.updated_at
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function transformCreateInput(input) {
|
|
721
|
+
return {
|
|
722
|
+
name: input.name,
|
|
723
|
+
repo_id: input.repoId
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function transformSession(api) {
|
|
727
|
+
return {
|
|
728
|
+
sessionId: api.session_id,
|
|
729
|
+
debugUrl: api.debug_url || ""
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
function transformSaveInput(input) {
|
|
733
|
+
return {
|
|
734
|
+
session_id: input.sessionId,
|
|
735
|
+
profile_id: input.profileId
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function transformStateResponse(api) {
|
|
739
|
+
return {
|
|
740
|
+
profileId: api.profile_id,
|
|
741
|
+
stateUrl: api.state_url,
|
|
742
|
+
expiresIn: api.expires_in
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// tools/browser/profiles/core.ts
|
|
747
|
+
var DEFAULT_API_URL = process.env.MORPH_ENVIRONMENT === "DEV" ? "http://localhost:8000" : "https://browser.morphllm.com";
|
|
748
|
+
var ProfilesClient = class {
|
|
749
|
+
config;
|
|
750
|
+
constructor(config) {
|
|
751
|
+
this.config = config;
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Create a new browser profile and immediately start a live session.
|
|
755
|
+
*
|
|
756
|
+
* @param input - Profile creation parameters
|
|
757
|
+
* @returns Profile setup handle with live URL + save()
|
|
758
|
+
* @throws {MorphValidationError} If input validation fails
|
|
759
|
+
* @throws {MorphProfileLimitError} If profile limit is exceeded
|
|
760
|
+
* @throws {MorphAuthenticationError} If API key is missing or invalid
|
|
761
|
+
*
|
|
762
|
+
* @example
|
|
763
|
+
* ```typescript
|
|
764
|
+
* const setup = await morph.browser.profiles.createProfile({
|
|
765
|
+
* name: 'LinkedIn Production',
|
|
766
|
+
* repoId: 'owner/repo'
|
|
767
|
+
* });
|
|
768
|
+
* console.log(setup.session.debugUrl);
|
|
769
|
+
* await setup.save();
|
|
770
|
+
* ```
|
|
771
|
+
*/
|
|
772
|
+
async createProfile(input) {
|
|
773
|
+
return createProfile(input, this.config);
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* List all profiles for the authenticated user.
|
|
777
|
+
*
|
|
778
|
+
* @param repoId - Optional repository ID to filter by
|
|
779
|
+
* @returns Array of profiles
|
|
780
|
+
*
|
|
781
|
+
* @example
|
|
782
|
+
* ```typescript
|
|
783
|
+
* // List all profiles
|
|
784
|
+
* const allProfiles = await morph.browser.profiles.listProfiles();
|
|
785
|
+
*
|
|
786
|
+
* // List profiles for a specific repo
|
|
787
|
+
* const repoProfiles = await morph.browser.profiles.listProfiles('owner/repo');
|
|
788
|
+
* ```
|
|
789
|
+
*/
|
|
790
|
+
async listProfiles(repoId) {
|
|
791
|
+
return listProfiles(this.config, repoId);
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Get a profile by ID with convenience methods.
|
|
795
|
+
*
|
|
796
|
+
* @param id - Profile ID
|
|
797
|
+
* @returns Profile with attached methods
|
|
798
|
+
* @throws {MorphNotFoundError} If profile is not found
|
|
799
|
+
*
|
|
800
|
+
* @example
|
|
801
|
+
* ```typescript
|
|
802
|
+
* const profile = await morph.browser.profiles.getProfile('profile-id');
|
|
803
|
+
* const state = await profile.getState();
|
|
804
|
+
* await profile.delete();
|
|
805
|
+
* ```
|
|
806
|
+
*/
|
|
807
|
+
async getProfile(id) {
|
|
808
|
+
return getProfile(id, this.config);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Update a profile by opening a live session (no rename).
|
|
812
|
+
*
|
|
813
|
+
* @param id - Profile ID
|
|
814
|
+
* @returns Profile setup handle with live URL + save()
|
|
815
|
+
*/
|
|
816
|
+
async updateProfile(id) {
|
|
817
|
+
return updateProfile(id, this.config);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Delete a profile.
|
|
821
|
+
*
|
|
822
|
+
* @param id - Profile ID
|
|
823
|
+
* @throws {MorphNotFoundError} If profile is not found
|
|
824
|
+
*/
|
|
825
|
+
async deleteProfile(id) {
|
|
826
|
+
return deleteProfile(id, this.config);
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Start a browser session for profile setup.
|
|
830
|
+
*
|
|
831
|
+
* Returns a live URL where the user can sign into accounts.
|
|
832
|
+
* After signing in, call `saveSession` to persist the state.
|
|
833
|
+
*
|
|
834
|
+
* @param input - Optional session parameters
|
|
835
|
+
* @returns Session with debug URL
|
|
836
|
+
*
|
|
837
|
+
* @example
|
|
838
|
+
* ```typescript
|
|
839
|
+
* const session = await morph.browser.profiles.startSession();
|
|
840
|
+
* console.log('Sign in at:', session.debugUrl);
|
|
841
|
+
* // Open debugUrl in browser, user signs in...
|
|
842
|
+
* await morph.browser.profiles.saveSession(session.sessionId, profile.id);
|
|
843
|
+
* ```
|
|
844
|
+
*/
|
|
845
|
+
async startSession(input) {
|
|
846
|
+
return startProfileSession(this.config, input);
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Save browser state from a session to a profile.
|
|
850
|
+
*
|
|
851
|
+
* Call this after the user is done signing into accounts.
|
|
852
|
+
* Extracts cookies, localStorage, and sessionStorage.
|
|
853
|
+
*
|
|
854
|
+
* @param sessionId - Browser session ID from startSession
|
|
855
|
+
* @param profileId - Profile ID to save state to
|
|
856
|
+
* @returns Updated profile with cookie domains
|
|
857
|
+
*/
|
|
858
|
+
async saveSession(sessionId, profileId) {
|
|
859
|
+
return saveProfileSession({ sessionId, profileId }, this.config);
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* List available repo IDs (discovery).
|
|
863
|
+
*
|
|
864
|
+
* @returns Repo summaries with profile counts
|
|
865
|
+
*/
|
|
866
|
+
async listRepos() {
|
|
867
|
+
return listRepos(this.config);
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Get the presigned URL for a profile's state.
|
|
871
|
+
*
|
|
872
|
+
* Use this to download the raw state JSON for debugging
|
|
873
|
+
* or to restore state manually.
|
|
874
|
+
*
|
|
875
|
+
* @param profileId - Profile ID
|
|
876
|
+
* @returns State URL with expiry information
|
|
877
|
+
*/
|
|
878
|
+
async getProfileState(profileId) {
|
|
879
|
+
return getProfileState(profileId, this.config);
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
function validateCreateInput(input) {
|
|
883
|
+
if (!input.name || typeof input.name !== "string") {
|
|
884
|
+
throw new MorphValidationError("name is required", "name");
|
|
885
|
+
}
|
|
886
|
+
const trimmedName = input.name.trim();
|
|
887
|
+
if (trimmedName.length === 0) {
|
|
888
|
+
throw new MorphValidationError("name cannot be empty", "name");
|
|
889
|
+
}
|
|
890
|
+
if (trimmedName.length > 100) {
|
|
891
|
+
throw new MorphValidationError("name must be 100 characters or less", "name");
|
|
892
|
+
}
|
|
893
|
+
if (!input.repoId || typeof input.repoId !== "string") {
|
|
894
|
+
throw new MorphValidationError("repoId is required", "repoId");
|
|
895
|
+
}
|
|
896
|
+
if (input.repoId.trim().length === 0) {
|
|
897
|
+
throw new MorphValidationError("repoId cannot be empty", "repoId");
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
function validateId(id, fieldName) {
|
|
901
|
+
if (!id || typeof id !== "string") {
|
|
902
|
+
throw new MorphValidationError(`${fieldName} is required`, fieldName);
|
|
903
|
+
}
|
|
904
|
+
if (id.trim().length === 0) {
|
|
905
|
+
throw new MorphValidationError(`${fieldName} cannot be empty`, fieldName);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
async function createProfile(input, config = {}) {
|
|
909
|
+
validateCreateInput(input);
|
|
910
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
911
|
+
const headers = buildHeaders(config);
|
|
912
|
+
const response = await fetchWithRetry(
|
|
913
|
+
`${apiUrl}/profiles`,
|
|
914
|
+
{
|
|
915
|
+
method: "POST",
|
|
916
|
+
headers,
|
|
917
|
+
body: JSON.stringify(transformCreateInput(input))
|
|
918
|
+
},
|
|
919
|
+
config.retryConfig
|
|
920
|
+
);
|
|
921
|
+
if (!response.ok) {
|
|
922
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
923
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
924
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
925
|
+
}
|
|
926
|
+
const apiProfile = await response.json();
|
|
927
|
+
const profile = transformProfile(apiProfile);
|
|
928
|
+
const session = await startProfileSession(config, { profileId: profile.id });
|
|
929
|
+
return buildProfileSetup(profile, session, config);
|
|
930
|
+
}
|
|
931
|
+
async function listProfiles(config = {}, repoId) {
|
|
932
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
933
|
+
const headers = buildHeaders(config);
|
|
934
|
+
const url = repoId ? `${apiUrl}/profiles?repo_id=${encodeURIComponent(repoId)}` : `${apiUrl}/profiles`;
|
|
935
|
+
const response = await fetchWithRetry(
|
|
936
|
+
url,
|
|
937
|
+
{ method: "GET", headers },
|
|
938
|
+
config.retryConfig
|
|
939
|
+
);
|
|
940
|
+
if (!response.ok) {
|
|
941
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
942
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
943
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
944
|
+
}
|
|
945
|
+
const data = await response.json();
|
|
946
|
+
return data.profiles.map(transformProfile);
|
|
947
|
+
}
|
|
948
|
+
async function getProfile(id, config = {}) {
|
|
949
|
+
validateId(id, "id");
|
|
950
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
951
|
+
const headers = buildHeaders(config);
|
|
952
|
+
const response = await fetchWithRetry(
|
|
953
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
954
|
+
{ method: "GET", headers },
|
|
955
|
+
config.retryConfig
|
|
956
|
+
);
|
|
957
|
+
if (!response.ok) {
|
|
958
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
959
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
960
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
961
|
+
}
|
|
962
|
+
const apiProfile = await response.json();
|
|
963
|
+
const profile = transformProfile(apiProfile);
|
|
964
|
+
return {
|
|
965
|
+
...profile,
|
|
966
|
+
getState: () => getProfileState(id, config),
|
|
967
|
+
delete: () => deleteProfile(id, config)
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
async function updateProfile(id, config = {}) {
|
|
971
|
+
validateId(id, "id");
|
|
972
|
+
const profile = await fetchProfile(id, config);
|
|
973
|
+
const session = await startProfileSession(config, { profileId: profile.id });
|
|
974
|
+
return buildProfileSetup(profile, session, config);
|
|
975
|
+
}
|
|
976
|
+
async function deleteProfile(id, config = {}) {
|
|
977
|
+
validateId(id, "id");
|
|
978
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
979
|
+
const headers = buildHeaders(config);
|
|
980
|
+
const response = await fetchWithRetry(
|
|
981
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
982
|
+
{ method: "DELETE", headers },
|
|
983
|
+
config.retryConfig
|
|
984
|
+
);
|
|
985
|
+
if (!response.ok) {
|
|
986
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
987
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
988
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
async function startProfileSession(config = {}, input) {
|
|
992
|
+
if (!config.apiKey) {
|
|
993
|
+
throw new MorphAuthenticationError();
|
|
994
|
+
}
|
|
995
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
996
|
+
const headers = buildHeaders(config);
|
|
997
|
+
const body = input?.profileId ? { profile_id: input.profileId } : {};
|
|
998
|
+
const response = await fetchWithRetry(
|
|
999
|
+
`${apiUrl}/profiles/session/start`,
|
|
1000
|
+
{
|
|
1001
|
+
method: "POST",
|
|
1002
|
+
headers,
|
|
1003
|
+
body: JSON.stringify(body)
|
|
1004
|
+
},
|
|
1005
|
+
config.retryConfig
|
|
1006
|
+
);
|
|
1007
|
+
if (!response.ok) {
|
|
1008
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1009
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1010
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1011
|
+
}
|
|
1012
|
+
const apiSession = await response.json();
|
|
1013
|
+
return transformSession(apiSession);
|
|
1014
|
+
}
|
|
1015
|
+
async function saveProfileSession(input, config = {}) {
|
|
1016
|
+
validateId(input.sessionId, "sessionId");
|
|
1017
|
+
validateId(input.profileId, "profileId");
|
|
1018
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
1019
|
+
const headers = buildHeaders(config);
|
|
1020
|
+
const response = await fetchWithRetry(
|
|
1021
|
+
`${apiUrl}/profiles/session/save`,
|
|
1022
|
+
{
|
|
1023
|
+
method: "POST",
|
|
1024
|
+
headers,
|
|
1025
|
+
body: JSON.stringify(transformSaveInput(input))
|
|
1026
|
+
},
|
|
1027
|
+
config.retryConfig
|
|
1028
|
+
);
|
|
1029
|
+
if (!response.ok) {
|
|
1030
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1031
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1032
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1033
|
+
}
|
|
1034
|
+
const apiProfile = await response.json();
|
|
1035
|
+
return transformProfile(apiProfile);
|
|
1036
|
+
}
|
|
1037
|
+
async function listRepos(config = {}) {
|
|
1038
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
1039
|
+
const headers = buildHeaders(config);
|
|
1040
|
+
const response = await fetchWithRetry(
|
|
1041
|
+
`${apiUrl}/repos`,
|
|
1042
|
+
{ method: "GET", headers },
|
|
1043
|
+
config.retryConfig
|
|
1044
|
+
);
|
|
1045
|
+
if (!response.ok) {
|
|
1046
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1047
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1048
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1049
|
+
}
|
|
1050
|
+
const data = await response.json();
|
|
1051
|
+
const repos = Array.isArray(data?.repos) ? data.repos : [];
|
|
1052
|
+
return repos.map((repo) => ({
|
|
1053
|
+
repoId: repo.repo_id,
|
|
1054
|
+
repoFullName: repo.repo_full_name,
|
|
1055
|
+
profileCount: repo.profile_count ?? 0
|
|
1056
|
+
}));
|
|
1057
|
+
}
|
|
1058
|
+
async function getProfileState(profileId, config = {}) {
|
|
1059
|
+
validateId(profileId, "profileId");
|
|
1060
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
1061
|
+
const headers = buildHeaders(config);
|
|
1062
|
+
const response = await fetchWithRetry(
|
|
1063
|
+
`${apiUrl}/profiles/${encodeURIComponent(profileId)}/state`,
|
|
1064
|
+
{ method: "GET", headers },
|
|
1065
|
+
config.retryConfig
|
|
1066
|
+
);
|
|
1067
|
+
if (!response.ok) {
|
|
1068
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1069
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1070
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1071
|
+
}
|
|
1072
|
+
const apiState = await response.json();
|
|
1073
|
+
return transformStateResponse(apiState);
|
|
1074
|
+
}
|
|
1075
|
+
function buildHeaders(config) {
|
|
1076
|
+
const headers = { "Content-Type": "application/json" };
|
|
1077
|
+
if (config.apiKey) {
|
|
1078
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1079
|
+
}
|
|
1080
|
+
return headers;
|
|
1081
|
+
}
|
|
1082
|
+
async function fetchProfile(id, config) {
|
|
1083
|
+
const apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
1084
|
+
const headers = buildHeaders(config);
|
|
1085
|
+
const response = await fetchWithRetry(
|
|
1086
|
+
`${apiUrl}/profiles/${encodeURIComponent(id)}`,
|
|
1087
|
+
{ method: "GET", headers },
|
|
1088
|
+
config.retryConfig
|
|
1089
|
+
);
|
|
1090
|
+
if (!response.ok) {
|
|
1091
|
+
const errorText = await response.text().catch(() => response.statusText);
|
|
1092
|
+
const requestId = response.headers.get("x-request-id") || void 0;
|
|
1093
|
+
throw parseAPIError(response.status, errorText, requestId);
|
|
1094
|
+
}
|
|
1095
|
+
const apiProfile = await response.json();
|
|
1096
|
+
return transformProfile(apiProfile);
|
|
1097
|
+
}
|
|
1098
|
+
function buildProfileSetup(profile, session, config) {
|
|
1099
|
+
return {
|
|
1100
|
+
profile,
|
|
1101
|
+
session,
|
|
1102
|
+
save: () => saveProfileSession({ sessionId: session.sessionId, profileId: profile.id }, config)
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
|
|
518
1106
|
// tools/browser/core.ts
|
|
519
1107
|
var DEFAULT_CONFIG2 = {
|
|
520
1108
|
apiUrl: process.env.MORPH_ENVIRONMENT === "DEV" ? "http://localhost:8000" : "https://browser.morphllm.com",
|
|
@@ -524,11 +1112,16 @@ var DEFAULT_CONFIG2 = {
|
|
|
524
1112
|
};
|
|
525
1113
|
var BrowserClient = class {
|
|
526
1114
|
config;
|
|
1115
|
+
/**
|
|
1116
|
+
* Profile management - create and manage browser profiles for storing login state.
|
|
1117
|
+
*/
|
|
1118
|
+
profiles;
|
|
527
1119
|
constructor(config = {}) {
|
|
528
1120
|
this.config = {
|
|
529
1121
|
...DEFAULT_CONFIG2,
|
|
530
1122
|
...config
|
|
531
1123
|
};
|
|
1124
|
+
this.profiles = new ProfilesClient(this.config);
|
|
532
1125
|
}
|
|
533
1126
|
/**
|
|
534
1127
|
* Execute a browser automation task
|
|
@@ -551,19 +1144,21 @@ var BrowserClient = class {
|
|
|
551
1144
|
body: JSON.stringify({
|
|
552
1145
|
task: input.task,
|
|
553
1146
|
url: input.url,
|
|
554
|
-
max_steps: input.
|
|
1147
|
+
max_steps: input.maxSteps ?? 10,
|
|
555
1148
|
model: input.model ?? "morph-computer-use-v0",
|
|
556
|
-
viewport_width: input.
|
|
557
|
-
viewport_height: input.
|
|
558
|
-
external_id: input.
|
|
559
|
-
repo_id: input.
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
1149
|
+
viewport_width: input.viewportWidth ?? 1280,
|
|
1150
|
+
viewport_height: input.viewportHeight ?? 720,
|
|
1151
|
+
external_id: input.externalId,
|
|
1152
|
+
repo_id: input.repoId,
|
|
1153
|
+
repo_full_name: input.repoFullName,
|
|
1154
|
+
commit_id: input.commitId,
|
|
1155
|
+
record_video: input.recordVideo ?? false,
|
|
1156
|
+
video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
|
|
1157
|
+
video_height: input.videoHeight ?? input.viewportHeight ?? 720,
|
|
1158
|
+
allow_resizing: input.allowResizing ?? false,
|
|
565
1159
|
structured_output: "schema" in input ? stringifyStructuredOutput(input.schema) : void 0,
|
|
566
|
-
auth: input.auth
|
|
1160
|
+
auth: input.auth,
|
|
1161
|
+
profile_id: input.profileId
|
|
567
1162
|
})
|
|
568
1163
|
});
|
|
569
1164
|
if (!response.ok) {
|
|
@@ -571,9 +1166,10 @@ var BrowserClient = class {
|
|
|
571
1166
|
if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);
|
|
572
1167
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
573
1168
|
}
|
|
574
|
-
const result = await response.json();
|
|
1169
|
+
const result = mapTaskResult(await response.json());
|
|
575
1170
|
if (debug) {
|
|
576
|
-
|
|
1171
|
+
const debugUrl = result.debugUrl;
|
|
1172
|
+
console.log(`[Browser] \u2705 Task created: recordingId=${result.recordingId ?? "none"} debugUrl=${debugUrl ? "available" : "none"}`);
|
|
577
1173
|
}
|
|
578
1174
|
if ("schema" in input) {
|
|
579
1175
|
return wrapTaskResponseWithSchema(result, this.config, input.schema);
|
|
@@ -628,15 +1224,15 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
628
1224
|
error: 'Task description is required. Example: "Go to example.com and click the login button"'
|
|
629
1225
|
};
|
|
630
1226
|
}
|
|
631
|
-
if (input.
|
|
1227
|
+
if (input.maxSteps !== void 0 && (input.maxSteps < 1 || input.maxSteps > 50)) {
|
|
632
1228
|
return {
|
|
633
1229
|
success: false,
|
|
634
|
-
error: "
|
|
1230
|
+
error: "maxSteps must be between 1 and 50. Use more steps for complex multi-page flows."
|
|
635
1231
|
};
|
|
636
1232
|
}
|
|
637
1233
|
if (debug) {
|
|
638
|
-
console.log(`[Browser] Task: "${input.task.slice(0, 60)}..." url=${input.url || "none"} maxSteps=${input.
|
|
639
|
-
console.log(`[Browser] Recording: ${input.
|
|
1234
|
+
console.log(`[Browser] Task: "${input.task.slice(0, 60)}..." url=${input.url || "none"} maxSteps=${input.maxSteps ?? 10}`);
|
|
1235
|
+
console.log(`[Browser] Recording: ${input.recordVideo ? "yes" : "no"} | Calling ${apiUrl}/browser-task`);
|
|
640
1236
|
}
|
|
641
1237
|
const startTime = Date.now();
|
|
642
1238
|
try {
|
|
@@ -650,19 +1246,20 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
650
1246
|
body: JSON.stringify({
|
|
651
1247
|
task: input.task,
|
|
652
1248
|
url: input.url,
|
|
653
|
-
max_steps: input.
|
|
1249
|
+
max_steps: input.maxSteps ?? 10,
|
|
654
1250
|
model: input.model ?? "morph-computer-use-v0",
|
|
655
|
-
viewport_width: input.
|
|
656
|
-
viewport_height: input.
|
|
657
|
-
external_id: input.
|
|
658
|
-
repo_id: input.
|
|
659
|
-
commit_id: input.
|
|
660
|
-
record_video: input.
|
|
661
|
-
video_width: input.
|
|
662
|
-
video_height: input.
|
|
663
|
-
allow_resizing: input.
|
|
664
|
-
structured_output: input.
|
|
665
|
-
auth: input.auth
|
|
1251
|
+
viewport_width: input.viewportWidth ?? 1280,
|
|
1252
|
+
viewport_height: input.viewportHeight ?? 720,
|
|
1253
|
+
external_id: input.externalId,
|
|
1254
|
+
repo_id: input.repoId,
|
|
1255
|
+
commit_id: input.commitId,
|
|
1256
|
+
record_video: input.recordVideo ?? false,
|
|
1257
|
+
video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
|
|
1258
|
+
video_height: input.videoHeight ?? input.viewportHeight ?? 720,
|
|
1259
|
+
allow_resizing: input.allowResizing ?? false,
|
|
1260
|
+
structured_output: input.structuredOutput,
|
|
1261
|
+
auth: input.auth,
|
|
1262
|
+
profile_id: input.profileId
|
|
666
1263
|
})
|
|
667
1264
|
},
|
|
668
1265
|
config.retryConfig
|
|
@@ -670,17 +1267,17 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
670
1267
|
const response = await withTimeout(
|
|
671
1268
|
fetchPromise,
|
|
672
1269
|
timeout,
|
|
673
|
-
`Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing
|
|
1270
|
+
`Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing maxSteps.`
|
|
674
1271
|
);
|
|
675
1272
|
if (!response.ok) {
|
|
676
1273
|
const errorText = await response.text().catch(() => response.statusText);
|
|
677
1274
|
if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);
|
|
678
1275
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
679
1276
|
}
|
|
680
|
-
const result = await response.json();
|
|
1277
|
+
const result = mapTaskResult(await response.json());
|
|
681
1278
|
const elapsed = Date.now() - startTime;
|
|
682
1279
|
if (debug) {
|
|
683
|
-
console.log(`[Browser] \u2705 ${result.success ? "Success" : "Failed"} in ${elapsed}ms | steps=${result.
|
|
1280
|
+
console.log(`[Browser] \u2705 ${result.success ? "Success" : "Failed"} in ${elapsed}ms | steps=${result.stepsTaken ?? 0} recordingId=${result.recordingId ?? "none"}`);
|
|
684
1281
|
}
|
|
685
1282
|
return result;
|
|
686
1283
|
} catch (error) {
|
|
@@ -718,7 +1315,7 @@ async function getRecording(recordingId, config = {}) {
|
|
|
718
1315
|
if (debug) console.error(`[Browser] getRecording error: ${response.status} - ${errorText}`);
|
|
719
1316
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
720
1317
|
}
|
|
721
|
-
const data = await response.json();
|
|
1318
|
+
const data = mapRecordingStatus(await response.json());
|
|
722
1319
|
if (debug) console.log(`[Browser] Recording status: ${data.status}`);
|
|
723
1320
|
return {
|
|
724
1321
|
...data,
|
|
@@ -741,10 +1338,10 @@ async function waitForRecording(recordingId, config = {}, options = {}) {
|
|
|
741
1338
|
}
|
|
742
1339
|
async function executeWithRecording(input, config = {}) {
|
|
743
1340
|
const taskResult = await executeBrowserTask(input, config);
|
|
744
|
-
if (taskResult.
|
|
1341
|
+
if (taskResult.recordingId) {
|
|
745
1342
|
try {
|
|
746
1343
|
const recording = await waitForRecording(
|
|
747
|
-
taskResult.
|
|
1344
|
+
taskResult.recordingId,
|
|
748
1345
|
config,
|
|
749
1346
|
{ timeout: 6e4, pollInterval: 2e3 }
|
|
750
1347
|
);
|
|
@@ -754,12 +1351,12 @@ async function executeWithRecording(input, config = {}) {
|
|
|
754
1351
|
};
|
|
755
1352
|
} catch (error) {
|
|
756
1353
|
const errorRecording = {
|
|
757
|
-
id: taskResult.
|
|
1354
|
+
id: taskResult.recordingId,
|
|
758
1355
|
status: "ERROR",
|
|
759
1356
|
error: error instanceof Error ? error.message : String(error),
|
|
760
|
-
|
|
761
|
-
getWebp: (options) => getWebp(taskResult.
|
|
762
|
-
getErrors: () => getErrors(taskResult.
|
|
1357
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1358
|
+
getWebp: (options) => getWebp(taskResult.recordingId, config, options),
|
|
1359
|
+
getErrors: () => getErrors(taskResult.recordingId, config)
|
|
763
1360
|
};
|
|
764
1361
|
return {
|
|
765
1362
|
...taskResult,
|
|
@@ -785,8 +1382,8 @@ async function getErrors(recordingId, config = {}) {
|
|
|
785
1382
|
if (debug) console.error(`[Browser] getErrors error: ${response.status} - ${errorText}`);
|
|
786
1383
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
787
1384
|
}
|
|
788
|
-
const errors = await response.json();
|
|
789
|
-
if (debug) console.log(`[Browser] Found ${errors.
|
|
1385
|
+
const errors = mapErrorsResponse(await response.json());
|
|
1386
|
+
if (debug) console.log(`[Browser] Found ${errors.totalErrors} errors`);
|
|
790
1387
|
return errors;
|
|
791
1388
|
}
|
|
792
1389
|
function stringifyStructuredOutput(schema) {
|
|
@@ -819,6 +1416,84 @@ function parseStructuredTaskOutput(result, schema) {
|
|
|
819
1416
|
throw error;
|
|
820
1417
|
}
|
|
821
1418
|
}
|
|
1419
|
+
function mapTaskResult(api) {
|
|
1420
|
+
if (!api || typeof api !== "object") {
|
|
1421
|
+
return api;
|
|
1422
|
+
}
|
|
1423
|
+
return {
|
|
1424
|
+
success: api.success,
|
|
1425
|
+
result: api.result,
|
|
1426
|
+
error: api.error,
|
|
1427
|
+
stepsTaken: api.steps_taken,
|
|
1428
|
+
executionTimeMs: api.execution_time_ms,
|
|
1429
|
+
urls: api.urls,
|
|
1430
|
+
actionNames: api.action_names,
|
|
1431
|
+
errors: api.errors,
|
|
1432
|
+
modelActions: api.model_actions,
|
|
1433
|
+
isDone: api.is_done,
|
|
1434
|
+
actionHistory: api.action_history,
|
|
1435
|
+
actionResults: api.action_results,
|
|
1436
|
+
hasErrors: api.has_errors,
|
|
1437
|
+
numberOfSteps: api.number_of_steps,
|
|
1438
|
+
judgement: api.judgement,
|
|
1439
|
+
isValidated: api.is_validated,
|
|
1440
|
+
replayId: api.replay_id,
|
|
1441
|
+
replayUrl: api.replay_url,
|
|
1442
|
+
recordingId: api.recording_id,
|
|
1443
|
+
recordingStatus: api.recording_status,
|
|
1444
|
+
taskId: api.task_id,
|
|
1445
|
+
status: api.status,
|
|
1446
|
+
output: api.output,
|
|
1447
|
+
debugUrl: api.debug_url
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
function mapRecordingStatus(api) {
|
|
1451
|
+
return {
|
|
1452
|
+
id: api.id,
|
|
1453
|
+
status: api.status,
|
|
1454
|
+
replayUrl: api.replay_url,
|
|
1455
|
+
networkUrl: api.network_url,
|
|
1456
|
+
consoleUrl: api.console_url,
|
|
1457
|
+
videoUrl: api.video_url,
|
|
1458
|
+
totalEvents: api.total_events,
|
|
1459
|
+
fileSize: api.file_size,
|
|
1460
|
+
duration: api.duration,
|
|
1461
|
+
error: api.error,
|
|
1462
|
+
createdAt: api.created_at
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
function mapBrowserError(api) {
|
|
1466
|
+
return {
|
|
1467
|
+
type: api.type,
|
|
1468
|
+
message: api.message,
|
|
1469
|
+
url: api.url,
|
|
1470
|
+
timestamp: api.timestamp,
|
|
1471
|
+
screenshotUrl: api.screenshot_url,
|
|
1472
|
+
capturedAt: api.captured_at,
|
|
1473
|
+
status: api.status
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
function mapErrorsResponse(api) {
|
|
1477
|
+
return {
|
|
1478
|
+
recordingId: api.recording_id,
|
|
1479
|
+
totalErrors: api.total_errors,
|
|
1480
|
+
errors: Array.isArray(api.errors) ? api.errors.map(mapBrowserError) : []
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
function mapWebpResponse(api) {
|
|
1484
|
+
return {
|
|
1485
|
+
webpUrl: api.webp_url,
|
|
1486
|
+
cached: api.cached,
|
|
1487
|
+
width: api.width,
|
|
1488
|
+
fps: api.fps,
|
|
1489
|
+
maxDuration: api.max_duration,
|
|
1490
|
+
fileSize: api.file_size,
|
|
1491
|
+
maxSizeMb: api.max_size_mb,
|
|
1492
|
+
budgetMet: api.budget_met,
|
|
1493
|
+
qualityUsed: api.quality_used,
|
|
1494
|
+
attempts: api.attempts
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
822
1497
|
async function getTaskStatus(taskId, config) {
|
|
823
1498
|
const apiUrl = config.apiUrl || DEFAULT_CONFIG2.apiUrl;
|
|
824
1499
|
const debug = config.debug || false;
|
|
@@ -834,7 +1509,7 @@ async function getTaskStatus(taskId, config) {
|
|
|
834
1509
|
if (debug) console.error(`[Browser] getTaskStatus error: ${response.status} - ${errorText}`);
|
|
835
1510
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
836
1511
|
}
|
|
837
|
-
const result = await response.json();
|
|
1512
|
+
const result = mapTaskResult(await response.json());
|
|
838
1513
|
if (debug) console.log(`[Browser] Task status: ${result.status}`);
|
|
839
1514
|
return result;
|
|
840
1515
|
}
|
|
@@ -857,42 +1532,44 @@ async function pollTaskUntilComplete(taskId, config, pollConfig = {}) {
|
|
|
857
1532
|
throw new Error(`Task polling timeout after ${timeout}ms`);
|
|
858
1533
|
}
|
|
859
1534
|
function wrapTaskResponse(result, config) {
|
|
1535
|
+
const debugUrl = result.debugUrl ?? "";
|
|
860
1536
|
const wrapped = {
|
|
861
1537
|
...result,
|
|
862
|
-
|
|
863
|
-
|
|
1538
|
+
debugUrl,
|
|
1539
|
+
taskId: result.taskId || "",
|
|
1540
|
+
liveUrl: result.taskId ? generateLiveUrl(result.taskId, config) : debugUrl,
|
|
864
1541
|
complete: async (pollConfig) => {
|
|
865
|
-
if (result.
|
|
866
|
-
return pollTaskUntilComplete(result.
|
|
1542
|
+
if (result.taskId) {
|
|
1543
|
+
return pollTaskUntilComplete(result.taskId, config, pollConfig);
|
|
867
1544
|
}
|
|
868
|
-
if (result.
|
|
1545
|
+
if (result.recordingId) {
|
|
869
1546
|
const recording = await waitForRecording(
|
|
870
|
-
result.
|
|
1547
|
+
result.recordingId,
|
|
871
1548
|
config,
|
|
872
1549
|
pollConfig
|
|
873
1550
|
);
|
|
874
1551
|
return {
|
|
875
1552
|
...result,
|
|
876
|
-
|
|
1553
|
+
recordingStatus: recording.status
|
|
877
1554
|
};
|
|
878
1555
|
}
|
|
879
|
-
throw new Error("Cannot poll completion: no
|
|
1556
|
+
throw new Error("Cannot poll completion: no taskId or recordingId available");
|
|
880
1557
|
},
|
|
881
1558
|
// Add Steel live session helpers - either functional or error-throwing
|
|
882
|
-
getLiveUrl:
|
|
1559
|
+
getLiveUrl: debugUrl ? (options) => buildLiveUrl(debugUrl, options) : () => {
|
|
883
1560
|
throw new Error(
|
|
884
1561
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
|
|
885
1562
|
);
|
|
886
1563
|
},
|
|
887
|
-
getLiveIframe:
|
|
1564
|
+
getLiveIframe: debugUrl ? (optionsOrPreset) => {
|
|
888
1565
|
const options = resolvePreset(optionsOrPreset);
|
|
889
|
-
return buildLiveIframe(
|
|
1566
|
+
return buildLiveIframe(debugUrl, options);
|
|
890
1567
|
} : () => {
|
|
891
1568
|
throw new Error(
|
|
892
1569
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
|
|
893
1570
|
);
|
|
894
1571
|
},
|
|
895
|
-
getEmbedCode:
|
|
1572
|
+
getEmbedCode: debugUrl ? () => buildEmbedCode(debugUrl) : () => {
|
|
896
1573
|
throw new Error(
|
|
897
1574
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help."
|
|
898
1575
|
);
|
|
@@ -901,44 +1578,46 @@ function wrapTaskResponse(result, config) {
|
|
|
901
1578
|
return wrapped;
|
|
902
1579
|
}
|
|
903
1580
|
function wrapTaskResponseWithSchema(result, config, schema) {
|
|
1581
|
+
const debugUrl = result.debugUrl ?? "";
|
|
904
1582
|
const parsed = result.output ? parseStructuredTaskOutput(result, schema) : { ...result, parsed: null };
|
|
905
1583
|
const wrapped = {
|
|
906
1584
|
...parsed,
|
|
907
|
-
|
|
908
|
-
|
|
1585
|
+
debugUrl,
|
|
1586
|
+
taskId: result.taskId || "",
|
|
1587
|
+
liveUrl: result.taskId ? generateLiveUrl(result.taskId, config) : debugUrl,
|
|
909
1588
|
complete: async (pollConfig) => {
|
|
910
|
-
if (result.
|
|
911
|
-
const finalResult = await pollTaskUntilComplete(result.
|
|
1589
|
+
if (result.taskId) {
|
|
1590
|
+
const finalResult = await pollTaskUntilComplete(result.taskId, config, pollConfig);
|
|
912
1591
|
return parseStructuredTaskOutput(finalResult, schema);
|
|
913
1592
|
}
|
|
914
|
-
if (result.
|
|
1593
|
+
if (result.recordingId) {
|
|
915
1594
|
const recording = await waitForRecording(
|
|
916
|
-
result.
|
|
1595
|
+
result.recordingId,
|
|
917
1596
|
config,
|
|
918
1597
|
pollConfig
|
|
919
1598
|
);
|
|
920
1599
|
return {
|
|
921
1600
|
...parsed,
|
|
922
|
-
|
|
1601
|
+
recordingStatus: recording.status
|
|
923
1602
|
};
|
|
924
1603
|
}
|
|
925
|
-
throw new Error("Cannot poll completion: no
|
|
1604
|
+
throw new Error("Cannot poll completion: no taskId or recordingId available");
|
|
926
1605
|
},
|
|
927
1606
|
// Add Steel live session helpers - either functional or error-throwing
|
|
928
|
-
getLiveUrl:
|
|
1607
|
+
getLiveUrl: debugUrl ? (options) => buildLiveUrl(debugUrl, options) : () => {
|
|
929
1608
|
throw new Error(
|
|
930
1609
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions. "
|
|
931
1610
|
);
|
|
932
1611
|
},
|
|
933
|
-
getLiveIframe:
|
|
1612
|
+
getLiveIframe: debugUrl ? (optionsOrPreset) => {
|
|
934
1613
|
const options = resolvePreset(optionsOrPreset);
|
|
935
|
-
return buildLiveIframe(
|
|
1614
|
+
return buildLiveIframe(debugUrl, options);
|
|
936
1615
|
} : () => {
|
|
937
1616
|
throw new Error(
|
|
938
1617
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions."
|
|
939
1618
|
);
|
|
940
1619
|
},
|
|
941
|
-
getEmbedCode:
|
|
1620
|
+
getEmbedCode: debugUrl ? () => buildEmbedCode(debugUrl) : () => {
|
|
942
1621
|
throw new Error(
|
|
943
1622
|
"Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help enabling live sessions."
|
|
944
1623
|
);
|
|
@@ -953,10 +1632,11 @@ async function getWebp(recordingId, config = {}, options = {}) {
|
|
|
953
1632
|
throw new Error("API key required for getWebp");
|
|
954
1633
|
}
|
|
955
1634
|
const params = new URLSearchParams();
|
|
956
|
-
if (options.
|
|
1635
|
+
if (options.maxDuration !== void 0) params.set("max_duration", String(options.maxDuration));
|
|
957
1636
|
if (options.fps !== void 0) params.set("fps", String(options.fps));
|
|
958
1637
|
if (options.width !== void 0) params.set("width", String(options.width));
|
|
959
1638
|
if (options.quality !== void 0) params.set("quality", String(options.quality));
|
|
1639
|
+
if (options.maxSizeMb !== void 0) params.set("max_size_mb", String(options.maxSizeMb));
|
|
960
1640
|
const url = `${apiUrl}/recordings/${recordingId}/webp${params.toString() ? "?" + params.toString() : ""}`;
|
|
961
1641
|
if (debug) console.log(`[Browser] getWebp: ${url}`);
|
|
962
1642
|
const response = await fetch(url, {
|
|
@@ -968,8 +1648,8 @@ async function getWebp(recordingId, config = {}, options = {}) {
|
|
|
968
1648
|
if (debug) console.error(`[Browser] getWebp error: ${response.status} - ${errorText}`);
|
|
969
1649
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
970
1650
|
}
|
|
971
|
-
const result = await response.json();
|
|
972
|
-
if (debug) console.log(`[Browser] WebP ready: ${result.
|
|
1651
|
+
const result = mapWebpResponse(await response.json());
|
|
1652
|
+
if (debug) console.log(`[Browser] WebP ready: ${result.webpUrl} (cached: ${result.cached})`);
|
|
973
1653
|
return result;
|
|
974
1654
|
}
|
|
975
1655
|
async function checkHealth(config = {}) {
|
|
@@ -1865,42 +2545,36 @@ function enforceContextLimit(messages, maxChars = AGENT_CONFIG.MAX_CONTEXT_CHARS
|
|
|
1865
2545
|
}
|
|
1866
2546
|
|
|
1867
2547
|
// tools/warp_grep/agent/runner.ts
|
|
2548
|
+
var import_openai = __toESM(require("openai"), 1);
|
|
1868
2549
|
var import_path3 = __toESM(require("path"), 1);
|
|
1869
2550
|
var parser = new LLMResponseParser();
|
|
1870
|
-
var
|
|
2551
|
+
var DEFAULT_API_URL2 = "https://api.morphllm.com";
|
|
1871
2552
|
async function callModel(messages, model, options = {}) {
|
|
1872
|
-
const baseUrl = options.morphApiUrl ||
|
|
2553
|
+
const baseUrl = options.morphApiUrl || DEFAULT_API_URL2;
|
|
1873
2554
|
const apiKey = options.morphApiKey || process.env.MORPH_API_KEY || "";
|
|
1874
|
-
const fetchPromise = fetchWithRetry(
|
|
1875
|
-
`${baseUrl}/v1/chat/completions`,
|
|
1876
|
-
{
|
|
1877
|
-
method: "POST",
|
|
1878
|
-
headers: {
|
|
1879
|
-
"Content-Type": "application/json",
|
|
1880
|
-
Authorization: `Bearer ${apiKey}`
|
|
1881
|
-
},
|
|
1882
|
-
body: JSON.stringify({
|
|
1883
|
-
model,
|
|
1884
|
-
temperature: 0,
|
|
1885
|
-
max_tokens: 1024,
|
|
1886
|
-
repetition_penalty: 1.05,
|
|
1887
|
-
messages
|
|
1888
|
-
})
|
|
1889
|
-
},
|
|
1890
|
-
options.retryConfig
|
|
1891
|
-
);
|
|
1892
2555
|
const timeoutMs = options.timeout ?? AGENT_CONFIG.TIMEOUT_MS;
|
|
1893
|
-
const
|
|
1894
|
-
|
|
1895
|
-
|
|
2556
|
+
const client = new import_openai.default({
|
|
2557
|
+
apiKey,
|
|
2558
|
+
baseURL: baseUrl,
|
|
2559
|
+
maxRetries: options.retryConfig?.maxRetries,
|
|
2560
|
+
timeout: timeoutMs
|
|
2561
|
+
});
|
|
2562
|
+
let data;
|
|
2563
|
+
try {
|
|
2564
|
+
data = await client.chat.completions.create({
|
|
2565
|
+
model,
|
|
2566
|
+
temperature: 0,
|
|
2567
|
+
max_tokens: 1024,
|
|
2568
|
+
messages
|
|
2569
|
+
});
|
|
2570
|
+
} catch (error) {
|
|
2571
|
+
if (error instanceof import_openai.default.APIError && error.status === 404) {
|
|
1896
2572
|
throw new Error(
|
|
1897
2573
|
"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"
|
|
1898
2574
|
);
|
|
1899
2575
|
}
|
|
1900
|
-
|
|
1901
|
-
throw new Error(`morph-warp-grep error ${resp.status}: ${t}`);
|
|
2576
|
+
throw error;
|
|
1902
2577
|
}
|
|
1903
|
-
const data = await resp.json();
|
|
1904
2578
|
const content = data?.choices?.[0]?.message?.content;
|
|
1905
2579
|
if (!content || typeof content !== "string") {
|
|
1906
2580
|
throw new Error("Invalid response from model");
|
|
@@ -3856,13 +4530,6 @@ function formatResult3(result) {
|
|
|
3856
4530
|
changes.linesRemoved && `-${changes.linesRemoved} lines`,
|
|
3857
4531
|
changes.linesModified && `~${changes.linesModified} lines modified`
|
|
3858
4532
|
].filter(Boolean).join(", ");
|
|
3859
|
-
if (result.udiff) {
|
|
3860
|
-
return `Successfully applied changes to ${result.filepath}:
|
|
3861
|
-
|
|
3862
|
-
${result.udiff}
|
|
3863
|
-
|
|
3864
|
-
Summary: ${summary}`;
|
|
3865
|
-
}
|
|
3866
4533
|
return `Successfully applied changes to ${result.filepath}. ${summary}`;
|
|
3867
4534
|
}
|
|
3868
4535
|
function createEditFileTool(config = {}) {
|
|
@@ -4063,13 +4730,6 @@ function formatResult5(result) {
|
|
|
4063
4730
|
changes.linesRemoved && `-${changes.linesRemoved} lines`,
|
|
4064
4731
|
changes.linesModified && `~${changes.linesModified} lines modified`
|
|
4065
4732
|
].filter(Boolean).join(", ");
|
|
4066
|
-
if (result.udiff) {
|
|
4067
|
-
return `Successfully applied changes to ${result.filepath}:
|
|
4068
|
-
|
|
4069
|
-
${result.udiff}
|
|
4070
|
-
|
|
4071
|
-
Summary: ${summary}`;
|
|
4072
|
-
}
|
|
4073
4733
|
return `Successfully applied changes to ${result.filepath}. ${summary}`;
|
|
4074
4734
|
}
|
|
4075
4735
|
function createEditFileTool2(config = {}) {
|