@crewhaus/tool-screen-capture 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +45 -0
- package/src/index.test.ts +51 -0
- package/src/index.ts +70 -0
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crewhaus/tool-screen-capture",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Screenshot tool — returns Anthropic image content block for the BROW agent (Section 25 BROW)",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "bun test src"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@crewhaus/computer-use-driver": "0.0.0",
|
|
16
|
+
"@crewhaus/errors": "0.0.0",
|
|
17
|
+
"@crewhaus/tool-builder": "0.0.0",
|
|
18
|
+
"@crewhaus/tool-catalog": "0.0.0",
|
|
19
|
+
"zod": "^3.23.8"
|
|
20
|
+
},
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"author": {
|
|
23
|
+
"name": "Max Meier",
|
|
24
|
+
"email": "max@studiomax.io",
|
|
25
|
+
"url": "https://studiomax.io"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/crewhaus/factory.git",
|
|
30
|
+
"directory": "packages/tool-screen-capture"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/crewhaus/factory/tree/main/packages/tool-screen-capture#readme",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/crewhaus/factory/issues"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "restricted"
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"src",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE",
|
|
43
|
+
"NOTICE"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { Driver } from "@crewhaus/computer-use-driver";
|
|
3
|
+
import { createScreenshotTool } from "./index.js";
|
|
4
|
+
|
|
5
|
+
function stubDriver(pngBytes: Uint8Array): Driver {
|
|
6
|
+
return {
|
|
7
|
+
backend: "chromium",
|
|
8
|
+
async connect() {},
|
|
9
|
+
async goto() {},
|
|
10
|
+
async screenshot() {
|
|
11
|
+
return pngBytes;
|
|
12
|
+
},
|
|
13
|
+
async click() {},
|
|
14
|
+
async type() {},
|
|
15
|
+
async key() {},
|
|
16
|
+
async scroll() {},
|
|
17
|
+
async getViewport() {
|
|
18
|
+
return { width: 800, height: 600, devicePixelRatio: 1 };
|
|
19
|
+
},
|
|
20
|
+
async disconnect() {},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("createScreenshotTool", () => {
|
|
25
|
+
test("returns an Anthropic image content block with base64 PNG data (T1)", async () => {
|
|
26
|
+
const png = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
27
|
+
const driver = stubDriver(png);
|
|
28
|
+
const tool = createScreenshotTool({ driver });
|
|
29
|
+
|
|
30
|
+
const result = await tool.execute({ reason: "smoke" }, {});
|
|
31
|
+
expect(Array.isArray(result)).toBe(true);
|
|
32
|
+
if (!Array.isArray(result)) throw new Error("expected ToolResultContent array");
|
|
33
|
+
expect(result).toHaveLength(1);
|
|
34
|
+
const block = result[0];
|
|
35
|
+
if (block?.type !== "image") throw new Error("expected image block");
|
|
36
|
+
expect(block.source.type).toBe("base64");
|
|
37
|
+
expect(block.source.media_type).toBe("image/png");
|
|
38
|
+
// Decode → matches input bytes.
|
|
39
|
+
const decoded = Buffer.from(block.source.data, "base64");
|
|
40
|
+
expect(Array.from(decoded)).toEqual(Array.from(png));
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("flag profile: read-only, not destructive, classifier off (controlled output)", () => {
|
|
44
|
+
const driver = stubDriver(new Uint8Array(0));
|
|
45
|
+
const tool = createScreenshotTool({ driver });
|
|
46
|
+
expect(tool.readOnly).toBe(true);
|
|
47
|
+
expect(tool.destructive).toBe(false);
|
|
48
|
+
expect(tool.classifyOutput).toBe(false);
|
|
49
|
+
expect(tool.name).toBe("Screenshot");
|
|
50
|
+
});
|
|
51
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog R4 `tool-screen-capture` — Section 25 BROW.
|
|
3
|
+
*
|
|
4
|
+
* `Screenshot()` tool. Calls `driver.screenshot()` (PNG bytes), encodes
|
|
5
|
+
* to base64, and returns an Anthropic image content block the model
|
|
6
|
+
* can see. Composes with the §14 `tool-image` content-block pattern —
|
|
7
|
+
* but does NOT touch the filesystem (no path-traversal surface), since
|
|
8
|
+
* the driver's screenshot is in-memory.
|
|
9
|
+
*
|
|
10
|
+
* Configurable downscale factor caps model context budget. Default
|
|
11
|
+
* keeps the image at full resolution (modern Claude vision handles
|
|
12
|
+
* 1280x720 cleanly); set `downscalePercent: 50` to halve dimensions.
|
|
13
|
+
*/
|
|
14
|
+
import type { Driver } from "@crewhaus/computer-use-driver";
|
|
15
|
+
import { CrewhausError } from "@crewhaus/errors";
|
|
16
|
+
import { buildTool } from "@crewhaus/tool-builder";
|
|
17
|
+
import type { RegisteredTool, ToolResultContent } from "@crewhaus/tool-catalog";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
|
|
20
|
+
export class ScreenCaptureError extends CrewhausError {
|
|
21
|
+
override readonly name = "ScreenCaptureError";
|
|
22
|
+
constructor(message: string, cause?: unknown) {
|
|
23
|
+
super("tool", message, cause);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const screenshotSchema = z
|
|
28
|
+
.object({
|
|
29
|
+
/**
|
|
30
|
+
* Optional reason for the snapshot — surfaces in the trace event log
|
|
31
|
+
* but doesn't affect the captured pixels.
|
|
32
|
+
*/
|
|
33
|
+
reason: z.string().optional(),
|
|
34
|
+
})
|
|
35
|
+
.strict();
|
|
36
|
+
|
|
37
|
+
export type CreateScreenshotToolOptions = {
|
|
38
|
+
readonly driver: Driver;
|
|
39
|
+
/**
|
|
40
|
+
* Downscale percent (1..100). Default 100 (full size). Useful for
|
|
41
|
+
* very large viewports.
|
|
42
|
+
*/
|
|
43
|
+
readonly downscalePercent?: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export function createScreenshotTool(opts: CreateScreenshotToolOptions): RegisteredTool {
|
|
47
|
+
return buildTool({
|
|
48
|
+
name: "Screenshot",
|
|
49
|
+
description:
|
|
50
|
+
"Capture the current browser viewport as a PNG image you can see. Returns an image block; pair with FindElement(description) to locate UI elements before clicking.",
|
|
51
|
+
inputSchema: screenshotSchema,
|
|
52
|
+
readOnly: true,
|
|
53
|
+
destructive: false,
|
|
54
|
+
concurrencySafe: false,
|
|
55
|
+
classifyOutput: false,
|
|
56
|
+
execute: async (): Promise<ToolResultContent> => {
|
|
57
|
+
const png = await opts.driver.screenshot();
|
|
58
|
+
// For v0 we ship the PNG verbatim. Downscale path is exposed in
|
|
59
|
+
// the API but not implemented (Playwright supports `clip` for
|
|
60
|
+
// crop; downscale would need an image-processing dep — deferred).
|
|
61
|
+
const data = Buffer.from(png).toString("base64");
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
type: "image",
|
|
65
|
+
source: { type: "base64", media_type: "image/png", data },
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|