UNPKG

test-wuying-agentbay-sdk

Version:

TypeScript SDK for interacting with the Wuying AgentBay cloud runtime environment

1 lines 715 kB
{"version":3,"sources":["../node_modules/dotenv/package.json","../node_modules/dotenv/lib/main.js","../node_modules/dotenv/lib/env-options.js","../node_modules/dotenv/lib/cli-options.js","../src/index.ts","../src/version.ts","../src/agent-bay.ts","../node_modules/dotenv/config.js","../src/api/index.ts","../src/api/client.ts","../src/context.ts","../src/exceptions.ts","../src/utils/logger.ts","../src/types/api-response.ts","../src/context-sync.ts","../src/session.ts","../src/agent/agent.ts","../src/browser/index.ts","../src/browser/browser.ts","../src/browser/browser_agent.ts","../src/browser/fingerprint.ts","../src/code/index.ts","../src/code/code.ts","../src/command/command.ts","../src/command/command-templates.ts","../src/computer/index.ts","../src/computer/computer.ts","../src/context-manager.ts","../src/filesystem/filesystem.ts","../src/filesystem/file-transfer.ts","../src/mobile/index.ts","../src/mobile/mobile.ts","../src/oss/index.ts","../src/oss/oss.ts","../src/agent/index.ts","../src/extension.ts","../src/types/index.ts","../src/types/list-session-params.ts","../src/types/extra-configs.ts","../src/session-params.ts"],"sourcesContent":["{\n \"name\": \"dotenv\",\n \"version\": \"16.6.1\",\n \"description\": \"Loads environment variables from .env file\",\n \"main\": \"lib/main.js\",\n \"types\": \"lib/main.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./lib/main.d.ts\",\n \"require\": \"./lib/main.js\",\n \"default\": \"./lib/main.js\"\n },\n \"./config\": \"./config.js\",\n \"./config.js\": \"./config.js\",\n \"./lib/env-options\": \"./lib/env-options.js\",\n \"./lib/env-options.js\": \"./lib/env-options.js\",\n \"./lib/cli-options\": \"./lib/cli-options.js\",\n \"./lib/cli-options.js\": \"./lib/cli-options.js\",\n \"./package.json\": \"./package.json\"\n },\n \"scripts\": {\n \"dts-check\": \"tsc --project tests/types/tsconfig.json\",\n \"lint\": \"standard\",\n \"pretest\": \"npm run lint && npm run dts-check\",\n \"test\": \"tap run --allow-empty-coverage --disable-coverage --timeout=60000\",\n \"test:coverage\": \"tap run --show-full-coverage --timeout=60000 --coverage-report=text --coverage-report=lcov\",\n \"prerelease\": \"npm test\",\n \"release\": \"standard-version\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git://github.com/motdotla/dotenv.git\"\n },\n \"homepage\": \"https://github.com/motdotla/dotenv#readme\",\n \"funding\": \"https://dotenvx.com\",\n \"keywords\": [\n \"dotenv\",\n \"env\",\n \".env\",\n \"environment\",\n \"variables\",\n \"config\",\n \"settings\"\n ],\n \"readmeFilename\": \"README.md\",\n \"license\": \"BSD-2-Clause\",\n \"devDependencies\": {\n \"@types/node\": \"^18.11.3\",\n \"decache\": \"^4.6.2\",\n \"sinon\": \"^14.0.1\",\n \"standard\": \"^17.0.0\",\n \"standard-version\": \"^9.5.0\",\n \"tap\": \"^19.2.0\",\n \"typescript\": \"^4.8.4\"\n },\n \"engines\": {\n \"node\": \">=12\"\n },\n \"browser\": {\n \"fs\": false\n }\n}\n","const fs = require('fs')\nconst path = require('path')\nconst os = require('os')\nconst crypto = require('crypto')\nconst packageJson = require('../package.json')\n\nconst version = packageJson.version\n\nconst LINE = /(?:^|^)\\s*(?:export\\s+)?([\\w.-]+)(?:\\s*=\\s*?|:\\s+?)(\\s*'(?:\\\\'|[^'])*'|\\s*\"(?:\\\\\"|[^\"])*\"|\\s*`(?:\\\\`|[^`])*`|[^#\\r\\n]+)?\\s*(?:#.*)?(?:$|$)/mg\n\n// Parse src into an Object\nfunction parse (src) {\n const obj = {}\n\n // Convert buffer to string\n let lines = src.toString()\n\n // Convert line breaks to same format\n lines = lines.replace(/\\r\\n?/mg, '\\n')\n\n let match\n while ((match = LINE.exec(lines)) != null) {\n const key = match[1]\n\n // Default undefined or null to empty string\n let value = (match[2] || '')\n\n // Remove whitespace\n value = value.trim()\n\n // Check if double quoted\n const maybeQuote = value[0]\n\n // Remove surrounding quotes\n value = value.replace(/^(['\"`])([\\s\\S]*)\\1$/mg, '$2')\n\n // Expand newlines if double quoted\n if (maybeQuote === '\"') {\n value = value.replace(/\\\\n/g, '\\n')\n value = value.replace(/\\\\r/g, '\\r')\n }\n\n // Add to object\n obj[key] = value\n }\n\n return obj\n}\n\nfunction _parseVault (options) {\n options = options || {}\n\n const vaultPath = _vaultPath(options)\n options.path = vaultPath // parse .env.vault\n const result = DotenvModule.configDotenv(options)\n if (!result.parsed) {\n const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`)\n err.code = 'MISSING_DATA'\n throw err\n }\n\n // handle scenario for comma separated keys - for use with key rotation\n // example: DOTENV_KEY=\"dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenvx.com/vault/.env.vault?environment=prod\"\n const keys = _dotenvKey(options).split(',')\n const length = keys.length\n\n let decrypted\n for (let i = 0; i < length; i++) {\n try {\n // Get full key\n const key = keys[i].trim()\n\n // Get instructions for decrypt\n const attrs = _instructions(result, key)\n\n // Decrypt\n decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key)\n\n break\n } catch (error) {\n // last key\n if (i + 1 >= length) {\n throw error\n }\n // try next key\n }\n }\n\n // Parse decrypted .env string\n return DotenvModule.parse(decrypted)\n}\n\nfunction _warn (message) {\n console.log(`[dotenv@${version}][WARN] ${message}`)\n}\n\nfunction _debug (message) {\n console.log(`[dotenv@${version}][DEBUG] ${message}`)\n}\n\nfunction _log (message) {\n console.log(`[dotenv@${version}] ${message}`)\n}\n\nfunction _dotenvKey (options) {\n // prioritize developer directly setting options.DOTENV_KEY\n if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {\n return options.DOTENV_KEY\n }\n\n // secondary infra already contains a DOTENV_KEY environment variable\n if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {\n return process.env.DOTENV_KEY\n }\n\n // fallback to empty string\n return ''\n}\n\nfunction _instructions (result, dotenvKey) {\n // Parse DOTENV_KEY. Format is a URI\n let uri\n try {\n uri = new URL(dotenvKey)\n } catch (error) {\n if (error.code === 'ERR_INVALID_URL') {\n const err = new Error('INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development')\n err.code = 'INVALID_DOTENV_KEY'\n throw err\n }\n\n throw error\n }\n\n // Get decrypt key\n const key = uri.password\n if (!key) {\n const err = new Error('INVALID_DOTENV_KEY: Missing key part')\n err.code = 'INVALID_DOTENV_KEY'\n throw err\n }\n\n // Get environment\n const environment = uri.searchParams.get('environment')\n if (!environment) {\n const err = new Error('INVALID_DOTENV_KEY: Missing environment part')\n err.code = 'INVALID_DOTENV_KEY'\n throw err\n }\n\n // Get ciphertext payload\n const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`\n const ciphertext = result.parsed[environmentKey] // DOTENV_VAULT_PRODUCTION\n if (!ciphertext) {\n const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`)\n err.code = 'NOT_FOUND_DOTENV_ENVIRONMENT'\n throw err\n }\n\n return { ciphertext, key }\n}\n\nfunction _vaultPath (options) {\n let possibleVaultPath = null\n\n if (options && options.path && options.path.length > 0) {\n if (Array.isArray(options.path)) {\n for (const filepath of options.path) {\n if (fs.existsSync(filepath)) {\n possibleVaultPath = filepath.endsWith('.vault') ? filepath : `${filepath}.vault`\n }\n }\n } else {\n possibleVaultPath = options.path.endsWith('.vault') ? options.path : `${options.path}.vault`\n }\n } else {\n possibleVaultPath = path.resolve(process.cwd(), '.env.vault')\n }\n\n if (fs.existsSync(possibleVaultPath)) {\n return possibleVaultPath\n }\n\n return null\n}\n\nfunction _resolveHome (envPath) {\n return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath\n}\n\nfunction _configVault (options) {\n const debug = Boolean(options && options.debug)\n const quiet = options && 'quiet' in options ? options.quiet : true\n\n if (debug || !quiet) {\n _log('Loading env from encrypted .env.vault')\n }\n\n const parsed = DotenvModule._parseVault(options)\n\n let processEnv = process.env\n if (options && options.processEnv != null) {\n processEnv = options.processEnv\n }\n\n DotenvModule.populate(processEnv, parsed, options)\n\n return { parsed }\n}\n\nfunction configDotenv (options) {\n const dotenvPath = path.resolve(process.cwd(), '.env')\n let encoding = 'utf8'\n const debug = Boolean(options && options.debug)\n const quiet = options && 'quiet' in options ? options.quiet : true\n\n if (options && options.encoding) {\n encoding = options.encoding\n } else {\n if (debug) {\n _debug('No encoding is specified. UTF-8 is used by default')\n }\n }\n\n let optionPaths = [dotenvPath] // default, look for .env\n if (options && options.path) {\n if (!Array.isArray(options.path)) {\n optionPaths = [_resolveHome(options.path)]\n } else {\n optionPaths = [] // reset default\n for (const filepath of options.path) {\n optionPaths.push(_resolveHome(filepath))\n }\n }\n }\n\n // Build the parsed data in a temporary object (because we need to return it). Once we have the final\n // parsed data, we will combine it with process.env (or options.processEnv if provided).\n let lastError\n const parsedAll = {}\n for (const path of optionPaths) {\n try {\n // Specifying an encoding returns a string instead of a buffer\n const parsed = DotenvModule.parse(fs.readFileSync(path, { encoding }))\n\n DotenvModule.populate(parsedAll, parsed, options)\n } catch (e) {\n if (debug) {\n _debug(`Failed to load ${path} ${e.message}`)\n }\n lastError = e\n }\n }\n\n let processEnv = process.env\n if (options && options.processEnv != null) {\n processEnv = options.processEnv\n }\n\n DotenvModule.populate(processEnv, parsedAll, options)\n\n if (debug || !quiet) {\n const keysCount = Object.keys(parsedAll).length\n const shortPaths = []\n for (const filePath of optionPaths) {\n try {\n const relative = path.relative(process.cwd(), filePath)\n shortPaths.push(relative)\n } catch (e) {\n if (debug) {\n _debug(`Failed to load ${filePath} ${e.message}`)\n }\n lastError = e\n }\n }\n\n _log(`injecting env (${keysCount}) from ${shortPaths.join(',')}`)\n }\n\n if (lastError) {\n return { parsed: parsedAll, error: lastError }\n } else {\n return { parsed: parsedAll }\n }\n}\n\n// Populates process.env from .env file\nfunction config (options) {\n // fallback to original dotenv if DOTENV_KEY is not set\n if (_dotenvKey(options).length === 0) {\n return DotenvModule.configDotenv(options)\n }\n\n const vaultPath = _vaultPath(options)\n\n // dotenvKey exists but .env.vault file does not exist\n if (!vaultPath) {\n _warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`)\n\n return DotenvModule.configDotenv(options)\n }\n\n return DotenvModule._configVault(options)\n}\n\nfunction decrypt (encrypted, keyStr) {\n const key = Buffer.from(keyStr.slice(-64), 'hex')\n let ciphertext = Buffer.from(encrypted, 'base64')\n\n const nonce = ciphertext.subarray(0, 12)\n const authTag = ciphertext.subarray(-16)\n ciphertext = ciphertext.subarray(12, -16)\n\n try {\n const aesgcm = crypto.createDecipheriv('aes-256-gcm', key, nonce)\n aesgcm.setAuthTag(authTag)\n return `${aesgcm.update(ciphertext)}${aesgcm.final()}`\n } catch (error) {\n const isRange = error instanceof RangeError\n const invalidKeyLength = error.message === 'Invalid key length'\n const decryptionFailed = error.message === 'Unsupported state or unable to authenticate data'\n\n if (isRange || invalidKeyLength) {\n const err = new Error('INVALID_DOTENV_KEY: It must be 64 characters long (or more)')\n err.code = 'INVALID_DOTENV_KEY'\n throw err\n } else if (decryptionFailed) {\n const err = new Error('DECRYPTION_FAILED: Please check your DOTENV_KEY')\n err.code = 'DECRYPTION_FAILED'\n throw err\n } else {\n throw error\n }\n }\n}\n\n// Populate process.env with parsed values\nfunction populate (processEnv, parsed, options = {}) {\n const debug = Boolean(options && options.debug)\n const override = Boolean(options && options.override)\n\n if (typeof parsed !== 'object') {\n const err = new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate')\n err.code = 'OBJECT_REQUIRED'\n throw err\n }\n\n // Set process.env\n for (const key of Object.keys(parsed)) {\n if (Object.prototype.hasOwnProperty.call(processEnv, key)) {\n if (override === true) {\n processEnv[key] = parsed[key]\n }\n\n if (debug) {\n if (override === true) {\n _debug(`\"${key}\" is already defined and WAS overwritten`)\n } else {\n _debug(`\"${key}\" is already defined and was NOT overwritten`)\n }\n }\n } else {\n processEnv[key] = parsed[key]\n }\n }\n}\n\nconst DotenvModule = {\n configDotenv,\n _configVault,\n _parseVault,\n config,\n decrypt,\n parse,\n populate\n}\n\nmodule.exports.configDotenv = DotenvModule.configDotenv\nmodule.exports._configVault = DotenvModule._configVault\nmodule.exports._parseVault = DotenvModule._parseVault\nmodule.exports.config = DotenvModule.config\nmodule.exports.decrypt = DotenvModule.decrypt\nmodule.exports.parse = DotenvModule.parse\nmodule.exports.populate = DotenvModule.populate\n\nmodule.exports = DotenvModule\n","// ../config.js accepts options via environment variables\nconst options = {}\n\nif (process.env.DOTENV_CONFIG_ENCODING != null) {\n options.encoding = process.env.DOTENV_CONFIG_ENCODING\n}\n\nif (process.env.DOTENV_CONFIG_PATH != null) {\n options.path = process.env.DOTENV_CONFIG_PATH\n}\n\nif (process.env.DOTENV_CONFIG_QUIET != null) {\n options.quiet = process.env.DOTENV_CONFIG_QUIET\n}\n\nif (process.env.DOTENV_CONFIG_DEBUG != null) {\n options.debug = process.env.DOTENV_CONFIG_DEBUG\n}\n\nif (process.env.DOTENV_CONFIG_OVERRIDE != null) {\n options.override = process.env.DOTENV_CONFIG_OVERRIDE\n}\n\nif (process.env.DOTENV_CONFIG_DOTENV_KEY != null) {\n options.DOTENV_KEY = process.env.DOTENV_CONFIG_DOTENV_KEY\n}\n\nmodule.exports = options\n","const re = /^dotenv_config_(encoding|path|quiet|debug|override|DOTENV_KEY)=(.+)$/\n\nmodule.exports = function optionMatcher (args) {\n const options = args.reduce(function (acc, cur) {\n const matches = cur.match(re)\n if (matches) {\n acc[matches[1]] = matches[2]\n }\n return acc\n }, {})\n\n if (!('quiet' in options)) {\n options.quiet = 'true'\n }\n\n return options\n}\n","// IMPORTANT: Load config first to ensure .env file is loaded before logger initialization\nexport { type Config } from \"./config\";\n\n// Export version information\nexport { VERSION, IS_RELEASE } from \"./version\";\n\n// Export all public classes and interfaces\nexport { AgentBay, type CreateSessionParams} from \"./agent-bay\";\nexport * from \"./agent\";\nexport * from \"./api\";\nexport * from \"./browser\";\nexport { Code } from \"./code\";\nexport * from \"./command\";\nexport { Context, ContextService } from \"./context\";\nexport { Computer } from \"./computer\";\nexport * from \"./exceptions\";\nexport * from \"./extension\";\nexport * from \"./filesystem\";\nexport * from \"./oss\";\nexport { Mobile } from \"./mobile\";\nexport { Session } from \"./session\";\nexport { type ListSessionParams } from \"./types\";\nexport * from \"./types\";\nexport * from \"./context-sync\";\nexport * from \"./context-manager\";\nexport * from \"./session-params\";\n// Export utility functions\nexport {\n log,\n logDebug,\n logInfo,\n logWarn,\n logError,\n setLogLevel,\n getLogLevel,\n setupLogger,\n type LogLevel,\n type LoggerConfig\n} from \"./utils/logger\";\n","/**\n * Version information for the AgentBay SDK\n * Automatically read from package.json\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\nfunction getVersionFromPackageJson(): string {\n try {\n // Get the path to package.json (relative to this file)\n // When compiled, this will be in dist/, so we need to go up to find package.json\n const packageJsonPath = path.join(__dirname, \"..\", \"package.json\");\n \n if (fs.existsSync(packageJsonPath)) {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, \"utf-8\"));\n return packageJson.version || \"0.0.0\";\n }\n } catch (error) {\n // Fallback to default version if reading fails\n }\n \n // Fallback version if package.json cannot be read\n return \"0.9.4\";\n}\n\n/**\n * Check if this is a release build.\n *\n * This value can be overridden at build time by replacing the placeholder below.\n * The CI/CD workflow will replace __AGENTBAY_IS_RELEASE_BUILD__ with true for release builds.\n */\nfunction isReleaseBuild(): boolean {\n // This placeholder will be replaced by the build process\n // For release builds: sed -i 's/__AGENTBAY_IS_RELEASE_BUILD__/true/g' src/version.ts\n return __AGENTBAY_IS_RELEASE_BUILD__; // Default: false for development builds\n}\n\n// For release builds, the CI/CD will replace __AGENTBAY_IS_RELEASE_BUILD__ with true\nconst __AGENTBAY_IS_RELEASE_BUILD__ = false;\n\nexport const VERSION = getVersionFromPackageJson();\nexport const IS_RELEASE = isReleaseBuild();\n\n","import { $OpenApiUtil } from \"@alicloud/openapi-core\";\nimport \"dotenv/config\";\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as dotenv from \"dotenv\";\nimport * as $_client from \"./api\";\nimport { ListSessionRequest, CreateMcpSessionRequestPersistenceDataList, GetSessionRequest as $GetSessionRequest } from \"./api/models/model\";\nimport { Client } from \"./api/client\";\n\nimport { Config } from \"./config\";\nimport { ContextService } from \"./context\";\nimport { ContextSync } from \"./context-sync\";\nimport { APIError, AuthenticationError } from \"./exceptions\";\nimport { Session } from \"./session\";\nimport { BrowserContext } from \"./session-params\";\nimport { Context } from \"./context\";\nimport { ExtraConfigs } from \"./types/extra-configs\";\n\nimport {\n DeleteResult,\n extractRequestId,\n GetSessionResult as $GetSessionResult,\n SessionResult,\n} from \"./types/api-response\";\nimport {\n ListSessionParams,\n SessionListResult,\n} from \"./types/list-session-params\";\nimport {\n log,\n logError,\n logInfo,\n logDebug,\n logAPICall,\n logAPIResponseWithDetails,\n maskSensitiveData,\n setRequestId,\n getRequestId,\n logInfoWithColor,\n} from \"./utils/logger\";\nimport { VERSION, IS_RELEASE } from \"./version\";\n\n// Browser data path constant (moved from config.ts)\nconst BROWSER_DATA_PATH = \"/tmp/agentbay_browser\";\n\n/**\n * Returns the default configuration\n */\nfunction defaultConfig(): Config {\n return {\n endpoint: \"wuyingai.cn-shanghai.aliyuncs.com\",\n timeout_ms: 60000,\n };\n}\n\n/**\n * Find .env file by searching upward from start_path.\n */\nfunction findDotEnvFile(startPath?: string): string | null {\n const currentPath = startPath ? path.resolve(startPath) : process.cwd();\n let searchPath = currentPath;\n\n while (searchPath !== path.dirname(searchPath)) {\n const envFile = path.join(searchPath, \".env\");\n if (fs.existsSync(envFile)) {\n return envFile;\n }\n\n const gitDir = path.join(searchPath, \".git\");\n if (fs.existsSync(gitDir)) {\n // Found git root, continue searching\n }\n\n searchPath = path.dirname(searchPath);\n }\n\n const rootEnv = path.join(searchPath, \".env\");\n if (fs.existsSync(rootEnv)) {\n return rootEnv;\n }\n\n return null;\n}\n\n/**\n * Load .env file with improved search strategy.\n */\nfunction loadDotEnvWithFallback(customEnvPath?: string): void {\n if (customEnvPath) {\n if (fs.existsSync(customEnvPath)) {\n try {\n const envConfig = dotenv.parse(fs.readFileSync(customEnvPath));\n for (const k in envConfig) {\n if (!process.env.hasOwnProperty(k)) {\n process.env[k] = envConfig[k];\n }\n }\n return;\n } catch (error) {\n // Silently fail - .env loading is optional\n }\n }\n }\n\n const envFile = findDotEnvFile();\n if (envFile) {\n try {\n const envConfig = dotenv.parse(fs.readFileSync(envFile));\n for (const k in envConfig) {\n if (!process.env.hasOwnProperty(k)) {\n process.env[k] = envConfig[k];\n }\n }\n } catch (error) {\n // Silently fail - .env loading is optional\n }\n }\n}\n\n/**\n * Load configuration with improved .env file search.\n */\nfunction loadConfig(customConfig?: Config, customEnvPath?: string): Config {\n if (customConfig) {\n return customConfig;\n }\n\n const config = defaultConfig();\n\n try {\n loadDotEnvWithFallback(customEnvPath);\n } catch (error) {\n // Silently fail - .env loading is optional\n }\n\n if (process.env.AGENTBAY_ENDPOINT) {\n config.endpoint = process.env.AGENTBAY_ENDPOINT;\n }\n\n if (process.env.AGENTBAY_TIMEOUT_MS) {\n const timeout = parseInt(process.env.AGENTBAY_TIMEOUT_MS, 10);\n if (!isNaN(timeout) && timeout > 0) {\n config.timeout_ms = timeout;\n }\n }\n\n return config;\n}\n\n/**\n * Generate a random context name using alphanumeric characters with timestamp.\n * This function is similar to the Python version's generate_random_context_name.\n */\nfunction generateRandomContextName(length = 8, includeTimestamp = true): string {\n const timestamp = new Date().toISOString().replace(/[-T:.Z]/g, '').slice(0, 14);\n\n const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let randomPart = '';\n for (let i = 0; i < length; i++) {\n randomPart += characters.charAt(Math.floor(Math.random() * characters.length));\n }\n\n return includeTimestamp ? `${timestamp}_${randomPart}` : randomPart;\n}\n\n/**\n * Parameters for creating a session.\n */\nexport interface CreateSessionParams {\n labels?: Record<string, string>;\n imageId?: string;\n contextSync?: ContextSync[];\n browserContext?: BrowserContext;\n isVpc?: boolean;\n policyId?: string;\n enableBrowserReplay?: boolean;\n extraConfigs?: ExtraConfigs;\n framework?: string;\n}\n\n/**\n * Main class for interacting with the AgentBay cloud runtime environment.\n */\nexport class AgentBay {\n private apiKey: string;\n private client: Client;\n private endpoint: string;\n private sessions: Map<string, Session> = new Map();\n private fileTransferContext: Context | null = null;\n\n /**\n * Context service for managing persistent contexts.\n */\n context: ContextService;\n\n /**\n * Initialize the AgentBay client.\n *\n * @param options - Configuration options\n * @param options.apiKey - API key for authentication. If not provided, will look for AGENTBAY_API_KEY environment variable.\n * @param options.config - Custom configuration object. If not provided, will use environment-based configuration.\n * @param options.envFile - Custom path to .env file. If not provided, will search upward from current directory.\n */\n constructor(\n options: {\n apiKey?: string;\n config?: Config;\n envFile?: string;\n } = {}\n ) {\n // Load .env file first to ensure AGENTBAY_API_KEY is available\n loadDotEnvWithFallback(options.envFile);\n\n this.apiKey = options.apiKey || process.env.AGENTBAY_API_KEY || \"\";\n\n if (!this.apiKey) {\n throw new AuthenticationError(\n \"API key is required. Provide it as a parameter or set the AGENTBAY_API_KEY environment variable.\"\n );\n }\n\n // Load configuration using the enhanced loadConfig function\n const configData = loadConfig(options.config, options.envFile);\n this.endpoint = configData.endpoint;\n\n const config = new $OpenApiUtil.Config({\n regionId: \"\",\n endpoint: this.endpoint,\n });\n\n config.readTimeout = configData.timeout_ms;\n config.connectTimeout = configData.timeout_ms;\n\n try {\n this.client = new Client(config);\n\n // Initialize context service\n this.context = new ContextService(this);\n } catch (error) {\n logError(`Failed to constructor:`, error);\n throw new AuthenticationError(`Failed to constructor: ${error}`);\n }\n }\n\n /**\n * Update browser replay context with AppInstanceId from response data.\n *\n * @param responseData - Response data containing AppInstanceId\n * @param recordContextId - The record context ID to update\n */\n private async _updateBrowserReplayContext(responseData: any, recordContextId: string): Promise<void> {\n // Check if record_context_id is provided\n if (!recordContextId) {\n return;\n }\n\n try {\n // Extract AppInstanceId from response data\n const appInstanceId = responseData?.appInstanceId;\n if (!appInstanceId) {\n logError(\"AppInstanceId not found in response data, skipping browser replay context update\");\n return;\n }\n\n // Create context name with prefix\n const contextName = `browserreplay-${appInstanceId}`;\n\n // Create Context object for update\n const contextObj = new Context(recordContextId, contextName);\n\n // Call context.update interface\n logDebug(`Updating browser replay context: ${contextName} -> ${recordContextId}`);\n const updateResult = await this.context.update(contextObj);\n\n if (updateResult.success) {\n logInfo(`✅ Successfully updated browser replay context: ${contextName}`);\n } else {\n logError(`⚠️ Failed to update browser replay context: ${updateResult.errorMessage}`);\n }\n } catch (error) {\n logError(`❌ Error updating browser replay context: ${error}`);\n // Continue execution even if context update fails\n }\n }\n\n /**\n * Create a new session in the AgentBay cloud environment.\n *\n * @param params - Optional parameters for creating the session\n * @returns SessionResult containing the created session and request ID\n */\n /**\n * Creates a new AgentBay session with specified configuration.\n *\n * @param params - Configuration parameters for the session:\n * - labels: Key-value pairs for session metadata\n * - imageId: Custom image ID for the session environment\n * - contextSync: Array of context synchronization configurations\n * - browserContext: Browser-specific context configuration\n * - isVpc: Whether to create a VPC session\n * - policyId: Security policy ID\n * - enableBrowserReplay: Enable browser session recording\n * - extraConfigs: Additional configuration options\n * - framework: Framework identifier for tracking\n *\n * @returns Promise resolving to SessionResult containing:\n * - success: Whether session creation succeeded\n * - session: Session object for interacting with the environment\n * - requestId: Unique identifier for this API request\n * - errorMessage: Error description if creation failed\n *\n * @throws Error if API call fails or authentication is invalid.\n *\n * @example\n * ```typescript\n * const agentBay = new AgentBay({ apiKey: 'your_api_key' });\n * const result = await agentBay.create({ labels: { project: 'demo' } });\n * if (result.success) {\n * await result.session.filesystem.readFile('/etc/hostname');\n * await result.session.delete();\n * }\n * ```\n *\n * @remarks\n * **Behavior:**\n * - Creates a new isolated cloud runtime environment\n * - Automatically creates file transfer context if not provided\n * - Waits for context synchronization if contextSync is specified\n * - For VPC sessions, includes VPC-specific configuration\n * - Browser replay creates a separate recording context\n *\n * @see {@link get}, {@link list}, {@link Session.delete}\n */\n async create(params: CreateSessionParams = {}): Promise<SessionResult> {\n try {\n logDebug(`default context syncs length: ${params.contextSync?.length}`);\n // Create a default context for file transfer operations if none provided\n // and no context_syncs are specified\n const contextName = `file-transfer-context-${Date.now()}`;\n const contextResult = await this.context.get(contextName, true);\n if (contextResult.success && contextResult.context) {\n this.fileTransferContext = contextResult.context;\n // Add the context to the session params for file transfer operations\n const fileTransferContextSync = new ContextSync(\n contextResult.context.id,\n \"/temp/file-transfer\"\n );\n if (!params.contextSync) {\n params.contextSync = [];\n }\n logDebug(`Adding context sync for file transfer operations: ${fileTransferContextSync}`);\n params.contextSync.push(fileTransferContextSync);\n }\n\n const request = new $_client.CreateMcpSessionRequest({\n authorization: \"Bearer \" + this.apiKey,\n });\n\n // Add SDK stats for tracking\n const framework = params?.framework || \"\";\n const sdkStatsJson = `{\"source\":\"sdk\",\"sdk_language\":\"typescript\",\"sdk_version\":\"${VERSION}\",\"is_release\":${IS_RELEASE},\"framework\":\"${framework}\"}`;\n request.sdkStats = sdkStatsJson;\n\n // Add labels if provided\n if (params.labels) {\n request.labels = JSON.stringify(params.labels);\n }\n\n // Add image_id if provided\n if (params.imageId) {\n request.imageId = params.imageId;\n }\n\n // Add PolicyId if provided\n if (params.policyId) {\n request.mcpPolicyId = params.policyId;\n }\n\n // Add VPC resource if specified\n request.vpcResource = params.isVpc || false;\n\n // Flag to indicate if we need to wait for context synchronization\n let needsContextSync = false;\n\n // Add context sync configurations if provided\n if (params.contextSync && params.contextSync.length > 0) {\n const persistenceDataList: CreateMcpSessionRequestPersistenceDataList[] = [];\n for (const contextSync of params.contextSync) {\n const persistenceItem = new CreateMcpSessionRequestPersistenceDataList({\n contextId: contextSync.contextId,\n path: contextSync.path,\n });\n\n // Convert policy to JSON string if provided\n if (contextSync.policy) {\n persistenceItem.policy = JSON.stringify(contextSync.policy);\n }\n\n persistenceDataList.push(persistenceItem);\n }\n request.persistenceDataList = persistenceDataList;\n needsContextSync = persistenceDataList.length > 0;\n }\n\n // Add BrowserContext as a ContextSync if provided\n if (params.browserContext) {\n // Create a simple sync policy for browser context\n const syncPolicy = {\n uploadPolicy: { autoUpload: params.browserContext.autoUpload },\n downloadPolicy: null,\n deletePolicy: null,\n bwList: null,\n recyclePolicy: null,\n };\n\n // Create browser context sync item\n const browserContextSync = new CreateMcpSessionRequestPersistenceDataList({\n contextId: params.browserContext.contextId,\n path: BROWSER_DATA_PATH, // Using a constant path for browser data\n policy: JSON.stringify(syncPolicy)\n });\n\n // Add to persistence data list or create new one if not exists\n if (!request.persistenceDataList) {\n request.persistenceDataList = [];\n }\n request.persistenceDataList.push(browserContextSync);\n needsContextSync = true;\n }\n\n // Add browser recording persistence if enabled\n let recordContextId = \"\"; // Initialize record_context_id\n if (params.enableBrowserReplay) {\n // Create browser recording persistence configuration\n const recordPath = \"/home/guest/record\";\n const recordContextName = generateRandomContextName();\n const result = await this.context.get(recordContextName, true);\n recordContextId = result.success ? result.contextId : \"\";\n const recordPersistence = new CreateMcpSessionRequestPersistenceDataList({\n contextId: recordContextId,\n path: recordPath,\n });\n\n // Add to persistence data list or create new one if not exists\n if (!request.persistenceDataList) {\n request.persistenceDataList = [];\n }\n request.persistenceDataList.push(recordPersistence);\n }\n\n // Add extra configs if provided\n if (params.extraConfigs) {\n request.extraConfigs = JSON.stringify(params.extraConfigs);\n }\n\n // Log API request\n logAPICall(\"CreateMcpSession\", {\n labels: params.labels,\n imageId: params.imageId,\n policyId: params.policyId,\n isVpc: params.isVpc,\n persistenceDataCount: params.contextSync ? params.contextSync.length : 0,\n });\n\n const response = await this.client.createMcpSession(request);\n\n // Extract request ID\n const requestId = extractRequestId(response) || \"\";\n setRequestId(requestId);\n\n const sessionData = response.body;\n\n if (!sessionData || typeof sessionData !== \"object\") {\n logAPIResponseWithDetails(\n \"CreateMcpSession\",\n requestId,\n false,\n {},\n \"Invalid response format: expected a dictionary\"\n );\n logDebug(\"Full response:\", JSON.stringify(sessionData, null, 2));\n return {\n requestId,\n success: false,\n errorMessage: \"Invalid response format: expected a dictionary\",\n };\n }\n\n // Check for API-level errors\n if (sessionData.success === false && sessionData.code) {\n const errorMessage = `[${sessionData.code}] ${sessionData.message || 'Unknown error'}`;\n logAPIResponseWithDetails(\n \"CreateMcpSession\",\n requestId,\n false,\n {},\n errorMessage\n );\n logDebug(\"Full response:\", JSON.stringify(sessionData, null, 2));\n return {\n requestId,\n success: false,\n errorMessage,\n };\n }\n\n const data = sessionData.data;\n if (!data || typeof data !== \"object\") {\n logAPIResponseWithDetails(\n \"CreateMcpSession\",\n requestId,\n false,\n {},\n \"Invalid response format: 'data' field is not a dictionary\"\n );\n logDebug(\"Full response:\", JSON.stringify(sessionData, null, 2));\n return {\n requestId,\n success: false,\n errorMessage:\n \"Invalid response format: 'data' field is not a dictionary\",\n };\n }\n\n // Check data-level success field (business logic success)\n if (data.success === false) {\n const errorMessage = data.errMsg || \"Session creation failed\";\n logAPIResponseWithDetails(\n \"CreateMcpSession\",\n requestId,\n false,\n {},\n errorMessage\n );\n logDebug(\"Full response:\", JSON.stringify(sessionData, null, 2));\n return {\n requestId,\n success: false,\n errorMessage,\n };\n }\n\n const sessionId = data.sessionId;\n if (!sessionId) {\n logAPIResponseWithDetails(\n \"CreateMcpSession\",\n requestId,\n false,\n {},\n \"SessionId not found in response\"\n );\n logDebug(\"Full response:\", JSON.stringify(sessionData, null, 2));\n return {\n requestId,\n success: false,\n errorMessage: \"SessionId not found in response\",\n };\n }\n\n // ResourceUrl is optional in CreateMcpSession response\n const resourceUrl = data.resourceUrl || \"\";\n\n logAPIResponseWithDetails(\n \"CreateMcpSession\",\n requestId,\n true,\n {\n sessionId,\n resourceUrl,\n }\n );\n\n const session = new Session(this, sessionId);\n\n // Set VPC-related information from response\n session.isVpc = params.isVpc || false;\n if (data.networkInterfaceIp) {\n session.networkInterfaceIp = data.networkInterfaceIp;\n }\n if (data.httpPort) {\n session.httpPort = data.httpPort;\n }\n if (data.token) {\n session.token = data.token;\n }\n\n // Set ResourceUrl\n session.resourceUrl = resourceUrl;\n\n // Set browser recording state\n session.enableBrowserReplay = params.enableBrowserReplay || false;\n\n // Store the file transfer context ID if we created one\n session.fileTransferContextId = this.fileTransferContext ? this.fileTransferContext.id : null;\n\n // Store the browser recording context ID if we created one\n session.recordContextId = recordContextId || null;\n\n // Store imageId used for this session\n (session as any).imageId = params.imageId;\n\n\n this.sessions.set(session.sessionId, session);\n\n // Apply mobile configuration if provided\n if (params.extraConfigs && params.extraConfigs.mobile) {\n log(\"Applying mobile configuration...\");\n try {\n const configResult = await session.mobile.configure(params.extraConfigs.mobile);\n if (configResult.success) {\n log(\"Mobile configuration applied successfully\");\n } else {\n logError(`Warning: Failed to apply mobile configuration: ${configResult.errorMessage}`);\n // Continue with session creation even if mobile config fails\n }\n } catch (error) {\n logError(`Warning: Failed to apply mobile configuration: ${error}`);\n // Continue with session creation even if mobile config fails\n }\n }\n\n // Update browser replay context if enabled\n if (params.enableBrowserReplay) {\n await this._updateBrowserReplayContext(data, recordContextId);\n }\n\n // For VPC sessions, automatically fetch MCP tools information\n if (params.isVpc) {\n logDebug(\"VPC session detected, automatically fetching MCP tools...\");\n try {\n const toolsResult = await session.listMcpTools();\n logDebug(`Successfully fetched ${toolsResult.tools.length} MCP tools for VPC session (RequestID: ${toolsResult.requestId})`);\n } catch (error) {\n logError(`Warning: Failed to fetch MCP tools for VPC session: ${error}`);\n // Continue with session creation even if tools fetch fails\n }\n }\n\n // If we have persistence data, wait for context synchronization\n if (needsContextSync) {\n logDebug(\"Waiting for context synchronization to complete...\");\n\n // Wait for context synchronization to complete\n const maxRetries = 150; // Maximum number of retries\n const retryInterval = 1500; // Milliseconds to wait between retries\n\n for (let retry = 0; retry < maxRetries; retry++) {\n try {\n // Get context status data\n const infoResult = await session.context.info();\n\n // Check if all context items have status \"Success\" or \"Failed\"\n let allCompleted = true;\n let hasFailure = false;\n\n for (const item of infoResult.contextStatusData) {\n logDebug(`Context ${item.contextId} status: ${item.status}, path: ${item.path}`);\n\n if (item.status !== \"Success\" && item.status !== \"Failed\") {\n allCompleted = false;\n break;\n }\n\n if (item.status === \"Failed\") {\n hasFailure = true;\n logError(`Context synchronization failed for ${item.contextId}: ${item.errorMessage}`);\n }\n }\n\n if (allCompleted || infoResult.contextStatusData.length === 0) {\n if (hasFailure) {\n logDebug(\"Context synchronization completed with failures\");\n } else {\n logDebug(\"Context synchronization completed successfully\");\n }\n break;\n }\n\n logDebug(`Waiting for context synchronization, attempt ${retry+1}/${maxRetries}`);\n await new Promise(resolve => setTimeout(resolve, retryInterval));\n } catch (error) {\n logError(`Error checking context status on attempt ${retry+1}: ${error}`);\n await new Promise(resolve => setTimeout(resolve, retryInterval));\n }\n }\n }\n\n // Return SessionResult with request ID\n return {\n requestId,\n success: true,\n session,\n };\n } catch (error) {\n logError(\"Error calling create_mcp_session:\", error);\n return {\n requestId: \"\",\n success: false,\n errorMessage: `Failed to create session: ${error}`,\n };\n }\n }\n\n\n /**\n * Returns paginated list of session IDs filtered by labels.\n *\n * @param labels - Optional labels to filter sessions (defaults to empty object)\n * @param page - Optional page number for pagination (starting from 1, defaults to 1)\n * @param limit - Optional maximum number of items per page (defaults to 10)\n * @returns SessionListResult - Paginated list of session IDs that match the labels\n *\n * @example\n * ```typescript\n * const agentBay = new AgentBay({ apiKey: \"your_api_key\" });\n * const result = await agentBay.list({ project: \"demo\" }, 1, 10);\n * if (result.success) {\n * console.log(`Found ${result.sessionIds.length} sessions`);\n * }\n * ```\n */\n async list(\n labels: Record<string, string> = {},\n page?: number,\n limit = 10\n ): Promise<SessionListResult> {\n try {\n // Validate page number\n if (page !== undefined && page < 1) {\n return {\n requestId: \"\",\n success: false,\n errorMessage: `Cannot reach page ${page}: Page number must be >= 1`,\n sessionIds: [],\n nextToken: \"\",\n maxResults: limit,\n totalCount: 0,\n };\n }\n\n // Calculate next_token based on page number\n // Page 1 or undefined means no next_token (first page)\n // For page > 1, we need to make multiple requests to get to that page\n let nextToken = \"\";\n if (page !== undefined && page > 1) {\n // We need to fetch pages 1 through page-1 to get the next_token\n let currentPage = 1;\n while (currentPage < page) {\n // Make API call to get next_token\n const request = new ListSessionRequest({\n authorization: `Bearer ${this.apiKey}`,\n labels: JSON.stringify(labels),\n maxResults: limit,\n });\n if (nextToken) {\n request.nextToken = nextToken;\n }\n\n const response = await this.client.listSession(request);\n const requestId = extractRequestId(response) || \"\";\n\n if (!response.body?.success) {\n const code = response.body?.code || \"Unknown\";\n const message = response.body?.message || \"Unknown error\";\n return {\n requestId,\n success: false,\n errorMessage: `[${code}] ${message}`,\n sessionIds: [],\n nextToken: \"\",\n maxResults: limit,\n totalCount: 0,\n };\n }\n\n nextToken = response.body.nextToken || \"\";\n if (!nextToken) {\n // No more pages available\n return {\n requestId,\n success: false,\n errorMessage: `Cannot reach page ${page}: No more pages available`,\n sessionIds: [],\n nextToken: \"\",\n maxResults: limit,\n totalCount: response.body.totalCount || 0,\n };\n }\n currentPage += 1;\n }\n }\n\n // Make the actual request for the desired page\n const request = new ListSessionRequest({\n authorization: `Bearer ${this.apiKey}`,\n labels: JSON.stringify(labels),\n maxResults: limit,\n });\n if (nextToken) {\n request.nextToken = nextToken;\n }\n\n // Log API request\n logAPICall(\"ListSession\", {\n labels,\n maxResults: limit,\n nextToken: nextToken || undefined,\n });\n\n const response = await this.client.listSession(request);\n\n // Extract request ID\n const requestId = extractRequestId(response) || \"\";\n setRequestId(requestId);\n\n // Check for errors in the response\n if (!response.body?.success) {\n const code = response.body?.code || \"Unknown\";\n const message = response.body?.message || \"Unknown error\";\n logAPIResponseWithDetails(\n \"ListSession\",\n requestId,\n false,\n {},\n `[${code}] ${message}`\n );\n logDebug(\"Full ListSession response:\", JSON.stringify(response.body, null, 2));\n return {\n requestId,\n success: false,\n errorMessage: `[${code}] ${message}`,\n sessionIds: [],\n nextToken: \"\",\n maxResults: limit,\n totalCount: 0,\n };\n }\n\n const sessionIds: string[] = [];\n\n // Extract session data\n if (response.body.data) {\n for (const sessionData of response.body.data) {\n if (sessionData.sessionId) {\n sessionIds.push(sessionData.sessionId);\n }\n }\n }\n\n logAPIResponseWithDetails(\n \"ListSession\",\n requestId,\n true,\n {\n sessionCount: sessionIds.length,\n totalCount: response.body.totalCount || 0,\n maxResults: response.body.maxResults || limit,\n }\n );\n\n // Return SessionListResult with request ID and pagination info\n return {\n requestId,\n success: true,\n sessionIds,\n nextToken: response.body.nextToken || \"\",\n maxResults: response.body.maxResults || limit,\n totalCount: response.body.totalCount || 0,\n };\n } catch (error) {\n logError(\"Error calling list_session:\", error);\n return {\n requestId: \"\",\n success: false,\n errorMessage: `Failed to list sessions: ${error}`,\n sessionIds: [],\n };\n }\n }\n\n /**\n * Delete a session by session object.\n *\n * @param session - The session to delete.\n * @param syncContext - Whether to sync context data (trigger file uploads) before deleting the session. Defaults to false.\n * @returns DeleteResult indicating success or failure and request ID\n *\n * @example\n * ```typescript\n * const agentBay = new AgentBay({ apiKey: 'your_api_key' });\n * const result = await agentBay.create();\n * if (result.success) {\n * await agentBay.delete(result.session);\n * }\n * ```\n */\n async delete(session: Session, syncContext = false): Promise<DeleteResult> {\n try {\n // Delete the session and get the result\n logAPICall(\"DeleteSession\", { sessionId: session.sessionId });\n const deleteResult = await session.delete(syncContext);\n\n logAPIResponseWithDetails(\n \"DeleteSession\",\n deleteResult.requestId,\n deleteResult.success,\n { sessionId: session.sessionId }\n );\n\n this.sessions.delete(session.sessionId);\n\n // Return the DeleteResult obtained from session.delete()\n return deleteResult;\n } catch (error) {\n logError(\"Error deleting session:\", error);\n return {\n requestId: \"\",\n success: false,\n errorMessage: `Failed to delete session ${session.sessionId}: ${error}`,\n };\n }\n }\n\n /**\n * Remove a session from the internal session cache.\n *\n * This is an internal utility method that removes a session reference from the AgentBay client's\n * session cache without actually deleting the session from the cloud. Use this when you need to\n * clean up local references to a session that was deleted externally or no longer needed.\n *\n * @param sessionId - The ID of the session to remove from the cache.\n *\n * @internal\n *\n * @remarks\n * **Note:** This method only removes the session from the local cache. It does not delete the\n * session from the cloud. To delete a session from the cloud, use {@link delete} or\n * {@link Session.delete}.\n *\n * @see {@l