Skip to content

Commit 16734d4

Browse files
Merge pull request #7 from alexandrevilain/feat/add-mistral
feat(providers): Add support for mistral
2 parents b1cfeb4 + 152f65d commit 16734d4

File tree

3 files changed

+106
-85
lines changed

3 files changed

+106
-85
lines changed

src/providers/providers.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ function languageModelProvider(providerId: ProviderID): LanguageModelProvider {
1616
case 'ovhcloud':
1717
case 'groq':
1818
case 'openai-compatible':
19+
case 'mistral':
20+
case 'mistral-codestral': // TODO: we should support FIM endpoint.
1921
return new OpenAICompatibleProvider();
2022
case 'ollama':
2123
return new OllamaProvider();
@@ -64,10 +66,19 @@ export const providers: Provider[] = [
6466
name: "OVHcloud",
6567
defaultBaseURL: "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1"
6668
},
67-
{
69+
{
6870
id: 'groq',
6971
name: "Groq",
7072
defaultBaseURL: "https://api.groq.com/openai/v1"
7173
},
72-
74+
{
75+
id: 'mistral',
76+
name: "Mistral",
77+
defaultBaseURL: "https://api.mistral.ai/v1"
78+
},
79+
{
80+
id: 'mistral-codestral',
81+
name: 'Codestral (mistral)',
82+
defaultBaseURL: "https://codestral.mistral.ai/v1"
83+
}
7384
];

src/types/provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type ProviderID = 'openai' | 'openrouter' | 'kilocode' | 'ollama' | 'ovhcloud' | 'groq' | 'openai-compatible';
1+
export type ProviderID = 'openai' | 'openrouter' | 'kilocode' | 'ollama' | 'ovhcloud' | 'groq' | 'mistral' | 'mistral-codestral' | 'openai-compatible';
22

33
export type Provider = {
44
id: ProviderID;

src/vscode/profileCommandProvider.ts

Lines changed: 92 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as vscode from 'vscode';
22
import { ProfileService } from '../services/profileService';
3-
import { ProfileWithAPIKey, ProviderID } from '../types';
3+
import { ProfileWithAPIKey, ProviderConnection, ProviderID } from '../types';
44
import { logger } from '../utils/logger';
55
import { listModelsFromProviderConnection, providers } from '../providers/providers';
66

@@ -17,7 +17,7 @@ export class ProfileCommandProvider {
1717
public async createProfile(): Promise<void> {
1818
try {
1919
// First, ask the user to select the provider.
20-
const availableProviders: { label: string; id: ProviderID }[] = providers.map(provider => ({ label: provider.name, id: provider.id}));
20+
const availableProviders: { label: string; id: ProviderID }[] = providers.map(provider => ({ label: provider.name, id: provider.id }));
2121

2222
const selectedProvider = await vscode.window.showQuickPick(availableProviders, {
2323
title: "Create New AI Profile - Step 1 of 5",
@@ -79,85 +79,19 @@ export class ProfileCommandProvider {
7979
apiKey = inputApiKey;
8080
}
8181

82-
// Load models for the selected provider.
83-
const qp = vscode.window.createQuickPick();
84-
qp.title = 'Create New AI Profile - Step 4 of 5';
85-
qp.placeholder = 'Loading available models from your provider...';
86-
qp.items = [{
87-
label: '$(loading~spin) Loading models...',
88-
description: 'This may take a few seconds',
89-
detail: 'Connecting to your AI provider to fetch available models'
90-
}];
91-
qp.ignoreFocusOut = true;
92-
qp.busy = true;
93-
qp.show();
94-
95-
let models: any[] = [];
96-
try {
97-
models = await listModelsFromProviderConnection({
82+
let selectedModelId = '';
83+
if (selectedProvider.id === 'mistral-codestral') {
84+
selectedModelId = 'codestral-latest';
85+
} else {
86+
selectedModelId = await this.askForModel({
9887
id: selectedProvider.id,
9988
baseURL,
10089
apiKey
10190
});
102-
} catch (error) {
103-
logger.error('Error loading models:', error);
104-
qp.items = [{
105-
label: '$(error) Connection Failed',
106-
description: 'Could not connect to your AI provider',
107-
detail: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
108-
}];
109-
qp.placeholder = 'Connection failed - please check your settings';
110-
qp.busy = false;
111-
112-
// Wait for user acknowledgment before closing.
113-
await new Promise<void>((resolve) => {
114-
qp.onDidHide(() => {
115-
qp.dispose();
116-
resolve();
117-
});
118-
});
119-
return;
12091
}
12192

122-
// Replace items as models are now loaded.
123-
if (models.length === 0) {
124-
qp.items = [{
125-
label: '$(warning) No models found',
126-
description: 'Your provider connection works, but no models are available',
127-
detail: 'This might be normal for some providers. You can continue with a custom model ID.'
128-
}];
129-
qp.placeholder = 'No models found - you may need to specify a model manually';
130-
} else {
131-
qp.items = models.map(model => ({
132-
label: model.name,
133-
id: model.id,
134-
description: `Model ID: ${model.id}`,
135-
detail: 'Select this model for code completions'
136-
}));
137-
qp.placeholder = `Choose from ${models.length} available models`;
138-
}
139-
qp.busy = false;
140-
141-
// Wait for user to select a model.
142-
const modelId = await new Promise<string>((resolve) => {
143-
qp.onDidAccept(() => {
144-
const selectedItems = qp.selectedItems;
145-
if (selectedItems.length > 0) {
146-
const modelName = selectedItems[0].label;
147-
const selectedModelId = models.find(m => m.name === modelName)?.id || '';
148-
qp.hide();
149-
resolve(selectedModelId);
150-
}
151-
});
152-
153-
qp.onDidHide(() => {
154-
qp.dispose();
155-
resolve(''); // User cancelled or closed the picker
156-
});
157-
});
158-
159-
if (!modelId) {
160-
return; // User cancelled.
93+
if (!selectedModelId) {
94+
return;
16195
}
16296

16397
// Ask the user to set profile name.
@@ -224,7 +158,7 @@ export class ProfileCommandProvider {
224158
name: name.trim(),
225159
provider: selectedProvider.id,
226160
baseURL: baseURL.trim(),
227-
modelId: modelId.trim(),
161+
modelId: selectedModelId.trim(),
228162
apiKey: apiKey.trim()
229163
};
230164

@@ -234,7 +168,7 @@ export class ProfileCommandProvider {
234168
// Set as default if requested
235169
if (shouldSetAsDefault) {
236170
try {
237-
await this.profileService.setActiveProfileId(newProfile.id);
171+
await this.profileService.setActiveProfileId(newProfile.id);
238172
logger.info(`Profile set as default: ${newProfile.name}`);
239173
vscode.window.showInformationMessage(`Profile "${newProfile.name}" created and activated! TabCoder is ready to use.`);
240174
} catch (error) {
@@ -251,13 +185,89 @@ export class ProfileCommandProvider {
251185
'Try Again',
252186
'Cancel'
253187
);
254-
188+
255189
if (retry === 'Try Again') {
256190
await this.createProfile();
257191
}
258192
}
259193
}
260194

195+
async askForModel(conn: ProviderConnection): Promise<string> {
196+
// Load models for the selected provider.
197+
const qp = vscode.window.createQuickPick();
198+
qp.title = 'Create New AI Profile - Step 4 of 5';
199+
qp.placeholder = 'Loading available models from your provider...';
200+
qp.items = [{
201+
label: '$(loading~spin) Loading models...',
202+
description: 'This may take a few seconds',
203+
detail: 'Connecting to your AI provider to fetch available models'
204+
}];
205+
qp.ignoreFocusOut = true;
206+
qp.busy = true;
207+
qp.show();
208+
209+
let models: any[] = [];
210+
try {
211+
models = await listModelsFromProviderConnection(conn);
212+
} catch (error) {
213+
logger.error('Error loading models:', error);
214+
qp.items = [{
215+
label: '$(error) Connection Failed',
216+
description: 'Could not connect to your AI provider',
217+
detail: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`
218+
}];
219+
qp.placeholder = 'Connection failed - please check your settings';
220+
qp.busy = false;
221+
222+
// Wait for user acknowledgment before closing.
223+
await new Promise<void>((resolve) => {
224+
qp.onDidHide(() => {
225+
qp.dispose();
226+
resolve();
227+
});
228+
});
229+
230+
return '';
231+
}
232+
233+
// Replace items as models are now loaded.
234+
if (models.length === 0) {
235+
qp.items = [{
236+
label: '$(warning) No models found',
237+
description: 'Your provider connection works, but no models are available',
238+
detail: 'This might be normal for some providers. You can continue with a custom model ID.'
239+
}];
240+
qp.placeholder = 'No models found - you may need to specify a model manually';
241+
} else {
242+
qp.items = models.map(model => ({
243+
label: model.name,
244+
id: model.id,
245+
description: `Model ID: ${model.id}`,
246+
detail: 'Select this model for code completions'
247+
}));
248+
qp.placeholder = `Choose from ${models.length} available models`;
249+
}
250+
qp.busy = false;
251+
252+
// Wait for user to select a model.
253+
return await new Promise<string>((resolve) => {
254+
qp.onDidAccept(() => {
255+
const selectedItems = qp.selectedItems;
256+
if (selectedItems.length > 0) {
257+
const modelName = selectedItems[0].label;
258+
const selectedModelId = models.find(m => m.name === modelName)?.id || '';
259+
qp.hide();
260+
resolve(selectedModelId);
261+
}
262+
});
263+
264+
qp.onDidHide(() => {
265+
qp.dispose();
266+
resolve(''); // User cancelled or closed the picker
267+
});
268+
});
269+
}
270+
261271
/**
262272
* Command: Set active configuration profile
263273
*/
@@ -278,7 +288,7 @@ export class ProfileCommandProvider {
278288
}
279289

280290
const currentActiveProfile = await this.profileService.getActiveProfile();
281-
291+
282292
// Create quick pick items
283293
const profileItems = profiles.map(profile => ({
284294
label: `${profile.id === currentActiveProfile?.id ? '$(check) ' : ''}${profile.name}`,
@@ -336,7 +346,7 @@ export class ProfileCommandProvider {
336346
}
337347

338348
const currentActiveProfile = await this.profileService.getActiveProfile();
339-
349+
340350
// Create quick pick items
341351
const profileItems = profiles.map(profile => ({
342352
label: `${profile.id === currentActiveProfile?.id ? '$(warning) ' : ''}${profile.name}`,
@@ -364,7 +374,7 @@ export class ProfileCommandProvider {
364374
const warningMessage = isActiveProfile
365375
? `You are about to delete "${profileName}", which is currently your active profile.\n\nThis will:\n• Remove all configuration data\n• Disable TabCoder completions\n• Cannot be undone\n\nAre you sure?`
366376
: `Are you sure you want to delete the profile "${profileName}"?\n\nThis action cannot be undone.`;
367-
377+
368378
const result = await vscode.window.showWarningMessage(
369379
warningMessage,
370380
{ modal: true },
@@ -377,7 +387,7 @@ export class ProfileCommandProvider {
377387
}
378388

379389
await this.profileService.deleteProfile(selectedItem.profileId);
380-
390+
381391
if (isActiveProfile) {
382392
vscode.window.showInformationMessage(`Profile "${profileName}" deleted. TabCoder is now disabled.`);
383393
} else {

0 commit comments

Comments
 (0)