Skip to content

refactor: modular provider plugin architecture (upstream parity) #67

Description

@badchars

Problem

All 18 provider loaders live as inline async functions inside a single CUSTOM_LOADERS record in provider.ts (lines 84-595, ~511 lines). Adding or modifying a provider means editing this monolithic file, which:

  • Increases merge conflict risk
  • Makes individual providers hard to test in isolation
  • Inflates cognitive load when working on a single provider
  • Diverges from upstream's modular architecture

Current providers in CUSTOM_LOADERS

# Provider Lines Complexity
1 anthropic 85-131 High — OAuth, subscription, API key, beta headers
2 cyberstrike 132-153 Medium — auto-filter paid models, public fallback
3 openai 154-162 Low
4 github-copilot 163-172 Low — responses/chat routing
5 github-copilot-enterprise 173-182 Low
6 azure 183-195 Low — responses/chat routing
7 azure-cognitive-services 196-211 Low
8 amazon-bedrock 212-371 Very high — AWS credential chain, cross-region, profiles
9 openrouter 372-382 Low — headers only
10 vercel 383-393 Low — headers only
11 google-vertex 394-418 Medium — ADC, project/location resolution
12 google-vertex-anthropic 419-446 Medium — regional endpoints
13 sap-ai-core 447-470 Medium — AICORE_SERVICE_KEY parsing
14 zenmux 471-481 Low — headers only
15 gitlab 482-523 Medium — OAuth/API, feature flags
16 cloudflare-workers-ai 524-546 Medium — account ID resolution
17 cloudflare-ai-gateway 547-584 Medium — unified API format
18 cerebras 585-595 Low — header only

Proposal

Refactor to per-provider files under src/provider/loaders/, mirroring upstream's modular plugin approach:

src/provider/
├── provider.ts          # core loading logic, imports loaders
├── loaders/
│   ├── index.ts         # re-exports all loaders as CUSTOM_LOADERS record
│   ├── anthropic.ts
│   ├── amazon-bedrock.ts
│   ├── google-vertex.ts
│   ├── google-vertex-anthropic.ts
│   ├── gitlab.ts
│   ├── cloudflare-workers-ai.ts
│   ├── cloudflare-ai-gateway.ts
│   ├── sap-ai-core.ts
│   ├── openrouter.ts    # (could group simple header-only loaders)
│   └── ...

Each loader file exports a single async function matching the current CustomLoader type signature. The loaders/index.ts assembles them into the CUSTOM_LOADERS record — zero change to the consumer API.

Suggested phases

  1. Extract complex providers firstamazon-bedrock (160 lines), anthropic, google-vertex*, gitlab, cloudflare-*
  2. Group simple header-only providersopenrouter, vercel, zenmux, cerebras could share a utility
  3. Add per-provider tests — currently untestable in isolation
  4. Align type with upstream's PluginV2.define() if applicable

Upstream reference

Upstream (anomalyco/opencode) uses a granular plugin architecture at packages/core/src/plugin/provider/ with PluginV2.define(), typed event hooks (catalog.transform, aisdk.sdk, aisdk.language), and Effect-based composition.

We don't need to adopt Effect or the full plugin system — the goal is file-level separation while keeping the existing CustomLoader interface.

Non-goals

  • Changing the provider loading flow (6-step process stays)
  • Adopting Effect or upstream's full plugin system
  • Breaking the CustomLoader type contract

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions