Skip to main content

ProviderManager Internals

ProviderManager handles the lifecycle of LLM providers, including create, read, update, and delete operations, along with the preset template system and persistent storage.


File Locations

FilePath
ProviderManagerpackages/desktop/app/main/services/capabilities/llm/config-service/ProviderManager.ts
LLMConfigServicepackages/desktop/app/main/services/capabilities/llm/config-service/LLMConfigService.ts
Provider templatespackages/desktop/app/shared/llm-config.ts (PROVIDER_TEMPLATES)
Provider presetspackages/desktop/app/shared/provider-presets.ts
Schemaspackages/desktop/app/main/services/capabilities/llm/config-service/schemas.ts
IPC Routerpackages/desktop/app/main/services/routers/llm/ProviderRouter.ts

Architectural Context

Delegate Pattern

LLMConfigService uses the Delegate Pattern to distribute responsibilities across four specialized managers. ProviderManager communicates with its host through the ProviderManagerDelegate interface to avoid circular dependencies.

graph TB
subgraph LLMConfigService
direction TB
PM[ProviderManager]
MDM[ModelDiscoveryManager]
CIOM[ConfigIOManager]
AMM[AgentModelsManager]
end

PM -->|Delegate| LLMConfigService
MDM -->|Delegate| LLMConfigService
CIOM -->|Delegate| LLMConfigService
AMM -->|Delegate| LLMConfigService

subgraph Storage ["Storage"]
SQLite[(SQLite primary storage)]
JSON[(JSON fallback)]
end

PM --> SQLite
PM --> JSON

subgraph Cache ["Cache"]
PI[Provider Index<br/>Map index]
end

PM --> PI

Delegate Interface

interface ProviderManagerDelegate {
loadConfig(): Promise<LLMsConfig>; // Load JSON config
saveConfig(): Promise<void>; // Save JSON config
getDb(): DbClient | null; // Get database instance
waitForMigration(): Promise<void>; // Wait for data migration to complete
useSQLite(): boolean; // Whether to use SQLite
isMigrated(): Promise<boolean>; // Whether migration is complete
invalidateProviderIndex(): void; // Invalidate the Provider index
invalidateConfig(): void; // Invalidate config cache
buildProviderIndex(providers: LLMProvider[]): void; // Build the index
getProviderFromIndex(id: string): LLMProvider | undefined; // Query the index
hasProviderIndex(): boolean; // Whether the index exists
}

Data Structures

Full LLMProvider Definition

interface LLMProvider {
id: string; // UUID or preset ID (e.g. 'openai', 'anthropic')
name: string; // Display name
apiFormat?: ApiFormat; // Preferred API format field
chatApiFormat?: ApiFormat; // Legacy format field (backward compatibility)
apiType?: string; // Legacy type field
api_base_url?: string; // API Base URL
api_key: string; // Single key value (legacy; new system uses ApiKeyPool)
models: string[]; // List of supported model IDs
modelConfigs?: ModelConfig[]; // Per-model detailed config
modelGroups?: ModelGroup[]; // Model groups (with category info)
modelsEndpoint?: string; // Model discovery API endpoint
enabled: boolean; // Whether enabled
transformer?: TransformerConfig; // Request/response transformer chain
icon?: string; // Icon path or emoji
website?: string; // Official website link
docsUrl?: string; // API documentation link
defaultSettings?: CompletionSettings; // Default completion settings
codingPlan?: CodingPlanConfig; // Coding Plan dedicated config
isSystem?: boolean; // System built-in flag (cannot be deleted)
presetId?: string; // Source preset template ID
headers?: Record<string, string>; // Custom HTTP headers
createdAt: string; // ISO timestamp
updatedAt: string; // ISO timestamp
}

interface ModelConfig {
id: string;
name: string;
enabled: boolean;
category?: 'chat' | 'reasoning' | 'image' | 'video' | 'embedding' | 'code';
group?: string;
contextLength?: number;
maxTokens?: number;
completionSettings?: CompletionSettings;
vision?: boolean; // Vision capability
functionCall?: boolean; // Function calling capability
reasoning?: boolean; // Reasoning capability
webSearch?: boolean; // Web search capability
}

