@morphllm/morphsdk 0.2.25 → 0.2.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/{chunk-W5CHJ6OX.js → chunk-4VAOPM2H.js} +3 -3
  2. package/dist/{chunk-VVYZYP44.js → chunk-5HY6Q45E.js} +4 -4
  3. package/dist/{chunk-GPFHYUKV.js → chunk-APMKEW7D.js} +7 -13
  4. package/dist/chunk-APMKEW7D.js.map +1 -0
  5. package/dist/chunk-EI4UKP24.js +44 -0
  6. package/dist/chunk-EI4UKP24.js.map +1 -0
  7. package/dist/{chunk-LN4CTQZG.js → chunk-ITA6CF76.js} +3 -3
  8. package/dist/{chunk-EAA7D24N.js → chunk-JJ5ONKDN.js} +7 -7
  9. package/dist/{chunk-YSBSDU75.js → chunk-LAA6XJBF.js} +3 -3
  10. package/dist/{chunk-UYBIKZPM.js → chunk-OVIJAG5M.js} +4 -4
  11. package/dist/client.cjs +6 -12
  12. package/dist/client.cjs.map +1 -1
  13. package/dist/client.js +2 -2
  14. package/dist/index.cjs +6 -12
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.js +4 -4
  17. package/dist/tools/browser/anthropic.cjs +33 -28
  18. package/dist/tools/browser/anthropic.cjs.map +1 -1
  19. package/dist/tools/browser/anthropic.js +2 -2
  20. package/dist/tools/browser/core.cjs +6 -12
  21. package/dist/tools/browser/core.cjs.map +1 -1
  22. package/dist/tools/browser/core.js +1 -1
  23. package/dist/tools/browser/index.cjs +37 -38
  24. package/dist/tools/browser/index.cjs.map +1 -1
  25. package/dist/tools/browser/index.js +2 -2
  26. package/dist/tools/browser/openai.cjs +33 -28
  27. package/dist/tools/browser/openai.cjs.map +1 -1
  28. package/dist/tools/browser/openai.js +2 -2
  29. package/dist/tools/browser/prompts.cjs +31 -26
  30. package/dist/tools/browser/prompts.cjs.map +1 -1
  31. package/dist/tools/browser/prompts.js +1 -1
  32. package/dist/tools/browser/types.cjs.map +1 -1
  33. package/dist/tools/browser/vercel.cjs +35 -14
  34. package/dist/tools/browser/vercel.cjs.map +1 -1
  35. package/dist/tools/browser/vercel.js +2 -2
  36. package/dist/tools/fastapply/index.js +3 -3
  37. package/dist/tools/index.js +3 -3
  38. package/dist/tools/warp_grep/agent/runner.js +3 -3
  39. package/dist/tools/warp_grep/anthropic.js +6 -6
  40. package/dist/tools/warp_grep/index.js +8 -8
  41. package/dist/tools/warp_grep/openai.js +6 -6
  42. package/dist/tools/warp_grep/providers/local.js +2 -2
  43. package/dist/tools/warp_grep/vercel.js +6 -6
  44. package/package.json +1 -1
  45. package/dist/chunk-7PZJQFCY.js +0 -39
  46. package/dist/chunk-7PZJQFCY.js.map +0 -1
  47. package/dist/chunk-GPFHYUKV.js.map +0 -1
  48. /package/dist/{chunk-W5CHJ6OX.js.map → chunk-4VAOPM2H.js.map} +0 -0
  49. /package/dist/{chunk-VVYZYP44.js.map → chunk-5HY6Q45E.js.map} +0 -0
  50. /package/dist/{chunk-LN4CTQZG.js.map → chunk-ITA6CF76.js.map} +0 -0
  51. /package/dist/{chunk-EAA7D24N.js.map → chunk-JJ5ONKDN.js.map} +0 -0
  52. /package/dist/{chunk-YSBSDU75.js.map → chunk-LAA6XJBF.js.map} +0 -0
  53. /package/dist/{chunk-UYBIKZPM.js.map → chunk-OVIJAG5M.js.map} +0 -0
@@ -24,38 +24,43 @@ __export(prompts_exports, {
24
24
  BROWSER_TOOL_DESCRIPTION: () => BROWSER_TOOL_DESCRIPTION
25
25
  });
26
26
  module.exports = __toCommonJS(prompts_exports);
27
- var BROWSER_TOOL_DESCRIPTION = `Execute natural language browser automation tasks using an AI-powered agent. The agent can navigate websites, interact with elements, fill forms, click buttons, and verify functionality.
27
+ 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.
28
28
 
29
- Use this tool to:
30
- - Test web applications end-to-end
31
- - Verify UI functionality
32
- - Automate user workflows
33
- - Extract information from web pages
29
+ ## When to Use
30
+ Use this AFTER coding is complete to verify your implementation:
31
+ - You've finished writing/modifying code and need to verify it works
32
+ - You need to test user interactions end-to-end (clicks, forms, navigation)
33
+ - You want to catch runtime errors, console warnings, or network failures
34
+ - You need visual confirmation that UI elements render and behave correctly
34
35
 
35
- The agent uses GPT-4o-mini to interpret your task and execute the necessary browser actions. It runs in a remote browser via Browserless.io, so it can access any publicly accessible URL.
36
+ ## What You Get Back
37
+ The tool returns debugging artifacts to help you identify and fix issues:
38
+ - **Video recording**: Watch exactly what happened in the browser session
39
+ - **Console logs**: All console.log, warnings, and errors with timestamps
40
+ - **Network logs**: Failed requests, 404s, API errors with screenshots
41
+ - **Error screenshots**: Visual snapshots captured when errors occur
36
42
 
37
- Important:
38
- - Provide clear, specific task descriptions
39
- - Include the starting URL if navigating to a specific page
40
- - Use remote URLs (e.g., https://3000-xyz.e2b.dev) not localhost
41
- - Complex tasks may require more max_steps`;
42
- var BROWSER_SYSTEM_PROMPT = `You have access to browser automation capabilities. When testing or interacting with web applications:
43
+ ## How to Write Good Tests
44
+ Be specific about the user journey you're testing:
45
+ \u274C Bad: "Test the login feature"
46
+ \u2705 Good: "Navigate to /login, enter 'test@example.com' and 'password123', click the Login button, verify we reach the /dashboard page"
43
47
 
44
- 1. Be specific about what you're testing or verifying
45
- 2. Break complex tasks into clear steps
46
- 3. Always provide the full URL including protocol (https://)
47
- 4. For localhost apps, use the remote tunnel URL (e.g., e2b.dev URLs)
48
- 5. Specify the number of steps needed - simple tasks use 5-10, complex flows use 15-30
48
+ Include verification steps:
49
+ \u2705 "Click the Add Item button, verify the item appears in the list"
50
+ \u2705 "Submit the form, verify a success message is displayed"
49
51
 
50
- Example good tasks:
51
- - "Go to https://3000-abc.e2b.dev and verify the landing page loads with a hero section"
52
- - "Test guest checkout: add a pineapple to cart, proceed to checkout, fill shipping info, and verify order summary"
53
- - "Navigate to the dashboard and click on the settings tab, then verify the API keys section is visible"
52
+ ## Iterating on Failures
53
+ 1. Run the test and review the video + logs
54
+ 2. Identify the specific issue (console error, failed request, wrong behavior)
55
+ 3. Fix the code based on the evidence
56
+ 4. Re-run the test to verify the fix
57
+ 5. Repeat until test passes
54
58
 
55
- Example bad tasks:
56
- - "test the app" (too vague)
57
- - "go to localhost:3000" (use remote URL instead)
58
- - "do everything" (not specific enough)`;
59
+ ## Requirements
60
+ - **URL**: Must be publicly accessible (use tunnels like ngrok, Cloudflare, or deploy to staging)
61
+ - **Timing**: Use this after implementation, not during coding
62
+ - **Complexity**: Set max_steps higher (20-30) for multi-step user workflows`;
63
+ 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"}}]}`;
59
64
  // Annotate the CommonJS export names for ESM import in node:
60
65
  0 && (module.exports = {
61
66
  BROWSER_SYSTEM_PROMPT,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../tools/browser/prompts.ts"],"sourcesContent":["/**\n * Tool descriptions and prompts for AI models\n */\n\nexport const BROWSER_TOOL_DESCRIPTION = `Execute natural language browser automation tasks using an AI-powered agent. The agent can navigate websites, interact with elements, fill forms, click buttons, and verify functionality.\n\nUse this tool to:\n- Test web applications end-to-end\n- Verify UI functionality\n- Automate user workflows\n- Extract information from web pages\n\nThe agent uses GPT-4o-mini to interpret your task and execute the necessary browser actions. It runs in a remote browser via Browserless.io, so it can access any publicly accessible URL.\n\nImportant:\n- Provide clear, specific task descriptions\n- Include the starting URL if navigating to a specific page\n- Use remote URLs (e.g., https://3000-xyz.e2b.dev) not localhost\n- Complex tasks may require more max_steps`;\n\nexport const BROWSER_SYSTEM_PROMPT = `You have access to browser automation capabilities. When testing or interacting with web applications:\n\n1. Be specific about what you're testing or verifying\n2. Break complex tasks into clear steps\n3. Always provide the full URL including protocol (https://)\n4. For localhost apps, use the remote tunnel URL (e.g., e2b.dev URLs)\n5. Specify the number of steps needed - simple tasks use 5-10, complex flows use 15-30\n\nExample good tasks:\n- \"Go to https://3000-abc.e2b.dev and verify the landing page loads with a hero section\"\n- \"Test guest checkout: add a pineapple to cart, proceed to checkout, fill shipping info, and verify order summary\"\n- \"Navigate to the dashboard and click on the settings tab, then verify the API keys section is visible\"\n\nExample bad tasks:\n- \"test the app\" (too vague)\n- \"go to localhost:3000\" (use remote URL instead)\n- \"do everything\" (not specific enough)`;\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,IAAM,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBjC,IAAM,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../../../tools/browser/prompts.ts"],"sourcesContent":["/**\n * Tool descriptions and prompts for AI models\n */\n\nexport const 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.\n\n## When to Use\nUse this AFTER coding is complete to verify your implementation:\n- You've finished writing/modifying code and need to verify it works\n- You need to test user interactions end-to-end (clicks, forms, navigation)\n- You want to catch runtime errors, console warnings, or network failures\n- You need visual confirmation that UI elements render and behave correctly\n\n## What You Get Back\nThe tool returns debugging artifacts to help you identify and fix issues:\n- **Video recording**: Watch exactly what happened in the browser session\n- **Console logs**: All console.log, warnings, and errors with timestamps\n- **Network logs**: Failed requests, 404s, API errors with screenshots\n- **Error screenshots**: Visual snapshots captured when errors occur\n\n## How to Write Good Tests\nBe specific about the user journey you're testing:\n❌ Bad: \"Test the login feature\"\n Good: \"Navigate to /login, enter 'test@example.com' and 'password123', click the Login button, verify we reach the /dashboard page\"\n\nInclude verification steps:\n✅ \"Click the Add Item button, verify the item appears in the list\"\n✅ \"Submit the form, verify a success message is displayed\"\n\n## Iterating on Failures\n1. Run the test and review the video + logs\n2. Identify the specific issue (console error, failed request, wrong behavior)\n3. Fix the code based on the evidence\n4. Re-run the test to verify the fix\n5. Repeat until test passes\n\n## Requirements\n- **URL**: Must be publicly accessible (use tunnels like ngrok, Cloudflare, or deploy to staging)\n- **Timing**: Use this after implementation, not during coding\n- **Complexity**: Set max_steps higher (20-30) for multi-step user workflows`;\n\n\nexport const 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\"}}]}`;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,IAAM,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsCjC,IAAM,wBAAwB;","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  BROWSER_SYSTEM_PROMPT,
3
3
  BROWSER_TOOL_DESCRIPTION
4
- } from "../../chunk-7PZJQFCY.js";
4
+ } from "../../chunk-EI4UKP24.js";
5
5
  import "../../chunk-PZ5AY32C.js";
