# AI Provider Integration Architecture in Secure-Design: Unifying OpenAI, Anthropic, OpenRouter, and Local LLMs

> Explore the AI provider integration architecture in Secure-Design, unifying OpenAI, Anthropic, OpenRouter, and local LLMs via an abstract AIProvider interface for seamless model discovery and execution.

- Repository: [Harold Martin/secure-design](https://github.com/hbmartin/secure-design)
- Tags: architecture
- Published: 2026-03-03

---

**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`](https://github.com/hbmartin/secure-design/blob/main/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.

```typescript
// 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`](https://github.com/hbmartin/secure-design/blob/main/src/providers/types.ts). This base class enforces a consistent contract through three critical abstract members:

- **`models`**: An array of `ModelConfig` objects describing available models, token limits, and default flags
- **`createInstance`**: Factory method returning a Vercel AI SDK `LanguageModelV2` configured for the specific provider
- **`validateCredentials`**: Security check ensuring required API keys or base URLs are present before instantiation

```typescript
// 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.

```typescript
// 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:

```typescript
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.

```typescript
// 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.

```typescript
// 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.

```typescript
// 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:

```typescript
// 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:

```typescript
// src/di/ServiceContainer.ts
import { MyLocalProvider } from '../providers/myLocalProvider';

const providerRegistry = new InMemoryProviderRegistry();
providerRegistry.register(new MyLocalProvider());

```

## Summary

- **Provider-Registry Pattern**: The `IProviderRegistry` interface in [`src/providers/types.ts`](https://github.com/hbmartin/secure-design/blob/main/src/providers/types.ts) maintains a type-safe map of all available AI providers, enabling runtime lookup and enumeration.
- **Abstract Base Class**: The `AIProvider` abstract class standardizes model configuration, instantiation via `createInstance`, and credential validation across OpenAI, Anthropic, OpenRouter, and local providers.
- **Secure Storage**: The `SecureStorageService` wraps VS Code's secret storage to handle API keys securely, separating credential management from provider logic.
- **SDK Abstraction**: The Vercel AI SDK's `LanguageModelV2` interface provides a unified execution layer where `streamText` and 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`](https://github.com/hbmartin/secure-design/blob/main/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`](https://github.com/hbmartin/secure-design/blob/main/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`](https://github.com/hbmartin/secure-design/blob/main/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`](https://github.com/hbmartin/secure-design/blob/main/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.