Skip to content

Instantly share code, notes, and snippets.

@hugefiver
Last active January 5, 2026 08:34
Show Gist options
  • Select an option

  • Save hugefiver/902e78db001e0bec0ffa07e5f2260ce5 to your computer and use it in GitHub Desktop.

Select an option

Save hugefiver/902e78db001e0bec0ffa07e5f2260ce5 to your computer and use it in GitHub Desktop.
antigravity-browser-launcher
/*
Notes:
- This is a *reference rewrite* for comprehension and further refactoring.
- The original runtime depends on VS Code extension APIs (vscode) and a custom
namespace: vscode.antigravityUnifiedStateSync.BrowserPreferences.
*/
import type * as vscode from "vscode";
import * as childProcess from "child_process";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
/** Parsed server address like: user@host:port */
export class ServerAddress {
constructor(
public hostname: string,
public user?: string,
public port?: number,
) {}
static parse(address: string): ServerAddress {
let user: string | undefined;
let port: number | undefined;
const atIndex = address.lastIndexOf("@");
if (atIndex !== -1) {
user = address.substring(0, atIndex);
}
const colonIndex = address.lastIndexOf(":");
if (colonIndex !== -1) {
const parsed = parseInt(address.substring(colonIndex + 1), 10);
port = Number.isFinite(parsed) ? parsed : undefined;
}
const hostStart = atIndex !== -1 ? atIndex + 1 : 0;
const hostEnd = colonIndex !== -1 ? colonIndex : address.length;
const hostname = address.substring(hostStart, hostEnd);
return new ServerAddress(hostname, user, port);
}
toString(): string {
let value = this.hostname;
if (this.user) value = `${this.user}@${value}`;
if (this.port) value += `:${this.port}`;
return value;
}
/**
* Accepts either:
* 1) hex-encoded JSON: { hostName, user, port }
* 2) legacy encoding with "\\xNN" escapes
*/
static parseEncoded(encoded: string): ServerAddress {
try {
const decodedJson = Buffer.from(encoded, "hex").toString();
const parsed = JSON.parse(decodedJson) as {
hostName?: string;
user?: string;
port?: number;
};
if (parsed?.hostName) {
return new ServerAddress(parsed.hostName, parsed.user, parsed.port);
}
} catch {
// fall back to legacy string encoding
}
const legacyDecoded = encoded.replace(/\\x([0-9a-f]{2})/g, (_m, hex) =>
String.fromCharCode(parseInt(hex, 16)),
);
return ServerAddress.parse(legacyDecoded);
}
/** Encodes by escaping capital letters into "\\xNN". */
toEncodedString(): string {
return this.toString().replace(/[A-Z]/g, (char) =>
`\\x${char.charCodeAt(0).toString(16).toLowerCase()}`,
);
}
}
const DEV_EXTENSION_ID = "Codeium.codeium-dev";
function hasDevExtension(vscodeApi: typeof vscode): boolean {
return vscodeApi.extensions.getExtension(DEV_EXTENSION_ID) !== undefined;
}
/** OutputChannel wrapper used by the original module. */
class BrowserLauncherOutput {
private static instance: vscode.OutputChannel | undefined;
static get(vscodeApi: typeof vscode): vscode.OutputChannel {
if (!BrowserLauncherOutput.instance) {
BrowserLauncherOutput.instance = vscodeApi.window.createOutputChannel(
"Browser Launcher",
{ log: true },
);
}
return BrowserLauncherOutput.instance;
}
}
class ChromeInstallationError extends Error {
constructor(message: string) {
super(message);
this.name = "ChromeInstallationError";
}
}
const CHROME_DOWNLOAD_URL = "https://www.google.com/chrome/";
// Cache values (loaded from Antigravity unified state sync).
let configuredChromePath: string | undefined;
let configuredProfilePath: string | undefined;
let configuredCdpPort: number | undefined;
let lastLaunchCommand: string | undefined;
let sshReverseProxyProcess: childProcess.ChildProcess | null = null;
const DEFAULT_CHROME_PATHS: string[] = [
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"/usr/bin/google-chrome",
"/usr/bin/chromium-browser",
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
];
const FALLBACK_CHROME_PATHS: string[] = [
"/Applications/Chromium.app/Contents/MacOS/Chromium",
"/usr/bin/google-chrome-stable",
"/usr/bin/chromium",
"/snap/bin/chromium",
"/usr/bin/google-chrome-beta",
"/opt/google/chrome/chrome",
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
process.env.LOCALAPPDATA
? `${process.env.LOCALAPPDATA}\\Google\\Chrome\\Application\\chrome.exe`
: "",
process.env.PROGRAMFILES
? `${process.env.PROGRAMFILES}\\Google\\Chrome\\Application\\chrome.exe`
: "",
"C:\\Program Files\\Chromium\\Application\\chrome.exe",
"C:\\Program Files (x86)\\Chromium\\Application\\chrome.exe",
].filter(Boolean);
function getChromeExecutablePath(): string | undefined {
if (configuredChromePath?.trim()) return configuredChromePath.trim();
for (const candidate of [...DEFAULT_CHROME_PATHS, ...FALLBACK_CHROME_PATHS]) {
if (fs.existsSync(candidate)) return candidate;
}
return undefined;
}
function getProfilePath(): string {
if (configuredProfilePath?.trim()) return configuredProfilePath.trim();
return path.join(os.homedir(), ".gemini", "antigravity-browser-profile");
}
function doesProfileExist(): boolean {
return fs.existsSync(getProfilePath());
}
function getCdpPort(): number {
return configuredCdpPort && configuredCdpPort > 0 ? configuredCdpPort : 9222;
}
function wait(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Checks if CDP endpoint responds on 127.0.0.1 by calling curl.
*
* NOTE: On Windows this assumes curl is available in PATH.
*/
function isCdpEndpointUp(port: number): boolean {
try {
childProcess.execSync(`curl -s http://127.0.0.1:${port}/json/version`, {
stdio: "ignore",
timeout: 500,
});
return true;
} catch {
return false;
}
}
async function waitForBrowserReady(
vscodeApi: typeof vscode,
port: number,
startTimeMs: number,
timeoutMs = 30_000,
): Promise<void> {
const output = BrowserLauncherOutput.get(vscodeApi);
while (Date.now() - startTimeMs < timeoutMs) {
if (isCdpEndpointUp(port)) {
output.appendLine(
`Browser ready on port ${port} after ${Date.now() - startTimeMs}ms`,
);
return;
}
await wait(100);
}
output.appendLine(`Browser startup timed out after ${timeoutMs}ms on port ${port}`);
}
function spawnBrowser(
vscodeApi: typeof vscode,
chromePath: string,
args: string[],
shouldFocus: boolean,
): childProcess.ChildProcess {
const output = BrowserLauncherOutput.get(vscodeApi);
// macOS uses `open -a <app> --args ...`
if (os.platform() === "darwin") {
const openArgs: string[] = ["--new"];
if (!shouldFocus) openArgs.push("--background");
openArgs.push("-a", chromePath, "--args", ...args);
lastLaunchCommand = `open ${openArgs.join(" ")}`;
output.appendLine(`Launching browser with command: ${lastLaunchCommand}`);
return childProcess.spawn("open", openArgs, {
detached: true,
stdio: "ignore",
shell: false,
});
}
lastLaunchCommand = `"${chromePath}" ${args.join(" ")}`;
output.appendLine(`Launching browser with command: ${lastLaunchCommand}`);
return childProcess.spawn(chromePath, args, {
detached: true,
stdio: "ignore",
shell: false,
});
}
function isChromeRunningWithCdp(vscodeApi: typeof vscode): boolean {
const output = BrowserLauncherOutput.get(vscodeApi);
const port = getCdpPort();
if (isCdpEndpointUp(port)) {
output.appendLine(`Chrome with CDP is already running on port ${port}.`);
return true;
}
output.appendLine(`No Chrome with CDP found on port ${port}. Safe to launch Chrome.`);
return false;
}
function startSshReverseProxyIfRemoteSsh(vscodeApi: typeof vscode): void {
const output = BrowserLauncherOutput.get(vscodeApi);
output.appendLine("Starting SSH reverse proxy...");
const remoteAuthority = vscodeApi.env.remoteAuthority;
if (!remoteAuthority?.startsWith("ssh-remote+")) {
output.appendLine("Not in remote SSH session. Skipping reverse proxy initialization.");
return;
}
// If we already have a proxy process, restart it.
if (sshReverseProxyProcess) {
sshReverseProxyProcess.kill();
sshReverseProxyProcess = null;
}
const parts = remoteAuthority.split("+");
if (parts.length < 2) {
output.appendLine("Warning: Invalid remote authority format. Expected ssh-remote+<hostData>");
return;
}
const encodedHostData = parts[1];
const host = ServerAddress.parseEncoded(encodedHostData);
const sshTarget = host.user ? `${host.user}@${host.hostname}` : host.hostname;
const port = getCdpPort();
const sshArgs = [
"-R",
`${port}:127.0.0.1:${port}`,
sshTarget,
"-N",
"-o",
"ExitOnForwardFailure=yes",
"-o",
"LogLevel=ERROR",
"-o",
"StreamLocalBindUnlink=yes",
];
sshReverseProxyProcess = childProcess.spawn("ssh", sshArgs);
}
function getBrowserLaunchFlags(vscodeApi: typeof vscode): string[] {
const customFlags = vscodeApi.workspace
.getConfiguration("codeiumDev")
.get<string[]>("browserLaunchFlags");
if (customFlags?.length) return customFlags;
return [
`--remote-debugging-port=${getCdpPort()}`,
`--user-data-dir=${getProfilePath()}`,
"--disable-fre",
"--no-default-browser-check",
"--no-first-run",
"--auto-accept-browser-signin-for-tests",
"--ash-no-nudges",
"--disable-features=OfferMigrationToDiceUsers,OptGuideOnDeviceModel",
];
}
async function loadBrowserPreferences(vscodeApi: typeof vscode): Promise<void> {
// This is a custom API provided by the original extension host.
// Keep it as loose `any` so this file can be typechecked without that dependency.
try {
const unifiedState = (vscodeApi as any).antigravityUnifiedStateSync;
if (!unifiedState?.BrowserPreferences) return;
const [chromePath, profilePath, cdpPort] = await Promise.all([
unifiedState.BrowserPreferences.getBrowserChromePath(),
unifiedState.BrowserPreferences.getBrowserUserProfilePath(),
unifiedState.BrowserPreferences.getBrowserCdpPort(),
]);
configuredChromePath = chromePath;
configuredProfilePath = profilePath;
configuredCdpPort = cdpPort;
} catch (err) {
// The original code logs to console and continues.
// eslint-disable-next-line no-console
console.error("[Browser Launcher] Failed to load browser settings:", err);
}
}
type LaunchDiagnostics = Record<string, string>;
async function collectLaunchFailureDiagnostics(
vscodeApi: typeof vscode,
err: Error,
): Promise<LaunchDiagnostics> {
const diagnostics: LaunchDiagnostics = {};
const output = BrowserLauncherOutput.get(vscodeApi);
const port = getCdpPort();
const platform = os.platform();
diagnostics._error_message = err.message;
if (err.stack) diagnostics._error_stack = err.stack.substring(0, 500);
// Port scan
try {
let portInfo = "";
if (platform === "win32") {
portInfo = childProcess
.execSync(`netstat -ano | findstr :${port}`, {
encoding: "utf-8",
timeout: 1000,
})
.trim();
} else {
portInfo = childProcess
.execSync(`lsof -ti:${port}`, {
encoding: "utf-8",
timeout: 1000,
})
.trim();
}
diagnostics.port_in_use = portInfo ? "true" : "false";
if (portInfo) diagnostics.port_scan_output = portInfo;
} catch {
diagnostics.port_in_use = "false";
}
diagnostics.platform = platform;
// Process scan
try {
let procCommand: string;
if (platform === "darwin" || platform === "linux") {
procCommand = 'pgrep -fl "chrome|chromium"';
} else if (platform === "win32") {
procCommand = 'tasklist /FI "IMAGENAME eq chrome.exe" /V';
} else {
throw new Error("Unsupported platform");
}
const processList = childProcess
.execSync(procCommand, {
encoding: "utf-8",
timeout: 2000,
})
.trim();
if (processList) {
diagnostics.chrome_processes_found = "true";
diagnostics._chrome_processes = processList.substring(0, 1000);
const debugPorts = processList.match(/--remote-debugging-port=(\d+)/g);
if (debugPorts) diagnostics._running_cdp_ports = debugPorts.join(", ");
const userDataDirs = processList.match(/--user-data-dir=([^\s]+)/g);
if (userDataDirs) {
diagnostics._running_user_data_dirs = userDataDirs
.map((m) => m.substring(0, 100))
.join(", ")
.substring(0, 300);
}
} else {
diagnostics.chrome_processes_found = "false";
}
} catch (scanErr) {
diagnostics.chrome_processes_found = "error";
output.appendLine(
`Failed to get Chrome processes: ${scanErr instanceof Error ? scanErr.message : String(scanErr)}`,
);
}
if (lastLaunchCommand) {
diagnostics._launch_command = lastLaunchCommand.substring(0, 500);
}
return diagnostics;
}
export interface LaunchOptions {
/** The original code passes a boolean into the launch routine; used as focus hint. */
runInForeground?: boolean;
}
async function performLaunch(vscodeApi: typeof vscode, options?: LaunchOptions): Promise<string | null> {
const output = BrowserLauncherOutput.get(vscodeApi);
try {
output.appendLine("Launching browser...");
const launchStartTime = Date.now();
await loadBrowserPreferences(vscodeApi);
// If profile doesn't exist, show onboarding (non-blocking).
if (!doesProfileExist()) {
vscodeApi.commands.executeCommand(
"antigravity.showBrowserOnboardingFullScreenView",
);
}
// If a browser is already running with CDP, we do not spawn a new one.
const usingExistingBrowser = isChromeRunningWithCdp(vscodeApi);
if (!usingExistingBrowser) {
const chromePath = getChromeExecutablePath();
if (!chromePath) {
const message =
`Chrome installation not found. Please install Google Chrome at ${CHROME_DOWNLOAD_URL} ` +
"or set a custom Chrome binary path in the browser section of the Antigravity user settings.";
output.appendLine(message);
void vscodeApi.window
.showErrorMessage(
"Chrome installation not detected and is required for browser agent features.",
{ modal: true },
{ title: "Install Chrome" },
{ title: "Set custom Chrome binary path" },
)
.then((choice) => {
if (choice?.title === "Install Chrome") {
vscodeApi.env.openExternal(vscodeApi.Uri.parse(CHROME_DOWNLOAD_URL));
} else if (choice?.title === "Set custom Chrome binary path") {
vscodeApi.commands.executeCommand(
"workbench.action.openAntigravitySettingsWithId",
"browserChromePath",
);
}
});
throw new ChromeInstallationError(message);
}
const flags = getBrowserLaunchFlags(vscodeApi);
const shouldFocus = options?.runInForeground ?? false;
spawnBrowser(vscodeApi, chromePath, flags, shouldFocus).unref();
}
// Setup reverse proxy if in Remote-SSH.
startSshReverseProxyIfRemoteSsh(vscodeApi);
// If we launched a new browser, wait a bit for it to become responsive.
if (!usingExistingBrowser) {
const port = getCdpPort();
await vscodeApi.window.withProgress(
{
location: vscodeApi.ProgressLocation.Notification,
title: "Launching browser...",
cancellable: true,
},
async (_progress, token) => {
const waitStart = Date.now();
while (!isCdpEndpointUp(port)) {
if (Date.now() - waitStart > 20_000) {
output.appendLine(
"Timed out waiting for chrome with remote debugging port to be ready.",
);
break;
}
if (token.isCancellationRequested) break;
await wait(500);
}
},
);
void waitForBrowserReady(vscodeApi, port, launchStartTime, 5000);
}
const browserUrl = `http://127.0.0.1:${getCdpPort()}`;
if (usingExistingBrowser) {
if (hasDevExtension(vscodeApi)) {
vscodeApi.window.showInformationMessage("Dev: Connected to existing browser.");
}
} else {
vscodeApi.window.showInformationMessage("Successfully launched browser.");
}
return browserUrl;
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
output.appendLine(`Failed to launch browser: ${error.message}`);
const diagnostics = await collectLaunchFailureDiagnostics(vscodeApi, error);
try {
await vscodeApi.commands.executeCommand(
"antigravity.logProductEvent",
"BROWSER_LAUNCH_FAILURE_DIAGNOSTICS",
0n,
false,
diagnostics,
);
} catch (uploadErr) {
output.appendLine(
`Failed to upload diagnostics: ${uploadErr instanceof Error ? uploadErr.message : String(uploadErr)}`,
);
}
if (error instanceof ChromeInstallationError) throw error;
vscodeApi.window.showErrorMessage(`Failed to launch browser: ${error.message}`);
return null;
}
}
class BrowserLauncher {
static instance: BrowserLauncher | null = null;
private runningWarmup: Promise<void> | null = null;
private runningLaunch: Promise<string | null> | null = null;
constructor(private readonly vscodeApi: typeof vscode) {}
launch(options?: LaunchOptions): Promise<string | null> {
if (!this.runningLaunch) {
this.runningLaunch = (async () => {
if (this.runningWarmup) await this.runningWarmup;
return performLaunch(this.vscodeApi, options);
})();
this.runningLaunch.finally(() => {
this.runningLaunch = null;
});
}
return this.runningLaunch;
}
warmUp(): Promise<void> {
// Keep original behavior: warmup is only performed when not launching.
if (this.runningLaunch) return Promise.resolve();
this.runningWarmup ||= (async () => {
const output = BrowserLauncherOutput.get(this.vscodeApi);
output.appendLine("Starting browser warmup...");
try {
const chromePath = getChromeExecutablePath();
if (!chromePath) {
output.appendLine("Chrome not found, skipping warmup.");
return;
}
const profilePath = getProfilePath();
const warmupPort = 9223;
const warmupArgs = [
`--user-data-dir=${profilePath}`,
`--remote-debugging-port=${warmupPort}`,
"--headless=new",
"--disable-gpu",
"--no-sandbox",
"--disable-dev-shm-usage",
"about:blank",
];
const start = Date.now();
const proc = spawnBrowser(this.vscodeApi, chromePath, warmupArgs, false);
await waitForBrowserReady(this.vscodeApi, warmupPort, start, 5000);
// macOS `open` returns a wrapper; original code kills by port.
if (os.platform() === "darwin") {
try {
const pidStr = childProcess
.execSync(`lsof -ti:${warmupPort}`, {
encoding: "utf-8",
stdio: ["pipe", "pipe", "ignore"],
})
.trim();
if (pidStr) {
const pid = parseInt(pidStr, 10);
output.appendLine(`Killing process ${pid} on port ${warmupPort}`);
process.kill(pid, "SIGTERM");
}
} catch (killErr) {
output.appendLine(
`No process found on port ${warmupPort} or failed to kill: ${killErr instanceof Error ? killErr.message : String(killErr)}`,
);
}
} else {
proc.kill();
}
} catch (warmErr) {
const error = warmErr instanceof Error ? warmErr : new Error(String(warmErr));
BrowserLauncherOutput.get(this.vscodeApi).appendLine(`Browser warmup failed: ${error.message}`);
}
})();
return this.runningWarmup;
}
}
/** VS Code extension entry. */
export async function activate(context: vscode.ExtensionContext): Promise<void> {
const vscodeApi = await import("vscode");
context.subscriptions.push(
vscodeApi.commands.registerCommand("browserLauncher.warmUpBrowser", async () => {
BrowserLauncher.instance ||= new BrowserLauncher(vscodeApi);
return BrowserLauncher.instance.warmUp();
}),
);
context.subscriptions.push(
vscodeApi.commands.registerCommand("browserLauncher.launchBrowser", async (options?: LaunchOptions) => {
BrowserLauncher.instance ||= new BrowserLauncher(vscodeApi);
return BrowserLauncher.instance.launch(options);
}),
);
context.subscriptions.push(
vscodeApi.commands.registerCommand("browserLauncher.resetBrowserOnboarding", async () => {
const output = BrowserLauncherOutput.get(vscodeApi);
const profilePath = getProfilePath();
try {
if (fs.existsSync(profilePath)) {
output.appendLine(`Deleting Chrome profile at: ${profilePath}`);
await fs.promises.rm(profilePath, { recursive: true, force: true });
output.appendLine("Successfully deleted Chrome profile.");
vscodeApi.window.showInformationMessage("Successfully deleted Chrome profile.");
} else {
output.appendLine(`Chrome profile not found at: ${profilePath}`);
vscodeApi.window.showInformationMessage("Chrome profile not found.");
}
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
output.appendLine(`Failed to delete Chrome profile: ${error.message}`);
vscodeApi.window.showErrorMessage(`Failed to delete Chrome profile: ${error.message}`);
}
try {
await vscodeApi.commands.executeCommand("antigravity.resetOnboardingBackend");
vscodeApi.window.showInformationMessage("Successfully reset onboarding.");
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
output.appendLine(`Failed to reset onboarding: ${error.message}`);
vscodeApi.window.showErrorMessage(`Failed to reset onboarding: ${error.message}`);
}
setTimeout(() => {
vscodeApi.commands.executeCommand("workbench.action.reloadWindow");
}, 1000);
}),
);
}
export function deactivate(): void {
// no-op (matches original)
}
/*
* Human-readable extraction from Antigravity webview bundle:
* resources/app/extensions/antigravity/out/media/chat.js
*
* Goal
* - Document the “tool/step/interaction/config” vocabulary the chat UI uses,
* especially browser automation via CDP + language server.
*
* Notes
* - This file is not a generated protobuf binding. It is a readable mirror of
* names/fields/enums observed in the bundle.
* - Some nested message types are left as `unknown` where the bundle does not
* show a stable/public shape.
*/
export const SOURCE = {
bundlePath:
'resources/app/extensions/antigravity/out/media/chat.js' as const,
} as const;
/** Webview <-> extension message/event names related to browser preferences + allowlist. */
export const WEBVIEW_EVENTS = {
REQUEST_BROWSER_PREFERENCES: 'request-browser-preferences',
PROVIDE_BROWSER_PREFERENCES: 'provide-browser-preferences',
REQUEST_OPEN_BROWSER_ALLOWLIST: 'request-open-browser-allowlist-function',
} as const;
export type WebviewEventName =
(typeof WEBVIEW_EVENTS)[keyof typeof WEBVIEW_EVENTS];
/**
* Browser JavaScript execution policy.
* Source: exa.codeium_common_pb.BrowserJsExecutionPolicy
*/
export enum BrowserJsExecutionPolicy {
UNSPECIFIED = 'UNSPECIFIED',
DISABLED = 'DISABLED',
ALWAYS_ASK = 'ALWAYS_ASK',
MODEL_DECIDES = 'MODEL_DECIDES',
TURBO = 'TURBO',
}
/**
* Browser JS auto-run policy.
* Source: exa.codeium_common_pb.BrowserJsAutoRunPolicy
*/
export enum BrowserJsAutoRunPolicy {
UNSPECIFIED = 'UNSPECIFIED',
DISABLED = 'DISABLED',
MODEL_DECIDES = 'MODEL_DECIDES',
ENABLED = 'ENABLED',
}
/**
* Whether/when to use the browser subagent.
* Source: exa.cortex_pb.BrowserSubagentMode
*/
export enum BrowserSubagentMode {
UNSPECIFIED = 'UNSPECIFIED',
MAIN_AGENT_ONLY = 'MAIN_AGENT_ONLY',
SUBAGENT_PRIMARILY = 'SUBAGENT_PRIMARILY',
BOTH_AGENTS = 'BOTH_AGENTS',
SUBAGENT_ONLY = 'SUBAGENT_ONLY',
}
/**
* Which browser tool set is enabled.
* Source: exa.cortex_pb.BrowserToolSetMode
*/
export enum BrowserToolSetMode {
UNSPECIFIED = 'UNSPECIFIED',
ALL_TOOLS = 'ALL_TOOLS',
PIXEL_ONLY = 'PIXEL_ONLY',
ALL_INPUT_PIXEL_OUTPUT = 'ALL_INPUT_PIXEL_OUTPUT',
}
/**
* Click feedback overlay type.
* Source: exa.cortex_pb.ClickFeedbackConfig.FeedbackType
*/
export enum ClickFeedbackType {
UNSPECIFIED = 'UNSPECIFIED',
DOT = 'DOT',
MOUSE_POINTER = 'MOUSE_POINTER',
}
/** Source: exa.cortex_pb.DOMExtractionConfig */
export interface DOMExtractionConfig {
/** include_coordinates */
includeCoordinates?: boolean;
}
/** Source: exa.cortex_pb.BrowserWindowSize */
export interface BrowserWindowSize {
/** width_px */
widthPx: number;
/** height_px */
heightPx: number;
}
/** Source: exa.cortex_pb.CaptureBrowserScreenshotToolConfig */
export interface CaptureBrowserScreenshotToolConfig {
/** enable_saving */
enableSaving?: boolean;
}
/** Source: exa.cortex_pb.ClickFeedbackConfig */
export interface ClickFeedbackConfig {
enabled?: boolean;
red?: number;
green?: number;
blue?: number;
alpha?: number;
/** display_color */
displayColor?: string;
radius?: number;
/** feedback_type */
feedbackType?: ClickFeedbackType;
}
/** Source: exa.cortex_pb.ClickBrowserPixelToolConfig */
export interface ClickBrowserPixelToolConfig {
/** click_feedback */
clickFeedback?: ClickFeedbackConfig;
}
/** Source: exa.cortex_pb.BrowserStateDiffingConfig */
export interface BrowserStateDiffingConfig {
/** capture_agent_action_diffs */
captureAgentActionDiffs?: boolean;
/** include_dom_tree_in_diffs */
includeDomTreeInDiffs?: boolean;
}
/** Source: exa.cortex_pb.BrowserSubagentContextConfig.ContextType */
export enum BrowserSubagentContextType {
UNSPECIFIED = 'UNSPECIFIED',
WITH_MARKDOWN_TRAJECTORY_SUMMARY = 'WITH_MARKDOWN_TRAJECTORY_SUMMARY',
TASK_ONLY = 'TASK_ONLY',
}
/** Source: exa.cortex_pb.BrowserSubagentContextConfig */
export interface BrowserSubagentContextConfig {
type?: BrowserSubagentContextType;
/** max_chars */
maxChars?: number;
}
/**
* Source: exa.cortex_pb.SubagentReminderMode
* Bundle shows it as a `oneof`; keep it loose.
*/
export type SubagentReminderMode =
| { case: string; value: unknown }
| { case?: undefined; value?: undefined };
/** Source: exa.cortex_pb.BrowserSubagentToolConfig */
export interface BrowserSubagentToolConfig {
mode?: BrowserSubagentMode;
/** browser_subagent_model (shares enum with other model selectors; left as unknown) */
browserSubagentModel?: unknown;
/** use_detailed_converter */
useDetailedConverter?: boolean;
/** suggested_max_tool_calls */
suggestedMaxToolCalls?: number;
/** disable_onboarding */
disableOnboarding?: boolean;
/** subagent_reminder_mode */
subagentReminderMode?: SubagentReminderMode;
/** max_context_tokens */
maxContextTokens?: number;
/** context_config */
contextConfig?: BrowserSubagentContextConfig;
/** dom_extraction_config */
domExtractionConfig?: DOMExtractionConfig;
}
/** Source: exa.cortex_pb.AntigravityBrowserToolConfig */
export interface AntigravityBrowserToolConfig {
enabled?: boolean;
/** auto_run_decision (enum appears elsewhere as mJ; kept as unknown) */
autoRunDecision?: unknown;
captureBrowserScreenshot?: CaptureBrowserScreenshotToolConfig;
browserSubagent?: BrowserSubagentToolConfig;
clickBrowserPixel?: ClickBrowserPixelToolConfig;
browserStateDiffingConfig?: BrowserStateDiffingConfig;
toolSetMode?: BrowserToolSetMode;
disableOpenUrl?: boolean;
isEvalMode?: boolean;
browserJsExecutionPolicy?: BrowserJsExecutionPolicy;
disableActuationOverlay?: boolean;
/** variable_wait_tool */
variableWaitTool?: boolean;
browserJsAutoRunPolicy?: BrowserJsAutoRunPolicy;
initialBrowserWindowSize?: BrowserWindowSize;
domExtractionConfig?: DOMExtractionConfig;
}
/**
* MCP (Model Context Protocol) pieces observed in the bundle.
*
* Notes
* - MCP “tools” are executed via the language server; the chat UI renders a step case `mcpTool`.
* - Some MCP configuration is present in `CascadeToolConfig.mcp`.
*/
/** Source: exa.cortex_pb.McpServerStatus */
export enum McpServerStatus {
UNSPECIFIED = 'UNSPECIFIED',
PENDING = 'PENDING',
READY = 'READY',
ERROR = 'ERROR',
}
/** Source: exa.cortex_pb.McpToolConfig */
export interface McpToolConfig {
/** force_disable */
forceDisable?: boolean;
/** max_output_bytes */
maxOutputBytes: number;
}
/** Source: exa.cortex_pb.McpServerSpec */
export interface McpServerSpec {
/** server_name */
serverName: string;
/** command */
command: string;
/** args */
args: string[];
/** env */
env: Record<string, string>;
/** server_url */
serverUrl: string;
/** disabled */
disabled: boolean;
/** disabled_tools */
disabledTools: string[];
/** headers */
headers: Record<string, string>;
/** server_index */
serverIndex: number;
}
/** Source: exa.cortex_pb.McpServerInfo */
export interface McpServerInfo {
name: string;
version: string;
}
/** Source: exa.cortex_pb.McpServerState */
export interface McpServerState {
spec?: McpServerSpec;
status?: McpServerStatus;
error?: string;
/** tools (shape not stable in UI bundle; left unknown) */
tools?: unknown[];
/** tool_errors */
toolErrors?: string[];
serverInfo?: McpServerInfo;
instructions?: string;
}
/** Source: exa.codeium_common_pb.McpResourceItem */
export interface McpResourceItem {
uri: string;
name: string;
description?: string;
mimeType?: string;
serverName: string;
}
/** Source: exa.cortex_pb.McpResource */
export interface McpResource {
uri: string;
name: string;
description: string;
mimeType: string;
}
/** Source: exa.cortex_pb.McpResourceContent */
export type McpResourceContent =
| { uri: string; text: { text: string } }
| { uri: string; image: unknown }
| { uri: string; mediaContent: unknown }
| { uri: string; data?: { case?: undefined; value?: undefined } };
/**
* Language server RPCs related to MCP (visible in the webview bundle).
* These names are helpful when tracing UI -> language server call chains.
*/
export const MCP_LANGUAGE_SERVER_RPCS = {
refreshMcpServers: 'RefreshMcpServers',
getMcpServerStates: 'GetMcpServerStates',
listMcpResources: 'ListMcpResources',
} as const;
/**
* Language Server (LS) overview (as seen from the chat webview)
*
* In this Antigravity build, the chat webview talks to a “language server client” via RPC.
* The bundle shows direct calls like:
* - `t.openUrl(new OpenUrlRequest({ url }))`
* - `t.listPages(new ListPagesRequest({}))`
* - `t.captureScreenshot(new CaptureScreenshotRequest({ pageId }))`
* - `t.captureConsoleLogs(new CaptureConsoleLogsRequest({ pageId }))`
* and throws `Error("Language server client not available")` when the client is missing.
*
* Practical meaning:
* - Browser actions in the UI are NOT implemented purely in the webview; they are executed
* by the backend (language server) through RPC.
* - The language server likely owns the CDP connection + automation plumbing; the UI only
* requests operations and renders results/steps.
*/
/** Browser-related LS RPC names observed in the service definition inside chat.js. */
export const LANGUAGE_SERVER_BROWSER_RPCS = {
listPages: 'ListPages',
openUrl: 'OpenUrl',
focusUserPage: 'FocusUserPage',
captureScreenshot: 'CaptureScreenshot',
captureConsoleLogs: 'CaptureConsoleLogs',
/** allowlist / whitelist management */
addToBrowserWhitelist: 'AddToBrowserWhitelist',
getBrowserWhitelistFilePath: 'GetBrowserWhitelistFilePath',
getAllBrowserWhitelistedUrls: 'GetAllBrowserWhitelistedUrls',
/** onboarding / orchestration */
smartOpenBrowser: 'SmartOpenBrowser',
browserValidateCascadeOrCancelOverlay: 'BrowserValidateCascadeOrCancelOverlay',
setBrowserOpenConversation: 'SetBrowserOpenConversation',
getBrowserOpenConversation: 'GetBrowserOpenConversation',
/** generic webview <- LS signaling */
sendActionToChatPanel: 'SendActionToChatPanel',
} as const;
export type LanguageServerBrowserRpcName =
(typeof LANGUAGE_SERVER_BROWSER_RPCS)[keyof typeof LANGUAGE_SERVER_BROWSER_RPCS];
/**
* A combined “RPC name” union useful for tracing.
* Note: this is not exhaustive for all LS methods, only the ones relevant to MCP + browser.
*/
export type LanguageServerRpcName =
| LanguageServerBrowserRpcName
| (typeof MCP_LANGUAGE_SERVER_RPCS)[keyof typeof MCP_LANGUAGE_SERVER_RPCS];
/** exa.language_server_pb.ListPagesRequest */
export interface ListPagesRequest {}
/** exa.language_server_pb.ListPagesResponse (page payload type is not expanded here) */
export interface ListPagesResponse {
pages: unknown[];
}
/** exa.language_server_pb.OpenUrlRequest */
export interface OpenUrlRequest {
url: string;
}
/** exa.language_server_pb.OpenUrlResponse */
export interface OpenUrlResponse {}
/** exa.language_server_pb.FocusUserPageRequest */
export interface FocusUserPageRequest {
pageId: string;
}
/** exa.language_server_pb.FocusUserPageResponse */
export interface FocusUserPageResponse {}
/** exa.language_server_pb.CaptureScreenshotRequest */
export interface CaptureScreenshotRequest {
pageId: string;
}
/** exa.language_server_pb.CaptureScreenshotResponse */
export interface CaptureScreenshotResponse {
screenshot?: unknown;
mediaScreenshot?: unknown;
}
/** exa.language_server_pb.CaptureConsoleLogsRequest */
export interface CaptureConsoleLogsRequest {
pageId: string;
}
/** exa.language_server_pb.CaptureConsoleLogsResponse */
export interface CaptureConsoleLogsResponse {
consoleLogs?: unknown;
}
/** exa.language_server_pb.AddToBrowserWhitelistRequest */
export interface AddToBrowserWhitelistRequest {
hostname?: string;
hostnames?: string[];
}
/** exa.language_server_pb.AddToBrowserWhitelistResponse */
export interface AddToBrowserWhitelistResponse {}
/** exa.language_server_pb.GetBrowserWhitelistFilePathRequest */
export interface GetBrowserWhitelistFilePathRequest {}
/** exa.language_server_pb.GetBrowserWhitelistFilePathResponse */
export interface GetBrowserWhitelistFilePathResponse {
path: string;
}
/** exa.language_server_pb.GetAllBrowserWhitelistedUrlsRequest */
export interface GetAllBrowserWhitelistedUrlsRequest {}
/** exa.language_server_pb.GetAllBrowserWhitelistedUrlsResponse */
export interface GetAllBrowserWhitelistedUrlsResponse {
whitelistedUrls: string[];
}
/** exa.language_server_pb.SmartOpenBrowserRequest */
export interface SmartOpenBrowserRequest {
url: string;
isOnboarded: boolean;
}
/** exa.language_server_pb.SmartOpenBrowserResponse */
export interface SmartOpenBrowserResponse {}
/** exa.language_server_pb.BrowserValidateCascadeOrCancelOverlayRequest */
export interface BrowserValidateCascadeOrCancelOverlayRequest {
cascadeId: string;
}
/** exa.language_server_pb.BrowserValidateCascadeOrCancelOverlayResponse */
export interface BrowserValidateCascadeOrCancelOverlayResponse {}
/** exa.language_server_pb.SetBrowserOpenConversationRequest */
export interface SetBrowserOpenConversationRequest {
cascadeId: string;
/** timestamp-like proto message; left unknown */
expiresAt?: unknown;
}
/** exa.language_server_pb.SetBrowserOpenConversationResponse */
export interface SetBrowserOpenConversationResponse {}
/** exa.language_server_pb.SendActionToChatPanelRequest */
export interface SendActionToChatPanelRequest {
actionType: string;
/** payload is bytes[] in proto; in JS it appears as Uint8Array-like */
payload: unknown[];
}
/** exa.language_server_pb.SendActionToChatPanelResponse */
export interface SendActionToChatPanelResponse {}
/**
* RequestedInteraction (oneof) cases the UI handles.
* Source: exa.cortex_pb.RequestedInteraction (subset)
*/
export type RequestedInteractionCase =
| 'openBrowserUrl'
| 'executeBrowserJavascript'
| 'captureBrowserScreenshot'
| 'clickBrowserPixel'
| 'browserAction'
| 'openBrowserSetup'
| 'confirmBrowserSetup'
| 'runCommand'
| 'runExtensionCode'
| 'sendCommandInput'
| 'readUrlContent'
| 'mcp'
| (string & {});
/**
* CortexStep UI “case” strings found in the webview renderer registry.
* These are the discriminants used by `step.step.case` in the UI.
*/
export type StepCase =
// Browser tools
| 'openBrowserUrl'
| 'executeBrowserJavascript'
| 'listBrowserPages'
| 'captureBrowserScreenshot'
| 'captureBrowserConsoleLogs'
| 'clickBrowserPixel'
| 'readBrowserPage'
| 'browserGetDom'
| 'browserMoveMouse'
| 'browserInput'
| 'browserSelectOption'
| 'browserScroll'
| 'browserScrollUp'
| 'browserScrollDown'
| 'browserResizeWindow'
| 'browserDragPixelToPixel'
| 'browserMouseWheel'
| 'browserPressKey'
| 'browserClickElement'
// Browser subagent
| 'browserSubagent'
// MCP tools/resources
| 'mcpTool'
| 'listResources'
| 'readResource'
// Other non-browser steps (partial; UI has many)
| 'runCommand'
| 'runExtensionCode'
| 'searchWeb'
| 'readUrlContent'
| 'plannerResponse'
| 'notifyUser'
| 'ephemeralMessage'
| 'errorMessage'
| 'userInput'
| 'finish'
| (string & {});
/** Source: exa.cortex_pb.CortexStepOpenBrowserUrl */
export interface CortexStepOpenBrowserUrl {
url: string;
pageIdToReplace?: string;
autoRunDecision?: unknown;
userRejected?: boolean;
pageId?: string;
webDocument?: unknown;
pageMetadata?: unknown;
screenshot?: unknown;
mediaScreenshot?: unknown;
browserStateDiff?: string;
}
/** Source: exa.cortex_pb.CortexStepExecuteBrowserJavaScript */
export interface CortexStepExecuteBrowserJavaScript {
title?: string;
pageId: string;
javascriptSource: string;
javascriptDescription?: string;
shouldAutoRun?: boolean;
userRejected?: boolean;
screenshotEnd?: unknown;
mediaScreenshotEnd?: unknown;
pageMetadata?: unknown;
executionDurationMs?: unknown;
javascriptResult?: string;
browserStateDiff?: string;
}
/** Source: exa.cortex_pb.CortexStepCaptureBrowserScreenshot */
export interface CortexStepCaptureBrowserScreenshot {
pageId: string;
saveScreenshot?: boolean;
screenshotName?: string;
captureByElementIndex?: boolean;
elementIndex?: number;
userRejected?: boolean;
screenshot?: unknown;
mediaScreenshot?: unknown;
pageMetadata?: unknown;
autoRunDecision?: unknown;
}
/** Source: exa.cortex_pb.CortexStepClickBrowserPixel */
export interface CortexStepClickBrowserPixel {
pageId: string;
x: number;
y: number;
clickType?: unknown;
userRejected?: boolean;
pageMetadata?: unknown;
screenshotWithClickFeedback?: unknown;
browserStateDiff?: string;
}
/** Source: exa.cortex_pb.CortexStepReadBrowserPage */
export interface CortexStepReadBrowserPage {
pageId: string;
webDocument?: unknown;
pageMetadata?: unknown;
}
/** Source: exa.cortex_pb.CortexStepBrowserGetDom */
export interface CortexStepBrowserGetDom {
pageId: string;
domTree?: unknown;
serializedDomTree?: string;
pageMetadata?: unknown;
}
/** Source: exa.cortex_pb.CortexStepListBrowserPages */
export interface CortexStepListBrowserPages {
pages: unknown[];
}
/**
* Union of browser-related step payloads.
* This mirrors `step.step.case` / `step.step.value` shape used in the UI.
*/
export type BrowserStep =
| { case: 'openBrowserUrl'; value: CortexStepOpenBrowserUrl }
| { case: 'executeBrowserJavascript'; value: CortexStepExecuteBrowserJavaScript }
| { case: 'captureBrowserScreenshot'; value: CortexStepCaptureBrowserScreenshot }
| { case: 'clickBrowserPixel'; value: CortexStepClickBrowserPixel }
| { case: 'readBrowserPage'; value: CortexStepReadBrowserPage }
| { case: 'browserGetDom'; value: CortexStepBrowserGetDom }
| { case: 'listBrowserPages'; value: CortexStepListBrowserPages }
| { case: 'captureBrowserConsoleLogs'; value: { pageId: string } }
| { case: 'browserAction'; value: unknown }
| { case: 'browserSubagent'; value: unknown };
export type StepCategory =
| 'browser'
| 'browser-input'
| 'browser-output'
| 'browser-subagent'
| 'mcp'
| 'code'
| 'terminal'
| 'web'
| 'system'
| 'other';
/** Source: exa.cortex_pb.CortexStepMcpTool */
export interface CortexStepMcpTool {
serverName: string;
/** tool_call (contains name + argumentsJson; exact shape varies; kept loose) */
toolCall?: {
name?: string;
argumentsJson?: string;
[k: string]: unknown;
};
serverInfo?: McpServerInfo;
resultString?: string;
images: unknown[];
media: unknown[];
userRejected?: boolean;
}
/** Source: exa.cortex_pb.CortexStepListResources */
export interface CortexStepListResources {
serverName?: string;
resources: McpResourceItem[];
}
/** Source: exa.cortex_pb.CortexStepReadResource */
export interface CortexStepReadResource {
uri: string;
content?: McpResourceContent;
}
export interface StepUiSpec {
/** UI discriminant (step.step.case) */
stepCase: StepCase;
/** Rough grouping for humans */
category: StepCategory;
/** Human-friendly title */
title: string;
/** Whether the UI marks it as a tool (isTool: true in renderer registry) */
isTool?: boolean;
/** If present, which RequestedInteraction.case is used for accept/reject UI */
requestedInteractionCase?: RequestedInteractionCase;
/** Security sensitive (e.g., allowlist / JS execution) */
sensitive?: boolean;
/** Short description of typical args */
argsHint?: string;
}
/**
* High-value subset: browser-related steps and the UI behavior around them.
* (Not an exhaustive list of all CortexStepType values.)
*/
export const STEP_UI: Record<string, StepUiSpec> = {
// MCP
mcpTool: {
stepCase: 'mcpTool',
category: 'mcp',
title: '运行 MCP 工具调用',
isTool: true,
requestedInteractionCase: 'mcp',
sensitive: true,
argsHint: '{ serverName: string, toolCall: { name: string, argumentsJson?: string } }',
},
listResources: {
stepCase: 'listResources',
category: 'mcp',
title: '列出 MCP 资源',
isTool: true,
sensitive: false,
argsHint: '{ serverName?: string, query?: string }',
},
readResource: {
stepCase: 'readResource',
category: 'mcp',
title: '读取 MCP 资源内容',
isTool: true,
sensitive: false,
argsHint: '{ uri: string }',
},
openBrowserUrl: {
stepCase: 'openBrowserUrl',
category: 'browser',
title: '在内置浏览器打开 URL',
isTool: true,
requestedInteractionCase: 'openBrowserUrl',
sensitive: true,
argsHint: '{ url: string, pageIdToReplace?: string }',
},
executeBrowserJavascript: {
stepCase: 'executeBrowserJavascript',
category: 'browser',
title: '在内置浏览器执行 JavaScript',
isTool: true,
requestedInteractionCase: 'executeBrowserJavascript',
sensitive: true,
argsHint: '{ pageId: string, javascriptSource: string, javascriptDescription?: string }',
},
captureBrowserScreenshot: {
stepCase: 'captureBrowserScreenshot',
category: 'browser-output',
title: '捕获浏览器截图',
isTool: true,
requestedInteractionCase: 'captureBrowserScreenshot',
sensitive: false,
argsHint: '{ pageId: string, saveScreenshot?: boolean, screenshotName?: string }',
},
clickBrowserPixel: {
stepCase: 'clickBrowserPixel',
category: 'browser-input',
title: '按坐标点击(像素级)',
isTool: true,
requestedInteractionCase: 'clickBrowserPixel',
sensitive: true,
argsHint: '{ pageId: string, x: number, y: number, clickType?: enum }',
},
listBrowserPages: {
stepCase: 'listBrowserPages',
category: 'browser',
title: '列出当前浏览器页面',
isTool: true,
argsHint: '{ }',
},
readBrowserPage: {
stepCase: 'readBrowserPage',
category: 'browser',
title: '读取浏览器页面内容(抽取为文档)',
isTool: true,
argsHint: '{ pageId: string }',
},
browserGetDom: {
stepCase: 'browserGetDom',
category: 'browser',
title: '提取页面 DOM(结构化)',
isTool: true,
argsHint: '{ pageId: string }',
},
captureBrowserConsoleLogs: {
stepCase: 'captureBrowserConsoleLogs',
category: 'browser-output',
title: '抓取浏览器 Console Logs',
isTool: true,
argsHint: '{ pageId: string }',
},
// The following are rendered as tools in the UI registry (pixel/input style)
browserMoveMouse: {
stepCase: 'browserMoveMouse',
category: 'browser-input',
title: '移动鼠标',
isTool: true,
sensitive: true,
},
browserInput: {
stepCase: 'browserInput',
category: 'browser-input',
title: '输入文本',
isTool: true,
sensitive: true,
},
browserSelectOption: {
stepCase: 'browserSelectOption',
category: 'browser-input',
title: '选择下拉选项',
isTool: true,
sensitive: true,
},
browserScroll: {
stepCase: 'browserScroll',
category: 'browser-input',
title: '滚动(通用)',
isTool: true,
sensitive: true,
},
browserScrollUp: {
stepCase: 'browserScrollUp',
category: 'browser-input',
title: '向上滚动',
isTool: true,
sensitive: true,
},
browserScrollDown: {
stepCase: 'browserScrollDown',
category: 'browser-input',
title: '向下滚动',
isTool: true,
sensitive: true,
},
browserResizeWindow: {
stepCase: 'browserResizeWindow',
category: 'browser-input',
title: '调整浏览器窗口大小',
isTool: true,
sensitive: true,
},
browserDragPixelToPixel: {
stepCase: 'browserDragPixelToPixel',
category: 'browser-input',
title: '拖拽(像素到像素)',
isTool: true,
sensitive: true,
},
browserMouseWheel: {
stepCase: 'browserMouseWheel',
category: 'browser-input',
title: '鼠标滚轮',
isTool: true,
sensitive: true,
},
browserSubagent: {
stepCase: 'browserSubagent',
category: 'browser-subagent',
title: '浏览器子代理(多步浏览器操作)',
isTool: false,
sensitive: true,
},
} as const;
export function formatMcpServerStatus(status?: McpServerStatus): string {
switch (status) {
case McpServerStatus.PENDING:
return 'pending';
case McpServerStatus.READY:
return 'ready';
case McpServerStatus.ERROR:
return 'error';
default:
return 'unspecified';
}
}
export function formatBrowserToolSetMode(mode?: BrowserToolSetMode): string {
switch (mode) {
case BrowserToolSetMode.ALL_TOOLS:
return 'all_tools';
case BrowserToolSetMode.PIXEL_ONLY:
return 'pixel_only';
case BrowserToolSetMode.ALL_INPUT_PIXEL_OUTPUT:
return 'all_input_pixel_output';
default:
return 'default';
}
}
export function formatBrowserSubagentMode(mode?: BrowserSubagentMode): string {
switch (mode) {
case BrowserSubagentMode.SUBAGENT_ONLY:
return 'subagent_only';
case BrowserSubagentMode.MAIN_AGENT_ONLY:
return 'main_only';
case BrowserSubagentMode.BOTH_AGENTS:
return 'both';
case BrowserSubagentMode.SUBAGENT_PRIMARILY:
return 'subagent_primarily';
default:
return 'default';
}
}
export function formatBrowserSubagentContextType(
type?: BrowserSubagentContextType,
): string {
switch (type) {
case BrowserSubagentContextType.WITH_MARKDOWN_TRAJECTORY_SUMMARY:
return 'with_markdown_trajectory_summary';
case BrowserSubagentContextType.TASK_ONLY:
return 'task_only';
default:
return 'default';
}
}
export function formatBrowserJsExecutionPolicy(
policy?: BrowserJsExecutionPolicy,
): string {
switch (policy) {
case BrowserJsExecutionPolicy.DISABLED:
return 'disabled';
case BrowserJsExecutionPolicy.ALWAYS_ASK:
return 'always_ask';
case BrowserJsExecutionPolicy.MODEL_DECIDES:
return 'model_decides';
case BrowserJsExecutionPolicy.TURBO:
return 'turbo';
default:
return 'unspecified';
}
}
export function formatBrowserJsAutoRunPolicy(policy?: BrowserJsAutoRunPolicy): string {
switch (policy) {
case BrowserJsAutoRunPolicy.DISABLED:
return 'disabled';
case BrowserJsAutoRunPolicy.MODEL_DECIDES:
return 'model_decides';
case BrowserJsAutoRunPolicy.ENABLED:
return 'enabled';
default:
return 'unspecified';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment