# Provider Plugin Architecture

The provider plugin system is the integration layer between the playground UI and the model-specific runtimes. It lets us add, swap, and configure AI backends without touching the rest of the application. Each provider ships as a self-contained plugin that declares its capabilities (streaming, tools, approvals, model settings), exposes a uniform API for running chats, and optionally contributes configuration and attachment tooling.

## How the pieces fit together

1. **Plugin registration** – A `ProviderPlugin` subclass (for example, `OpenAIProvider` or `OllamaProvider`) is passed to `registerProviderPlugin`. Registration happens during application boot and any time the provider list is refreshed.
2. **Provider creation** – `ProviderPlugin.createProvider()` returns a `ChatProvider` object. This object is what the runtime uses at execution time.
3. **Runtime wiring** – The registry (`src/lib/providers/registry.ts`) stores providers, stream handlers, model definitions, and attachment tool definitions. Context providers and hooks pull from this registry when constructing chat agents and UI state.
4. **Chat execution** – When the UI sends a message, the active provider’s `startRun` (or `resumeRun`) method is invoked. The provider drives streaming via callbacks and resolves with a `ProviderRunHandle` that exposes cancellation and final results.

Working backwards from the plugin class ensures every provider opts in consciously to the capabilities it supports, while sharing a common execution surface for the rest of the app.

## The `ProviderPlugin` contract

All providers extend the abstract `ProviderPlugin` (`src/lib/providers/types.ts`). The class defines the minimal surface a plugin may implement:

```ts
export abstract class ProviderPlugin {
  abstract readonly id: ProviderId
  abstract createProvider(): ChatProvider | Promise<ChatProvider>

  getModelDefinition?(): ModelProviderDefinition | Promise<ModelProviderDefinition | undefined>
  getStreamHandler?(): ProviderStreamHandler | Promise<ProviderStreamHandler | undefined>
  configureAttachmentTools?(resolve: () => AttachmentToolDefinition[]): void | Promise<void>
  onRegistered?(runtime: ProviderRuntime): void | Promise<void>
}
```

- **`id`** – Stable identifier used everywhere (UI badges, registry lookups, persisted models). The registry enforces that the provider’s `metadata.kind` and any model definition IDs match this value.
- **`createProvider()`** – Must return the concrete `ChatProvider`. This is the only required method besides `id`.
- **`getModelDefinition()`** *(optional)* – Allows a plugin to surface UI metadata and forms for model selection. The definition object hooks into the model manager and settings UI.
- **`getStreamHandler()`** *(optional)* – Provides a streaming decoder (`ProviderStreamHandler`) to normalize vendor-specific streaming payloads. The runtime will call this handler when it needs to process raw responses outside of a `startRun` call (for example, in saved conversations or when resuming runs with an external payload).
- **`configureAttachmentTools()`** *(optional)* – Grants access to the shared attachment tool definitions resolver. Used to integrate the built-in file attachment tools with vendor-specific implementations.
- **`onRegistered()`** *(optional)* – Runs after the plugin has been registered. The hook receives the `ProviderRuntime` (access to the registry and attachment definitions) so that plugins can perform cross-provider coordination.

### Registration flow

`registerProviderPlugin(plugin)` (see `src/lib/providers/index.ts`) carries out these steps:

1. Validate `plugin.id` and skip registration if it was already added.
2. Lazily initialize attachment tool definitions.
3. Call `configureAttachmentTools` so the plugin can augment tool definitions before they are used elsewhere.
4. Await `createProvider()`, validate the returned `ChatProvider`, and register it.
5. Optionally register the model definition and stream handler.
6. Invoke `onRegistered()` with a runtime helper.

Because registration is idempotent and tracked in-memory, the same plugin can be passed multiple times without duplication (useful for refresh flows).

## The `ChatProvider` interface

`ChatProvider` is the runtime-facing interface produced by `createProvider()`:

```ts
export interface ChatProvider {
  metadata: ProviderMetadata
  createAgent(options: UseChatAgentOptions): ProviderAgent | Promise<ProviderAgent>
  startRun(agent: ProviderAgent, input: ProviderRunInput, callbacks: ProviderRunCallbacks): Promise<ProviderRunHandle>
  resumeRun?(agent: ProviderAgent, input: ProviderResumeInput, callbacks: ProviderRunCallbacks): Promise<ProviderRunHandle>
  deserializeRunState?(agent: ProviderAgent, serialized: string): Promise<ProviderRunState>
}
```

- **`metadata`** – Declares the provider name, feature flags (streaming, tools, approvals), and exposes a `kind` that must match the plugin ID.
- **`createAgent`** – Instantiates a long-lived agent object configured with model credentials, fine-tuning, or provider-specific options. The UI calls this once per session.
- **`startRun`** – Starts a chat exchange. The provider should translate `ProviderRunInput` into whatever API request it needs, stream deltas via the `ProviderRunCallbacks`, and return a `ProviderRunHandle` that exposes `cancel()` and a `completed` promise.
- **`resumeRun`** *(optional)* – Restarts a chat using a previously persisted state object. Providers implement this to support human-in-the-loop approvals or recovering from network issues.
- **`deserializeRunState`** *(optional)* – Converts a serialized run state string back into the provider’s native state object. Useful when rehydrating state from storage before calling `resumeRun`.

### Run input, callbacks, and results

- **`ProviderRunInput`** – Contains the thread ID, message list, optional attachments, and an abort controller pair.
- **`ProviderRunCallbacks`** – The streaming hooks a provider should invoke (`onOutputTextDelta`, `onMessageOutputCreated`, `onToolCall`, `onToolOutput`, etc.). The app uses these to update the conversation UI, tool call panels, and reasoning traces in real time.
- **`ProviderRunHandle`** – Returned from `startRun`/`resumeRun`; drives cancellation and resolves with a `ProviderRunResult` containing the final output, interruptions requiring approval, serialized state, and the raw provider response.

Providers can choose any internal representation as long as these public contracts are satisfied. For example:

- **OpenAI provider** (`src/providers/openai/index.ts`) instantiates `OpenAIChatAgent`, forwards streaming tokens to the callbacks, and surfaces the OpenAI `RunState` as the provider run state.
- **Ollama provider** (`src/providers/ollama/index.ts`) manages MCP tool permission interruptions, converts streaming SSE events to deltas, and returns an `OllamaRunState` for resumption.

## Runtime registry and accessors

Behind the scenes, `ProviderRegistry` (`src/lib/providers/registry.ts`) keeps track of everything the UI needs:

- **Providers** – Mapped by `ProviderId`. `getRegisteredProvider(kind)` throws if the provider is missing, while `getOptionalRegisteredProvider` returns `undefined` so plugins can probe for optional peers.
- **Model definitions** – Used by the Model Manager to render settings forms. Each definition’s `id` must match the provider ID.
- **Stream handlers** – Optional decoders registered per provider for normalizing streaming responses.
- **Attachment tool definitions** – A lazily provided list that plugins can extend. The registry exposes both `setAttachmentToolDefinitionsResolver` and `getAttachmentToolDefinitions`.

The top-level playground component (`src/index.tsx`) resolves the list of plugins (either statically or from user-provided configuration), calls `registerProviderPlugin` for each, and exposes the resulting providers through context so components like `ModelManager` and `ChatDialog` can access the active agent.

## Implementing a new provider

1. **Create the plugin class**

    ```ts
    export class MyProvider extends ProviderPlugin {
      readonly id = 'my-provider'

      async createProvider(): Promise<ChatProvider> {
        return {
          metadata: {
            kind: this.id,
            name: 'My Provider',
            supportsTools: false,
            supportsApprovals: false,
            supportsStreaming: true,
            isImplemented: true
          },
          createAgent: (options) => new MyAgent(options),
          startRun: async (agent, input, callbacks) => {
            const controller = input.abortController ?? new AbortController()
            const signal = input.signal ?? controller.signal

            const runPromise = runMyApi({ agent, input, callbacks, signal })

            return {
              cancel: async () => {
                if (!signal.aborted) controller.abort()
                await runPromise.catch(() => {})
              },
              completed: runPromise,
              signal
            }
          }
        }
      }
    }
    ```

1. **Optional extras**

    - Return a `ModelProviderDefinition` (view fields, settings form, defaults) from `getModelDefinition()`.
    - Register a custom `ProviderStreamHandler` if the provider exposes external streaming payloads the app needs to interpret.
    - Use `configureAttachmentTools()` if the provider relies on shared MCP tools or wants to expose additional attachment handlers.

1. **Register the plugin**

    - Ensure your plugin instance is passed to `registerProviderPlugin` during application startup or via the provider factory. Consumers embedding the playground can register their own providers by importing `{ registerProviderPlugin, ProviderPlugin }` from `model-context-playground` and calling the function before rendering the app.

## Provider lifecycle checklist

- [ ] Ensure `metadata.kind` matches `ProviderPlugin.id` across the provider, model definition, and stream handler registration.
- [ ] Respect the `ProviderRunCallbacks` contract—always emit deltas, tool calls, and final outputs where applicable.
- [ ] Surface interruptions via `ProviderRunResult.interruptions` when human approval is required; include a serializable `ProviderRunState` when the run can be resumed.
- [ ] Protect against double registration by letting `registerProviderPlugin` manage the `id` set.
- [ ] Use `ProviderRunInput.abortController`/`signal` instead of creating new controllers unless absolutely necessary. This keeps the UI cancel button responsive.
- [ ] Prefer returning structured errors so the calling UI can display meaningful messages.

## Further reading

- `src/lib/providers/types.ts` – Source of all provider-facing types.
- `src/lib/providers/index.ts` – Registration logic and exported helpers.
- `src/providers/openai/index.ts` & `src/providers/ollama/index.ts` – End-to-end reference implementations showing streaming, tool execution, and run state management.
- `src/index.tsx` – How the playground loads, registers, and reuses provider plugins at runtime.
