Skip to content

Commit 1fa8d6c

Browse files
authored
Merge pull request #3334 from Kilo-Org/bmc/continue-start
Lite autocomplete implementation
2 parents d745c42 + cd471b9 commit 1fa8d6c

File tree

14 files changed

+1349
-52
lines changed

14 files changed

+1349
-52
lines changed

src/extension.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { initializeI18n } from "./i18n"
4545
import { registerGhostProvider } from "./services/ghost" // kilocode_change
4646
import { registerMainThreadForwardingLogger } from "./utils/fowardingLogger" // kilocode_change
4747
import { getKiloCodeWrapperProperties } from "./core/kilocode/wrapper" // kilocode_change
48+
import { registerAutocompleteProvider } from "./services/autocomplete" // kilocode_change
4849

4950
/**
5051
* Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@@ -321,6 +322,8 @@ export async function activate(context: vscode.ExtensionContext) {
321322
if (!kiloCodeWrapped) {
322323
// Only use autocomplete in VS Code
323324
registerGhostProvider(context, provider)
325+
// Experimental
326+
// registerAutocompleteProvider(context, provider)
324327
} else {
325328
// Only foward logs in Jetbrains
326329
registerMainThreadForwardingLogger(context)
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
import {
2+
AUTOCOMPLETE_PROVIDER_MODELS,
3+
AutocompleteProviderKey,
4+
defaultProviderUsabilityChecker,
5+
getKiloBaseUriFromToken,
6+
modelIdKeysByProvider,
7+
ProviderSettings,
8+
ProviderSettingsEntry,
9+
} from "@roo-code/types"
10+
import { ApiHandler, buildApiHandler } from "../../api"
11+
import { ProviderSettingsManager } from "../../core/config/ProviderSettingsManager"
12+
import { OpenRouterHandler } from "../../api/providers"
13+
import { ApiStreamChunk } from "../../api/transform/stream"
14+
import { ILLM, LLMOptions } from "../continuedev/core/index.js"
15+
import { DEFAULT_AUTOCOMPLETE_OPTS } from "../continuedev/core/util/parameters.js"
16+
import Mistral from "../continuedev/core/llm/llms/Mistral"
17+
import { OpenAI } from "../continuedev/core/llm/llms/OpenAI"
18+
19+
export class AutocompleteModel {
20+
private apiHandler: ApiHandler | null = null
21+
private profile: ProviderSettings | null = null
22+
public loaded = false
23+
24+
constructor(apiHandler: ApiHandler | null = null) {
25+
if (apiHandler) {
26+
this.apiHandler = apiHandler
27+
this.loaded = true
28+
}
29+
}
30+
private cleanup(): void {
31+
this.apiHandler = null
32+
this.profile = null
33+
this.loaded = false
34+
}
35+
36+
public async reload(providerSettingsManager: ProviderSettingsManager): Promise<boolean> {
37+
const profiles = await providerSettingsManager.listConfig()
38+
const supportedProviders = Object.keys(AUTOCOMPLETE_PROVIDER_MODELS) as Array<
39+
keyof typeof AUTOCOMPLETE_PROVIDER_MODELS
40+
>
41+
42+
this.cleanup()
43+
44+
// Check providers in order, but skip unusable ones (e.g., kilocode with zero balance)
45+
for (const provider of supportedProviders) {
46+
const selectedProfile = profiles.find(
47+
(x): x is typeof x & { apiProvider: string } => x?.apiProvider === provider,
48+
)
49+
if (selectedProfile) {
50+
const isUsable = await defaultProviderUsabilityChecker(provider, providerSettingsManager)
51+
if (!isUsable) continue
52+
53+
this.loadProfile(providerSettingsManager, selectedProfile, provider)
54+
this.loaded = true
55+
return true
56+
}
57+
}
58+
59+
this.loaded = true // we loaded, and found nothing, but we do not wish to reload
60+
return false
61+
}
62+
63+
public async loadProfile(
64+
providerSettingsManager: ProviderSettingsManager,
65+
selectedProfile: ProviderSettingsEntry,
66+
provider: keyof typeof AUTOCOMPLETE_PROVIDER_MODELS,
67+
): Promise<void> {
68+
this.profile = await providerSettingsManager.getProfile({
69+
id: selectedProfile.id,
70+
})
71+
72+
this.apiHandler = buildApiHandler({
73+
...this.profile,
74+
[modelIdKeysByProvider[provider]]: AUTOCOMPLETE_PROVIDER_MODELS[provider],
75+
})
76+
77+
if (this.apiHandler instanceof OpenRouterHandler) {
78+
await this.apiHandler.fetchModel()
79+
}
80+
}
81+
82+
/**
83+
* Creates an ILLM-compatible instance from provider settings for autocomplete.
84+
* Supports mistral, kilocode, openrouter, and bedrock providers.
85+
* Uses the current profile loaded in this.profile.
86+
*
87+
* @returns ILLM instance or null if configuration is invalid
88+
*/
89+
public getILLM(): ILLM | null {
90+
if (!this.profile?.apiProvider) {
91+
console.warn("[AutocompleteModel] No profile loaded")
92+
return null
93+
}
94+
95+
const provider = this.profile.apiProvider as AutocompleteProviderKey
96+
97+
try {
98+
// Extract provider-specific configuration
99+
const config = this.extractProviderConfig()
100+
if (!config) {
101+
console.warn(`[AutocompleteModel] Failed to extract config for provider: ${provider}`)
102+
return null
103+
}
104+
105+
// Build LLM options
106+
const llmOptions: LLMOptions = {
107+
model: config.model,
108+
apiKey: config.apiKey,
109+
apiBase: config.apiBase,
110+
contextLength: 32000, // Default for Codestral models
111+
completionOptions: {
112+
model: config.model,
113+
temperature: 0.2, // Lower temperature for more deterministic autocomplete
114+
maxTokens: 256, // Reasonable limit for code completions
115+
},
116+
autocompleteOptions: {
117+
...DEFAULT_AUTOCOMPLETE_OPTS,
118+
useCache: false, // Disable caching for autocomplete
119+
},
120+
uniqueId: `autocomplete-${provider}-${Date.now()}`,
121+
}
122+
123+
// Create appropriate LLM instance based on provider
124+
return this.createLLMInstance(provider, llmOptions)
125+
} catch (error) {
126+
console.error(`[AutocompleteModel] Error creating ILLM for provider ${provider}:`, error)
127+
return null
128+
}
129+
}
130+
131+
/**
132+
* Extracts provider-specific configuration (API key, base URL, model) from this.profile
133+
*/
134+
private extractProviderConfig(): { apiKey: string; apiBase: string; model: string } | null {
135+
if (!this.profile?.apiProvider) {
136+
return null
137+
}
138+
139+
const provider = this.profile.apiProvider as AutocompleteProviderKey
140+
const model = AUTOCOMPLETE_PROVIDER_MODELS[provider]
141+
142+
switch (provider) {
143+
case "mistral":
144+
if (!this.profile.mistralApiKey) {
145+
console.warn("[AutocompleteModel] Missing Mistral API key")
146+
return null
147+
}
148+
return {
149+
apiKey: this.profile.mistralApiKey,
150+
apiBase: this.profile.mistralCodestralUrl || "https://codestral.mistral.ai/v1/",
151+
model,
152+
}
153+
154+
case "kilocode":
155+
if (!this.profile.kilocodeToken) {
156+
console.warn("[AutocompleteModel] Missing Kilocode token")
157+
return null
158+
}
159+
return {
160+
apiKey: this.profile.kilocodeToken,
161+
apiBase: `${getKiloBaseUriFromToken(this.profile.kilocodeToken)}/openrouter/api/v1`,
162+
model,
163+
}
164+
165+
case "openrouter":
166+
if (!this.profile.openRouterApiKey) {
167+
console.warn("[AutocompleteModel] Missing OpenRouter API key")
168+
return null
169+
}
170+
return {
171+
apiKey: this.profile.openRouterApiKey,
172+
apiBase: this.profile.openRouterBaseUrl || "https://openrouter.ai/api/v1",
173+
model,
174+
}
175+
176+
case "bedrock":
177+
// Bedrock uses AWS credentials, not a simple API key
178+
// For now, return null as it requires more complex setup
179+
console.warn("[AutocompleteModel] Bedrock provider not yet supported for autocomplete")
180+
return null
181+
182+
default:
183+
console.warn(`[AutocompleteModel] Unsupported provider: ${provider}`)
184+
return null
185+
}
186+
}
187+
188+
/**
189+
* Creates the appropriate LLM instance based on provider type
190+
*/
191+
private createLLMInstance(provider: AutocompleteProviderKey, options: LLMOptions): ILLM | null {
192+
switch (provider) {
193+
case "mistral":
194+
return new Mistral(options)
195+
196+
case "kilocode":
197+
case "openrouter":
198+
// Both use OpenAI-compatible API
199+
return new OpenAI(options)
200+
201+
case "bedrock":
202+
// Bedrock would need a custom implementation
203+
return null
204+
205+
default:
206+
return null
207+
}
208+
}
209+
210+
/**
211+
* Generate response with streaming callback support
212+
*/
213+
public async generateResponse(
214+
systemPrompt: string,
215+
userPrompt: string,
216+
onChunk: (chunk: ApiStreamChunk) => void,
217+
): Promise<{
218+
cost: number
219+
inputTokens: number
220+
outputTokens: number
221+
cacheWriteTokens: number
222+
cacheReadTokens: number
223+
}> {
224+
if (!this.apiHandler) {
225+
console.error("API handler is not initialized")
226+
throw new Error("API handler is not initialized. Please check your configuration.")
227+
}
228+
229+
console.log("USED MODEL", this.apiHandler.getModel())
230+
231+
const stream = this.apiHandler.createMessage(systemPrompt, [
232+
{ role: "user", content: [{ type: "text", text: userPrompt }] },
233+
])
234+
235+
let cost = 0
236+
let inputTokens = 0
237+
let outputTokens = 0
238+
let cacheReadTokens = 0
239+
let cacheWriteTokens = 0
240+
241+
try {
242+
for await (const chunk of stream) {
243+
// Call the callback with each chunk
244+
onChunk(chunk)
245+
246+
// Track usage information
247+
if (chunk.type === "usage") {
248+
cost = chunk.totalCost ?? 0
249+
cacheReadTokens = chunk.cacheReadTokens ?? 0
250+
cacheWriteTokens = chunk.cacheWriteTokens ?? 0
251+
inputTokens = chunk.inputTokens ?? 0
252+
outputTokens = chunk.outputTokens ?? 0
253+
}
254+
}
255+
} catch (error) {
256+
console.error("Error streaming completion:", error)
257+
throw error
258+
}
259+
260+
return {
261+
cost,
262+
inputTokens,
263+
outputTokens,
264+
cacheWriteTokens,
265+
cacheReadTokens,
266+
}
267+
}
268+
269+
public getModelName(): string | null {
270+
if (!this.apiHandler) return null
271+
272+
return this.apiHandler.getModel().id ?? "unknown"
273+
}
274+
275+
public getProviderDisplayName(): string | null {
276+
if (!this.apiHandler) return null
277+
278+
const handler = this.apiHandler as any
279+
if (handler.providerName && typeof handler.providerName === "string") {
280+
return handler.providerName
281+
} else {
282+
return "unknown"
283+
}
284+
}
285+
286+
public hasValidCredentials(): boolean {
287+
return this.apiHandler !== null && this.loaded
288+
}
289+
}

0 commit comments

Comments
 (0)