Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
4 changes: 4 additions & 0 deletions packages/types/src/codebase-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const codebaseIndexConfigSchema = z.object({
"vercel-ai-gateway",
"bedrock",
"openrouter",
"semble",
])
.optional(),
codebaseIndexEmbedderBaseUrl: z.string().optional(),
Expand All @@ -50,6 +51,8 @@ export const codebaseIndexConfigSchema = z.object({
codebaseIndexBedrockProfile: z.string().optional(),
// OpenRouter specific fields
codebaseIndexOpenRouterSpecificProvider: z.string().optional(),
// Semble specific fields
codebaseIndexSemblePath: z.string().optional(),
})

export type CodebaseIndexConfig = z.infer<typeof codebaseIndexConfigSchema>
Expand All @@ -67,6 +70,7 @@ export const codebaseIndexModelsSchema = z.object({
"vercel-ai-gateway": z.record(z.string(), z.object({ dimension: z.number() })).optional(),
openrouter: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
bedrock: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
semble: z.record(z.string(), z.object({ dimension: z.number() })).optional(),
})

export type CodebaseIndexModels = z.infer<typeof codebaseIndexModelsSchema>
Expand Down
3 changes: 2 additions & 1 deletion packages/types/src/embedding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export type EmbedderProvider =
| "mistral"
| "vercel-ai-gateway"
| "bedrock"
| "openrouter" // Add other providers as needed.
| "openrouter"
| "semble" // Local hybrid search via semble CLI — no API keys or Qdrant required.

export interface EmbeddingModelProfile {
dimension: number
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ export interface WebviewMessage {
| "vercel-ai-gateway"
| "bedrock"
| "openrouter"
| "semble"
codebaseIndexEmbedderBaseUrl?: string
codebaseIndexEmbedderModelId: string
codebaseIndexEmbedderModelDimension?: number // Generic dimension for all providers
Expand All @@ -670,6 +671,7 @@ export interface WebviewMessage {
codebaseIndexSearchMaxResults?: number
codebaseIndexSearchMinScore?: number
codebaseIndexOpenRouterSpecificProvider?: string // OpenRouter provider routing
codebaseIndexSemblePath?: string // Path to the semble executable

// Secret settings
codeIndexOpenAiKey?: string
Expand Down
2 changes: 2 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2253,6 +2253,7 @@ export class ClineProvider
codebaseIndexBedrockRegion: codebaseIndexConfig?.codebaseIndexBedrockRegion,
codebaseIndexBedrockProfile: codebaseIndexConfig?.codebaseIndexBedrockProfile,
codebaseIndexOpenRouterSpecificProvider: codebaseIndexConfig?.codebaseIndexOpenRouterSpecificProvider,
codebaseIndexSemblePath: codebaseIndexConfig?.codebaseIndexSemblePath,
},
// Phase 1 cloud removal: do not let Cloud-auth MDM enforcement force login-only UI flows.
mdmCompliant: undefined,
Expand Down Expand Up @@ -2455,6 +2456,7 @@ export class ClineProvider
codebaseIndexBedrockProfile: stateValues.codebaseIndexConfig?.codebaseIndexBedrockProfile,
codebaseIndexOpenRouterSpecificProvider:
stateValues.codebaseIndexConfig?.codebaseIndexOpenRouterSpecificProvider,
codebaseIndexSemblePath: stateValues.codebaseIndexConfig?.codebaseIndexSemblePath,
},
profileThresholds: stateValues.profileThresholds ?? {},
lockApiConfigAcrossModes: this.context.workspaceState.get("lockApiConfigAcrossModes", false),
Expand Down
1 change: 1 addition & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2500,6 +2500,7 @@ export const webviewMessageHandler = async (
codebaseIndexSearchMaxResults: settings.codebaseIndexSearchMaxResults,
codebaseIndexSearchMinScore: settings.codebaseIndexSearchMinScore,
codebaseIndexOpenRouterSpecificProvider: settings.codebaseIndexOpenRouterSpecificProvider,
codebaseIndexSemblePath: settings.codebaseIndexSemblePath,
}

// Save global state first
Expand Down
27 changes: 27 additions & 0 deletions src/services/code-index/config-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export class CodeIndexConfigManager {
private vercelAiGatewayOptions?: { apiKey: string }
private bedrockOptions?: { region: string; profile?: string }
private openRouterOptions?: { apiKey: string; specificProvider?: string }
private semblePath?: string
private qdrantUrl?: string = "http://localhost:6333"
private qdrantApiKey?: string
private searchMinScore?: number
Expand Down Expand Up @@ -120,6 +121,8 @@ export class CodeIndexConfigManager {
this.embedderProvider = "bedrock"
} else if (codebaseIndexEmbedderProvider === "openrouter") {
this.embedderProvider = "openrouter"
} else if (codebaseIndexEmbedderProvider === "semble") {
this.embedderProvider = "semble"
} else {
this.embedderProvider = "openai"
}
Expand Down Expand Up @@ -148,6 +151,9 @@ export class CodeIndexConfigManager {
this.bedrockOptions = bedrockRegion
? { region: bedrockRegion, profile: bedrockProfile || undefined }
: undefined

// Semble path (optional — defaults to "semble" in the SembleProvider)
this.semblePath = codebaseIndexConfig.codebaseIndexSemblePath || undefined
}

/**
Expand Down Expand Up @@ -194,6 +200,7 @@ export class CodeIndexConfigManager {
openRouterSpecificProvider: this.openRouterOptions?.specificProvider ?? "",
qdrantUrl: this.qdrantUrl ?? "",
qdrantApiKey: this.qdrantApiKey ?? "",
semblePath: this.semblePath ?? "",
}

// Refresh secrets from VSCode storage to ensure we have the latest values
Expand Down Expand Up @@ -231,6 +238,11 @@ export class CodeIndexConfigManager {
* Checks if the service is properly configured based on the embedder type.
*/
public isConfigured(): boolean {
if (this.embedderProvider === "semble") {
// Semble requires no API keys or Qdrant — it's always configured
return true
}

if (this.embedderProvider === "openai") {
const openAiKey = this.openAiOptions?.openAiNativeApiKey
const qdrantUrl = this.qdrantUrl
Expand Down Expand Up @@ -405,6 +417,13 @@ export class CodeIndexConfigManager {
return true
}

// Semble path change requires restart to use the new executable
const prevSemblePath = prev?.semblePath ?? ""
const currentSemblePath = this.semblePath ?? ""
if (prevSemblePath !== currentSemblePath) {
return true
}

// Vector dimension changes (still important for compatibility)
if (this._hasVectorDimensionChanged(prevProvider, prev?.modelId)) {
return true
Expand Down Expand Up @@ -541,4 +560,12 @@ export class CodeIndexConfigManager {
public get currentSearchMaxResults(): number {
return this.searchMaxResults ?? DEFAULT_MAX_SEARCH_RESULTS
}

/**
* Gets the configured path to the semble executable.
* Returns undefined if not explicitly configured (provider will use default "semble").
*/
public get currentSemblePath(): string | undefined {
return this.semblePath
}
}
1 change: 1 addition & 0 deletions src/services/code-index/interfaces/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ export type PreviousConfigSnapshot = {
openRouterSpecificProvider?: string
qdrantUrl?: string
qdrantApiKey?: string
semblePath?: string
}
1 change: 1 addition & 0 deletions src/services/code-index/interfaces/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export type EmbedderProvider =
| "vercel-ai-gateway"
| "bedrock"
| "openrouter"
| "semble"

export interface IndexProgressUpdate {
systemStatus: IndexingState
Expand Down
73 changes: 66 additions & 7 deletions src/services/code-index/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CodeIndexServiceFactory } from "./service-factory"
import { CodeIndexSearchService } from "./search-service"
import { CodeIndexOrchestrator } from "./orchestrator"
import { CacheManager } from "./cache-manager"
import { SembleProvider } from "./semble"
import { RooIgnoreController } from "../../core/ignore/RooIgnoreController"
import fs from "fs/promises"
import ignore from "ignore"
Expand All @@ -27,6 +28,7 @@ export class CodeIndexManager {
private _orchestrator: CodeIndexOrchestrator | undefined
private _searchService: CodeIndexSearchService | undefined
private _cacheManager: CacheManager | undefined
private _sembleProvider: SembleProvider | undefined

// Flag to prevent race conditions during error recovery
private _isRecoveringFromError = false
Expand Down Expand Up @@ -125,6 +127,10 @@ export class CodeIndexManager {
}

private assertInitialized() {
if (this._sembleProvider) {
// When semble is active, we don't need orchestrator/searchService
return
}
if (!this._configManager || !this._orchestrator || !this._searchService || !this._cacheManager) {
throw new Error("CodeIndexManager not initialized. Call initialize() first.")
}
Expand All @@ -134,6 +140,9 @@ export class CodeIndexManager {
if (!this.isFeatureEnabled) {
return "Standby"
}
if (this._sembleProvider) {
return this._sembleProvider.state
}
this.assertInitialized()
return this._orchestrator!.state
}
Expand Down Expand Up @@ -173,6 +182,9 @@ export class CodeIndexManager {
if (this._orchestrator) {
this._orchestrator.stopWatcher()
}
if (this._sembleProvider) {
this._sembleProvider.stopIndexing()
}
return { requiresRestart }
}

Expand All @@ -196,19 +208,27 @@ export class CodeIndexManager {
}

// 6. Determine if Core Services Need Recreation
const needsServiceRecreation = !this._serviceFactory || requiresRestart
const needsServiceRecreation = (!this._serviceFactory && !this._sembleProvider) || requiresRestart

if (needsServiceRecreation) {
await this._recreateServices()
}

// 7. Handle Indexing Start/Restart
const shouldStartOrRestartIndexing =
requiresRestart ||
(needsServiceRecreation && (!this._orchestrator || this._orchestrator.state !== "Indexing"))
if (this._sembleProvider) {
// For semble, start indexing if needed
const shouldStartIndexing = requiresRestart || needsServiceRecreation
if (shouldStartIndexing) {
await this._sembleProvider.startIndexing()
}
} else {
const shouldStartOrRestartIndexing =
requiresRestart ||
(needsServiceRecreation && (!this._orchestrator || this._orchestrator.state !== "Indexing"))

if (shouldStartOrRestartIndexing) {
this._orchestrator?.startIndexing()
if (shouldStartOrRestartIndexing) {
this._orchestrator?.startIndexing()
}
}

return { requiresRestart }
Expand All @@ -226,6 +246,12 @@ export class CodeIndexManager {
return
}

// Delegate to semble provider if active
if (this._sembleProvider) {
await this._sembleProvider.startIndexing()
return
}

// Check if we're in error state and recover if needed
const currentStatus = this.getCurrentStatus()
if (currentStatus.systemStatus === "Error") {
Expand All @@ -244,6 +270,10 @@ export class CodeIndexManager {
* Stops any in-progress indexing operation and the file watcher.
*/
public stopIndexing(): void {
if (this._sembleProvider) {
this._sembleProvider.stopIndexing()
return
}
if (this._orchestrator) {
this._orchestrator.stopIndexing()
}
Expand Down Expand Up @@ -295,6 +325,7 @@ export class CodeIndexManager {
this._serviceFactory = undefined
this._orchestrator = undefined
this._searchService = undefined
this._sembleProvider = undefined

// Reset the flag after recovery is complete
this._isRecoveringFromError = false
Expand All @@ -306,6 +337,10 @@ export class CodeIndexManager {
*/
public dispose(): void {
this.stopIndexing()
if (this._sembleProvider) {
this._sembleProvider.dispose()
this._sembleProvider = undefined
}
this._stateManager.dispose()
}

Expand All @@ -317,6 +352,10 @@ export class CodeIndexManager {
if (!this.isFeatureEnabled) {
return
}
if (this._sembleProvider) {
await this._sembleProvider.clearIndexData()
return
}
this.assertInitialized()
await this._orchestrator!.clearIndexData()
await this._cacheManager!.clearCacheFile()
Expand All @@ -338,6 +377,9 @@ export class CodeIndexManager {
if (!this.isFeatureEnabled) {
return []
}
if (this._sembleProvider) {
return this._sembleProvider.searchIndex(query, directoryPrefix)
}
this.assertInitialized()
return this._searchService!.searchIndex(query, directoryPrefix)
}
Expand All @@ -351,11 +393,28 @@ export class CodeIndexManager {
if (this._orchestrator) {
this.stopWatcher()
}
// Dispose existing semble provider if switching away
if (this._sembleProvider) {
this._sembleProvider.dispose()
this._sembleProvider = undefined
}
// Clear existing services to ensure clean state
this._orchestrator = undefined
this._searchService = undefined

// (Re)Initialize service factory
// Branch: if provider is "semble", create SembleProvider instead of external services
if (this._configManager!.currentEmbedderProvider === "semble") {
this._sembleProvider = new SembleProvider(
this.workspacePath,
this.context,
this._stateManager,
this._configManager!.currentSemblePath,
)
await this._sembleProvider.initialize()
return
}

// (Re)Initialize service factory for external providers
this._serviceFactory = new CodeIndexServiceFactory(
this._configManager!,
this.workspacePath,
Expand Down
Loading
Loading