Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-anthropic-cua-triple-click.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@browserbasehq/stagehand": patch
---

Fix Anthropic CUA `triple_click` action mapping.
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
11 changes: 11 additions & 0 deletions packages/core/lib/v3/agent/AnthropicCUAClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
1 change: 1 addition & 0 deletions packages/core/lib/v3/handlers/v3CuaAgentHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ export class V3CuaAgentHandler {
}
return { success: true };
}
case "triple_click":
case "tripleClick": {
const { x, y } = action;
if (recording) {
Expand Down
104 changes: 104 additions & 0 deletions packages/core/tests/unit/anthropic-cua-triple-click.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
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<typeof vi.fn>;
let client: AnthropicCUAClient;
let executedActions: Array<Record<string, unknown>>;

beforeEach(() => {
vi.clearAllMocks();
const anthropic = new Anthropic({ apiKey: "test" });
mockCreate = anthropic.beta.messages.create as ReturnType<typeof vi.fn>;

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);
});
});
Loading