Skip to content

Commit 07dcfa3

Browse files
committed
Adds generate draft message
1 parent 1c30df4 commit 07dcfa3

File tree

8 files changed

+363
-103
lines changed

8 files changed

+363
-103
lines changed

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3532,10 +3532,17 @@
35323532
"gitlens.experimental.generateCommitMessagePrompt": {
35333533
"type": "string",
35343534
"default": "Now, please generate a commit message. Ensure that it includes a precise and informative subject line that succinctly summarizes the crux of the changes in under 50 characters. If necessary, follow with an explanatory body providing insight into the nature of the changes, the reasoning behind them, and any significant consequences or considerations arising from them. Conclude with any relevant issue references at the end of the message.",
3535-
"markdownDescription": "Specifies the prompt to use to tell OpenAI how to structure or format the generated commit message",
3535+
"markdownDescription": "Specifies the prompt to use to tell the AI provider how to structure or format the generated commit message",
35363536
"scope": "window",
35373537
"order": 2
35383538
},
3539+
"gitlens.experimental.generateDraftMessagePrompt": {
3540+
"type": "string",
3541+
"default": "Now, please generate a commit message. Ensure that it includes a precise and informative subject line that succinctly summarizes the crux of the changes in under 50 characters. If necessary, follow with an explanatory body providing insight into the nature of the changes, the reasoning behind them, and any significant consequences or considerations arising from them. Conclude with any relevant issue references at the end of the message.",
3542+
"markdownDescription": "Specifies the prompt to use to tell the AI provider how to structure or format the generated title and description",
3543+
"scope": "window",
3544+
"order": 3
3545+
},
35393546
"gitlens.ai.experimental.model": {
35403547
"type": [
35413548
"string",

src/ai/aiProviderService.ts

Lines changed: 69 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ export interface AIProvider<Provider extends AIProviders = AIProviders> extends
6565
diff: string,
6666
options?: { cancellation?: CancellationToken; context?: string },
6767
): Promise<string | undefined>;
68+
generateDraftMessage(
69+
model: AIModel<Provider, AIModels<Provider>>,
70+
diff: string,
71+
options?: { cancellation?: CancellationToken; context?: string; codeSuggestion?: boolean },
72+
): Promise<string | undefined>;
6873
}
6974

7075
export class AIProviderService implements Disposable {
@@ -204,24 +209,43 @@ export class AIProviderService implements Disposable {
204209
changesOrRepoOrPath: string[] | Repository | Uri,
205210
options?: { cancellation?: CancellationToken; context?: string; progress?: ProgressOptions },
206211
): Promise<string | undefined> {
207-
let changes: string;
208-
if (Array.isArray(changesOrRepoOrPath)) {
209-
changes = changesOrRepoOrPath.join('\n');
210-
} else {
211-
const repository = isRepository(changesOrRepoOrPath)
212-
? changesOrRepoOrPath
213-
: this.container.git.getRepository(changesOrRepoOrPath);
214-
if (repository == null) throw new Error('Unable to find repository');
212+
const changes: string | undefined = await this.getChanges(changesOrRepoOrPath);
213+
if (changes == null) return undefined;
215214

216-
let diff = await this.container.git.getDiff(repository.uri, uncommittedStaged);
217-
if (!diff?.contents) {
218-
diff = await this.container.git.getDiff(repository.uri, uncommitted);
219-
if (!diff?.contents) throw new Error('No changes to generate a commit message from.');
220-
}
221-
if (options?.cancellation?.isCancellationRequested) return undefined;
215+
const model = await this.getModel();
216+
if (model == null) return undefined;
222217

223-
changes = diff.contents;
218+
const provider = this._provider!;
219+
220+
const confirmed = await confirmAIProviderToS(model, this.container.storage);
221+
if (!confirmed) return undefined;
222+
if (options?.cancellation?.isCancellationRequested) return undefined;
223+
224+
if (options?.progress != null) {
225+
return window.withProgress(options.progress, async () =>
226+
provider.generateCommitMessage(model, changes, {
227+
cancellation: options?.cancellation,
228+
context: options?.context,
229+
}),
230+
);
224231
}
232+
return provider.generateCommitMessage(model, changes, {
233+
cancellation: options?.cancellation,
234+
context: options?.context,
235+
});
236+
}
237+
238+
async generateDraftMessage(
239+
changesOrRepoOrPath: string[] | Repository | Uri,
240+
options?: {
241+
cancellation?: CancellationToken;
242+
context?: string;
243+
progress?: ProgressOptions;
244+
codeSuggestion?: boolean;
245+
},
246+
): Promise<string | undefined> {
247+
const changes: string | undefined = await this.getChanges(changesOrRepoOrPath);
248+
if (changes == null) return undefined;
225249

226250
const model = await this.getModel();
227251
if (model == null) return undefined;
@@ -234,18 +258,46 @@ export class AIProviderService implements Disposable {
234258

235259
if (options?.progress != null) {
236260
return window.withProgress(options.progress, async () =>
237-
provider.generateCommitMessage(model, changes, {
261+
provider.generateDraftMessage(model, changes, {
238262
cancellation: options?.cancellation,
239263
context: options?.context,
264+
codeSuggestion: options?.codeSuggestion,
240265
}),
241266
);
242267
}
243-
return provider.generateCommitMessage(model, changes, {
268+
return provider.generateDraftMessage(model, changes, {
244269
cancellation: options?.cancellation,
245270
context: options?.context,
271+
codeSuggestion: options?.codeSuggestion,
246272
});
247273
}
248274

275+
private async getChanges(
276+
changesOrRepoOrPath: string[] | Repository | Uri,
277+
options?: { cancellation?: CancellationToken; context?: string; progress?: ProgressOptions },
278+
): Promise<string | undefined> {
279+
let changes: string;
280+
if (Array.isArray(changesOrRepoOrPath)) {
281+
changes = changesOrRepoOrPath.join('\n');
282+
} else {
283+
const repository = isRepository(changesOrRepoOrPath)
284+
? changesOrRepoOrPath
285+
: this.container.git.getRepository(changesOrRepoOrPath);
286+
if (repository == null) throw new Error('Unable to find repository');
287+
288+
let diff = await this.container.git.getDiff(repository.uri, uncommittedStaged);
289+
if (!diff?.contents) {
290+
diff = await this.container.git.getDiff(repository.uri, uncommitted);
291+
if (!diff?.contents) throw new Error('No changes to generate a commit message from.');
292+
}
293+
if (options?.cancellation?.isCancellationRequested) return undefined;
294+
295+
changes = diff.contents;
296+
}
297+
298+
return changes;
299+
}
300+
249301
async explainCommit(
250302
repoPath: string | Uri,
251303
sha: string,

src/ai/anthropicProvider.ts

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { configuration } from '../system/configuration';
77
import type { Storage } from '../system/storage';
88
import type { AIModel, AIProvider } from './aiProviderService';
99
import { getApiKey as getApiKeyCore, getMaxCharacters } from './aiProviderService';
10+
import { commitMessageSystemPrompt, draftMessageSystemPrompt } from './prompts';
1011

1112
const provider = { id: 'anthropic', name: 'Anthropic' } as const;
1213
type LegacyModels = Extract<AnthropicModels, 'claude-instant-1' | 'claude-2'>;
@@ -74,29 +75,19 @@ export class AnthropicProvider implements AIProvider<typeof provider.id> {
7475
return Promise.resolve(models);
7576
}
7677

77-
async generateCommitMessage(
78+
async generateMessage(
7879
model: AnthropicModel,
7980
diff: string,
81+
promptConfig: {
82+
systemPrompt: string;
83+
customPrompt: string;
84+
contextName: string;
85+
},
8086
options?: { cancellation?: CancellationToken; context?: string },
8187
): Promise<string | undefined> {
8288
const apiKey = await getApiKey(this.container.storage);
8389
if (apiKey == null) return undefined;
8490

85-
let customPrompt = configuration.get('experimental.generateCommitMessagePrompt');
86-
if (!customPrompt.endsWith('.')) {
87-
customPrompt += '.';
88-
}
89-
90-
const systemPrompt = `You are an advanced AI programming assistant tasked with summarizing code changes into a concise and meaningful commit message. Compose a commit message that:
91-
- Strictly synthesizes meaningful information from the provided code diff
92-
- Utilizes any additional user-provided context to comprehend the rationale behind the code changes
93-
- Is clear and brief, with an informal yet professional tone, and without superfluous descriptions
94-
- Avoids unnecessary phrases such as "this commit", "this change", and the like
95-
- Avoids direct mention of specific code identifiers, names, or file names, unless they are crucial for understanding the purpose of the changes
96-
- Most importantly emphasizes the 'why' of the change, its benefits, or the problem it addresses rather than only the 'what' that changed
97-
98-
Follow the user's instructions carefully, don't repeat yourself, don't include the code in the output, or make anything up!`;
99-
10091
try {
10192
let result: string;
10293
let maxCodeCharacters: number;
@@ -107,12 +98,12 @@ Follow the user's instructions carefully, don't repeat yourself, don't include t
10798
apiKey,
10899
max => {
109100
const code = diff.substring(0, max);
110-
let prompt = `\n\nHuman: ${systemPrompt}\n\nHuman: Here is the code diff to use to generate the commit message:\n\n${code}\n`;
101+
let prompt = `\n\nHuman: ${promptConfig.systemPrompt}\n\nHuman: Here is the code diff to use to generate the ${promptConfig.contextName}:\n\n${code}\n`;
111102
if (options?.context) {
112-
prompt += `\nHuman: Here is additional context which should be taken into account when generating the commit message:\n\n${options.context}\n`;
103+
prompt += `\nHuman: Here is additional context which should be taken into account when generating the ${promptConfig.contextName}:\n\n${options.context}\n`;
113104
}
114-
if (customPrompt) {
115-
prompt += `\nHuman: ${customPrompt}\n`;
105+
if (promptConfig.customPrompt) {
106+
prompt += `\nHuman: ${promptConfig.customPrompt}\n`;
116107
}
117108
prompt += '\nAssistant:';
118109
return prompt;
@@ -124,15 +115,15 @@ Follow the user's instructions carefully, don't repeat yourself, don't include t
124115
[result, maxCodeCharacters] = await this.makeRequest(
125116
model,
126117
apiKey,
127-
systemPrompt,
118+
promptConfig.systemPrompt,
128119
max => {
129120
const code = diff.substring(0, max);
130121
const message: Message = {
131122
role: 'user',
132123
content: [
133124
{
134125
type: 'text',
135-
text: 'Here is the code diff to use to generate the commit message:',
126+
text: `Here is the code diff to use to generate the ${promptConfig.contextName}:`,
136127
},
137128
{
138129
type: 'text',
@@ -144,18 +135,18 @@ Follow the user's instructions carefully, don't repeat yourself, don't include t
144135
message.content.push(
145136
{
146137
type: 'text',
147-
text: 'Here is additional context which should be taken into account when generating the commit message:',
138+
text: `Here is additional context which should be taken into account when generating the ${promptConfig.contextName}:`,
148139
},
149140
{
150141
type: 'text',
151142
text: options.context,
152143
},
153144
);
154145
}
155-
if (customPrompt) {
146+
if (promptConfig.customPrompt) {
156147
message.content.push({
157148
type: 'text',
158-
text: customPrompt,
149+
text: promptConfig.customPrompt,
159150
});
160151
}
161152
return [message];
@@ -173,8 +164,55 @@ Follow the user's instructions carefully, don't repeat yourself, don't include t
173164

174165
return result;
175166
} catch (ex) {
176-
throw new Error(`Unable to generate commit message: ${ex.message}`);
167+
throw new Error(`Unable to generate ${promptConfig.contextName}: ${ex.message}`);
168+
}
169+
}
170+
171+
async generateDraftMessage(
172+
model: AnthropicModel,
173+
diff: string,
174+
options?: { cancellation?: CancellationToken; context?: string; codeSuggestion?: boolean },
175+
): Promise<string | undefined> {
176+
let customPrompt = configuration.get('experimental.generateDraftMessagePrompt');
177+
if (!customPrompt.endsWith('.')) {
178+
customPrompt += '.';
177179
}
180+
181+
return this.generateMessage(
182+
model,
183+
diff,
184+
{
185+
systemPrompt: draftMessageSystemPrompt,
186+
customPrompt: customPrompt,
187+
contextName:
188+
options?.codeSuggestion === true
189+
? 'code suggestion title and description'
190+
: 'cloud patch title and description',
191+
},
192+
options,
193+
);
194+
}
195+
196+
async generateCommitMessage(
197+
model: AnthropicModel,
198+
diff: string,
199+
options?: { cancellation?: CancellationToken; context?: string },
200+
): Promise<string | undefined> {
201+
let customPrompt = configuration.get('experimental.generateCommitMessagePrompt');
202+
if (!customPrompt.endsWith('.')) {
203+
customPrompt += '.';
204+
}
205+
206+
return this.generateMessage(
207+
model,
208+
diff,
209+
{
210+
systemPrompt: commitMessageSystemPrompt,
211+
customPrompt: customPrompt,
212+
contextName: 'commit message',
213+
},
214+
options,
215+
);
178216
}
179217

180218
async explainChanges(

0 commit comments

Comments
 (0)