6
6
  export {
7
7
  BROWSER_SYSTEM_PROMPT,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../tools/browser/types.ts"],"sourcesContent":["/**\n * Type definitions for browser automation tool\n */\n\nimport type { RetryConfig } from '../utils/resilience.js';\n\n/**\n * Available browser automation models\n */\nexport type BrowserModel = \n | 'gemini-flash-latest'\n | 'morph-computer-use-v0';\n\n/**\n * Configuration for the browser worker service\n */\nexport interface BrowserConfig {\n /** Browser worker API URL (default: https://browser.morphllm.com) */\n apiUrl?: string;\n /** Morph API key for authentication (required in production) */\n apiKey?: string;\n /** Request timeout in milliseconds (default: 120000) */\n timeout?: number;\n /** Retry configuration for API calls */\n retryConfig?: RetryConfig;\n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\n/**\n * Input parameters for browser automation task\n */\nexport interface BrowserTaskInput {\n /** Natural language description of what to do */\n task: string;\n /** Starting URL (e.g., https://3000-xyz.e2b.dev) */\n url?: string;\n /** Maximum number of browser actions to take (1-50, default: 10) */\n max_steps?: number;\n /** Model to use for task execution (default: morph-computer-use-v0) */\n model?: BrowserModel;\n /** Browserless region: 'sfo' (US West) or 'lon' (Europe) */\n region?: 'sfo' | 'lon';\n /** Enable stealth mode to avoid bot detection (default: true) */\n stealth?: boolean;\n /** Browser viewport width (default: 1280) */\n viewport_width?: number;\n /** Browser viewport height (default: 720) */\n viewport_height?: number;\n /** User-defined reference/tracking ID (e.g., \"PR-1234\", \"jira-PROJ-567\") */\n external_id?: string;\n /** Optional repository ID to associate with this session */\n repo_id?: string;\n /** Optional commit UUID to associate with this session */\n commit_id?: string;\n /** Record session video to S3 (default: false) */\n record_video?: boolean;\n /** Video width (default: 1280) */\n video_width?: number;\n /** Video height (default: 720) */\n video_height?: number;\n /** Serialized structured output schema (internal use) */\n structured_output?: string;\n}\n\n/**\n * Input with Zod schema for structured output\n */\nexport interface BrowserTaskInputWithSchema<T> extends Omit<BrowserTaskInput, 'structured_output'> {\n /** Zod schema for structured output */\n schema: any;\n}\n\n/**\n * Result from executing a browser task\n */\nexport interface BrowserTaskResult {\n /** Whether the task completed successfully */\n success: boolean;\n /** Task result or findings */\n result?: string;\n /** Error message if task failed */\n error?: string;\n /** Number of browser actions taken */\n steps_taken?: number;\n /** Total execution time in milliseconds */\n execution_time_ms?: number;\n /** UUID of saved replay record (if session replay enabled) */\n replay_id?: string;\n /** Browserless replay URL (if session replay enabled) */\n replay_url?: string;\n /** Recording ID (if record_video=true) */\n recording_id?: string;\n /** Recording status: PENDING, PROCESSING, COMPLETED, ERROR */\n recording_status?: string;\n /** Task ID for async task tracking */\n task_id?: string;\n /** Task status: pending, running, completed, failed */\n status?: string;\n /** Structured output (JSON string) */\n output?: string;\n /** Live session debug URL for real-time viewing (WebRTC streaming) */\n debugUrl?: string;\n}\n\n/**\n * Configuration for live session viewing\n */\nexport interface LiveSessionOptions {\n /** Enable or disable remote control (default: true for headful, varies for headless) */\n interactive?: boolean;\n /** UI theme: 'dark' or 'light' (headless only, default: 'dark') */\n theme?: 'dark' | 'light';\n /** Show or hide navigation controls (headless only, default: true) */\n showControls?: boolean;\n /** Focus view on specific page/tab ID (headless only) */\n pageId?: string;\n /** Display specific tab by index (headless only) */\n pageIndex?: string;\n}\n\n/**\n * Configuration for iframe generation\n */\nexport interface IframeOptions extends LiveSessionOptions {\n /** Iframe width (default: '100%') */\n width?: string | number;\n /** Iframe height (default: '600px') */\n height?: string | number;\n /** Additional inline styles */\n style?: string;\n /** CSS class name */\n className?: string;\n}\n\n/**\n * Task result with convenience methods for async execution\n */\nexport interface BrowserTaskWithPromise extends BrowserTaskResult {\n task_id: string;\n /** Live URL to watch task execution in real-time */\n liveUrl: string;\n /** Wait for task completion */\n complete: (pollConfig?: { interval?: number; timeout?: number }) => Promise<BrowserTaskResult>;\n /** Get live session URL with optional parameters (Steel.dev) - throws if debugUrl not available */\n getLiveUrl: (options?: LiveSessionOptions) => string;\n /** Get iframe HTML for embedding live session - throws if debugUrl not available */\n getLiveIframe: (optionsOrPreset?: string | IframeOptions) => string;\n /** Get complete embed code snippet - throws if debugUrl not available */\n getEmbedCode: () => string;\n}\n\n/**\n * Task result with schema validation\n */\nexport interface BrowserTaskWithPromiseAndSchema<T> extends BrowserTaskResult {\n task_id: string;\n /** Live URL to watch task execution in real-time */\n liveUrl: string;\n /** Parsed and validated output */\n parsed: T | null;\n /** Wait for task completion with schema validation */\n complete: (pollConfig?: { interval?: number; timeout?: number }) => Promise<BrowserTaskResult & { parsed: T | null }>;\n /** Get live session URL with optional parameters - throws if debugUrl not available */\n getLiveUrl: (options?: LiveSessionOptions) => string;\n /** Get iframe HTML for embedding live session - throws if debugUrl not available */\n getLiveIframe: (optionsOrPreset?: string | IframeOptions) => string;\n /** Get complete embed code snippet - throws if debugUrl not available */\n getEmbedCode: () => string;\n}\n\n/**\n * Recording status and metadata\n */\nexport interface RecordingStatus {\n /** Recording ID */\n id: string;\n /** Current status */\n status: 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'ERROR';\n /** Presigned S3 URL for rrweb replay JSON (interactive DOM replay) */\n replay_url?: string;\n /** Presigned S3 URL for network logs (NDJSON format) */\n network_url?: string;\n /** Presigned S3 URL for console logs (NDJSON format) */\n console_url?: string;\n /** Presigned S3 URL for native browser video (WebM or MP4 format, real-time recording) */\n video_url?: string;\n /** Total rrweb events captured */\n total_events?: number;\n /** Total bytes of all files */\n file_size?: number;\n /** Duration in milliseconds */\n duration?: number;\n /** Error message if status=ERROR */\n error?: string;\n /** When recording was created */\n created_at: string;\n}\n\n/**\n * Browser error with screenshot captured at error time\n */\nexport interface BrowserError {\n /** Error type: console error or network error */\n type: 'console' | 'network';\n /** Error message */\n message: string;\n /** URL where error occurred */\n url?: string;\n /** CDP timestamp in seconds since browser start */\n timestamp: number;\n /** Presigned S3 URL to screenshot JPEG (captured 500ms after error) */\n screenshot_url?: string;\n /** When screenshot was captured (Unix timestamp) */\n captured_at?: number;\n \n // Network-specific fields\n /** HTTP status code (for network errors) */\n status?: number;\n}\n\n/**\n * Response from getErrors()\n */\nexport interface ErrorsResponse {\n /** Recording ID */\n recording_id: string;\n /** Total number of errors */\n total_errors: number;\n /** Errors with real-time screenshots */\n errors: BrowserError[];\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../../../tools/browser/types.ts"],"sourcesContent":["/**\n * Type definitions for browser automation tool\n */\n\nimport type { RetryConfig } from '../utils/resilience.js';\n\n/**\n * Available browser automation models\n */\nexport type BrowserModel = \n | 'gemini-flash-latest'\n | 'morph-computer-use-v0';\n\n/**\n * Configuration for the browser worker service\n */\nexport interface BrowserConfig {\n /** Browser worker API URL (default: https://browser.morphllm.com) */\n apiUrl?: string;\n /** Morph API key for authentication (required in production) */\n apiKey?: string;\n /** Request timeout in milliseconds (default: 1000000) */\n timeout?: number;\n /** Retry configuration for API calls */\n retryConfig?: RetryConfig;\n /** Enable debug logging (default: false) */\n debug?: boolean;\n}\n\n/**\n * Input parameters for browser automation task\n */\nexport interface BrowserTaskInput {\n /** Natural language description of what to do */\n task: string;\n /** Starting URL (e.g., https://3000-xyz.e2b.dev) */\n url?: string;\n /** Maximum number of browser actions to take (1-50, default: 10) */\n max_steps?: number;\n /** Model to use for task execution (default: morph-computer-use-v0) */\n model?: BrowserModel;\n /** Browserless region: 'sfo' (US West) or 'lon' (Europe) */\n region?: 'sfo' | 'lon';\n /** Enable stealth mode to avoid bot detection (default: true) */\n stealth?: boolean;\n /** Browser viewport width (default: 1280) */\n viewport_width?: number;\n /** Browser viewport height (default: 720) */\n viewport_height?: number;\n /** User-defined reference/tracking ID (e.g., \"PR-1234\", \"jira-PROJ-567\") */\n external_id?: string;\n /** Optional repository ID to associate with this session */\n repo_id?: string;\n /** Optional commit UUID to associate with this session */\n commit_id?: string;\n /** Record session video to S3 (default: false) */\n record_video?: boolean;\n /** Video width (default: 1280) */\n video_width?: number;\n /** Video height (default: 720) */\n video_height?: number;\n /** Serialized structured output schema (internal use) */\n structured_output?: string;\n}\n\n/**\n * Input with Zod schema for structured output\n */\nexport interface BrowserTaskInputWithSchema<T> extends Omit<BrowserTaskInput, 'structured_output'> {\n /** Zod schema for structured output */\n schema: any;\n}\n\n/**\n * Result from executing a browser task\n */\nexport interface BrowserTaskResult {\n /** Whether the task completed successfully */\n success: boolean;\n /** Task result or findings */\n result?: string;\n /** Error message if task failed */\n error?: string;\n /** Number of browser actions taken */\n steps_taken?: number;\n /** Total execution time in milliseconds */\n execution_time_ms?: number;\n /** UUID of saved replay record (if session replay enabled) */\n replay_id?: string;\n /** Browserless replay URL (if session replay enabled) */\n replay_url?: string;\n /** Recording ID (if record_video=true) */\n recording_id?: string;\n /** Recording status: PENDING, PROCESSING, COMPLETED, ERROR */\n recording_status?: string;\n /** Task ID for async task tracking */\n task_id?: string;\n /** Task status: pending, running, completed, failed */\n status?: string;\n /** Structured output (JSON string) */\n output?: string;\n /** Live session debug URL for real-time viewing (WebRTC streaming) */\n debugUrl?: string;\n}\n\n/**\n * Configuration for live session viewing\n */\nexport interface LiveSessionOptions {\n /** Enable or disable remote control (default: true for headful, varies for headless) */\n interactive?: boolean;\n /** UI theme: 'dark' or 'light' (headless only, default: 'dark') */\n theme?: 'dark' | 'light';\n /** Show or hide navigation controls (headless only, default: true) */\n showControls?: boolean;\n /** Focus view on specific page/tab ID (headless only) */\n pageId?: string;\n /** Display specific tab by index (headless only) */\n pageIndex?: string;\n}\n\n/**\n * Configuration for iframe generation\n */\nexport interface IframeOptions extends LiveSessionOptions {\n /** Iframe width (default: '100%') */\n width?: string | number;\n /** Iframe height (default: '600px') */\n height?: string | number;\n /** Additional inline styles */\n style?: string;\n /** CSS class name */\n className?: string;\n}\n\n/**\n * Task result with convenience methods for async execution\n */\nexport interface BrowserTaskWithPromise extends BrowserTaskResult {\n task_id: string;\n /** Live URL to watch task execution in real-time */\n liveUrl: string;\n /** Wait for task completion */\n complete: (pollConfig?: { interval?: number; timeout?: number }) => Promise<BrowserTaskResult>;\n /** Get live session URL with optional parameters (Steel.dev) - throws if debugUrl not available */\n getLiveUrl: (options?: LiveSessionOptions) => string;\n /** Get iframe HTML for embedding live session - throws if debugUrl not available */\n getLiveIframe: (optionsOrPreset?: string | IframeOptions) => string;\n /** Get complete embed code snippet - throws if debugUrl not available */\n getEmbedCode: () => string;\n}\n\n/**\n * Task result with schema validation\n */\nexport interface BrowserTaskWithPromiseAndSchema<T> extends BrowserTaskResult {\n task_id: string;\n /** Live URL to watch task execution in real-time */\n liveUrl: string;\n /** Parsed and validated output */\n parsed: T | null;\n /** Wait for task completion with schema validation */\n complete: (pollConfig?: { interval?: number; timeout?: number }) => Promise<BrowserTaskResult & { parsed: T | null }>;\n /** Get live session URL with optional parameters - throws if debugUrl not available */\n getLiveUrl: (options?: LiveSessionOptions) => string;\n /** Get iframe HTML for embedding live session - throws if debugUrl not available */\n getLiveIframe: (optionsOrPreset?: string | IframeOptions) => string;\n /** Get complete embed code snippet - throws if debugUrl not available */\n getEmbedCode: () => string;\n}\n\n/**\n * Recording status and metadata\n */\nexport interface RecordingStatus {\n /** Recording ID */\n id: string;\n /** Current status */\n status: 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'ERROR';\n /** Presigned S3 URL for rrweb replay JSON (interactive DOM replay) */\n replay_url?: string;\n /** Presigned S3 URL for network logs (NDJSON format) */\n network_url?: string;\n /** Presigned S3 URL for console logs (NDJSON format) */\n console_url?: string;\n /** Presigned S3 URL for native browser video (WebM or MP4 format, real-time recording) */\n video_url?: string;\n /** Total rrweb events captured */\n total_events?: number;\n /** Total bytes of all files */\n file_size?: number;\n /** Duration in milliseconds */\n duration?: number;\n /** Error message if status=ERROR */\n error?: string;\n /** When recording was created */\n created_at: string;\n}\n\n/**\n * Browser error with screenshot captured at error time\n */\nexport interface BrowserError {\n /** Error type: console error or network error */\n type: 'console' | 'network';\n /** Error message */\n message: string;\n /** URL where error occurred */\n url?: string;\n /** CDP timestamp in seconds since browser start */\n timestamp: number;\n /** Presigned S3 URL to screenshot JPEG (captured 500ms after error) */\n screenshot_url?: string;\n /** When screenshot was captured (Unix timestamp) */\n captured_at?: number;\n \n // Network-specific fields\n /** HTTP status code (for network errors) */\n status?: number;\n}\n\n/**\n * Response from getErrors()\n */\nexport interface ErrorsResponse {\n /** Recording ID */\n recording_id: string;\n /** Total number of errors */\n total_errors: number;\n /** Errors with real-time screenshots */\n errors: BrowserError[];\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
@@ -104,8 +104,8 @@ function sleep(ms) {
104
104
  // tools/browser/core.ts
105
105
  var DEFAULT_CONFIG = {
106
106
  apiUrl: process.env.MORPH_ENVIRONMENT === "DEV" ? "http://localhost:8000" : "https://browser.morphllm.com",
107
- timeout: 12e4,
108
- // 2 minutes for complex tasks
107
+ timeout: 1e6,
108
+ // 10 minutes for complex tasks
109
109
  debug: false
110
110
  };
111
111
  async function executeBrowserTask(input, config = {}) {
@@ -192,21 +192,42 @@ async function executeBrowserTask(input, config = {}) {
192
192
  }
193
193
 
194
194
  // tools/browser/prompts.ts
195
- var BROWSER_TOOL_DESCRIPTION = `Execute natural language browser automation tasks using an AI-powered agent. The agent can navigate websites, interact with elements, fill forms, click buttons, and verify functionality.
195
+ 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.
196
196
 
197
- Use this tool to:
198
- - Test web applications end-to-end
199
- - Verify UI functionality
200
- - Automate user workflows
201
- - Extract information from web pages
197
+ ## When to Use
198
+ Use this AFTER coding is complete to verify your implementation:
199
+ - You've finished writing/modifying code and need to verify it works
200
+ - You need to test user interactions end-to-end (clicks, forms, navigation)
201
+ - You want to catch runtime errors, console warnings, or network failures
202
+ - You need visual confirmation that UI elements render and behave correctly
202
203
 
203
- The agent uses GPT-4o-mini to interpret your task and execute the necessary browser actions. It runs in a remote browser via Browserless.io, so it can access any publicly accessible URL.
204
+ ## What You Get Back
205
+ The tool returns debugging artifacts to help you identify and fix issues:
206
+ - **Video recording**: Watch exactly what happened in the browser session
207
+ - **Console logs**: All console.log, warnings, and errors with timestamps
208
+ - **Network logs**: Failed requests, 404s, API errors with screenshots
209
+ - **Error screenshots**: Visual snapshots captured when errors occur
204
210
 
205
- Important:
206
- - Provide clear, specific task descriptions
207
- - Include the starting URL if navigating to a specific page
208
- - Use remote URLs (e.g., https://3000-xyz.e2b.dev) not localhost
209
- - Complex tasks may require more max_steps`;
211
+ ## How to Write Good Tests
212
+ Be specific about the user journey you're testing:
213
+ \u274C Bad: "Test the login feature"
214
+ \u2705 Good: "Navigate to /login, enter 'test@example.com' and 'password123', click the Login button, verify we reach the /dashboard page"
215
+
216
+ Include verification steps:
217
+ \u2705 "Click the Add Item button, verify the item appears in the list"
218
+ \u2705 "Submit the form, verify a success message is displayed"
219
+
220
+ ## Iterating on Failures
221
+ 1. Run the test and review the video + logs
222
+ 2. Identify the specific issue (console error, failed request, wrong behavior)
223
+ 3. Fix the code based on the evidence
224
+ 4. Re-run the test to verify the fix
225
+ 5. Repeat until test passes
226
+
227
+ ## Requirements
228
+ - **URL**: Must be publicly accessible (use tunnels like ngrok, Cloudflare, or deploy to staging)
229
+ - **Timing**: Use this after implementation, not during coding
230
+ - **Complexity**: Set max_steps higher (20-30) for multi-step user workflows`;
210
231
 
211
232
  // tools/browser/vercel.ts
212
233
  function createBrowserTool(config) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../tools/browser/vercel.ts","../../../tools/utils/resilience.ts","../../../tools/browser/core.ts","../../../tools/browser/prompts.ts"],"sourcesContent":["/**\n * Vercel AI SDK adapter for browser automation tool\n */\n\nimport { tool as createTool } from 'ai';\nimport { z } from 'zod';\nimport { executeBrowserTask } from './core.js';\nimport type { BrowserConfig } from './types.js';\nimport { BROWSER_TOOL_DESCRIPTION } from './prompts.js';\n\n/**\n * Create Vercel AI SDK tool for browser automation\n * \n * @param config - Optional browser worker configuration\n * @returns Vercel AI SDK tool\n * \n * @example\n * ```typescript\n * import { generateText } from 'ai';\n * import { anthropic } from '@ai-sdk/anthropic';\n * import { createBrowserTool } from 'morphsdk/tools/browser/vercel';\n * \n * const browserTool = createBrowserTool({\n * apiUrl: 'https://browser-worker.example.com'\n * });\n * \n * const result = await generateText({\n * model: anthropic('claude-sonnet-4-5-20250929'),\n * tools: { browserTask: browserTool },\n * prompt: 'Test the checkout flow at https://3000-abc.e2b.dev',\n * maxSteps: 5\n * });\n * ```\n */\nexport function createBrowserTool(config?: BrowserConfig) {\n const schema = z.object({\n task: z.string().describe('Natural language description of what to do (e.g., \"Test checkout flow for buying a pineapple\")'),\n url: z.string().optional().describe('Starting URL (e.g., https://3000-xyz.e2b.dev)'),\n max_steps: z.number().min(1).max(50).default(10).describe('Maximum number of browser actions to take'),\n region: z.enum(['sfo', 'lon']).default('sfo').describe('Browserless region: sfo (US West) or lon (Europe)'),\n });\n\n return createTool({\n description: BROWSER_TOOL_DESCRIPTION,\n inputSchema: schema,\n execute: async (params) => {\n const { task, url, max_steps, region } = params;\n const result = await executeBrowserTask(\n { task, url, max_steps, region },\n config\n );\n\n if (result.success) {\n return {\n success: true,\n result: result.result,\n steps_taken: result.steps_taken,\n execution_time_ms: result.execution_time_ms,\n };\n }\n\n return {\n success: false,\n error: result.error,\n };\n },\n });\n}\n\n/**\n * Default browser tool for Vercel AI SDK\n */\nexport const browserTool = createBrowserTool();\n\n","/**\n * Resilience utilities for retry logic and timeout handling\n */\n\nexport interface RetryConfig {\n maxRetries?: number; // Default: 3\n initialDelay?: number; // Default: 1000ms\n maxDelay?: number; // Default: 30000ms\n backoffMultiplier?: number; // Default: 2\n retryableErrors?: string[]; // Default: ['ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND']\n onRetry?: (attempt: number, error: Error) => void;\n}\n\nconst DEFAULT_RETRY_CONFIG: Required<Omit<RetryConfig, 'onRetry'>> = {\n maxRetries: 3,\n initialDelay: 1000,\n maxDelay: 30000,\n backoffMultiplier: 2,\n retryableErrors: ['ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND'],\n};\n\n/**\n * Retry a fetch request with exponential backoff\n * \n * @param url - Request URL\n * @param options - Fetch options\n * @param retryConfig - Retry configuration\n * @returns Response from fetch\n * \n * @example\n * ```typescript\n * const response = await fetchWithRetry(\n * 'https://api.example.com/data',\n * { method: 'POST', body: JSON.stringify(data) },\n * { maxRetries: 5, initialDelay: 500 }\n * );\n * ```\n */\nexport async function fetchWithRetry(\n url: string,\n options: RequestInit,\n retryConfig: RetryConfig = {}\n): Promise<Response> {\n const {\n maxRetries = DEFAULT_RETRY_CONFIG.maxRetries,\n initialDelay = DEFAULT_RETRY_CONFIG.initialDelay,\n maxDelay = DEFAULT_RETRY_CONFIG.maxDelay,\n backoffMultiplier = DEFAULT_RETRY_CONFIG.backoffMultiplier,\n retryableErrors = DEFAULT_RETRY_CONFIG.retryableErrors,\n onRetry,\n } = retryConfig;\n\n let lastError: Error | null = null;\n let delay = initialDelay;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await fetch(url, options);\n \n // Retry on 429 (rate limit) or 503 (service unavailable)\n if (response.status === 429 || response.status === 503) {\n if (attempt < maxRetries) {\n // Check for Retry-After header\n const retryAfter = response.headers.get('Retry-After');\n const waitTime = retryAfter \n ? parseInt(retryAfter) * 1000 \n : Math.min(delay, maxDelay);\n \n const error = new Error(`HTTP ${response.status}: Retrying after ${waitTime}ms`);\n if (onRetry) {\n onRetry(attempt + 1, error);\n }\n \n await sleep(waitTime);\n delay *= backoffMultiplier;\n continue;\n }\n }\n\n return response;\n } catch (error) {\n lastError = error as Error;\n \n // Check if error is retryable\n const isRetryable = retryableErrors.some(errType => \n lastError?.message?.includes(errType)\n );\n\n if (!isRetryable || attempt === maxRetries) {\n throw lastError;\n }\n\n // Exponential backoff\n const waitTime = Math.min(delay, maxDelay);\n if (onRetry) {\n onRetry(attempt + 1, lastError);\n }\n \n await sleep(waitTime);\n delay *= backoffMultiplier;\n }\n }\n\n throw lastError || new Error('Max retries exceeded');\n}\n\n/**\n * Add timeout to any promise\n * \n * @param promise - Promise to wrap with timeout\n * @param timeoutMs - Timeout in milliseconds\n * @param errorMessage - Optional custom error message\n * @returns Promise that rejects if timeout is reached\n * \n * @example\n * ```typescript\n * const result = await withTimeout(\n * fetchData(),\n * 5000,\n * 'Data fetch timed out'\n * );\n * ```\n */\nexport async function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n errorMessage?: string\n): Promise<T> {\n let timeoutId: NodeJS.Timeout | number;\n \n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error(errorMessage || `Operation timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n });\n\n try {\n const result = await Promise.race([promise, timeoutPromise]);\n clearTimeout(timeoutId!);\n return result;\n } catch (error) {\n clearTimeout(timeoutId!);\n throw error;\n }\n}\n\n/**\n * Sleep for specified milliseconds\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Unified error type for all tools\n */\nexport class MorphError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode?: number,\n public retryable: boolean = false\n ) {\n super(message);\n this.name = 'MorphError';\n }\n}\n\n\n","/**\n * Core implementation for browser automation tasks\n */\n\nimport { fetchWithRetry, withTimeout } from '../utils/resilience.js';\nimport type {\n BrowserConfig,\n BrowserTaskInput,\n BrowserTaskInputWithSchema,\n BrowserTaskResult,\n BrowserTaskWithPromise,\n BrowserTaskWithPromiseAndSchema,\n RecordingStatus,\n ErrorsResponse,\n LiveSessionOptions,\n IframeOptions,\n} from './types.js';\nimport { buildLiveUrl, buildLiveIframe, buildEmbedCode, resolvePreset } from './live.js';\n\nconst DEFAULT_CONFIG = {\n apiUrl: process.env.MORPH_ENVIRONMENT === 'DEV' \n ? 'http://localhost:8000'\n : 'https://browser.morphllm.com',\n timeout: 120000, // 2 minutes for complex tasks\n debug: false,\n};\n\n/**\n * BrowserClient class for easier usage with instance configuration\n */\nexport class BrowserClient {\n private config: BrowserConfig;\n\n constructor(config: BrowserConfig = {}) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n }\n\n /**\n * Execute a browser automation task\n */\n async execute(input: BrowserTaskInput): Promise<BrowserTaskResult> {\n return executeBrowserTask(input, this.config);\n }\n\n async createTask(input: BrowserTaskInput): Promise<BrowserTaskWithPromise>;\n async createTask<T>(input: BrowserTaskInputWithSchema<T>): Promise<BrowserTaskWithPromiseAndSchema<T>>;\n async createTask<T>(\n input: BrowserTaskInput | BrowserTaskInputWithSchema<T>\n ): Promise<BrowserTaskWithPromise | BrowserTaskWithPromiseAndSchema<T>> {\n if ('schema' in input) {\n const taskInput: BrowserTaskInput = {\n ...input,\n structured_output: stringifyStructuredOutput(input.schema),\n };\n const result = await executeBrowserTask(taskInput, this.config);\n return wrapTaskResponseWithSchema(result, this.config, input.schema);\n } else {\n const result = await executeBrowserTask(input, this.config);\n return wrapTaskResponse(result, this.config);\n }\n }\n\n /**\n * Execute task with recording and wait for video to be ready\n */\n async executeWithRecording(\n input: BrowserTaskInput & { record_video: true }\n ): Promise<BrowserTaskResult & { recording?: RecordingStatus }> {\n return executeWithRecording(input, this.config);\n }\n\n /**\n * Get recording status and URLs\n */\n async getRecording(recordingId: string): Promise<RecordingStatus> {\n return getRecording(recordingId, this.config);\n }\n\n /**\n * Wait for recording to complete with automatic polling\n */\n async waitForRecording(\n recordingId: string,\n options?: { timeout?: number; pollInterval?: number }\n ): Promise<RecordingStatus> {\n return waitForRecording(recordingId, this.config, options);\n }\n\n /**\n * Get errors from recording with screenshots\n */\n async getErrors(recordingId: string): Promise<ErrorsResponse> {\n return getErrors(recordingId, this.config);\n }\n\n /**\n * Check if browser worker service is healthy\n */\n async checkHealth(): Promise<{\n ok: boolean;\n google_configured: boolean;\n database_configured: boolean;\n s3_configured: boolean;\n error?: string;\n }> {\n return checkHealth(this.config);\n }\n}\n\n/**\n * Execute a natural language browser automation task\n * \n * @param input - Task parameters\n * @param config - Optional configuration (apiKey, apiUrl to override default)\n * @returns Task result with success status and findings\n * \n * @example\n * ```typescript\n * const result = await executeBrowserTask(\n * {\n * task: \"Test checkout flow for buying a pineapple\",\n * url: \"https://3000-abc.e2b.dev\",\n * max_steps: 20,\n * repo_id: \"my-project\",\n * commit_id: \"uuid-here\"\n * },\n * {\n * apiKey: process.env.MORPH_API_KEY,\n * // apiUrl: 'http://localhost:8001' // Override for local testing\n * }\n * );\n * \n * if (result.success) {\n * console.log('Task completed:', result.result);\n * console.log('Replay:', result.replay_url);\n * }\n * ```\n */\nexport async function executeBrowserTask(\n input: BrowserTaskInput,\n config: BrowserConfig = {}\n): Promise<BrowserTaskResult> {\n const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;\n const timeout = config.timeout || DEFAULT_CONFIG.timeout;\n const debug = config.debug || false;\n\n if (!input.task || input.task.trim().length === 0) {\n return { \n success: false, \n error: 'Task description is required. Example: \"Go to example.com and click the login button\"' \n };\n }\n\n if (input.max_steps !== undefined && (input.max_steps < 1 || input.max_steps > 50)) {\n return { \n success: false, \n error: 'max_steps must be between 1 and 50. Use more steps for complex multi-page flows.' \n };\n }\n\n if (debug) {\n console.log(`[Browser] Task: \"${input.task.slice(0, 60)}...\" url=${input.url || 'none'} maxSteps=${input.max_steps ?? 10}`);\n console.log(`[Browser] Recording: ${input.record_video ? 'yes' : 'no'} | Calling ${apiUrl}/browser-task`);\n }\n\n const startTime = Date.now();\n\n try {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (config.apiKey) headers['Authorization'] = `Bearer ${config.apiKey}`;\n\n const fetchPromise = fetchWithRetry(\n `${apiUrl}/browser-task`,\n {\n method: 'POST',\n headers,\n body: JSON.stringify({\n task: input.task,\n url: input.url,\n max_steps: input.max_steps ?? 10,\n model: input.model ?? 'morph-computer-use-v0',\n viewport_width: input.viewport_width ?? 1280,\n viewport_height: input.viewport_height ?? 720,\n external_id: input.external_id,\n repo_id: input.repo_id,\n commit_id: input.commit_id,\n record_video: input.record_video ?? false,\n video_width: input.video_width ?? input.viewport_width ?? 1280,\n video_height: input.video_height ?? input.viewport_height ?? 720,\n structured_output: input.structured_output,\n }),\n },\n config.retryConfig\n );\n\n const response = await withTimeout(\n fetchPromise,\n timeout,\n `Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing max_steps.`\n );\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => response.statusText);\n if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);\n throw new Error(`HTTP ${response.status}: ${errorText}`);\n }\n\n const result: BrowserTaskResult = await response.json();\n const elapsed = Date.now() - startTime;\n \n if (debug) {\n console.log(`[Browser] ✅ ${result.success ? 'Success' : 'Failed'} in ${elapsed}ms | steps=${result.steps_taken ?? 0} recordingId=${result.recording_id ?? 'none'}`);\n }\n\n return result;\n\n } catch (error) {\n if (error instanceof Error) {\n // Handle network errors\n if (error.message.includes('ECONNREFUSED') || error.message.includes('fetch failed')) {\n return {\n success: false,\n error: `Cannot connect to browser worker at ${apiUrl}. Ensure the service is running and accessible. For local dev, set MORPH_ENVIRONMENT=DEV.`,\n };\n }\n\n return {\n success: false,\n error: error.message,\n };\n }\n\n return {\n success: false,\n error: String(error),\n };\n }\n}\n\n/**\n * Get recording status and video URL\n * \n * @param recordingId - Recording UUID from BrowserTaskResult\n * @param config - Configuration with apiKey\n * @returns Recording status with video URL when ready\n * \n * @example\n * ```typescript\n * const status = await getRecording('uuid-here', { apiKey: 'key' });\n * if (status.status === 'COMPLETED' && status.video_url) {\n * console.log('Video ready:', status.video_url);\n * }\n * ```\n */\nexport async function getRecording(\n recordingId: string,\n config: BrowserConfig = {}\n): Promise<RecordingStatus> {\n const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;\n const debug = config.debug || false;\n\n if (!config.apiKey) {\n throw new Error('API key required for getRecording');\n }\n\n if (debug) console.log(`[Browser] getRecording: ${recordingId}`);\n\n const response = await fetch(`${apiUrl}/recordings/${recordingId}`, {\n method: 'GET',\n headers: { 'Authorization': `Bearer ${config.apiKey}` },\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => response.statusText);\n if (debug) console.error(`[Browser] getRecording error: ${response.status} - ${errorText}`);\n throw new Error(`HTTP ${response.status}: ${errorText}`);\n }\n\n const recording = await response.json();\n if (debug) console.log(`[Browser] Recording status: ${recording.status}`);\n\n return recording;\n}\n\n/**\n * Wait for recording to complete with automatic polling\n * \n * @param recordingId - Recording UUID\n * @param config - Configuration with apiKey\n * @param options - Polling options\n * @returns Recording status when completed or errored\n * \n * @example\n * ```typescript\n * const result = await executeBrowserTask({ task: '...', record_video: true }, config);\n * if (result.recording_id) {\n * const recording = await waitForRecording(result.recording_id, config, {\n * timeout: 60000, // 1 minute\n * pollInterval: 2000 // Check every 2 seconds\n * });\n * console.log('Video URL:', recording.video_url);\n * }\n * ```\n */\nexport async function waitForRecording(\n recordingId: string,\n config: BrowserConfig = {},\n options: { timeout?: number; pollInterval?: number } = {}\n): Promise<RecordingStatus> {\n const timeout = options.timeout ?? 60000; // Default 1 minute\n const pollInterval = options.pollInterval ?? 2000; // Default 2 seconds\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeout) {\n const status = await getRecording(recordingId, config);\n \n if (status.status === 'COMPLETED' || status.status === 'ERROR') {\n return status;\n }\n\n // Wait before next poll\n await new Promise(resolve => setTimeout(resolve, pollInterval));\n }\n\n throw new Error(`Recording timeout after ${timeout}ms - status still pending`);\n}\n\n/**\n * Execute task with recording and wait for video to be ready\n * \n * @param input - Task parameters with record_video=true\n * @param config - Configuration with apiKey\n * @returns Task result with ready video URL\n * \n * @example\n * ```typescript\n * const result = await executeWithRecording(\n * {\n * task: \"Test checkout flow\",\n * url: \"https://example.com\",\n * record_video: true,\n * repo_id: \"my-project\"\n * },\n * { apiKey: process.env.MORPH_API_KEY }\n * );\n * \n * console.log('Task result:', result.result);\n * console.log('Video URL:', result.recording?.video_url);\n * ```\n */\nexport async function executeWithRecording(\n input: BrowserTaskInput & { record_video: true },\n config: BrowserConfig = {}\n): Promise<BrowserTaskResult & { recording?: RecordingStatus }> {\n // Execute task with recording\n const taskResult = await executeBrowserTask(input, config);\n\n // If recording was created, wait for it to complete\n if (taskResult.recording_id) {\n try {\n const recording = await waitForRecording(\n taskResult.recording_id,\n config,\n { timeout: 60000, pollInterval: 2000 }\n );\n return {\n ...taskResult,\n recording,\n };\n } catch (error) {\n // Return task result even if recording fails\n return {\n ...taskResult,\n recording: {\n id: taskResult.recording_id,\n status: 'ERROR',\n error: error instanceof Error ? error.message : String(error),\n created_at: new Date().toISOString(),\n },\n };\n }\n }\n\n return taskResult;\n}\n\n/**\n * Get errors from recording with screenshots\n * \n * Screenshots are captured in real-time (500ms after error occurs) during the browser session.\n * \n * @param recordingId - Recording UUID from BrowserTaskResult\n * @param config - Configuration with apiKey\n * @returns Errors with real-time screenshots\n * \n * @example\n * ```typescript\n * const { errors, total_errors } = await getErrors('uuid-here', { apiKey: 'key' });\n * \n * console.log(`Found ${total_errors} errors`);\n * \n * errors.forEach(err => {\n * console.log(`[${err.type}] ${err.message}`);\n * if (err.url) console.log(` URL: ${err.url}`);\n * if (err.screenshot_url) console.log(` Screenshot: ${err.screenshot_url}`);\n * \n * // Download screenshot\n * if (err.screenshot_url) {\n * const response = await fetch(err.screenshot_url);\n * const screenshot = await response.arrayBuffer();\n * // Save or process screenshot\n * }\n * });\n * ```\n */\nexport async function getErrors(\n recordingId: string,\n config: BrowserConfig = {}\n): Promise<ErrorsResponse> {\n const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;\n const debug = config.debug || false;\n\n if (!config.apiKey) {\n throw new Error('API key required for getErrors');\n }\n\n if (debug) console.log(`[Browser] getErrors: ${recordingId}`);\n\n const response = await fetch(`${apiUrl}/recordings/${recordingId}/errors`, {\n method: 'GET',\n headers: { 'Authorization': `Bearer ${config.apiKey}` },\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => response.statusText);\n if (debug) console.error(`[Browser] getErrors error: ${response.status} - ${errorText}`);\n throw new Error(`HTTP ${response.status}: ${errorText}`);\n }\n\n const errors = await response.json();\n if (debug) console.log(`[Browser] Found ${errors.total_errors} errors`);\n\n return errors;\n}\n\n/**\n * Helper to serialize Zod schema for API\n */\nfunction stringifyStructuredOutput(schema: any): string {\n try {\n return JSON.stringify({\n type: 'object',\n description: 'Zod schema definition (Zod v3)',\n zodDef: schema._def,\n });\n } catch (error) {\n console.warn('[Browser] Failed to serialize Zod schema:', error);\n return JSON.stringify({\n type: 'object',\n description: 'Schema serialization failed',\n });\n }\n}\n\n/**\n * Parse and validate structured task output\n */\nfunction parseStructuredTaskOutput<T>(\n result: BrowserTaskResult,\n schema: any\n): BrowserTaskResult & { parsed: T | null } {\n if (!result.output) {\n return { ...result, parsed: null };\n }\n\n try {\n const parsed = JSON.parse(result.output);\n const validated = schema.parse(parsed) as T;\n return { ...result, parsed: validated };\n } catch (error) {\n if (error instanceof SyntaxError) {\n return { ...result, parsed: null };\n }\n throw error;\n }\n}\n\n/**\n * Get current task status\n */\nasync function getTaskStatus(\n taskId: string,\n config: BrowserConfig\n): Promise<BrowserTaskResult> {\n const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;\n const debug = config.debug || false;\n\n if (debug) console.log(`[Browser] getTaskStatus: ${taskId}`);\n\n const headers: Record<string, string> = {};\n if (config.apiKey) headers['Authorization'] = `Bearer ${config.apiKey}`;\n\n const response = await fetch(`${apiUrl}/tasks/${taskId}`, {\n method: 'GET',\n headers,\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => response.statusText);\n if (debug) console.error(`[Browser] getTaskStatus error: ${response.status} - ${errorText}`);\n throw new Error(`HTTP ${response.status}: ${errorText}`);\n }\n\n const result: BrowserTaskResult = await response.json();\n if (debug) console.log(`[Browser] Task status: ${result.status}`);\n\n return result;\n}\n\n/**\n * Generate live URL for watching task execution in real-time\n */\nfunction generateLiveUrl(taskId: string, config: BrowserConfig): string {\n const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;\n const baseUrl = apiUrl.replace('/api', '');\n return `${baseUrl}/tasks/${taskId}/live`;\n}\n\n/**\n * Poll task until completion\n */\nasync function pollTaskUntilComplete(\n taskId: string,\n config: BrowserConfig,\n pollConfig: { interval?: number; timeout?: number } = {}\n): Promise<BrowserTaskResult> {\n const interval = pollConfig.interval ?? 2000; // 2 seconds\n const timeout = pollConfig.timeout ?? 300000; // 5 minutes\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeout) {\n const status = await getTaskStatus(taskId, config);\n \n if (status.status === 'completed' || status.status === 'failed') {\n return status;\n }\n\n await new Promise(resolve => setTimeout(resolve, interval));\n }\n\n throw new Error(`Task polling timeout after ${timeout}ms`);\n}\n\n/**\n * Wrap task response with convenience methods\n */\nfunction wrapTaskResponse(\n result: BrowserTaskResult,\n config: BrowserConfig\n): BrowserTaskWithPromise {\n if (!result.task_id) {\n throw new Error('task_id is required to wrap response');\n }\n\n const wrapped: BrowserTaskWithPromise = {\n ...result,\n task_id: result.task_id,\n liveUrl: generateLiveUrl(result.task_id, config),\n complete: async (pollConfig?: { interval?: number; timeout?: number }) => {\n return pollTaskUntilComplete(result.task_id!, config, pollConfig);\n },\n // Add Steel live session helpers - either functional or error-throwing\n getLiveUrl: result.debugUrl\n ? (options?: LiveSessionOptions) => buildLiveUrl(result.debugUrl!, options)\n : () => {\n throw new Error(\n 'Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help.'\n );\n },\n getLiveIframe: result.debugUrl\n ? (optionsOrPreset?: string | IframeOptions) => {\n const options = resolvePreset(optionsOrPreset);\n return buildLiveIframe(result.debugUrl!, options);\n }\n : () => {\n throw new Error(\n 'Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help.'\n );\n },\n getEmbedCode: result.debugUrl\n ? () => buildEmbedCode(result.debugUrl!)\n : () => {\n throw new Error(\n 'Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help.'\n );\n },\n };\n\n return wrapped;\n}\n\n/**\n * Wrap task response with schema validation\n */\nfunction wrapTaskResponseWithSchema<T>(\n result: BrowserTaskResult,\n config: BrowserConfig,\n schema: any\n): BrowserTaskWithPromiseAndSchema<T> {\n if (!result.task_id) {\n throw new Error('task_id is required to wrap response');\n }\n\n const parsed = result.output\n ? parseStructuredTaskOutput<T>(result, schema)\n : { ...result, parsed: null };\n\n const wrapped: BrowserTaskWithPromiseAndSchema<T> = {\n ...parsed,\n task_id: result.task_id,\n liveUrl: generateLiveUrl(result.task_id, config),\n complete: async (pollConfig?: { interval?: number; timeout?: number }) => {\n const finalResult = await pollTaskUntilComplete(result.task_id!, config, pollConfig);\n return parseStructuredTaskOutput<T>(finalResult, schema);\n },\n // Add Steel live session helpers - either functional or error-throwing\n getLiveUrl: result.debugUrl\n ? (options?: LiveSessionOptions) => buildLiveUrl(result.debugUrl!, options)\n : () => {\n throw new Error(\n 'Live sessions not available. Your backend must return a debugUrl in the response. ' +\n 'Contact support@morphllm.com if you need help enabling live sessions. '\n );\n },\n getLiveIframe: result.debugUrl\n ? (optionsOrPreset?: string | IframeOptions) => {\n const options = resolvePreset(optionsOrPreset);\n return buildLiveIframe(result.debugUrl!, options);\n }\n : () => {\n throw new Error(\n 'Live sessions not available. Your backend must return a debugUrl in the response. ' +\n 'Contact support@morphllm.com if you need help enabling live sessions.'\n );\n },\n getEmbedCode: result.debugUrl\n ? () => buildEmbedCode(result.debugUrl!)\n : () => {\n throw new Error(\n 'Live sessions not available. Your backend must return a debugUrl in the response. ' +\n 'Contact support@morphllm.com if you need help enabling live sessions.'\n );\n },\n };\n\n return wrapped;\n}\n\n/**\n * Check if browser worker service is healthy\n * \n * @param config - Optional configuration\n * @returns Health status\n */\nexport async function checkHealth(config: BrowserConfig = {}): Promise<{\n ok: boolean;\n google_configured: boolean;\n database_configured: boolean;\n s3_configured: boolean;\n error?: string;\n}> {\n const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;\n\n try {\n const response = await fetch(`${apiUrl}/health`, {\n method: 'GET',\n headers: config.apiKey\n ? { 'Authorization': `Bearer ${config.apiKey}` }\n : {},\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n return {\n ok: true,\n google_configured: data.google_configured ?? false,\n database_configured: data.database_configured ?? false,\n s3_configured: data.s3_configured ?? false,\n };\n } catch (error) {\n return {\n ok: false,\n google_configured: false,\n database_configured: false,\n s3_configured: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n","/**\n * Tool descriptions and prompts for AI models\n */\n\nexport const BROWSER_TOOL_DESCRIPTION = `Execute natural language browser automation tasks using an AI-powered agent. The agent can navigate websites, interact with elements, fill forms, click buttons, and verify functionality.\n\nUse this tool to:\n- Test web applications end-to-end\n- Verify UI functionality\n- Automate user workflows\n- Extract information from web pages\n\nThe agent uses GPT-4o-mini to interpret your task and execute the necessary browser actions. It runs in a remote browser via Browserless.io, so it can access any publicly accessible URL.\n\nImportant:\n- Provide clear, specific task descriptions\n- Include the starting URL if navigating to a specific page\n- Use remote URLs (e.g., https://3000-xyz.e2b.dev) not localhost\n- Complex tasks may require more max_steps`;\n\nexport const BROWSER_SYSTEM_PROMPT = `You have access to browser automation capabilities. When testing or interacting with web applications:\n\n1. Be specific about what you're testing or verifying\n2. Break complex tasks into clear steps\n3. Always provide the full URL including protocol (https://)\n4. For localhost apps, use the remote tunnel URL (e.g., e2b.dev URLs)\n5. Specify the number of steps needed - simple tasks use 5-10, complex flows use 15-30\n\nExample good tasks:\n- \"Go to https://3000-abc.e2b.dev and verify the landing page loads with a hero section\"\n- \"Test guest checkout: add a pineapple to cart, proceed to checkout, fill shipping info, and verify order summary\"\n- \"Navigate to the dashboard and click on the settings tab, then verify the API keys section is visible\"\n\nExample bad tasks:\n- \"test the app\" (too vague)\n- \"go to localhost:3000\" (use remote URL instead)\n- \"do everything\" (not specific enough)`;\n\n"],"mappings":"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","names":["createTool"]}
1
+ {"version":3,"sources":["../../../tools/browser/vercel.ts","../../../tools/utils/resilience.ts","../../../tools/browser/core.ts","../../../tools/browser/prompts.ts"],"sourcesContent":["/**\n * Vercel AI SDK adapter for browser automation tool\n */\n\nimport { tool as createTool } from 'ai';\nimport { z } from 'zod';\nimport { executeBrowserTask } from './core.js';\nimport type { BrowserConfig } from './types.js';\nimport { BROWSER_TOOL_DESCRIPTION } from './prompts.js';\n\n/**\n * Create Vercel AI SDK tool for browser automation\n * \n * @param config - Optional browser worker configuration\n * @returns Vercel AI SDK tool\n * \n * @example\n * ```typescript\n * import { generateText } from 'ai';\n * import { anthropic } from '@ai-sdk/anthropic';\n * import { createBrowserTool } from 'morphsdk/tools/browser/vercel';\n * \n * const browserTool = createBrowserTool({\n * apiUrl: 'https://browser-worker.example.com'\n * });\n * \n * const result = await generateText({\n * model: anthropic('claude-sonnet-4-5-20250929'),\n * tools: { browserTask: browserTool },\n * prompt: 'Test the checkout flow at https://3000-abc.e2b.dev',\n * maxSteps: 5\n * });\n * ```\n */\nexport function createBrowserTool(config?: BrowserConfig) {\n const schema = z.object({\n task: z.string().describe('Natural language description of what to do (e.g., \"Test checkout flow for buying a pineapple\")'),\n url: z.string().optional().describe('Starting URL (e.g., https://3000-xyz.e2b.dev)'),\n max_steps: z.number().min(1).max(50).default(10).describe('Maximum number of browser actions to take'),\n region: z.enum(['sfo', 'lon']).default('sfo').describe('Browserless region: sfo (US West) or lon (Europe)'),\n });\n\n return createTool({\n description: BROWSER_TOOL_DESCRIPTION,\n inputSchema: schema,\n execute: async (params) => {\n const { task, url, max_steps, region } = params;\n const result = await executeBrowserTask(\n { task, url, max_steps, region },\n config\n );\n\n if (result.success) {\n return {\n success: true,\n result: result.result,\n steps_taken: result.steps_taken,\n execution_time_ms: result.execution_time_ms,\n };\n }\n\n return {\n success: false,\n error: result.error,\n };\n },\n });\n}\n\n/**\n * Default browser tool for Vercel AI SDK\n */\nexport const browserTool = createBrowserTool();\n\n","/**\n * Resilience utilities for retry logic and timeout handling\n */\n\nexport interface RetryConfig {\n maxRetries?: number; // Default: 3\n initialDelay?: number; // Default: 1000ms\n maxDelay?: number; // Default: 30000ms\n backoffMultiplier?: number; // Default: 2\n retryableErrors?: string[]; // Default: ['ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND']\n onRetry?: (attempt: number, error: Error) => void;\n}\n\nconst DEFAULT_RETRY_CONFIG: Required<Omit<RetryConfig, 'onRetry'>> = {\n maxRetries: 3,\n initialDelay: 1000,\n maxDelay: 30000,\n backoffMultiplier: 2,\n retryableErrors: ['ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND'],\n};\n\n/**\n * Retry a fetch request with exponential backoff\n * \n * @param url - Request URL\n * @param options - Fetch options\n * @param retryConfig - Retry configuration\n * @returns Response from fetch\n * \n * @example\n * ```typescript\n * const response = await fetchWithRetry(\n * 'https://api.example.com/data',\n * { method: 'POST', body: JSON.stringify(data) },\n * { maxRetries: 5, initialDelay: 500 }\n * );\n * ```\n */\nexport async function fetchWithRetry(\n url: string,\n options: RequestInit,\n retryConfig: RetryConfig = {}\n): Promise<Response> {\n const {\n maxRetries = DEFAULT_RETRY_CONFIG.maxRetries,\n initialDelay = DEFAULT_RETRY_CONFIG.initialDelay,\n maxDelay = DEFAULT_RETRY_CONFIG.maxDelay,\n backoffMultiplier = DEFAULT_RETRY_CONFIG.backoffMultiplier,\n retryableErrors = DEFAULT_RETRY_CONFIG.retryableErrors,\n onRetry,\n } = retryConfig;\n\n let lastError: Error | null = null;\n let delay = initialDelay;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const response = await fetch(url, options);\n \n // Retry on 429 (rate limit) or 503 (service unavailable)\n if (response.status === 429 || response.status === 503) {\n if (attempt < maxRetries) {\n // Check for Retry-After header\n const retryAfter = response.headers.get('Retry-After');\n const waitTime = retryAfter \n ? parseInt(retryAfter) * 1000 \n : Math.min(delay, maxDelay);\n \n const error = new Error(`HTTP ${response.status}: Retrying after ${waitTime}ms`);\n if (onRetry) {\n onRetry(attempt + 1, error);\n }\n \n await sleep(waitTime);\n delay *= backoffMultiplier;\n continue;\n }\n }\n\n return response;\n } catch (error) {\n lastError = error as Error;\n \n // Check if error is retryable\n const isRetryable = retryableErrors.some(errType => \n lastError?.message?.includes(errType)\n );\n\n if (!isRetryable || attempt === maxRetries) {\n throw lastError;\n }\n\n // Exponential backoff\n const waitTime = Math.min(delay, maxDelay);\n if (onRetry) {\n onRetry(attempt + 1, lastError);\n }\n \n await sleep(waitTime);\n delay *= backoffMultiplier;\n }\n }\n\n throw lastError || new Error('Max retries exceeded');\n}\n\n/**\n * Add timeout to any promise\n * \n * @param promise - Promise to wrap with timeout\n * @param timeoutMs - Timeout in milliseconds\n * @param errorMessage - Optional custom error message\n * @returns Promise that rejects if timeout is reached\n * \n * @example\n * ```typescript\n * const result = await withTimeout(\n * fetchData(),\n * 5000,\n * 'Data fetch timed out'\n * );\n * ```\n */\nexport async function withTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n errorMessage?: string\n): Promise<T> {\n let timeoutId: NodeJS.Timeout | number;\n \n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error(errorMessage || `Operation timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n });\n\n try {\n const result = await Promise.race([promise, timeoutPromise]);\n clearTimeout(timeoutId!);\n return result;\n } catch (error) {\n clearTimeout(timeoutId!);\n throw error;\n }\n}\n\n/**\n * Sleep for specified milliseconds\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Unified error type for all tools\n */\nexport class MorphError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode?: number,\n public retryable: boolean = false\n ) {\n super(message);\n this.name = 'MorphError';\n }\n}\n\n\n","/**\n * Core implementation for browser automation tasks\n */\n\nimport { fetchWithRetry, withTimeout } from '../utils/resilience.js';\nimport type {\n BrowserConfig,\n BrowserTaskInput,\n BrowserTaskInputWithSchema,\n BrowserTaskResult,\n BrowserTaskWithPromise,\n BrowserTaskWithPromiseAndSchema,\n RecordingStatus,\n ErrorsResponse,\n LiveSessionOptions,\n IframeOptions,\n} from './types.js';\nimport { buildLiveUrl, buildLiveIframe, buildEmbedCode, resolvePreset } from './live.js';\n\nconst DEFAULT_CONFIG = {\n apiUrl: process.env.MORPH_ENVIRONMENT === 'DEV' \n ? 'http://localhost:8000'\n : 'https://browser.morphllm.com',\n timeout: 1000000, // 10 minutes for complex tasks\n debug: false,\n};\n\n/**\n * BrowserClient class for easier usage with instance configuration\n */\nexport class BrowserClient {\n private config: BrowserConfig;\n\n constructor(config: BrowserConfig = {}) {\n this.config = {\n ...DEFAULT_CONFIG,\n ...config,\n };\n }\n\n /**\n * Execute a browser automation task\n */\n async execute(input: BrowserTaskInput): Promise<BrowserTaskResult> {\n return executeBrowserTask(input, this.config);\n }\n\n async createTask(input: BrowserTaskInput): Promise<BrowserTaskWithPromise>;\n async createTask<T>(input: BrowserTaskInputWithSchema<T>): Promise<BrowserTaskWithPromiseAndSchema<T>>;\n async createTask<T>(\n input: BrowserTaskInput | BrowserTaskInputWithSchema<T>\n ): Promise<BrowserTaskWithPromise | BrowserTaskWithPromiseAndSchema<T>> {\n if ('schema' in input) {\n const taskInput: BrowserTaskInput = {\n ...input,\n structured_output: stringifyStructuredOutput(input.schema),\n };\n const result = await executeBrowserTask(taskInput, this.config);\n return wrapTaskResponseWithSchema(result, this.config, input.schema);\n } else {\n const result = await executeBrowserTask(input, this.config);\n return wrapTaskResponse(result, this.config);\n }\n }\n\n /**\n * Execute task with recording and wait for video to be ready\n */\n async executeWithRecording(\n input: BrowserTaskInput & { record_video: true }\n ): Promise<BrowserTaskResult & { recording?: RecordingStatus }> {\n return executeWithRecording(input, this.config);\n }\n\n /**\n * Get recording status and URLs\n */\n async getRecording(recordingId: string): Promise<RecordingStatus> {\n return getRecording(recordingId, this.config);\n }\n\n /**\n * Wait for recording to complete with automatic polling\n */\n async waitForRecording(\n recordingId: string,\n options?: { timeout?: number; pollInterval?: number }\n ): Promise<RecordingStatus> {\n return waitForRecording(recordingId, this.config, options);\n }\n\n /**\n * Get errors from recording with screenshots\n */\n async getErrors(recordingId: string): Promise<ErrorsResponse> {\n return getErrors(recordingId, this.config);\n }\n\n /**\n * Check if browser worker service is healthy\n */\n async checkHealth(): Promise<{\n ok: boolean;\n google_configured: boolean;\n database_configured: boolean;\n s3_configured: boolean;\n error?: string;\n }> {\n return checkHealth(this.config);\n }\n}\n\n/**\n * Execute a natural language browser automation task\n * \n * @param input - Task parameters\n * @param config - Optional configuration (apiKey, apiUrl to override default)\n * @returns Task result with success status and findings\n * \n * @example\n * ```typescript\n * const result = await executeBrowserTask(\n * {\n * task: \"Test checkout flow for buying a pineapple\",\n * url: \"https://3000-abc.e2b.dev\",\n * max_steps: 20,\n * repo_id: \"my-project\",\n * commit_id: \"uuid-here\"\n * },\n * {\n * apiKey: process.env.MORPH_API_KEY,\n * // apiUrl: 'http://localhost:8001' // Override for local testing\n * }\n * );\n * \n * if (result.success) {\n * console.log('Task completed:', result.result);\n * console.log('Replay:', result.replay_url);\n * }\n * ```\n */\nexport async function executeBrowserTask(\n input: BrowserTaskInput,\n config: BrowserConfig = {}\n): Promise<BrowserTaskResult> {\n const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;\n const timeout = config.timeout || DEFAULT_CONFIG.timeout;\n const debug = config.debug || false;\n\n if (!input.task || input.task.trim().length === 0) {\n return { \n success: false, \n error: 'Task description is required. Example: \"Go to example.com and click the login button\"' \n };\n }\n\n if (input.max_steps !== undefined && (input.max_steps < 1 || input.max_steps > 50)) {\n return { \n success: false, \n error: 'max_steps must be between 1 and 50. Use more steps for complex multi-page flows.' \n };\n }\n\n if (debug) {\n console.log(`[Browser] Task: \"${input.task.slice(0, 60)}...\" url=${input.url || 'none'} maxSteps=${input.max_steps ?? 10}`);\n console.log(`[Browser] Recording: ${input.record_video ? 'yes' : 'no'} | Calling ${apiUrl}/browser-task`);\n }\n\n const startTime = Date.now();\n\n try {\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n if (config.apiKey) headers['Authorization'] = `Bearer ${config.apiKey}`;\n\n const fetchPromise = fetchWithRetry(\n `${apiUrl}/browser-task`,\n {\n method: 'POST',\n headers,\n body: JSON.stringify({\n task: input.task,\n url: input.url,\n max_steps: input.max_steps ?? 10,\n model: input.model ?? 'morph-computer-use-v0',\n viewport_width: input.viewport_width ?? 1280,\n viewport_height: input.viewport_height ?? 720,\n external_id: input.external_id,\n repo_id: input.repo_id,\n commit_id: input.commit_id,\n record_video: input.record_video ?? false,\n video_width: input.video_width ?? input.viewport_width ?? 1280,\n video_height: input.video_height ?? input.viewport_height ?? 720,\n structured_output: input.structured_output,\n }),\n },\n config.retryConfig\n );\n\n const response = await withTimeout(\n fetchPromise,\n timeout,\n `Browser task timed out after ${timeout}ms. Consider increasing timeout or reducing max_steps.`\n );\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => response.statusText);\n if (debug) console.error(`[Browser] Error: ${response.status} - ${errorText}`);\n throw new Error(`HTTP ${response.status}: ${errorText}`);\n }\n\n const result: BrowserTaskResult = await response.json();\n const elapsed = Date.now() - startTime;\n \n if (debug) {\n console.log(`[Browser] ✅ ${result.success ? 'Success' : 'Failed'} in ${elapsed}ms | steps=${result.steps_taken ?? 0} recordingId=${result.recording_id ?? 'none'}`);\n }\n\n return result;\n\n } catch (error) {\n if (error instanceof Error) {\n // Handle network errors\n if (error.message.includes('ECONNREFUSED') || error.message.includes('fetch failed')) {\n return {\n success: false,\n error: `Cannot connect to browser worker at ${apiUrl}. Ensure the service is running and accessible. For local dev, set MORPH_ENVIRONMENT=DEV.`,\n };\n }\n\n return {\n success: false,\n error: error.message,\n };\n }\n\n return {\n success: false,\n error: String(error),\n };\n }\n}\n\n/**\n * Get recording status and video URL\n * \n * @param recordingId - Recording UUID from BrowserTaskResult\n * @param config - Configuration with apiKey\n * @returns Recording status with video URL when ready\n * \n * @example\n * ```typescript\n * const status = await getRecording('uuid-here', { apiKey: 'key' });\n * if (status.status === 'COMPLETED' && status.video_url) {\n * console.log('Video ready:', status.video_url);\n * }\n * ```\n */\nexport async function getRecording(\n recordingId: string,\n config: BrowserConfig = {}\n): Promise<RecordingStatus> {\n const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;\n const debug = config.debug || false;\n\n if (!config.apiKey) {\n throw new Error('API key required for getRecording');\n }\n\n if (debug) console.log(`[Browser] getRecording: ${recordingId}`);\n\n const response = await fetch(`${apiUrl}/recordings/${recordingId}`, {\n method: 'GET',\n headers: { 'Authorization': `Bearer ${config.apiKey}` },\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => response.statusText);\n if (debug) console.error(`[Browser] getRecording error: ${response.status} - ${errorText}`);\n throw new Error(`HTTP ${response.status}: ${errorText}`);\n }\n\n const recording = await response.json();\n if (debug) console.log(`[Browser] Recording status: ${recording.status}`);\n\n return recording;\n}\n\n/**\n * Wait for recording to complete with automatic polling\n * \n * @param recordingId - Recording UUID\n * @param config - Configuration with apiKey\n * @param options - Polling options\n * @returns Recording status when completed or errored\n * \n * @example\n * ```typescript\n * const result = await executeBrowserTask({ task: '...', record_video: true }, config);\n * if (result.recording_id) {\n * const recording = await waitForRecording(result.recording_id, config, {\n * timeout: 60000, // 1 minute\n * pollInterval: 2000 // Check every 2 seconds\n * });\n * console.log('Video URL:', recording.video_url);\n * }\n * ```\n */\nexport async function waitForRecording(\n recordingId: string,\n config: BrowserConfig = {},\n options: { timeout?: number; pollInterval?: number } = {}\n): Promise<RecordingStatus> {\n const timeout = options.timeout ?? 60000; // Default 1 minute\n const pollInterval = options.pollInterval ?? 2000; // Default 2 seconds\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeout) {\n const status = await getRecording(recordingId, config);\n \n if (status.status === 'COMPLETED' || status.status === 'ERROR') {\n return status;\n }\n\n // Wait before next poll\n await new Promise(resolve => setTimeout(resolve, pollInterval));\n }\n\n throw new Error(`Recording timeout after ${timeout}ms - status still pending`);\n}\n\n/**\n * Execute task with recording and wait for video to be ready\n * \n * @param input - Task parameters with record_video=true\n * @param config - Configuration with apiKey\n * @returns Task result with ready video URL\n * \n * @example\n * ```typescript\n * const result = await executeWithRecording(\n * {\n * task: \"Test checkout flow\",\n * url: \"https://example.com\",\n * record_video: true,\n * repo_id: \"my-project\"\n * },\n * { apiKey: process.env.MORPH_API_KEY }\n * );\n * \n * console.log('Task result:', result.result);\n * console.log('Video URL:', result.recording?.video_url);\n * ```\n */\nexport async function executeWithRecording(\n input: BrowserTaskInput & { record_video: true },\n config: BrowserConfig = {}\n): Promise<BrowserTaskResult & { recording?: RecordingStatus }> {\n // Execute task with recording\n const taskResult = await executeBrowserTask(input, config);\n\n // If recording was created, wait for it to complete\n if (taskResult.recording_id) {\n try {\n const recording = await waitForRecording(\n taskResult.recording_id,\n config,\n { timeout: 60000, pollInterval: 2000 }\n );\n return {\n ...taskResult,\n recording,\n };\n } catch (error) {\n // Return task result even if recording fails\n return {\n ...taskResult,\n recording: {\n id: taskResult.recording_id,\n status: 'ERROR',\n error: error instanceof Error ? error.message : String(error),\n created_at: new Date().toISOString(),\n },\n };\n }\n }\n\n return taskResult;\n}\n\n/**\n * Get errors from recording with screenshots\n * \n * Screenshots are captured in real-time (500ms after error occurs) during the browser session.\n * \n * @param recordingId - Recording UUID from BrowserTaskResult\n * @param config - Configuration with apiKey\n * @returns Errors with real-time screenshots\n * \n * @example\n * ```typescript\n * const { errors, total_errors } = await getErrors('uuid-here', { apiKey: 'key' });\n * \n * console.log(`Found ${total_errors} errors`);\n * \n * errors.forEach(err => {\n * console.log(`[${err.type}] ${err.message}`);\n * if (err.url) console.log(` URL: ${err.url}`);\n * if (err.screenshot_url) console.log(` Screenshot: ${err.screenshot_url}`);\n * \n * // Download screenshot\n * if (err.screenshot_url) {\n * const response = await fetch(err.screenshot_url);\n * const screenshot = await response.arrayBuffer();\n * // Save or process screenshot\n * }\n * });\n * ```\n */\nexport async function getErrors(\n recordingId: string,\n config: BrowserConfig = {}\n): Promise<ErrorsResponse> {\n const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;\n const debug = config.debug || false;\n\n if (!config.apiKey) {\n throw new Error('API key required for getErrors');\n }\n\n if (debug) console.log(`[Browser] getErrors: ${recordingId}`);\n\n const response = await fetch(`${apiUrl}/recordings/${recordingId}/errors`, {\n method: 'GET',\n headers: { 'Authorization': `Bearer ${config.apiKey}` },\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => response.statusText);\n if (debug) console.error(`[Browser] getErrors error: ${response.status} - ${errorText}`);\n throw new Error(`HTTP ${response.status}: ${errorText}`);\n }\n\n const errors = await response.json();\n if (debug) console.log(`[Browser] Found ${errors.total_errors} errors`);\n\n return errors;\n}\n\n/**\n * Helper to serialize Zod schema for API\n */\nfunction stringifyStructuredOutput(schema: any): string {\n try {\n return JSON.stringify({\n type: 'object',\n description: 'Zod schema definition (Zod v3)',\n zodDef: schema._def,\n });\n } catch (error) {\n console.warn('[Browser] Failed to serialize Zod schema:', error);\n return JSON.stringify({\n type: 'object',\n description: 'Schema serialization failed',\n });\n }\n}\n\n/**\n * Parse and validate structured task output\n */\nfunction parseStructuredTaskOutput<T>(\n result: BrowserTaskResult,\n schema: any\n): BrowserTaskResult & { parsed: T | null } {\n if (!result.output) {\n return { ...result, parsed: null };\n }\n\n try {\n const parsed = JSON.parse(result.output);\n const validated = schema.parse(parsed) as T;\n return { ...result, parsed: validated };\n } catch (error) {\n if (error instanceof SyntaxError) {\n return { ...result, parsed: null };\n }\n throw error;\n }\n}\n\n/**\n * Get current task status\n */\nasync function getTaskStatus(\n taskId: string,\n config: BrowserConfig\n): Promise<BrowserTaskResult> {\n const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;\n const debug = config.debug || false;\n\n if (debug) console.log(`[Browser] getTaskStatus: ${taskId}`);\n\n const headers: Record<string, string> = {};\n if (config.apiKey) headers['Authorization'] = `Bearer ${config.apiKey}`;\n\n const response = await fetch(`${apiUrl}/tasks/${taskId}`, {\n method: 'GET',\n headers,\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => response.statusText);\n if (debug) console.error(`[Browser] getTaskStatus error: ${response.status} - ${errorText}`);\n throw new Error(`HTTP ${response.status}: ${errorText}`);\n }\n\n const result: BrowserTaskResult = await response.json();\n if (debug) console.log(`[Browser] Task status: ${result.status}`);\n\n return result;\n}\n\n/**\n * Generate live URL for watching task execution in real-time\n */\nfunction generateLiveUrl(taskId: string, config: BrowserConfig): string {\n const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;\n const baseUrl = apiUrl.replace('/api', '');\n return `${baseUrl}/tasks/${taskId}/live`;\n}\n\n/**\n * Poll task until completion\n */\nasync function pollTaskUntilComplete(\n taskId: string,\n config: BrowserConfig,\n pollConfig: { interval?: number; timeout?: number } = {}\n): Promise<BrowserTaskResult> {\n const interval = pollConfig.interval ?? 2000; // 2 seconds\n const timeout = pollConfig.timeout ?? 300000; // 5 minutes\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeout) {\n const status = await getTaskStatus(taskId, config);\n \n if (status.status === 'completed' || status.status === 'failed') {\n return status;\n }\n\n await new Promise(resolve => setTimeout(resolve, interval));\n }\n\n throw new Error(`Task polling timeout after ${timeout}ms`);\n}\n\n/**\n * Wrap task response with convenience methods\n */\nfunction wrapTaskResponse(\n result: BrowserTaskResult,\n config: BrowserConfig\n): BrowserTaskWithPromise {\n const wrapped: BrowserTaskWithPromise = {\n ...result,\n task_id: result.task_id || '',\n liveUrl: result.task_id \n ? generateLiveUrl(result.task_id, config)\n : result.debugUrl || '',\n complete: async (pollConfig?: { interval?: number; timeout?: number }) => {\n return pollTaskUntilComplete(result.task_id!, config, pollConfig);\n },\n // Add Steel live session helpers - either functional or error-throwing\n getLiveUrl: result.debugUrl\n ? (options?: LiveSessionOptions) => buildLiveUrl(result.debugUrl!, options)\n : () => {\n throw new Error(\n 'Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help.'\n );\n },\n getLiveIframe: result.debugUrl\n ? (optionsOrPreset?: string | IframeOptions) => {\n const options = resolvePreset(optionsOrPreset);\n return buildLiveIframe(result.debugUrl!, options);\n }\n : () => {\n throw new Error(\n 'Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help.'\n );\n },\n getEmbedCode: result.debugUrl\n ? () => buildEmbedCode(result.debugUrl!)\n : () => {\n throw new Error(\n 'Live sessions not available. Your backend must return a debugUrl in the response. Contact support@morphllm.com if you need help.'\n );\n },\n };\n\n return wrapped;\n}\n\n/**\n * Wrap task response with schema validation\n */\nfunction wrapTaskResponseWithSchema<T>(\n result: BrowserTaskResult,\n config: BrowserConfig,\n schema: any\n): BrowserTaskWithPromiseAndSchema<T> {\n const parsed = result.output\n ? parseStructuredTaskOutput<T>(result, schema)\n : { ...result, parsed: null };\n\n const wrapped: BrowserTaskWithPromiseAndSchema<T> = {\n ...parsed,\n task_id: result.task_id || '',\n liveUrl: result.task_id\n ? generateLiveUrl(result.task_id, config)\n : result.debugUrl || '',\n complete: async (pollConfig?: { interval?: number; timeout?: number }) => {\n const finalResult = await pollTaskUntilComplete(result.task_id!, config, pollConfig);\n return parseStructuredTaskOutput<T>(finalResult, schema);\n },\n // Add Steel live session helpers - either functional or error-throwing\n getLiveUrl: result.debugUrl\n ? (options?: LiveSessionOptions) => buildLiveUrl(result.debugUrl!, options)\n : () => {\n throw new Error(\n 'Live sessions not available. Your backend must return a debugUrl in the response. ' +\n 'Contact support@morphllm.com if you need help enabling live sessions. '\n );\n },\n getLiveIframe: result.debugUrl\n ? (optionsOrPreset?: string | IframeOptions) => {\n const options = resolvePreset(optionsOrPreset);\n return buildLiveIframe(result.debugUrl!, options);\n }\n : () => {\n throw new Error(\n 'Live sessions not available. Your backend must return a debugUrl in the response. ' +\n 'Contact support@morphllm.com if you need help enabling live sessions.'\n );\n },\n getEmbedCode: result.debugUrl\n ? () => buildEmbedCode(result.debugUrl!)\n : () => {\n throw new Error(\n 'Live sessions not available. Your backend must return a debugUrl in the response. ' +\n 'Contact support@morphllm.com if you need help enabling live sessions.'\n );\n },\n };\n\n return wrapped;\n}\n\n/**\n * Check if browser worker service is healthy\n * \n * @param config - Optional configuration\n * @returns Health status\n */\nexport async function checkHealth(config: BrowserConfig = {}): Promise<{\n ok: boolean;\n google_configured: boolean;\n database_configured: boolean;\n s3_configured: boolean;\n error?: string;\n}> {\n const apiUrl = config.apiUrl || DEFAULT_CONFIG.apiUrl;\n\n try {\n const response = await fetch(`${apiUrl}/health`, {\n method: 'GET',\n headers: config.apiKey\n ? { 'Authorization': `Bearer ${config.apiKey}` }\n : {},\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`);\n }\n\n const data = await response.json();\n return {\n ok: true,\n google_configured: data.google_configured ?? false,\n database_configured: data.database_configured ?? false,\n s3_configured: data.s3_configured ?? false,\n };\n } catch (error) {\n return {\n ok: false,\n google_configured: false,\n database_configured: false,\n s3_configured: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n\n","/**\n * Tool descriptions and prompts for AI models\n */\n\nexport const 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.\n\n## When to Use\nUse this AFTER coding is complete to verify your implementation:\n- You've finished writing/modifying code and need to verify it works\n- You need to test user interactions end-to-end (clicks, forms, navigation)\n- You want to catch runtime errors, console warnings, or network failures\n- You need visual confirmation that UI elements render and behave correctly\n\n## What You Get Back\nThe tool returns debugging artifacts to help you identify and fix issues:\n- **Video recording**: Watch exactly what happened in the browser session\n- **Console logs**: All console.log, warnings, and errors with timestamps\n- **Network logs**: Failed requests, 404s, API errors with screenshots\n- **Error screenshots**: Visual snapshots captured when errors occur\n\n## How to Write Good Tests\nBe specific about the user journey you're testing:\n❌ Bad: \"Test the login feature\"\n✅ Good: \"Navigate to /login, enter 'test@example.com' and 'password123', click the Login button, verify we reach the /dashboard page\"\n\nInclude verification steps:\n✅ \"Click the Add Item button, verify the item appears in the list\"\n✅ \"Submit the form, verify a success message is displayed\"\n\n## Iterating on Failures\n1. Run the test and review the video + logs\n2. Identify the specific issue (console error, failed request, wrong behavior)\n3. Fix the code based on the evidence\n4. Re-run the test to verify the fix\n5. Repeat until test passes\n\n## Requirements\n- **URL**: Must be publicly accessible (use tunnels like ngrok, Cloudflare, or deploy to staging)\n- **Timing**: Use this after implementation, not during coding\n- **Complexity**: Set max_steps higher (20-30) for multi-step user workflows`;\n\n\nexport const 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\"}}]}`;\n"],"mappings":"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","names":["createTool"]}
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  BROWSER_TOOL_DESCRIPTION
3
- } from "../../chunk-7PZJQFCY.js";
3
+ } from "../../chunk-EI4UKP24.js";
4
4
  import {
5
5
  executeBrowserTask
6
- } from "../../chunk-GPFHYUKV.js";
6
+ } from "../../chunk-APMKEW7D.js";
7
7
  import "../../chunk-4VWJFZVS.js";
