# How to Configure and Add Support for Custom AI Provider Endpoints in Secure-Design

> Learn to configure custom AI provider endpoints in Secure-Design. Implement AIProvider, register with IProviderRegistry, and store credentials securely. Enhance your AI integrations now.

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

---

**Developers can configure and add support for custom AI provider endpoints by implementing the `AIProvider` abstract class, registering the instance with the `IProviderRegistry` in the DI container, and exposing a VS Code configuration command that stores credentials in secure storage.**

Secure-Design provides an extensible provider registry that abstracts AI services behind a unified strategy-pattern interface. This architecture allows developers to configure and add support for custom AI provider endpoints—whether self-hosted LLMs, private OpenAI-compatible APIs, or any HTTP-based model—without modifying core chat or canvas logic.

## Step 1: Define a Provider Class

Create a new file in `src/providers/<your-provider>.ts` that extends the `AIProvider` base class defined in [`src/providers/types.ts`](https://github.com/hbmartin/secure-design/blob/main/src/providers/types.ts). You must implement three core methods and define a static `metadata` object that describes the provider to the UI.

The `metadata` object specifies the provider ID, display name, configuration command ID, and required configuration keys:

```typescript
static readonly metadata: ProviderMetadata = {
    id: 'mycustom',
    name: 'My Custom LLM',
    configureCommand: 'secureDesign.configureMyCustom',
    additionalConfigKeys: ['mycustom.baseUrl', 'mycustom.apiKey'],
    description: 'Connects to a self-hosted LLM that follows the OpenAI-compatible REST spec.',
    documentationUrl: 'https://example.com/docs/mycustom',
};

```

Implement the `models` property to expose available model IDs:

```typescript
readonly models: ModelConfig[] = [
    { id: 'gpt-4-custom', displayName: 'GPT-4 Custom', isDefault: true },
    { id: 'gpt-3.5-custom', displayName: 'GPT-3.5 Custom' },
];

```

The `createInstance()` method constructs the actual SDK client. Retrieve configuration values from VS Code settings and return a `LanguageModelV2` instance:

```typescript
createInstance(params: ProviderInstanceParams): LanguageModelV2 {
    const { model, config } = params;
    const baseUrl = config.config.get<string>('mycustom.baseUrl')!;
    const apiKey = config.config.get<string>('mycustom.apiKey')!;

    return getSdkLanguageModel({
        modelId: model,
        provider: {
            request: async (url, init) => {
                const response = await fetch(`${baseUrl}/${url}`, {
                    ...init,
                    headers: { ...init?.headers, Authorization: `Bearer ${apiKey}` },
                });
                return response.json();
            },
        },
    });
}

```

Finally, implement `validateCredentials()` to ensure required fields are present before the provider appears active:

```typescript
validateCredentials(config: VsCodeConfiguration) {
    const baseUrl = config.config.get<string>('mycustom.baseUrl');
    const apiKey = config.config.get<string>('mycustom.apiKey');
    const missing = [];
    if (!baseUrl) missing.push('baseUrl');
    if (!apiKey) missing.push('apiKey');
    return {
        isValid: missing.length === 0,
        error: missing.length ? `Missing ${missing.join(', ')}` : undefined,
    };
}

```

## Step 2: Register the Provider

In [`src/di/ServiceContainer.ts`](https://github.com/hbmartin/secure-design/blob/main/src/di/ServiceContainer.ts), import your provider class and register it with the `IProviderRegistry` singleton during initialization:

```typescript
// src/di/ServiceContainer.ts
import { MyCustomProvider } from '../providers/myCustomProvider';
import type { IProviderRegistry } from '../providers/types';

// Inside initialize()
const providerRegistry: IProviderRegistry = {
    _providers: new Map(),
    register(p) { 
        this._providers.set((p.constructor as typeof AIProvider).metadata.id, p); 
    },
    getProvider(id) { return this._providers.get(id); },
    getAllProviders() { return Array.from(this._providers.values()); },
    getAllModels() {
        const models: ModelConfigWithProvider[] = [];
        this.getAllProviders().forEach(p => {
            p.models.forEach(m => models.push({ 
                model: m, 
                provider: (p.constructor as typeof AIProvider).metadata 
            }));
        });
        return models;
    },
};

providerRegistry.register(new MyCustomProvider());
this.services.set('providerRegistry', providerRegistry);

```

Once registered, downstream services like `CustomAgentService` (defined in [`src/services/customAgentService.ts`](https://github.com/hbmartin/secure-design/blob/main/src/services/customAgentService.ts)) automatically discover your provider when resolving models for chat or design generation tasks.

## Step 3: Expose Configuration UI

Add a VS Code command to collect endpoint credentials and store them securely.

First, declare the command in [`package.json`](https://github.com/hbmartin/secure-design/blob/main/package.json):

```json
{
  "contributes": {
    "commands": [
      {
        "command": "secureDesign.configureMyCustom",
        "title": "Configure My Custom LLM"
      }
    ]
  }
}

```

Then implement the command handler, using `SecureStorageService` from [`src/services/secureStorageService.ts`](https://github.com/hbmartin/secure-design/blob/main/src/services/secureStorageService.ts) to encrypt sensitive data:

```typescript
// src/commands/configureMyCustom.ts
import * as vscode from 'vscode';
import { SecureStorageService } from '../services/secureStorageService';

export function registerMyCustomCommand(context: vscode.ExtensionContext) {
    const storage = new SecureStorageService(context.secrets);
    
    const cmd = vscode.commands.registerCommand('secureDesign.configureMyCustom', async () => {
        const baseUrl = await vscode.window.showInputBox({ 
            prompt: 'Base URL for My Custom LLM (e.g., https://api.example.com/v1)' 
        });
        const apiKey = await vscode.window.showInputBox({ 
            prompt: 'API Key (will be stored securely)', 
            password: true 
        });
        
        if (baseUrl && apiKey) {
            await storage.store('mycustom.baseUrl', baseUrl);
            await storage.store('mycustom.apiKey', apiKey);
            vscode.window.showInformationMessage('My Custom LLM configured successfully');
        }
    });
    
    context.subscriptions.push(cmd);
}

```

After configuration, the provider’s `validateCredentials()` method returns `true`, causing the model picker UI to display your custom models alongside built-in providers.

## Summary

- **Extend `AIProvider`**: Create a subclass in `src/providers/<name>.ts` with `metadata`, `models`, `createInstance()`, and `validateCredentials()`.
- **Register with DI**: Import and register the provider in [`src/di/ServiceContainer.ts`](https://github.com/hbmartin/secure-design/blob/main/src/di/ServiceContainer.ts) using the `IProviderRegistry` singleton.
- **Secure configuration**: Add a VS Code command in [`package.json`](https://github.com/hbmartin/secure-design/blob/main/package.json) and implement credential storage via `SecureStorageService` in [`src/services/secureStorageService.ts`](https://github.com/hbmartin/secure-design/blob/main/src/services/secureStorageService.ts).

Once these steps are complete, the Secure-Design extension automatically surfaces your custom endpoint in the model picker and routes all LLM traffic through your implementation without requiring changes to chat or canvas logic.

## Frequently Asked Questions

### What is the minimum code required to implement a custom AI provider?

At minimum, you must create a class that extends `AIProvider` from [`src/providers/types.ts`](https://github.com/hbmartin/secure-design/blob/main/src/providers/types.ts) and implements three things: a static `metadata` object with `id` and `configureCommand`, a `models` array listing available model IDs, and a `createInstance()` method that returns a `LanguageModelV2` instance. You should also implement `validateCredentials()` to ensure the UI only shows your provider when properly configured.

### How does Secure-Design store API keys for custom providers?

The extension uses `SecureStorageService` defined in [`src/services/secureStorageService.ts`](https://github.com/hbmartin/secure-design/blob/main/src/services/secureStorageService.ts), which wraps VS Code's built-in `SecretStorage` API. When you implement a configuration command, you instantiate `SecureStorageService` with `context.secrets` and call `storage.store(key, value)` to encrypt and persist credentials. These values are retrieved later via `config.config.get()` in the provider's `createInstance()` method.

### Can I use a custom provider with existing chat features without modifying core code?

Yes. Once you register your provider with the `IProviderRegistry` in [`src/di/ServiceContainer.ts`](https://github.com/hbmartin/secure-design/blob/main/src/di/ServiceContainer.ts), downstream services like `CustomAgentService` automatically discover your models. The chat sidebar, model picker, and design generation features query the registry via `getAllModels()` and instantiate your provider through `createInstance()` without requiring any changes to the chat or canvas logic.

### What configuration keys should I use for custom provider settings?

Follow the pattern `<providerId>.<settingName>` to avoid collisions. For example, if your provider ID is `mycustom`, use `mycustom.baseUrl` and `mycustom.apiKey`. List these keys in the `additionalConfigKeys` array of your `metadata` object so the configuration UI knows which settings to prompt for, and retrieve them in `createInstance()` using `config.config.get<string>('mycustom.baseUrl')`.