Understanding Magnitude's Provider-Agnostic Contract in the AI Package
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 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.
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.
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.
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.
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.
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.
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. 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.
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 |
Core interface definitions (Provider, ModelCatalog, BoundModel, BaseCallOptions) |
packages/ai/src/provider/catalog.ts |
Default ModelCatalog implementation used by providers |
packages/ai/src/provider/model.ts |
Model definition helpers and utilities |
packages/ai/src/errors/failure.ts |
ProviderCall and ProviderErrorEnvelope for uniform error handling |
packages/providers/src/registry.ts |
Central registry for provider implementations |
packages/sdk/src/index.ts |
SDK entry point consuming the provider contract |
Summary
- The provider-agnostic contract in
packages/ai/src/provider/contract.tsenables Magnitude to work with any LLM backend through four generic TypeScript interfaces. Providerencapsulates the SDK client, model catalog, and default options for a concrete AI service.ModelCatalogprovides read-only model discovery throughlist()andget()methods.BoundModelpairs a model specification with call options, ready for execution.BaseCallOptionsdefines universal parameters that all providers support, with extension points for provider-specific flags.- Error types in
packages/ai/src/errors/failure.tspreserve abstraction across different backend failure modes. - New providers plug into
packages/providers/src/registry.tswithout 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →