File tree Expand file tree Collapse file tree 4 files changed +87
-3
lines changed
Expand file tree Collapse file tree 4 files changed +87
-3
lines changed Original file line number Diff line number Diff line change 1+ /**
2+ * Test that SUPPORTED_PROVIDERS stays in sync
3+ */
4+
5+ import { describe , test , expect } from "bun:test" ;
6+ import { SUPPORTED_PROVIDERS , isValidProvider } from "./providers" ;
7+
8+ describe ( "Provider Registry" , ( ) => {
9+ test ( "SUPPORTED_PROVIDERS includes all expected providers" , ( ) => {
10+ const expected = [ "anthropic" , "openai" , "ollama" , "openrouter" ] ;
11+ expect ( SUPPORTED_PROVIDERS ) . toEqual ( expected ) ;
12+ } ) ;
13+
14+ test ( "isValidProvider correctly identifies valid providers" , ( ) => {
15+ expect ( isValidProvider ( "anthropic" ) ) . toBe ( true ) ;
16+ expect ( isValidProvider ( "openai" ) ) . toBe ( true ) ;
17+ expect ( isValidProvider ( "ollama" ) ) . toBe ( true ) ;
18+ expect ( isValidProvider ( "openrouter" ) ) . toBe ( true ) ;
19+ } ) ;
20+
21+ test ( "isValidProvider rejects invalid providers" , ( ) => {
22+ expect ( isValidProvider ( "invalid" ) ) . toBe ( false ) ;
23+ expect ( isValidProvider ( "" ) ) . toBe ( false ) ;
24+ expect ( isValidProvider ( "gpt-4" ) ) . toBe ( false ) ;
25+ } ) ;
26+ } ) ;
Original file line number Diff line number Diff line change 1+ /**
2+ * Centralized provider registry
3+ *
4+ * All supported AI providers must be listed here. This prevents bugs where
5+ * a new provider is added to aiService but forgotten in PROVIDERS_LIST.
6+ *
7+ * When adding a new provider:
8+ * 1. Add the provider name to this array
9+ * 2. Implement provider handling in aiService.ts getModel()
10+ * 3. The test in aiService will fail if not all providers are handled
11+ */
12+ export const SUPPORTED_PROVIDERS = [
13+ "anthropic" ,
14+ "openai" ,
15+ "ollama" ,
16+ "openrouter" ,
17+ ] as const ;
18+
19+ /**
20+ * Union type of all supported provider names
21+ */
22+ export type ProviderName = ( typeof SUPPORTED_PROVIDERS ) [ number ] ;
23+
24+ /**
25+ * Type guard to check if a string is a valid provider name
26+ */
27+ export function isValidProvider ( provider : string ) : provider is ProviderName {
28+ return SUPPORTED_PROVIDERS . includes ( provider as ProviderName ) ;
29+ }
30+
31+ /**
32+ * Assert exhaustiveness at compile-time for switch/if-else chains
33+ *
34+ * Usage:
35+ * ```ts
36+ * if (provider === 'anthropic') { ... }
37+ * else if (provider === 'openai') { ... }
38+ * else if (provider === 'ollama') { ... }
39+ * else if (provider === 'openrouter') { ... }
40+ * else {
41+ * assertExhaustive(provider); // TypeScript error if a case is missing
42+ * }
43+ * ```
44+ */
45+ export function assertExhaustive ( value : never ) : never {
46+ throw new Error ( `Unhandled provider case: ${ value } ` ) ;
47+ }
Original file line number Diff line number Diff line change @@ -7,6 +7,7 @@ import { sanitizeToolInputs } from "@/utils/messages/sanitizeToolInput";
77import type { Result } from "@/types/result" ;
88import { Ok , Err } from "@/types/result" ;
99import type { WorkspaceMetadata } from "@/types/workspace" ;
10+ import { SUPPORTED_PROVIDERS , type ProviderName } from "@/constants/providers" ;
1011
1112import type { CmuxMessage , CmuxTextPart } from "@/types/message" ;
1213import { createCmuxMessage } from "@/types/message" ;
@@ -261,6 +262,15 @@ export class AIService extends EventEmitter {
261262 } ) ;
262263 }
263264
265+ // Check if provider is supported (prevents silent failures when adding to SUPPORTED_PROVIDERS
266+ // but forgetting to implement handler below)
267+ if ( ! SUPPORTED_PROVIDERS . includes ( providerName as ProviderName ) ) {
268+ return Err ( {
269+ type : "provider_not_supported" ,
270+ provider : providerName ,
271+ } ) ;
272+ }
273+
264274 // Load providers configuration - the ONLY source of truth
265275 const providersConfig = this . config . loadProvidersConfig ( ) ;
266276 let providerConfig = providersConfig ?. [ providerName ] ?? { } ;
Original file line number Diff line number Diff line change @@ -14,6 +14,7 @@ import { log } from "@/services/log";
1414import { countTokens , countTokensBatch } from "@/utils/main/tokenizer" ;
1515import { calculateTokenStats } from "@/utils/tokens/tokenStatsCalculator" ;
1616import { IPC_CHANNELS , getChatChannel } from "@/constants/ipc-constants" ;
17+ import { SUPPORTED_PROVIDERS } from "@/constants/providers" ;
1718import type { SendMessageError } from "@/types/errors" ;
1819import type { SendMessageOptions , DeleteMessage } from "@/types/ipc" ;
1920import { Ok , Err } from "@/types/result" ;
@@ -1120,9 +1121,9 @@ export class IpcMain {
11201121
11211122 ipcMain . handle ( IPC_CHANNELS . PROVIDERS_LIST , ( ) => {
11221123 try {
1123- // Return all supported providers, not just configured ones
1124- // This matches the providers defined in the registry
1125- return [ "anthropic" , "openai" ] ;
1124+ // Return all supported providers from centralized registry
1125+ // This automatically stays in sync as new providers are added
1126+ return [ ... SUPPORTED_PROVIDERS ] ;
11261127 } catch ( error ) {
11271128 log . error ( "Failed to list providers:" , error ) ;
11281129 return [ ] ;
You can’t perform that action at this time.
0 commit comments