# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview The eval-server is a **thin HTTP API wrapper for Browser Operator**. It provides: - WebSocket server for browser agent connections - REST APIs for browser automation via Chrome DevTools Protocol (CDP) - JSON-RPC 2.0 bidirectional communication - Screenshot capture and page content retrieval **Important:** Evaluation orchestration and LLM-as-a-judge logic lives in the separate `evals/` Python project, which calls these APIs. ## Architecture ### Separation of Concerns **eval-server (Node.js)**: Thin API layer - WebSocket server for agent connections - JSON-RPC 2.0 bidirectional communication - REST APIs for CDP operations (screenshots, page content, tab management) - NO evaluation logic, NO judges, NO test orchestration **evals (Python)**: Evaluation orchestration and judging - LLM judges (LLMJudge, VisionJudge) in `lib/judge.py` - Evaluation runners that call eval-server APIs - Test case definitions (YAML files in `data/`) - Result reporting and analysis ## Core Components ### WebSocket Server (src/lib/EvalServer.js) - Accepts connections from browser agents (DevTools clients) - Manages agent lifecycle (connect, ready, disconnect) - Handles bidirectional RPC communication - Integrates directly with Chrome DevTools Protocol ### HTTP API Server (src/api-server.js) - Exposes REST endpoints for external callers (e.g., Python evals) - Main endpoint: `POST /v1/responses` - Send task to agent - CDP endpoints: screenshot, page content, JavaScript execution, tab management - Returns metadata (clientId, tabId) for subsequent operations ### RPC Client (src/rpc-client.js) - Implements JSON-RPC 2.0 protocol for bidirectional communication - Manages request/response correlation with unique IDs - Handles timeouts and error conditions - Calls `evaluate(params)` method on connected agents ### Client Manager (src/client-manager.js) - Tracks WebSocket client connections - Manages tab-level connections (composite clientId:tabId) - Maintains client state (connected, ready) ### CDP Integration (src/lib/EvalServer.js) - Direct Chrome DevTools Protocol communication - Screenshot capture via `Page.captureScreenshot` - Page content access via `Runtime.evaluate` - JavaScript execution via `Runtime.evaluate` (with configurable options) - Tab management via `Target.createTarget` / `Target.closeTarget` ### Logger (src/logger.js) - Structured logging using Winston - Separate log files for different event types - JSON format for easy parsing and analysis ## Key API Endpoints ### POST /v1/responses Primary endpoint for sending tasks to browser agents. **Request (OpenAI):** ```json { "input": "Click the submit button", "url": "https://example.com", "wait_timeout": 5000, "model": { "main_model": {"provider": "openai", "model": "gpt-5-mini", "api_key": "sk-..."}, "mini_model": {"provider": "openai", "model": "gpt-5-nano", "api_key": "sk-..."}, "nano_model": {"provider": "openai", "model": "gpt-5-nano", "api_key": "sk-..."} } } ``` **Request (LiteLLM with endpoint):** ```json { "input": "Navigate to google.com", "url": "about:blank", "wait_timeout": 5000, "model": { "main_model": { "provider": "litellm", "model": "qwen3:14b", "endpoint": "http://localhost:4000" }, "mini_model": { "provider": "litellm", "model": "qwen3:14b", "endpoint": "http://localhost:4000" }, "nano_model": { "provider": "litellm", "model": "qwen3:14b", "endpoint": "http://localhost:4000" } } } ``` **Note:** The `endpoint` field can be: - Specified per model tier (e.g., `main_model.endpoint`) - Specified at top-level (`model.endpoint`) to apply to all tiers - Omitted to use `LITELLM_ENDPOINT` environment variable **Request (Conversation State - OpenAI Responses API format):** ```json { "input": [ { "role": "system", "content": "You are a web automation expert." }, { "role": "user", "content": "Navigate to bloomberg.com" }, { "role": "assistant", "content": "I've navigated to bloomberg.com. I can see the homepage." }, { "role": "user", "content": "Summarize todays news" } ], "url": "https://bloomberg.com", "model": { "main_model": { "provider": "litellm", "model": "gemma3:12b", "endpoint": "http://localhost:4000" } } } ``` **Input Format Options:** 1. **String format**: `"input": "Your message"` (simple, single message) 2. **Conversation array**: `"input": [{role, content}, ...]` (multi-turn with history) **Message Requirements:** - Each message needs `role` (`system`, `user`, or `assistant`) and `content` (string) - At least one `user` message required - System messages are extracted as system prompt - Maximum 100 messages, 10,000 characters each **Response (OpenAI-compatible format):** ```json [ { "id": "msg_abc123", "type": "message", "role": "assistant", "content": [{"type": "output_text", "text": "Done", "annotations": []}], "metadata": { "clientId": "9907fd8d-92a8-4a6a-bce9-458ec8c57306", "tabId": "482D56EE57B1931A3B9D1BFDAF935429" } } ] ``` **Important:** The `metadata` field contains `clientId` and `tabId` which are used by the evals framework for screenshot capture. ### POST /page/screenshot Capture screenshot of a browser tab via CDP. **Request:** ```json { "clientId": "9907fd8d-92a8-4a6a-bce9-458ec8c57306", "tabId": "482D56EE57B1931A3B9D1BFDAF935429", "fullPage": false } ``` **Response:** ```json { "clientId": "...", "tabId": "...", "imageData": "data:image/png;base64,iVBORw0KG...", "format": "png", "timestamp": 1234567890 } ``` ### POST /page/content Get HTML or text content of a page. **Request:** ```json { "clientId": "9907fd8d-92a8-4a6a-bce9-458ec8c57306", "tabId": "482D56EE57B1931A3B9D1BFDAF935429", "format": "html", "includeIframes": true } ``` **Parameters:** - `clientId` (required): The client ID from `/v1/responses` metadata - `tabId` (required): The tab ID from `/v1/responses` metadata - `format` (optional, default: `"html"`): Content format - either `"html"` or `"text"` - `includeIframes` (optional, default: `false`): Whether to include HTML content from iframes. When `true`, recursively captures content from all iframe elements on the page. **Response:** ```json { "clientId": "9907fd8d-92a8-4a6a-bce9-458ec8c57306", "tabId": "482D56EE57B1931A3B9D1BFDAF935429", "content": "...", "format": "html", "length": 12345, "frameCount": 3, "timestamp": 1234567890 } ``` **Response fields:** - `frameCount` (number, optional): Number of frames included in the content. Only present when `includeIframes: true` is used. ### POST /page/execute Execute JavaScript code in the context of a specific browser tab via Chrome DevTools Protocol. **Request:** ```json { "clientId": "9907fd8d-92a8-4a6a-bce9-458ec8c57306", "tabId": "482D56EE57B1931A3B9D1BFDAF935429", "expression": "document.title", "returnByValue": true, "awaitPromise": false } ``` **Parameters:** - `clientId` (required): The client ID from `/v1/responses` metadata - `tabId` (required): The tab ID from `/v1/responses` metadata - `expression` (required): JavaScript code to execute (string) - `returnByValue` (optional, default: `true`): Whether to return result by value or as object reference - `awaitPromise` (optional, default: `false`): Whether to await if the result is a Promise **Response:** ```json { "clientId": "9907fd8d-92a8-4a6a-bce9-458ec8c57306", "tabId": "482D56EE57B1931A3B9D1BFDAF935429", "result": { "type": "string", "value": "Example Page Title" }, "exceptionDetails": null, "timestamp": 1234567890 } ``` **Response Fields:** - `clientId`: Base client ID (without tab suffix) - `tabId`: The tab ID where JavaScript was executed - `result`: CDP `Runtime.evaluate` result object containing: - `type`: Result type (string, number, object, etc.) - `value`: The actual value (if `returnByValue: true`) - `exceptionDetails`: Error details if execution failed, otherwise `null` - `timestamp`: Unix timestamp in milliseconds **Implementation:** - Uses CDP `Runtime.evaluate` via `browserAgentServer.evaluateJavaScript()` - Executes code in the page's main JavaScript context - First 100 characters of expression logged for debugging **Example Usage:** ```bash # Get page title curl -X POST http://localhost:8080/page/execute \ -H "Content-Type: application/json" \ -d '{ "clientId": "9907fd8d-92a8-4a6a-bce9-458ec8c57306", "tabId": "482D56EE57B1931A3B9D1BFDAF935429", "expression": "document.title" }' # Count elements curl -X POST http://localhost:8080/page/execute \ -H "Content-Type: application/json" \ -d '{ "clientId": "9907fd8d-92a8-4a6a-bce9-458ec8c57306", "tabId": "482D56EE57B1931A3B9D1BFDAF935429", "expression": "document.querySelectorAll(\"button\").length" }' # Execute async code with await curl -X POST http://localhost:8080/page/execute \ -H "Content-Type: application/json" \ -d '{ "clientId": "9907fd8d-92a8-4a6a-bce9-458ec8c57306", "tabId": "482D56EE57B1931A3B9D1BFDAF935429", "expression": "fetch(\"https://api.example.com/data\").then(r => r.json())", "awaitPromise": true }' ``` **Use Cases:** - Extract specific data from the page (e.g., element counts, text content) - Verify JavaScript state/variables for evaluations - Check DOM state programmatically - Execute custom validation logic - Interact with page APIs directly This endpoint complements `/page/content` by allowing precise JavaScript execution rather than just fetching full HTML/text content. ### POST /tabs/open, POST /tabs/close Tab management via CDP. ## Configuration All configuration is managed through environment variables and `src/config.js`: ```bash # .env file PORT=8080 # WebSocket server port API_PORT=8081 # HTTP API server port AUTH_KEY=hello # Authentication key CDP_HOST=localhost # Chrome DevTools Protocol host CDP_PORT=9223 # Chrome DevTools Protocol port ``` ## Model Configuration Schema The server uses a canonical nested model configuration format: ```typescript interface ModelTierConfig { provider: string; // "openai" | "groq" | "openrouter" | "litellm" model: string; // Model name (e.g., "gpt-5-mini") api_key: string; // API key for this tier } interface ModelConfig { main_model: ModelTierConfig; // Primary model mini_model: ModelTierConfig; // Secondary model nano_model: ModelTierConfig; // Tertiary model } ``` ## Tab Management Each browser tab is identified by a **composite client ID**: `baseClientId:tabId` Example: - Base Client ID: `9907fd8d-92a8-4a6a-bce9-458ec8c57306` - Tab ID: `482D56EE57B1931A3B9D1BFDAF935429` - Composite: `9907fd8d-92a8-4a6a-bce9-458ec8c57306:482D56EE57B1931A3B9D1BFDAF935429` ## CDP Communication Pattern The server uses **direct CDP communication**: 1. Discover CDP WebSocket endpoint via `http://localhost:9223/json/version` 2. For each command, establish WebSocket connection to CDP endpoint 3. Send commands using JSON-RPC 2.0: - **Browser-level**: `Target.createTarget`, `Target.closeTarget` - **Tab-level**: `Runtime.evaluate`, `Page.captureScreenshot` 4. For tab-level operations: attach → execute → detach 5. Close WebSocket after receiving response ## Integration with Evals Framework The eval-server is designed to work with the separate `evals/` Python project: **Flow:** 1. Python evals runner sends request to `/v1/responses` 2. Eval-server forwards to DevTools agent via WebSocket/JSON-RPC 3. Agent performs browser automation task 4. Response includes `metadata.clientId` and `metadata.tabId` 5. Python evals uses these IDs to capture screenshot via `/page/screenshot` 6. Python evals uses VisionJudge to evaluate with screenshot 7. Python evals generates report and saves screenshot See `evals/README.md` for detailed evaluation framework documentation. ## Development Commands ```bash # Install dependencies npm install # Start server npm start # Check status curl http://localhost:8081/status # Test screenshot curl -X POST http://localhost:8081/page/screenshot \ -H "Content-Type: application/json" \ -d '{"clientId":"CLIENT_ID","tabId":"TAB_ID","fullPage":false}' ``` ## Chrome Setup The browser must be started with remote debugging enabled: ```bash chromium --remote-debugging-port=9223 ``` CDP endpoint: `http://localhost:9223/json/version` ## File Structure ``` nodejs/ ├── package.json ├── .env.example └── src/ ├── api-server.js # HTTP REST API endpoints ├── client-manager.js # WebSocket client management ├── rpc-client.js # JSON-RPC 2.0 communication ├── config.js # Configuration management ├── logger.js # Winston logging └── lib/ ├── EvalServer.js # Core server + CDP integration └── HTTPWrapper.js # HTTP wrapper around EvalServer ``` ## Key Implementation Details ### formatResponse() Method Located in `src/api-server.js:706` Converts agent responses to OpenAI-compatible format and **adds metadata**: ```javascript formatResponse(responseText, clientId = null, tabId = null) { const messageId = `msg_${uuidv4().replace(/-/g, '')}`; const response = [{ id: messageId, type: 'message', role: 'assistant', content: [{ type: 'output_text', text: responseText, annotations: [] }] }]; // Add metadata for screenshot capture if (clientId && tabId) { response[0].metadata = { clientId, tabId }; } return response; } ``` This metadata is critical for the evals framework to capture screenshots. ### Screenshot Capture Flow 1. Evals calls `/v1/responses` with task 2. Server returns response with `metadata: {clientId, tabId}` 3. Evals extracts metadata from response 4. Evals calls `/page/screenshot` with extracted IDs 5. Server uses CDP to capture screenshot 6. Returns base64-encoded PNG 7. Evals saves screenshot and uses for VisionJudge evaluation ## Logging Logs are written to `logs/` directory (auto-created): - `combined.log` - All log events - `error.log` - Error events only - `api.log` - API request/response logs ## Docker Integration The eval-server runs inside `kernel-browser-extended` Docker container. Volume mount for live development: ```yaml volumes: - "./eval-server/nodejs:/opt/eval-server" ``` ## Dependencies Core dependencies: - `ws` - WebSocket server - `uuid` - ID generation - `winston` - Structured logging - `dotenv` - Environment variable management Removed dependencies: - ~~`openai`~~ - Not needed (evals handles judging) - ~~`js-yaml`~~ - Not needed (evals handles YAML loading) ## What This Server Does NOT Do - ❌ Load YAML evaluation definitions (handled by evals/) - ❌ LLM-as-a-judge evaluation (handled by evals/) - ❌ Test orchestration (handled by evals/) - ❌ Result reporting (handled by evals/) - ❌ Screenshot analysis (handled by evals/) ## What This Server DOES Do - ✅ WebSocket server for browser agent connections - ✅ JSON-RPC 2.0 bidirectional communication - ✅ HTTP REST API endpoints - ✅ CDP screenshot capture - ✅ CDP page content retrieval - ✅ CDP JavaScript execution - ✅ CDP tab management - ✅ Return metadata (clientId, tabId) for screenshot capture