@@ -112,6 +112,125 @@ describe("CodeIndexConfigManager", () => {
112112 } )
113113 } )
114114
115+ it ( "should load OpenAI Compatible configuration with modelDimension from globalState" , async ( ) => {
116+ const mockGlobalState = {
117+ codebaseIndexEnabled : true ,
118+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
119+ codebaseIndexEmbedderProvider : "openai-compatible" ,
120+ codebaseIndexEmbedderBaseUrl : "" ,
121+ codebaseIndexEmbedderModelId : "custom-model" ,
122+ }
123+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
124+ if ( key === "codebaseIndexConfig" ) return mockGlobalState
125+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
126+ if ( key === "codebaseIndexOpenAiCompatibleModelDimension" ) return 1024
127+ return undefined
128+ } )
129+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
130+ if ( key === "codeIndexQdrantApiKey" ) return "test-qdrant-key"
131+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return "test-openai-compatible-key"
132+ return undefined
133+ } )
134+
135+ const result = await configManager . loadConfiguration ( )
136+
137+ expect ( result . currentConfig ) . toEqual ( {
138+ isEnabled : true ,
139+ isConfigured : true ,
140+ embedderProvider : "openai-compatible" ,
141+ modelId : "custom-model" ,
142+ openAiOptions : { openAiNativeApiKey : "" } ,
143+ ollamaOptions : { ollamaBaseUrl : "" } ,
144+ openAiCompatibleOptions : {
145+ baseUrl : "https://api.example.com/v1" ,
146+ apiKey : "test-openai-compatible-key" ,
147+ modelDimension : 1024 ,
148+ } ,
149+ qdrantUrl : "http://qdrant.local" ,
150+ qdrantApiKey : "test-qdrant-key" ,
151+ searchMinScore : 0.4 ,
152+ } )
153+ } )
154+
155+ it ( "should handle missing modelDimension for OpenAI Compatible configuration" , async ( ) => {
156+ const mockGlobalState = {
157+ codebaseIndexEnabled : true ,
158+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
159+ codebaseIndexEmbedderProvider : "openai-compatible" ,
160+ codebaseIndexEmbedderBaseUrl : "" ,
161+ codebaseIndexEmbedderModelId : "custom-model" ,
162+ }
163+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
164+ if ( key === "codebaseIndexConfig" ) return mockGlobalState
165+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
166+ if ( key === "codebaseIndexOpenAiCompatibleModelDimension" ) return undefined
167+ return undefined
168+ } )
169+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
170+ if ( key === "codeIndexQdrantApiKey" ) return "test-qdrant-key"
171+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return "test-openai-compatible-key"
172+ return undefined
173+ } )
174+
175+ const result = await configManager . loadConfiguration ( )
176+
177+ expect ( result . currentConfig ) . toEqual ( {
178+ isEnabled : true ,
179+ isConfigured : true ,
180+ embedderProvider : "openai-compatible" ,
181+ modelId : "custom-model" ,
182+ openAiOptions : { openAiNativeApiKey : "" } ,
183+ ollamaOptions : { ollamaBaseUrl : "" } ,
184+ openAiCompatibleOptions : {
185+ baseUrl : "https://api.example.com/v1" ,
186+ apiKey : "test-openai-compatible-key" ,
187+ } ,
188+ qdrantUrl : "http://qdrant.local" ,
189+ qdrantApiKey : "test-qdrant-key" ,
190+ searchMinScore : 0.4 ,
191+ } )
192+ } )
193+
194+ it ( "should handle invalid modelDimension type for OpenAI Compatible configuration" , async ( ) => {
195+ const mockGlobalState = {
196+ codebaseIndexEnabled : true ,
197+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
198+ codebaseIndexEmbedderProvider : "openai-compatible" ,
199+ codebaseIndexEmbedderBaseUrl : "" ,
200+ codebaseIndexEmbedderModelId : "custom-model" ,
201+ }
202+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
203+ if ( key === "codebaseIndexConfig" ) return mockGlobalState
204+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
205+ if ( key === "codebaseIndexOpenAiCompatibleModelDimension" ) return "invalid-dimension"
206+ return undefined
207+ } )
208+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
209+ if ( key === "codeIndexQdrantApiKey" ) return "test-qdrant-key"
210+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return "test-openai-compatible-key"
211+ return undefined
212+ } )
213+
214+ const result = await configManager . loadConfiguration ( )
215+
216+ expect ( result . currentConfig ) . toEqual ( {
217+ isEnabled : true ,
218+ isConfigured : true ,
219+ embedderProvider : "openai-compatible" ,
220+ modelId : "custom-model" ,
221+ openAiOptions : { openAiNativeApiKey : "" } ,
222+ ollamaOptions : { ollamaBaseUrl : "" } ,
223+ openAiCompatibleOptions : {
224+ baseUrl : "https://api.example.com/v1" ,
225+ apiKey : "test-openai-compatible-key" ,
226+ modelDimension : "invalid-dimension" ,
227+ } ,
228+ qdrantUrl : "http://qdrant.local" ,
229+ qdrantApiKey : "test-qdrant-key" ,
230+ searchMinScore : 0.4 ,
231+ } )
232+ } )
233+
115234 it ( "should detect restart requirement when provider changes" , async ( ) => {
116235 // Initial state - properly configured
117236 mockContextProxy . getGlobalState . mockReturnValue ( {
@@ -378,6 +497,171 @@ describe("CodeIndexConfigManager", () => {
378497 expect ( result . requiresRestart ) . toBe ( true )
379498 } )
380499
500+ it ( "should handle OpenAI Compatible modelDimension changes" , async ( ) => {
501+ // Initial state with modelDimension
502+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
503+ if ( key === "codebaseIndexConfig" ) {
504+ return {
505+ codebaseIndexEnabled : true ,
506+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
507+ codebaseIndexEmbedderProvider : "openai-compatible" ,
508+ codebaseIndexEmbedderModelId : "custom-model" ,
509+ }
510+ }
511+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
512+ if ( key === "codebaseIndexOpenAiCompatibleModelDimension" ) return 1024
513+ return undefined
514+ } )
515+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
516+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return "test-api-key"
517+ return undefined
518+ } )
519+
520+ await configManager . loadConfiguration ( )
521+
522+ // Change modelDimension
523+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
524+ if ( key === "codebaseIndexConfig" ) {
525+ return {
526+ codebaseIndexEnabled : true ,
527+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
528+ codebaseIndexEmbedderProvider : "openai-compatible" ,
529+ codebaseIndexEmbedderModelId : "custom-model" ,
530+ }
531+ }
532+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
533+ if ( key === "codebaseIndexOpenAiCompatibleModelDimension" ) return 2048
534+ return undefined
535+ } )
536+
537+ const result = await configManager . loadConfiguration ( )
538+ expect ( result . requiresRestart ) . toBe ( true )
539+ } )
540+
541+ it ( "should not require restart when modelDimension remains the same" , async ( ) => {
542+ // Initial state with modelDimension
543+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
544+ if ( key === "codebaseIndexConfig" ) {
545+ return {
546+ codebaseIndexEnabled : true ,
547+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
548+ codebaseIndexEmbedderProvider : "openai-compatible" ,
549+ codebaseIndexEmbedderModelId : "custom-model" ,
550+ }
551+ }
552+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
553+ if ( key === "codebaseIndexOpenAiCompatibleModelDimension" ) return 1024
554+ return undefined
555+ } )
556+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
557+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return "test-api-key"
558+ return undefined
559+ } )
560+
561+ await configManager . loadConfiguration ( )
562+
563+ // Keep modelDimension the same, change unrelated setting
564+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
565+ if ( key === "codebaseIndexConfig" ) {
566+ return {
567+ codebaseIndexEnabled : true ,
568+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
569+ codebaseIndexEmbedderProvider : "openai-compatible" ,
570+ codebaseIndexEmbedderModelId : "custom-model" ,
571+ codebaseIndexSearchMinScore : 0.5 , // Changed unrelated setting
572+ }
573+ }
574+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
575+ if ( key === "codebaseIndexOpenAiCompatibleModelDimension" ) return 1024
576+ return undefined
577+ } )
578+
579+ const result = await configManager . loadConfiguration ( )
580+ expect ( result . requiresRestart ) . toBe ( false )
581+ } )
582+
583+ it ( "should require restart when modelDimension is added" , async ( ) => {
584+ // Initial state without modelDimension
585+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
586+ if ( key === "codebaseIndexConfig" ) {
587+ return {
588+ codebaseIndexEnabled : true ,
589+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
590+ codebaseIndexEmbedderProvider : "openai-compatible" ,
591+ codebaseIndexEmbedderModelId : "custom-model" ,
592+ }
593+ }
594+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
595+ if ( key === "codebaseIndexOpenAiCompatibleModelDimension" ) return undefined
596+ return undefined
597+ } )
598+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
599+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return "test-api-key"
600+ return undefined
601+ } )
602+
603+ await configManager . loadConfiguration ( )
604+
605+ // Add modelDimension
606+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
607+ if ( key === "codebaseIndexConfig" ) {
608+ return {
609+ codebaseIndexEnabled : true ,
610+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
611+ codebaseIndexEmbedderProvider : "openai-compatible" ,
612+ codebaseIndexEmbedderModelId : "custom-model" ,
613+ }
614+ }
615+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
616+ if ( key === "codebaseIndexOpenAiCompatibleModelDimension" ) return 1024
617+ return undefined
618+ } )
619+
620+ const result = await configManager . loadConfiguration ( )
621+ expect ( result . requiresRestart ) . toBe ( true )
622+ } )
623+
624+ it ( "should require restart when modelDimension is removed" , async ( ) => {
625+ // Initial state with modelDimension
626+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
627+ if ( key === "codebaseIndexConfig" ) {
628+ return {
629+ codebaseIndexEnabled : true ,
630+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
631+ codebaseIndexEmbedderProvider : "openai-compatible" ,
632+ codebaseIndexEmbedderModelId : "custom-model" ,
633+ }
634+ }
635+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
636+ if ( key === "codebaseIndexOpenAiCompatibleModelDimension" ) return 1024
637+ return undefined
638+ } )
639+ mockContextProxy . getSecret . mockImplementation ( ( key : string ) => {
640+ if ( key === "codebaseIndexOpenAiCompatibleApiKey" ) return "test-api-key"
641+ return undefined
642+ } )
643+
644+ await configManager . loadConfiguration ( )
645+
646+ // Remove modelDimension
647+ mockContextProxy . getGlobalState . mockImplementation ( ( key : string ) => {
648+ if ( key === "codebaseIndexConfig" ) {
649+ return {
650+ codebaseIndexEnabled : true ,
651+ codebaseIndexQdrantUrl : "http://qdrant.local" ,
652+ codebaseIndexEmbedderProvider : "openai-compatible" ,
653+ codebaseIndexEmbedderModelId : "custom-model" ,
654+ }
655+ }
656+ if ( key === "codebaseIndexOpenAiCompatibleBaseUrl" ) return "https://api.example.com/v1"
657+ if ( key === "codebaseIndexOpenAiCompatibleModelDimension" ) return undefined
658+ return undefined
659+ } )
660+
661+ const result = await configManager . loadConfiguration ( )
662+ expect ( result . requiresRestart ) . toBe ( true )
663+ } )
664+
381665 it ( "should not require restart when disabled remains disabled" , async ( ) => {
382666 // Initial state - disabled but configured
383667 mockContextProxy . getGlobalState . mockReturnValue ( {
0 commit comments