import { Schema } from "effect";
import { EnvironmentId, ThreadId, TrimmedNonEmptyString } from "./baseSchemas.ts";
import {
PREVIEW_VIEWPORT_MAX_AREA,
PreviewRenderedViewportSize,
PreviewTabId,
PreviewViewportPresetId,
PreviewViewportSetting,
PreviewViewportSize,
} from "./preview.ts";
import { ProviderInstanceId } from "./providerInstance.ts";
import { BrowserProfileId } from "./browserProfile.ts";
const BoundedUrl = Schema.String.check(Schema.isTrimmed())
.check(Schema.isNonEmpty())
.check(Schema.isMaxLength(2048));
const URL_GUIDANCE = "Absolute http(s) URL, or a bare host (https; http for loopback).";
const PROFILE_GUIDANCE =
"Profile id or name from preview_status, for a new tab. Default: the user's. A tab keeps its profile.";
const OptionalTimeoutMs = Schema.optional(
Schema.Int.check(Schema.isGreaterThan(0)).check(Schema.isLessThanOrEqualTo(60_000)),
).annotate({ description: "Default 15000." });
export const PREVIEW_AUTOMATION_V1_OPERATIONS = [
"status",
"open",
"navigate",
"snapshot",
"click",
"type",
"press",
"scroll",
"evaluate",
"waitFor",
"recordingStart",
"recordingStop",
] as const;
const PREVIEW_AUTOMATION_OPERATIONS = [
...PREVIEW_AUTOMATION_V1_OPERATIONS,
"resize",
"setColorScheme",
] as const;
export const PREVIEW_AUTOMATION_SERVER_OPERATIONS = [
...PREVIEW_AUTOMATION_OPERATIONS,
"dialog",
"close",
"upload",
"hover",
"select",
"drag",
] as const;
export const PreviewAutomationOperation = Schema.Literals(PREVIEW_AUTOMATION_SERVER_OPERATIONS);
export type PreviewAutomationOperation = typeof PreviewAutomationOperation.Type;
const PreviewAutomationTabTargetFields = {
tabId: Schema.optional(PreviewTabId),
};
export const PreviewAutomationTabTargetInput = Schema.Struct(PreviewAutomationTabTargetFields);
export type PreviewAutomationTabTargetInput = typeof PreviewAutomationTabTargetInput.Type;
export const PreviewAutomationTabOwner = Schema.Literals(["agent", "human", "unclaimed"]);
export const PreviewAutomationProfile = Schema.Struct({
id: BrowserProfileId,
name: Schema.String,
incognito: Schema.optional(Schema.Boolean),
});
export type PreviewAutomationProfile = typeof PreviewAutomationProfile.Type;
export const PreviewAutomationStatus = Schema.Struct({
available: Schema.Boolean,
visible: Schema.Boolean,
tabId: Schema.NullOr(PreviewTabId),
url: Schema.NullOr(Schema.String),
title: Schema.NullOr(Schema.String),
loading: Schema.Boolean,
control: Schema.optional(
Schema.Struct({
owner: PreviewAutomationTabOwner,
ownedByCaller: Schema.Boolean,
generation: Schema.Number,
}),
),
dialog: Schema.optional(
Schema.NullOr(
Schema.Struct({
type: Schema.String,
message: Schema.String,
defaultValue: Schema.String,
}),
),
),
viewportSetting: Schema.optional(PreviewViewportSetting),
viewport: Schema.optional(PreviewRenderedViewportSize),
fileChooser: Schema.optional(
Schema.NullOr(Schema.Struct({ multiple: Schema.Boolean, accept: Schema.String })),
),
downloads: Schema.optional(
Schema.Array(
Schema.Struct({
fileName: Schema.String,
path: Schema.String,
sizeBytes: Schema.Number,
url: Schema.String,
completedAt: Schema.String,
}),
),
),
/** Server hosts: all thread tabs are readable; actions require ownedByCaller or an unclaimed tab without human control. */
tabs: Schema.optional(
Schema.Array(
Schema.Struct({
tabId: PreviewTabId,
url: Schema.NullOr(Schema.String),
openerTabId: Schema.optional(PreviewTabId),
owner: Schema.optional(PreviewAutomationTabOwner),
ownedByCaller: Schema.optional(Schema.Boolean),
visible: Schema.optional(Schema.Boolean),
profileId: Schema.optional(BrowserProfileId),
}),
),
),
profiles: Schema.optional(Schema.Array(PreviewAutomationProfile)),
defaultProfileId: Schema.optional(BrowserProfileId),
});
export type PreviewAutomationStatus = typeof PreviewAutomationStatus.Type;
export const PreviewAutomationDialogInput = Schema.Struct({
...PreviewAutomationTabTargetFields,
accept: Schema.Boolean,
promptText: Schema.optional(Schema.String),
});
export type PreviewAutomationDialogInput = typeof PreviewAutomationDialogInput.Type;
export const PreviewAutomationOpenInput = Schema.Struct({
...PreviewAutomationTabTargetFields,
url: Schema.optional(BoundedUrl).annotate({
description: `First page URL. ${URL_GUIDANCE} Omit to open a blank tab.`,
}),
open: Schema.optional(
Schema.Boolean.annotate({
description: "Show the preview to the user. Default true. False for background only.",
}),
),
show: Schema.optional(
Schema.Boolean.annotate({
description: "Old alias for `open`.",
}),
),
reuseExistingTab: Schema.optional(
Schema.Boolean.annotate({
description: "Reuse `tabId`, or your current tab. Default true. False makes a new tab.",
}),
),
profileId: Schema.optional(TrimmedNonEmptyString.check(Schema.isMaxLength(64))).annotate({
description: PROFILE_GUIDANCE,
}),
}).check(
Schema.makeFilter(
(input) =>
!(input.tabId !== undefined && input.reuseExistingTab === false) ||
"tabId cannot be combined with reuseExistingTab=false.",
),
);
export type PreviewAutomationOpenInput = typeof PreviewAutomationOpenInput.Type;
export const BrowserNavigationTarget = Schema.Union([
Schema.Struct({
kind: Schema.Literal("url"),
url: BoundedUrl.annotate({
description: `Website URL. ${URL_GUIDANCE}`,
}),
}),
Schema.Struct({
kind: Schema.Literal("environment-port"),
port: Schema.Int.check(Schema.isGreaterThan(0))
.check(Schema.isLessThan(65_536))
.annotate({ description: "Dev-server port here." }),
protocol: Schema.optional(
Schema.Literals(["http", "https"]).annotate({
description: "Default http.",
}),
),
path: Schema.optional(
Schema.String.annotate({
description: "Path, query, and fragment, such as /settings?tab=account.",
}),
),
}),
]);
export type BrowserNavigationTarget = typeof BrowserNavigationTarget.Type;
export const PreviewAutomationNavigateInput = Schema.Struct({
...PreviewAutomationTabTargetFields,
url: Schema.optional(BoundedUrl).annotate({
description: `Website URL. ${URL_GUIDANCE} For public pages.`,
}),
target: Schema.optional(BrowserNavigationTarget),
readiness: Schema.optional(Schema.Literals(["load", "domContentLoaded", "none"])).annotate({
description: "Default 'load'. 'none': return at once.",
}),
timeoutMs: OptionalTimeoutMs,
}).check(
Schema.makeFilter(
(input) =>
Number(input.url !== undefined) + Number(input.target !== undefined) === 1 ||
"Provide exactly one of url or target.",
),
);
export type PreviewAutomationNavigateInput = typeof PreviewAutomationNavigateInput.Type;
export const PreviewAutomationResizeInput = Schema.Struct({
...PreviewAutomationTabTargetFields,
mode: Schema.Literals(["fill", "freeform", "preset"]),
preset: Schema.optional(PreviewViewportPresetId),
width: Schema.optional(PreviewViewportSize.fields.width),
height: Schema.optional(PreviewViewportSize.fields.height),
orientation: Schema.optional(Schema.Literals(["portrait", "landscape"])),
timeoutMs: OptionalTimeoutMs,
}).check(
Schema.makeFilter((input) => {
const hasPreset = input.preset !== undefined;
const hasWidth = input.width !== undefined;
const hasHeight = input.height !== undefined;
if (hasWidth !== hasHeight) return "Custom dimensions require both width and height.";
if (input.mode === "fill") {
return !hasPreset && !hasWidth && input.orientation === undefined
? true
: "Fill mode does not accept a preset, dimensions, or orientation.";
}
if (input.mode === "freeform") {
if (!hasWidth || !hasHeight || hasPreset || input.orientation !== undefined) {
return "Freeform mode requires width and height and does not accept a preset or orientation.";
}
} else if (!hasPreset || hasWidth || hasHeight) {
return "Preset mode requires a preset and does not accept custom dimensions.";
}
if (hasWidth && hasHeight && input.width! * input.height! > PREVIEW_VIEWPORT_MAX_AREA) {
return `Custom viewport area must not exceed ${PREVIEW_VIEWPORT_MAX_AREA} pixels.`;
}
return true;
}),
);
export type PreviewAutomationResizeInput = typeof PreviewAutomationResizeInput.Type;
export const PreviewAutomationResizeResult = Schema.Struct({
tabId: PreviewTabId,
setting: PreviewViewportSetting,
viewport: PreviewRenderedViewportSize,
});
export type PreviewAutomationResizeResult = typeof PreviewAutomationResizeResult.Type;
// Mirrors DesktopPreviewColorScheme without importing ipc.ts.
export const PreviewAutomationColorScheme = Schema.Literals(["system", "light", "dark"]);
export type PreviewAutomationColorScheme = typeof PreviewAutomationColorScheme.Type;
export const PreviewAutomationSetColorSchemeInput = Schema.Struct({
...PreviewAutomationTabTargetFields,
colorScheme: PreviewAutomationColorScheme,
});
export type PreviewAutomationSetColorSchemeInput = typeof PreviewAutomationSetColorSchemeInput.Type;
export const PreviewAutomationSetColorSchemeResult = Schema.Struct({
tabId: PreviewTabId,
colorScheme: PreviewAutomationColorScheme,
});
export type PreviewAutomationSetColorSchemeResult =
typeof PreviewAutomationSetColorSchemeResult.Type;
const Locator = TrimmedNonEmptyString;
const LegacySelector = TrimmedNonEmptyString;
const PointerTargetFields = {
...PreviewAutomationTabTargetFields,
selector: Schema.optional(LegacySelector),
locator: Schema.optional(Locator),
x: Schema.optional(Schema.Finite),
y: Schema.optional(Schema.Finite),
timeoutMs: OptionalTimeoutMs,
};
const singlePointerTarget = Schema.makeFilter(
(input: {
readonly selector?: string | undefined;
readonly locator?: string | undefined;
readonly x?: number | undefined;
readonly y?: number | undefined;
}) => {
const selectorModes =
Number(input.selector !== undefined) + Number(input.locator !== undefined);
const hasX = input.x !== undefined;
const hasY = input.y !== undefined;
if (hasX !== hasY) return "Coordinates require both x and y.";
const coordinateModes = hasX && hasY ? 1 : 0;
return selectorModes + coordinateModes === 1 || "Provide exactly one target.";
},
);
export const PreviewAutomationClickInput = Schema.Struct({
...PointerTargetFields,
button: Schema.optional(
Schema.Literals(["left", "right", "middle"]).annotate({
description: "Button. Default left. Right opens the menu.",
}),
),
clickCount: Schema.optional(
Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 3 })).annotate({
description: "1 click, 2 double, 3 triple. Default 1.",
}),
),
}).check(singlePointerTarget);
export type PreviewAutomationClickInput = typeof PreviewAutomationClickInput.Type;
export const PreviewAutomationHoverInput =
Schema.Struct(PointerTargetFields).check(singlePointerTarget);
export type PreviewAutomationHoverInput = typeof PreviewAutomationHoverInput.Type;
export const PreviewAutomationSelectInput = Schema.Struct({
...PreviewAutomationTabTargetFields,
selector: Schema.optional(LegacySelector),
locator: Schema.optional(Locator),
values: Schema.Array(Schema.String).check(Schema.isMaxLength(100)).annotate({
description: "Empty clears it.",
}),
timeoutMs: OptionalTimeoutMs,
}).check(
Schema.makeFilter(
(input) =>
Number(input.selector !== undefined) + Number(input.locator !== undefined) === 1 ||
"Provide exactly one of selector or locator.",
),
);
export type PreviewAutomationSelectInput = typeof PreviewAutomationSelectInput.Type;
export const PreviewAutomationSelectResult = Schema.Struct({
selected: Schema.Array(Schema.String).annotate({
description: "The values of the options now selected.",
}),
});
export type PreviewAutomationSelectResult = typeof PreviewAutomationSelectResult.Type;
export const PreviewAutomationDragInput = Schema.Struct({
...PreviewAutomationTabTargetFields,
source: Locator,
target: Locator,
timeoutMs: OptionalTimeoutMs,
});
export type PreviewAutomationDragInput = typeof PreviewAutomationDragInput.Type;
export const PreviewAutomationUploadInput = Schema.Struct({
...PreviewAutomationTabTargetFields,
paths: Schema.Array(TrimmedNonEmptyString).check(Schema.isMaxLength(20)),
selector: Schema.optional(LegacySelector),
locator: Schema.optional(Locator).annotate({
description: "Set files on this instead of the open picker.",
}),
timeoutMs: OptionalTimeoutMs,
}).check(
Schema.makeFilter(
(input) =>
!(input.selector !== undefined && input.locator !== undefined) ||
"Provide at most one of selector or locator.",
),
);
export type PreviewAutomationUploadInput = typeof PreviewAutomationUploadInput.Type;
export const PreviewAutomationTypeInput = Schema.Struct({
...PreviewAutomationTabTargetFields,
text: Schema.String,
selector: Schema.optional(LegacySelector),
locator: Schema.optional(Locator),
clear: Schema.optional(
Schema.Boolean.annotate({
description: "Clear the input first. Default false.",
}),
),
timeoutMs: OptionalTimeoutMs,
}).check(
Schema.makeFilter(
(input) =>
!(input.selector !== undefined && input.locator !== undefined) ||
"Provide at most one of selector or locator.",
),
);
export type PreviewAutomationTypeInput = typeof PreviewAutomationTypeInput.Type;
export const PreviewAutomationPressInput = Schema.Struct({
...PreviewAutomationTabTargetFields,
key: Schema.String.check(Schema.isTrimmed()).check(Schema.isNonEmpty()),
modifiers: Schema.optional(Schema.Array(Schema.Literals(["Alt", "Control", "Meta", "Shift"]))),
});
export type PreviewAutomationPressInput = typeof PreviewAutomationPressInput.Type;
export const PreviewAutomationScrollInput = Schema.Struct({
...PreviewAutomationTabTargetFields,
deltaX: Schema.optional(Schema.Finite),
deltaY: Schema.optional(Schema.Finite),
selector: Schema.optional(LegacySelector),
locator: Schema.optional(Locator),
}).check(
Schema.makeFilter((input) => {
if (input.selector !== undefined && input.locator !== undefined) {
return "Provide at most one of selector or locator.";
}
return input.deltaX !== undefined || input.deltaY !== undefined || "Provide deltaX or deltaY.";
}),
);
export type PreviewAutomationScrollInput = typeof PreviewAutomationScrollInput.Type;
/** Strict input for each `preview_act` action. */
export const PreviewAutomationActInputs = {
click: PreviewAutomationClickInput,
type: PreviewAutomationTypeInput,
hover: PreviewAutomationHoverInput,
select: PreviewAutomationSelectInput,
drag: PreviewAutomationDragInput,
upload: PreviewAutomationUploadInput,
press: PreviewAutomationPressInput,
scroll: PreviewAutomationScrollInput,
} as const;
export type PreviewAutomationAction = keyof typeof PreviewAutomationActInputs;
/** One flat shape for every page action. The server decodes it with the action's strict input. */
export const PreviewAutomationActInput = Schema.Struct({
action: Schema.Literals([
"click",
"type",
"hover",
"select",
"drag",
"upload",
"press",
"scroll",
]),
...PreviewAutomationTabTargetFields,
locator: Schema.optional(Locator),
selector: Schema.optional(LegacySelector),
x: Schema.optional(Schema.Finite),
y: Schema.optional(Schema.Finite),
button: Schema.optional(Schema.Literals(["left", "right", "middle"])),
clickCount: Schema.optional(Schema.Int),
text: Schema.optional(Schema.String),
clear: Schema.optional(Schema.Boolean),
values: Schema.optional(Schema.Array(Schema.String)),
source: Schema.optional(Locator),
target: Schema.optional(Locator),
paths: Schema.optional(Schema.Array(Schema.String)),
key: Schema.optional(Schema.String),
modifiers: PreviewAutomationPressInput.fields.modifiers,
deltaX: Schema.optional(Schema.Finite),
deltaY: Schema.optional(Schema.Finite),
timeoutMs: Schema.optional(Schema.Int),
});
export type PreviewAutomationActInput = typeof PreviewAutomationActInput.Type;
export const PreviewAutomationEvaluateInput = Schema.Struct({
...PreviewAutomationTabTargetFields,
expression: Schema.String.check(Schema.isTrimmed())
.check(Schema.isNonEmpty())
.check(Schema.isMaxLength(64_000)),
awaitPromise: Schema.optional(Schema.Boolean.annotate({ description: "Default true." })),
returnByValue: Schema.optional(
Schema.Boolean.annotate({
description: "False returns a ref. Default true.",
}),
),
});
export type PreviewAutomationEvaluateInput = typeof PreviewAutomationEvaluateInput.Type;
export const PreviewAutomationWaitForInput = Schema.Struct({
...PreviewAutomationTabTargetFields,
selector: Schema.optional(LegacySelector),
locator: Schema.optional(Locator),
text: Schema.optional(TrimmedNonEmptyString).annotate({
description: "Case counts.",
}),
urlIncludes: Schema.optional(TrimmedNonEmptyString),
timeoutMs: OptionalTimeoutMs,
}).check(
Schema.makeFilter((input) => {
if (input.selector !== undefined && input.locator !== undefined) {
return "Provide at most one of selector or locator.";
}
return (
input.selector !== undefined ||
input.locator !== undefined ||
input.text !== undefined ||
input.urlIncludes !== undefined ||
"Provide at least one wait condition."
);
}),
);
export type PreviewAutomationWaitForInput = typeof PreviewAutomationWaitForInput.Type;
export const PreviewAutomationElement = Schema.Struct({
tag: Schema.String,
role: Schema.NullOr(Schema.String),
name: Schema.String,
selector: Schema.String,
x: Schema.Number,
y: Schema.Number,
width: Schema.Number,
height: Schema.Number,
});
export type PreviewAutomationElement = typeof PreviewAutomationElement.Type;
export const PreviewAutomationConsoleEntry = Schema.Struct({
level: Schema.String,
text: Schema.String,
timestamp: Schema.String,
source: Schema.optional(Schema.String),
});
export type PreviewAutomationConsoleEntry = typeof PreviewAutomationConsoleEntry.Type;
export const PreviewAutomationNetworkEntry = Schema.Struct({
url: Schema.String,
method: Schema.String,
status: Schema.NullOr(Schema.Number),
failed: Schema.Boolean,
errorText: Schema.optional(Schema.String),
timestamp: Schema.String,
});
export type PreviewAutomationNetworkEntry = typeof PreviewAutomationNetworkEntry.Type;
export const PreviewAutomationActionEvent = Schema.Struct({
id: Schema.String,
action: Schema.String,
status: Schema.Literals(["running", "succeeded", "failed", "interrupted"]),
startedAt: Schema.String,
completedAt: Schema.optional(Schema.String),
error: Schema.optional(Schema.String),
});
export type PreviewAutomationActionEvent = typeof PreviewAutomationActionEvent.Type;
export const PreviewAutomationSnapshot = Schema.Struct({
url: Schema.String,
title: Schema.String,
loading: Schema.Boolean,
visibleText: Schema.String,
interactiveElements: Schema.Array(PreviewAutomationElement),
accessibilityTree: Schema.Unknown,
consoleEntries: Schema.Array(PreviewAutomationConsoleEntry),
networkEntries: Schema.Array(PreviewAutomationNetworkEntry),
actionTimeline: Schema.Array(PreviewAutomationActionEvent),
screenshot: Schema.Struct({
mimeType: Schema.Literal("image/png"),
data: Schema.String,
width: Schema.Int,
height: Schema.Int,
}),
});
export type PreviewAutomationSnapshot = typeof PreviewAutomationSnapshot.Type;
export const PreviewAutomationRecordingStatus = Schema.Struct({
tabId: PreviewTabId,
recording: Schema.Boolean,
startedAt: Schema.NullOr(Schema.String),
});
export type PreviewAutomationRecordingStatus = typeof PreviewAutomationRecordingStatus.Type;
export const PREVIEW_RECORDING_STOP_TIMEOUT_MS = 120_000;
export const PreviewAutomationRecordingArtifact = Schema.Struct({
id: Schema.String,
tabId: PreviewTabId,
path: Schema.String,
mimeType: Schema.String,
sizeBytes: Schema.Int,
createdAt: Schema.String,
});
export type PreviewAutomationRecordingArtifact = typeof PreviewAutomationRecordingArtifact.Type;
export const PreviewAutomationClientId = TrimmedNonEmptyString.check(Schema.isMaxLength(128));
export type PreviewAutomationClientId = typeof PreviewAutomationClientId.Type;
export const PreviewAutomationConnectionId = TrimmedNonEmptyString.check(Schema.isMaxLength(64));
export type PreviewAutomationConnectionId = typeof PreviewAutomationConnectionId.Type;
export const PreviewAutomationHostIdentity = Schema.Struct({
clientId: PreviewAutomationClientId,
environmentId: EnvironmentId,
});
export type PreviewAutomationHostIdentity = typeof PreviewAutomationHostIdentity.Type;
export const PreviewAutomationHost = Schema.Struct({
...PreviewAutomationHostIdentity.fields,
/** Missing means the V1 operation set from before capability negotiation. */
supportedOperations: Schema.optional(Schema.Array(PreviewAutomationOperation)),
});
export type PreviewAutomationHost = typeof PreviewAutomationHost.Type;
export const PreviewAutomationHostFocus = Schema.Struct({
...PreviewAutomationHostIdentity.fields,
connectionId: PreviewAutomationConnectionId,
focused: Schema.Boolean,
liveTabs: Schema.optional(
Schema.Array(
Schema.Struct({
threadId: ThreadId,
tabId: PreviewTabId,
visible: Schema.optional(Schema.Boolean),
}),
),
),
});
export type PreviewAutomationHostFocus = typeof PreviewAutomationHostFocus.Type;
export const PreviewAutomationRequest = Schema.Struct({
requestId: TrimmedNonEmptyString,
threadId: ThreadId,
tabId: Schema.optional(PreviewTabId),
tabIdExplicit: Schema.optional(Schema.Boolean),
/** Supplied by the broker from authenticated scope, never from tool input. */
agentSessionId: Schema.optional(Schema.String),
operation: PreviewAutomationOperation,
input: Schema.Unknown,
timeoutMs: Schema.Int.check(Schema.isGreaterThan(0)),
});
export type PreviewAutomationRequest = typeof PreviewAutomationRequest.Type;
export const PreviewAutomationStreamEvent = Schema.Union([
Schema.Struct({
type: Schema.Literal("connected"),
connectionId: PreviewAutomationConnectionId,
}),
Schema.Struct({
type: Schema.Literal("request"),
connectionId: PreviewAutomationConnectionId,
request: PreviewAutomationRequest,
}),
]);
export type PreviewAutomationStreamEvent = typeof PreviewAutomationStreamEvent.Type;
export const PreviewAutomationResponse = Schema.Struct({
clientId: PreviewAutomationClientId,
connectionId: PreviewAutomationConnectionId,
requestId: TrimmedNonEmptyString,
ok: Schema.Boolean,
result: Schema.optional(Schema.Unknown),
error: Schema.optional(
Schema.Struct({
_tag: TrimmedNonEmptyString,
message: Schema.String,
detail: Schema.optional(Schema.Unknown),
}),
),
});
export type PreviewAutomationResponse = typeof PreviewAutomationResponse.Type;
const McpCapabilityErrorFields = {
environmentId: EnvironmentId,
threadId: Schema.optional(ThreadId),
providerSessionId: Schema.optional(TrimmedNonEmptyString),
providerInstanceId: Schema.optional(ProviderInstanceId),
};
export class PreviewAutomationUnavailableError extends Schema.TaggedError()(
"PreviewAutomationUnavailableError",
{
capability: Schema.Literal("preview"),
...McpCapabilityErrorFields,
},
) {
override get message(): string {
return `MCP credential does not grant the ${this.capability} capability: browser preview tools are off for this thread. Do not retry them. To check a page, use a headless browser from the shell, such as Playwright, or curl. The user can turn on "Agent browser access" in Settings; it applies when the agent session next starts.`;
}
}
export class McpCapabilityUnavailableError extends Schema.TaggedError()(
"McpCapabilityUnavailableError",
{
capability: TrimmedNonEmptyString,
...McpCapabilityErrorFields,
},
) {
override get message(): string {
return `MCP credential does not grant the ${this.capability} capability.`;
}
}
const PreviewAutomationScopeErrorFields = {
operation: PreviewAutomationOperation,
environmentId: EnvironmentId,
threadId: ThreadId,
providerSessionId: TrimmedNonEmptyString,
providerInstanceId: ProviderInstanceId,
};
export const SERVER_BROWSER_AUTOMATION_CLIENT_ID = "server-browser";
const PreviewAutomationRequestErrorFields = {
...PreviewAutomationScopeErrorFields,
clientId: TrimmedNonEmptyString,
connectionId: PreviewAutomationConnectionId,
requestId: TrimmedNonEmptyString,
tabId: Schema.optional(PreviewTabId),
timeoutMs: Schema.Int.check(Schema.isGreaterThan(0)),
};
const PreviewAutomationRemoteDiagnosticFields = {
remoteTag: TrimmedNonEmptyString,
remoteMessageLength: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
remoteDetailKind: Schema.optional(
Schema.Literals(["null", "array", "object", "string", "number", "boolean"]),
),
cause: Schema.Defect(),
};
const PreviewAutomationOptionalRemoteDiagnosticFields = {
remoteTag: Schema.optional(TrimmedNonEmptyString),
remoteMessageLength: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
remoteDetailKind: Schema.optional(
Schema.Literals(["null", "array", "object", "string", "number", "boolean"]),
),
cause: Schema.optional(Schema.Defect()),
};
export class PreviewAutomationNoAvailableHostError extends Schema.TaggedError()(
"PreviewAutomationNoAvailableHostError",
{
...PreviewAutomationScopeErrorFields,
clientId: Schema.optional(TrimmedNonEmptyString),
connectionId: Schema.optional(PreviewAutomationConnectionId),
requestId: Schema.optional(TrimmedNonEmptyString),
tabId: Schema.optional(PreviewTabId),
timeoutMs: Schema.optional(Schema.Int.check(Schema.isGreaterThan(0))),
...PreviewAutomationOptionalRemoteDiagnosticFields,
},
) {
override get message(): string {
return `No preview automation host is available for ${this.operation} in environment ${this.environmentId}. The server-owned browser may be starting or reconnecting. Retry preview_status, then call preview_open if no tab is available. If it remains unavailable, report the browser connection failure.`;
}
}
export class PreviewAutomationUnsupportedClientError extends Schema.TaggedError()(
"PreviewAutomationUnsupportedClientError",
{
...PreviewAutomationRequestErrorFields,
...PreviewAutomationRemoteDiagnosticFields,
},
) {
override get message(): string {
return `Preview automation client ${this.clientId} does not support ${this.operation}.`;
}
}
export class PreviewAutomationTabNotFoundError extends Schema.TaggedError()(
"PreviewAutomationTabNotFoundError",
{
...PreviewAutomationRequestErrorFields,
...PreviewAutomationRemoteDiagnosticFields,
},
) {
override get message(): string {
return this.tabId
? `Preview tab ${this.tabId} was not found for ${this.operation}. Omit tabId to use the current tab, or call preview_open.`
: `No active preview tab was found for ${this.operation}. Call preview_open first.`;
}
}
export class PreviewAutomationTimeoutError extends Schema.TaggedError()(
"PreviewAutomationTimeoutError",
{
...PreviewAutomationRequestErrorFields,
...PreviewAutomationOptionalRemoteDiagnosticFields,
reason: Schema.optional(Schema.String),
},
) {
override get message(): string {
const summary = `Preview automation ${this.operation} timed out after ${this.timeoutMs}ms.`;
return this.reason === undefined ? summary : `${summary} ${this.reason}`;
}
}
export const PreviewAutomationControlReason = Schema.Literals([
"agentMismatch",
"humanControl",
"tabRequired",
"closed",
"interrupted",
"dialogPending",
"tabLimit",
]);
export type PreviewAutomationControlReason = typeof PreviewAutomationControlReason.Type;
export class PreviewAutomationControlInterruptedError extends Schema.TaggedError()(
"PreviewAutomationControlInterruptedError",
{
...PreviewAutomationRequestErrorFields,
...PreviewAutomationRemoteDiagnosticFields,
reason: Schema.optional(PreviewAutomationControlReason),
},
) {
override get message(): string {
if (this.reason === "agentMismatch")
return "This browser tab belongs to another agent session or a human. Open your own tab.";
if (this.reason === "humanControl")
return "A human controls this browser tab. Wait until they release control.";
if (this.reason === "tabRequired")
return "Multiple browser tabs belong to this session. Pass a tabId from preview_open or from the tabs listed by preview_status.";
if (this.reason === "dialogPending")
return "A browser dialog is pending. Read preview_status and resolve it with preview_dialog.";
if (this.reason === "tabLimit")
return "Too many server browser tabs are open. Close one with t3_preview_close, or reuse a tabId from preview_status tabs.";
if (this.reason === "closed") return "This browser tab is closed. Call preview_open.";
if (this.reason === "interrupted")
return "Browser control changed. Take a fresh snapshot before trying again.";
return `Preview automation ${this.operation} was interrupted on client ${this.clientId}.`;
}
}
export class PreviewAutomationExecutionError extends Schema.TaggedError()(
"PreviewAutomationExecutionError",
{
...PreviewAutomationRequestErrorFields,
...PreviewAutomationRemoteDiagnosticFields,
reason: Schema.optional(Schema.String),
},
) {
override get message(): string {
return this.reason === undefined
? `Preview automation ${this.operation} failed on client ${this.clientId}.`
: `Preview automation ${this.operation} failed: ${this.reason}`;
}
}
export class PreviewAutomationInvalidSelectorError extends Schema.TaggedError()(
"PreviewAutomationInvalidSelectorError",
{
...PreviewAutomationRequestErrorFields,
...PreviewAutomationRemoteDiagnosticFields,
selectorKind: Schema.optional(Schema.Literals(["locator", "selector"])),
selectorLength: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
staleRef: Schema.optional(Schema.Boolean),
},
) {
override get message(): string {
if (this.staleRef)
return "This element ref is stale. Navigation, dialogs, and new snapshots replace refs; take a fresh snapshot and use its refs.";
if (this.selectorKind !== undefined && this.selectorLength !== undefined) {
return `Preview automation ${this.operation} received an invalid ${this.selectorKind} (${this.selectorLength} characters).`;
}
return `Preview automation ${this.operation} received an invalid selector.`;
}
}
export class PreviewAutomationTargetNotEditableError extends Schema.TaggedError()(
"PreviewAutomationTargetNotEditableError",
{
...PreviewAutomationRequestErrorFields,
...PreviewAutomationRemoteDiagnosticFields,
selectorKind: Schema.optional(Schema.Literals(["focused-element", "locator", "selector"])),
selectorLength: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
},
) {
override get message(): string {
if (this.selectorKind === "focused-element") {
return `Preview automation ${this.operation} requires an editable focused element.`;
}
if (this.selectorKind !== undefined && this.selectorLength !== undefined) {
return `Preview automation ${this.operation} requires an editable ${this.selectorKind} (${this.selectorLength} characters).`;
}
return `Preview automation ${this.operation} requires an editable target.`;
}
}
export class PreviewAutomationResultTooLargeError extends Schema.TaggedError()(
"PreviewAutomationResultTooLargeError",
{
...PreviewAutomationRequestErrorFields,
...PreviewAutomationRemoteDiagnosticFields,
maximumBytes: Schema.optional(Schema.Int.check(Schema.isGreaterThan(0))),
},
) {
override get message(): string {
const summary =
this.maximumBytes === undefined
? `Preview automation ${this.operation} produced a result that is too large.`
: `Preview automation ${this.operation} produced a result larger than ${this.maximumBytes} bytes.`;
return summary;
}
}
export class PreviewAutomationClientDisconnectedError extends Schema.TaggedError()(
"PreviewAutomationClientDisconnectedError",
PreviewAutomationRequestErrorFields,
) {
override get message(): string {
return `Preview automation client ${this.clientId} disconnected during ${this.operation}.`;
}
}
export class PreviewAutomationRequestQueueClosedError extends Schema.TaggedError()(
"PreviewAutomationRequestQueueClosedError",
PreviewAutomationRequestErrorFields,
) {
override get message(): string {
return `Preview automation client ${this.clientId} stopped accepting ${this.operation} requests.`;
}
}
export class PreviewAutomationRemoteUnavailableError extends Schema.TaggedError()(
"PreviewAutomationRemoteUnavailableError",
{
...PreviewAutomationRequestErrorFields,
...PreviewAutomationRemoteDiagnosticFields,
},
) {
override get message(): string {
return `Preview automation ${this.operation} is unavailable on client ${this.clientId}.`;
}
}
export class PreviewAutomationMalformedResponseError extends Schema.TaggedError()(
"PreviewAutomationMalformedResponseError",
PreviewAutomationRequestErrorFields,
) {
override get message(): string {
return `Preview automation client ${this.clientId} returned a malformed response for ${this.operation}.`;
}
}
export class PreviewAutomationRecordingTransferError extends Schema.TaggedError()(
"PreviewAutomationRecordingTransferError",
{
threadId: ThreadId,
cause: Schema.optional(Schema.Defect()),
},
) {
override get message(): string {
return "Preview recording could not be saved to the agent environment.";
}
}
export class PreviewAutomationRecordingDesktopUpdateRequiredError extends Schema.TaggedError()(
"PreviewAutomationRecordingDesktopUpdateRequiredError",
{ threadId: ThreadId, cause: Schema.optional(Schema.Defect()) },
) {
override get message(): string {
return "Update the desktop app to transfer recordings. The recording remains on the desktop.";
}
}
export class PreviewAutomationRecordingTooLargeError extends Schema.TaggedError()(
"PreviewAutomationRecordingTooLargeError",
{ threadId: ThreadId, cause: Schema.optional(Schema.Defect()) },
) {
override get message(): string {
return "The recording exceeds 50 MiB. Make a shorter recording.";
}
}
export class PreviewAutomationRecordingDeadlineExpiredError extends Schema.TaggedError()(
"PreviewAutomationRecordingDeadlineExpiredError",
{ threadId: ThreadId, cause: Schema.optional(Schema.Defect()) },
) {
override get message(): string {
return "The recording transfer deadline expired.";
}
}
export const PreviewAutomationError = Schema.Union([
PreviewAutomationRecordingTransferError,
PreviewAutomationRecordingDesktopUpdateRequiredError,
PreviewAutomationRecordingTooLargeError,
PreviewAutomationRecordingDeadlineExpiredError,
PreviewAutomationUnavailableError,
PreviewAutomationNoAvailableHostError,
PreviewAutomationUnsupportedClientError,
PreviewAutomationTabNotFoundError,
PreviewAutomationTimeoutError,
PreviewAutomationControlInterruptedError,
PreviewAutomationExecutionError,
PreviewAutomationInvalidSelectorError,
PreviewAutomationTargetNotEditableError,
PreviewAutomationResultTooLargeError,
PreviewAutomationClientDisconnectedError,
PreviewAutomationRequestQueueClosedError,
PreviewAutomationRemoteUnavailableError,
PreviewAutomationMalformedResponseError,
]);
export type PreviewAutomationError = typeof PreviewAutomationError.Type;
export const PreviewUrlResolution = Schema.Struct({
requestedUrl: Schema.String,
resolvedUrl: Schema.String,
resolutionKind: Schema.Literals(["direct", "direct-private-network"]),
environmentId: EnvironmentId,
});
export type PreviewUrlResolution = typeof PreviewUrlResolution.Type;