From a51948a528bb9735564fcf09b712eef94aae935b Mon Sep 17 00:00:00 2001 From: "Eugene (via Claudius)" Date: Fri, 9 Jan 2026 10:41:49 +0000 Subject: [PATCH] fix(tools): flatten gateway tool schema for Vertex AI compatibility Claude API on Vertex AI (Cloud Code Assist / Antigravity) enforces strict JSON Schema 2020-12 validation and rejects root-level anyOf without a top-level type field. TypeBox Type.Union compiles to { anyOf: [...] } which Anthropic's direct API accepts but Vertex rejects with: tools.11.custom.input_schema: JSON schema is invalid This follows the same pattern used in browser-tool.ts which has the same fix with an explanatory comment. Flatten the schema to Type.Object with an action enum, matching how browser tool handles this constraint. --- src/agents/tools/gateway-tool.ts | 67 ++++++++++++++------------------ 1 file changed, 30 insertions(+), 37 deletions(-) diff --git a/src/agents/tools/gateway-tool.ts b/src/agents/tools/gateway-tool.ts index 83d0571c5..337ed564f 100644 --- a/src/agents/tools/gateway-tool.ts +++ b/src/agents/tools/gateway-tool.ts @@ -5,44 +5,37 @@ import { scheduleGatewaySigusr1Restart } from "../../infra/restart.js"; import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js"; import { callGatewayTool } from "./gateway.js"; -const GatewayToolSchema = Type.Union([ - Type.Object({ - action: Type.Literal("restart"), - delayMs: Type.Optional(Type.Number()), - reason: Type.Optional(Type.String()), +const GATEWAY_ACTIONS = [ + "restart", + "config.get", + "config.schema", + "config.apply", + "update.run", +] as const; + +type GatewayAction = (typeof GATEWAY_ACTIONS)[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 (action) determines which properties are relevant; runtime validates. +const GatewayToolSchema = Type.Object({ + action: Type.Unsafe({ + type: "string", + enum: [...GATEWAY_ACTIONS], }), - Type.Object({ - action: Type.Literal("config.get"), - gatewayUrl: Type.Optional(Type.String()), - gatewayToken: Type.Optional(Type.String()), - timeoutMs: Type.Optional(Type.Number()), - }), - Type.Object({ - action: Type.Literal("config.schema"), - gatewayUrl: Type.Optional(Type.String()), - gatewayToken: Type.Optional(Type.String()), - timeoutMs: Type.Optional(Type.Number()), - }), - Type.Object({ - action: Type.Literal("config.apply"), - raw: Type.String(), - sessionKey: Type.Optional(Type.String()), - note: Type.Optional(Type.String()), - restartDelayMs: Type.Optional(Type.Number()), - gatewayUrl: Type.Optional(Type.String()), - gatewayToken: Type.Optional(Type.String()), - timeoutMs: Type.Optional(Type.Number()), - }), - Type.Object({ - action: Type.Literal("update.run"), - sessionKey: Type.Optional(Type.String()), - note: Type.Optional(Type.String()), - restartDelayMs: Type.Optional(Type.Number()), - timeoutMs: Type.Optional(Type.Number()), - gatewayUrl: Type.Optional(Type.String()), - gatewayToken: Type.Optional(Type.String()), - }), -]); + // restart + delayMs: Type.Optional(Type.Number()), + reason: Type.Optional(Type.String()), + // config.get, config.schema, config.apply, update.run + gatewayUrl: Type.Optional(Type.String()), + gatewayToken: Type.Optional(Type.String()), + timeoutMs: Type.Optional(Type.Number()), + // config.apply, update.run + raw: Type.Optional(Type.String()), + sessionKey: Type.Optional(Type.String()), + note: Type.Optional(Type.String()), + restartDelayMs: Type.Optional(Type.Number()), +}); export function createGatewayTool(opts?: { agentSessionKey?: string;