AI Provider Integration Architecture in Secure-Design: Unifying OpenAI, Anthropic, OpenRouter, and Local LLMs
Secure-Design implements a provider-registry pattern combined with the Vercel AI SDK to make any LLM interchangeable through an abstract AIProvider interface that standardizes model discovery, credential validation, and runtime execution across cloud-hosted and local providers.
The Secure-Design VS Code extension abstracts away provider-specific complexity to deliver a unified chat-agent experience regardless of whether you're using OpenAI, Anthropic, OpenRouter, or local models via Ollama or LM Studio. This article examines the underlying AI provider integration architecture that enables seamless swapping between cloud APIs and self-hosted LLMs without changing agent logic.
Core Architectural Components
The architecture lives primarily in src/providers/types.ts and separates concerns into type-safe identification, metadata declaration, and runtime instantiation.
The Provider Registry Interface
At the heart of the system is the IProviderRegistry interface that maintains a lookup map of all available providers. This registry allows the extension to enumerate providers, retrieve specific implementations by ID, and aggregate model configurations across the entire ecosystem.
// src/providers/types.ts
export interface IProviderRegistry {
register(provider: AIProvider): void;
getProvider(id: ProviderId): AIProvider | undefined;
getAllProviders(): AIProvider[];
getAllModels(): ModelConfigWithProvider[];
}
The registry operates as a simple TypeScript map, making provider registration a matter of instantiating a concrete class and calling register().
Abstract AIProvider Class
Every concrete provider—whether for OpenAI, Anthropic, or Ollama—must extend the abstract AIProvider class defined in src/providers/types.ts. This base class enforces a consistent contract through three critical abstract members:
models: An array ofModelConfigobjects describing available models, token limits, and default flagscreateInstance: Factory method returning a Vercel AI SDKLanguageModelV2configured for the specific providervalidateCredentials: Security check ensuring required API keys or base URLs are present before instantiation
// src/providers/types.ts
export abstract class AIProvider {
static readonly metadata: ProviderMetadata;
abstract readonly models: ModelConfig[];
abstract createInstance(params: ProviderInstanceParams): LanguageModelV2;
abstract validateCredentials(config: ValidationParams): ValidationResult;
}
Type-Safe Provider Identification
The system uses a compile-time ProviderIdMap with support for declaration merging to ensure type safety when referencing providers. The ProviderId type is defined as a branded string, allowing extension authors to add new providers without modifying core types.
// src/providers/types.ts
export type ProviderId = keyof ProviderIdMap | Brand<string, 'ProviderId'>;
Each provider also exposes static metadata implementing ProviderMetadata, which declares human-readable names, VS Code configuration commands, and optional additional configuration keys:
export interface ProviderMetadata {
id: ProviderId;
name: string;
configureCommand: string;
additionalConfigKeys?: string[];
}
Runtime Provider Selection and Initialization
The extension decouples provider selection from execution through a dependency-injection container and secure storage abstractions.
Secure Credential Management
API keys and sensitive configuration are never stored in plain text. Instead, the SecureStorageService wraps VS Code’s native SecretStorage to provide encrypted persistence. This service implements the StorageAdapter interface and is consumed by the model picker when reading credentials.
// src/services/secureStorageService.ts
class SecureStorageService implements StorageAdapter {
// Wraps vscode.ExtensionContext.secrets
async get(key: string): Promise<string | undefined>;
async store(key: string, value: string): Promise<void>;
}
Dynamic Model Resolution
When a user triggers a chat interaction, the CustomAgentService requests a model instance through getSdkLanguageModel(storage). This utility reads the currently selected provider from VS Code settings, queries the provider registry, validates credentials via hasCredentials or validateCredentials, and returns a configured LanguageModelV2 instance.
// src/services/customAgentService.ts
const model: LanguageModelV2 = await getSdkLanguageModel(this.storage);
The getSdkLanguageModel function handles the branching logic to instantiate the correct SDK client—whether that's an OpenAI-compatible client for cloud APIs or a local HTTP endpoint configuration for Ollama and LM Studio.
Unified Execution Layer
Once instantiated, all providers expose the same Vercel AI SDK interface, enabling the chat agent to operate without provider-specific conditional logic.
Standardized Tool Calling
The CustomAgentService uses the streamText API from the Vercel AI SDK to handle streaming responses and tool invocations. Because every provider returns a LanguageModelV2 compliant instance, the same tool set (read, write, glob, etc.) passes unchanged regardless of the underlying LLM.
// src/services/customAgentService.ts
const result = streamText({
model, // Provider-agnostic LanguageModelV2
system,
messages,
tools, // Same tools for OpenAI, Anthropic, or Ollama
abortSignal
});
This unification means tool definitions, streaming handlers, and response parsers are written once and work across all supported providers.
Extending the Architecture: Adding a New Provider
Adding support for a new LLM provider requires only three steps: implementing the abstract class, declaring metadata, and registering the instance.
The following example demonstrates adding a custom local provider that communicates with an OpenAI-compatible endpoint:
// src/providers/myLocalProvider.ts
import { AIProvider, ProviderMetadata, ProviderInstanceParams, ModelConfig } from './types';
import { createOpenAI } from 'ai';
export const MyLocalMetadata: ProviderMetadata = {
id: 'myLocal' as any,
name: 'My Local LLM',
configureCommand: 'secureDesign.configureMyLocal',
additionalConfigKeys: ['myLocal.baseUrl'],
};
export class MyLocalProvider extends AIProvider {
static readonly metadata = MyLocalMetadata;
readonly models: ModelConfig[] = [
{
id: 'my-local-1',
displayName: 'MyLocal‑1',
isDefault: true,
maxTokens: 4096
}
];
createInstance(params: ProviderInstanceParams) {
const base = params.config.config.get<string>('myLocal.baseUrl')
?? 'http://localhost:11434/v1';
return createOpenAI({
baseURL: base,
apiKey: 'none'
});
}
validateCredentials(config) {
const url = config.config.get<string>('myLocal.baseUrl');
return url
? { isValid: true }
: { isValid: false, error: 'Base URL missing' };
}
}
Registration occurs in the dependency injection container:
// src/di/ServiceContainer.ts
import { MyLocalProvider } from '../providers/myLocalProvider';
const providerRegistry = new InMemoryProviderRegistry();
providerRegistry.register(new MyLocalProvider());
Summary
- Provider-Registry Pattern: The
IProviderRegistryinterface insrc/providers/types.tsmaintains a type-safe map of all available AI providers, enabling runtime lookup and enumeration. - Abstract Base Class: The
AIProviderabstract class standardizes model configuration, instantiation viacreateInstance, and credential validation across OpenAI, Anthropic, OpenRouter, and local providers. - Secure Storage: The
SecureStorageServicewraps VS Code's secret storage to handle API keys securely, separating credential management from provider logic. - SDK Abstraction: The Vercel AI SDK's
LanguageModelV2interface provides a unified execution layer wherestreamTextand tool calling work identically for cloud and local LLMs. - Extensibility: New providers require only a class extending
AIProvider, static metadata, and registry registration—no changes to the core chat agent logic.
Frequently Asked Questions
How does Secure-Design handle API key security across different providers?
The extension uses the SecureStorageService located in src/services/secureStorageService.ts, which implements the StorageAdapter interface to wrap VS Code's native SecretStorage. This ensures API keys for OpenAI, Anthropic, and other providers are encrypted at rest using the operating system's keychain or credential manager, not stored in plain text settings files.
Can I switch between cloud and local providers without restarting VS Code?
Yes. The getSdkLanguageModel function in src/services/customAgentService.ts reads the current provider selection from VS Code settings dynamically on each chat invocation. When you change the secureDesign.provider setting via the configuration UI, the next chat request instantiates the newly selected provider through the registry pattern without requiring an extension reload.
What makes a provider compatible with Secure-Design's tool calling?
Any provider returning a Vercel AI SDK LanguageModelV2 instance supports tool calling. The streamText function in src/services/customAgentService.ts passes the same tools array to all providers. Local providers like Ollama and LM Studio work because they expose OpenAI-compatible chat completion endpoints that the Vercel AI SDK can consume, allowing tool definitions to serialize correctly regardless of whether the underlying model is GPT-4, Claude, or Llama.
How do I add a provider that isn't listed in the default configuration?
Extend the AIProvider abstract class from src/providers/types.ts, implement the createInstance method to return a LanguageModelV2 (using the Vercel AI SDK's provider-specific constructors like createOpenAI or createAnthropic), and register your class with the IProviderRegistry. You must also declare ProviderMetadata with a unique ID and configuration command to integrate with the settings UI.
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 →