[FEATURE]: Add provider.list plugin hook for modifying providers #6712

Closed
opened 2026-02-16 18:05:01 -05:00 by yindo · 5 comments
Owner

Originally created by @zortos293 on GitHub (Jan 18, 2026).

Originally assigned to: @thdxr on GitHub.

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Add a new provider.list hook to the plugin system that allows plugins to modify the provider list after it's loaded. This enables plugins to add virtual providers, modify existing ones, or implement features like provider fallback/aggregation.

Currently, there's no way for plugins to interact with the provider system. This limits what plugins can do - for example, implementing features like:

  • Combined/fallback models that aggregate the same model from multiple providers
  • Custom virtual providers
  • Provider filtering or modification based on user preferences

Add a provider.list hook to the Hooks interface in @opencode-ai/plugin:

export type ProviderWithModels = {
  id: string
  name: string
  source: "env" | "config" | "custom" | "api"
  env: string[]
  options: Record<string, any>
  models: Record<string, Model>
}

// In Hooks interface:
"provider.list"?: (
  input: { config: Config },
  output: { providers: Record<string, ProviderWithModels> },
) => Promise<void>

The hook is called after all providers are loaded, and plugins can mutate the output.providers object to add or modify providers.

This would enable implementing the model provider fallback feature requested in #8983 as a plugin rather than core functionality.

Originally created by @zortos293 on GitHub (Jan 18, 2026). Originally assigned to: @thdxr on GitHub. ### Feature hasn't been suggested before. - [x] I have verified this feature I'm about to request hasn't been suggested before. ### Describe the enhancement you want to request Add a new `provider.list` hook to the plugin system that allows plugins to modify the provider list after it's loaded. This enables plugins to add virtual providers, modify existing ones, or implement features like provider fallback/aggregation. Currently, there's no way for plugins to interact with the provider system. This limits what plugins can do - for example, implementing features like: - Combined/fallback models that aggregate the same model from multiple providers - Custom virtual providers - Provider filtering or modification based on user preferences Add a `provider.list` hook to the `Hooks` interface in `@opencode-ai/plugin`: ```ts export type ProviderWithModels = { id: string name: string source: "env" | "config" | "custom" | "api" env: string[] options: Record<string, any> models: Record<string, Model> } // In Hooks interface: "provider.list"?: ( input: { config: Config }, output: { providers: Record<string, ProviderWithModels> }, ) => Promise<void> ``` The hook is called after all providers are loaded, and plugins can mutate the `output.providers` object to add or modify providers. This would enable implementing the model provider fallback feature requested in #8983 as a plugin rather than core functionality.
yindo added the discussion label 2026-02-16 18:05:01 -05:00
yindo closed this issue 2026-02-16 18:05:01 -05:00
Author
Owner

@github-actions[bot] commented on GitHub (Jan 18, 2026):

This issue might be a duplicate of existing issues. Please check:

  • #8983: Is there any way to combine 1 model from different providers into one? (mentioned in your issue as the use case for provider fallback)
  • #7602: [FEATURE]: Native Model Fallback / Failover Support (related to the fallback/aggregation feature you want to enable)
  • #6217: Have multiple instances of the same provider (related provider system extensibility)

Feel free to ignore if none of these address your specific case.

@github-actions[bot] commented on GitHub (Jan 18, 2026): This issue might be a duplicate of existing issues. Please check: - #8983: Is there any way to combine 1 model from different providers into one? (mentioned in your issue as the use case for provider fallback) - #7602: [FEATURE]: Native Model Fallback / Failover Support (related to the fallback/aggregation feature you want to enable) - #6217: Have multiple instances of the same provider (related provider system extensibility) Feel free to ignore if none of these address your specific case.
Author
Owner

@zortos293 commented on GitHub (Jan 18, 2026):

I've implemented a working proof-of-concept for this hook along with a plugin that uses it to solve #8983, #7602, and partially #6217.

Implementation Details

The hook itself (packages/plugin/src/index.ts):

export type ProviderWithModels = {
  id: string
  name: string
  source: "env" | "config" | "custom" | "api"
  env: string[]
  options: Record<string, any>
  models: Record<string, Model>
}

"provider.list"?: (
  input: { config: Config },
  output: { providers: Record<string, ProviderWithModels> },
) => Promise<void>

Example plugin using this hook - automatically creates "combined" models:

