114 lines
4.9 KiB
TypeScript
114 lines
4.9 KiB
TypeScript
/**
|
|
* Unified configuration: parse GUI_AGENT_* environment variables,
|
|
* validate, and return a type-safe GuiAgentConfig object.
|
|
*
|
|
* Missing required fields throw ConfigError immediately (Fail Fast).
|
|
*/
|
|
|
|
import { ConfigError } from './utils/errors.js';
|
|
import { logInfo } from './utils/logger.js';
|
|
import type { CoordinateMode } from './coordinates/modelProfiles.js';
|
|
|
|
const VALID_COORDINATE_MODES: readonly string[] = ['image-absolute', 'normalized-1000', 'normalized-999', 'normalized-0-1'];
|
|
|
|
export interface GuiAgentConfig {
|
|
/** LLM provider (e.g. 'anthropic', 'openai', 'google', 'zhipu', 'qwen', 'deepseek') */
|
|
provider: string;
|
|
/** API protocol: 'anthropic' (x-api-key + /v1/messages) or 'openai' (Bearer + /chat/completions) */
|
|
apiProtocol: 'anthropic' | 'openai';
|
|
/** LLM model name */
|
|
model: string;
|
|
/** API key for the LLM provider (optional - only required for gui_analyze_screen tool) */
|
|
apiKey?: string;
|
|
/** Optional base URL for the LLM API */
|
|
baseUrl?: string;
|
|
|
|
/** Memory model provider (defaults to main provider) */
|
|
memoryProvider?: string;
|
|
/** Memory model name (defaults to main model) */
|
|
memoryModel?: string;
|
|
|
|
/** Override coordinate mode (auto-detected from model if empty) */
|
|
coordinateMode?: CoordinateMode;
|
|
/** Target display index */
|
|
displayIndex: number;
|
|
|
|
/** JPEG quality for screenshots (1-100) */
|
|
jpegQuality: number;
|
|
/** Maximum steps per task */
|
|
maxSteps: number;
|
|
/** Delay between steps in ms */
|
|
stepDelayMs: number;
|
|
/** Consecutive similar screenshots to trigger stuck detection */
|
|
stuckThreshold: number;
|
|
|
|
/** MCP server transport mode */
|
|
transport: 'http' | 'stdio';
|
|
/** HTTP server port */
|
|
port: number;
|
|
|
|
/** Optional log file path */
|
|
logFile?: string;
|
|
}
|
|
|
|
function parseIntRange(name: string, value: string | undefined, min: number, max: number, defaultVal: number): number {
|
|
if (!value) return defaultVal;
|
|
const n = parseInt(value, 10);
|
|
if (isNaN(n) || n < min || n > max) {
|
|
throw new ConfigError(`${name} must be between ${min} and ${max}, got: ${value}`);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
export function loadConfig(overrides?: Partial<GuiAgentConfig>): GuiAgentConfig {
|
|
const env = process.env;
|
|
|
|
// API Key is optional at startup - it's only required when gui_analyze_screen tool is used
|
|
// (which requires GUI_AGENT_VISION_MODEL to be configured)
|
|
const apiKey = overrides?.apiKey ?? env.GUI_AGENT_API_KEY;
|
|
|
|
const transport = (overrides?.transport ?? env.GUI_AGENT_TRANSPORT ?? 'http') as 'http' | 'stdio';
|
|
if (transport !== 'http' && transport !== 'stdio') {
|
|
throw new ConfigError(`GUI_AGENT_TRANSPORT must be "http" or "stdio", got: ${transport}`);
|
|
}
|
|
|
|
const apiProtocol = (overrides?.apiProtocol ?? env.GUI_AGENT_API_PROTOCOL ?? 'anthropic') as 'anthropic' | 'openai';
|
|
if (apiProtocol !== 'anthropic' && apiProtocol !== 'openai') {
|
|
throw new ConfigError(`GUI_AGENT_API_PROTOCOL must be "anthropic" or "openai", got: ${apiProtocol}`);
|
|
}
|
|
|
|
const rawCoordinateMode = overrides?.coordinateMode ?? env.GUI_AGENT_COORDINATE_MODE;
|
|
// 'auto' means auto-detect from model name (via modelProfiles), treat as undefined
|
|
const effectiveCoordinateMode = rawCoordinateMode === 'auto' ? undefined : rawCoordinateMode;
|
|
if (effectiveCoordinateMode && !VALID_COORDINATE_MODES.includes(effectiveCoordinateMode)) {
|
|
throw new ConfigError(`GUI_AGENT_COORDINATE_MODE must be one of: auto, ${VALID_COORDINATE_MODES.join(', ')}, got: ${rawCoordinateMode}`);
|
|
}
|
|
|
|
const config: GuiAgentConfig = {
|
|
provider: overrides?.provider ?? env.GUI_AGENT_PROVIDER ?? 'anthropic',
|
|
apiProtocol,
|
|
model: overrides?.model ?? env.GUI_AGENT_MODEL ?? 'claude-sonnet-4-20250514',
|
|
apiKey,
|
|
baseUrl: overrides?.baseUrl ?? env.GUI_AGENT_BASE_URL,
|
|
|
|
memoryProvider: overrides?.memoryProvider ?? env.GUI_AGENT_MEMORY_PROVIDER,
|
|
memoryModel: overrides?.memoryModel ?? env.GUI_AGENT_MEMORY_MODEL,
|
|
|
|
coordinateMode: effectiveCoordinateMode as CoordinateMode | undefined,
|
|
displayIndex: parseIntRange('GUI_AGENT_DISPLAY_INDEX', overrides?.displayIndex?.toString() ?? env.GUI_AGENT_DISPLAY_INDEX, 0, 15, 0),
|
|
|
|
jpegQuality: parseIntRange('GUI_AGENT_JPEG_QUALITY', overrides?.jpegQuality?.toString() ?? env.GUI_AGENT_JPEG_QUALITY, 1, 100, 75),
|
|
maxSteps: parseIntRange('GUI_AGENT_MAX_STEPS', overrides?.maxSteps?.toString() ?? env.GUI_AGENT_MAX_STEPS, 1, 200, 50),
|
|
stepDelayMs: parseIntRange('GUI_AGENT_STEP_DELAY_MS', overrides?.stepDelayMs?.toString() ?? env.GUI_AGENT_STEP_DELAY_MS, 100, 30000, 1500),
|
|
stuckThreshold: parseIntRange('GUI_AGENT_STUCK_THRESHOLD', overrides?.stuckThreshold?.toString() ?? env.GUI_AGENT_STUCK_THRESHOLD, 1, 20, 3),
|
|
|
|
transport,
|
|
port: parseIntRange('GUI_AGENT_PORT', overrides?.port?.toString() ?? env.GUI_AGENT_PORT, 1, 65535, 60008),
|
|
|
|
logFile: overrides?.logFile ?? env.GUI_AGENT_LOG_FILE,
|
|
};
|
|
|
|
logInfo(`Config loaded: provider=${config.provider}, model=${config.model}, transport=${config.transport}, port=${config.port}`);
|
|
return config;
|
|
}
|