8
8
  import "../../chunk-PZ5AY32C.js";
9
9
 
@@ -1,13 +1,13 @@
1
1
  import "../../chunk-X2K57BH6.js";
2
- import {
3
- vercel_exports
4
- } from "../../chunk-Y5RCNDLV.js";
5
2
  import {
6
3
  anthropic_exports
7
4
  } from "../../chunk-S3HTYGYF.js";
8
5
  import {
9
6
  openai_exports
10
7
  } from "../../chunk-OZMHDB6O.js";
8
+ import {
9
+ vercel_exports
10
+ } from "../../chunk-Y5RCNDLV.js";
11
11
  import {
12
12
  EDIT_FILE_SYSTEM_PROMPT,
13
13
  EDIT_FILE_TOOL_DESCRIPTION
@@ -1,13 +1,13 @@
1
1
  import "../chunk-X2K57BH6.js";
2
- import {
3
- vercel_exports
4
- } from "../chunk-Y5RCNDLV.js";
5
2
  import {
6
3
  anthropic_exports
7
4
  } from "../chunk-S3HTYGYF.js";
8
5
  import {
9
6
  openai_exports
10
7
  } from "../chunk-OZMHDB6O.js";
8
+ import {
9
+ vercel_exports
10
+ } from "../chunk-Y5RCNDLV.js";
11
11
  import {
12
12
  EDIT_FILE_SYSTEM_PROMPT,
13
13
  EDIT_FILE_TOOL_DESCRIPTION
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  runWarpGrep
3
- } from "../../../chunk-EAA7D24N.js";
4
- import "../../../chunk-Z2FBMSNE.js";
5
- import "../../../chunk-HKZB23U7.js";
3
+ } from "../../../chunk-JJ5ONKDN.js";
6
4
  import "../../../chunk-73RQWOQC.js";
