mirror of
https://github.com/trycua/lume.git
synced 2026-03-13 19:40:58 -05:00
Use an actual logger
This commit is contained in:
@@ -39,6 +39,7 @@
|
||||
"prepublishOnly": "pnpm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"pino": "^9.7.0",
|
||||
"sharp": "^0.33.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
93
libs/computer/typescript/pnpm-lock.yaml
generated
93
libs/computer/typescript/pnpm-lock.yaml
generated
@@ -8,6 +8,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
pino:
|
||||
specifier: ^9.7.0
|
||||
version: 9.7.0
|
||||
sharp:
|
||||
specifier: ^0.33.0
|
||||
version: 0.33.5
|
||||
@@ -636,6 +639,10 @@ packages:
|
||||
resolution: {integrity: sha512-ROM2LlXbZBZVk97crfw8PGDOBzzsJvN2uJCmwswvPUNyfH14eg90mSN3xNqsri1JS1G9cz0VzeDUhxJkTrr4Ew==}
|
||||
engines: {node: '>=20.18.0'}
|
||||
|
||||
atomic-sleep@1.0.0:
|
||||
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
birpc@2.3.0:
|
||||
resolution: {integrity: sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==}
|
||||
|
||||
@@ -758,6 +765,10 @@ packages:
|
||||
exsolve@1.0.5:
|
||||
resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==}
|
||||
|
||||
fast-redact@3.5.0:
|
||||
resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
fdir@6.4.6:
|
||||
resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
|
||||
peerDependencies:
|
||||
@@ -828,6 +839,10 @@ packages:
|
||||
ohash@2.0.11:
|
||||
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
||||
|
||||
on-exit-leak-free@2.1.2:
|
||||
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
package-manager-detector@1.3.0:
|
||||
resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==}
|
||||
|
||||
@@ -848,6 +863,16 @@ packages:
|
||||
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pino-abstract-transport@2.0.0:
|
||||
resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==}
|
||||
|
||||
pino-std-serializers@7.0.0:
|
||||
resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==}
|
||||
|
||||
pino@9.7.0:
|
||||
resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==}
|
||||
hasBin: true
|
||||
|
||||
pkg-types@2.1.0:
|
||||
resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==}
|
||||
|
||||
@@ -855,9 +880,15 @@ packages:
|
||||
resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
process-warning@5.0.0:
|
||||
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
|
||||
|
||||
quansync@0.2.10:
|
||||
resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==}
|
||||
|
||||
quick-format-unescaped@4.0.4:
|
||||
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
||||
|
||||
rc9@2.1.2:
|
||||
resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
|
||||
|
||||
@@ -865,6 +896,10 @@ packages:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
real-require@0.2.0:
|
||||
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
||||
engines: {node: '>= 12.13.0'}
|
||||
|
||||
resolve-pkg-maps@1.0.0:
|
||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||
|
||||
@@ -898,6 +933,10 @@ packages:
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
safe-stable-stringify@2.5.0:
|
||||
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
semver@7.7.2:
|
||||
resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -913,10 +952,17 @@ packages:
|
||||
simple-swizzle@0.2.2:
|
||||
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
|
||||
|
||||
sonic-boom@4.2.0:
|
||||
resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==}
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
split2@4.2.0:
|
||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||
engines: {node: '>= 10.x'}
|
||||
|
||||
stackback@0.0.2:
|
||||
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
|
||||
|
||||
@@ -926,6 +972,9 @@ packages:
|
||||
strip-literal@3.0.0:
|
||||
resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==}
|
||||
|
||||
thread-stream@3.1.0:
|
||||
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
|
||||
|
||||
tinybench@2.9.0:
|
||||
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
|
||||
|
||||
@@ -1514,6 +1563,8 @@ snapshots:
|
||||
'@babel/parser': 7.27.5
|
||||
pathe: 2.0.3
|
||||
|
||||
atomic-sleep@1.0.0: {}
|
||||
|
||||
birpc@2.3.0: {}
|
||||
|
||||
bumpp@10.1.1:
|
||||
@@ -1647,6 +1698,8 @@ snapshots:
|
||||
|
||||
exsolve@1.0.5: {}
|
||||
|
||||
fast-redact@3.5.0: {}
|
||||
|
||||
fdir@6.4.6(picomatch@4.0.2):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.2
|
||||
@@ -1706,6 +1759,8 @@ snapshots:
|
||||
|
||||
ohash@2.0.11: {}
|
||||
|
||||
on-exit-leak-free@2.1.2: {}
|
||||
|
||||
package-manager-detector@1.3.0: {}
|
||||
|
||||
pathe@2.0.3: {}
|
||||
@@ -1718,6 +1773,26 @@ snapshots:
|
||||
|
||||
picomatch@4.0.2: {}
|
||||
|
||||
pino-abstract-transport@2.0.0:
|
||||
dependencies:
|
||||
split2: 4.2.0
|
||||
|
||||
pino-std-serializers@7.0.0: {}
|
||||
|
||||
pino@9.7.0:
|
||||
dependencies:
|
||||
atomic-sleep: 1.0.0
|
||||
fast-redact: 3.5.0
|
||||
on-exit-leak-free: 2.1.2
|
||||
pino-abstract-transport: 2.0.0
|
||||
pino-std-serializers: 7.0.0
|
||||
process-warning: 5.0.0
|
||||
quick-format-unescaped: 4.0.4
|
||||
real-require: 0.2.0
|
||||
safe-stable-stringify: 2.5.0
|
||||
sonic-boom: 4.2.0
|
||||
thread-stream: 3.1.0
|
||||
|
||||
pkg-types@2.1.0:
|
||||
dependencies:
|
||||
confbox: 0.2.2
|
||||
@@ -1730,8 +1805,12 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
process-warning@5.0.0: {}
|
||||
|
||||
quansync@0.2.10: {}
|
||||
|
||||
quick-format-unescaped@4.0.4: {}
|
||||
|
||||
rc9@2.1.2:
|
||||
dependencies:
|
||||
defu: 6.1.4
|
||||
@@ -1739,6 +1818,8 @@ snapshots:
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
real-require@0.2.0: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
rolldown-plugin-dts@0.13.11(rolldown@1.0.0-beta.9)(typescript@5.8.3):
|
||||
@@ -1803,6 +1884,8 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc': 4.43.0
|
||||
fsevents: 2.3.3
|
||||
|
||||
safe-stable-stringify@2.5.0: {}
|
||||
|
||||
semver@7.7.2: {}
|
||||
|
||||
sharp@0.33.5:
|
||||
@@ -1837,8 +1920,14 @@ snapshots:
|
||||
dependencies:
|
||||
is-arrayish: 0.3.2
|
||||
|
||||
sonic-boom@4.2.0:
|
||||
dependencies:
|
||||
atomic-sleep: 1.0.0
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
split2@4.2.0: {}
|
||||
|
||||
stackback@0.0.2: {}
|
||||
|
||||
std-env@3.9.0: {}
|
||||
@@ -1847,6 +1936,10 @@ snapshots:
|
||||
dependencies:
|
||||
js-tokens: 9.0.1
|
||||
|
||||
thread-stream@3.1.0:
|
||||
dependencies:
|
||||
real-require: 0.2.0
|
||||
|
||||
tinybench@2.9.0: {}
|
||||
|
||||
tinyexec@0.3.2: {}
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
import type { Display, ComputerConfig } from '../models';
|
||||
import type { BaseComputerInterface } from '../interface/base';
|
||||
import { InterfaceFactory } from '../interface/factory';
|
||||
import type { BaseVMProvider } from '../providers/base';
|
||||
import { VMProviderType } from '../providers/base';
|
||||
import { VMProviderFactory } from '../providers/factory';
|
||||
import { Logger, LogLevel } from '../logger';
|
||||
import { recordComputerInitialization, recordVMStart, recordVMStop } from '../telemetry';
|
||||
import { setDefaultComputer } from '../helpers';
|
||||
import { parseDisplayString, parseImageString, sleep, withTimeout } from '../utils';
|
||||
import sharp from 'sharp';
|
||||
import type { Display, ComputerConfig } from "../models";
|
||||
import type { BaseComputerInterface } from "../interface/base";
|
||||
import { InterfaceFactory } from "../interface/factory";
|
||||
import type { BaseVMProvider } from "../providers/base";
|
||||
import { VMProviderType } from "../providers/base";
|
||||
import { VMProviderFactory } from "../providers/factory";
|
||||
import pino from "pino";
|
||||
import {
|
||||
recordComputerInitialization,
|
||||
recordVMStart,
|
||||
recordVMStop,
|
||||
} from "../telemetry";
|
||||
import { setDefaultComputer } from "../helpers";
|
||||
import {
|
||||
parseDisplayString,
|
||||
parseImageString,
|
||||
sleep,
|
||||
withTimeout,
|
||||
} from "../utils";
|
||||
import sharp from "sharp";
|
||||
|
||||
export type OSType = 'macos' | 'linux' | 'windows';
|
||||
export type OSType = "macos" | "linux" | "windows";
|
||||
|
||||
export interface ComputerOptions {
|
||||
display?: Display | { width: number; height: number } | string;
|
||||
@@ -21,7 +30,7 @@ export interface ComputerOptions {
|
||||
image?: string;
|
||||
sharedDirectories?: string[];
|
||||
useHostComputerServer?: boolean;
|
||||
verbosity?: number | LogLevel;
|
||||
verbosity?: pino.Level;
|
||||
telemetryEnabled?: boolean;
|
||||
providerType?: VMProviderType | string;
|
||||
port?: number;
|
||||
@@ -37,10 +46,8 @@ export interface ComputerOptions {
|
||||
* Computer is the main class for interacting with the computer.
|
||||
*/
|
||||
export class Computer {
|
||||
private logger: Logger;
|
||||
private vmLogger: Logger;
|
||||
private interfaceLogger: Logger;
|
||||
|
||||
private logger: pino.Logger;
|
||||
|
||||
private image: string;
|
||||
private port?: number;
|
||||
private noVNCPort?: number;
|
||||
@@ -56,7 +63,7 @@ export class Computer {
|
||||
private _telemetryEnabled: boolean;
|
||||
private _initialized: boolean = false;
|
||||
private _running: boolean = false;
|
||||
private verbosity: number | LogLevel;
|
||||
|
||||
private useHostComputerServer: boolean;
|
||||
private config?: ComputerConfig;
|
||||
private _providerContext?: BaseVMProvider;
|
||||
@@ -71,29 +78,28 @@ export class Computer {
|
||||
*/
|
||||
constructor(options: ComputerOptions = {}) {
|
||||
const {
|
||||
display = '1024x768',
|
||||
memory = '8GB',
|
||||
cpu = '4',
|
||||
osType = 'macos',
|
||||
name = '',
|
||||
image = 'macos-sequoia-cua:latest',
|
||||
display = "1024x768",
|
||||
memory = "8GB",
|
||||
cpu = "4",
|
||||
osType = "macos",
|
||||
name = "",
|
||||
image = "macos-sequoia-cua:latest",
|
||||
sharedDirectories = [],
|
||||
useHostComputerServer = false,
|
||||
verbosity = LogLevel.NORMAL,
|
||||
verbosity = "info",
|
||||
telemetryEnabled = true,
|
||||
providerType = VMProviderType.LUME,
|
||||
port = 7777,
|
||||
noVNCPort = 8006,
|
||||
host = process.env.PYLUME_HOST || 'localhost',
|
||||
host = process.env.PYLUME_HOST || "localhost",
|
||||
storage,
|
||||
ephemeral = false,
|
||||
apiKey,
|
||||
experiments = []
|
||||
experiments = [],
|
||||
} = options;
|
||||
|
||||
this.verbosity = verbosity;
|
||||
this.logger = new Logger('cua.computer', verbosity);
|
||||
this.logger.info('Initializing Computer...');
|
||||
this.logger = pino({ name: "cua.computer", level: verbosity });
|
||||
this.logger.info("Initializing Computer...");
|
||||
|
||||
// Store original parameters
|
||||
this.image = image;
|
||||
@@ -106,46 +112,46 @@ export class Computer {
|
||||
this.apiKey = apiKey;
|
||||
this.experiments = experiments;
|
||||
|
||||
if (this.experiments.includes('app-use')) {
|
||||
if (this.osType !== 'macos') {
|
||||
throw new Error('App use experiment is only supported on macOS');
|
||||
if (this.experiments.includes("app-use")) {
|
||||
if (this.osType !== "macos") {
|
||||
throw new Error("App use experiment is only supported on macOS");
|
||||
}
|
||||
}
|
||||
|
||||
// The default is currently to use non-ephemeral storage
|
||||
if (storage && ephemeral && storage !== 'ephemeral') {
|
||||
throw new Error('Storage path and ephemeral flag cannot be used together');
|
||||
if (storage && ephemeral && storage !== "ephemeral") {
|
||||
throw new Error(
|
||||
"Storage path and ephemeral flag cannot be used together"
|
||||
);
|
||||
}
|
||||
this.storage = ephemeral ? 'ephemeral' : storage;
|
||||
this.storage = ephemeral ? "ephemeral" : storage;
|
||||
|
||||
// For Lumier provider, store the first shared directory path to use
|
||||
// for VM file sharing
|
||||
this.sharedPath = undefined;
|
||||
if (sharedDirectories && sharedDirectories.length > 0) {
|
||||
this.sharedPath = sharedDirectories[0];
|
||||
this.logger.info(`Using first shared directory for VM file sharing: ${this.sharedPath}`);
|
||||
this.logger.info(
|
||||
`Using first shared directory for VM file sharing: ${this.sharedPath}`
|
||||
);
|
||||
}
|
||||
|
||||
// Store telemetry preference
|
||||
this._telemetryEnabled = telemetryEnabled;
|
||||
|
||||
// Configure component loggers with proper hierarchy
|
||||
this.vmLogger = new Logger('cua.vm', verbosity);
|
||||
this.interfaceLogger = new Logger('cua.interface', verbosity);
|
||||
|
||||
this.useHostComputerServer = useHostComputerServer;
|
||||
|
||||
if (!useHostComputerServer) {
|
||||
const imageInfo = parseImageString(image);
|
||||
|
||||
const vmName = name || image.replace(':', '_');
|
||||
|
||||
const vmName = name || image.replace(":", "_");
|
||||
|
||||
// Convert display parameter to Display object
|
||||
let displayConfig: Display;
|
||||
if (typeof display === 'string') {
|
||||
if (typeof display === "string") {
|
||||
const { width, height } = parseDisplayString(display);
|
||||
displayConfig = { width, height };
|
||||
} else if ('width' in display && 'height' in display) {
|
||||
} else if ("width" in display && "height" in display) {
|
||||
displayConfig = display as Display;
|
||||
} else {
|
||||
displayConfig = display as Display;
|
||||
@@ -168,7 +174,9 @@ export class Computer {
|
||||
if (telemetryEnabled) {
|
||||
recordComputerInitialization();
|
||||
} else {
|
||||
this.logger.debug('Telemetry disabled - skipping initialization tracking');
|
||||
this.logger.debug(
|
||||
"Telemetry disabled - skipping initialization tracking"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,11 +188,13 @@ export class Computer {
|
||||
* @returns A proxy object with the Diorama interface, but using diorama_cmds.
|
||||
*/
|
||||
createDesktopFromApps(apps: string[]): any {
|
||||
if (!this.experiments.includes('app-use')) {
|
||||
throw new Error("App Usage is an experimental feature. Enable it by passing experiments=['app-use'] to Computer()");
|
||||
if (!this.experiments.includes("app-use")) {
|
||||
throw new Error(
|
||||
"App Usage is an experimental feature. Enable it by passing experiments=['app-use'] to Computer()"
|
||||
);
|
||||
}
|
||||
// DioramaComputer would be imported and used here
|
||||
throw new Error('DioramaComputer not yet implemented');
|
||||
throw new Error("DioramaComputer not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,11 +218,11 @@ export class Computer {
|
||||
async run(): Promise<string | undefined> {
|
||||
// If already initialized, just log and return
|
||||
if (this._initialized) {
|
||||
this.logger.info('Computer already initialized, skipping initialization');
|
||||
this.logger.info("Computer already initialized, skipping initialization");
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info('Starting computer...');
|
||||
this.logger.info("Starting computer...");
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
@@ -220,29 +230,32 @@ export class Computer {
|
||||
|
||||
// If using host computer server
|
||||
if (this.useHostComputerServer) {
|
||||
this.logger.info('Using host computer server');
|
||||
ipAddress = 'localhost';
|
||||
|
||||
this.logger.info("Using host computer server");
|
||||
ipAddress = "localhost";
|
||||
|
||||
// Create the interface
|
||||
this._interface = InterfaceFactory.createInterfaceForOS(
|
||||
this.osType,
|
||||
ipAddress
|
||||
);
|
||||
|
||||
this.logger.info('Waiting for host computer server to be ready...');
|
||||
this.logger.info("Waiting for host computer server to be ready...");
|
||||
await this._interface.waitForReady();
|
||||
this.logger.info('Host computer server ready');
|
||||
this.logger.info("Host computer server ready");
|
||||
} else {
|
||||
// Start or connect to VM
|
||||
this.logger.info(`Starting VM: ${this.image}`);
|
||||
|
||||
|
||||
if (!this._providerContext) {
|
||||
try {
|
||||
const providerTypeName = typeof this.providerType === 'object'
|
||||
? this.providerType
|
||||
: this.providerType;
|
||||
|
||||
this.logger.verbose(`Initializing ${providerTypeName} provider context...`);
|
||||
const providerTypeName =
|
||||
typeof this.providerType === "object"
|
||||
? this.providerType
|
||||
: this.providerType;
|
||||
|
||||
this.logger.info(
|
||||
`Initializing ${providerTypeName} provider context...`
|
||||
);
|
||||
|
||||
// Create VM provider instance with explicit parameters
|
||||
const providerOptions = {
|
||||
@@ -251,14 +264,15 @@ export class Computer {
|
||||
storage: this.storage,
|
||||
sharedPath: this.sharedPath,
|
||||
image: this.image,
|
||||
verbose: this.verbosity >= LogLevel.DEBUG,
|
||||
verbose:
|
||||
this.logger.level === "debug" || this.logger.level === "trace",
|
||||
ephemeral: this.ephemeral,
|
||||
noVNCPort: this.noVNCPort,
|
||||
apiKey: this.apiKey
|
||||
apiKey: this.apiKey,
|
||||
};
|
||||
|
||||
if (!this.config) {
|
||||
throw new Error('Computer config not initialized');
|
||||
throw new Error("Computer config not initialized");
|
||||
}
|
||||
|
||||
this.config.vm_provider = await VMProviderFactory.createProvider(
|
||||
@@ -267,33 +281,38 @@ export class Computer {
|
||||
);
|
||||
|
||||
this._providerContext = await this.config.vm_provider.__aenter__();
|
||||
this.logger.verbose('VM provider context initialized successfully');
|
||||
this.logger.debug("VM provider context initialized successfully");
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to import provider dependencies: ${error}`);
|
||||
this.logger.error(
|
||||
`Failed to import provider dependencies: ${error}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Run the VM
|
||||
if (!this.config || !this.config.vm_provider) {
|
||||
throw new Error('VM provider not initialized');
|
||||
throw new Error("VM provider not initialized");
|
||||
}
|
||||
|
||||
const runOpts = {
|
||||
display: this.config.display,
|
||||
memory: this.config.memory,
|
||||
cpu: this.config.cpu,
|
||||
shared_directories: this.sharedDirectories
|
||||
shared_directories: this.sharedDirectories,
|
||||
};
|
||||
|
||||
this.logger.info(`Running VM ${this.config.name} with options:`, runOpts);
|
||||
|
||||
this.logger.info(
|
||||
`Running VM ${this.config.name} with options:`,
|
||||
runOpts
|
||||
);
|
||||
|
||||
if (this._telemetryEnabled) {
|
||||
recordVMStart(this.config.name, String(this.providerType));
|
||||
}
|
||||
|
||||
const storageParam = this.ephemeral ? 'ephemeral' : this.storage;
|
||||
|
||||
const storageParam = this.ephemeral ? "ephemeral" : this.storage;
|
||||
|
||||
try {
|
||||
await this.config.vm_provider.runVM(
|
||||
this.image,
|
||||
@@ -302,7 +321,7 @@ export class Computer {
|
||||
storageParam
|
||||
);
|
||||
} catch (error: any) {
|
||||
if (error.message?.includes('already running')) {
|
||||
if (error.message?.includes("already running")) {
|
||||
this.logger.info(`VM ${this.config.name} is already running`);
|
||||
} else {
|
||||
throw error;
|
||||
@@ -311,9 +330,9 @@ export class Computer {
|
||||
|
||||
// Wait for VM to be ready
|
||||
try {
|
||||
this.logger.info('Waiting for VM to be ready...');
|
||||
this.logger.info("Waiting for VM to be ready...");
|
||||
await this.waitVMReady();
|
||||
|
||||
|
||||
// Get IP address
|
||||
ipAddress = await this.getIP();
|
||||
this.logger.info(`VM is ready with IP: ${ipAddress}`);
|
||||
@@ -326,14 +345,22 @@ export class Computer {
|
||||
// Initialize the interface
|
||||
try {
|
||||
// Verify we have a valid IP before initializing the interface
|
||||
if (!ipAddress || ipAddress === 'unknown' || ipAddress === '0.0.0.0') {
|
||||
throw new Error(`Cannot initialize interface - invalid IP address: ${ipAddress}`);
|
||||
if (!ipAddress || ipAddress === "unknown" || ipAddress === "0.0.0.0") {
|
||||
throw new Error(
|
||||
`Cannot initialize interface - invalid IP address: ${ipAddress}`
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.info(`Initializing interface for ${this.osType} at ${ipAddress}`);
|
||||
this.logger.info(
|
||||
`Initializing interface for ${this.osType} at ${ipAddress}`
|
||||
);
|
||||
|
||||
// Pass authentication credentials if using cloud provider
|
||||
if (this.providerType === VMProviderType.CLOUD && this.apiKey && this.config?.name) {
|
||||
if (
|
||||
this.providerType === VMProviderType.CLOUD &&
|
||||
this.apiKey &&
|
||||
this.config?.name
|
||||
) {
|
||||
this._interface = InterfaceFactory.createInterfaceForOS(
|
||||
this.osType,
|
||||
ipAddress,
|
||||
@@ -348,7 +375,7 @@ export class Computer {
|
||||
}
|
||||
|
||||
// Wait for the WebSocket interface to be ready
|
||||
this.logger.info('Connecting to WebSocket interface...');
|
||||
this.logger.info("Connecting to WebSocket interface...");
|
||||
|
||||
try {
|
||||
await withTimeout(
|
||||
@@ -356,9 +383,11 @@ export class Computer {
|
||||
30000,
|
||||
`Could not connect to WebSocket interface at ${ipAddress}:8000/ws`
|
||||
);
|
||||
this.logger.info('WebSocket interface connected successfully');
|
||||
this.logger.info("WebSocket interface connected successfully");
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to connect to WebSocket interface at ${ipAddress}`);
|
||||
this.logger.error(
|
||||
`Failed to connect to WebSocket interface at ${ipAddress}`
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -366,13 +395,13 @@ export class Computer {
|
||||
if (!this.useHostComputerServer) {
|
||||
// In TypeScript, we'll use a Promise instead of asyncio.Event
|
||||
let resolveStop: () => void;
|
||||
this._stopEvent = new Promise<void>(resolve => {
|
||||
this._stopEvent = new Promise<void>((resolve) => {
|
||||
resolveStop = resolve;
|
||||
});
|
||||
this._keepAliveTask = this._stopEvent;
|
||||
}
|
||||
|
||||
this.logger.info('Computer is ready');
|
||||
this.logger.info("Computer is ready");
|
||||
|
||||
// Set the initialization flag
|
||||
this._initialized = true;
|
||||
@@ -380,13 +409,15 @@ export class Computer {
|
||||
// Set this instance as the default computer for remote decorators
|
||||
setDefaultComputer(this);
|
||||
|
||||
this.logger.info('Computer successfully initialized');
|
||||
this.logger.info("Computer successfully initialized");
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} finally {
|
||||
// Log initialization time for performance monitoring
|
||||
const durationMs = Date.now() - startTime;
|
||||
this.logger.debug(`Computer initialization took ${durationMs.toFixed(2)}ms`);
|
||||
this.logger.debug(
|
||||
`Computer initialization took ${durationMs.toFixed(2)}ms`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to initialize computer: ${error}`);
|
||||
@@ -413,34 +444,37 @@ export class Computer {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
this.logger.info('Stopping Computer...');
|
||||
this.logger.info("Stopping Computer...");
|
||||
|
||||
// In VM mode, first explicitly stop the VM, then exit the provider context
|
||||
if (!this.useHostComputerServer && this._providerContext && this.config?.vm_provider) {
|
||||
if (
|
||||
!this.useHostComputerServer &&
|
||||
this._providerContext &&
|
||||
this.config?.vm_provider
|
||||
) {
|
||||
try {
|
||||
this.logger.info(`Stopping VM ${this.config.name}...`);
|
||||
await this.config.vm_provider.stopVM(
|
||||
this.config.name,
|
||||
this.storage
|
||||
);
|
||||
await this.config.vm_provider.stopVM(this.config.name, this.storage);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error stopping VM: ${error}`);
|
||||
}
|
||||
|
||||
this.logger.verbose('Closing VM provider context...');
|
||||
this.logger.info("Closing VM provider context...");
|
||||
await this.config.vm_provider.__aexit__(null, null, null);
|
||||
this._providerContext = undefined;
|
||||
}
|
||||
|
||||
await this.disconnect();
|
||||
this.logger.info('Computer stopped');
|
||||
this.logger.info("Computer stopped");
|
||||
} catch (error) {
|
||||
this.logger.debug(`Error during cleanup: ${error}`);
|
||||
} finally {
|
||||
// Log stop time for performance monitoring
|
||||
const durationMs = Date.now() - startTime;
|
||||
this.logger.debug(`Computer stop process took ${durationMs.toFixed(2)}ms`);
|
||||
|
||||
this.logger.debug(
|
||||
`Computer stop process took ${durationMs.toFixed(2)}ms`
|
||||
);
|
||||
|
||||
if (this._telemetryEnabled && this.config?.name) {
|
||||
recordVMStop(this.config.name, durationMs);
|
||||
}
|
||||
@@ -450,22 +484,27 @@ export class Computer {
|
||||
/**
|
||||
* Get the IP address of the VM or localhost if using host computer server.
|
||||
*/
|
||||
async getIP(maxRetries: number = 15, retryDelay: number = 2): Promise<string> {
|
||||
async getIP(
|
||||
maxRetries: number = 15,
|
||||
retryDelay: number = 2
|
||||
): Promise<string> {
|
||||
// For host computer server, always return localhost immediately
|
||||
if (this.useHostComputerServer) {
|
||||
return '127.0.0.1';
|
||||
return "127.0.0.1";
|
||||
}
|
||||
|
||||
// Get IP from the provider
|
||||
if (!this.config?.vm_provider) {
|
||||
throw new Error('VM provider is not initialized');
|
||||
throw new Error("VM provider is not initialized");
|
||||
}
|
||||
|
||||
// Log that we're waiting for the IP
|
||||
this.logger.info(`Waiting for VM ${this.config.name} to get an IP address...`);
|
||||
this.logger.info(
|
||||
`Waiting for VM ${this.config.name} to get an IP address...`
|
||||
);
|
||||
|
||||
// Call the provider's get_ip method which will wait indefinitely
|
||||
const storageParam = this.ephemeral ? 'ephemeral' : this.storage;
|
||||
const storageParam = this.ephemeral ? "ephemeral" : this.storage;
|
||||
|
||||
// Log the image being used
|
||||
this.logger.info(`Running VM using image: ${this.image}`);
|
||||
@@ -496,16 +535,18 @@ export class Computer {
|
||||
let lastStatus: string | undefined;
|
||||
let attempts = 0;
|
||||
|
||||
this.logger.info(`Waiting for VM ${this.config?.name} to be ready (timeout: ${timeout}s)...`);
|
||||
this.logger.info(
|
||||
`Waiting for VM ${this.config?.name} to be ready (timeout: ${timeout}s)...`
|
||||
);
|
||||
|
||||
while ((Date.now() / 1000) - startTime < timeout) {
|
||||
while (Date.now() / 1000 - startTime < timeout) {
|
||||
attempts++;
|
||||
const elapsed = (Date.now() / 1000) - startTime;
|
||||
const elapsed = Date.now() / 1000 - startTime;
|
||||
|
||||
try {
|
||||
// Keep polling for VM info
|
||||
if (!this.config?.vm_provider) {
|
||||
this.logger.error('VM provider is not initialized');
|
||||
this.logger.error("VM provider is not initialized");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -522,14 +563,23 @@ export class Computer {
|
||||
const currentStatus = vm?.status;
|
||||
if (currentStatus !== lastStatus) {
|
||||
this.logger.info(
|
||||
`VM status changed to: ${currentStatus} (after ${elapsed.toFixed(1)}s)`
|
||||
`VM status changed to: ${currentStatus} (after ${elapsed.toFixed(
|
||||
1
|
||||
)}s)`
|
||||
);
|
||||
lastStatus = currentStatus;
|
||||
}
|
||||
|
||||
// Check if VM is ready
|
||||
if (vm && vm.status === 'running' && vm.ip_address && vm.ip_address !== '0.0.0.0') {
|
||||
this.logger.info(`VM ${this.config.name} is ready with IP: ${vm.ip_address}`);
|
||||
if (
|
||||
vm &&
|
||||
vm.status === "running" &&
|
||||
vm.ip_address &&
|
||||
vm.ip_address !== "0.0.0.0"
|
||||
) {
|
||||
this.logger.info(
|
||||
`VM ${this.config.name} is ready with IP: ${vm.ip_address}`
|
||||
);
|
||||
return vm;
|
||||
}
|
||||
|
||||
@@ -541,7 +591,9 @@ export class Computer {
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`VM ${this.config?.name} failed to become ready within ${timeout} seconds`);
|
||||
throw new Error(
|
||||
`VM ${this.config?.name} failed to become ready within ${timeout} seconds`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -549,12 +601,12 @@ export class Computer {
|
||||
*/
|
||||
async update(cpu?: number, memory?: string): Promise<void> {
|
||||
if (this.useHostComputerServer) {
|
||||
this.logger.warning('Cannot update settings for host computer server');
|
||||
this.logger.warn("Cannot update settings for host computer server");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.config?.vm_provider) {
|
||||
throw new Error('VM provider is not initialized');
|
||||
throw new Error("VM provider is not initialized");
|
||||
}
|
||||
|
||||
await this.config.vm_provider.updateVM(
|
||||
@@ -568,11 +620,13 @@ export class Computer {
|
||||
/**
|
||||
* Get the dimensions of a screenshot.
|
||||
*/
|
||||
async getScreenshotSize(screenshot: Buffer): Promise<{ width: number; height: number }> {
|
||||
async getScreenshotSize(
|
||||
screenshot: Buffer
|
||||
): Promise<{ width: number; height: number }> {
|
||||
const metadata = await sharp(screenshot).metadata();
|
||||
return {
|
||||
width: metadata.width || 0,
|
||||
height: metadata.height || 0
|
||||
height: metadata.height || 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -581,7 +635,7 @@ export class Computer {
|
||||
*/
|
||||
get interface(): BaseComputerInterface {
|
||||
if (!this._interface) {
|
||||
throw new Error('Computer interface not initialized. Call run() first.');
|
||||
throw new Error("Computer interface not initialized. Call run() first.");
|
||||
}
|
||||
return this._interface;
|
||||
}
|
||||
@@ -598,18 +652,18 @@ export class Computer {
|
||||
*/
|
||||
toScreenCoordinates(x: number, y: number): [number, number] {
|
||||
if (!this.config?.display) {
|
||||
throw new Error('Display configuration not available');
|
||||
throw new Error("Display configuration not available");
|
||||
}
|
||||
return [
|
||||
x * this.config.display.width,
|
||||
y * this.config.display.height
|
||||
];
|
||||
return [x * this.config.display.width, y * this.config.display.height];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert screen coordinates to screenshot coordinates.
|
||||
*/
|
||||
async toScreenshotCoordinates(x: number, y: number): Promise<[number, number]> {
|
||||
async toScreenshotCoordinates(
|
||||
x: number,
|
||||
y: number
|
||||
): Promise<[number, number]> {
|
||||
// In the Python version, this uses the interface to get screenshot dimensions
|
||||
// For now, we'll assume 1:1 mapping
|
||||
return [x, y];
|
||||
@@ -618,10 +672,13 @@ export class Computer {
|
||||
/**
|
||||
* Install packages in a virtual environment.
|
||||
*/
|
||||
async venvInstall(venvName: string, requirements: string[]): Promise<[string, string]> {
|
||||
async venvInstall(
|
||||
venvName: string,
|
||||
requirements: string[]
|
||||
): Promise<[string, string]> {
|
||||
// This would be implemented using the interface to run commands
|
||||
// TODO: Implement venvInstall
|
||||
throw new Error('venvInstall not yet implemented');
|
||||
throw new Error("venvInstall not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -630,15 +687,19 @@ export class Computer {
|
||||
async venvCmd(venvName: string, command: string): Promise<[string, string]> {
|
||||
// This would be implemented using the interface to run commands
|
||||
// TODO: Implement venvCmd
|
||||
throw new Error('venvCmd not yet implemented');
|
||||
throw new Error("venvCmd not yet implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute function in a virtual environment using source code extraction.
|
||||
*/
|
||||
async venvExec(venvName: string, pythonFunc: Function, ...args: any[]): Promise<any> {
|
||||
async venvExec(
|
||||
venvName: string,
|
||||
pythonFunc: Function,
|
||||
...args: any[]
|
||||
): Promise<any> {
|
||||
// This would be implemented using the interface to run Python code
|
||||
// TODO: Implement venvExec
|
||||
throw new Error('venvExec not yet implemented');
|
||||
throw new Error("venvExec not yet implemented");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* Logger implementation for the Computer library.
|
||||
*/
|
||||
|
||||
export enum LogLevel {
|
||||
DEBUG = 10,
|
||||
VERBOSE = 15,
|
||||
INFO = 20,
|
||||
NORMAL = 20,
|
||||
WARNING = 30,
|
||||
ERROR = 40,
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
private name: string;
|
||||
private verbosity: number;
|
||||
|
||||
constructor(name: string, verbosity: number | LogLevel = LogLevel.NORMAL) {
|
||||
this.name = name;
|
||||
this.verbosity = typeof verbosity === 'number' ? verbosity : verbosity;
|
||||
}
|
||||
|
||||
private log(level: LogLevel, message: string, ...args: any[]): void {
|
||||
if (level >= this.verbosity) {
|
||||
const timestamp = new Date().toISOString();
|
||||
const levelName = LogLevel[level];
|
||||
console.log(`[${timestamp}] [${this.name}] [${levelName}] ${message}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
debug(message: string, ...args: any[]): void {
|
||||
this.log(LogLevel.DEBUG, message, ...args);
|
||||
}
|
||||
|
||||
info(message: string, ...args: any[]): void {
|
||||
this.log(LogLevel.INFO, message, ...args);
|
||||
}
|
||||
|
||||
verbose(message: string, ...args: any[]): void {
|
||||
this.log(LogLevel.VERBOSE, message, ...args);
|
||||
}
|
||||
|
||||
warning(message: string, ...args: any[]): void {
|
||||
this.log(LogLevel.WARNING, message, ...args);
|
||||
}
|
||||
|
||||
error(message: string, ...args: any[]): void {
|
||||
this.log(LogLevel.ERROR, message, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +73,8 @@ export class LumierProvider extends BaseVMProviderImpl {
|
||||
|
||||
const match = memoryStr.match(/^(\d+)([A-Za-z]*)$/);
|
||||
if (match) {
|
||||
const value = parseInt(match[1] || "0");
|
||||
const unit = match[2]?.toUpperCase() || "";
|
||||
const value = parseInt(match[1]);
|
||||
const unit = match[2].toUpperCase();
|
||||
|
||||
if (unit === "GB" || unit === "G") {
|
||||
return value * 1024;
|
||||
|
||||
Reference in New Issue
Block a user