parseEnvProviderPreconfig function

EnvProviderPreconfig? parseEnvProviderPreconfig({
  1. required String? providerType,
  2. required String? providerName,
  3. required String? providerConfig,
  4. required String? providerConfigBase64,
  5. required String? envVarValue(
    1. String name
    ),
  6. required Iterable<String> takenNames,
})

Parses the FA_PROVIDER_* preconfig, or returns null when the feature is off (providerType null/blank).

Throws ConfigException naming the offending input for every invalid value: an unknown provider type, a missing/empty FA_PROVIDER_CONFIG, malformed or non-object config JSON (plain or base64 twin), an unknown config key, a missing required baseUrl/model, and a declared key env var (or its _BASE64 twin) left empty. Nothing falls back to the catalog spec values.

Implementation

EnvProviderPreconfig? parseEnvProviderPreconfig({
  required String? providerType,
  required String? providerName,
  required String? providerConfig,
  required String? providerConfigBase64,
  required String? Function(String name) envVarValue,
  required Iterable<String> takenNames,
}) {
  if (providerType == null || providerType.trim().isEmpty) return null;
  // Resolution goes through the one both-identifier seam (issue #772),
  // honoring the FA_PROVIDERS build filter — exactly what the switch-time
  // and validation surfaces do.
  final spec = resolveCliProviderSpec(providerType, honorBuildFilter: true);
  if (spec == null) {
    throw ConfigException(
      'unknown FA_PROVIDER_TYPE "$providerType" — supported providers: '
      '${enabledProviderNames().join(', ')}',
    );
  }

  final declared = _resolveTwin(
    'FA_PROVIDER_CONFIG',
    providerConfig,
    'FA_PROVIDER_CONFIG_BASE64',
    providerConfigBase64,
  );
  if (declared == null) {
    throw ConfigException(
      'FA_PROVIDER_CONFIG is required when FA_PROVIDER_TYPE is set — '
      'declare at least '
      '${_requiredConfigKeys.map((key) => '"$key"').join(' and ')}',
    );
  }
  final config = _parseConfig(declared);
  for (final key in _requiredConfigKeys) {
    if (!config.values.containsKey(key)) {
      throw ConfigException(
        'FA_PROVIDER_CONFIG is missing "$key" — an env-declared provider '
        'gets no catalog defaults; required keys: '
        '${_requiredConfigKeys.join(', ')}',
      );
    }
  }

  // The entry name must not shadow a saved registry entry or another
  // catalog provider: auto-resolve `-2`, `-3`, ... to the first free
  // suffix. The resolved spec's own name is exempt — the default name
  // (== the type) is that provider's canonical identity, not a collision.
  final requested = (providerName ?? '').trim();
  final catalogNames = providerCatalog.keys.toSet()..remove(spec.name);
  final name = _uniqueName(requested.isEmpty ? spec.name : requested, {
    ...takenNames,
    ...catalogNames,
  });

  // `apiKeyEnvVar` is optional but strict when declared: the named env
  // var (or its `_BASE64` twin) MUST resolve to a non-empty key. Absent
  // means a legitimate keyless boot — no probing of the spec's env names
  // (an unnamed key source is exactly the silent misconfiguration class
  // this parser exists to prevent).
  final ref = config.values['apiKeyEnvVar'];
  final String? keyVar;
  final String apiKey;
  if (ref == null) {
    keyVar = null;
    apiKey = '';
  } else {
    final value =
        _resolveTwin(
          ref,
          envVarValue(ref),
          '${ref}_BASE64',
          envVarValue('${ref}_BASE64'),
        ) ??
        '';
    if (value.isEmpty) {
      throw ConfigException(
        'FA_PROVIDER_CONFIG apiKeyEnvVar "$ref" names an env variable that '
        'is empty or missing — set it before starting the harness',
      );
    }
    keyVar = ref;
    apiKey = value;
  }

  return EnvProviderPreconfig(
    spec: spec,
    name: name,
    baseUrl: config.values['baseUrl']!,
    modelId: config.values['model']!,
    apiKeyEnvVar: keyVar,
    apiKey: apiKey,
    input: config.input,
    thinkingLevel: config.thinkingLevel,
  );
}