7
5
  import "../../../chunk-EK7OQPWD.js";
6
+ import "../../../chunk-Z2FBMSNE.js";
7
+ import "../../../chunk-HKZB23U7.js";
8
8
  import "../../../chunk-AFEPUNAO.js";
9
9
  import "../../../chunk-TICMYDII.js";
10
10
  import "../../../chunk-NDZO5IPV.js";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  createMorphWarpGrepTool
3
- } from "../../chunk-W5CHJ6OX.js";
4
- import "../../chunk-EAA7D24N.js";
3
+ } from "../../chunk-4VAOPM2H.js";
4
+ import "../../chunk-JJ5ONKDN.js";
5
+ import "../../chunk-73RQWOQC.js";
6
+ import "../../chunk-EK7OQPWD.js";
5
7
  import "../../chunk-Z2FBMSNE.js";
6
8
  import "../../chunk-HKZB23U7.js";
7
- import "../../chunk-UYBIKZPM.js";
8
- import "../../chunk-G2RSY56Q.js";
9
+ import "../../chunk-OVIJAG5M.js";
9
10
  import "../../chunk-SMGZ6A64.js";
10
11
  import "../../chunk-RSLIOCOE.js";
11
- import "../../chunk-73RQWOQC.js";
12
- import "../../chunk-EK7OQPWD.js";
12
+ import "../../chunk-G2RSY56Q.js";
13
13
  import "../../chunk-AFEPUNAO.js";
