Skip to content

Commit 254d56c

Browse files
Add Anthropic multi-action tool choice regression
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 237dedc commit 254d56c

2 files changed

Lines changed: 64 additions & 0 deletions

File tree

currentState.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ HyperAgent exposes a TypeScript SDK for browser automation with three primary pa
130130
- Replay cached-step execution now honors `cdpActions: false` by skipping CDP XPath resolution/runtime initialization and using Playwright-path action execution for that attempt.
131131
- Normalized CLI shutdown failure logging in `cli/index.ts` to use `formatCliError`, avoiding raw unsanitized shutdown diagnostics.
132132
- Normalized CLI per-step failure rendering in `cli/index.ts` so action error messages are sanitized/truncated via `formatCliError` before display.
133+
- Added Anthropic structured-output regression coverage confirming multi-action calls enforce deterministic `tool_choice: { type: "any", disable_parallel_tool_use: true }`.
133134
- Expanded top-level package exports for key workflow/config types at `@hyperbrowser/agent`.
134135
- Removed stale script entry (`build-dom-tree-script`) and improved README usage docs.
135136
- Added canonical single-action debug writer helper (`writePerformDebug`) while preserving deprecated alias compatibility.

src/llm/providers/anthropic.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,69 @@ describe("AnthropicClient", () => {
242242
warnSpy.mockRestore();
243243
});
244244

245+
it("uses deterministic tool choice policy for multi-action structured calls", async () => {
246+
createMessageMock.mockResolvedValue({
247+
content: [
248+
{
249+
type: "tool_use",
250+
name: "click",
251+
input: {
252+
thoughts: "select click",
253+
memory: "state",
254+
action: {
255+
params: {},
256+
},
257+
},
258+
},
259+
],
260+
});
261+
262+
const client = new AnthropicClient({ model: "claude-test" });
263+
const result = await client.invokeStructured(
264+
{
265+
schema: z.object({
266+
thoughts: z.string().optional(),
267+
memory: z.string().optional(),
268+
action: z.object({
269+
type: z.string(),
270+
params: z.record(z.string(), z.unknown()),
271+
}),
272+
}),
273+
actions: [
274+
{
275+
type: "click",
276+
actionParams: z.object({}),
277+
run: async () => ({ success: true, message: "ok" }),
278+
},
279+
{
280+
type: "type",
281+
actionParams: z.object({}),
282+
run: async () => ({ success: true, message: "ok" }),
283+
},
284+
],
285+
},
286+
[{ role: "user", content: "pick one action" }]
287+
);
288+
289+
expect(result.parsed).toEqual({
290+
thoughts: "select click",
291+
memory: "state",
292+
action: {
293+
type: "click",
294+
params: {},
295+
},
296+
});
297+
298+
const payload = createMessageMock.mock.calls[0]?.[0] as Record<
299+
string,
300+
unknown
301+
>;
302+
expect(payload?.tool_choice).toEqual({
303+
type: "any",
304+
disable_parallel_tool_use: true,
305+
});
306+
});
307+
245308
it("does not crash simple-tool debug logging on circular tool payloads", async () => {
246309
const circularTool: Record<string, unknown> = { name: "structured_output" };
247310
circularTool.self = circularTool;

0 commit comments

Comments
 (0)