buildLaunchEnv function

Future<Map<String, String>> buildLaunchEnv({
  1. required ProviderProfile profile,
  2. required ProfileFile? persisted,
  3. required RecommendationGoal goal,
  4. Map<String, String>? processEnv,
  5. String getOllamaChatBaseUrl(
    1. String? baseUrl
    )?,
  6. Future<String> resolveOllamaDefaultModel(
    1. RecommendationGoal goal
    )?,
  7. ProviderRequestResolver? resolveProviderRequest,
  8. CodexCredentialResolver? resolveCodexApiCredentials,
  9. bool isCodexBaseUrl(
    1. String url
    )?,
})

Build the full launch environment for a given profile.

Merges process env, persisted profile, and profile-specific defaults. Cleans up keys that don't belong to the chosen profile.

Implementation

Future<Map<String, String>> buildLaunchEnv({
  required ProviderProfile profile,
  required ProfileFile? persisted,
  required RecommendationGoal goal,
  Map<String, String>? processEnv,
  String Function(String? baseUrl)? getOllamaChatBaseUrl,
  Future<String> Function(RecommendationGoal goal)? resolveOllamaDefaultModel,
  ProviderRequestResolver? resolveProviderRequest,
  CodexCredentialResolver? resolveCodexApiCredentials,
  bool Function(String url)? isCodexBaseUrl,
}) async {
  final env = Map<String, String>.from(processEnv ?? {});
  final persistedEnv = (persisted?.profile == profile)
      ? persisted!.env
      : const ProfileEnv();

  final shellGeminiKey = sanitizeApiKey(
    env['GEMINI_API_KEY'] ?? env['GOOGLE_API_KEY'],
  );
  final persistedGeminiKey = sanitizeApiKey(persistedEnv.geminiApiKey);

  // ── Gemini ──
  if (profile == ProviderProfile.gemini) {
    env['MAGE_USE_GEMINI'] = '1';
    env.remove('MAGE_USE_OPENAI');
    env['GEMINI_MODEL'] =
        env['GEMINI_MODEL'] ?? persistedEnv.geminiModel ?? _defaultGeminiModel;
    env['GEMINI_BASE_URL'] =
        env['GEMINI_BASE_URL'] ??
        persistedEnv.geminiBaseUrl ??
        _defaultGeminiBaseUrl;
    final geminiKey = shellGeminiKey ?? persistedGeminiKey;
    if (geminiKey != null) {
      env['GEMINI_API_KEY'] = geminiKey;
    } else {
      env.remove('GEMINI_API_KEY');
    }
    _removeKeys(env, [
      'GOOGLE_API_KEY',
      'OPENAI_BASE_URL',
      'OPENAI_MODEL',
      'OPENAI_API_KEY',
      'CODEX_API_KEY',
      'CHATGPT_ACCOUNT_ID',
      'CODEX_ACCOUNT_ID',
    ]);
    return env;
  }

  // ── OpenAI-compatible profiles ──
  env['MAGE_USE_OPENAI'] = '1';
  _removeKeys(env, [
    'MAGE_USE_GEMINI',
    'GEMINI_API_KEY',
    'GEMINI_MODEL',
    'GEMINI_BASE_URL',
    'GOOGLE_API_KEY',
  ]);

  if (profile == ProviderProfile.ollama) {
    final getBaseUrl =
        getOllamaChatBaseUrl ?? ((_) => 'http://localhost:11434/v1');
    final resolveModel =
        resolveOllamaDefaultModel ?? ((_) async => 'llama3.1:8b');
    env['OPENAI_BASE_URL'] = persistedEnv.openaiBaseUrl ?? getBaseUrl(null);
    env['OPENAI_MODEL'] = persistedEnv.openaiModel ?? await resolveModel(goal);
    _removeKeys(env, [
      'OPENAI_API_KEY',
      'CODEX_API_KEY',
      'CHATGPT_ACCOUNT_ID',
      'CODEX_ACCOUNT_ID',
    ]);
    return env;
  }

  if (profile == ProviderProfile.codex) {
    final isCodex = isCodexBaseUrl ?? (_) => false;
    env['OPENAI_BASE_URL'] =
        (persistedEnv.openaiBaseUrl != null &&
            isCodex(persistedEnv.openaiBaseUrl!))
        ? persistedEnv.openaiBaseUrl!
        : _defaultCodexBaseUrl;
    env['OPENAI_MODEL'] = persistedEnv.openaiModel ?? 'codexplan';
    env.remove('OPENAI_API_KEY');

    final codexKey =
        sanitizeApiKey(env['CODEX_API_KEY']) ??
        sanitizeApiKey(persistedEnv.codexApiKey);
    final codexAccountId =
        env['CHATGPT_ACCOUNT_ID'] ??
        env['CODEX_ACCOUNT_ID'] ??
        persistedEnv.chatgptAccountId ??
        persistedEnv.codexAccountId;

    _setOrRemove(env, 'CODEX_API_KEY', codexKey);
    _setOrRemove(env, 'CHATGPT_ACCOUNT_ID', codexAccountId);
    env.remove('CODEX_ACCOUNT_ID');
    return env;
  }

  // ── Default: OpenAI profile ──
  final defaultOpenAIModel = getGoalDefaultOpenAIModel(goal);
  final shellTransport = resolveProviderRequest != null
      ? resolveProviderRequest(
          model: env['OPENAI_MODEL'],
          baseUrl: env['OPENAI_BASE_URL'],
          fallbackModel: defaultOpenAIModel,
        )
      : 'chat_completions';
  final persistedTransport = resolveProviderRequest != null
      ? resolveProviderRequest(
          model: persistedEnv.openaiModel,
          baseUrl: persistedEnv.openaiBaseUrl,
          fallbackModel: defaultOpenAIModel,
        )
      : 'chat_completions';

  final useShellConfig = shellTransport == 'chat_completions';
  final usePersistedConfig =
      (persistedEnv.openaiModel == null &&
          persistedEnv.openaiBaseUrl == null) ||
      persistedTransport == 'chat_completions';

  env['OPENAI_BASE_URL'] =
      (useShellConfig ? env['OPENAI_BASE_URL'] : null) ??
      (usePersistedConfig ? persistedEnv.openaiBaseUrl : null) ??
      _defaultOpenaiBaseUrl;
  env['OPENAI_MODEL'] =
      (useShellConfig ? env['OPENAI_MODEL'] : null) ??
      (usePersistedConfig ? persistedEnv.openaiModel : null) ??
      defaultOpenAIModel;
  env['OPENAI_API_KEY'] =
      env['OPENAI_API_KEY'] ?? persistedEnv.openaiApiKey ?? '';
  _removeKeys(env, ['CODEX_API_KEY', 'CHATGPT_ACCOUNT_ID', 'CODEX_ACCOUNT_ID']);
  return env;
}