Algorithms and Logic

Provider CRUD Operation Flow

Add Provider (SQLite path)

1. Receive LLMProviderInput
2. Generate UUID as provider.id
3. Fill defaults (enabled=false, defaultSettings=DEFAULT_COMPLETION_SETTINGS)
4. Validate with Zod Schema (LLMProviderSchema.parse)
5. Uniqueness check: query SQLite to confirm ID does not exist
6. Write to SQLite: db.llmProvidersInsert(provider)
7. Sync model list to llm_models table
8. Sync model groups to llm_model_groups table
9. Invalidate Provider Index: invalidateProviderIndex()
10. Return { success: true, provider }

Update Provider

1. Receive { id, ...updates }
2. Read existing provider from SQLite
3. Merge updated fields (shallow merge)
4. Update updatedAt timestamp
5. Write to SQLite: db.llmProvidersUpdate(id, updates)
6. If models or modelConfigs changed → sync model tables
7. Invalidate Provider Index
8. Return { success: true, provider }

Delete Provider

1. Check isSystem flag → system built-ins cannot be deleted
2. Delete from SQLite: db.llmProvidersDelete(id)
3. Cascade delete associated models and groups
4. Invalidate Provider Index
5. Return { success: true }

Provider Index (O(1) Lookup)

graph LR
subgraph FirstQuery ["First Query"]
Load[loadConfig or SQLite query] --> Build[Build Map]
Build --> Cache[providerIndex: Map&lt;id, Provider&gt;]
end

subgraph SubsequentQuery ["Subsequent Queries"]
Cache --> Lookup[O(1) Map.get]
end

subgraph Mutations ["Mutations"]
CRUD[add/update/delete] --> Invalidate[providerIndex = null]
Invalidate --> Load
end

Behavioral rules:

OperationEffect on index
getProvider(id)Index hit → return; miss → rebuild index, then look up
getProviders()Return full list and rebuild index
addProviderInvalidate index
updateProviderInvalidate index
deleteProviderInvalidate index
toggleProviderInvalidate index

Dual-Storage Mechanism (SQLite + JSON Fallback)

flowchart TD
Start[Startup] --> CheckDB{DbClient available?}
CheckDB -->|Yes| Migrate[ensureMigrated]
CheckDB -->|No| JSONMode[JSON mode]

Migrate --> CheckMigrated{Already migrated?}
CheckMigrated -->|Yes| SQLiteMode[SQLite mode]
CheckMigrated -->|No| DoMigrate[JSON → SQLite migration]
DoMigrate --> SQLiteMode

subgraph SQLiteMode ["SQLite Mode"]
SQLiteMode --> SQLiteRead[db.llmProvidersGetAll]
SQLiteMode --> SQLiteWrite[db.llmProvidersInsert/Update/Delete]
end

subgraph JSONMode ["JSON Mode"]
JSONMode --> JSONRead[fs.readFile + JSON.parse]
JSONMode --> JSONWrite[JSON.stringify + fs.writeFile]
end

Migration process:

  1. On startup, check the llm_config_migrated flag in SQLite
  2. If not yet migrated, read llms-config.json
  3. Write each provider record into SQLite one by one
  4. Sync models and modelGroups to the corresponding tables
  5. Set the migration-complete flag
  6. The JSON file is kept as a backup (no longer the primary storage)

Preset / Template System

Default Provider List

The system includes the following built-in provider templates (in the order defined in getDefaultProviders()):

#IDNameAPI Format
1deepseekDeepSeekopenai
2openrouterOpenRouteropenai
3siliconSiliconFlowopenai
4openaiOpenAIopenai
5anthropicAnthropicanthropic
6geminiGoogle Geminigoogle
7zhipuZhipu AIopenai
8moonshotMoonshotopenai
9dashscopeTongyi Qianwenopenai
10ollamaOllamaopenai
11groqGroqopenai
12claude-codeClaude Codeanthropic

