@morphllm/morphsdk 0.2.94 → 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-YJ354BA2.js → chunk-2AMEQAO2.js} +2 -2
- 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-UJ7LVT5G.js → chunk-2VERUKO2.js} +1 -1
- 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-R7WN43L2.js → chunk-4KMBU6T3.js} +4 -4
- package/dist/{chunk-BVVDDTI7.js → chunk-73RV6EXR.js} +2 -2
- package/dist/{chunk-IH3KN4AT.js → chunk-7D6TXC7X.js} +2 -2
- package/dist/{chunk-FIA6LBW2.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-TXYCM4NP.js → chunk-QAT5UVPX.js} +3 -3
- package/dist/{chunk-M7GFXRKL.js → chunk-QJP62BXH.js} +157 -72
- package/dist/chunk-QJP62BXH.js.map +1 -0
- package/dist/{chunk-AV6YV2MH.js → chunk-R7IQWNSA.js} +8 -8
- package/dist/chunk-R7IQWNSA.js.map +1 -0
- package/dist/{chunk-ESRZJRTQ.js → chunk-SI2CKRKJ.js} +86 -56
- package/dist/chunk-SI2CKRKJ.js.map +1 -0
- package/dist/{chunk-FMWNVTJJ.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-WSQMWVSD.js → chunk-YZ5NCWO2.js} +6 -6
- package/dist/chunk-YZ5NCWO2.js.map +1 -0
- package/dist/{chunk-4WO7PJNT.js → chunk-ZYTAKEBW.js} +8 -8
- package/dist/client.cjs +280 -162
- package/dist/client.cjs.map +1 -1
- package/dist/client.js +18 -18
- package/dist/index.cjs +280 -162
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +18 -18
- package/dist/tools/browser/anthropic.cjs +54 -23
- package/dist/tools/browser/anthropic.cjs.map +1 -1
- package/dist/tools/browser/anthropic.js +7 -7
- package/dist/tools/browser/core.cjs +262 -124
- package/dist/tools/browser/core.cjs.map +1 -1
- package/dist/tools/browser/core.d.ts +24 -24
- package/dist/tools/browser/core.js +5 -5
- package/dist/tools/browser/errors.cjs.map +1 -1
- package/dist/tools/browser/errors.d.ts +1 -1
- package/dist/tools/browser/errors.js +1 -1
- package/dist/tools/browser/index.cjs +277 -139
- package/dist/tools/browser/index.cjs.map +1 -1
- package/dist/tools/browser/index.d.ts +3 -3
- package/dist/tools/browser/index.js +12 -12
- 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 +54 -23
- package/dist/tools/browser/openai.cjs.map +1 -1
- package/dist/tools/browser/openai.js +7 -7
- package/dist/tools/browser/profiles/core.cjs +85 -54
- package/dist/tools/browser/profiles/core.cjs.map +1 -1
- package/dist/tools/browser/profiles/core.d.ts +33 -25
- package/dist/tools/browser/profiles/core.js +5 -3
- package/dist/tools/browser/profiles/index.cjs +85 -54
- package/dist/tools/browser/profiles/index.cjs.map +1 -1
- package/dist/tools/browser/profiles/index.d.ts +2 -2
- package/dist/tools/browser/profiles/index.js +5 -3
- package/dist/tools/browser/profiles/types.cjs +1 -1
- package/dist/tools/browser/profiles/types.cjs.map +1 -1
- package/dist/tools/browser/profiles/types.d.ts +28 -9
- package/dist/tools/browser/profiles/types.js +1 -1
- 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 +54 -52
- package/dist/tools/browser/vercel.cjs +56 -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 -7
- 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 +2 -2
- 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 +2 -2
- 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 +7 -8
- 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 +10 -11
- 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/providers/local.js +2 -2
- 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 +1 -1
- package/dist/chunk-5QIWYEHJ.js.map +0 -1
- package/dist/chunk-AV6YV2MH.js.map +0 -1
- package/dist/chunk-ESRZJRTQ.js.map +0 -1
- package/dist/chunk-FMWNVTJJ.js.map +0 -1
- package/dist/chunk-IUG2FHNN.js.map +0 -1
- package/dist/chunk-M7GFXRKL.js.map +0 -1
- package/dist/chunk-SQN4DUQS.js.map +0 -1
- package/dist/chunk-UJ7LVT5G.js.map +0 -1
- package/dist/chunk-VHOWYK66.js.map +0 -1
- package/dist/chunk-WSQMWVSD.js.map +0 -1
- package/dist/chunk-YJ354BA2.js.map +0 -1
- /package/dist/{chunk-R7WN43L2.js.map → chunk-4KMBU6T3.js.map} +0 -0
- /package/dist/{chunk-BVVDDTI7.js.map → chunk-73RV6EXR.js.map} +0 -0
- /package/dist/{chunk-IH3KN4AT.js.map → chunk-7D6TXC7X.js.map} +0 -0
- /package/dist/{chunk-FIA6LBW2.js.map → chunk-O7LDZA52.js.map} +0 -0
- /package/dist/{chunk-TXYCM4NP.js.map → chunk-QAT5UVPX.js.map} +0 -0
- /package/dist/{chunk-4WO7PJNT.js.map → chunk-ZYTAKEBW.js.map} +0 -0
|
@@ -5,10 +5,10 @@ export { BROWSER_SYSTEM_PROMPT, BROWSER_TOOL_DESCRIPTION } from './prompts.js';
|
|
|
5
5
|
export { LIVE_PRESETS, buildEmbedCode, buildLiveIframe, buildLiveUrl } from './live.js';
|
|
6
6
|
export { a as anthropic } from '../../anthropic-B6my2oBx.js';
|
|
7
7
|
export { o as openai } from '../../openai-BQMeDFef.js';
|
|
8
|
-
export { v as vercel } from '../../vercel-
|
|
8
|
+
export { v as vercel } from '../../vercel-CVF27qFK.js';
|
|
9
9
|
import { FunctionDeclaration } from '@google/generative-ai';
|
|
10
10
|
export { ProfilesClient } from './profiles/core.js';
|
|
11
|
-
export { CreateProfileInput, Profile, ProfileListResponse, ProfileSession, ProfileSessionInput, ProfileStateResponse, ProfileWithMethods,
|
|
11
|
+
export { CreateProfileInput, Profile, ProfileListResponse, ProfileSession, ProfileSessionInput, ProfileSetup, ProfileStateResponse, ProfileWithMethods, RepoListResponse, RepoSummary, SaveProfileSessionInput } from './profiles/types.js';
|
|
12
12
|
export { MorphAPIError, MorphAuthenticationError, MorphError, MorphErrorCode, MorphNotFoundError, MorphProfileLimitError, MorphRateLimitError, MorphValidationError, parseAPIError } from './errors.js';
|
|
13
13
|
import '../utils/resilience.js';
|
|
14
14
|
import '@anthropic-ai/sdk/resources/messages.mjs';
|
|
@@ -48,7 +48,7 @@ declare const browserFunctionDeclaration: FunctionDeclaration;
|
|
|
48
48
|
/**
|
|
49
49
|
* Execute browser task
|
|
50
50
|
*
|
|
51
|
-
* @param input - Tool input with task and optional url/
|
|
51
|
+
* @param input - Tool input with task and optional url/maxSteps
|
|
52
52
|
* @param config - Configuration with apiKey
|
|
53
53
|
* @returns Task execution result
|
|
54
54
|
*/
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
vercel_exports
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-R7IQWNSA.js";
|
|
4
4
|
import {
|
|
5
5
|
anthropic_exports
|
|
6
|
-
} from "../../chunk-
|
|
6
|
+
} from "../../chunk-TSENDJQI.js";
|
|
7
7
|
import {
|
|
8
8
|
openai_exports
|
|
9
|
-
} from "../../chunk-
|
|
9
|
+
} from "../../chunk-YZ5NCWO2.js";
|
|
10
10
|
import {
|
|
11
11
|
BROWSER_SYSTEM_PROMPT,
|
|
12
12
|
BROWSER_TOOL_DESCRIPTION
|
|
13
|
-
} from "../../chunk-
|
|
13
|
+
} from "../../chunk-2HMEZZKK.js";
|
|
14
14
|
import {
|
|
15
15
|
BrowserClient,
|
|
16
16
|
checkHealth,
|
|
@@ -20,11 +20,11 @@ import {
|
|
|
20
20
|
getRecording,
|
|
21
21
|
getWebp,
|
|
22
22
|
waitForRecording
|
|
23
|
-
} from "../../chunk-
|
|
23
|
+
} from "../../chunk-QJP62BXH.js";
|
|
24
24
|
import {
|
|
25
25
|
ProfilesClient
|
|
26
|
-
} from "../../chunk-
|
|
27
|
-
import "../../chunk-
|
|
26
|
+
} from "../../chunk-SI2CKRKJ.js";
|
|
27
|
+
import "../../chunk-2AMEQAO2.js";
|
|
28
28
|
import {
|
|
29
29
|
MorphAPIError,
|
|
30
30
|
MorphAuthenticationError,
|
|
@@ -34,13 +34,13 @@ import {
|
|
|
34
34
|
MorphRateLimitError,
|
|
35
35
|
MorphValidationError,
|
|
36
36
|
parseAPIError
|
|
37
|
-
} from "../../chunk-
|
|
37
|
+
} from "../../chunk-2VERUKO2.js";
|
|
38
38
|
import {
|
|
39
39
|
LIVE_PRESETS,
|
|
40
40
|
buildEmbedCode,
|
|
41
41
|
buildLiveIframe,
|
|
42
42
|
buildLiveUrl
|
|
43
|
-
} from "../../chunk-
|
|
43
|
+
} from "../../chunk-Q6Y4R236.js";
|
|
44
44
|
import "../../chunk-4VWJFZVS.js";
|
|
45
45
|
import {
|
|
46
46
|
__export
|
|
@@ -69,7 +69,7 @@ var TOOL_PARAMETERS = {
|
|
|
69
69
|
type: SchemaType.STRING,
|
|
70
70
|
description: "Starting URL (e.g., https://3000-xyz.e2b.dev). Required if navigating to a specific page."
|
|
71
71
|
},
|
|
72
|
-
|
|
72
|
+
maxSteps: {
|
|
73
73
|
type: SchemaType.NUMBER,
|
|
74
74
|
description: "Maximum number of browser actions to take (1-50). Default: 10. Use 15-30 for complex flows."
|
|
75
75
|
},
|
|
@@ -93,8 +93,8 @@ function formatResult(result) {
|
|
|
93
93
|
if (result.success) {
|
|
94
94
|
const parts = [
|
|
95
95
|
"\u2705 Browser task completed successfully",
|
|
96
|
-
`Steps taken: ${result.
|
|
97
|
-
result.
|
|
96
|
+
`Steps taken: ${result.stepsTaken ?? 0}`,
|
|
97
|
+
result.executionTimeMs ? `Execution time: ${result.executionTimeMs}ms` : null,
|
|
98
98
|
"",
|
|
99
99
|
"Result:",
|
|
100
100
|
result.result || "Task completed"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../tools/browser/gemini.ts"],"sourcesContent":["/**\n * Google Gemini SDK adapter for browser automation tool\n */\n\nimport type { FunctionDeclaration, FunctionDeclarationSchema } from '@google/generative-ai';\nimport { SchemaType } from '@google/generative-ai';\nimport { executeBrowserTask } from './core.js';\nimport type {\n BrowserConfig,\n BrowserTaskInput,\n BrowserTaskResult,\n} from './types.js';\nimport { BROWSER_TOOL_DESCRIPTION, BROWSER_SYSTEM_PROMPT } from './prompts.js';\n\n/**\n * Parameter schema for the browser tool (Gemini format)\n */\nconst TOOL_PARAMETERS: FunctionDeclarationSchema = {\n type: SchemaType.OBJECT,\n properties: {\n task: {\n type: SchemaType.STRING,\n description: 'Natural language description of what to do (e.g., \"Test checkout flow for buying a pineapple\")',\n },\n url: {\n type: SchemaType.STRING,\n description: 'Starting URL (e.g., https://3000-xyz.e2b.dev). Required if navigating to a specific page.',\n },\n
|
|
1
|
+
{"version":3,"sources":["../../../tools/browser/gemini.ts"],"sourcesContent":["/**\n * Google Gemini SDK adapter for browser automation tool\n */\n\nimport type { FunctionDeclaration, FunctionDeclarationSchema } from '@google/generative-ai';\nimport { SchemaType } from '@google/generative-ai';\nimport { executeBrowserTask } from './core.js';\nimport type {\n BrowserConfig,\n BrowserTaskInput,\n BrowserTaskResult,\n} from './types.js';\nimport { BROWSER_TOOL_DESCRIPTION, BROWSER_SYSTEM_PROMPT } from './prompts.js';\n\n/**\n * Parameter schema for the browser tool (Gemini format)\n */\nconst TOOL_PARAMETERS: FunctionDeclarationSchema = {\n type: SchemaType.OBJECT,\n properties: {\n task: {\n type: SchemaType.STRING,\n description: 'Natural language description of what to do (e.g., \"Test checkout flow for buying a pineapple\")',\n },\n url: {\n type: SchemaType.STRING,\n description: 'Starting URL (e.g., https://3000-xyz.e2b.dev). Required if navigating to a specific page.',\n },\n maxSteps: {\n type: SchemaType.NUMBER,\n description: 'Maximum number of browser actions to take (1-50). Default: 10. Use 15-30 for complex flows.',\n },\n region: {\n type: SchemaType.STRING,\n description: 'Browserless region: sfo (US West Coast) or lon (Europe). Default: sfo.',\n },\n },\n required: ['task'],\n};\n\n/**\n * Gemini-native browser function declaration\n * \n * @example\n * ```typescript\n * import { GoogleGenerativeAI } from '@google/generative-ai';\n * import { browserFunctionDeclaration, execute } from '@morphllm/morphsdk/tools/browser/gemini';\n * \n * const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY);\n * const model = genAI.getGenerativeModel({\n * model: 'gemini-3-flash-preview',\n * tools: [{ functionDeclarations: [browserFunctionDeclaration] }]\n * });\n * \n * const chat = model.startChat();\n * const result = await chat.sendMessage('Test the checkout flow');\n * \n * // Handle function call\n * const call = result.response.functionCalls()?.[0];\n * if (call) {\n * const taskResult = await execute(call.args, { apiKey: 'key' });\n * console.log(taskResult);\n * }\n * ```\n */\nexport const browserFunctionDeclaration: FunctionDeclaration = {\n name: 'browser_task',\n description: BROWSER_TOOL_DESCRIPTION,\n parameters: TOOL_PARAMETERS,\n};\n\n/**\n * Execute browser task\n * \n * @param input - Tool input with task and optional url/maxSteps\n * @param config - Configuration with apiKey\n * @returns Task execution result\n */\nexport async function execute(\n input: BrowserTaskInput | string,\n config?: BrowserConfig\n): Promise<BrowserTaskResult> {\n const parsedInput = typeof input === 'string' ? JSON.parse(input) : input;\n return executeBrowserTask(parsedInput, config);\n}\n\n/**\n * Format browser task result for Gemini tool result\n * \n * Returns a concise summary suitable for agent context. The full result object\n * (with urls, errors, action_history, judgement, etc.) is available when calling\n * execute() directly, but this formatted string omits those details to save tokens.\n * \n * @param result - Browser task result with full history data\n * @returns Formatted string summary for tool result\n */\nexport function formatResult(result: BrowserTaskResult): string {\n if (result.success) {\n const parts = [\n '✅ Browser task completed successfully',\n `Steps taken: ${result.stepsTaken ?? 0}`,\n result.executionTimeMs ? `Execution time: ${result.executionTimeMs}ms` : null,\n '',\n 'Result:',\n result.result || 'Task completed',\n ];\n return parts.filter(Boolean).join('\\n');\n }\n\n return `❌ Browser task failed: ${result.error || 'Unknown error'}`;\n}\n\n/**\n * Get system prompt for browser automation\n */\nexport function getSystemPrompt(): string {\n return BROWSER_SYSTEM_PROMPT;\n}\n\n/**\n * Gemini tool with execute method attached\n */\nexport interface GeminiBrowserTool extends FunctionDeclaration {\n execute: (input: BrowserTaskInput | string) => Promise<BrowserTaskResult>;\n formatResult: (result: BrowserTaskResult) => string;\n getSystemPrompt: () => string;\n}\n\n/**\n * Create a configured browser tool with execute and formatResult methods\n * \n * @param config - Browser worker configuration\n * @returns Function declaration with execute and formatResult methods\n * \n * @example\n * ```typescript\n * import { GoogleGenerativeAI, FunctionCallingMode } from '@google/generative-ai';\n * import { createBrowserTool } from '@morphllm/morphsdk/tools/browser/gemini';\n * \n * const tool = createBrowserTool({ apiKey: process.env.MORPH_API_KEY });\n * \n * const genAI = new GoogleGenerativeAI(process.env.GOOGLE_API_KEY);\n * const model = genAI.getGenerativeModel({\n * model: 'gemini-3-flash-preview',\n * tools: [{ functionDeclarations: [tool] }],\n * toolConfig: { functionCallingConfig: { mode: FunctionCallingMode.AUTO } }\n * });\n * \n * const chat = model.startChat();\n * const result = await chat.sendMessage('Test the checkout flow at https://example.com');\n * \n * // Handle function call\n * const call = result.response.functionCalls()?.[0];\n * if (call && call.name === tool.name) {\n * const taskResult = await tool.execute(call.args);\n * console.log(tool.formatResult(taskResult));\n * \n * // Send result back to model\n * await chat.sendMessage([{\n * functionResponse: {\n * name: call.name,\n * response: { result: tool.formatResult(taskResult) }\n * }\n * }]);\n * }\n * ```\n */\nexport function createBrowserTool(config?: BrowserConfig): GeminiBrowserTool {\n const declaration: FunctionDeclaration = {\n name: 'browser_task',\n description: BROWSER_TOOL_DESCRIPTION,\n parameters: TOOL_PARAMETERS,\n };\n\n return Object.assign(declaration, {\n execute: async (input: BrowserTaskInput | string): Promise<BrowserTaskResult> => {\n return execute(input, config);\n },\n formatResult: (result: BrowserTaskResult): string => {\n return formatResult(result);\n },\n getSystemPrompt: (): string => {\n return getSystemPrompt();\n },\n });\n}\n\n// Legacy alias for consistency with some patterns\nexport const browserTool = browserFunctionDeclaration;\n\nexport default browserFunctionDeclaration;\n\n\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,SAAS,kBAAkB;AAY3B,IAAM,kBAA6C;AAAA,EACjD,MAAM,WAAW;AAAA,EACjB,YAAY;AAAA,IACV,MAAM;AAAA,MACJ,MAAM,WAAW;AAAA,MACjB,aAAa;AAAA,IACf;AAAA,IACA,KAAK;AAAA,MACH,MAAM,WAAW;AAAA,MACjB,aAAa;AAAA,IACf;AAAA,IACA,UAAU;AAAA,MACR,MAAM,WAAW;AAAA,MACjB,aAAa;AAAA,IACf;AAAA,IACA,QAAQ;AAAA,MACN,MAAM,WAAW;AAAA,MACjB,aAAa;AAAA,IACf;AAAA,EACF;AAAA,EACA,UAAU,CAAC,MAAM;AACnB;AA2BO,IAAM,6BAAkD;AAAA,EAC7D,MAAM;AAAA,EACN,aAAa;AAAA,EACb,YAAY;AACd;AASA,eAAsB,QACpB,OACA,QAC4B;AAC5B,QAAM,cAAc,OAAO,UAAU,WAAW,KAAK,MAAM,KAAK,IAAI;AACpE,SAAO,mBAAmB,aAAa,MAAM;AAC/C;AAYO,SAAS,aAAa,QAAmC;AAC9D,MAAI,OAAO,SAAS;AAClB,UAAM,QAAQ;AAAA,MACZ;AAAA,MACA,gBAAgB,OAAO,cAAc,CAAC;AAAA,MACtC,OAAO,kBAAkB,mBAAmB,OAAO,eAAe,OAAO;AAAA,MACzE;AAAA,MACA;AAAA,MACA,OAAO,UAAU;AAAA,IACnB;AACA,WAAO,MAAM,OAAO,OAAO,EAAE,KAAK,IAAI;AAAA,EACxC;AAEA,SAAO,+BAA0B,OAAO,SAAS,eAAe;AAClE;AAKO,SAAS,kBAA0B;AACxC,SAAO;AACT;AAkDO,SAAS,kBAAkB,QAA2C;AAC3E,QAAM,cAAmC;AAAA,IACvC,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,EACd;AAEA,SAAO,OAAO,OAAO,aAAa;AAAA,IAChC,SAAS,OAAO,UAAiE;AAC/E,aAAO,QAAQ,OAAO,MAAM;AAAA,IAC9B;AAAA,IACA,cAAc,CAAC,WAAsC;AACnD,aAAO,aAAa,MAAM;AAAA,IAC5B;AAAA,IACA,iBAAiB,MAAc;AAC7B,aAAO,gBAAgB;AAAA,IACzB;AAAA,EACF,CAAC;AACH;AAGO,IAAM,cAAc;AAE3B,IAAO,iBAAQ;","names":[]}
|
|
@@ -41,7 +41,8 @@ function buildLiveUrl(debugUrl, options = {}) {
|
|
|
41
41
|
"debugUrl is required. Ensure your backend returns debugUrl in the task response. Contact support@morphllm.com if you need help."
|
|
42
42
|
);
|
|
43
43
|
}
|
|
44
|
-
const
|
|
44
|
+
const normalized = normalizeLiveUrl(debugUrl);
|
|
45
|
+
const url = new URL(normalized);
|
|
45
46
|
if (options.interactive !== void 0) {
|
|
46
47
|
url.searchParams.set("interactive", String(options.interactive));
|
|
47
48
|
}
|
|
@@ -59,6 +60,29 @@ function buildLiveUrl(debugUrl, options = {}) {
|
|
|
59
60
|
}
|
|
60
61
|
return url.toString();
|
|
61
62
|
}
|
|
63
|
+
function normalizeLiveUrl(debugUrl) {
|
|
64
|
+
const trimmed = debugUrl.trim();
|
|
65
|
+
if (!trimmed) {
|
|
66
|
+
return trimmed;
|
|
67
|
+
}
|
|
68
|
+
if (trimmed.startsWith("wss://") || trimmed.startsWith("ws://")) {
|
|
69
|
+
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
70
|
+
}
|
|
71
|
+
let url;
|
|
72
|
+
try {
|
|
73
|
+
url = new URL(trimmed);
|
|
74
|
+
} catch {
|
|
75
|
+
return trimmed;
|
|
76
|
+
}
|
|
77
|
+
if (url.protocol === "wss:" || url.protocol === "ws:") {
|
|
78
|
+
return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;
|
|
79
|
+
}
|
|
80
|
+
const wssParam = url.searchParams.get("wss");
|
|
81
|
+
if (wssParam && (wssParam.startsWith("wss://") || wssParam.startsWith("ws://"))) {
|
|
82
|
+
url.searchParams.set("wss", wssParam);
|
|
83
|
+
}
|
|
84
|
+
return url.toString();
|
|
85
|
+
}
|
|
62
86
|
function buildLiveIframe(debugUrl, options = {}) {
|
|
63
87
|
const {
|
|
64
88
|
width = "100%",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../tools/browser/live.ts"],"sourcesContent":["/**\n * Live session utilities for Morph browser sessions\n * \n * Provides helpers for embedding and sharing live browser sessions with WebRTC streaming.\n */\n\nimport type { LiveSessionOptions, IframeOptions } from './types.js';\n\n/**\n * Preset configurations for common use cases\n */\nexport const LIVE_PRESETS = {\n /** Read-only monitoring (no interaction) */\n readonly: { interactive: false } as LiveSessionOptions,\n /** Interactive control (human-in-the-loop) */\n interactive: { interactive: true } as LiveSessionOptions,\n /** Watch-only without controls */\n monitoring: { interactive: false, showControls: false } as LiveSessionOptions,\n} as const;\n\n/**\n * Build a live session URL with query parameters\n * \n * @param debugUrl - Live session debug URL (e.g., from task.debugUrl)\n * @param options - Live session configuration options\n * @returns URL with query parameters for iframe embedding\n * \n * @example\n * ```typescript\n * const url = buildLiveUrl(task.debugUrl, { interactive: true });\n * // Returns: https://example.com/sessions/abc?interactive=true\n * ```\n */\nexport function buildLiveUrl(\n debugUrl: string,\n options: LiveSessionOptions = {}\n): string {\n if (!debugUrl) {\n throw new Error(\n 'debugUrl is required. Ensure your backend returns debugUrl in the task response. ' +\n 'Contact support@morphllm.com if you need help.'\n );\n }
|
|
1
|
+
{"version":3,"sources":["../../../tools/browser/live.ts"],"sourcesContent":["/**\n * Live session utilities for Morph browser sessions\n * \n * Provides helpers for embedding and sharing live browser sessions with WebRTC streaming.\n */\n\nimport type { LiveSessionOptions, IframeOptions } from './types.js';\n\n/**\n * Preset configurations for common use cases\n */\nexport const LIVE_PRESETS = {\n /** Read-only monitoring (no interaction) */\n readonly: { interactive: false } as LiveSessionOptions,\n /** Interactive control (human-in-the-loop) */\n interactive: { interactive: true } as LiveSessionOptions,\n /** Watch-only without controls */\n monitoring: { interactive: false, showControls: false } as LiveSessionOptions,\n} as const;\n\n/**\n * Build a live session URL with query parameters\n * \n * @param debugUrl - Live session debug URL (e.g., from task.debugUrl)\n * @param options - Live session configuration options\n * @returns URL with query parameters for iframe embedding\n * \n * @example\n * ```typescript\n * const url = buildLiveUrl(task.debugUrl, { interactive: true });\n * // Returns: https://example.com/sessions/abc?interactive=true\n * ```\n */\nexport function buildLiveUrl(\n debugUrl: string,\n options: LiveSessionOptions = {}\n): string {\n if (!debugUrl) {\n throw new Error(\n 'debugUrl is required. Ensure your backend returns debugUrl in the task response. ' +\n 'Contact support@morphllm.com if you need help.'\n );\n }\n\n const normalized = normalizeLiveUrl(debugUrl);\n const url = new URL(normalized);\n \n // Add query parameters for supported options\n if (options.interactive !== undefined) {\n url.searchParams.set('interactive', String(options.interactive));\n }\n \n if (options.theme) {\n url.searchParams.set('theme', options.theme);\n }\n \n if (options.showControls !== undefined) {\n url.searchParams.set('showControls', String(options.showControls));\n }\n \n if (options.pageId) {\n url.searchParams.set('pageId', options.pageId);\n }\n \n if (options.pageIndex) {\n url.searchParams.set('pageIndex', options.pageIndex);\n }\n \n return url.toString();\n}\n\nfunction normalizeLiveUrl(debugUrl: string): string {\n const trimmed = debugUrl.trim();\n if (!trimmed) {\n return trimmed;\n }\n\n if (trimmed.startsWith('wss://') || trimmed.startsWith('ws://')) {\n return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;\n }\n\n let url: URL;\n try {\n url = new URL(trimmed);\n } catch {\n return trimmed;\n }\n\n if (url.protocol === 'wss:' || url.protocol === 'ws:') {\n return `https://live.browser-use.com?wss=${encodeURIComponent(trimmed)}`;\n }\n\n const wssParam = url.searchParams.get('wss');\n if (wssParam && (wssParam.startsWith('wss://') || wssParam.startsWith('ws://'))) {\n url.searchParams.set('wss', wssParam);\n }\n\n return url.toString();\n}\n\n/**\n * Build iframe HTML for embedding a live session\n * \n * @param debugUrl - Live session debug URL\n * @param options - Iframe configuration including dimensions and session options\n * @returns HTML iframe element as string\n * \n * @example\n * ```typescript\n * const iframe = buildLiveIframe(task.debugUrl, {\n * interactive: true,\n * width: '100%',\n * height: '600px'\n * });\n * ```\n */\nexport function buildLiveIframe(\n debugUrl: string,\n options: IframeOptions = {}\n): string {\n const {\n width = '100%',\n height = '600px',\n style = '',\n className = '',\n ...sessionOptions\n } = options;\n\n const src = buildLiveUrl(debugUrl, sessionOptions);\n \n // Convert numeric dimensions to pixels\n const widthStr = typeof width === 'number' ? `${width}px` : width;\n const heightStr = typeof height === 'number' ? `${height}px` : height;\n \n // Build style attribute\n const baseStyle = `width: ${widthStr}; height: ${heightStr}; border: none;`;\n const fullStyle = style ? `${baseStyle} ${style}` : baseStyle;\n \n // Build iframe attributes\n const attributes = [\n `src=\"${src}\"`,\n `style=\"${fullStyle}\"`,\n ];\n \n if (className) {\n attributes.push(`class=\"${className}\"`);\n }\n \n return `<iframe ${attributes.join(' ')}></iframe>`;\n}\n\n/**\n * Build complete embed code with HTML snippet\n * \n * @param debugUrl - Live session debug URL\n * @param options - Iframe configuration\n * @returns Multi-line HTML snippet ready to copy-paste\n * \n * @example\n * ```typescript\n * const code = buildEmbedCode(task.debugUrl, { interactive: false });\n * console.log(code);\n * // <!-- Embed Morph Live Session -->\n * // <iframe src=\"...\" style=\"...\"></iframe>\n * ```\n */\nexport function buildEmbedCode(\n debugUrl: string,\n options: IframeOptions = {}\n): string {\n const iframe = buildLiveIframe(debugUrl, options);\n return `<!-- Embed Morph Live Session -->\\n${iframe}`;\n}\n\n/**\n * Get live session options from preset name or custom config\n * \n * @internal\n */\nexport function resolvePreset(\n optionsOrPreset?: string | IframeOptions\n): IframeOptions {\n if (!optionsOrPreset) {\n return {};\n }\n \n if (typeof optionsOrPreset === 'string') {\n const preset = LIVE_PRESETS[optionsOrPreset as keyof typeof LIVE_PRESETS];\n if (!preset) {\n throw new Error(\n `Unknown preset: ${optionsOrPreset}. Available presets: ${Object.keys(LIVE_PRESETS).join(', ')}`\n );\n }\n return preset;\n }\n \n return optionsOrPreset;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -122,15 +122,15 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
122
122
|
error: 'Task description is required. Example: "Go to example.com and click the login button"'
|
|
123
123
|
};
|
|
124
124
|
}
|
|
125
|
-
if (input.
|
|
125
|
+
if (input.maxSteps !== void 0 && (input.maxSteps < 1 || input.maxSteps > 50)) {
|
|
126
126
|
return {
|
|
127
127
|
success: false,
|
|
128
|
-
error: "
|
|
128
|
+
error: "maxSteps must be between 1 and 50. Use more steps for complex multi-page flows."
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
if (debug) {
|
|
132
|
-
console.log(`[Browser] Task: "${input.task.slice(0, 60)}..." url=${input.url || "none"} maxSteps=${input.
|
|
133
|
-
console.log(`[Browser] Recording: ${input.
|
|
132
|
+
console.log(`[Browser] Task: "${input.task.slice(0, 60)}..." url=${input.url || "none"} maxSteps=${input.maxSteps ?? 10}`);
|
|
133
|
+
console.log(`[Browser] Recording: ${input.recordVideo ? "yes" : "no"} | Calling ${apiUrl}/browser-task`);
|
|
134
134
|
}
|
|
135
135
|
const startTime = Date.now();
|
|
136
136
|
try {
|
|
@@ -144,20 +144,20 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
144
144
|
body: JSON.stringify({
|
|
145
145
|
task: input.task,
|
|
146
146
|
url: input.url,
|
|
147
|
-
max_steps: input.
|
|
147
|
+
max_steps: input.maxSteps ?? 10,
|
|
148
148
|
model: input.model ?? "morph-computer-use-v0",
|
|
149
|
-
viewport_width: input.
|
|
150
|
-
viewport_height: input.
|
|
151
|
-
external_id: input.
|
|
152
|
-
repo_id: input.
|
|
153
|
-
commit_id: input.
|
|
154
|
-
record_video: input.
|
|
155
|
-
video_width: input.
|
|
156
|
-
video_height: input.
|
|
157
|
-
allow_resizing: input.
|
|
158
|
-
structured_output: input.
|
|
149
|
+
viewport_width: input.viewportWidth ?? 1280,
|
|
150
|
+
viewport_height: input.viewportHeight ?? 720,
|
|
151
|
+
external_id: input.externalId,
|
|
152
|
+
repo_id: input.repoId,
|
|
153
|
+
commit_id: input.commitId,
|
|
154
|
+
record_video: input.recordVideo ?? false,
|
|
155
|
+
video_width: input.videoWidth ?? input.viewportWidth ?? 1280,
|
|
156
|
+
video_height: input.videoHeight ?? input.viewportHeight ?? 720,
|
|
157
|
+
allow_resizing: input.allowResizing ?? false,
|
|
158
|
+
structured_output: input.structuredOutput,
|
|
159
159
|
auth: input.auth,
|
|
160
|
-
profile_id: input.
|
|
160
|
+
profile_id: input.profileId
|
|
161
161
|
})
|
|
162
162
|
},
|
|
163
163
|
config.retryConfig
|
|
@@ -165,17 +165,17 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
165
165
|
const response = await withTimeout(
|
|
166
166
|
fetchPromise,
|
|
167
167
|
timeout,
|
|
168
|
-
`Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing
|
|
168
|
+
`Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing maxSteps.`
|
|
169
169
|
);
|
|
170
170
|
if (!response.ok) {
|
|
171
171
|
const errorText = await response.text().catch(() => response.statusText);
|
|
172
172
|
if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);
|
|
173
173
|
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
174
174
|
}
|
|
175
|
-
const result = await response.json();
|
|
175
|
+
const result = mapTaskResult(await response.json());
|
|
176
176
|
const elapsed = Date.now() - startTime;
|
|
177
177
|
if (debug) {
|
|
178
|
-
console.log(`[Browser] \u2705 ${result.success ? "Success" : "Failed"} in ${elapsed}ms | steps=${result.
|
|
178
|
+
console.log(`[Browser] \u2705 ${result.success ? "Success" : "Failed"} in ${elapsed}ms | steps=${result.stepsTaken ?? 0} recordingId=${result.recordingId ?? "none"}`);
|
|
179
179
|
}
|
|
180
180
|
return result;
|
|
181
181
|
} catch (error) {
|
|
@@ -197,6 +197,37 @@ async function executeBrowserTask(input, config = {}) {
|
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
|
+
function mapTaskResult(api) {
|
|
201
|
+
if (!api || typeof api !== "object") {
|
|
202
|
+
return api;
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
success: api.success,
|
|
206
|
+
result: api.result,
|
|
207
|
+
error: api.error,
|
|
208
|
+
stepsTaken: api.steps_taken,
|
|
209
|
+
executionTimeMs: api.execution_time_ms,
|
|
210
|
+
urls: api.urls,
|
|
211
|
+
actionNames: api.action_names,
|
|
212
|
+
errors: api.errors,
|
|
213
|
+
modelActions: api.model_actions,
|
|
214
|
+
isDone: api.is_done,
|
|
215
|
+
actionHistory: api.action_history,
|
|
216
|
+
actionResults: api.action_results,
|
|
217
|
+
hasErrors: api.has_errors,
|
|
218
|
+
numberOfSteps: api.number_of_steps,
|
|
219
|
+
judgement: api.judgement,
|
|
220
|
+
isValidated: api.is_validated,
|
|
221
|
+
replayId: api.replay_id,
|
|
222
|
+
replayUrl: api.replay_url,
|
|
223
|
+
recordingId: api.recording_id,
|
|
224
|
+
recordingStatus: api.recording_status,
|
|
225
|
+
taskId: api.task_id,
|
|
226
|
+
status: api.status,
|
|
227
|
+
output: api.output,
|
|
228
|
+
debugUrl: api.debug_url
|
|
229
|
+
};
|
|
230
|
+
}
|
|
200
231
|
|
|
201
232
|
// tools/browser/prompts.ts
|
|
202
233
|
var BROWSER_TOOL_DESCRIPTION = `Test and verify your implemented code in a live browser. This tool executes natural language test instructions and returns a video recording plus detailed logs to help you debug issues.
|
|
@@ -234,7 +265,7 @@ Include verification steps:
|
|
|
234
265
|
## Requirements
|
|
235
266
|
- **URL**: Must be publicly accessible (use tunnels like ngrok, Cloudflare, or deploy to staging)
|
|
236
267
|
- **Timing**: Use this after implementation, not during coding
|
|
237
|
-
- **Complexity**: Set
|
|
268
|
+
- **Complexity**: Set maxSteps higher (20-30) for multi-step user workflows`;
|
|
238
269
|
var BROWSER_SYSTEM_PROMPT = `You are an AI agent designed to automate browser tasks to accomplish the <user_request>. Respond with a valid JSON object in the format: {"thinking": "Reason step-by-step about your current state, history, and the user request to decide your next goal and action. Analyze the browser state and screenshot to confirm the outcome of your last action.", "evaluation_previous_goal": "A concise, one-sentence evaluation of your last action's outcome (e.g., Success, Failure, or Uncertain).", "memory": "1-3 sentences summarizing key information and progress so far. This helps you track progress across multiple steps (e.g., items collected, pages visited).", "next_goal": "A clear, one-sentence description of your immediate next objective.", "action": [{"action_name": {"parameter": "value"}}]}`;
|
|
239
270
|
|
|
240
271
|
// tools/browser/openai.ts
|
|
@@ -254,7 +285,7 @@ var browserTool = {
|
|
|
254
285
|
type: "string",
|
|
255
286
|
description: "Starting URL (e.g., https://3000-xyz.e2b.dev). Required if navigating to a specific page."
|
|
256
287
|
},
|
|
257
|
-
|
|
288
|
+
maxSteps: {
|
|
258
289
|
type: "number",
|
|
259
290
|
description: "Maximum number of browser actions to take (1-50). Default: 10. Use 15-30 for complex flows.",
|
|
260
291
|
default: 10
|
|
@@ -278,8 +309,8 @@ function formatResult(result) {
|
|
|
278
309
|
if (result.success) {
|
|
279
310
|
const parts = [
|
|
280
311
|
"\u2705 Browser task completed successfully",
|
|
281
|
-
`Steps taken: ${result.
|
|
282
|
-
result.
|
|
312
|
+
`Steps taken: ${result.stepsTaken ?? 0}`,
|
|
313
|
+
result.executionTimeMs ? `Execution time: ${result.executionTimeMs}ms` : null,
|
|
283
314
|
"",
|
|
284
315
|
"Result:",
|
|
285
316
|
result.result || "Task completed"
|