Skip to main content

How to Extend LLM Providers

This document provides step-by-step instructions for three common extension scenarios: adding a new preset template, adding a new API format, and configuring search capabilities for a provider.


File Locations

FilePath
Provider templatespackages/desktop/app/shared/llm-config.ts
Provider presetspackages/desktop/app/shared/provider-presets.ts
ProviderManagerpackages/desktop/app/main/services/capabilities/llm/config-service/ProviderManager.ts
URL Builderpackages/desktop/app/main/services/capabilities/llm/completion/url-builder.ts
Header Builderpackages/desktop/app/main/services/capabilities/llm/completion/header-builder.ts
Message Converterpackages/desktop/app/main/services/capabilities/llm/completion/message-converter.ts
DirectApiHandlerpackages/desktop/app/main/services/capabilities/llm/completion/DirectApiHandler.ts
StreamHandlerpackages/desktop/app/main/services/capabilities/llm/completion/StreamHandler.ts
CompletionServicepackages/desktop/app/main/services/capabilities/llm/completion/CompletionService.ts
Typespackages/desktop/app/main/services/capabilities/llm/completion/types.ts
ProviderSearchInjectorpackages/desktop/app/main/services/capabilities/llm/completion/ProviderSearchInjector.ts
NativeSearchInjectorpackages/desktop/app/main/services/capabilities/llm/completion/NativeSearchInjector.ts
IPC Router Schemaspackages/desktop/app/main/services/routers/llm/schemas.ts
Frontend i18npackages/renderer/src/locales/{en,zh,ja}/providers/llm.json

Architectural Context

graph TB
subgraph ExtensionPoints ["Extension Points"]
direction TB
A["(1) New preset template<br/>provider-presets.ts"]
B["(2) New API format<br/>types + handlers"]
C["(3) Search config<br/>PROVIDER_SEARCH_CONFIGS"]
end

subgraph AffectedLayers ["Affected Layers"]
direction TB
Shared[shared layer<br/>types + presets]
Completion[completion layer<br/>Handlers + URL]
Config[config-service layer<br/>ProviderManager]
Router[router layer<br/>IPC Schemas]
Frontend[renderer layer<br/>UI + i18n]
end

A --> Shared
A --> Config
A --> Frontend

B --> Shared
B --> Completion
B --> Router

C --> Shared
C --> Completion

Scenario 1: Adding a New Preset Template

When you need to support a new LLM provider (such as a new Chinese cloud vendor or an international provider), create a preset template so users can add it from the UI in one click.

Data Structure

// Full interface for a preset template
interface PresetProviderTemplate {
id: string; // Unique ID (lowercase, e.g. 'minimax')
name: string; // Display name
apiFormat: ApiFormat; // API format
baseUrl: string; // Base URL
modelsEndpoint?: string; // Model discovery endpoint
models: string[]; // List of supported model IDs
modelConfigs: ModelConfig[]; // Per-model detailed config
features: string[]; // Feature tags
icon?: string; // Icon
website?: string; // Official website
docsUrl?: string; // API documentation
defaultSettings?: CompletionSettings; // Default completion parameters
}

Steps

Step 1: Define the Preset Template

Add an entry to the PROVIDER_PRESETS array in packages/desktop/app/shared/provider-presets.ts:

// Pseudocode — adding a new preset
{
id: 'newprovider',
name: 'NewProvider AI',
apiFormat: 'openai', // Most Chinese vendors are OpenAI-compatible
baseUrl: 'https://api.newprovider.com/v1',
modelsEndpoint: '/models',
models: ['np-large', 'np-lite', 'np-vision'],
modelConfigs: [
{
id: 'np-large',
name: 'NP Large',
enabled: true,
category: 'chat',
contextLength: 128000,
maxTokens: 8192,
vision: false,
functionCall: true,
reasoning: false,
},
{
id: 'np-lite',
name: 'NP Lite',
enabled: true,
category: 'chat',
contextLength: 32000,
maxTokens: 4096,
},
{
id: 'np-vision',
name: 'NP Vision',
enabled: true,
category: 'chat',
contextLength: 64000,
maxTokens: 4096,
vision: true,
},
],
features: ['chat', 'function_call'],
website: 'https://newprovider.com',
docsUrl: 'https://docs.newprovider.com/api',
}

