fix(browser): describe supported browser tool actions (#111381)

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
FMLS
2026-08-26 17:29:01 +08:00
committed by GitHub
parent ea49e77bbc
commit ec0f112279
3 changed files with 95 additions and 11 deletions
+10
View File
@@ -223,6 +223,14 @@ describe("browser plugin", () => {
expect(tool.description).toContain("action=profiles");
expect(tool.description).not.toContain('profile="user"');
expect(tool.outputSchema).toBe(BrowserToolOutputSchema);
const properties = (
tool.parameters as {
properties: Record<string, { description?: string }>;
}
).properties;
expect(properties.actions?.description).toContain("batch");
expect(properties.doubleClick?.description).toContain("clickCoords");
expect(properties.labels?.description).toContain("snapshot");
expect(runtimeApiMocks.createBrowserTool).not.toHaveBeenCalled();
await tool.execute("call-1", { action: "status" });
expect(runtimeApiMocks.createBrowserTool).toHaveBeenCalledWith({
@@ -394,6 +402,8 @@ describe("browser plugin", () => {
expect(actions).not.toEqual(expect.arrayContaining(["pdf", "download", "waitfordownload"]));
expect(actions).toEqual(expect.arrayContaining(["snapshot", "screenshot"]));
expect(actKinds).not.toContain("batch");
expect((properties.actions as { description?: string }).description).toBeUndefined();
expect((properties.stopOnError as { description?: string }).description).toBeUndefined();
});
it("rejects malformed run bindings before creating the lazy browser tool", () => {
@@ -89,5 +89,49 @@ describe("browser tool schema", () => {
requireSchemaProperty(requestProperties, "kind", "browser request kind schema").enum,
).toContain("batch");
expect(requestProperties.actions).toBeDefined();
for (const name of ["kind", "actions", "stopOnError", "doubleClick", "ref"] as const) {
const flattened = requireSchemaProperty(properties, name, `browser ${name} schema`);
const nested = requireSchemaProperty(
requestProperties,
name,
`browser request ${name} schema`,
);
expect(flattened.description, name).toBeTruthy();
expect(nested.description, name).toBe(flattened.description);
}
expect(properties.actions?.description).toContain("batch");
expect(properties.stopOnError?.description).toContain("default");
expect(properties.doubleClick?.description).toContain("clickCoords");
expect(properties.ref?.description).toContain("snapshot");
expect(properties.profile?.description).toContain("default");
expect(properties.labels?.description).toContain("snapshot");
expect(properties.request?.description).toContain("act");
});
it("preserves batch fields without advertising them on unsupported profiles", () => {
const schema = createBrowserToolSchema(
resolveBrowserToolCapabilities({
profileCapabilities: {
supportsBatchActions: false,
supportsDownloads: false,
supportsPdf: false,
},
}),
);
const properties = schema.properties as BrowserSchemaRecord;
const requestProperties = requireSchemaProperty(properties, "request", "browser request schema")
.properties as BrowserSchemaRecord;
for (const scopedProperties of [properties, requestProperties]) {
expect(scopedProperties.kind?.enum).not.toContain("batch");
expect(scopedProperties.kind?.description).not.toContain("batch");
expect(scopedProperties.actions).toBeDefined();
expect(scopedProperties.stopOnError).toBeDefined();
expect(scopedProperties.actions?.description).toBeUndefined();
expect(scopedProperties.stopOnError?.description).toBeUndefined();
}
});
});
+41 -11
View File
@@ -104,15 +104,29 @@ export function resolveBrowserToolCapabilities(params?: {
}
function createBrowserActProperties(capabilities: BrowserToolCapabilities) {
const supportsBatch = capabilities.actKinds.includes("batch");
return {
// Common fields
targetId: Type.Optional(Type.String({ description: TAB_REFERENCE_DESCRIPTION })),
ref: Type.Optional(Type.String()),
ref: Type.Optional(
Type.String({ description: "Element reference from a current-tab snapshot." }),
),
// 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()),
actions: Type.Optional(
Type.Array(
Type.Object({}, { additionalProperties: true }),
supportsBatch ? { description: "Nested act requests for kind=batch." } : {},
),
),
stopOnError: Type.Optional(
Type.Boolean(
supportsBatch ? { description: "Stop batch at first error (default: true)." } : {},
),
),
// click
doubleClick: Type.Optional(Type.Boolean()),
doubleClick: Type.Optional(
Type.Boolean({ description: "Double-click for kind=click or clickCoords." }),
),
button: Type.Optional(Type.String()),
modifiers: Type.Optional(Type.Array(Type.String())),
x: optionalFiniteNumberSchema(),
@@ -152,15 +166,27 @@ function createBrowserActProperties(capabilities: BrowserToolCapabilities) {
/** Provider-compatible Browser tool argument schema. */
export function createBrowserToolSchema(capabilities: BrowserToolCapabilities) {
const actProperties = createBrowserActProperties(capabilities);
const BrowserActSchema = Type.Object({
kind: stringEnum(capabilities.actKinds),
...actProperties,
});
const actKindDescription = capabilities.actKinds.includes("batch")
? "Browser action kind; batch runs the nested actions array."
: "Browser action kind.";
const BrowserActSchema = Type.Object(
{
kind: stringEnum(capabilities.actKinds, { description: actKindDescription }),
...actProperties,
},
{ description: "Preferred nested request for action=act." },
);
return Type.Object({
action: stringEnum(capabilities.actions),
target: optionalStringEnum(BROWSER_TARGETS),
node: Type.Optional(Type.String()),
profile: Type.Optional(Type.String()),
profile: Type.Optional(
Type.String({
description: capabilities.tabBound
? "Bound browser profile selected for this run."
: "Browser profile name; omit to use the configured default.",
}),
),
browser: Type.Optional(Type.String()),
systemProfile: Type.Optional(Type.String()),
into: Type.Optional(Type.String()),
@@ -176,7 +202,11 @@ export function createBrowserToolSchema(capabilities: BrowserToolCapabilities) {
compact: Type.Optional(Type.Boolean()),
depth: optionalNonNegativeIntegerSchema(),
frame: Type.Optional(Type.String()),
labels: Type.Optional(Type.Boolean()),
labels: Type.Optional(
Type.Boolean({
description: "Overlay snapshot or screenshot with element reference labels.",
}),
),
urls: Type.Optional(Type.Boolean()),
fullPage: Type.Optional(Type.Boolean()),
path: Type.Optional(Type.String()),
@@ -189,7 +219,7 @@ export function createBrowserToolSchema(capabilities: BrowserToolCapabilities) {
accept: Type.Optional(Type.Boolean()),
promptText: Type.Optional(Type.String()),
// Legacy flattened act params (preferred: request={...})
kind: Type.Optional(stringEnum(capabilities.actKinds)),
kind: Type.Optional(stringEnum(capabilities.actKinds, { description: actKindDescription })),
...actProperties,
request: Type.Optional(BrowserActSchema),
});