import 'dart:convert'; import 'dart:io'; import 'platform_environment.dart'; /// Bridge for generating Codex configuration files. /// /// This class generates `~/.codex/config.toml` and `~/.codex/auth.json` /// to configure Codex CLI to use XWorkmate's AI Gateway. class CodexConfigBridge { static const String _managedBlockStart = '# BEGIN XWORKMATE MANAGED BLOCK'; static const String _managedBlockEnd = '# END XWORKMATE MANAGED BLOCK'; static const String _managedMcpBlockStart = '# BEGIN XWORKMATE MANAGED MCP BLOCK'; static const String _managedMcpBlockEnd = '# END XWORKMATE MANAGED MCP BLOCK'; final String codexHome; CodexConfigBridge({String? codexHome}) : codexHome = codexHome ?? resolveCodexHomeDirectory(); /// Generate config.toml to use XWorkmate AI Gateway. Future configureForGateway({ required String gatewayUrl, required String apiKey, String providerName = 'xworkmate', String defaultModel = 'gpt-4.1', CodexSandboxMode sandbox = CodexSandboxMode.workspaceWrite, CodexApprovalPolicy approval = CodexApprovalPolicy.suggest, Map? extraConfig, }) async { final configDir = Directory(codexHome); if (!await configDir.exists()) { await configDir.create(recursive: true); } final configFile = File('$codexHome/config.toml'); final existingConfig = await configFile.exists() ? await configFile.readAsString() : ''; final preserved = _stripManagedBlock(existingConfig).trimRight(); final managedBlock = _buildManagedBlock( gatewayUrl: gatewayUrl, apiKey: apiKey, providerName: providerName, defaultModel: defaultModel, sandbox: sandbox, approval: approval, extraConfig: extraConfig, ); final merged = preserved.isEmpty ? '$managedBlock\n' : '$preserved\n\n$managedBlock\n'; await configFile.writeAsString(merged); } String _buildProviderSection({ required String providerName, required String gatewayUrl, required String apiKey, }) { final buffer = StringBuffer(); buffer.writeln('[model_providers.$providerName]'); buffer.writeln('name = "XWorkmate AI Gateway"'); buffer.writeln('base_url = "$gatewayUrl"'); // Use experimental_bearer_token for API key if (apiKey.isNotEmpty) { buffer.writeln('experimental_bearer_token = "$apiKey"'); } buffer.writeln('wire_api = "responses"'); buffer.writeln('supports_websockets = false'); return buffer.toString(); } String _buildManagedBlock({ required String gatewayUrl, required String apiKey, required String providerName, required String defaultModel, required CodexSandboxMode sandbox, required CodexApprovalPolicy approval, Map? extraConfig, }) { final providerSection = _buildProviderSection( providerName: providerName, gatewayUrl: gatewayUrl, apiKey: apiKey, ); final config = StringBuffer(); config.writeln(_managedBlockStart); config.writeln('# Generated by XWorkmate - AI Gateway Configuration'); config.writeln('# Last updated: ${DateTime.now().toIso8601String()}'); config.writeln(); config.writeln(providerSection); config.writeln(); config.writeln('[model]'); config.writeln('model = "$defaultModel"'); config.writeln(); config.writeln('[approval_policy]'); config.writeln('policy = "${approval.value}"'); config.writeln(); config.writeln('[sandbox]'); config.writeln('mode = "${sandbox.value}"'); config.writeln(); config.writeln('[features]'); config.writeln('child_agents_md = true'); config.writeln('realtime = false'); config.writeln(); if (extraConfig != null && extraConfig.isNotEmpty) { config.writeln('# Custom configuration'); for (final entry in extraConfig.entries) { config.writeln('${entry.key} = "${entry.value}"'); } config.writeln(); } config.writeln(_managedBlockEnd); return config.toString().trimRight(); } String _stripManagedBlock(String content) { return _stripBlock(content, _managedBlockStart, _managedBlockEnd); } String _stripManagedMcpBlock(String content) { return _stripBlock(content, _managedMcpBlockStart, _managedMcpBlockEnd); } String _stripBlock(String content, String startMarker, String endMarker) { if (content.isEmpty) { return content; } var remaining = content; while (true) { final start = remaining.indexOf(startMarker); if (start < 0) { break; } final end = remaining.indexOf(endMarker, start); if (end < 0) { remaining = remaining.substring(0, start); break; } remaining = remaining.substring(0, start) + remaining.substring(end + endMarker.length); } return remaining; } /// Generate auth.json for ChatGPT OAuth authentication. Future configureAuth({ required String accessToken, String? refreshToken, DateTime? expiresAt, String? email, String? plan, }) async { final authFile = File('$codexHome/auth.json'); final auth = { 'access_token': accessToken, 'last_refresh': DateTime.now().toIso8601String(), }; if (refreshToken != null && refreshToken.isNotEmpty) { auth['refresh_token'] = refreshToken; } if (expiresAt != null) { auth['expires_at'] = expiresAt.millisecondsSinceEpoch; } if (email != null && email.isNotEmpty) { auth['email'] = email; } if (plan != null && plan.isNotEmpty) { auth['plan'] = plan; } await authFile.writeAsString(JsonEncoder.withIndent(' ').convert(auth)); } /// Configure MCP servers for Codex. Future configureMcpServers({ required List servers, bool append = true, }) async { final configFile = File('$codexHome/config.toml'); String existingConfig = ''; if (await configFile.exists()) { existingConfig = await configFile.readAsString(); } final buffer = StringBuffer(); if (append && existingConfig.isNotEmpty) { buffer.writeln(existingConfig); buffer.writeln(); } buffer.writeln('# MCP Servers'); for (final server in servers) { buffer.writeln('[mcp_servers.${server.name}]'); buffer.writeln('command = "${server.command}"'); if (server.args.isNotEmpty) { buffer.writeln('args = ${_formatTomlArray(server.args)}'); } if (server.env.isNotEmpty) { buffer.writeln('[mcp_servers.${server.name}.env]'); for (final entry in server.env.entries) { buffer.writeln('${entry.key} = "${entry.value}"'); } } buffer.writeln(); } await configFile.writeAsString(buffer.toString()); } Future configureManagedMcpServers({ required List servers, }) async { final configDir = Directory(codexHome); if (!await configDir.exists()) { await configDir.create(recursive: true); } final configFile = File('$codexHome/config.toml'); final existingConfig = await configFile.exists() ? await configFile.readAsString() : ''; final preserved = _stripManagedMcpBlock(existingConfig).trimRight(); final managedBlock = _buildManagedMcpBlock(servers); final merged = preserved.isEmpty ? '$managedBlock\n' : '$preserved\n\n$managedBlock\n'; await configFile.writeAsString(merged); } String _buildManagedMcpBlock(List servers) { final buffer = StringBuffer() ..writeln(_managedMcpBlockStart) ..writeln('# Generated by XWorkmate - Managed MCP Server Configuration') ..writeln('# Last updated: ${DateTime.now().toIso8601String()}') ..writeln(); for (final server in servers) { buffer.writeln('[mcp_servers.${server.name}]'); buffer.writeln('command = "${server.command}"'); if (server.args.isNotEmpty) { buffer.writeln('args = ${_formatTomlArray(server.args)}'); } if (server.env.isNotEmpty) { buffer.writeln('[mcp_servers.${server.name}.env]'); for (final entry in server.env.entries) { buffer.writeln('${entry.key} = "${entry.value}"'); } } buffer.writeln(); } buffer.writeln(_managedMcpBlockEnd); return buffer.toString().trimRight(); } String _formatTomlArray(List items) { if (items.isEmpty) return '[]'; if (items.length == 1) return '["${items[0]}"]'; return '[${items.map((s) => '"$s"').join(', ')}]'; } /// Generate configuration for OpenClaw Gateway integration. Future configureOpenClawGateway({ required String gatewayUrl, required String token, String providerName = 'openclaw', }) async { await configureForGateway( gatewayUrl: gatewayUrl, apiKey: token, providerName: providerName, ); // Add MCP server for OpenClaw await configureMcpServers( servers: [ CodexMcpServer( name: 'openclaw', command: 'openclaw-mcp', args: ['--gateway', gatewayUrl], env: {'OPENCLAW_TOKEN': token}, ), ], append: true, ); } /// Check if Codex configuration exists. Future hasConfig() async { final configFile = File('$codexHome/config.toml'); return configFile.exists(); } /// Check if auth.json exists. Future hasAuth() async { final authFile = File('$codexHome/auth.json'); return authFile.exists(); } /// Read current model provider configuration. Future?> readProviderConfig(String providerName) async { final configFile = File('$codexHome/config.toml'); if (!await configFile.exists()) { return null; } final content = await configFile.readAsString(); return _parseTomlSection(content, 'model_providers.$providerName'); } /// Parse a TOML section into a Map. Map? _parseTomlSection(String content, String section) { final lines = content.split('\n'); final result = {}; bool inSection = false; for (final line in lines) { final trimmed = line.trim(); if (trimmed.isEmpty || trimmed.startsWith('#')) continue; if (trimmed.startsWith('[')) { final sectionName = trimmed.substring(1, trimmed.length - 1); inSection = sectionName == section; continue; } if (inSection) { final eqIndex = trimmed.indexOf('='); if (eqIndex > 0) { final key = trimmed.substring(0, eqIndex).trim(); var value = trimmed.substring(eqIndex + 1).trim(); // Remove quotes if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { value = value.substring(1, value.length - 1); } result[key] = value; } } } return result.isNotEmpty ? result : null; } /// Clear all Codex configuration. Future clearConfig() async { final configDir = Directory(codexHome); if (await configDir.exists()) { await configDir.delete(recursive: true); } } } /// Codex sandbox mode for configuration. enum CodexSandboxMode { readOnly('read-only'), workspaceWrite('workspace-write'), dangerFullAccess('danger-full-access'); final String value; const CodexSandboxMode(this.value); } /// Codex approval policy for configuration. enum CodexApprovalPolicy { suggest('suggest'), autoEdit('auto-edit'), fullAuto('full-auto'); final String value; const CodexApprovalPolicy(this.value); } /// MCP server configuration for Codex. class CodexMcpServer { final String name; final String command; final List args; final Map env; const CodexMcpServer({ required this.name, required this.command, this.args = const [], this.env = const {}, }); }