ArchitectureAdapter Pattern
Adapter Pattern
How the toolkit isolates third-party library changes
Why Adapters?
AI libraries change rapidly. LangChain, Vercel AI SDK, and others release breaking changes regularly. The adapter pattern isolates your application from these changes.
How It Works
Each module has three layers:
types.ts → Public interface (stable)
[module].ts → Adapter logic (translates between interface and library)
adapters/ → Provider-specific implementations
Example: AI Module
Public interface (types.ts):
interface AIClient {
generate(prompt: string, options?: GenerateOptions): Promise<GenerateResult>;
stream(prompt: string, options?: StreamOptions): Promise<StreamResult>;
structured<T>(prompt: string, options: StructuredOptions<T>): Promise<StructuredResult<T>>;
}
Adapter (ai-client.ts):
export function createAI(config?: AIConfig): AIClient {
// 1. Detect provider from env vars
// 2. Load provider SDK dynamically
// 3. Return AIClient that delegates to Vercel AI SDK
return {
generate: (prompt, options) => {
// Calls: import('ai').then(m => m.generateText({...}))
// Translates result to GenerateResult
},
// ...
};
}
Rules
- Never expose raw library types — always translate to toolkit types
- Dynamic imports — peer deps are loaded at runtime, not compile time
- Graceful errors — missing peer dep → clear error message with install command
- Input validation — Zod or type guards before calling the library
Adding a New Provider
To add a new AI provider:
- Add the provider type to
AIProvider - Add detection logic (env var check)
- Add model loading in
provider.ts - Add default model mapping
- Tests pass without any external API calls