Creating a Provider from a Template

createProviderFromTemplate(template):
1. Copy all config fields from template
2. Set api_key = '' (user must configure this)
3. Set enabled = false
4. Fill defaultSettings (use template.defaultSettings or DEFAULT_COMPLETION_SETTINGS)
5. Generate createdAt / updatedAt timestamps
6. Return LLMProvider instance

Provider Presets

Preset templates come from provider-presets.ts and offer richer configurations than built-in templates, typically targeting Chinese cloud vendors. A preset template includes:

interface PresetProviderTemplate {
id: string; // Preset ID
name: string; // Display name
apiFormat: ApiFormat; // API format
baseUrl: string; // Base URL
models: string[]; // Model list
modelConfigs: ModelConfig[]; // Per-model detailed config
features: string[]; // Feature tags
// ... more fields
}

Adding a provider from a preset:

addFromPreset(presetId, apiKey?):
1. Find preset via getPresetById(presetId)
2. Convert preset to ProviderTemplate
3. Call createProviderFromTemplate(template)
4. If apiKey provided → set the key
5. Call addProvider() to persist
6. Return new provider

IPC Integration Table

IPC ChannelDirectionParametersReturn ValueZod Validation
llmConfig:getProvidersR → MNoneLLMProvider[]None
llmConfig:getProviderR → M{ id: string }LLMProvider | nullllmProviderIdSchema
llmConfig:addProviderR → MLLMProviderInputLLMProviderResultllmProviderCreateSchema
llmConfig:updateProviderR → M{ id, ...fields }LLMProviderResultllmProviderUpdateSchema
llmConfig:deleteProviderR → M{ id: string }{ success, message? }llmProviderIdSchema
llmConfig:toggleProviderR → M{ id, enabled }LLMProviderResultid + boolean
llmConfig:discoverModelsR → M{ id, options? }ProviderModelDiscoveryResultmodelDiscoverOptionsSchema
llmConfig:getProviderPresetsR → MNonePresetProviderTemplate[]None
llmConfig:addFromPresetR → M{ presetId, apiKey? }LLMProviderResultstring + string?
llmConfig:exportConfigR → MNoneLLMsConfigNone
llmConfig:importConfigR → MConfig object{ success, message? }Zod object

Extension Points

Adding a New Built-in Provider

  1. Add a ProviderTemplate to the PROVIDER_TEMPLATES array in packages/desktop/app/shared/llm-config.ts
  2. Add the new ID to the defaultTemplateIds array in ProviderManager.getDefaultProviders()
  3. If special URL-building logic is needed, add handling in url-builder.ts
  4. If search support is needed, add an entry to PROVIDER_SEARCH_CONFIGS in provider-presets.ts
  5. If Coding Plan support is needed, add an entry to CODING_PLAN_URL_PRESETS
  6. If Follow-Provider support is needed, add an entry to PROVIDER_MODEL_MAPPINGS

Adding a New Preset Template

  1. Add a PresetProviderTemplate to the PROVIDER_PRESETS array in provider-presets.ts
  2. Fill in complete model configs (modelConfigs, contextLength, maxTokens, capabilities)
  3. The frontend will automatically display the new template in the preset list

FileRelationship
capabilities/llm/config-service/LLMConfigService.tsHost service that initializes ProviderManager and provides the Delegate
capabilities/llm/config-service/schemas.tsZod validation schemas (LLMProviderSchema, etc.)
routers/llm/ProviderRouter.tsIPC layer: receives frontend requests and calls LLMConfigService
routers/llm/schemas.tsIPC parameter validation schemas
shared/llm-config.tsShared type definitions and built-in templates
shared/provider-presets.tsPreset templates, search configs, Coding Plan URLs
workers/DbClient.tsSQLite database operations
workers/db/apiKeys.tsAPI key table CRUD
capabilities/llm/completion/CompletionService.tsConsumes provider config for API calls