# Understanding Magnitude's Provider-Agnostic Contract in the AI Package

> Explore Magnitude's provider-agnostic contract in the ai package. Interact with any LLM backend uniformly and type-safely using interfaces like Provider and ModelCatalog.

- Repository: [Magnitude/magnitude](https://github.com/magnitudedev/magnitude)
- Tags: internals
- Published: 2026-09-06

---

**The `ai` package in `magnitudedev/magnitude` defines a four-interface contract—`Provider`, `ModelCatalog`, `BoundModel`, and `BaseCallOptions`—that enables uniform, type-safe interaction with any LLM backend without hard-coding provider-specific logic.**

Magnitude's provider-agnostic contract lives in [`packages/ai/src/provider/contract.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/ai/src/provider/contract.ts) and serves as the architectural foundation for the entire system. This contract allows the SDK, CLI, and UI components to work with OpenAI, Anthropic, or any future provider through a single, consistent abstraction. By purely declaring interfaces rather than implementing network logic, the `ai` package ensures that adding a new AI backend requires only implementing four TypeScript interfaces—no changes needed elsewhere in the codebase.

## The Four Core Interfaces

The contract revolves around four generic interfaces that together cover provider definition, model discovery, runtime binding, and call configuration.

### The `Provider<TProvider, TModel, TOptions>` Interface

The **Provider** interface describes a concrete AI provider implementation. It is generic over three types: the underlying SDK client, the model specification type, and the provider-specific call options.

```typescript
interface Provider<TProvider, TModel, TOptions> {
  name: string;                    // Human-readable provider name
  client: TProvider;               // Low-level SDK instance
  catalog: ModelCatalog<TModel>;   // Available models for this provider
  defaultOptions?: TOptions;       // Default call options
}

```

This design lets each provider expose its own typed client while remaining interchangeable. The `name` property enables registry lookups, while `client` holds the actual SDK instance used for network calls.

### The `ModelCatalog<TModel>` Interface

The **ModelCatalog** interface provides read-only access to a provider's available models. It powers discovery in UI dropdowns and validation in the SDK.

```typescript
interface ModelCatalog<TModel> {
  list(): readonly TModel[];           // All model specifications
  get(id: string): TModel | undefined; // Lookup by identifier
}

```

Implementations typically store models in a Map or Array and expose these two methods. The interface is intentionally minimal to keep provider implementations lightweight while supporting enumeration and lookup use cases.

### The `BoundModel<TModel, TOptions>` Interface

The **BoundModel** interface represents a model that is already paired with execution options, ready for invocation.

```typescript
interface BoundModel<TModel, TOptions> {
  model: TModel;      // The model specification
  options: TOptions;  // Call options bound to this model
}

```

Separation of concerns is key here: `ModelCatalog` answers "what models exist?" while `BoundModel` answers "which model with what settings should I call?" This allows the SDK to construct requests without callers needing provider-specific knowledge.

### The `BaseCallOptions` Interface

The **BaseCallOptions** interface defines parameters common to all provider calls. Specific providers extend this to add their own flags.

```typescript
interface BaseCallOptions {
  maxTokens?: number;
  temperature?: number;
  stop?: string[];
  logprobs?: number;
}

```

Common extensions include `top_p`, `frequency_penalty`, and `presence_penalty` for OpenAI-compatible providers, or `anthropic_version` for Anthropic's API.

## How the Contract Enables Provider Agnosticism

The system uses this contract through three coordinated mechanisms.

### Model Discovery Through `ModelCatalog`

UI components in `client-common` enumerate available models via `catalog.list()`. This populates model selection dropdowns without hard-coding provider knowledge.

```typescript
import { sdk } from '@magnitudedev/sdk';

// Retrieve all models from the currently registered provider
const models = sdk.provider.catalog.list();
models.forEach((m) => console.log(`${m.id} – context: ${m.contextLength}`));

```

### Runtime Binding with `BoundModel`

The SDK constructs provider calls using a `BoundModel`, combining model identity with execution parameters.

```typescript
import { sdk } from '@magnitudedev/sdk';
import { openAIProvider, gpt4 } from './openai-provider';

// Register once per application
sdk.registerProvider(openAIProvider);

// Call using bound model options
const response = await sdk.chat.completions.create({
  provider: openAIProvider.name,
  model: gpt4.model.id,
  messages: [{ role: 'user', content: 'Hello, world!' }],
  ...gpt4.options,  // Spread bound options (temperature, maxTokens, etc.)
});

```

### Error Handling Preserves Abstraction

Errors are wrapped in `ProviderCall` and `ProviderErrorEnvelope` types defined in [`packages/ai/src/errors/failure.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/ai/src/errors/failure.ts). This ensures that failure modes remain provider-agnostic at the contract level, even when underlying SDKs expose different error shapes.

## Implementing a Custom Provider

Any provider can be added by implementing the four interfaces and registering with [`packages/providers/src/registry.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/providers/src/registry.ts).

```typescript
import { Provider, ModelCatalog, BoundModel, BaseCallOptions } from '@magnitudedev/ai';
import { OpenAIClient } from 'openai';

// 1. Define model specification type
interface OpenAIModel {
  id: string;
  contextLength: number;
  supportsVision: boolean;
}

// 2. Implement catalog
const openAICatalog: ModelCatalog<OpenAIModel> = {
  list: () => [
    { id: 'gpt-4', contextLength: 8192, supportsVision: false },
    { id: 'gpt-4-vision', contextLength: 128000, supportsVision: true },
  ],
  get: (id) => openAICatalog.list().find(m => m.id === id),
};

// 3. Extend base options
interface OpenAIOptions extends BaseCallOptions {
  top_p?: number;
  frequencyPenalty?: number;
}

// 4. Assemble provider
export const openAIProvider: Provider<OpenAIClient, OpenAIModel, OpenAIOptions> = {
  name: 'openai',
  client: new OpenAIClient({ apiKey: process.env.OPENAI_API_KEY! }),
  catalog: openAICatalog,
  defaultOptions: { temperature: 0.7, maxTokens: 1024 },
};

// 5. Create bound model ready for use
export const gpt4: BoundModel<OpenAIModel, OpenAIOptions> = {
  model: openAICatalog.get('gpt-4')!,
  options: { temperature: 0.5, top_p: 0.95 },
};

```

The rest of Magnitude—agents, SDK methods, and UI components—operates unchanged because it programs against the abstract interfaces, not concrete implementations.

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`packages/ai/src/provider/contract.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/ai/src/provider/contract.ts) | Core interface definitions (`Provider`, `ModelCatalog`, `BoundModel`, `BaseCallOptions`) |
| [`packages/ai/src/provider/catalog.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/ai/src/provider/catalog.ts) | Default `ModelCatalog` implementation used by providers |
| [`packages/ai/src/provider/model.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/ai/src/provider/model.ts) | Model definition helpers and utilities |
| [`packages/ai/src/errors/failure.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/ai/src/errors/failure.ts) | `ProviderCall` and `ProviderErrorEnvelope` for uniform error handling |
| [`packages/providers/src/registry.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/providers/src/registry.ts) | Central registry for provider implementations |
| [`packages/sdk/src/index.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/sdk/src/index.ts) | SDK entry point consuming the provider contract |

