import { StringEnum, Type, type Static } from "@earendil-works/pi-ai"; const heldButtons = Type.Array(StringEnum(["left", "middle", "right"] as const), { description: "Buttons held for this action", }); export const parameters = Type.Object( { actions: Type.Array( Type.Union([ Type.Object( { action: StringEnum(["pointer"] as const), x: Type.Integer({ minimum: 0, maximum: 1919, description: "Absolute screen x-coordinate: 0 is left, 1919 is right", }), y: Type.Integer({ minimum: 0, maximum: 1079, description: "Absolute screen y-coordinate: 0 is top, 1079 is bottom", }), buttons: Type.Optional(heldButtons), }, { additionalProperties: false }, ), Type.Object( { action: StringEnum(["scroll"] as const), x: Type.Integer({ minimum: 0, maximum: 1919, description: "Absolute screen x-coordinate: 0 is left, 1919 is right", }), y: Type.Integer({ minimum: 0, maximum: 1079, description: "Absolute screen y-coordinate: 0 is top, 1079 is bottom", }), direction: StringEnum(["up", "down", "left", "right"] as const, { description: "Wheel direction", }), buttons: Type.Optional(heldButtons), ticks: Type.Optional( Type.Integer({ minimum: 1, default: 1, description: "Wheel notches; omit for one", }), ), }, { additionalProperties: false }, ), Type.Object( { action: StringEnum(["type"] as const), text: Type.String({ description: "Literal text to type; use a key action for named keys and shortcuts. On Wayland, characters missing from the remote keymap are dropped, not transliterated. Windows supports Unicode independently of the keyboard layout", }), }, { additionalProperties: false }, ), Type.Object( { action: StringEnum(["key"] as const), key: Type.String({ description: "One XKB-style key name, such as Return, Escape, Tab, BackSpace, Delete, Left, Page_Down, F5, or a. Put shortcut modifiers in modifiers; do not put them in key", pattern: "^[A-Za-z0-9_]+$", }), modifiers: Type.Optional( Type.Array(StringEnum(["shift", "ctrl", "alt", "logo"] as const), { description: "Modifiers held while pressing the key", uniqueItems: true, }), ), }, { additionalProperties: false }, ), Type.Object( { action: StringEnum(["new_browser_tab"] as const), url: Type.String({ description: "HTTP or HTTPS URL to open in a new Firefox tab every time on Wayland, or in the default browser on Windows (which controls tab reuse); prefer web_fetch for ordinary page reading", pattern: "^https?://", }), }, { additionalProperties: false }, ), Type.Object( { action: StringEnum(["recall"] as const), id: Type.String({ description: "ID from an earlier screenshot result", pattern: "^sc_[0-9a-f]{16}$", }), }, { additionalProperties: false }, ), Type.Object( { action: StringEnum(["screenshot"] as const) }, { additionalProperties: false }, ), Type.Object( { action: StringEnum(["sleep"] as const), ms: Type.Integer({ minimum: 0, maximum: 30_000, description: "Time to wait, in milliseconds", }), }, { additionalProperties: false }, ), ]), { minItems: 1, maxItems: 20 }, ), }, { additionalProperties: false }, ); export type Action = Static["actions"][number]; export type DesktopAction = Exclude; export interface DesktopBackend { execute(action: DesktopAction, signal?: AbortSignal): Promise; close(): Promise; } export function checkSignal(signal?: AbortSignal): void { if (signal?.aborted) throw new Error("Operation aborted"); } export function wait(ms: number, signal?: AbortSignal): Promise { return new Promise((resolve, reject) => { if (signal?.aborted) { reject(new Error("Operation aborted")); return; } const timer = setTimeout(() => { signal?.removeEventListener("abort", abort); resolve(); }, ms); const abort = () => { clearTimeout(timer); reject(new Error("Operation aborted")); }; signal?.addEventListener("abort", abort, { once: true }); }); }