crossProviderSwitchRefusal function

ModelCoherenceRefusal? crossProviderSwitchRefusal({
  1. required String providerName,
  2. required bool targetResolvable,
  3. required bool rolesMode,
  4. required bool targetKeyResolved,
  5. required String? targetKeySlot,
})

Why a cross-provider pick is refused UP FRONT (G1, E1/E3) — null when the whole-triple switch may proceed.

  • targetResolvable false → E1: the entry's provider disappeared between listing and picking; the refusal names the provider and the reconfigure command, and no partial triple is applied.
  • Legacy wiring (no roles resolver) with targetKeyResolved false → E3: the switch would bake an empty key and the request would leave credential-less; the refusal names the TARGET provider's key slot. Roles mode is exempt: the chain resolves keys per entry at request time and skips with the correct-slot hint, so a switch is allowed.

Implementation

ModelCoherenceRefusal? crossProviderSwitchRefusal({
  required String providerName,
  required bool targetResolvable,
  required bool rolesMode,
  required bool targetKeyResolved,
  required String? targetKeySlot,
}) {
  if (!targetResolvable) {
    return ModelCoherenceRefusal(
      problem:
          'provider "$providerName" is not configured '
          '(removed since the list was built?)',
      fields: const ['provider'],
      repair: '/provider add — or pick a configured provider with /provider',
    );
  }
  if (!rolesMode && !targetKeyResolved) {
    return ModelCoherenceRefusal(
      problem: 'no key resolved for provider "$providerName"',
      fields: const ['key'],
      repair:
          '/key set ${targetKeySlot ?? '<ENDPOINT_KEY_NAME>'} <value> and '
          'retry /model',
    );
  }
  return null;
}