Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/server/src/mcp/toolkits/preview/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
PreviewAutomationRecordingArtifact,
PreviewAutomationRecordingStatus,
PreviewAutomationResizeResult,
PreviewAutomationScreenshotArtifact,
PreviewAutomationSetColorSchemeResult,
PreviewAutomationSnapshot,
PreviewAutomationStatus,
Expand Down Expand Up @@ -85,6 +86,8 @@ const handlers = {
invokeTargeted<PreviewAutomationRecordingStatus>("recordingStart", input ?? {}),
preview_recording_stop: (input) =>
invokeTargeted<PreviewAutomationRecordingArtifact>("recordingStop", input ?? {}),
preview_screenshot: (input) =>
invokeTargeted<PreviewAutomationScreenshotArtifact>("screenshot", input ?? {}),
} satisfies Parameters<typeof PreviewToolkit.toLayer>[0];

const { preview_snapshot, ...standardHandlers } = handlers;
Expand Down
14 changes: 14 additions & 0 deletions apps/server/src/mcp/toolkits/preview/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
PreviewAutomationRecordingStatus,
PreviewAutomationResizeInput,
PreviewAutomationResizeResult,
PreviewAutomationScreenshotArtifact,
PreviewAutomationScrollInput,
PreviewAutomationSetColorSchemeInput,
PreviewAutomationSetColorSchemeResult,
Expand Down Expand Up @@ -200,6 +201,17 @@ export const PreviewRecordingStopTool = safeBrowserTool(
}).annotate(Tool.Title, "Stop browser recording"),
);

export const PreviewScreenshotTool = safeBrowserTool(
Tool.make("preview_screenshot", {
description:
"Save a full-resolution PNG of the collaborative browser tab selected by tabId, or this agent session's current tab when omitted, as a local evidence artifact and return its path. Use this to hand the user a still image; use preview_snapshot instead to look at the page yourself.",
parameters: PreviewAutomationTabTargetInput,
success: PreviewAutomationScreenshotArtifact,
failure: PreviewAutomationError,
dependencies,
}).annotate(Tool.Title, "Save browser screenshot"),
);

export const PreviewToolkit = Toolkit.make(
PreviewStatusTool,
PreviewOpenTool,
Expand All @@ -215,6 +227,7 @@ export const PreviewToolkit = Toolkit.make(
PreviewWaitForTool,
PreviewRecordingStartTool,
PreviewRecordingStopTool,
PreviewScreenshotTool,
);

export const PreviewStandardToolkit = Toolkit.make(
Expand All @@ -231,6 +244,7 @@ export const PreviewStandardToolkit = Toolkit.make(
PreviewWaitForTool,
PreviewRecordingStartTool,
PreviewRecordingStopTool,
PreviewScreenshotTool,
);

export const PreviewSnapshotToolkit = Toolkit.make(PreviewSnapshotTool);
7 changes: 7 additions & 0 deletions apps/web/src/components/preview/PreviewAutomationHosts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,13 @@ function PreviewAutomationHost(props: { readonly environmentId: EnvironmentId })
}
return { ...artifact, tabId: stopTabId };
}
case "screenshot": {
const ready = await requireReadyTab();
const artifact = await ready.bridge.captureScreenshot(ready.runtimeTabId);
// The desktop artifact carries the runtime tab id; callers address
// tabs by their server id.
return { ...artifact, tabId: ready.tabId };
}
}
} catch (cause) {
throw PreviewAutomationOperationError.fromCause({
Expand Down
11 changes: 11 additions & 0 deletions packages/contracts/src/previewAutomation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const PREVIEW_AUTOMATION_OPERATIONS = [
...PREVIEW_AUTOMATION_V1_OPERATIONS,
"resize",
"setColorScheme",
"screenshot",
] as const;

export const PreviewAutomationOperation = Schema.Literals(PREVIEW_AUTOMATION_OPERATIONS);
Expand Down Expand Up @@ -563,6 +564,16 @@ export const PreviewAutomationRecordingArtifact = Schema.Struct({
});
export type PreviewAutomationRecordingArtifact = typeof PreviewAutomationRecordingArtifact.Type;

export const PreviewAutomationScreenshotArtifact = Schema.Struct({
id: Schema.String,
tabId: PreviewTabId,
path: Schema.String,
mimeType: Schema.Literal("image/png"),
sizeBytes: Schema.Int,
createdAt: Schema.String,
});
export type PreviewAutomationScreenshotArtifact = typeof PreviewAutomationScreenshotArtifact.Type;

export const PreviewAutomationClientId = TrimmedNonEmptyString.check(Schema.isMaxLength(128));
export type PreviewAutomationClientId = typeof PreviewAutomationClientId.Type;
export const PreviewAutomationConnectionId = TrimmedNonEmptyString.check(Schema.isMaxLength(64));
Expand Down
Loading