From 2ccf9efced4fbd19167fdfb1c25adc1e330e0a11 Mon Sep 17 00:00:00 2001 From: Chromie Date: Mon, 11 May 2026 13:58:04 -0700 Subject: [PATCH 1/3] [fix] Anthropic CUA `triple_click` mapping (#2104) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Why `AnthropicCUAClient.convertToolUseToAction()` handled actions like `click`, `double_click`, and `left_click`, but did not explicitly support `triple_click`. Two issues: 1. The action type remained in snake_case (`triple_click`) instead of being normalized to `tripleClick` — the format expected by `v3CuaAgentHandler.executeAction()`. 2. Coordinates provided via Anthropic’s coordinate: [x, y] format were not converted into the separate x / y fields required by the handler. As a result, Anthropic triple-click actions were silently ignored by the handler switch. # What Changed - AnthropicCUAClient.ts - Added explicit handling for triple_click / tripleClick in convertToolUseToAction() - Mirrors the existing double_click implementation - Normalizes action type to tripleClick - Extracts coordinates from both: - coordinate: [x, y] - direct x / y fields - v3CuaAgentHandler.ts - Added "triple_click" as a switch-case alias in executeAction() # Test Plan Added `anthropic-cua-triple-click.test.ts` (Vitest) covering: - triple_click with coordinate: [x, y] - triple_click with direct x / y fields All existing unit tests continue to pass. --- ## Summary by cubic Fix incorrect handling of Anthropic CUA `triple_click` so triple-click actions run and coordinates are parsed correctly. Adds unit tests for both coordinate-array and x/y formats, and a changeset for a patch release. - **Bug Fixes** - Normalize `triple_click`/`tripleClick` to internal `tripleClick` in `AnthropicCUAClient`, extracting `x`/`y` from `coordinate` or explicit fields. - Add `"triple_click"` case to `V3CuaAgentHandler` so both naming styles execute consistently. - **Dependencies** - Add changeset to publish a patch for `@browserbasehq/stagehand`. Written for commit 7a545489590830f1e3289097c97b28c185d9f004. Summary will update on new commits. --------- Co-authored-by: Claude --- .changeset/fix-anthropic-cua-triple-click.md | 5 + .../core/lib/v3/agent/AnthropicCUAClient.ts | 11 +++ .../core/lib/v3/handlers/v3CuaAgentHandler.ts | 1 + .../unit/anthropic-cua-triple-click.test.ts | 99 +++++++++++++++++++ 4 files changed, 116 insertions(+) create mode 100644 .changeset/fix-anthropic-cua-triple-click.md create mode 100644 packages/core/tests/unit/anthropic-cua-triple-click.test.ts diff --git a/.changeset/fix-anthropic-cua-triple-click.md b/.changeset/fix-anthropic-cua-triple-click.md new file mode 100644 index 000000000..6f2d57fe6 --- /dev/null +++ b/.changeset/fix-anthropic-cua-triple-click.md @@ -0,0 +1,5 @@ +--- +"@browserbasehq/stagehand": patch +--- + +Fix Anthropic CUA `triple_click` action mapping. diff --git a/packages/core/lib/v3/agent/AnthropicCUAClient.ts b/packages/core/lib/v3/agent/AnthropicCUAClient.ts index 4bbe2d47b..752d208e2 100644 --- a/packages/core/lib/v3/agent/AnthropicCUAClient.ts +++ b/packages/core/lib/v3/agent/AnthropicCUAClient.ts @@ -901,6 +901,17 @@ export class AnthropicCUAClient extends AgentClient { (input.coordinate ? (input.coordinate as number[])[1] : 0), ...input, }; + } else if (action === "triple_click" || action === "tripleClick") { + return { + type: "tripleClick", + x: + (input.x as number) || + (input.coordinate ? (input.coordinate as number[])[0] : 0), + y: + (input.y as number) || + (input.coordinate ? (input.coordinate as number[])[1] : 0), + ...input, + }; } else if (action === "scroll") { // Convert Anthropic's coordinate, scroll_amount and scroll_direction into scroll_x and scroll_y const x = diff --git a/packages/core/lib/v3/handlers/v3CuaAgentHandler.ts b/packages/core/lib/v3/handlers/v3CuaAgentHandler.ts index 6cefa4b4d..af3a3dad8 100644 --- a/packages/core/lib/v3/handlers/v3CuaAgentHandler.ts +++ b/packages/core/lib/v3/handlers/v3CuaAgentHandler.ts @@ -324,6 +324,7 @@ export class V3CuaAgentHandler { } return { success: true }; } + case "triple_click": case "tripleClick": { const { x, y } = action; if (recording) { diff --git a/packages/core/tests/unit/anthropic-cua-triple-click.test.ts b/packages/core/tests/unit/anthropic-cua-triple-click.test.ts new file mode 100644 index 000000000..727fe5173 --- /dev/null +++ b/packages/core/tests/unit/anthropic-cua-triple-click.test.ts @@ -0,0 +1,99 @@ +import { describe, expect, it, vi, beforeEach } from "vitest"; +import { AnthropicCUAClient } from "../../lib/v3/agent/AnthropicCUAClient.js"; +import Anthropic from "@anthropic-ai/sdk"; + +vi.mock("@anthropic-ai/sdk", () => { + const mockCreate = vi.fn(); + + return { + default: class MockAnthropic { + beta = { + messages: { + create: mockCreate, + }, + }; + }, + }; +}); + +describe("AnthropicCUAClient triple_click handling", () => { + let mockCreate: ReturnType; + let client: AnthropicCUAClient; + let executedActions: Array>; + + beforeEach(() => { + vi.clearAllMocks(); + const anthropic = new Anthropic({ apiKey: "test" }); + mockCreate = anthropic.beta.messages.create as ReturnType; + + client = new AnthropicCUAClient("anthropic", "claude-sonnet-4-5-20250929", undefined, { + apiKey: "test-key", + }); + client.setViewport(1280, 720); + client.setScreenshotProvider(async () => "fake-base64-screenshot"); + + executedActions = []; + client.setActionHandler(async (action) => { + executedActions.push({ ...action }); + }); + }); + + it("should convert triple_click with coordinate array to tripleClick action", async () => { + mockCreate.mockResolvedValue({ + id: "test-id", + content: [ + { + type: "tool_use", + id: "tool-1", + name: "computer", + input: { + action: "triple_click", + coordinate: [640, 360], + }, + }, + ], + usage: { input_tokens: 10, output_tokens: 20 }, + }); + + const logger = vi.fn(); + await client.executeStep( + [{ role: "user", content: "triple click the paragraph" }], + logger, + ); + + expect(executedActions).toHaveLength(1); + expect(executedActions[0].type).toBe("tripleClick"); + expect(executedActions[0].x).toBe(640); + expect(executedActions[0].y).toBe(360); + }); + + it("should convert triple_click with x/y fields to tripleClick action", async () => { + mockCreate.mockResolvedValue({ + id: "test-id", + content: [ + { + type: "tool_use", + id: "tool-2", + name: "computer", + input: { + action: "triple_click", + x: 100, + y: 200, + }, + }, + ], + usage: { input_tokens: 10, output_tokens: 20 }, + }); + + const logger = vi.fn(); + await client.executeStep( + [{ role: "user", content: "triple click the line" }], + logger, + ); + + expect(executedActions).toHaveLength(1); + expect(executedActions[0].type).toBe("tripleClick"); + expect(executedActions[0].x).toBe(100); + expect(executedActions[0].y).toBe(200); + }); +}); From cfc89af628bf020af84912185d7c5953b727322d Mon Sep 17 00:00:00 2001 From: miguel Date: Mon, 11 May 2026 15:39:34 -0700 Subject: [PATCH 2/3] linting --- .../tests/unit/anthropic-cua-triple-click.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/core/tests/unit/anthropic-cua-triple-click.test.ts b/packages/core/tests/unit/anthropic-cua-triple-click.test.ts index 727fe5173..fe07561da 100644 --- a/packages/core/tests/unit/anthropic-cua-triple-click.test.ts +++ b/packages/core/tests/unit/anthropic-cua-triple-click.test.ts @@ -26,9 +26,14 @@ describe("AnthropicCUAClient triple_click handling", () => { const anthropic = new Anthropic({ apiKey: "test" }); mockCreate = anthropic.beta.messages.create as ReturnType; - client = new AnthropicCUAClient("anthropic", "claude-sonnet-4-5-20250929", undefined, { - apiKey: "test-key", - }); + client = new AnthropicCUAClient( + "anthropic", + "claude-sonnet-4-5-20250929", + undefined, + { + apiKey: "test-key", + }, + ); client.setViewport(1280, 720); client.setScreenshotProvider(async () => "fake-base64-screenshot"); From a230bfcd987b1ec229956f338ac8caa4b58c0a2f Mon Sep 17 00:00:00 2001 From: miguel Date: Mon, 11 May 2026 16:24:03 -0700 Subject: [PATCH 3/3] trigger evals tests on ci --- .github/workflows/ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f74bdd3f..6891afed4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -335,6 +335,24 @@ jobs: - name: Run CLI Tests run: pnpm exec turbo run test:cli --filter=@browserbasehq/browse-cli + run-evals-unit-tests: + name: Evals Unit Tests + runs-on: ubuntu-latest + needs: [run-build, determine-changes] + if: needs.determine-changes.outputs.evals == 'true' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - uses: ./.github/actions/setup-node-pnpm-turbo + with: + use-prebuilt-artifacts: "true" + restore-turbo-cache: "false" + + - name: Run Evals Unit Tests + run: pnpm --filter @browserbasehq/stagehand-evals run test:unit + discover-core-tests: runs-on: ubuntu-latest needs: [determine-changes]