14
14
  import "../../chunk-TICMYDII.js";
15
15
  import "../../chunk-NDZO5IPV.js";
@@ -4,26 +4,26 @@ import {
4
4
  } from "../../chunk-VBBJGWHY.js";
5
5
  import {
6
6
  createMorphWarpGrepTool
7
- } from "../../chunk-W5CHJ6OX.js";
7
+ } from "../../chunk-4VAOPM2H.js";
8
8
  import {
9
9
  createMorphWarpGrepTool as createMorphWarpGrepTool2
10
- } from "../../chunk-LN4CTQZG.js";
10
+ } from "../../chunk-ITA6CF76.js";
11
11
  import {
12
12
  createMorphWarpGrepTool as createMorphWarpGrepTool3
13
- } from "../../chunk-YSBSDU75.js";
13
+ } from "../../chunk-LAA6XJBF.js";
14
14
  import {
15
15
  runWarpGrep
16
- } from "../../chunk-EAA7D24N.js";
16
+ } from "../../chunk-JJ5ONKDN.js";
17
+ import "../../chunk-73RQWOQC.js";
18
+ import "../../chunk-EK7OQPWD.js";
17
19
  import "../../chunk-Z2FBMSNE.js";
18
20
  import "../../chunk-HKZB23U7.js";
