Skip to content

[ENHANCEMENT] Isolate provider behavior behind capability registries #1018

Description

@WebMad

Problem (one or two sentences)

Provider integration behavior is spread across parallel switch/case hubs for runtime construction, model defaults and selection, settings validation, model discovery and caching, profile handling, and CLI configuration. Adding or changing a provider requires coordinated edits across disconnected layers, increasing maintenance cost and allowing incomplete integrations or behavior drift.

Context (who is affected and when)

This primarily affects maintainers and contributors whenever a provider is added, changed, renamed, or retired. Each central branch knows implementation details for every provider, so a provider update can work in one environment while remain incomplete in another.

The current production hotspots concentrate these responsibilities:

Area Responsibility concentrated in the central branch Provider-specific knowledge carried there
API runtime Chooses the concrete API handler, rejects retired providers, and handles special model-dependent routing Every handler constructor and runtime implementation
Shared provider metadata Resolves the default model Provider model constants and regional rules
Webview model state Reads provider-specific model fields, combines static and dynamic catalogs, and applies capability or pricing overrides Model catalogs and provider-specific capability rules
Webview settings validation Validates credentials, endpoints, selectors, authentication modes, and account state Provider setting fields and user-facing validation messages
Profile validation Extracts a model ID from differently named provider fields The complete provider settings shape
Dynamic model discovery Selects a model-list fetcher and adapts generic input to provider-specific arguments Fetchers, key conventions, base URLs, and cache isolation rules
CLI settings construction Maps generic CLI key and model input into provider-specific settings Provider setting fields
CLI context-window resolution Extracts the configured model ID A duplicate of the profile model-field mapping
Embedding defaults Chooses an embedding model default Embedding-specific profiles and defaults
Embedding UI validation Builds provider-specific indexing validation Embedding credentials, endpoints, model fields, and validation rules

The highest-value API-provider hotspots are runtime construction, webview model resolution, webview validation, and dynamic model discovery because each commonly changes when a provider is introduced or its configuration evolves.

The duplicated model-ID extraction in profile validation and CLI context-window handling is a smaller concrete example: both locations independently encode which model field belongs to each provider.

Current dependency shape:

flowchart TB
    Provider[Provider identifier]
    Provider --> Runtime[API handler selection]
    Provider --> Defaults[Default model selection]
    Provider --> ModelState[Selected model resolution]
    Provider --> Validation[Settings validation]
    Provider --> Profiles[Profile model resolution]
    Provider --> Discovery[Model discovery and cache policy]
    Provider --> CLI[CLI settings and context]
    Runtime --> Handlers[All runtime handlers]
    Defaults --> Models[All provider model defaults]
    ModelState --> Catalogs[All model catalogs and capability rules]
    Validation --> Fields[All credential and endpoint fields]
    Profiles --> Fields
    Discovery --> Fetchers[All provider model fetchers]
    CLI --> Fields
Loading

These branches act as parallel registries. A provider is threaded through several layers, and every layer must understand details belonging to that provider.

Related work demonstrates the need for a broader architectural discussion:

Desired behavior (conceptual, not technical)

Provider-specific decisions should be owned by provider modules and exposed through small, focused capabilities. Central orchestration should look up those capabilities through type-checked registries rather than contain exhaustive provider branches.

Use separate registries for each dependency boundary instead of one large provider base class:

  • A shared descriptor registry owns provider identity, configured model resolution, and default model resolution.
  • An extension runtime registry owns API handler construction, optional dynamic model discovery, and cache-isolation policy.
  • A webview registry owns settings validation and selected-model resolution.
  • A CLI registry owns mapping generic CLI input to provider-specific settings.
  • A separate embedding-provider registry owns embedding defaults and indexing UI behavior.

Proposed dependency shape:

flowchart LR
    Settings[Provider settings] --> Id[Provider identifier]
    Id --> Shared[Shared descriptor registry]
    Id --> Runtime[Extension runtime registry]
    Id --> Webview[Webview registry]
    Id --> CLI[CLI registry]
    Shared --> SharedCapabilities[Model ID and defaults]
    Runtime --> RuntimeCapabilities[Handler, model discovery, cache policy]
    Webview --> WebviewCapabilities[Validation and selected-model resolution]
    CLI --> CLICapabilities[CLI input mapping]
    SharedCapabilities --> Modules[Provider-owned behavior]
    RuntimeCapabilities --> Modules
    WebviewCapabilities --> Modules
    CLICapabilities --> Modules
