From c3b3f571e9dbe6b8c6be98c9444276e1c4a2c076 Mon Sep 17 00:00:00 2001
From: Peter Steinberger
Date: Wed, 7 Jan 2026 17:54:19 +0000
Subject: [PATCH] fix(tools): finalize Vertex schema flattening (#409)
---
CHANGELOG.md | 1 +
README.md | 2 +-
src/agents/pi-tools.test.ts | 33 +++++++++++++++++++++++++++++
src/agents/tools/browser-tool.ts | 36 +++++++++++++++++++-------------
4 files changed, 56 insertions(+), 16 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea155db21..b7c1f0845 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,7 @@
- Sandbox: add `agent.sandbox.workspaceAccess` (`none`/`ro`/`rw`) to control agent workspace visibility inside the container; `ro` hard-disables `write`/`edit`.
- Routing: allow per-agent sandbox overrides (including `workspaceAccess` and `sandbox.tools`) plus per-agent tool policies in multi-agent configs. Thanks @pasogott for PR #380.
- Tools: add Telegram/WhatsApp reaction tools (with per-provider gating). Thanks @zats for PR #353.
+- Tools: flatten literal-union schemas for Claude on Vertex AI. Thanks @carlulsoe for PR #409.
- Tools: unify reaction removal semantics across Discord/Slack/Telegram/WhatsApp and allow WhatsApp reaction routing across accounts.
- Gateway/CLI: add daemon runtime selection (Node recommended; Bun optional) and document WhatsApp/Baileys Bun WebSocket instability on reconnect.
- CLI: add `clawdbot docs` live docs search with pretty output.
diff --git a/README.md b/README.md
index d15bd5680..a6623a632 100644
--- a/README.md
+++ b/README.md
@@ -454,5 +454,5 @@ Thanks to all clawtributors:
-
+
diff --git a/src/agents/pi-tools.test.ts b/src/agents/pi-tools.test.ts
index 566e85659..d805eff0c 100644
--- a/src/agents/pi-tools.test.ts
+++ b/src/agents/pi-tools.test.ts
@@ -31,6 +31,39 @@ describe("createClawdbotCodingTools", () => {
expect(parameters.required ?? []).toContain("action");
});
+ it("flattens anyOf-of-literals to enum for provider compatibility", () => {
+ const tools = createClawdbotCodingTools();
+ const browser = tools.find((tool) => tool.name === "browser");
+ expect(browser).toBeDefined();
+
+ const parameters = browser?.parameters as {
+ properties?: Record;
+ };
+ const action = parameters.properties?.action as
+ | {
+ type?: unknown;
+ enum?: unknown[];
+ anyOf?: unknown[];
+ }
+ | undefined;
+
+ expect(action?.type).toBe("string");
+ expect(action?.anyOf).toBeUndefined();
+ expect(Array.isArray(action?.enum)).toBe(true);
+ expect(action?.enum).toContain("act");
+
+ const format = parameters.properties?.format as
+ | {
+ type?: unknown;
+ enum?: unknown[];
+ anyOf?: unknown[];
+ }
+ | undefined;
+ expect(format?.type).toBe("string");
+ expect(format?.anyOf).toBeUndefined();
+ expect(format?.enum).toEqual(["aria", "ai"]);
+ });
+
it("preserves action enums in normalized schemas", () => {
const tools = createClawdbotCodingTools();
const toolNames = ["browser", "canvas", "nodes", "cron", "gateway"];
diff --git a/src/agents/tools/browser-tool.ts b/src/agents/tools/browser-tool.ts
index 12adc177a..6e997a1ae 100644
--- a/src/agents/tools/browser-tool.ts
+++ b/src/agents/tools/browser-tool.ts
@@ -28,25 +28,29 @@ import {
readStringParam,
} from "./common.js";
+const BROWSER_ACT_KINDS = [
+ "click",
+ "type",
+ "press",
+ "hover",
+ "drag",
+ "select",
+ "fill",
+ "resize",
+ "wait",
+ "evaluate",
+ "close",
+] as const;
+
+type BrowserActKind = (typeof BROWSER_ACT_KINDS)[number];
+
// NOTE: Using a flattened object schema instead of Type.Union([Type.Object(...), ...])
// because Claude API on Vertex AI rejects nested anyOf schemas as invalid JSON Schema.
// The discriminator (kind) determines which properties are relevant; runtime validates.
const BrowserActSchema = Type.Object({
- kind: Type.Unsafe({
+ kind: Type.Unsafe({
type: "string",
- enum: [
- "click",
- "type",
- "press",
- "hover",
- "drag",
- "select",
- "fill",
- "resize",
- "wait",
- "evaluate",
- "close",
- ],
+ enum: [...BROWSER_ACT_KINDS],
}),
// Common fields
targetId: Type.Optional(Type.String()),
@@ -67,7 +71,9 @@ const BrowserActSchema = Type.Object({
// select
values: Type.Optional(Type.Array(Type.String())),
// fill - use permissive array of objects
- fields: Type.Optional(Type.Array(Type.Object({}, { additionalProperties: true }))),
+ fields: Type.Optional(
+ Type.Array(Type.Object({}, { additionalProperties: true })),
+ ),
// resize
width: Type.Optional(Type.Number()),
height: Type.Optional(Type.Number()),