Step 2 (Optional): Set as a Default Provider

If you want the new provider to appear in all users' initial lists:

Add the ID to the defaultTemplateIds array in ProviderManager.getDefaultProviders().

Also add the corresponding ProviderTemplate to PROVIDER_TEMPLATES in packages/desktop/app/shared/llm-config.ts.

Step 3 (Optional): Add Coding Plan Support

If the provider has a dedicated Coding Plan API:

// Add to CODING_PLAN_URL_PRESETS
CODING_PLAN_URL_PRESETS['newprovider'] = {
baseUrl: 'https://api.newprovider.com/coding/v1',
separateApiKey: false, // Whether a separate API Key is needed
};

Step 4 (Optional): Add Follow-Provider Model Mapping

If you want automatic selection of the same provider's background/vision models:

// Add to PROVIDER_MODEL_MAPPINGS
PROVIDER_MODEL_MAPPINGS['newprovider'] = {
primary: 'np-large',
background: 'np-lite',
vision: 'np-vision', // Set to null if no vision model exists
};

Step 5: Add Frontend i18n

Add the provider name translation to providers/llm.json under packages/renderer/src/locales/ for en/zh/ja.


Scenario 2: Adding a New API Format

When you encounter a provider API that is incompatible with existing formats (openai/anthropic/google/azure-openai/openai-response), you need to add a new API format.

Step Overview

flowchart TD
S1["(1) Define format identifier<br/>types.ts"] --> S2["(2) URL builder<br/>url-builder.ts"]
S2 --> S3["(3) Header builder<br/>header-builder.ts"]
S3 --> S4["(4) Message conversion<br/>message-converter.ts"]
S4 --> S5["(5) Non-streaming handler<br/>DirectApiHandler.ts"]
S5 --> S6["(6) Streaming handler<br/>StreamHandler.ts"]
S6 --> S7["(7) Register dispatch<br/>CompletionService.ts"]
S7 --> S8["(8) Schema update<br/>schemas.ts"]

Step 1: Define the Format Identifier

Add the new value to ApiFormat in packages/desktop/app/main/services/capabilities/llm/completion/types.ts:

// Before
type ApiFormat = 'openai' | 'anthropic' | 'google' | 'azure-openai' | 'openai-response';

// After
type ApiFormat = 'openai' | 'anthropic' | 'google' | 'azure-openai' | 'openai-response' | 'newformat';

Also update ApiFormatSchema in packages/desktop/app/main/services/capabilities/llm/config-service/schemas.ts:

const ApiFormatSchema = z.enum([
'openai', 'anthropic', 'google', 'azure-openai', 'openai-response', 'newformat'
]);

And update llmProviderCreateSchema in packages/desktop/app/main/services/routers/llm/schemas.ts.

Step 2: URL Builder

Add a builder function in url-builder.ts:

// Pseudocode
function buildNewFormatApiUrl(baseUrl: string): string {
// Build the correct endpoint URL according to the provider's API docs
// Handle various baseUrl input formats
}

Add recognition logic for the new format in resolveApiFormat() (if it needs to be inferred from apiType).

Add a branch for the new format in buildProviderApiUrl().

Step 3: Header Builder

Handle authentication headers for the new format in getProviderHeaders() within header-builder.ts:

// Pseudocode — different providers use different auth schemes
// OpenAI: Authorization: Bearer <key>
// Anthropic: x-api-key: <key>
// New format may use different headers

Step 4: Message Format Conversion

Add a conversion function in message-converter.ts:

