"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
    var useValue = arguments.length > 2;
    for (var i = 0; i < initializers.length; i++) {
        value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
    }
    return useValue ? value : void 0;
};
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
    function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
    var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
    var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
    var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
    var _, done = false;
    for (var i = decorators.length - 1; i >= 0; i--) {
        var context = {};
        for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
        for (var p in contextIn.access) context.access[p] = contextIn.access[p];
        context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
        var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
        if (kind === "accessor") {
            if (result === void 0) continue;
            if (result === null || typeof result !== "object") throw new TypeError("Object expected");
            if (_ = accept(result.get)) descriptor.get = _;
            if (_ = accept(result.set)) descriptor.set = _;
            if (_ = accept(result.init)) initializers.unshift(_);
        }
        else if (_ = accept(result)) {
            if (kind === "field") initializers.unshift(_);
            else descriptor[key] = _;
        }
    }
    if (target) Object.defineProperty(target, contextIn.name, descriptor);
    done = true;
};
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.V3 = void 0;
const events_1 = require("events");
const fs_1 = __importDefault(require("fs"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const process_1 = __importDefault(require("process"));
const uuid_1 = require("uuid");
const zodCompat_js_1 = require("./zodCompat.js");
const utils_js_1 = require("../utils.js");
const modelUtils_js_1 = require("../modelUtils.js");
const logger_js_1 = require("../logger.js");
const ActCache_js_1 = require("./cache/ActCache.js");
const AgentCache_js_1 = require("./cache/AgentCache.js");
const CacheStorage_js_1 = require("./cache/CacheStorage.js");
const actHandler_js_1 = require("./handlers/actHandler.js");
const extractHandler_js_1 = require("./handlers/extractHandler.js");
const observeHandler_js_1 = require("./handlers/observeHandler.js");
const v3AgentHandler_js_1 = require("./handlers/v3AgentHandler.js");
const v3CuaAgentHandler_js_1 = require("./handlers/v3CuaAgentHandler.js");
const browserbase_js_1 = require("./launch/browserbase.js");
const local_js_1 = require("./launch/local.js");
const LLMProvider_js_1 = require("./llm/LLMProvider.js");
const logger_js_2 = require("./logger.js");
const cleanupLocal_js_1 = require("./shutdown/cleanupLocal.js");
const supervisorClient_js_1 = require("./shutdown/supervisorClient.js");
const utils_js_2 = require("./mcp/utils.js");
const index_js_1 = require("./types/public/index.js");
const context_js_1 = require("./understudy/context.js");
const modelUtils_js_2 = require("../modelUtils.js");
const api_js_1 = require("./api.js");
const validateExperimentalFeatures_js_1 = require("./agent/utils/validateExperimentalFeatures.js");
const variables_js_1 = require("./agent/utils/variables.js");
const flowLogger_js_1 = require("./flowLogger.js");
const timeoutGuard_js_1 = require("./handlers/handlerUtils/timeoutGuard.js");
const sdkErrors_js_1 = require("./types/public/sdkErrors.js");
const DEFAULT_MODEL_NAME = "openai/gpt-4.1-mini";
const DEFAULT_VIEWPORT = { width: 1288, height: 711 };
function resolveModelConfiguration(model) {
    if (!model) {
        return { modelName: DEFAULT_MODEL_NAME };
    }
    if (typeof model === "string") {
        return { modelName: model };
    }
    if (model && typeof model === "object") {
        const { modelName, ...clientOptions } = model;
        if (!modelName) {
            throw new index_js_1.StagehandInvalidArgumentError("model.modelName is required when providing client options.");
        }
        return {
            modelName,
            clientOptions: clientOptions,
        };
    }
    return { modelName: DEFAULT_MODEL_NAME };
}
/**
 * V3
 *
 * Purpose:
 * A high-level orchestrator for Stagehand V3. Abstracts away whether the browser
 * runs **locally via Chrome** or remotely on **Browserbase**, and exposes simple
 * entrypoints (`act`, `extract`, `observe`) that delegate to the corresponding
 * handler classes.
 *
 * Responsibilities:
 * - Bootstraps Chrome or Browserbase, ensures a working CDP WebSocket, and builds a `V3Context`.
 * - Manages lifecycle: init, context access, cleanup.
 * - Bridges external page objects (Playwright/Puppeteer) into internal frameIds for handlers.
 * - Provides a stable API surface for downstream code regardless of runtime environment.
 */
let V3 = (() => {
    let _instanceExtraInitializers = [];
    let _act_decorators;
    let _extract_decorators;
    let _observe_decorators;
    return class V3 {
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
            _act_decorators = [(0, flowLogger_js_1.logStagehandStep)("Stagehand.act", "ACT")];
            _extract_decorators = [(0, flowLogger_js_1.logStagehandStep)("Stagehand.extract", "EXTRACT")];
            _observe_decorators = [(0, flowLogger_js_1.logStagehandStep)("Stagehand.observe", "OBSERVE")];
            __esDecorate(this, null, _act_decorators, { kind: "method", name: "act", static: false, private: false, access: { has: obj => "act" in obj, get: obj => obj.act }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(this, null, _extract_decorators, { kind: "method", name: "extract", static: false, private: false, access: { has: obj => "extract" in obj, get: obj => obj.extract }, metadata: _metadata }, null, _instanceExtraInitializers);
            __esDecorate(this, null, _observe_decorators, { kind: "method", name: "observe", static: false, private: false, access: { has: obj => "observe" in obj, get: obj => obj.observe }, metadata: _metadata }, null, _instanceExtraInitializers);
            if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        opts = __runInitializers(this, _instanceExtraInitializers);
        state = { kind: "UNINITIALIZED" };
        actHandler = null;
        extractHandler = null;
        observeHandler = null;
        ctx = null;
        llmClient;
        /**
         * Event bus for internal communication.
         * Emits events like 'screenshot' when screenshots are captured during agent execution.
         */
        bus = new events_1.EventEmitter();
        modelName;
        modelClientOptions;
        llmProvider;
        overrideLlmClients = new Map();
        domSettleTimeoutMs;
        _isClosing = false;
        browserbaseSessionId;
        browserbaseSessionUrl;
        browserbaseDebugUrl;
        get browserbaseSessionID() {
            return this.browserbaseSessionId;
        }
        get browserbaseSessionURL() {
            return this.browserbaseSessionUrl;
        }
        get browserbaseDebugURL() {
            return this.browserbaseDebugUrl;
        }
        /**
         * Returns true if the browser is running on Browserbase.
         */
        get isBrowserbase() {
            return this.state.kind === "BROWSERBASE";
        }
        /**
         * Returns true if advancedStealth is enabled in Browserbase settings.
         */
        get isAdvancedStealth() {
            return (this.opts.browserbaseSessionCreateParams?.browserSettings
                ?.advancedStealth === true);
        }
        /**
         * Returns the configured viewport dimensions from launch options.
         * Falls back to default 1288x711 if not configured.
         */
        get configuredViewport() {
            const defaultWidth = 1288;
            const defaultHeight = 711;
            if (this.opts.env === "BROWSERBASE") {
                const vp = this.opts.browserbaseSessionCreateParams?.browserSettings?.viewport;
                return {
                    width: vp?.width ?? defaultWidth,
                    height: vp?.height ?? defaultHeight,
                };
            }
            // LOCAL env
            const vp = this.opts.localBrowserLaunchOptions?.viewport;
            return {
                width: vp?.width ?? defaultWidth,
                height: vp?.height ?? defaultHeight,
            };
        }
        _onCdpClosed = (why) => {
            if (this.state.kind === "BROWSERBASE") {
                void this._logBrowserbaseSessionStatus();
            }
            // Single place to react to the transport closing
            this._immediateShutdown(`CDP transport closed: ${why}`).catch(() => { });
        };
        experimental = false;
        logInferenceToFile = false;
        disableAPI = false;
        externalLogger;
        verbose = 1;
        stagehandLogger;
        _history = [];
        instanceId;
        static _processGuardsInstalled = false;
        static _instances = new Set();
        cacheStorage;
        actCache;
        agentCache;
        apiClient = null;
        keepAlive;
        shutdownSupervisor = null;
        stagehandMetrics = {
            actPromptTokens: 0,
            actCompletionTokens: 0,
            actReasoningTokens: 0,
            actCachedInputTokens: 0,
            actInferenceTimeMs: 0,
            extractPromptTokens: 0,
            extractCompletionTokens: 0,
            extractReasoningTokens: 0,
            extractCachedInputTokens: 0,
            extractInferenceTimeMs: 0,
            observePromptTokens: 0,
            observeCompletionTokens: 0,
            observeReasoningTokens: 0,
            observeCachedInputTokens: 0,
            observeInferenceTimeMs: 0,
            agentPromptTokens: 0,
            agentCompletionTokens: 0,
            agentReasoningTokens: 0,
            agentCachedInputTokens: 0,
            agentInferenceTimeMs: 0,
            totalPromptTokens: 0,
            totalCompletionTokens: 0,
            totalReasoningTokens: 0,
            totalCachedInputTokens: 0,
            totalInferenceTimeMs: 0,
        };
        constructor(opts) {
            this.externalLogger = opts.logger;
            this.verbose = opts.verbose ?? 1;
            this.instanceId = (0, uuid_1.v7)();
            this.keepAlive =
                opts.keepAlive ?? opts.browserbaseSessionCreateParams?.keepAlive;
            // Create per-instance StagehandLogger (handles usePino, verbose, externalLogger)
            // This gives each V3 instance independent logger configuration
            // while still sharing the underlying Pino worker thread via StagehandLogger.sharedPinoLogger
            const loggerOptions = {
                pretty: true,
                level: "info", // Most permissive - filtering happens at instance level
            };
            if (opts.disablePino !== undefined) {
                loggerOptions.usePino = !opts.disablePino;
            }
            this.stagehandLogger = new logger_js_1.StagehandLogger(loggerOptions, opts.logger);
            this.stagehandLogger.setVerbosity(this.verbose);
            // Also bind to AsyncLocalStorage for v3Logger() calls from handlers
            // This maintains backward compatibility with code that uses v3Logger() directly
            try {
                if (this.externalLogger) {
                    // Use external logger directly when provided
                    (0, logger_js_2.bindInstanceLogger)(this.instanceId, this.externalLogger);
                }
                else {
                    // Fall back to stagehandLogger when no external logger
                    (0, logger_js_2.bindInstanceLogger)(this.instanceId, (line) => {
                        this.stagehandLogger.log(line);
                    });
                }
            }
            catch {
                // ignore
            }
            const { modelName, clientOptions } = resolveModelConfiguration(opts.model);
            this.modelName = modelName;
            this.experimental = opts.experimental ?? false;
            this.logInferenceToFile = opts.logInferenceToFile ?? false;
            this.llmProvider = new LLMProvider_js_1.LLMProvider(this.logger);
            this.domSettleTimeoutMs = opts.domSettleTimeout;
            this.disableAPI = opts.disableAPI ?? false;
            const baseClientOptions = clientOptions
                ? { ...clientOptions }
                : {};
            if (opts.llmClient) {
                this.llmClient = opts.llmClient;
                this.modelClientOptions = baseClientOptions;
                this.disableAPI = true;
            }
            else {
                // Ensure API key is set
                let apiKey = baseClientOptions.apiKey;
                if (!apiKey) {
                    try {
                        apiKey = (0, utils_js_1.loadApiKeyFromEnv)(this.modelName.split("/")[0], // "openai", "anthropic", etc
                        this.logger);
                    }
                    catch (error) {
                        this.logger({
                            category: "init",
                            message: `Error loading API key for model ${this.modelName}: ${error}. Continuing without LLM client.`,
                            level: 0,
                        });
                        throw error;
                    }
                }
                this.modelClientOptions = {
                    ...baseClientOptions,
                    apiKey,
                };
                // Get the default client for this model
                this.llmClient = this.llmProvider.getClient(this.modelName, this.modelClientOptions, { experimental: this.experimental, disableAPI: this.disableAPI });
            }
            this.cacheStorage = CacheStorage_js_1.CacheStorage.create(opts.cacheDir, this.logger, {
                label: "cache directory",
            });
            this.actCache = new ActCache_js_1.ActCache({
                storage: this.cacheStorage,
                logger: this.logger,
                getActHandler: () => this.actHandler,
                getDefaultLlmClient: () => this.resolveLlmClient(),
                domSettleTimeoutMs: this.domSettleTimeoutMs,
            });
            this.agentCache = new AgentCache_js_1.AgentCache({
                storage: this.cacheStorage,
                logger: this.logger,
                getActHandler: () => this.actHandler,
                getContext: () => this.ctx,
                getDefaultLlmClient: () => this.resolveLlmClient(),
                getBaseModelName: () => this.modelName,
                getSystemPrompt: () => opts.systemPrompt,
                domSettleTimeoutMs: this.domSettleTimeoutMs,
                act: this.act.bind(this),
            });
            this.opts = opts;
            // Initialize session file logger
            flowLogger_js_1.SessionFileLogger.init(this.instanceId, opts);
            // Track instance for global process guard handling
            V3._instances.add(this);
        }
        /**
         * Async property for metrics so callers can `await v3.metrics`.
         * When using API mode, fetches metrics from the API. Otherwise returns local metrics.
         */
        get metrics() {
            if (this.apiClient) {
                // Fetch metrics from the API
                return this.apiClient.getReplayMetrics().catch((error) => {
                    this.logger({
                        category: "metrics",
                        message: `Failed to fetch metrics from API: ${error}`,
                        level: 0,
                    });
                    // Fall back to local metrics on error
                    return this.stagehandMetrics;
                });
            }
            // Return local metrics wrapped in a Promise for consistency
            return Promise.resolve(this.stagehandMetrics);
        }
        resolveLlmClient(model) {
            if (!model) {
                return this.llmClient;
            }
            let modelName;
            let clientOptions;
            if (typeof model === "string") {
                modelName = model;
            }
            else {
                const { modelName: overrideModelName, ...rest } = model;
                modelName = overrideModelName;
                clientOptions = rest;
            }
            if (modelName === this.modelName &&
                (!clientOptions || Object.keys(clientOptions).length === 0)) {
                return this.llmClient;
            }
            const overrideProvider = String(modelName).split("/")[0];
            const baseProvider = String(this.modelName).split("/")[0];
            const mergedOptions = {
                ...(overrideProvider === baseProvider ? this.modelClientOptions : {}),
                ...(clientOptions ?? {}),
            };
            const providerKey = overrideProvider;
            if (!mergedOptions.apiKey) {
                const apiKey = (0, utils_js_1.loadApiKeyFromEnv)(providerKey, this.logger);
                if (apiKey) {
                    mergedOptions.apiKey = apiKey;
                }
            }
            const cacheKey = JSON.stringify({
                modelName,
                clientOptions: mergedOptions,
            });
            const cached = this.overrideLlmClients.get(cacheKey);
            if (cached) {
                return cached;
            }
            const client = this.llmProvider.getClient(modelName, mergedOptions, { experimental: this.experimental, disableAPI: this.disableAPI });
            this.overrideLlmClients.set(cacheKey, client);
            return client;
        }
        beginAgentReplayRecording() {
            this.agentCache.beginRecording();
        }
        endAgentReplayRecording() {
            return this.agentCache.endRecording();
        }
        discardAgentReplayRecording() {
            this.agentCache.discardRecording();
        }
        isAgentReplayRecording() {
            return this.agentCache.isRecording();
        }
        isAgentReplayActive() {
            return this.agentCache.isReplayActive();
        }
        recordAgentReplayStep(step) {
            this.agentCache.recordStep(step);
        }
        /**
         * Async property for history so callers can `await v3.history`.
         * Returns a frozen copy to avoid external mutation.
         */
        get history() {
            return Promise.resolve(Object.freeze([...this._history]));
        }
        addToHistory(method, parameters, result) {
            this._history.push({
                method,
                parameters,
                result: result ?? null,
                timestamp: new Date().toISOString(),
            });
        }
        updateMetrics(functionName, promptTokens, completionTokens, reasoningTokens, cachedInputTokens, inferenceTimeMs) {
            switch (functionName) {
                case index_js_1.V3FunctionName.ACT:
                    this.stagehandMetrics.actPromptTokens += promptTokens;
                    this.stagehandMetrics.actCompletionTokens += completionTokens;
                    this.stagehandMetrics.actReasoningTokens += reasoningTokens;
                    this.stagehandMetrics.actCachedInputTokens += cachedInputTokens;
                    this.stagehandMetrics.actInferenceTimeMs += inferenceTimeMs;
                    break;
                case index_js_1.V3FunctionName.EXTRACT:
                    this.stagehandMetrics.extractPromptTokens += promptTokens;
                    this.stagehandMetrics.extractCompletionTokens += completionTokens;
                    this.stagehandMetrics.extractReasoningTokens += reasoningTokens;
                    this.stagehandMetrics.extractCachedInputTokens += cachedInputTokens;
                    this.stagehandMetrics.extractInferenceTimeMs += inferenceTimeMs;
                    break;
                case index_js_1.V3FunctionName.OBSERVE:
                    this.stagehandMetrics.observePromptTokens += promptTokens;
                    this.stagehandMetrics.observeCompletionTokens += completionTokens;
                    this.stagehandMetrics.observeReasoningTokens += reasoningTokens;
                    this.stagehandMetrics.observeCachedInputTokens += cachedInputTokens;
                    this.stagehandMetrics.observeInferenceTimeMs += inferenceTimeMs;
                    break;
                case index_js_1.V3FunctionName.AGENT:
                    this.stagehandMetrics.agentPromptTokens += promptTokens;
                    this.stagehandMetrics.agentCompletionTokens += completionTokens;
                    this.stagehandMetrics.agentReasoningTokens += reasoningTokens;
                    this.stagehandMetrics.agentCachedInputTokens += cachedInputTokens;
                    this.stagehandMetrics.agentInferenceTimeMs += inferenceTimeMs;
                    break;
            }
            this.updateTotalMetrics(promptTokens, completionTokens, reasoningTokens, cachedInputTokens, inferenceTimeMs);
        }
        updateTotalMetrics(promptTokens, completionTokens, reasoningTokens, cachedInputTokens, inferenceTimeMs) {
            this.stagehandMetrics.totalPromptTokens += promptTokens;
            this.stagehandMetrics.totalCompletionTokens += completionTokens;
            this.stagehandMetrics.totalReasoningTokens += reasoningTokens;
            this.stagehandMetrics.totalCachedInputTokens += cachedInputTokens;
            this.stagehandMetrics.totalInferenceTimeMs += inferenceTimeMs;
        }
        async _immediateShutdown(reason) {
            try {
                this.logger({
                    category: "v3",
                    message: `initiating shutdown → ${reason}`,
                    level: 0,
                });
            }
            catch {
                //
            }
            try {
                this.logger({
                    category: "v3",
                    message: `closing resources → ${reason}`,
                    level: 0,
                });
                await this.close({ force: true });
            }
            catch {
                // swallow — already shutting down
            }
        }
        /** Spawn a crash-only supervisor that cleans up when this process dies. */
        startShutdownSupervisor(config) {
            if (this.shutdownSupervisor)
                return this.shutdownSupervisor;
            this.shutdownSupervisor = (0, supervisorClient_js_1.startShutdownSupervisor)(config, {
                onError: (error, context) => {
                    try {
                        this.logger({
                            category: "v3",
                            message: "Shutdown supervisor unavailable; crash cleanup disabled. " +
                                "If this process exits unexpectedly, local Chrome or Browserbase " +
                                "sessions may remain running even with keepAlive=false.",
                            level: 0,
                            auxiliary: {
                                context: { value: context, type: "string" },
                                error: { value: error.message, type: "string" },
                            },
                        });
                    }
                    catch {
                        // ignore logging failures
                    }
                },
            });
            return this.shutdownSupervisor;
        }
        /** Stop the supervisor during a normal shutdown. */
        stopShutdownSupervisor() {
            if (!this.shutdownSupervisor)
                return;
            try {
                this.shutdownSupervisor.stop();
            }
            catch {
                // best-effort
            }
            this.shutdownSupervisor = null;
        }
        /**
         * Entrypoint: initializes handlers, launches Chrome or Browserbase,
         * and sets up a CDP context.
         */
        async init() {
            try {
                return await (0, logger_js_2.withInstanceLogContext)(this.instanceId, async () => {
                    this.actHandler = new actHandler_js_1.ActHandler(this.llmClient, this.modelName, this.modelClientOptions, (model) => this.resolveLlmClient(model), this.opts.systemPrompt ?? "", this.logInferenceToFile, this.opts.selfHeal ?? true, (functionName, promptTokens, completionTokens, reasoningTokens, cachedInputTokens, inferenceTimeMs) => this.updateMetrics(functionName, promptTokens, completionTokens, reasoningTokens, cachedInputTokens, inferenceTimeMs), this.domSettleTimeoutMs);
                    this.extractHandler = new extractHandler_js_1.ExtractHandler(this.llmClient, this.modelName, this.modelClientOptions, (model) => this.resolveLlmClient(model), this.opts.systemPrompt ?? "", this.logInferenceToFile, this.experimental, (functionName, promptTokens, completionTokens, reasoningTokens, cachedInputTokens, inferenceTimeMs) => this.updateMetrics(functionName, promptTokens, completionTokens, reasoningTokens, cachedInputTokens, inferenceTimeMs));
                    this.observeHandler = new observeHandler_js_1.ObserveHandler(this.llmClient, this.modelName, this.modelClientOptions, (model) => this.resolveLlmClient(model), this.opts.systemPrompt ?? "", this.logInferenceToFile, this.experimental, (functionName, promptTokens, completionTokens, reasoningTokens, cachedInputTokens, inferenceTimeMs) => this.updateMetrics(functionName, promptTokens, completionTokens, reasoningTokens, cachedInputTokens, inferenceTimeMs));
                    if (this.opts.env === "LOCAL") {
                        // chrome-launcher conditionally adds --headless when the environment variable
                        // HEADLESS is set, without parsing its value.
                        // if it is not equal to true, then we delete it from the process
                        const envHeadless = process_1.default.env.HEADLESS;
                        if (envHeadless !== undefined) {
                            const normalized = envHeadless.trim().toLowerCase();
                            if (normalized !== "true") {
                                delete process_1.default.env.HEADLESS;
                            }
                        }
                        const lbo = this.opts.localBrowserLaunchOptions ?? {};
                        // If a CDP URL is provided, attach instead of launching.
                        if (lbo.cdpUrl) {
                            this.logger({
                                category: "init",
                                message: "Connecting to local browser",
                                level: 1,
                            });
                            this.ctx = await context_js_1.V3Context.create(lbo.cdpUrl, {
                                env: "LOCAL",
                            });
                            const logCtx = flowLogger_js_1.SessionFileLogger.getContext();
                            this.ctx.conn.cdpLogger = (info) => flowLogger_js_1.SessionFileLogger.logCdpCallEvent(info, logCtx);
                            this.ctx.conn.cdpEventLogger = (info) => flowLogger_js_1.SessionFileLogger.logCdpMessageEvent(info, logCtx);
                            this.ctx.conn.onTransportClosed(this._onCdpClosed);
                            this.state = {
                                kind: "LOCAL",
                                // no LaunchedChrome when attaching externally; create a stub kill
                                chrome: {
                                    kill: async () => { },
                                },
                                ws: lbo.cdpUrl,
                            };
                            this.resetBrowserbaseSessionMetadata();
                            // Post-connect settings (downloads and viewport) if provided
                            await this._applyPostConnectLocalOptions(lbo);
                            return;
                        }
                        this.logger({
                            category: "init",
                            message: "Launching local browser",
                            level: 1,
                        });
                        // Determine or create user data dir
                        let userDataDir = lbo.userDataDir;
                        let createdTemp = false;
                        if (!userDataDir) {
                            const base = path_1.default.join(os_1.default.tmpdir(), "stagehand-v3");
                            fs_1.default.mkdirSync(base, { recursive: true });
                            userDataDir = fs_1.default.mkdtempSync(path_1.default.join(base, "profile-"));
                            createdTemp = true;
                        }
                        // Build chrome flags
                        const defaults = [
                            "--remote-allow-origins=*",
                            "--no-first-run",
                            "--no-default-browser-check",
                            "--disable-dev-shm-usage",
                            "--site-per-process",
                        ];
                        let chromeFlags;
                        const ignore = lbo.ignoreDefaultArgs;
                        if (ignore === true) {
                            // drop defaults
                            chromeFlags = [];
                        }
                        else if (Array.isArray(ignore)) {
                            chromeFlags = defaults.filter((f) => !ignore.some((ex) => f.includes(ex)));
                        }
                        else {
                            chromeFlags = [...defaults];
                        }
                        // headless handled by launchLocalChrome
                        if (lbo.devtools)
                            chromeFlags.push("--auto-open-devtools-for-tabs");
                        if (lbo.locale)
                            chromeFlags.push(`--lang=${lbo.locale}`);
                        if (!lbo.viewport) {
                            lbo.viewport = DEFAULT_VIEWPORT;
                        }
                        if (lbo.viewport?.width && lbo.viewport?.height) {
                            chromeFlags.push(`--window-size=${lbo.viewport.width},${lbo.viewport.height + 87}`);
                        }
                        if (typeof lbo.deviceScaleFactor === "number") {
                            chromeFlags.push(`--force-device-scale-factor=${Math.max(0.1, lbo.deviceScaleFactor)}`);
                        }
                        if (lbo.hasTouch)
                            chromeFlags.push("--touch-events=enabled");
                        if (lbo.ignoreHTTPSErrors)
                            chromeFlags.push("--ignore-certificate-errors");
                        if (lbo.proxy?.server)
                            chromeFlags.push(`--proxy-server=${lbo.proxy.server}`);
                        if (lbo.proxy?.bypass)
                            chromeFlags.push(`--proxy-bypass-list=${lbo.proxy.bypass}`);
                        // add user-supplied args last
                        if (Array.isArray(lbo.args))
                            chromeFlags.push(...lbo.args);
                        const keepAlive = this.keepAlive === true;
                        const { ws, chrome } = await (0, local_js_1.launchLocalChrome)({
                            chromePath: lbo.executablePath,
                            chromeFlags,
                            port: lbo.port,
                            headless: lbo.headless,
                            userDataDir,
                            connectTimeoutMs: lbo.connectTimeoutMs,
                            handleSIGINT: !keepAlive,
                        });
                        if (keepAlive) {
                            try {
                                chrome.process?.unref?.();
                            }
                            catch {
                                // best-effort: avoid keeping the event loop alive
                            }
                        }
                        this.ctx = await context_js_1.V3Context.create(ws, {
                            env: "LOCAL",
                            localBrowserLaunchOptions: lbo,
                        });
                        const logCtx = flowLogger_js_1.SessionFileLogger.getContext();
                        this.ctx.conn.cdpLogger = (info) => flowLogger_js_1.SessionFileLogger.logCdpCallEvent(info, logCtx);
                        this.ctx.conn.cdpEventLogger = (info) => flowLogger_js_1.SessionFileLogger.logCdpMessageEvent(info, logCtx);
                        this.ctx.conn.onTransportClosed(this._onCdpClosed);
                        this.state = {
                            kind: "LOCAL",
                            chrome,
                            ws,
                            userDataDir,
                            createdTempProfile: createdTemp,
                            preserveUserDataDir: !!lbo.preserveUserDataDir,
                        };
                        this.resetBrowserbaseSessionMetadata();
                        const chromePid = chrome.process?.pid ?? chrome.pid;
                        if (!keepAlive && chromePid) {
                            this.startShutdownSupervisor({
                                kind: "LOCAL",
                                pid: chromePid,
                                userDataDir,
                                createdTempProfile: createdTemp,
                                preserveUserDataDir: !!lbo.preserveUserDataDir,
                            });
                        }
                        // Post-connect settings (downloads and viewport) if provided
                        await this._applyPostConnectLocalOptions(lbo);
                        return;
                    }
                    if (this.opts.env === "BROWSERBASE") {
                        const { apiKey, projectId } = this.requireBrowserbaseCreds();
                        if (!apiKey || !projectId) {
                            throw new index_js_1.MissingEnvironmentVariableError("BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID", "Browserbase environment");
                        }
                        this.logger({
                            category: "init",
                            message: "Starting browserbase session",
                            level: 1,
                        });
                        const baseSessionParams = this.opts.browserbaseSessionCreateParams ?? {};
                        const resolvedKeepAlive = this.keepAlive;
                        const keepAlive = this.keepAlive === true;
                        const effectiveSessionParams = resolvedKeepAlive !== undefined
                            ? { ...baseSessionParams, keepAlive: resolvedKeepAlive }
                            : baseSessionParams;
                        if (!this.disableAPI && !this.experimental) {
                            this.apiClient = new api_js_1.StagehandAPIClient({
                                apiKey,
                                projectId,
                                logger: this.logger,
                                serverCache: this.opts.serverCache,
                            });
                            const createSessionPayload = {
                                projectId: effectiveSessionParams.projectId ?? projectId,
                                ...effectiveSessionParams,
                                browserSettings: {
                                    ...(effectiveSessionParams.browserSettings ?? {}),
                                    viewport: effectiveSessionParams.browserSettings?.viewport ?? {
                                        width: 1288,
                                        height: 711,
                                    },
                                },
                                userMetadata: {
                                    ...(effectiveSessionParams.userMetadata ?? {}),
                                    stagehand: "true",
                                },
                            };
                            const { sessionId, available } = await this.apiClient.init({
                                modelName: this.modelName,
                                modelApiKey: this.modelClientOptions.apiKey,
                                domSettleTimeoutMs: this.domSettleTimeoutMs,
                                verbose: this.verbose,
                                systemPrompt: this.opts.systemPrompt,
                                selfHeal: this.opts.selfHeal,
                                browserbaseSessionCreateParams: createSessionPayload,
                                browserbaseSessionID: this.opts.browserbaseSessionID,
                            });
                            if (!available) {
                                this.apiClient = null;
                            }
                            this.opts.browserbaseSessionID = sessionId;
                        }
                        const { ws, sessionId, bb } = await (0, browserbase_js_1.createBrowserbaseSession)(apiKey, projectId, effectiveSessionParams, this.opts.browserbaseSessionID);
                        this.ctx = await context_js_1.V3Context.create(ws, {
                            env: "BROWSERBASE",
                            apiClient: this.apiClient,
                        });
                        const logCtx = flowLogger_js_1.SessionFileLogger.getContext();
                        this.ctx.conn.cdpLogger = (info) => flowLogger_js_1.SessionFileLogger.logCdpCallEvent(info, logCtx);
                        this.ctx.conn.cdpEventLogger = (info) => flowLogger_js_1.SessionFileLogger.logCdpMessageEvent(info, logCtx);
                        this.ctx.conn.onTransportClosed(this._onCdpClosed);
                        this.state = { kind: "BROWSERBASE", sessionId, ws, bb };
                        this.browserbaseSessionId = sessionId;
                        if (!keepAlive && !this.disableAPI) {
                            this.startShutdownSupervisor({
                                kind: "STAGEHAND_API",
                                sessionId,
                                apiKey,
                                projectId,
                            });
                        }
                        await this._ensureBrowserbaseDownloadsEnabled();
                        const resumed = !!this.opts.browserbaseSessionID;
                        let debugUrl;
                        try {
                            const dbg = (await bb.sessions.debug(sessionId));
                            debugUrl = dbg?.debuggerUrl;
                        }
                        catch {
                            // Ignore debug fetch failures; continue with sessionUrl only
                        }
                        const sessionUrl = `https://www.browserbase.com/sessions/${sessionId}`;
                        this.browserbaseSessionUrl = sessionUrl;
                        this.browserbaseDebugUrl = debugUrl;
                        try {
                            this.logger({
                                category: "init",
                                message: resumed
                                    ? this.apiClient
                                        ? "Browserbase session started"
                                        : "Browserbase session resumed"
                                    : "Browserbase session started",
                                level: 1,
                                auxiliary: {
                                    sessionUrl: { value: sessionUrl, type: "string" },
                                    ...(debugUrl && {
                                        debugUrl: { value: debugUrl, type: "string" },
                                    }),
                                    sessionId: { value: sessionId, type: "string" },
                                },
                            });
                        }
                        catch {
                            // best-effort logging — ignore failures
                        }
                        return;
                    }
                    const neverEnv = this.opts.env;
                    throw new index_js_1.StagehandInitError(`Unsupported env: ${neverEnv}`);
                });
            }
            catch (error) {
                // Cleanup instanceLoggers map on init failure to prevent memory leak
                if (this.externalLogger) {
                    try {
                        (0, logger_js_2.unbindInstanceLogger)(this.instanceId);
                    }
                    catch {
                        // ignore cleanup errors
                    }
                }
                throw error;
            }
        }
        /** Apply post-connect local browser options that require CDP. */
        async _applyPostConnectLocalOptions(lbo) {
            try {
                // Downloads behavior
                if (lbo.downloadsPath || lbo.acceptDownloads !== undefined) {
                    const behavior = lbo.acceptDownloads === false ? "deny" : "allow";
                    await this.ctx?.conn
                        .send("Browser.setDownloadBehavior", {
                        behavior,
                        downloadPath: lbo.downloadsPath,
                        eventsEnabled: true,
                    })
                        .catch(() => { });
                }
            }
            catch {
                // best-effort only
            }
        }
        async _ensureBrowserbaseDownloadsEnabled() {
            const conn = this.ctx?.conn;
            if (!conn)
                return;
            try {
                await conn.send("Browser.setDownloadBehavior", {
                    behavior: "allow",
                    downloadPath: "downloads",
                    eventsEnabled: true,
                });
            }
            catch {
                // best-effort only
            }
        }
        resetBrowserbaseSessionMetadata() {
            this.browserbaseSessionId = undefined;
            this.browserbaseSessionUrl = undefined;
            this.browserbaseDebugUrl = undefined;
        }
        async act(input, options) {
            return await (0, logger_js_2.withInstanceLogContext)(this.instanceId, async () => {
                if (!this.actHandler)
                    throw new index_js_1.StagehandNotInitializedError("act()");
                let actResult;
                if (isObserveResult(input)) {
                    // Resolve page: use provided page if any, otherwise default active page
                    const v3Page = await this.resolvePage(options?.page);
                    // Use selector as provided to support XPath, CSS, and other engines
                    const selector = input.selector;
                    if (this.apiClient) {
                        actResult = await this.apiClient.act({
                            input,
                            options,
                            frameId: v3Page.mainFrameId(),
                        });
                    }
                    else {
                        const effectiveTimeoutMs = typeof options?.timeout === "number" && options.timeout > 0
                            ? options.timeout
                            : undefined;
                        const ensureTimeRemaining = (0, timeoutGuard_js_1.createTimeoutGuard)(effectiveTimeoutMs, (ms) => new sdkErrors_js_1.ActTimeoutError(ms));
                        actResult = await this.actHandler.takeDeterministicAction({ ...input, selector }, v3Page, this.domSettleTimeoutMs, this.resolveLlmClient(options?.model), ensureTimeRemaining, options?.variables);
                    }
                    // history: record ObserveResult-based act call
                    this.addToHistory("act", {
                        observeResult: input,
                    }, actResult);
                    return actResult;
                }
                // instruction path
                if (typeof input !== "string" || !input.trim()) {
                    throw new index_js_1.StagehandInvalidArgumentError("act(): instruction string is required unless passing an Action");
                }
                // Resolve page from options or default
                const page = await this.resolvePage(options?.page);
                const actCacheLlmClient = options?.model
                    ? this.resolveLlmClient(options.model)
                    : undefined;
                let actCacheContext = null;
                const canUseCache = typeof input === "string" &&
                    !this.isAgentReplayRecording() &&
                    this.actCache.enabled;
                if (canUseCache) {
                    actCacheContext = await this.actCache.prepareContext(input, page, (0, variables_js_1.flattenVariables)(options?.variables));
                    if (actCacheContext) {
                        const cachedResult = await this.actCache.tryReplay(actCacheContext, page, options?.timeout, actCacheLlmClient);
                        if (cachedResult) {
                            this.addToHistory("act", {
                                instruction: input,
                                variables: options?.variables,
                                timeout: options?.timeout,
                                cacheHit: true,
                            }, cachedResult);
                            return cachedResult;
                        }
                    }
                }
                const handlerParams = {
                    instruction: input,
                    page,
                    variables: options?.variables,
                    timeout: options?.timeout,
                    model: options?.model,
                };
                if (this.apiClient) {
                    const frameId = page.mainFrameId();
                    actResult = await this.apiClient.act({ input, options, frameId });
                }
                else {
                    actResult = await this.actHandler.act(handlerParams);
                }
                // history: record instruction-based act call (omit page object)
                this.addToHistory("act", {
                    instruction: input,
                    variables: options?.variables,
                    timeout: options?.timeout,
                }, actResult);
                if (actCacheContext &&
                    actResult.success &&
                    Array.isArray(actResult.actions) &&
                    actResult.actions.length > 0) {
                    await this.actCache.store(actCacheContext, actResult);
                }
                return actResult;
            });
        }
        async extract(a, b, c) {
            return await (0, logger_js_2.withInstanceLogContext)(this.instanceId, async () => {
                if (!this.extractHandler) {
                    throw new index_js_1.StagehandNotInitializedError("extract()");
                }
                // Normalize args
                let instruction;
                let schema;
                let options;
                if (typeof a === "string") {
                    instruction = a;
                    const isZodSchema = (val) => !!val &&
                        typeof val === "object" &&
                        "parse" in val &&
                        "safeParse" in val;
                    if (isZodSchema(b)) {
                        schema = b;
                        options = c;
                    }
                    else {
                        options = b;
                    }
                }
                else {
                    // a is options or undefined
                    options = a || undefined;
                }
                if (!instruction && schema) {
                    throw new index_js_1.StagehandInvalidArgumentError("extract(): schema provided without instruction");
                }
                // If instruction without schema → defaultExtractSchema
                const effectiveSchema = instruction && !schema ? index_js_1.defaultExtractSchema : schema;
                // Resolve page from options or use active page
                const page = await this.resolvePage(options?.page);
                const handlerParams = {
                    instruction,
                    schema: effectiveSchema,
                    model: options?.model,
                    timeout: options?.timeout,
                    selector: options?.selector,
                    page,
                };
                let result;
                if (this.apiClient) {
                    const frameId = page.mainFrameId();
                    result = await this.apiClient.extract({
                        instruction: handlerParams.instruction,
                        schema: handlerParams.schema,
                        options,
                        frameId,
                    });
                }
                else {
                    result =
                        await this.extractHandler.extract(handlerParams);
                }
                const historySchemaDescriptor = effectiveSchema
                    ? (0, zodCompat_js_1.toJsonSchema)(effectiveSchema)
                    : undefined;
                this.addToHistory("extract", {
                    instruction,
                    selector: options?.selector,
                    timeout: options?.timeout,
                    schema: historySchemaDescriptor,
                }, result);
                return result;
            });
        }
        async observe(a, b) {
            return await (0, logger_js_2.withInstanceLogContext)(this.instanceId, async () => {
                if (!this.observeHandler) {
                    throw new index_js_1.StagehandNotInitializedError("observe()");
                }
                // Normalize args
                let instruction;
                let options;
                if (typeof a === "string") {
                    instruction = a;
                    options = b;
                }
                else {
                    options = a;
                }
                // Resolve to our internal Page type
                const page = await this.resolvePage(options?.page);
                const handlerParams = {
                    instruction,
                    model: options?.model,
                    timeout: options?.timeout,
                    selector: options?.selector,
                    page: page,
                };
                let results;
                if (this.apiClient) {
                    const frameId = page.mainFrameId();
                    results = await this.apiClient.observe({
                        instruction,
                        options,
                        frameId,
                    });
                }
                else {
                    results = await this.observeHandler.observe(handlerParams);
                }
                // history: record observe call (omit page object)
                this.addToHistory("observe", {
                    instruction,
                    timeout: options?.timeout,
                }, results);
                return results;
            });
        }
        /** Return the browser-level CDP WebSocket endpoint. */
        connectURL() {
            if (this.state.kind === "UNINITIALIZED") {
                throw new index_js_1.StagehandNotInitializedError("connectURL()");
            }
            return this.state.ws;
        }
        /** Expose the current CDP-backed context. */
        get context() {
            return this.ctx;
        }
        /** Best-effort cleanup of context and launched resources. */
        async close(opts) {
            // If we're already closing and this isn't a forced close, no-op.
            if (this._isClosing && !opts?.force)
                return;
            this._isClosing = true;
            const keepAlive = this.keepAlive === true;
            // Unhook CDP transport close handler BEFORE ending the API session.
            // apiClient.end() can cause the hosted API to terminate the Browserbase
            // session, which closes the CDP WebSocket. If the handler is still
            // registered, _onCdpClosed fires and re-enters close() with force=true,
            // causing a double-close cascade.
            try {
                if (this.ctx?.conn && this._onCdpClosed) {
                    this.ctx.conn.offTransportClosed?.(this._onCdpClosed);
                }
            }
            catch {
                // ignore
            }
            // End Browserbase session via API when keepAlive is not enabled
            if (!keepAlive && this.apiClient) {
                try {
                    await this.apiClient.end();
                }
                catch {
                    // best-effort cleanup
                }
            }
            try {
                // Close session file logger
                try {
                    await flowLogger_js_1.SessionFileLogger.close();
                }
                catch {
                    // ignore
                }
                // Close CDP context
                try {
                    await this.ctx?.close();
                }
                catch {
                    // ignore
                }
                // Kill local Chrome and clean up temp profile when keepAlive is not enabled
                if (!keepAlive && this.state.kind === "LOCAL") {
                    const localState = this.state;
                    await (0, cleanupLocal_js_1.cleanupLocalBrowser)({
                        killChrome: () => localState.chrome.kill(),
                        userDataDir: localState.userDataDir,
                        createdTempProfile: localState.createdTempProfile,
                        preserveUserDataDir: localState.preserveUserDataDir,
                    });
                }
            }
            finally {
                this.stopShutdownSupervisor();
                // Reset internal state
                this.state = { kind: "UNINITIALIZED" };
                this.ctx = null;
                this._isClosing = false;
                this.resetBrowserbaseSessionMetadata();
                try {
                    (0, logger_js_2.unbindInstanceLogger)(this.instanceId);
                }
                catch {
                    // ignore
                }
                try {
                    this.bus.removeAllListeners();
                }
                catch {
                    // ignore
                }
                this._history = [];
                this.actHandler = null;
                this.extractHandler = null;
                this.observeHandler = null;
                V3._instances.delete(this);
            }
        }
        /** Guard: ensure Browserbase credentials exist in options. */
        requireBrowserbaseCreds() {
            let { apiKey, projectId } = this.opts;
            // Fall back to environment variables if not explicitly provided
            if (!apiKey)
                apiKey = process_1.default.env.BROWSERBASE_API_KEY ?? process_1.default.env.BB_API_KEY;
            if (!projectId)
                projectId =
                    process_1.default.env.BROWSERBASE_PROJECT_ID ?? process_1.default.env.BB_PROJECT_ID;
            if (!apiKey || !projectId) {
                const missing = [];
                if (!apiKey)
                    missing.push("BROWSERBASE_API_KEY");
                if (!projectId)
                    missing.push("BROWSERBASE_PROJECT_ID");
                throw new index_js_1.MissingEnvironmentVariableError(missing.join(", "), "Browserbase");
            }
            // Cache resolved values back into opts for consistency
            this.opts.apiKey = apiKey;
            this.opts.projectId = projectId;
            // Informational log
            this.logger({
                category: "init",
                message: "Using Browserbase credentials",
                level: 1,
            });
            return { apiKey, projectId };
        }
        get logger() {
            // Delegate to per-instance StagehandLogger
            // StagehandLogger handles: verbosity filtering, usePino selection, external logger routing
            // This provides per-instance configuration while maintaining shared Pino optimization
            return (logLine) => {
                const line = { ...logLine, level: logLine.level ?? 1 };
                this.stagehandLogger.log(line);
            };
        }
        /**
         * Normalize a Playwright/Puppeteer page object into its top frame id,
         * so handlers can resolve it to a `Page` within our V3Context.
         */
        async resolveTopFrameId(page) {
            if (this.isPlaywrightPage(page)) {
                const cdp = await page.context().newCDPSession(page);
                const { frameTree } = await cdp.send("Page.getFrameTree");
                return frameTree.frame.id;
            }
            if (this.isPatchrightPage(page)) {
                const cdp = await page.context().newCDPSession(page);
                const { frameTree } = await cdp.send("Page.getFrameTree");
                return frameTree.frame.id;
            }
            if (this.isPuppeteerPage(page)) {
                const cdp = await page.createCDPSession();
                const { frameTree } = await cdp.send("Page.getFrameTree");
                this.logger({
                    category: "v3",
                    message: "Puppeteer frame id",
                    level: 2,
                    auxiliary: { frameId: { value: frameTree.frame.id, type: "string" } },
                });
                return frameTree.frame.id;
            }
            throw new index_js_1.StagehandInvalidArgumentError("Unsupported page object passed to V3.act()");
        }
        isPlaywrightPage(p) {
            return (typeof p === "object" &&
                p !== null &&
                typeof p.context === "function");
        }
        isPatchrightPage(p) {
            return (typeof p === "object" &&
                p !== null &&
                typeof p.context === "function");
        }
        isPuppeteerPage(p) {
            return (typeof p === "object" &&
                p !== null &&
                typeof p.target === "function");
        }
        /** Resolve an external page reference or fall back to the active V3 page. */
        async resolvePage(page) {
            if (page) {
                return await this.normalizeToV3Page(page);
            }
            const ctx = this.ctx;
            if (!ctx) {
                throw new index_js_1.StagehandNotInitializedError("resolvePage()");
            }
            return await ctx.awaitActivePage();
        }
        async normalizeToV3Page(input) {
            if (input instanceof (await Promise.resolve().then(() => __importStar(require("./understudy/page.js")))).Page) {
                return input;
            }
            if (this.isPlaywrightPage(input)) {
                const frameId = await this.resolveTopFrameId(input);
                const page = this.ctx.resolvePageByMainFrameId(frameId);
                if (!page)
                    throw new index_js_1.StagehandInitError("Failed to resolve V3 Page from Playwright page.");
                return page;
            }
            if (this.isPatchrightPage(input)) {
                const frameId = await this.resolveTopFrameId(input);
                const page = this.ctx.resolvePageByMainFrameId(frameId);
                if (!page)
                    throw new index_js_1.StagehandInitError("Failed to resolve V3 Page from Patchright page.");
                return page;
            }
            if (this.isPuppeteerPage(input)) {
                const frameId = await this.resolveTopFrameId(input);
                const page = this.ctx.resolvePageByMainFrameId(frameId);
                if (!page)
                    throw new index_js_1.StagehandInitError("Failed to resolve V3 Page from Puppeteer page.");
                return page;
            }
            throw new index_js_1.StagehandInvalidArgumentError("Unsupported page object.");
        }
        async _logBrowserbaseSessionStatus() {
            if (this.state.kind !== "BROWSERBASE") {
                return;
            }
            try {
                const snapshot = (await this.state.bb.sessions.retrieve(this.state.sessionId));
                if (!snapshot?.status)
                    return;
                const sessionId = snapshot.id ?? this.state.sessionId;
                const message = snapshot.status === "TIMED_OUT"
                    ? `Browserbase session timed out (sessionId: ${sessionId})`
                    : `Browserbase session status: ${snapshot.status}`;
                this.logger({
                    category: "v3",
                    message,
                    level: 0,
                });
            }
            catch {
                // Ignore failures; nothing to log
            }
        }
        /**
         * Prepares shared context for agent execution (both execute and stream).
         * Extracts duplicated setup logic into a single helper.
         */
        async prepareAgentExecution(options, instructionOrOptions, agentConfigSignature) {
            // Note: experimental validation is done at the call site before this method
            // Warn if mode is not explicitly set (defaults to "dom")
            if (options?.mode === undefined) {
                this.logger({
                    category: "agent",
                    message: "Using agent in default DOM mode (legacy). Agent will default to 'hybrid' on an upcoming release for improved performance.\n  → https://docs.stagehand.dev/v3/basics/agent\n",
                    level: 0,
                });
            }
            const tools = options?.integrations
                ? await (0, utils_js_2.resolveTools)(options.integrations, options.tools)
                : (options?.tools ?? {});
            const agentLlmClient = options?.model
                ? this.resolveLlmClient(options.model)
                : this.llmClient;
            const resolvedExecutionModel = options?.executionModel ?? options?.model;
            const handler = new v3AgentHandler_js_1.V3AgentHandler(this, this.logger, agentLlmClient, resolvedExecutionModel, options?.systemPrompt, tools, options?.mode);
            const resolvedOptions = typeof instructionOrOptions === "string"
                ? { instruction: instructionOrOptions }
                : instructionOrOptions;
            const callbacksWithSafety = resolvedOptions.callbacks;
            if (callbacksWithSafety?.onSafetyConfirmation) {
                throw new index_js_1.StagehandInvalidArgumentError('onSafetyConfirmation callback is only supported when using mode: "cua" agents.');
            }
            if (resolvedOptions.page) {
                const normalizedPage = await this.normalizeToV3Page(resolvedOptions.page);
                this.ctx.setActivePage(normalizedPage);
            }
            const instruction = resolvedOptions.instruction.trim();
            const sanitizedOptions = this.agentCache.sanitizeExecuteOptions(resolvedOptions);
            const cacheVariables = (0, variables_js_1.flattenVariables)(resolvedOptions.variables);
            const cacheContext = this.agentCache.shouldAttemptCache(instruction)
                ? await this.agentCache.prepareContext({
                    instruction,
                    options: sanitizedOptions,
                    configSignature: agentConfigSignature,
                    page: await this.ctx.awaitActivePage(),
                    variables: cacheVariables,
                })
                : null;
            return {
                handler,
                resolvedOptions,
                instruction,
                cacheContext,
                llmClient: agentLlmClient,
            };
        }
        agent(options) {
            // Determine if CUA mode is enabled (via mode: "cua" or deprecated cua: true)
            const isCuaMode = options?.mode !== undefined
                ? options.mode === "cua"
                : options?.cua === true;
            // Emit deprecation warning for cua: true
            if (options?.cua === true) {
                this.logger({
                    category: "agent",
                    message: '[DEPRECATED] The "cua: true" option is deprecated. Use "mode: \'cua\'" instead. This option will be removed in a future version.',
                    level: 0,
                });
                console.warn('[Stagehand] DEPRECATED: The "cua: true" option is deprecated. Use "mode: \'cua\'" instead.');
            }
            this.logger({
                category: "agent",
                message: "Creating v3 agent instance",
                level: 1,
                auxiliary: {
                    cua: { value: isCuaMode ? "true" : "false", type: "boolean" },
                    mode: { value: options?.mode ?? "dom", type: "string" },
                    model: {
                        value: (0, modelUtils_js_1.extractModelName)(options?.model) ?? this.llmClient.modelName,
                        type: "string",
                    },
                    systemPrompt: { value: options?.systemPrompt ?? "", type: "string" },
                    tools: { value: JSON.stringify(options?.tools ?? {}), type: "object" },
                    ...(options?.integrations && {
                        integrations: {
                            value: JSON.stringify(options.integrations),
                            type: "object",
                        },
                    }),
                },
            });
            // If CUA mode is enabled (via mode: "cua" or deprecated cua: true), use the computer-use agent path
            if (isCuaMode) {
                // Validate agent config at creation time (includes CUA+streaming conflict check)
                (0, validateExperimentalFeatures_js_1.validateExperimentalFeatures)({
                    isExperimental: this.experimental,
                    agentConfig: options,
                });
                const modelToUse = options?.model || {
                    modelName: this.modelName,
                    ...this.modelClientOptions,
                };
                const { modelName, isCua, clientOptions } = (0, modelUtils_js_2.resolveModel)(modelToUse);
                if (!isCua) {
                    throw new index_js_1.CuaModelRequiredError(index_js_1.AVAILABLE_CUA_MODELS);
                }
                const agentConfigSignature = this.agentCache.buildConfigSignature(options);
                return {
                    execute: async (instructionOrOptions) => (0, logger_js_2.withInstanceLogContext)(this.instanceId, async () => {
                        (0, validateExperimentalFeatures_js_1.validateExperimentalFeatures)({
                            isExperimental: this.experimental,
                            agentConfig: options,
                            executeOptions: typeof instructionOrOptions === "object"
                                ? instructionOrOptions
                                : null,
                        });
                        flowLogger_js_1.SessionFileLogger.logAgentTaskStarted({
                            invocation: "Agent.execute",
                            args: [instructionOrOptions],
                        });
                        const tools = options?.integrations
                            ? await (0, utils_js_2.resolveTools)(options.integrations, options.tools)
                            : (options?.tools ?? {});
                        const handler = new v3CuaAgentHandler_js_1.V3CuaAgentHandler(this, this.logger, {
                            modelName,
                            clientOptions,
                            userProvidedInstructions: options.systemPrompt ??
                                `You are a helpful assistant that can use a web browser.\nDo not ask follow up questions, the user will trust your judgement.`,
                        }, tools);
                        const resolvedOptions = typeof instructionOrOptions === "string"
                            ? { instruction: instructionOrOptions }
                            : instructionOrOptions;
                        if (resolvedOptions.page) {
                            const normalizedPage = await this.normalizeToV3Page(resolvedOptions.page);
                            this.ctx.setActivePage(normalizedPage);
                        }
                        const instruction = resolvedOptions.instruction.trim();
                        const sanitizedOptions = this.agentCache.sanitizeExecuteOptions(resolvedOptions);
                        const cacheVariables = (0, variables_js_1.flattenVariables)(resolvedOptions.variables);
                        let cacheContext = null;
                        if (this.agentCache.shouldAttemptCache(instruction)) {
                            const startPage = await this.ctx.awaitActivePage();
                            cacheContext = await this.agentCache.prepareContext({
                                instruction,
                                options: sanitizedOptions,
                                configSignature: agentConfigSignature,
                                page: startPage,
                                variables: cacheVariables,
                            });
                            if (cacheContext) {
                                const replayed = await this.agentCache.tryReplay(cacheContext);
                                if (replayed) {
                                    flowLogger_js_1.SessionFileLogger.logAgentTaskCompleted({ cacheHit: true });
                                    return replayed;
                                }
                            }
                        }
                        let agentSteps = [];
                        const shouldRecordLocally = !!cacheContext && (!this.apiClient || this.experimental);
                        if (shouldRecordLocally) {
                            this.beginAgentReplayRecording();
                        }
                        let result;
                        try {
                            if (this.apiClient && !this.experimental) {
                                const page = await this.ctx.awaitActivePage();
                                result = await this.apiClient.agentExecute(options, resolvedOptions, page.mainFrameId(), !!cacheContext);
                                if (cacheContext) {
                                    const transferredEntry = this.apiClient.consumeLatestAgentCacheEntry();
                                    await this.agentCache.storeTransferredEntry(transferredEntry);
                                }
                            }
                            else {
                                result = await handler.execute(instructionOrOptions);
                            }
                            if (shouldRecordLocally) {
                                agentSteps = this.endAgentReplayRecording();
                            }
                            if (shouldRecordLocally &&
                                cacheContext &&
                                result.success &&
                                agentSteps.length > 0) {
                                await this.agentCache.store(cacheContext, agentSteps, result);
                            }
                            return result;
                        }
                        catch (err) {
                            if (shouldRecordLocally)
                                this.discardAgentReplayRecording();
                            throw err;
                        }
                        finally {
                            if (shouldRecordLocally) {
                                this.discardAgentReplayRecording();
                            }
                            flowLogger_js_1.SessionFileLogger.logAgentTaskCompleted();
                        }
                    }),
                };
            }
            // Default: AISDK tools-based agent
            const agentConfigSignature = this.agentCache.buildConfigSignature(options);
            const isStreaming = options?.stream ?? false;
            return {
                execute: async (instructionOrOptions) => (0, logger_js_2.withInstanceLogContext)(this.instanceId, async () => {
                    (0, validateExperimentalFeatures_js_1.validateExperimentalFeatures)({
                        isExperimental: this.experimental,
                        agentConfig: options,
                        executeOptions: typeof instructionOrOptions === "object"
                            ? instructionOrOptions
                            : null,
                        isStreaming,
                    });
                    flowLogger_js_1.SessionFileLogger.logAgentTaskStarted({
                        invocation: "Agent.execute",
                        args: [instructionOrOptions],
                    });
                    // Streaming mode
                    if (isStreaming) {
                        const { handler, resolvedOptions, cacheContext, llmClient } = await this.prepareAgentExecution(options, instructionOrOptions, agentConfigSignature);
                        if (cacheContext) {
                            const replayed = await this.agentCache.tryReplayAsStream(cacheContext, llmClient);
                            if (replayed) {
                                flowLogger_js_1.SessionFileLogger.logAgentTaskCompleted({ cacheHit: true });
                                return replayed;
                            }
                        }
                        const streamResult = await handler.stream(resolvedOptions);
                        if (cacheContext) {
                            const wrappedStream = this.agentCache.wrapStreamForCaching(cacheContext, streamResult, () => this.beginAgentReplayRecording(), () => this.endAgentReplayRecording(), () => this.discardAgentReplayRecording());
                            // Log completion when stream is returned (stream completes asynchronously)
                            flowLogger_js_1.SessionFileLogger.logAgentTaskCompleted();
                            return wrappedStream;
                        }
                        // Log completion when stream is returned (stream completes asynchronously)
                        flowLogger_js_1.SessionFileLogger.logAgentTaskCompleted();
                        return streamResult;
                    }
                    // Non-streaming mode (default)
                    const { handler, resolvedOptions, cacheContext, llmClient } = await this.prepareAgentExecution(options, instructionOrOptions, agentConfigSignature);
                    if (cacheContext) {
                        const replayed = await this.agentCache.tryReplay(cacheContext, llmClient);
                        if (replayed) {
                            flowLogger_js_1.SessionFileLogger.logAgentTaskCompleted({ cacheHit: true });
                            return replayed;
                        }
                    }
                    let agentSteps = [];
                    const shouldRecordLocally = !!cacheContext && (!this.apiClient || this.experimental);
                    if (shouldRecordLocally) {
                        this.beginAgentReplayRecording();
                    }
                    let result;
                    try {
                        if (this.apiClient && !this.experimental) {
                            const page = await this.ctx.awaitActivePage();
                            result = await this.apiClient.agentExecute(options ?? {}, resolvedOptions, page.mainFrameId(), !!cacheContext);
                            if (cacheContext) {
                                const transferredEntry = this.apiClient.consumeLatestAgentCacheEntry();
                                await this.agentCache.storeTransferredEntry(transferredEntry);
                            }
                        }
                        else {
                            result = await handler.execute(resolvedOptions);
                        }
                        if (shouldRecordLocally) {
                            agentSteps = this.endAgentReplayRecording();
                        }
                        if (shouldRecordLocally &&
                            cacheContext &&
                            result.success &&
                            agentSteps.length > 0) {
                            await this.agentCache.store(cacheContext, agentSteps, result);
                        }
                        return result;
                    }
                    catch (err) {
                        if (shouldRecordLocally)
                            this.discardAgentReplayRecording();
                        throw err;
                    }
                    finally {
                        if (shouldRecordLocally) {
                            this.discardAgentReplayRecording();
                        }
                        flowLogger_js_1.SessionFileLogger.logAgentTaskCompleted();
                    }
                }),
            };
        }
    };
})();
exports.V3 = V3;
function isObserveResult(v) {
    return (!!v && typeof v === "object" && "selector" in v);
}
//# sourceMappingURL=v3.js.map