19
21
  import {
20
22
  LocalRipgrepProvider
21
- } from "../../chunk-UYBIKZPM.js";
22
- import "../../chunk-G2RSY56Q.js";
23
+ } from "../../chunk-OVIJAG5M.js";
23
24
  import "../../chunk-SMGZ6A64.js";
24
25
  import "../../chunk-RSLIOCOE.js";
25
- import "../../chunk-73RQWOQC.js";
26
- import "../../chunk-EK7OQPWD.js";
26
+ import "../../chunk-G2RSY56Q.js";
27
27
  import {
28
28
  AGENT_CONFIG,
29
29
  DEFAULT_EXCLUDES,
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  createMorphWarpGrepTool
3
- } from "../../chunk-LN4CTQZG.js";
4
- import "../../chunk-EAA7D24N.js";
3
+ } from "../../chunk-ITA6CF76.js";
4
+ import "../../chunk-JJ5ONKDN.js";
5
+ import "../../chunk-73RQWOQC.js";
6
+ import "../../chunk-EK7OQPWD.js";
5
7
  import "../../chunk-Z2FBMSNE.js";
6
8
  import "../../chunk-HKZB23U7.js";
7
- import "../../chunk-UYBIKZPM.js";
8
- import "../../chunk-G2RSY56Q.js";
9
+ import "../../chunk-OVIJAG5M.js";
9
10
  import "../../chunk-SMGZ6A64.js";
