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
| File | Path |
|---|---|
| ProviderManager | packages/desktop/app/main/services/capabilities/llm/config-service/ProviderManager.ts |
| LLMConfigService | packages/desktop/app/main/services/capabilities/llm/config-service/LLMConfigService.ts |
| Provider templates | packages/desktop/app/shared/llm-config.ts (PROVIDER_TEMPLATES) |
| Provider presets | packages/desktop/app/shared/provider-presets.ts |
| Schemas | packages/desktop/app/main/services/capabilities/llm/config-service/schemas.ts |
| IPC Router | packages/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<id, Provider>]
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:
| Operation | Effect on index |
|---|---|
getProvider(id) | Index hit → return; miss → rebuild index, then look up |
getProviders() | Return full list and rebuild index |
addProvider | Invalidate index |
updateProvider | Invalidate index |
deleteProvider | Invalidate index |
toggleProvider | Invalidate 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:
- On startup, check the
llm_config_migratedflag in SQLite - If not yet migrated, read
llms-config.json - Write each provider record into SQLite one by one
- Sync models and modelGroups to the corresponding tables
- Set the migration-complete flag
- 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()):
| # | ID | Name | API Format |
|---|---|---|---|
| 1 | deepseek | DeepSeek | openai |
| 2 | openrouter | OpenRouter | openai |
| 3 | silicon | SiliconFlow | openai |
| 4 | openai | OpenAI | openai |
| 5 | anthropic | Anthropic | anthropic |
| 6 | gemini | Google Gemini | |
| 7 | zhipu | Zhipu AI | openai |
| 8 | moonshot | Moonshot | openai |
| 9 | dashscope | Tongyi Qianwen | openai |
| 10 | ollama | Ollama | openai |
| 11 | groq | Groq | openai |
| 12 | claude-code | Claude Code | anthropic |
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 Channel | Direction | Parameters | Return Value | Zod Validation |
|---|---|---|---|---|
llmConfig:getProviders | R → M | None | LLMProvider[] | None |
llmConfig:getProvider | R → M | { id: string } | LLMProvider | null | llmProviderIdSchema |
llmConfig:addProvider | R → M | LLMProviderInput | LLMProviderResult | llmProviderCreateSchema |
llmConfig:updateProvider | R → M | { id, ...fields } | LLMProviderResult | llmProviderUpdateSchema |
llmConfig:deleteProvider | R → M | { id: string } | { success, message? } | llmProviderIdSchema |
llmConfig:toggleProvider | R → M | { id, enabled } | LLMProviderResult | id + boolean |
llmConfig:discoverModels | R → M | { id, options? } | ProviderModelDiscoveryResult | modelDiscoverOptionsSchema |
llmConfig:getProviderPresets | R → M | None | PresetProviderTemplate[] | None |
llmConfig:addFromPreset | R → M | { presetId, apiKey? } | LLMProviderResult | string + string? |
llmConfig:exportConfig | R → M | None | LLMsConfig | None |
llmConfig:importConfig | R → M | Config object | { success, message? } | Zod object |
Extension Points
Adding a New Built-in Provider
- Add a
ProviderTemplateto thePROVIDER_TEMPLATESarray inpackages/desktop/app/shared/llm-config.ts - Add the new ID to the
defaultTemplateIdsarray inProviderManager.getDefaultProviders() - If special URL-building logic is needed, add handling in
url-builder.ts - If search support is needed, add an entry to
PROVIDER_SEARCH_CONFIGSinprovider-presets.ts - If Coding Plan support is needed, add an entry to
CODING_PLAN_URL_PRESETS - If Follow-Provider support is needed, add an entry to
PROVIDER_MODEL_MAPPINGS
Adding a New Preset Template
- Add a
PresetProviderTemplateto thePROVIDER_PRESETSarray inprovider-presets.ts - Fill in complete model configs (modelConfigs, contextLength, maxTokens, capabilities)
- The frontend will automatically display the new template in the preset list
Related Files
| File | Relationship |
|---|---|
capabilities/llm/config-service/LLMConfigService.ts | Host service that initializes ProviderManager and provides the Delegate |
capabilities/llm/config-service/schemas.ts | Zod validation schemas (LLMProviderSchema, etc.) |
routers/llm/ProviderRouter.ts | IPC layer: receives frontend requests and calls LLMConfigService |
routers/llm/schemas.ts | IPC parameter validation schemas |
shared/llm-config.ts | Shared type definitions and built-in templates |
shared/provider-presets.ts | Preset templates, search configs, Coding Plan URLs |
workers/DbClient.ts | SQLite database operations |
workers/db/apiKeys.ts | API key table CRUD |
capabilities/llm/completion/CompletionService.ts | Consumes provider config for API calls |