Skip to content

Commit 2ccf9ef

Browse files
chromiebotclaude
andauthored
[fix] Anthropic CUA triple_click mapping (#2104)
# 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. <!-- This is an auto-generated description by cubic. --> --- ## 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`. <sup>Written for commit 7a54548. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 26e6c96 commit 2ccf9ef

4 files changed

Lines changed: 116 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@browserbasehq/stagehand": patch
3+
---
4+
5+
Fix Anthropic CUA `triple_click` action mapping.

packages/core/lib/v3/agent/AnthropicCUAClient.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,17 @@ export class AnthropicCUAClient extends AgentClient {
901901
(input.coordinate ? (input.coordinate as number[])[1] : 0),
902902
...input,
903903
};
904+
} else if (action === "triple_click" || action === "tripleClick") {
905+
return {
906+
type: "tripleClick",
907+
x:
908+
(input.x as number) ||
909+
(input.coordinate ? (input.coordinate as number[])[0] : 0),
910+
y:
911+
(input.y as number) ||
912+
(input.coordinate ? (input.coordinate as number[])[1] : 0),
913+
...input,
914+
};
904915
} else if (action === "scroll") {
905916
// Convert Anthropic's coordinate, scroll_amount and scroll_direction into scroll_x and scroll_y
906917
const x =

packages/core/lib/v3/handlers/v3CuaAgentHandler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ export class V3CuaAgentHandler {
324324
}
325325
return { success: true };
326326
}
327+
case "triple_click":
327328
case "tripleClick": {
328329
const { x, y } = action;
329330
if (recording) {
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { describe, expect, it, vi, beforeEach } from "vitest";
2+
import { AnthropicCUAClient } from "../../lib/v3/agent/AnthropicCUAClient.js";
3+
import Anthropic from "@anthropic-ai/sdk";
4+
5+
vi.mock("@anthropic-ai/sdk", () => {
6+
const mockCreate = vi.fn();
7+
8+
return {
9+
default: class MockAnthropic {
10+
beta = {
11+
messages: {
12+
create: mockCreate,
13+
},
14+
};
15+
},
16+
};
17+
});
18+
19+
describe("AnthropicCUAClient triple_click handling", () => {
20+
let mockCreate: ReturnType<typeof vi.fn>;
21+
let client: AnthropicCUAClient;
22+
let executedActions: Array<Record<string, unknown>>;
23+
24+
beforeEach(() => {
25+
vi.clearAllMocks();
26+
const anthropic = new Anthropic({ apiKey: "test" });
27+
mockCreate = anthropic.beta.messages.create as ReturnType<typeof vi.fn>;
28+
29+
client = new AnthropicCUAClient("anthropic", "claude-sonnet-4-5-20250929", undefined, {
30+
apiKey: "test-key",
31+
});
32+
client.setViewport(1280, 720);
33+
client.setScreenshotProvider(async () => "fake-base64-screenshot");
34+
35+
executedActions = [];
36+
client.setActionHandler(async (action) => {
37+
executedActions.push({ ...action });
38+
});
39+
});
40+
41+
it("should convert triple_click with coordinate array to tripleClick action", async () => {
42+
mockCreate.mockResolvedValue({
43+
id: "test-id",
44+
content: [
45+
{
46+
type: "tool_use",
47+
id: "tool-1",
48+
name: "computer",
49+
input: {
50+
action: "triple_click",
51+
coordinate: [640, 360],
52+
},
53+
},
54+
],
55+
usage: { input_tokens: 10, output_tokens: 20 },
56+
});
57+
58+
const logger = vi.fn();
59+
await client.executeStep(
60+
[{ role: "user", content: "triple click the paragraph" }],
61+
logger,
62+
);
63+
64+
expect(executedActions).toHaveLength(1);
65+
expect(executedActions[0].type).toBe("tripleClick");
66+
expect(executedActions[0].x).toBe(640);
67+
expect(executedActions[0].y).toBe(360);
68+
});
69+
70+
it("should convert triple_click with x/y fields to tripleClick action", async () => {
71+
mockCreate.mockResolvedValue({
72+
id: "test-id",
73+
content: [
74+
{
75+
type: "tool_use",
76+
id: "tool-2",
77+
name: "computer",
78+
input: {
79+
action: "triple_click",
80+
x: 100,
81+
y: 200,
82+
},
83+
},
84+
],
85+
usage: { input_tokens: 10, output_tokens: 20 },
86+
});
87+
88+
const logger = vi.fn();
89+
await client.executeStep(
90+
[{ role: "user", content: "triple click the line" }],
91+
logger,
92+
);
93+
94+
expect(executedActions).toHaveLength(1);
95+
expect(executedActions[0].type).toBe("tripleClick");
96+
expect(executedActions[0].x).toBe(100);
97+
expect(executedActions[0].y).toBe(200);
98+
});
99+
});

0 commit comments

Comments
 (0)