## Summary

- The **provider-agnostic contract** in [`packages/ai/src/provider/contract.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/ai/src/provider/contract.ts) enables Magnitude to work with any LLM backend through four generic TypeScript interfaces.
- **`Provider`** encapsulates the SDK client, model catalog, and default options for a concrete AI service.
- **`ModelCatalog`** provides read-only model discovery through `list()` and `get()` methods.
- **`BoundModel`** pairs a model specification with call options, ready for execution.
- **`BaseCallOptions`** defines universal parameters that all providers support, with extension points for provider-specific flags.
- Error types in [`packages/ai/src/errors/failure.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/ai/src/errors/failure.ts) preserve abstraction across different backend failure modes.
- New providers plug into [`packages/providers/src/registry.ts`](https://github.com/magnitudedev/magnitude/blob/main/packages/providers/src/registry.ts) without modifying downstream code.

## Frequently Asked Questions

### What makes Magnitude's AI contract "provider-agnostic"?

The contract defines only TypeScript interfaces—no concrete network logic. Any provider implements the same four interfaces (`Provider`, `ModelCatalog`, `BoundModel`, `BaseCallOptions`) with its own SDK client and model types. The rest of Magnitude imports only the interface types, so swapping providers requires zero changes to agents, UI components, or SDK consumers.

### How does `ModelCatalog` support dynamic model discovery?

The `list()` method returns all available models for a provider, enabling runtime enumeration. UI components call this to populate dropdown menus without hard-coding model lists. The `get(id)` method enables validation and lookup by identifier, ensuring only supported models are requested.

### Can I extend `BaseCallOptions` with provider-specific parameters?

Yes. Each provider defines its own options interface extending `BaseCallOptions`. For example, OpenAI might add `top_p` and `frequencyPenalty`, while Anthropic could add `anthropic_version`. The generic `TOptions` type parameter on `Provider` and `BoundModel` preserves full type safety through the call chain.