// Pseudocode
function convertMessageToNewFormat(message: SimpleChatMessage): NewFormatMessage {
// Convert the generic message format to the provider-specific format
// Handle role mapping, content structure, images, tool calls, etc.
}

Step 5: Non-Streaming Handler

Add to DirectApiHandler.ts:

// Pseudocode
async function callNewFormatCompletion(
provider: LLMProvider,
apiKey: string,
options: CompletionOptions,
logger: LoggerService
): Promise<CompletionResult> {
// Build request body
// Send request
// Parse response into CompletionResult
}

Step 6: Streaming Handler

Add to StreamHandler.ts:

// Pseudocode
async function streamNewFormatCompletion(
provider: LLMProvider,
apiKey: string,
options: CompletionOptions,
messageId: string,
callbacks: StreamCallbacks,
logger: LoggerService
): Promise<void> {
// Build streaming request body
// Use streamSSEResponse() or custom stream parsing
// Call callbacks to deliver incremental content
}

Step 7: Register Dispatch

Register the new format in CompletionService:

// In the switch inside callDirectHandler
case 'newformat':
return callNewFormatCompletion(provider, apiKey, options, this.logger);

// In the switch inside callStreamHandler
case 'newformat':
await streamNewFormatCompletion(provider, apiKey, options, messageId, callbacks, this.logger);
return;

Step 8: Schema Update

Ensure all Zod schemas that reference the ApiFormat enum are updated:

  • capabilities/llm/config-service/schemas.tsApiFormatSchema
  • routers/llm/schemas.tsllmProviderCreateSchema.apiFormat

Scenario 3: Adding Search Configuration for a Provider

When a provider supports web search, you need to configure the search injection method.

Search Type Decision

flowchart TD
Start[Provider supports search?] --> Type{Search implementation}
Type -->|Request parameter| ModelParam["model-param<br/>modify request body"]
Type -->|Built-in tool definition| BuiltinTool["builtin-tool<br/>inject into tools array"]
Type -->|MCP server| MCP["mcp<br/>external handling"]
Type -->|SDK native| SDKNative["sdk-native<br/>NativeSearchInjector"]
Type -->|Not supported| None["none<br/>no config needed"]

Steps

Step 1: Determine the Search Type

Search implementationCriteriaExamples
model-paramAPI enables search via a request parameter (e.g. enable_search: true)DashScope, Baidu
builtin-toolAPI requires injecting a specific tool definition into the tools arrayKimi, Volcengine
mcpSearch is provided by an external MCP serverCustom deployment
sdk-nativeSDK handles search natively (e.g. Anthropic server-side tools)Anthropic
noneSearch not supportedOllama

Step 2: Add the Search Config

Add an entry to PROVIDER_SEARCH_CONFIGS in packages/desktop/app/shared/provider-presets.ts:

model-param type (request parameter):

// Pseudocode
PROVIDER_SEARCH_CONFIGS['newprovider'] = {
type: 'model-param',
paramName: 'enable_search', // parameter name
paramValue: true, // parameter value
extraParams: { // extra parameters (optional)
search_mode: 'auto',
},
applicableModels: null, // null means all models
};

builtin-tool type (built-in tool):

// Pseudocode
PROVIDER_SEARCH_CONFIGS['newprovider'] = {
type: 'builtin-tool',
toolDefinition: {
type: 'function',
function: {
name: 'web_search',
description: 'Search the web for information',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
},
required: ['query'],
},
},
},
conflictsWithFC: false, // Whether it conflicts with function calling
applicableModels: ['np-large'], // Only certain models support it (null = all)
};

Step 3: Verify Injection

ProviderSearchInjector automatically detects a provider's search config via getSearchConfig() and builds the injection content in buildSearchAugmentation(). No additional code changes are required.


File Modification Checklist

Scenario 1: New Preset Template