export const CombinedModelsPlugin: Plugin = async () => ({
  "provider.list": async (input, output) => {
    const providers = output.providers
    
    // Group models by normalized name across providers
    const modelsByName: Record<string, { providerID: string; model: Model }[]> = {}
    
    for (const [providerID, provider] of Object.entries(providers)) {
      for (const [modelID, model] of Object.entries(provider.models)) {
        const normalized = normalizeModelName(modelID)
        modelsByName[normalized] ??= []
        modelsByName[normalized].push({ providerID, model })
      }
    }
    
    // Create combined models for those available from 2+ providers
    const combinedModels: Record<string, Model> = {}
    
    for (const [name, providerModels] of Object.entries(modelsByName)) {
      if (providerModels.length < 2) continue
      
      combinedModels[name] = {
        ...providerModels[0].model,
        id: name,
        providerID: "combined",
        name: `${providerModels[0].model.name} (${providerModels.length} providers)`,
        options: {
          combined: {
            models: providerModels.map(p => `${p.providerID}/${p.model.id}`),
            strategy: "on_error",
            max_attempts: 3,
          },
        },
      }
    }
    
    if (Object.keys(combinedModels).length > 0) {
      providers["combined"] = {
        id: "combined",
        name: "Combined Models",
        source: "config",
        env: [],
        options: {},
        models: combinedModels,
      }
    }
  },
})

This approach keeps the core provider system clean while enabling powerful extensions. The hook is called at the end of provider initialization in provider.ts with a single line:

await Plugin.trigger("provider.list", { config }, { providers })

I have a branch with the full implementation ready if this direction is approved. Happy to open a PR.

@zortos293 commented on GitHub (Jan 18, 2026): I've implemented a working proof-of-concept for this hook along with a plugin that uses it to solve #8983, #7602, and partially #6217. ### Implementation Details **The hook itself** (`packages/plugin/src/index.ts`): ```typescript export type ProviderWithModels = { id: string name: string source: "env" | "config" | "custom" | "api" env: string[] options: Record<string, any> models: Record<string, Model> } "provider.list"?: ( input: { config: Config }, output: { providers: Record<string, ProviderWithModels> }, ) => Promise<void> ``` **Example plugin using this hook** - automatically creates "combined" models: ```typescript export const CombinedModelsPlugin: Plugin = async () => ({ "provider.list": async (input, output) => { const providers = output.providers // Group models by normalized name across providers const modelsByName: Record<string, { providerID: string; model: Model }[]> = {} for (const [providerID, provider] of Object.entries(providers)) { for (const [modelID, model] of Object.entries(provider.models)) { const normalized = normalizeModelName(modelID) modelsByName[normalized] ??= [] modelsByName[normalized].push({ providerID, model }) } } // Create combined models for those available from 2+ providers const combinedModels: Record<string, Model> = {} for (const [name, providerModels] of Object.entries(modelsByName)) { if (providerModels.length < 2) continue combinedModels[name] = { ...providerModels[0].model, id: name, providerID: "combined", name: `${providerModels[0].model.name} (${providerModels.length} providers)`, options: { combined: { models: providerModels.map(p => `${p.providerID}/${p.model.id}`), strategy: "on_error", max_attempts: 3, }, }, } } if (Object.keys(combinedModels).length > 0) { providers["combined"] = { id: "combined", name: "Combined Models", source: "config", env: [], options: {}, models: combinedModels, } } }, }) ``` This approach keeps the core provider system clean while enabling powerful extensions. The hook is called at the end of provider initialization in `provider.ts` with a single line: ```typescript await Plugin.trigger("provider.list", { config }, { providers }) ``` I have a branch with the full implementation ready if this direction is approved. Happy to open a PR.
Author
Owner

@zortos293 commented on GitHub (Jan 18, 2026):

https://github.com/zortos293/opencode-combined-models made an quick plugin with the hook

@zortos293 commented on GitHub (Jan 18, 2026): https://github.com/zortos293/opencode-combined-models made an quick plugin with the hook
Author
Owner

@zortos293 commented on GitHub (Jan 18, 2026):

Image
@zortos293 commented on GitHub (Jan 18, 2026): <img width="416" height="133" alt="Image" src="https://github.com/user-attachments/assets/8240ccef-3e68-453c-b920-dcaa8127d6d0" />
Author
Owner

@zortos293 commented on GitHub (Jan 18, 2026):

This wouldn't work there needs to be a lot of stuff modified to make this work

@zortos293 commented on GitHub (Jan 18, 2026): This wouldn't work there needs to be a lot of stuff modified to make this work
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#6712