Skip to content

Commit 359fe73

Browse files
committed
Add model configuration feature to Cursor Whisper extension
- Introduced a new command to configure the OpenAI model for prompt transformation. - Updated package.json to include the new command and model options. - Enhanced VSCodeConfigRepository to manage the transformation model setting. - Modified OpenAIPromptTransformer to utilize the selected model during prompt transformation. - Updated pnpm-lock.yaml to include new dependencies for GitHub Copilot integration.
1 parent 1cf0dcd commit 359fe73

9 files changed

Lines changed: 349 additions & 5 deletions

File tree

cursor-whisper-0.1.0.vsix

1.11 KB
Binary file not shown.

package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@
5454
{
5555
"command": "cursor-whisper.configureApiKey",
5656
"title": "Cursor Whisper: Configure API Key"
57+
},
58+
{
59+
"command": "cursor-whisper.configureModel",
60+
"title": "Cursor Whisper: Configure Model"
5761
}
5862
],
5963
"keybindings": [
@@ -93,6 +97,25 @@
9397
"default": true,
9498
"description": "Enable AI-powered prompt transformation (requires GPT-4 API access, costs ~$0.01 per transformation)"
9599
},
100+
"cursorWhisper.transformationModel": {
101+
"type": "string",
102+
"default": "gpt-4o",
103+
"enum": [
104+
"gpt-4o",
105+
"gpt-4o-mini",
106+
"gpt-4-turbo",
107+
"gpt-4",
108+
"gpt-3.5-turbo"
109+
],
110+
"enumDescriptions": [
111+
"GPT-4o — fast, cost-effective (recommended default)",
112+
"GPT-4o mini — lower cost, slightly reduced quality",
113+
"GPT-4 Turbo — high intelligence with large context",
114+
"GPT-4 — original GPT-4 model",
115+
"GPT-3.5 Turbo — cheapest option, lower quality"
116+
],
117+
"description": "OpenAI model used for prompt transformation. Use 'Cursor Whisper: Configure Model' to pick from models available on your API key."
118+
},
96119
"cursorWhisper.audioQuality": {
97120
"type": "string",
98121
"enum": [

pnpm-lock.yaml

Lines changed: 100 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/application/ports/IConfigRepository.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ export interface Config {
1414
*/
1515
enablePromptTransformation: boolean;
1616

17+
/**
18+
* OpenAI model ID used for prompt transformation (default: gpt-4o).
19+
*/
20+
transformationModel: string;
21+
1722
/**
1823
* Audio recording quality ('low' | 'medium' | 'high').
1924
*/

src/extension.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { VSCodeOutputChannelLogger } from './infrastructure/logging/VSCodeOutput
55
import { VSCodeConfigRepository } from './infrastructure/configuration/VSCodeConfigRepository';
66
import { OpenAIWhisperService } from './infrastructure/transcription/OpenAIWhisperService';
77
import { OpenAIPromptTransformer } from './infrastructure/transformation/OpenAIPromptTransformer';
8+
import { OpenAIModelService } from './infrastructure/openai/OpenAIModelService';
89
import { ChatParticipantInserter } from './infrastructure/insertion/ChatParticipantInserter';
910
import { EditorTextInserter } from './infrastructure/insertion/EditorTextInserter';
1011
import { FallbackTextInserter } from './infrastructure/insertion/FallbackTextInserter';
@@ -23,6 +24,7 @@ import { registerStartRecordingCommand } from './presentation/commands/StartReco
2324
import { registerStopRecordingCommand } from './presentation/commands/StopRecordingCommand';
2425
import { registerCancelRecordingCommand } from './presentation/commands/CancelRecordingCommand';
2526
import { registerConfigureApiKeyCommand } from './presentation/commands/ConfigureApiKeyCommand';
27+
import { registerConfigureModelCommand } from './presentation/commands/ConfigureModelCommand';
2628
import { RecordingStatusBarItem } from './presentation/ui/RecordingStatusBarItem';
2729

2830
let activeAudioRecorder: NativeAudioRecorder | null = null;
@@ -56,8 +58,14 @@ export function activate(context: vscode.ExtensionContext): void {
5658
return config.apiKey;
5759
};
5860

61+
const getModel = async (): Promise<string | undefined> => {
62+
const config = await configRepository.getConfig();
63+
return config.transformationModel;
64+
};
65+
5966
const whisperService = new OpenAIWhisperService(getApiKey, logger);
60-
const promptTransformer = new OpenAIPromptTransformer(getApiKey, logger);
67+
const promptTransformer = new OpenAIPromptTransformer(getApiKey, getModel, logger);
68+
const modelService = new OpenAIModelService(getApiKey, logger);
6169

6270
// Text Insertion (Chain of Responsibility)
6371
const inserters = [
@@ -117,8 +125,20 @@ export function activate(context: vscode.ExtensionContext): void {
117125
});
118126
const cancelCommand = registerCancelRecordingCommand(context, cancelRecordingUseCase);
119127
const configureCommand = registerConfigureApiKeyCommand(context, configRepository);
128+
const configureModelCommand = registerConfigureModelCommand(
129+
context,
130+
configRepository,
131+
modelService,
132+
logger
133+
);
120134

121-
context.subscriptions.push(startCommand, stopCommand, cancelCommand, configureCommand);
135+
context.subscriptions.push(
136+
startCommand,
137+
stopCommand,
138+
cancelCommand,
139+
configureCommand,
140+
configureModelCommand
141+
);
122142

123143
// ========================================
124144
// STARTUP CHECKS

src/infrastructure/configuration/VSCodeConfigRepository.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export class VSCodeConfigRepository implements IConfigRepository {
66
private static readonly SECTION = 'cursorWhisper';
77
private static readonly SECRET_KEY = 'cursor-whisper.openai.apiKey';
88
private static readonly LEGACY_SECRET_KEY = 'openai-api-key';
9+
static readonly DEFAULT_TRANSFORMATION_MODEL = 'gpt-4o';
910
private callbacks: Array<(config: Config) => void> = [];
1011

1112
constructor(
@@ -40,6 +41,10 @@ export class VSCodeConfigRepository implements IConfigRepository {
4041
apiKey,
4142
transcriptionLanguage: config.get<string>('transcriptionLanguage', 'auto'),
4243
enablePromptTransformation: config.get<boolean>('enablePromptTransformation', false),
44+
transformationModel: config.get<string>(
45+
'transformationModel',
46+
VSCodeConfigRepository.DEFAULT_TRANSFORMATION_MODEL
47+
),
4348
audioQuality: config.get<'low' | 'medium' | 'high'>('audioQuality', 'high'),
4449
maxRecordingDuration: config.get<number>('maxRecordingDuration', 120),
4550
showNotifications: config.get<boolean>('showNotifications', true),
@@ -93,6 +98,16 @@ export class VSCodeConfigRepository implements IConfigRepository {
9398
);
9499
}
95100

101+
if (partialConfig.transformationModel !== undefined) {
102+
updates.push(
103+
config.update(
104+
'transformationModel',
105+
partialConfig.transformationModel,
106+
vscode.ConfigurationTarget.Global
107+
)
108+
);
109+
}
110+
96111
if (partialConfig.audioQuality !== undefined) {
97112
updates.push(
98113
config.update('audioQuality', partialConfig.audioQuality, vscode.ConfigurationTarget.Global)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import OpenAI from 'openai';
2+
import { ApiKey } from '../../domain/value-objects/ApiKey';
3+
import { ILogger } from '../../application/ports/ILogger';
4+
5+
export class OpenAIModelServiceError extends Error {
6+
constructor(message: string, public readonly cause?: Error) {
7+
super(message);
8+
this.name = 'OpenAIModelServiceError';
9+
}
10+
}
11+
12+
/** Models unsuitable for chat completion prompt transformation. */
13+
const EXCLUDED_MODEL_PATTERNS = [
14+
/whisper/i,
15+
/tts/i,
16+
/dall-e/i,
17+
/embedding/i,
18+
/moderation/i,
19+
/realtime/i,
20+
/audio/i,
21+
/transcribe/i,
22+
/search-preview/i,
23+
/instruct/i,
24+
];
25+
26+
/**
27+
* Fetches GPT chat models available to the user's OpenAI API key.
28+
*/
29+
export class OpenAIModelService {
30+
constructor(
31+
private readonly getApiKey: () => Promise<string | undefined>,
32+
private readonly logger: ILogger
33+
) {}
34+
35+
async listGptModels(): Promise<string[]> {
36+
const apiKeyStr = await this.getApiKey();
37+
if (!apiKeyStr) {
38+
throw new OpenAIModelServiceError('OpenAI API key not configured');
39+
}
40+
41+
const apiKey = new ApiKey(apiKeyStr);
42+
const client = new OpenAI({ apiKey: apiKey.toString() });
43+
44+
try {
45+
this.logger.debug('Fetching available OpenAI models');
46+
const response = await client.models.list();
47+
const modelIds = response.data.map(model => model.id);
48+
49+
const gptModels = modelIds
50+
.filter(id => id.startsWith('gpt-'))
51+
.filter(id => !EXCLUDED_MODEL_PATTERNS.some(pattern => pattern.test(id)))
52+
.sort((a, b) => a.localeCompare(b));
53+
54+
this.logger.info('Fetched GPT models from OpenAI API', { count: gptModels.length });
55+
56+
return gptModels;
57+
} catch (error) {
58+
this.logger.error('Failed to fetch OpenAI models', error as Error);
59+
60+
if (error instanceof Error) {
61+
if (error.message.includes('invalid_api_key')) {
62+
throw new OpenAIModelServiceError('Invalid API key', error);
63+
}
64+
}
65+
66+
throw new OpenAIModelServiceError(
67+
'Failed to fetch models from OpenAI',
68+
error instanceof Error ? error : undefined
69+
);
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)