10
11
  import "../../chunk-RSLIOCOE.js";
11
- import "../../chunk-73RQWOQC.js";
12
- import "../../chunk-EK7OQPWD.js";
12
+ import "../../chunk-G2RSY56Q.js";
13
13
  import "../../chunk-AFEPUNAO.js";
14
14
  import "../../chunk-TICMYDII.js";
15
15
  import "../../chunk-NDZO5IPV.js";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  LocalRipgrepProvider
3
- } from "../../../chunk-UYBIKZPM.js";
4
- import "../../../chunk-G2RSY56Q.js";
3
+ } from "../../../chunk-OVIJAG5M.js";
5
4
  import "../../../chunk-SMGZ6A64.js";
6
5
  import "../../../chunk-RSLIOCOE.js";
6
+ import "../../../chunk-G2RSY56Q.js";
7
7
  import "../../../chunk-AFEPUNAO.js";
8
8
  import "../../../chunk-PZ5AY32C.js";
9
9
  export {
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  createMorphWarpGrepTool
3
- } from "../../chunk-YSBSDU75.js";
4
- import "../../chunk-EAA7D24N.js";
3
+ } from "../../chunk-LAA6XJBF.js";
4
+ import "../../chunk-JJ5ONKDN.js";
5
+ import "../../chunk-73RQWOQC.js";
6
+ import "../../chunk-EK7OQPWD.js";
5
7
  import "../../chunk-Z2FBMSNE.js";
6
8
  import "../../chunk-HKZB23U7.js";
7
- import "../../chunk-UYBIKZPM.js";
8
- import "../../chunk-G2RSY56Q.js";
9
+ import "../../chunk-OVIJAG5M.js";
9
10
  import "../../chunk-SMGZ6A64.js";
10
11
  import "../../chunk-RSLIOCOE.js";
11
- import "../../chunk-73RQWOQC.js";
12
- import "../../chunk-EK7OQPWD.js";
12
+ import "../../chunk-G2RSY56Q.js";
13
13
  import "../../chunk-AFEPUNAO.js";
14
14
  import "../../chunk-TICMYDII.js";
15
15
  import "../../chunk-NDZO5IPV.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morphllm/morphsdk",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
4
4
  "description": "TypeScript SDK and CLI for Morph Fast Apply integration",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",