Move codebase indexing out of experimental (#5481)

This commit is contained in:
Daniel 2025-07-08 22:07:36 -05:00 • committed by GitHub
parent cb4652e45e
commit 09e348e21c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 67 additions and 154 deletions

View file

@ -1510,7 +1510,7 @@ export class ClineProvider
customCondensingPrompt,
codebaseIndexModels: codebaseIndexModels ?? EMBEDDING_MODEL_PROFILES,
codebaseIndexConfig: codebaseIndexConfig ?? {
codebaseIndexEnabled: false,
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://localhost:6333",
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderBaseUrl: "",
@ -1668,7 +1668,7 @@ export class ClineProvider
customCondensingPrompt: stateValues.customCondensingPrompt,
codebaseIndexModels: stateValues.codebaseIndexModels ?? EMBEDDING_MODEL_PROFILES,
codebaseIndexConfig: stateValues.codebaseIndexConfig ?? {
codebaseIndexEnabled: false,
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://localhost:6333",
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderBaseUrl: "",

View file

@ -501,7 +501,7 @@ describe("ClineProvider", () => {
alwaysAllowReadOnlyOutsideWorkspace: false,
alwaysAllowWrite: false,
codebaseIndexConfig: {
codebaseIndexEnabled: false,
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "",
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderBaseUrl: "",

View file

@ -1221,21 +1221,6 @@ export const webviewMessageHandler = async (
break
case "browserToolEnabled":
await updateGlobalState("browserToolEnabled", message.bool ?? true)
await provider.postStateToWebview()
break
case "codebaseIndexEnabled":
// Update the codebaseIndexConfig with the new enabled state
const currentCodebaseConfig = getGlobalState("codebaseIndexConfig") || {}
await updateGlobalState("codebaseIndexConfig", {
...currentCodebaseConfig,
codebaseIndexEnabled: message.bool ?? false,
})
// Notify the code index manager about the change
if (provider.codeIndexManager) {
await provider.codeIndexManager.handleSettingsChange()
}
await provider.postStateToWebview()
break
case "language":

View file

@ -32,7 +32,7 @@ describe("CodeIndexConfigManager", () => {
describe("constructor", () => {
it("should initialize with ContextProxy", () => {
expect(configManager).toBeDefined()
expect(configManager.isFeatureEnabled).toBe(false)
expect(configManager.isFeatureEnabled).toBe(true)
expect(configManager.currentEmbedderProvider).toBe("openai")
})
})
@ -45,7 +45,6 @@ describe("CodeIndexConfigManager", () => {
const result = await configManager.loadConfiguration()
expect(result.currentConfig).toEqual({
isEnabled: false,
isConfigured: false,
embedderProvider: "openai",
modelId: undefined,
@ -77,7 +76,6 @@ describe("CodeIndexConfigManager", () => {
const result = await configManager.loadConfiguration()
expect(result.currentConfig).toEqual({
isEnabled: true,
isConfigured: true,
embedderProvider: "openai",
modelId: "text-embedding-3-large",
@ -111,7 +109,6 @@ describe("CodeIndexConfigManager", () => {
const result = await configManager.loadConfiguration()
expect(result.currentConfig).toEqual({
isEnabled: true,
isConfigured: true,
embedderProvider: "openai-compatible",
modelId: "text-embedding-3-large",
@ -149,7 +146,6 @@ describe("CodeIndexConfigManager", () => {
const result = await configManager.loadConfiguration()
expect(result.currentConfig).toEqual({
isEnabled: true,
isConfigured: true,
embedderProvider: "openai-compatible",
modelId: "custom-model",
@ -188,7 +184,6 @@ describe("CodeIndexConfigManager", () => {
const result = await configManager.loadConfiguration()
expect(result.currentConfig).toEqual({
isEnabled: true,
isConfigured: true,
embedderProvider: "openai-compatible",
modelId: "custom-model",
@ -227,7 +222,6 @@ describe("CodeIndexConfigManager", () => {
const result = await configManager.loadConfiguration()
expect(result.currentConfig).toEqual({
isEnabled: true,
isConfigured: true,
embedderProvider: "openai-compatible",
modelId: "custom-model",
@ -326,14 +320,14 @@ describe("CodeIndexConfigManager", () => {
})
it("should detect restart requirement when transitioning to enabled+configured", async () => {
// Initial state - disabled
// Initial state - enabled but not configured
mockContextProxy.getGlobalState.mockReturnValue({
codebaseIndexEnabled: false,
codebaseIndexEnabled: true,
})
await configManager.loadConfiguration()
// Enable and configure
// Configure the feature
mockContextProxy.getGlobalState.mockReturnValue({
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://qdrant.local",
@ -689,29 +683,28 @@ describe("CodeIndexConfigManager", () => {
expect(result.requiresRestart).toBe(true)
})
it("should not require restart when disabled remains disabled", async () => {
// Initial state - disabled but configured
it("should require restart when enabled and provider changes even if unconfigured", async () => {
// Initial state - enabled but not configured (missing API key)
mockContextProxy.getGlobalState.mockReturnValue({
codebaseIndexEnabled: false,
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai",
})
setupSecretMocks({
codeIndexOpenAiKey: "test-key",
})
setupSecretMocks({})
await configManager.loadConfiguration()
// Still disabled but change other settings
// Still enabled but change provider while remaining unconfigured
mockContextProxy.getGlobalState.mockReturnValue({
codebaseIndexEnabled: false,
codebaseIndexQdrantUrl: "http://different-qdrant.local",
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "ollama",
codebaseIndexEmbedderBaseUrl: "http://ollama.local",
})
const result = await configManager.loadConfiguration()
expect(result.requiresRestart).toBe(false)
// Should require restart because provider changed while enabled
expect(result.requiresRestart).toBe(true)
})
it("should not require restart when unconfigured remains unconfigured", async () => {
@ -970,7 +963,7 @@ describe("CodeIndexConfigManager", () => {
it("should not require restart when API keys transition from undefined to empty string", async () => {
// Initial state with undefined API keys
mockContextProxy.getGlobalState.mockReturnValue({
codebaseIndexEnabled: false, // Start disabled to avoid restart due to enable+configure
codebaseIndexEnabled: true, // Always enabled now
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai",
})
@ -1208,7 +1201,6 @@ describe("CodeIndexConfigManager", () => {
it("should return correct configuration via getConfig", () => {
const config = configManager.getConfig()
expect(config).toEqual({
isEnabled: true,
isConfigured: true,
embedderProvider: "openai",
modelId: "text-embedding-3-large",
@ -1267,7 +1259,7 @@ describe("CodeIndexConfigManager", () => {
it("should properly initialize with current config to prevent false restarts", async () => {
// Setup configuration
mockContextProxy.getGlobalState.mockReturnValue({
codebaseIndexEnabled: false, // Start disabled to avoid transition restart
codebaseIndexEnabled: true, // Always enabled now
codebaseIndexQdrantUrl: "http://qdrant.local",
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderModelId: "text-embedding-3-small",

View file

@ -81,7 +81,6 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
isFeatureConfigured: true,
isFeatureEnabled: true,
getConfig: vi.fn().mockReturnValue({
isEnabled: true,
isConfigured: true,
embedderProvider: "openai",
modelId: "text-embedding-3-small",
@ -149,7 +148,6 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
isFeatureConfigured: true,
isFeatureEnabled: true,
getConfig: vi.fn().mockReturnValue({
isEnabled: true,
isConfigured: true,
embedderProvider: "openai",
modelId: "text-embedding-3-small",
@ -276,7 +274,6 @@ describe("CodeIndexManager - handleSettingsChange regression", () => {
isFeatureConfigured: true,
isFeatureEnabled: true,
getConfig: vitest.fn().mockReturnValue({
isEnabled: true,
isConfigured: true,
embedderProvider: "openai",
modelId: "text-embedding-3-small",

View file

@ -10,7 +10,6 @@ import { getDefaultModelId, getModelDimension, getModelScoreThreshold } from "..
* Handles loading, validating, and providing access to configuration values.
*/
export class CodeIndexConfigManager {
private isEnabled: boolean = false
private embedderProvider: EmbedderProvider = "openai"
private modelId?: string
private modelDimension?: number
@ -42,7 +41,7 @@ export class CodeIndexConfigManager {
private _loadAndSetConfiguration(): void {
// Load configuration from storage
const codebaseIndexConfig = this.contextProxy?.getGlobalState("codebaseIndexConfig") ?? {
codebaseIndexEnabled: false,
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://localhost:6333",
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderBaseUrl: "",
@ -69,7 +68,7 @@ export class CodeIndexConfigManager {
const geminiApiKey = this.contextProxy?.getSecret("codebaseIndexGeminiApiKey") ?? ""
// Update instance variables with configuration
this.isEnabled = codebaseIndexEnabled || false
// Note: codebaseIndexEnabled is no longer used as the feature is always enabled
this.qdrantUrl = codebaseIndexQdrantUrl
this.qdrantApiKey = qdrantApiKey ?? ""
this.searchMinScore = codebaseIndexSearchMinScore
@ -127,7 +126,6 @@ export class CodeIndexConfigManager {
public async loadConfiguration(): Promise<{
configSnapshot: PreviousConfigSnapshot
currentConfig: {
isEnabled: boolean
isConfigured: boolean
embedderProvider: EmbedderProvider
modelId?: string
@ -144,7 +142,7 @@ export class CodeIndexConfigManager {
}> {
// Capture the ACTUAL previous state before loading new configuration
const previousConfigSnapshot: PreviousConfigSnapshot = {
enabled: this.isEnabled,
enabled: true, // Feature is always enabled
configured: this.isConfigured(),
embedderProvider: this.embedderProvider,
modelId: this.modelId,
@ -169,7 +167,6 @@ export class CodeIndexConfigManager {
return {
configSnapshot: previousConfigSnapshot,
currentConfig: {
isEnabled: this.isEnabled,
isConfigured: this.isConfigured(),
embedderProvider: this.embedderProvider,
modelId: this.modelId,
@ -246,66 +243,62 @@ export class CodeIndexConfigManager {
const prevQdrantUrl = prev?.qdrantUrl ?? ""
const prevQdrantApiKey = prev?.qdrantApiKey ?? ""
// 1. Transition from disabled/unconfigured to enabled+configured
if ((!prevEnabled || !prevConfigured) && this.isEnabled && nowConfigured) {
// 1. Transition from unconfigured to configured
// Since the feature is always enabled, we only check configuration status
if (!prevConfigured && nowConfigured) {
return true
}
// 2. If was disabled and still is, no restart needed
if (!prevEnabled && !this.isEnabled) {
return false
}
// 3. If wasn't ready before and isn't ready now, no restart needed
if (!prevConfigured && !nowConfigured) {
return false
}
// 4. CRITICAL CHANGES - Always restart for these
if (this.isEnabled || prevEnabled) {
// Provider change
if (prevProvider !== this.embedderProvider) {
return true
}
// Since feature is always enabled, we always check for critical changes
// Authentication changes (API keys)
const currentOpenAiKey = this.openAiOptions?.openAiNativeApiKey ?? ""
const currentOllamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl ?? ""
const currentOpenAiCompatibleBaseUrl = this.openAiCompatibleOptions?.baseUrl ?? ""
const currentOpenAiCompatibleApiKey = this.openAiCompatibleOptions?.apiKey ?? ""
const currentModelDimension = this.modelDimension
const currentGeminiApiKey = this.geminiOptions?.apiKey ?? ""
const currentQdrantUrl = this.qdrantUrl ?? ""
const currentQdrantApiKey = this.qdrantApiKey ?? ""
// Provider change
if (prevProvider !== this.embedderProvider) {
return true
}
if (prevOpenAiKey !== currentOpenAiKey) {
return true
}
// Authentication changes (API keys)
const currentOpenAiKey = this.openAiOptions?.openAiNativeApiKey ?? ""
const currentOllamaBaseUrl = this.ollamaOptions?.ollamaBaseUrl ?? ""
const currentOpenAiCompatibleBaseUrl = this.openAiCompatibleOptions?.baseUrl ?? ""
const currentOpenAiCompatibleApiKey = this.openAiCompatibleOptions?.apiKey ?? ""
const currentModelDimension = this.modelDimension
const currentGeminiApiKey = this.geminiOptions?.apiKey ?? ""
const currentQdrantUrl = this.qdrantUrl ?? ""
const currentQdrantApiKey = this.qdrantApiKey ?? ""
if (prevOllamaBaseUrl !== currentOllamaBaseUrl) {
return true
}
if (prevOpenAiKey !== currentOpenAiKey) {
return true
}
if (
prevOpenAiCompatibleBaseUrl !== currentOpenAiCompatibleBaseUrl ||
prevOpenAiCompatibleApiKey !== currentOpenAiCompatibleApiKey
) {
return true
}
if (prevOllamaBaseUrl !== currentOllamaBaseUrl) {
return true
}
// Check for model dimension changes (generic for all providers)
if (prevModelDimension !== currentModelDimension) {
return true
}
if (
prevOpenAiCompatibleBaseUrl !== currentOpenAiCompatibleBaseUrl ||
prevOpenAiCompatibleApiKey !== currentOpenAiCompatibleApiKey
) {
return true
}
if (prevQdrantUrl !== currentQdrantUrl || prevQdrantApiKey !== currentQdrantApiKey) {
return true
}
// Check for model dimension changes (generic for all providers)
if (prevModelDimension !== currentModelDimension) {
return true
}
// Vector dimension changes (still important for compatibility)
if (this._hasVectorDimensionChanged(prevProvider, prev?.modelId)) {
return true
}
if (prevQdrantUrl !== currentQdrantUrl || prevQdrantApiKey !== currentQdrantApiKey) {
return true
}
// Vector dimension changes (still important for compatibility)
if (this._hasVectorDimensionChanged(prevProvider, prev?.modelId)) {
return true
}
return false
@ -342,7 +335,6 @@ export class CodeIndexConfigManager {
*/
public getConfig(): CodeIndexConfig {
return {
isEnabled: this.isEnabled,
isConfigured: this.isConfigured(),
embedderProvider: this.embedderProvider,
modelId: this.modelId,
@ -362,7 +354,7 @@ export class CodeIndexConfigManager {
* Gets whether the code indexing feature is enabled
*/
public get isFeatureEnabled(): boolean {
return this.isEnabled
return true
}
/**

View file

@ -5,7 +5,6 @@ import { EmbedderProvider } from "./manager"
* Configuration state for the code indexing feature
*/
export interface CodeIndexConfig {
isEnabled: boolean
isConfigured: boolean
embedderProvider: EmbedderProvider
modelId?: string

View file

@ -81,7 +81,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
// Default settings template
const getDefaultSettings = (): LocalCodeIndexSettings => ({
codebaseIndexEnabled: false,
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "",
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderBaseUrl: "",
@ -111,7 +111,7 @@ export const CodeIndexPopover: React.FC<CodeIndexPopoverProps> = ({
useEffect(() => {
if (codebaseIndexConfig) {
const settings = {
codebaseIndexEnabled: codebaseIndexConfig.codebaseIndexEnabled || false,
codebaseIndexEnabled: codebaseIndexConfig.codebaseIndexEnabled ?? true,
codebaseIndexQdrantUrl: codebaseIndexConfig.codebaseIndexQdrantUrl || "",
codebaseIndexEmbedderProvider: codebaseIndexConfig.codebaseIndexEmbedderProvider || "openai",
codebaseIndexEmbedderBaseUrl: codebaseIndexConfig.codebaseIndexEmbedderBaseUrl || "",

View file

@ -1,40 +1,26 @@
import { HTMLAttributes } from "react"
import { FlaskConical } from "lucide-react"
import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react"
import { Trans } from "react-i18next"
import type { Experiments, CodebaseIndexConfig, CodebaseIndexModels } from "@roo-code/types"
import type { Experiments } from "@roo-code/types"
import { EXPERIMENT_IDS, experimentConfigsMap } from "@roo/experiments"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { cn } from "@src/lib/utils"
import { buildDocLink } from "@src/utils/docLinks"
import { SetExperimentEnabled } from "./types"
import { SectionHeader } from "./SectionHeader"
import { Section } from "./Section"
import { ExperimentalFeature } from "./ExperimentalFeature"
import { SetCachedStateField } from "./types"
type ExperimentalSettingsProps = HTMLAttributes<HTMLDivElement> & {
experiments: Experiments
setExperimentEnabled: SetExperimentEnabled
// CodeIndexSettings props
codebaseIndexModels: CodebaseIndexModels | undefined
codebaseIndexConfig: CodebaseIndexConfig | undefined
// For codebase index enabled toggle
codebaseIndexEnabled?: boolean
setCachedStateField?: SetCachedStateField<any>
}
export const ExperimentalSettings = ({
experiments,
setExperimentEnabled,
codebaseIndexModels,
codebaseIndexConfig,
codebaseIndexEnabled,
setCachedStateField,
className,
...props
}: ExperimentalSettingsProps) => {
@ -79,32 +65,6 @@ export const ExperimentalSettings = ({
/>
)
})}
{/* Codebase Indexing Enable/Disable Toggle */}
<div className="mt-4">
<div className="flex items-center gap-2">
<VSCodeCheckbox
checked={codebaseIndexEnabled || false}
onChange={(e: any) => {
const newEnabledState = e.target.checked
if (setCachedStateField && codebaseIndexConfig) {
setCachedStateField("codebaseIndexConfig", {
...codebaseIndexConfig,
codebaseIndexEnabled: newEnabledState,
})
}
}}>
<span className="font-medium">{t("settings:codeIndex.enableLabel")}</span>
</VSCodeCheckbox>
</div>
<p className="text-vscode-descriptionForeground text-sm mt-1 ml-6">
<Trans i18nKey="settings:codeIndex.enableDescription">
<VSCodeLink
href={buildDocLink("features/experimental/codebase-indexing", "settings")}
style={{ display: "inline" }}></VSCodeLink>
</Trans>
</p>
</div>
</Section>
</div>
)

View file

@ -170,8 +170,6 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
maxConcurrentFileReads,
condensingApiConfigId,
customCondensingPrompt,
codebaseIndexConfig,
codebaseIndexModels,
customSupportPrompts,
profileThresholds,
alwaysAllowFollowupQuestions,
@ -322,9 +320,6 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
vscode.postMessage({ type: "updateSupportPrompt", values: customSupportPrompts || {} })
vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting })
if (codebaseIndexConfig) {
vscode.postMessage({ type: "codebaseIndexEnabled", bool: codebaseIndexConfig.codebaseIndexEnabled })
}
vscode.postMessage({ type: "profileThresholds", values: profileThresholds })
setChangeDetected(false)
}
@ -691,14 +686,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
{/* Experimental Section */}
{activeTab === "experimental" && (
<ExperimentalSettings
setExperimentEnabled={setExperimentEnabled}
experiments={experiments}
codebaseIndexModels={codebaseIndexModels}
codebaseIndexConfig={codebaseIndexConfig}
codebaseIndexEnabled={codebaseIndexConfig?.codebaseIndexEnabled}
setCachedStateField={setCachedStateField}
/>
<ExperimentalSettings setExperimentEnabled={setExperimentEnabled} experiments={experiments} />
)}
{/* Language Section */}

View file

@ -214,7 +214,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
autoCondenseContextPercent: 100,
profileThresholds: {},
codebaseIndexConfig: {
codebaseIndexEnabled: false,
codebaseIndexEnabled: true,
codebaseIndexQdrantUrl: "http://localhost:6333",
codebaseIndexEmbedderProvider: "openai",
codebaseIndexEmbedderBaseUrl: "",