/** * JSON schema for the Browser agent tool. * * The schema stays intentionally flat because provider function-tool validators * reject several nested union shapes that TypeBox can otherwise emit. */ import { optionalFiniteNumberSchema, optionalNonNegativeIntegerSchema, optionalPositiveIntegerSchema, optionalStringEnum, stringEnum, } from "openclaw/plugin-sdk/channel-actions"; import { Type } from "typebox"; import { ACT_MAX_VIEWPORT_DIMENSION } from "./browser/act-policy.js"; const BROWSER_ACT_KINDS = [ "batch", "click", "clickCoords", "type", "press", "hover", "scrollIntoView", "drag", "select", "fill", "resize", "wait", "evaluate", "close", ] as const; const BROWSER_TOOL_ACTIONS = [ "doctor", "status", "start", "stop", "profiles", "importprofile", "tabs", "open", "focus", "close", "snapshot", "screenshot", "navigate", "console", "pdf", "download", "waitfordownload", "upload", "dialog", "act", ] as const; const BROWSER_TARGETS = ["sandbox", "host", "node"] as const; const BROWSER_SNAPSHOT_FORMATS = ["aria", "ai"] as const; const BROWSER_SNAPSHOT_MODES = ["efficient"] as const; const BROWSER_SNAPSHOT_REFS = ["role", "aria"] as const; const BROWSER_IMAGE_TYPES = ["png", "jpeg"] as const; const TAB_REFERENCE_DESCRIPTION = "Tab reference. Prefer suggestedTargetId, tabId, or label from tabs output; raw CDP targetId and unique raw prefixes remain supported for compatibility."; // 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: stringEnum(BROWSER_ACT_KINDS), // Common fields targetId: Type.Optional(Type.String({ description: TAB_REFERENCE_DESCRIPTION })), ref: Type.Optional(Type.String()), // batch - permissive children keep the provider schema flat; runtime validates each action. actions: Type.Optional(Type.Array(Type.Object({}, { additionalProperties: true }))), stopOnError: Type.Optional(Type.Boolean()), // click doubleClick: Type.Optional(Type.Boolean()), button: Type.Optional(Type.String()), modifiers: Type.Optional(Type.Array(Type.String())), x: optionalFiniteNumberSchema(), y: optionalFiniteNumberSchema(), // type text: Type.Optional(Type.String()), submit: Type.Optional(Type.Boolean()), slowly: Type.Optional(Type.Boolean()), // press key: Type.Optional(Type.String()), delayMs: optionalNonNegativeIntegerSchema(), // drag startRef: Type.Optional(Type.String()), endRef: Type.Optional(Type.String()), // select values: Type.Optional(Type.Array(Type.String())), // fill - use permissive array of objects fields: Type.Optional(Type.Array(Type.Object({}, { additionalProperties: true }))), // resize width: optionalPositiveIntegerSchema({ maximum: ACT_MAX_VIEWPORT_DIMENSION }), height: optionalPositiveIntegerSchema({ maximum: ACT_MAX_VIEWPORT_DIMENSION }), // wait timeMs: optionalNonNegativeIntegerSchema(), selector: Type.Optional(Type.String()), url: Type.Optional(Type.String()), loadState: Type.Optional(Type.String()), textGone: Type.Optional(Type.String()), timeoutMs: optionalPositiveIntegerSchema(), // evaluate fn: Type.Optional(Type.String()), }); // IMPORTANT: OpenAI function tool schemas must have a top-level `type: "object"`. // A root-level `Type.Union([...])` compiles to `{ anyOf: [...] }` (no `type`), // which OpenAI rejects ("Invalid schema ... type: None"). Keep this schema an object. /** Provider-compatible Browser tool argument schema. */ export const BrowserToolSchema = Type.Object({ action: stringEnum(BROWSER_TOOL_ACTIONS), target: optionalStringEnum(BROWSER_TARGETS), node: Type.Optional(Type.String()), profile: Type.Optional(Type.String()), browser: Type.Optional(Type.String()), systemProfile: Type.Optional(Type.String()), into: Type.Optional(Type.String()), domains: Type.Optional(Type.Array(Type.String())), targetUrl: Type.Optional(Type.String()), url: Type.Optional(Type.String()), targetId: Type.Optional(Type.String({ description: TAB_REFERENCE_DESCRIPTION })), label: Type.Optional(Type.String()), limit: optionalPositiveIntegerSchema(), maxChars: optionalNonNegativeIntegerSchema(), mode: optionalStringEnum(BROWSER_SNAPSHOT_MODES), snapshotFormat: optionalStringEnum(BROWSER_SNAPSHOT_FORMATS), refs: optionalStringEnum(BROWSER_SNAPSHOT_REFS), interactive: Type.Optional(Type.Boolean()), compact: Type.Optional(Type.Boolean()), depth: optionalNonNegativeIntegerSchema(), selector: Type.Optional(Type.String()), frame: Type.Optional(Type.String()), labels: Type.Optional(Type.Boolean()), urls: Type.Optional(Type.Boolean()), fullPage: Type.Optional(Type.Boolean()), ref: Type.Optional(Type.String()), path: Type.Optional(Type.String()), element: Type.Optional(Type.String()), type: optionalStringEnum(BROWSER_IMAGE_TYPES), level: Type.Optional(Type.String()), paths: Type.Optional(Type.Array(Type.String())), inputRef: Type.Optional(Type.String()), timeoutMs: optionalPositiveIntegerSchema(), dialogId: Type.Optional(Type.String()), accept: Type.Optional(Type.Boolean()), promptText: Type.Optional(Type.String()), // Legacy flattened act params (preferred: request={...}) kind: Type.Optional(stringEnum(BROWSER_ACT_KINDS)), actions: Type.Optional(Type.Array(Type.Object({}, { additionalProperties: true }))), stopOnError: Type.Optional(Type.Boolean()), doubleClick: Type.Optional(Type.Boolean()), button: Type.Optional(Type.String()), modifiers: Type.Optional(Type.Array(Type.String())), x: optionalFiniteNumberSchema(), y: optionalFiniteNumberSchema(), text: Type.Optional(Type.String()), submit: Type.Optional(Type.Boolean()), slowly: Type.Optional(Type.Boolean()), key: Type.Optional(Type.String()), delayMs: optionalNonNegativeIntegerSchema(), startRef: Type.Optional(Type.String()), endRef: Type.Optional(Type.String()), values: Type.Optional(Type.Array(Type.String())), fields: Type.Optional(Type.Array(Type.Object({}, { additionalProperties: true }))), width: optionalPositiveIntegerSchema({ maximum: ACT_MAX_VIEWPORT_DIMENSION }), height: optionalPositiveIntegerSchema({ maximum: ACT_MAX_VIEWPORT_DIMENSION }), timeMs: optionalNonNegativeIntegerSchema(), textGone: Type.Optional(Type.String()), loadState: Type.Optional(Type.String()), fn: Type.Optional(Type.String()), request: Type.Optional(BrowserActSchema), });