resolveEffectiveCliArgs function

({CliArgs args, EnvProviderPreconfig? faPreconfig, String? incompatibleSavedEndpoint, String provider, String? unknownSavedProvider}) resolveEffectiveCliArgs(
  1. CliArgs parsed,
  2. CliConfig saved, {
  3. required Map<String, String> env,
})

Provider/model restoration. Precedence: an explicit --provider flag (full manual control, preconfigs disabled) > the FA_PROVIDER_* env declaration (faProviderPreconfig) > the saved provider: (the persisted /provider switch) > the parsed default.

The saved kind restores whenever it resolves through the catalog (resolveCliProviderSpec — every catalog name AND adapter kind, chatgpt-codex included; coverage by construction, issue #760) and restores AS THE RESOLVED SPEC'S KIND: a saved catalog name (openai, chatgpt) resolves to its adapter kind here, because everything downstream (AgentCliConfig.providerKind → providerStreamFunction) speaks kinds only. A saved id NO version knows must never brick the boot: it is reported back as unknownSavedProvider and the parsed default (a known provider) takes over — the executable prints the loud named warning (bad value, file, fallback taken) and keeps booting.

The preconfig supplies model/baseUrl too, so the saved restore never leaks through while it is active (--model/--base-url flags still override individual fields). No env-key auto-pick: a bare API key in the environment never activates a provider — the model would be an implicit default, and providers carry none by design. FA_PROVIDER_* stays: it names the model explicitly. The environment is injected (env is required): the CLI passes Platform.environment, tests pass exactly the map they mean — resolution never reads ambient process state.

Returns the effective CliArgs, the resolved provider kind — the same value, the explicit record field saves the caller a re-derivation — the FA_PROVIDER_* declaration when one is active (the caller needs it for the roles pinning, the key decision and the extra redaction), the saved provider id when it was unrecognizable, and the saved provider id when the persisted provider/baseUrl pair was unservable (endpoint-locked kind, foreign baseUrl — the caller degrades it with a named warning). Both report fields are null otherwise.

Implementation

({
  CliArgs args,
  String provider,
  EnvProviderPreconfig? faPreconfig,
  String? unknownSavedProvider,
  String? incompatibleSavedEndpoint,
})
resolveEffectiveCliArgs(
  CliArgs parsed,
  CliConfig saved, {
  required Map<String, String> env,
}) {
  final faPreconfig = faProviderPreconfig(parsed, saved, env: env);
  final restore = _judgeSavedRestore(saved);
  final restoredKind = restore.endpointConflict ? null : restore.spec?.kind;
  final provider = _bootProvider(
    parsed: parsed,
    faPreconfig: faPreconfig,
    restoredKind: restoredKind,
  );
  final explicitOverride = parsed.providerExplicit || faPreconfig != null;
  final reports = _savedRestoreReports(
    savedRaw: restore.raw,
    savedSpec: restore.spec,
    endpointConflict: restore.endpointConflict,
    explicitOverride: explicitOverride,
  );
  // gh-1570 (L9/E2): an endpoint-conflict DEGRADE must not inherit the
  // stale pair — the saved kind was refused over its baseUrl. "Stale" is
  // the binding's verdict, not the URL's spelling: the pair rides the
  // degraded boot only when it still resolves through the one validating
  // constructor (E2 "rebuilds a valid binding") — the gh-760
  // mock-endpoint partial write keeps serving, the codex-over-openrouter
  // default spelling keeps working, and a pair the degraded kind cannot
  // bind (a claude id pinned over api.openai.com) is dropped.
  final degraded = restore.endpointConflict && !explicitOverride;
  final degradedDefault = resolveCliProviderSpec(provider)?.defaultBaseUrl;
  final keepSavedPair = !degraded || _degradedPairResolves(provider, saved);
  final modelId = parsed.model ?? faPreconfig?.modelId ?? saved.modelId;
  final baseUrl = parsed.baseUrl ?? faPreconfig?.baseUrl ?? saved.baseUrl;
  final effective = CliArgs(
    model: keepSavedPair ? modelId : (parsed.model ?? faPreconfig?.modelId),
    provider: provider,
    baseUrl: keepSavedPair ? baseUrl : (parsed.baseUrl ?? faPreconfig?.baseUrl),
    visionModel: parsed.visionModel,
    visionBaseUrl: parsed.visionBaseUrl,
    transcribeModel: parsed.transcribeModel,
    transcribeBaseUrl: parsed.transcribeBaseUrl,
    plugins: parsed.plugins,
    promptTemplateDirs: parsed.promptTemplateDirs,
    mode: parsed.mode ?? saved.mode,
    tools: parsed.tools,
    redact: parsed.redact ?? saved.redact,
    cwd: parsed.cwd,
    sessionRoot: parsed.sessionRoot,
    session: parsed.session,
  );
  // gh-1570 (AC3/H2): an explicit `--model`/`--base-url` over a restored
  // or preconfigured binding is a PARTIAL override — the effective
  // binding (flag slot winning, everything else from the current source)
  // is revalidated through the one constructor and a mismatch is a
  // boot-time rejection naming both sides, never a silent cross-provider
  // ride (`--model gpt-5` over a saved/active anthropic binding dies
  // here, before any network call). An ACTIVE FA_PROVIDER_* preconfig is
  // a binding too (AC3/H2 review): its kind survives and the flag wins
  // its slot, so the same joint check applies.
  _revalidatePartialOverride(
    parsed,
    faPreconfig,
    saved,
    provider: provider,
    baseUrl: baseUrl,
    keepSavedPair: keepSavedPair,
    degradedDefault: degradedDefault,
  );
  return (
    args: effective,
    provider: provider,
    faPreconfig: faPreconfig,
    unknownSavedProvider: reports.unknownSavedProvider,
    incompatibleSavedEndpoint: reports.incompatibleSavedEndpoint,
  );
}