diff --git a/.gitignore b/.gitignore index a7bd963a..7cb36a85 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ yarn-error.log* lerna-debug.log* .pnpm-debug.log* tmp -debug +/debug # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/README.md b/README.md index 0af20eb2..e6381531 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ HyperAgent provides two complementary APIs optimized for different use cases: - β‘ **Fast** - Uses accessibility tree (no screenshots) - π° **Cheap** - Single LLM call per action - π― **Reliable** - Direct element finding and execution -- π **Efficient** - Text-based DOM analysis +- π **Efficient** - Text-based DOM analysis with automatic ad-frame filtering **Example**: ```typescript @@ -136,18 +136,27 @@ await page.aiAction("click the login button"); **Best for**: Complex workflows requiring multiple steps and visual context **Advantages**: -- πΌοΈ **Visual Understanding** - Uses screenshots with element overlays + +- πΌοΈ **Visual Understanding** - Can use screenshots with element overlays - π **Complex Tasks** - Handles multi-step workflows automatically - π§ **Context-Aware** - Better at understanding page layout and relationships - π **Adaptive** - Can adjust strategy based on page state +**Parameters**: + +- `useDomCache` (boolean): Reuse DOM snapshots for speed +- `enableVisualMode` (boolean): Enable screenshots and overlays (default: false) + **Example**: + ```typescript const page = await agent.newPage(); await page.goto("https://flights.google.com"); // Complex task with multiple steps handled automatically -await page.ai("search for flights from Miami to New Orleans on July 16"); +await page.ai("search for flights from Miami to New Orleans on July 16", { + useDomCache: true, +}); ``` ### π¨ Mix and Match @@ -370,6 +379,22 @@ const agent = new HyperAgent({ }); ``` +## CDP First + +HyperAgent speaks Chrome DevTools Protocol natively. Element lookup, scrolling, typing, frame management, and screenshots all go through CDP so every action has exact coordinates, execution contexts, and browser events. This allows for more custom commands and deep iframe tracking. + +HyperAgent integrates seamlessly with Playwright, so you can still use familiar commands, while the actions take full advantage of native CDP protocol with fast locators and advanced iframe tracking. + +**Key Features:** + +- **Auto-Ad Filtering**: Automatically filters out ad and tracking iframes to keep context clean +- **Deep Iframe Support**: Tracking across nested and cross-origin iframes (OOPIFs) +- **Exact Coordinates**: Actions use precise CDP coordinates for reliability + +Keep in mind that CDP is still experimental, and stability is not guaranteed. If youβd like the agent to use Playwrightβs native locators/actions instead, set `cdpActions: false` when you create the agent and it will fall back automatically. + +The CDP layer is still evolvingβexpect rapid polish (and the occasional sharp edge). If you hit something quirky you can toggle CDP off for that workflow and drop us a bug report. + ## Contributing We welcome contributions to Hyperagent! Here's how you can help: diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md deleted file mode 100644 index 52e7c8bd..00000000 --- a/docs/MIGRATION.md +++ /dev/null @@ -1,203 +0,0 @@ -# LangChain to Native SDK Migration Guide - -This guide helps you migrate from LangChain-based HyperAgent to the new native SDK implementation. - -## Breaking Changes - -### 1. LLM Configuration - -**Before (LangChain):** -```typescript -import { ChatOpenAI } from "@langchain/openai"; -import { ChatAnthropic } from "@langchain/anthropic"; - -const agent = new HyperAgent({ - llm: new ChatOpenAI({ - openAIApiKey: process.env.OPENAI_API_KEY, - modelName: "gpt-4o", - }), -}); -``` - -**After (Native SDK):** -```typescript -const agent = new HyperAgent({ - llm: { - provider: "openai", - model: "gpt-4o", - }, -}); -``` - -### 2. Provider-Specific Configuration - -**OpenAI:** -```typescript -// Before -const llm = new ChatOpenAI({ - openAIApiKey: process.env.OPENAI_API_KEY, - modelName: "gpt-4o", - temperature: 0.7, - maxTokens: 1000, -}); - -// After -const agent = new HyperAgent({ - llm: { - provider: "openai", - model: "gpt-4o", - temperature: 0.7, - maxTokens: 1000, - }, -}); -``` - -**Anthropic:** -```typescript -// Before -const llm = new ChatAnthropic({ - anthropicApiKey: process.env.ANTHROPIC_API_KEY, - modelName: "claude-3-7-sonnet-latest", -}); - -// After -const agent = new HyperAgent({ - llm: { - provider: "anthropic", - model: "claude-3-7-sonnet-latest", - }, -}); -``` - -**Gemini:** -```typescript -// Before (if using LangChain Gemini) -const llm = new ChatGemini({ - apiKey: process.env.GEMINI_API_KEY, - modelName: "gemini-2.5-pro-preview-03-25", -}); - -// After -const agent = new HyperAgent({ - llm: { - provider: "gemini", - model: "gemini-2.5-pro-preview-03-25", - }, -}); -``` - -### 3. Direct LLM Instance Usage - -If you were passing a direct LLM instance, you can still do so, but the interface has changed: - -**Before:** -```typescript -import { ChatOpenAI } from "@langchain/openai"; - -const llm = new ChatOpenAI({...}); -const agent = new HyperAgent({ llm }); -``` - -**After:** -```typescript -import { createOpenAIClient } from "@hyperbrowser/agent/llm/providers"; - -const llm = createOpenAIClient({ - apiKey: process.env.OPENAI_API_KEY, - model: "gpt-4o", -}); -const agent = new HyperAgent({ llm }); -``` - -## Migration Steps - -### Step 1: Update Dependencies - -Remove LangChain dependencies from your `package.json`: -```bash -npm uninstall langchain @langchain/core @langchain/openai @langchain/anthropic -``` - -The new dependencies are automatically included with HyperAgent. - -### Step 2: Update Imports - -Remove LangChain imports: -```typescript -// Remove these imports -import { ChatOpenAI } from "@langchain/openai"; -import { ChatAnthropic } from "@langchain/anthropic"; -``` - -### Step 3: Update Agent Configuration - -Replace LangChain LLM instances with configuration objects: - -```typescript -// Before -const agent = new HyperAgent({ - llm: new ChatOpenAI({ - openAIApiKey: process.env.OPENAI_API_KEY, - modelName: "gpt-4o", - }), -}); - -// After -const agent = new HyperAgent({ - llm: { - provider: "openai", - model: "gpt-4o", - }, -}); -``` - -### Step 4: Update Environment Variables - -The environment variable names remain the same: -- `OPENAI_API_KEY` for OpenAI -- `ANTHROPIC_API_KEY` for Anthropic -- `GEMINI_API_KEY` or `GOOGLE_API_KEY` for Gemini -- `DEEPSEEK_API_KEY` for DeepSeek - -### Step 5: Test Your Application - -Run your application to ensure everything works correctly: - -```bash -yarn build -yarn cli -c "Go to hackernews and list the top 3 stories" -``` - -## Benefits of Migration - -1. **Better Performance**: Native SDKs are optimized for their respective providers -2. **Reduced Bundle Size**: No LangChain overhead -3. **Latest Features**: Access to newest provider features immediately -4. **Better Error Handling**: Provider-specific error messages -5. **Improved Reliability**: Direct API communication without abstraction layers - -## Troubleshooting - -### Common Issues - -1. **Import Errors**: Make sure to remove all LangChain imports -2. **Type Errors**: Update your TypeScript types to use the new interfaces -3. **Configuration Issues**: Double-check your provider and model names - -### Getting Help - -If you encounter issues during migration: - -1. Check the [examples](examples/) directory for working code -2. Review the [API documentation](docs/) -3. Open an issue on GitHub - -## Rollback Plan - -If you need to rollback temporarily: - -1. Reinstall LangChain dependencies -2. Revert your code changes -3. Use the previous version of HyperAgent - -However, we recommend completing the migration for the best experience. diff --git a/docs/cdp-overview.md b/docs/cdp-overview.md new file mode 100644 index 00000000..cb27a434 --- /dev/null +++ b/docs/cdp-overview.md @@ -0,0 +1,3484 @@ +# CDP / Agent Integration Deep Dive + +This document explains every relevant function added or changed since commit `f3cdfb478f5dfc724c24309165ad961c914064e0`. It targets readers with zero context and covers: + +1. Frame/session infrastructure and why it exists. +2. How the accessibility DOM (A11yDOM) pipeline gathers full-frame data. +3. How bounding boxes/visual overlays work without Playwright. +4. How `page.aiAction()` (`executeAction`/`runAgentTask`) and `page.ai()` (`executeSingleAction`) flow through CDP. +5. What each CDP runtime action does under the hood. +6. **THE BIG PICTURE: Why we have so many maps and events** +7. Areas that still need abstraction or cleanup. + +--- + +## 0. THE BIG PICTURE: The Map Problem & Why We Need Multiple Event Listeners + +### Why So Many Maps? + +**TL;DR:** Chrome separates DOM structure, accessibility data, frame information, and execution contexts into different CDP domains. We need multiple maps to stitch them together because Chrome doesn't provide a single unified view. + +### The Core Problem: Multiple Identifiers for the Same Element + +When you click a button on a webpage with iframes, Chrome needs to track that element using **4 different identifiers**: + +1. **`backendNodeId`** (DOM domain) - Identifies the DOM node +2. **`nodeId`** (Accessibility domain) - Identifies the accessibility tree node +3. **`frameId`** (Page domain) - Identifies which frame contains the element +4. **`executionContextId`** (Runtime domain) - Identifies the JavaScript execution context to run scripts in that frame + +**The problem:** These IDs are managed by separate CDP domains and are NOT directly linked by Chrome! + +### Concrete Example: A Button in an Iframe + +Let's say we have this HTML structure: + +```html + + +
+ + + + + + + + + + + +``` + +When the agent needs to click `#iframe-btn`, here's what we need to resolve: + +| **What We Know** | **What We Need to Find** | **Which Map Provides It** | +|------------------|---------------------------|----------------------------| +| LLM says: "click the button with text 'I'm in an iframe'" | Which `encodedId` is this element? | `elements` Map (from A11y tree) β **`1-42`** (frame 1, node 42) | +| `encodedId: "1-42"` | What's the `backendNodeId`? | `backendNodeMap["1-42"]` β **`42`** | +| `encodedId: "1-42"` | What's the XPath to this element? | `xpathMap["1-42"]` β **`"//button[1]"`** | +| `frameIndex: 1` | What's the iframe metadata? | `frameMap.get(1)` β `IframeInfo` | +| `frameIndex: 1` | What's the Chrome `frameId`? | `IframeInfo.frameId` β **`"ABC123"`** OR `FrameContextManager.getFrameIdByIndex(1)` β **`"ABC123"`** | +| `frameId: "ABC123"` | Which CDP session controls this frame? | `FrameContextManager.getFrameSession("ABC123")` β **`CDPSession`** | +| `frameId: "ABC123"` | What's the `executionContextId` to run scripts? | `FrameContextManager.getExecutionContextId("ABC123")` β **`5`** | +| `backendNodeId: 42` + `executionContextId: 5` | How do I click this element? | `resolveElement()` + `dispatchCDPAction()` | + +### The 5 Core Maps Explained + +#### 1. **`backendNodeMap: Record