FileChangeRequired
shared/provider-presets.tsAdd PROVIDER_PRESETS entryYes
shared/llm-config.tsAdd PROVIDER_TEMPLATES entry (if needed as default)Optional
capabilities/llm/config-service/ProviderManager.tsAdd to defaultTemplateIds (if needed as default)Optional
shared/provider-presets.tsCODING_PLAN_URL_PRESETS (if needed)Optional
shared/provider-presets.tsPROVIDER_MODEL_MAPPINGS (if needed)Optional
shared/provider-presets.tsPROVIDER_SEARCH_CONFIGS (if search needed)Optional
i18n JSON files (en/zh/ja)Provider name translationsRecommended

Scenario 2: New API Format

FileChangeRequired
capabilities/llm/completion/types.tsApiFormat typeYes
capabilities/llm/completion/url-builder.tsURL builder + resolveApiFormat + buildProviderApiUrlYes
capabilities/llm/completion/header-builder.tsAuth headersYes
capabilities/llm/completion/message-converter.tsMessage format conversionYes
capabilities/llm/completion/DirectApiHandler.tsNon-streaming handlerYes
capabilities/llm/completion/StreamHandler.tsStreaming handlerYes
capabilities/llm/completion/CompletionService.tsSwitch branch registrationYes
capabilities/llm/config-service/schemas.tsApiFormatSchemaYes
routers/llm/schemas.tsllmProviderCreateSchemaYes

Scenario 3: Add Search Config

FileChangeRequired
shared/provider-presets.tsPROVIDER_SEARCH_CONFIGS entryYes

Testing Guide

Preset Template Tests

  1. Unit tests: Verify template data integrity

    • All required fields are present and valid
    • Every model ID in modelConfigs exists in the models array
    • apiFormat value is within the ApiFormat enum
  2. Integration tests:

    • Create a provider from preset → verify provider data is correct
    • Enable provider → configure a valid API Key → send a test message
    • Model discovery → verify returned model list
  3. UI tests:

    • New preset appears in the preset list on the settings page
    • Clicking "Add" correctly creates the provider
    • Provider config page displays the correct fields

API Format Tests

  1. URL builder tests:

    • Various baseUrl input formats → correct API endpoint
    • URLs with path prefixes → prefix not lost
  2. Message conversion tests:

    • Plain text message → correct format
    • Message with images → correctly handled
    • Message with tool calls → correct format
  3. Handler tests:

    • Non-streaming: send request → parse response → CompletionResult
    • Streaming: SSE events → callbacks triggered correctly
    • Error handling: network errors, API errors, format errors
  4. End-to-end tests:

    • Use testModel() to verify the full pipeline
    • Streaming conversation → verify onDelta/onDone callbacks

Search Config Tests

  1. Injection tests:

    • getSearchConfig() correctly recognizes the new provider
    • buildSearchAugmentation() generates correct injection content
    • model-param type: request body contains correct parameters
    • builtin-tool type: tools array contains the correct tool definition
  2. Conflict tests:

    • When conflictsWithFC is true: search tool and function calling do not coexist
    • applicableModels restriction: search is not injected for non-applicable models

FileRelationship
shared/llm-config.tsPROVIDER_TEMPLATES, ApiFormat, core types
shared/provider-presets.tsPreset templates, search configs, Coding Plan URLs, model mappings
capabilities/llm/config-service/ProviderManager.tsDefault provider list, template creation logic
capabilities/llm/config-service/schemas.tsApiFormatSchema, validation schemas
capabilities/llm/completion/types.tsApiFormat type definition
capabilities/llm/completion/url-builder.tsresolveApiFormat, URL builders
capabilities/llm/completion/header-builder.tsAuth headers
capabilities/llm/completion/message-converter.tsMessage format conversion
capabilities/llm/completion/DirectApiHandler.tsNon-streaming API handler
capabilities/llm/completion/StreamHandler.tsStreaming API handler
capabilities/llm/completion/CompletionService.tsHandler dispatch registration
capabilities/llm/completion/ProviderSearchInjector.tsSearch injection logic
routers/llm/schemas.tsIPC parameter validation schemas
i18n JSON filesFrontend translations