Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8e26a0d
feat: Add OpenAI Compatible embedder for codebase indexing
SannidhyaSah May 28, 2025
18f572f
fix: Update CodeIndexSettings tests for OpenAI Compatible provider
SannidhyaSah May 28, 2025
9755fd7
fix: resolve UI test failures and ESLint errors
SannidhyaSah May 28, 2025
7a086cb
Merge branch 'RooCodeInc:main' into feat/openai-compatible-embedder
SannidhyaSah Jun 3, 2025
f0de952
Merge branch 'RooCodeInc:main' into feat/openai-compatible-embedder
SannidhyaSah Jun 4, 2025
0950b4e
feat: add custom model infrastructure for OpenAI-compatible embedder
SannidhyaSah Jun 4, 2025
97ec212
Add missing translations for OpenAI-compatible model dimension settin…
SannidhyaSah Jun 4, 2025
2cd9acf
refactor: remove unused modelDimension parameter from OpenAiCompatibl…
daniel-lxs Jun 4, 2025
77f4787
chore: bot suggestion
daniel-lxs Jun 4, 2025
b9800d2
chore: bot suggestion
daniel-lxs Jun 4, 2025
217075d
refactor: rename OpenAiCompatibleEmbedder to OpenAICompatibleEmbedder…
daniel-lxs Jun 4, 2025
8c7ca18
feat: add model dimension validation for OpenAI-compatible settings
daniel-lxs Jun 4, 2025
15d2ce6
refactor: improve default model ID retrieval logic for embedding prov…
daniel-lxs Jun 4, 2025
892a4cd
feat: add default model ID retrieval for openai-compatible provider
daniel-lxs Jun 4, 2025
9921dbf
refactor: update default model ID retrieval to use shared utility fun…
daniel-lxs Jun 4, 2025
ee6e3e4
fix: Remove unnecessary type assertion in OpenAICompatibleEmbedder
daniel-lxs Jun 4, 2025
f68a349
feat: add model dimension input for openai-compatible provider
daniel-lxs Jun 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/types/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,12 @@ describe("GLOBAL_STATE_KEYS", () => {
it("should not contain secret state keys", () => {
expect(GLOBAL_STATE_KEYS).not.toContain("openRouterApiKey")
})

it("should contain OpenAI Compatible base URL setting", () => {
expect(GLOBAL_STATE_KEYS).toContain("codebaseIndexOpenAiCompatibleBaseUrl")
})

it("should not contain OpenAI Compatible API key (secret)", () => {
expect(GLOBAL_STATE_KEYS).not.toContain("codebaseIndexOpenAiCompatibleApiKey")
})
})
5 changes: 4 additions & 1 deletion packages/types/src/codebase-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { z } from "zod"
export const codebaseIndexConfigSchema = z.object({
codebaseIndexEnabled: z.boolean().optional(),
codebaseIndexQdrantUrl: z.string().optional(),
codebaseIndexEmbedderProvider: z.enum(["openai", "ollama"]).optional(),
codebaseIndexEmbedderProvider: z.enum(["openai", "ollama", "openai-compatible"]).optional(),
codebaseIndexEmbedderBaseUrl: z.string().optional(),
codebaseIndexEmbedderModelId: z.string().optional(),
})
Expand All @@ -21,6 +21,7 @@ export type CodebaseIndexConfig = z.infer<typeof codebaseIndexConfigSchema>
export const codebaseIndexModelsSchema = z.object({
openai: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
ollama: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
"openai-compatible": z.record(z.string(), z.object({ dimension: z.number() })).optional(),
})

export type CodebaseIndexModels = z.infer<typeof codebaseIndexModelsSchema>
Expand All @@ -32,6 +33,8 @@ export type CodebaseIndexModels = z.infer<typeof codebaseIndexModelsSchema>
export const codebaseIndexProviderSchema = z.object({
codeIndexOpenAiKey: z.string().optional(),
codeIndexQdrantApiKey: z.string().optional(),
codebaseIndexOpenAiCompatibleBaseUrl: z.string().optional(),
codebaseIndexOpenAiCompatibleApiKey: z.string().optional(),
})

export type CodebaseIndexProvider = z.infer<typeof codebaseIndexProviderSchema>
2 changes: 2 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ export type SecretState = Pick<
| "litellmApiKey"
| "codeIndexOpenAiKey"
| "codeIndexQdrantApiKey"
| "codebaseIndexOpenAiCompatibleApiKey"
>

export const SECRET_STATE_KEYS = keysOf<SecretState>()([
Expand All @@ -241,6 +242,7 @@ export const SECRET_STATE_KEYS = keysOf<SecretState>()([
"litellmApiKey",
"codeIndexOpenAiKey",
"codeIndexQdrantApiKey",
"codebaseIndexOpenAiCompatibleApiKey",
])

export const isSecretStateKey = (key: string): key is Keys<SecretState> =>
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,8 @@ export const PROVIDER_SETTINGS_KEYS = keysOf<ProviderSettings>()([
// Code Index
"codeIndexOpenAiKey",
"codeIndexQdrantApiKey",
"codebaseIndexOpenAiCompatibleBaseUrl",
"codebaseIndexOpenAiCompatibleApiKey",
// Reasoning
"enableReasoningEffort",
"reasoningEffort",
Expand Down
171 changes: 171 additions & 0 deletions src/services/code-index/__tests__/config-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,44 @@ describe("CodeIndexConfigManager", () => {
})
})

it("should load OpenAI Compatible configuration from globalState and secrets", async () => {
const mockGlobalState = {
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderBaseUrl: "",
codebaseIndexEmbedderModelId: "text-embedding-3-large",
}
mockContextProxy.getGlobalState.mockImplementation((key: string) => {
if (key === "codebaseIndexConfig") return mockGlobalState
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
return undefined
})
mockContextProxy.getSecret.mockImplementation((key: string) => {
if (key === "codeIndexQdrantApiKey") return "test-qdrant-key"
if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-openai-compatible-key"
return undefined
})

const result = await configManager.loadConfiguration()

expect(result.currentConfig).toEqual({
isEnabled: true,
isConfigured: true,
embedderProvider: "openai-compatible",
modelId: "text-embedding-3-large",
openAiOptions: { openAiNativeApiKey: "" },
ollamaOptions: { ollamaBaseUrl: "" },
openAiCompatibleOptions: {
baseUrl: "https://api.example.com/v1",
apiKey: "test-openai-compatible-key",
},
qdrantUrl: "http://qdrant.local",
qdrantApiKey: "test-qdrant-key",
searchMinScore: 0.4,
})
})

it("should detect restart requirement when provider changes", async () => {
// Initial state - properly configured
mockContextProxy.getGlobalState.mockReturnValue({
Expand Down Expand Up @@ -270,6 +308,76 @@ describe("CodeIndexConfigManager", () => {
expect(result.requiresRestart).toBe(true)
})

it("should handle OpenAI Compatible configuration changes", async () => {
// Initial state
mockContextProxy.getGlobalState.mockImplementation((key: string) => {
if (key === "codebaseIndexConfig") {
return {
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderModelId: "text-embedding-3-small",
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://old-api.example.com/v1"
return undefined
})
mockContextProxy.getSecret.mockImplementation((key: string) => {
if (key === "codebaseIndexOpenAiCompatibleApiKey") return "old-api-key"
return undefined
})

await configManager.loadConfiguration()

// Change OpenAI Compatible base URL
mockContextProxy.getGlobalState.mockImplementation((key: string) => {
if (key === "codebaseIndexConfig") {
return {
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderModelId: "text-embedding-3-small",
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://new-api.example.com/v1"
return undefined
})

const result = await configManager.loadConfiguration()
expect(result.requiresRestart).toBe(true)
})

it("should handle OpenAI Compatible API key changes", async () => {
// Initial state
mockContextProxy.getGlobalState.mockImplementation((key: string) => {
if (key === "codebaseIndexConfig") {
return {
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
codebaseIndexEmbedderModelId: "text-embedding-3-small",
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
return undefined
})
mockContextProxy.getSecret.mockImplementation((key: string) => {
if (key === "codebaseIndexOpenAiCompatibleApiKey") return "old-api-key"
return undefined
})

await configManager.loadConfiguration()

// Change OpenAI Compatible API key
mockContextProxy.getSecret.mockImplementation((key: string) => {
if (key === "codebaseIndexOpenAiCompatibleApiKey") return "new-api-key"
return undefined
})

const result = await configManager.loadConfiguration()
expect(result.requiresRestart).toBe(true)
})

it("should not require restart when disabled remains disabled", async () => {
// Initial state - disabled but configured
mockContextProxy.getGlobalState.mockReturnValue({
Expand Down Expand Up @@ -448,6 +556,69 @@ describe("CodeIndexConfigManager", () => {
expect(configManager.isFeatureConfigured).toBe(true)
})

it("should validate OpenAI Compatible configuration correctly", async () => {
mockContextProxy.getGlobalState.mockImplementation((key: string) => {
if (key === "codebaseIndexConfig") {
return {
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
return undefined
})
mockContextProxy.getSecret.mockImplementation((key: string) => {
if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-api-key"
return undefined
})

await configManager.loadConfiguration()
expect(configManager.isFeatureConfigured).toBe(true)
})

it("should return false when OpenAI Compatible base URL is missing", async () => {
mockContextProxy.getGlobalState.mockImplementation((key: string) => {
if (key === "codebaseIndexConfig") {
return {
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return ""
return undefined
})
mockContextProxy.getSecret.mockImplementation((key: string) => {
if (key === "codebaseIndexOpenAiCompatibleApiKey") return "test-api-key"
return undefined
})

await configManager.loadConfiguration()
expect(configManager.isFeatureConfigured).toBe(false)
})

it("should return false when OpenAI Compatible API key is missing", async () => {
mockContextProxy.getGlobalState.mockImplementation((key: string) => {
if (key === "codebaseIndexConfig") {
return {
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai-compatible",
}
}
if (key === "codebaseIndexOpenAiCompatibleBaseUrl") return "https://api.example.com/v1"
return undefined
})
mockContextProxy.getSecret.mockImplementation((key: string) => {
if (key === "codebaseIndexOpenAiCompatibleApiKey") return ""
return undefined
})

await configManager.loadConfiguration()
expect(configManager.isFeatureConfigured).toBe(false)
})

it("should return false when required values are missing", async () => {
mockContextProxy.getGlobalState.mockReturnValue({
codebaseIndexEnabled: true,
Expand Down
Loading