Loading

There may still be one lookup per environment, but it should be data-driven, exhaustive for the providers supported by that environment, and dependent on narrow capabilities. Provider modules should receive only the settings valid for that provider rather than the complete provider-settings union.

The current API-handler abstraction should remain responsible for inference operations such as creating messages, reporting model information, and counting tokens. The proposed runtime capability complements it by owning handler creation and surrounding provider integration policies. The existing public handler factory can remain as a compatibility facade while its dispatch moves behind registrations.

Validation capabilities should return stable domain outcomes rather than translated text. The webview can translate those outcomes, keeping shared provider behavior independent of UI and localization frameworks.

Chat/API providers and embedding providers should remain separate domains. Although some provider names overlap, their settings, model metadata, and validation behavior differ; combining them would create a broad interface dominated by optional capabilities.

Constraints / preferences (optional)

  • Treat this as an architectural discussion proposal before beginning a repository-wide migration.
  • Prefer composition and focused capabilities over one large inheritance hierarchy.
  • Keep shared descriptors independent of extension-runtime, React, localization, and validation-framework dependencies.
  • Preserve the canonical identifier registry introduced by [ENHANCEMENT] Centralize provider identifiers and replace hardcoded provider-name literals #944 as the source of provider identities; behavior registries complement it rather than replace it.
  • Preserve serialized provider values and compatibility with existing settings, profiles, imports, and extension state.
  • Preserve current behavior throughout incremental migration.
  • Only providers supporting dynamic model discovery should expose that capability; avoid required no-op implementations.
  • Keep model-fetch behavior and its cache discriminator together so key and base-URL isolation rules cannot drift.
  • Migrate selected-model behavior provider by provider because it contains the largest amount of policy and has the highest regression risk.
  • Keep API/chat and embedding provider registries separate.
  • Add regression coverage at the narrowest test layer appropriate for each migrated responsibility.

Request checklist

  • I have searched existing Issues and Discussions for duplicates
  • This describes a specific problem with clear context and impact

Acceptance criteria (optional)

  • A documented provider capability model and environment boundaries are agreed upon before broad implementation.
  • Shared provider descriptors resolve configured and default model IDs without requiring consumers to maintain provider switches.
  • Runtime handler construction is registered per provider while the existing public factory remains compatible.
  • Dynamic model discovery and cache-isolation policy can be registered together for providers that support discovery.
  • Webview settings validation and selected-model resolution can be moved behind provider-owned UI capabilities without introducing UI dependencies into shared packages.
  • CLI provider input mapping reuses provider-owned metadata or capabilities instead of maintaining a parallel field mapping.
  • Chat/API provider behavior and embedding provider behavior remain separate.
  • Adding a representative provider requires registering its capabilities rather than editing each migrated central switch.
  • Existing serialized settings and provider behavior remain compatible.
  • Focused regression tests cover each responsibility as it is migrated.

Proposed approach (optional)

Use an incremental migration rather than replacing all branches at once:

  1. Introduce the shared provider descriptor contract.
  2. Consolidate duplicated configured-model resolution from profile and CLI logic.
  3. Register runtime handler factories while retaining the existing public factory facade.
  4. Attach dynamic model discovery and cache-isolation policy to runtime registrations.
  5. Extract webview validation capabilities while keeping translation in the UI layer.
  6. Migrate selected-model resolution provider by provider.
  7. Move CLI settings mapping behind provider-owned capabilities or declarative metadata.
  8. Introduce a separate embedding-provider registry for embedding defaults and indexing validation.

Trade-offs / risks (optional)

  • A broad provider interface could become optional-heavy and reproduce the same coupling in another form; capabilities should remain small and environment-specific.
  • Registries introduce indirection and require clear ownership and exhaustiveness tests.
  • Moving high-policy model-selection behavior has significant regression risk and should happen incrementally.
  • Shared packages cannot import runtime or webview implementations without creating dependency-direction or circular-dependency problems.
  • Some provider-specific behavior may be better represented as shared protocol utilities rather than registry methods; work such as [ENHANCEMENT] Extract shared OpenAI-compatible chunk processing from RouterProvider subclasses #338 should remain complementary.
  • During migration, registry-based and switch-based paths may temporarily coexist.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions