Screen interaction
Let the agent point at and act on UI elements — you implement capture, activate, and highlight; the server does the visual grounding.
Screen share lets the agent see; screen interaction lets it act: "click the export button", "highlight the field the user should fill in next". The division of labor is strict — your app implements four platform primitives, and the server does the hard part (grounding the model's natural-language target to a concrete element with a vision model).
The client side is a four-method contract:
interface ScreenInteraction {
capture(): Promise<ScreenCapture>;
activate(element: ScreenElement, capture: ScreenCapture, action: ScreenAction): Promise<boolean>;
highlightElement(
element: ScreenElement,
capture: ScreenCapture,
opts: { label: string; placement: ScreenPlacement; affordance: ScreenAffordance },
): Promise<boolean | ScreenHighlightResult>;
highlightRegion(
region: ScreenRegion,
opts: { element?: ScreenElementHint; label: string; placement: ScreenPlacement; affordance: ScreenAffordance },
): Promise<boolean | ScreenHighlightResult>;
}capture()returns aScreenCapture: a JPEG of the current screen plus a list ofScreenElements — indexed regions with arole, aframe(x, y, w, h), and optionaltitle/label/value. Build the element list from whatever your platform offers: the accessibility tree, your own component registry, or a DOM walk.activate(element, capture, action)performs a real interaction on one grounded element, from the same capture it was chosen in.highlightElement(element, capture, …)marks or points at a grounded element instead of acting — a learning spotlight, a coach-mark, a tour overlay. Theaffordancesays what gesture the mark should suggest (pointer,click,double_click,drag_show, …).highlightRegion(region, …)marks a region the caller located itself — no capture, no grounding round trip, so it draws immediately. Marking may skip the freshness check because a stale marker is harmless; a stale click is not, which is why acting always goes through a grounded capture.
Return true when done, false to decline (the model will try a different approach), and reject only for unexpected errors. The highlight methods may instead return a ScreenHighlightResult reporting whether the mark actually landed on a real control — the signal the caller needs to know when to re-target.
The flow
- The model decides to interact with the screen and calls the server's screen tool.
- The server calls your
capture()and receives the screenshot + element list. - A vision model grounds the request ("the blue Export button") to a specific
ScreenElement. - The server calls your
activate(...)orhighlightElement(...)with that element (orhighlightRegion(...)when it already knows the coordinates). - The result flows back to the model, which narrates or continues.
Your app never parses model output or does visual matching — you only ever execute a concrete element + action, which keeps the dangerous part (deciding what to touch) server-side and the trusted part (actually touching it) in code you wrote.
Opting in
Pass your implementation when building the client:
const client = new RealtimeClient({ token, screenInteraction: myScreenInteraction });Available in TypeScript today.
Safety
activate performs real actions in your app, so treat it like any other privileged tool surface:
- Only include elements in
capture()that the agent may legitimately touch — the element list is an allowlist by construction. Omit destructive controls entirely, or returnfalsefromactivatefor them. - Prefer
highlightElement+ a human click for anything irreversible. - Actions land in the tool event stream like every other dispatch, so your session timeline records what the agent touched and when.