From 7e8ebbc485dda800bcd6f88fe65d5283b761403a Mon Sep 17 00:00:00 2001 From: George Stagg Date: Thu, 4 Sep 2025 13:44:02 +0100 Subject: [PATCH 1/3] Assistant: Improve git integration logging and model selection --- .../positron-assistant/src/extension.ts | 12 ++++-- extensions/positron-assistant/src/git.ts | 41 +++++++++++++++---- .../positron-assistant/src/participants.ts | 11 +++++ 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/extensions/positron-assistant/src/extension.ts b/extensions/positron-assistant/src/extension.ts index dc80dadd863a..ae53b4a58b3b 100644 --- a/extensions/positron-assistant/src/extension.ts +++ b/extensions/positron-assistant/src/extension.ts @@ -8,7 +8,7 @@ import * as positron from 'positron'; import { EncryptedSecretStorage, expandConfigToSource, getEnabledProviders, getModelConfiguration, getModelConfigurations, getStoredModels, GlobalSecretStorage, logStoredModels, ModelConfig, SecretStorage, showConfigurationDialog, StoredModelConfig } from './config'; import { createModelConfigsFromEnv, newLanguageModelChatProvider } from './models'; import { registerMappedEditsProvider } from './edits'; -import { registerParticipants } from './participants'; +import { ParticipantService, registerParticipants } from './participants'; import { newCompletionProvider, registerHistoryTracking } from './completion'; import { registerAssistantTools } from './tools.js'; import { registerCopilotService } from './copilot.js'; @@ -188,10 +188,14 @@ function registerConfigureModelsCommand(context: vscode.ExtensionContext, storag ); } -function registerGenerateCommitMessageCommand(context: vscode.ExtensionContext) { +function registerGenerateCommitMessageCommand( + context: vscode.ExtensionContext, + participantService: ParticipantService, + log: vscode.LogOutputChannel, +) { context.subscriptions.push( vscode.commands.registerCommand('positron-assistant.generateCommitMessage', () => { - generateCommitMessage(context); + generateCommitMessage(context, participantService, log); }) ); } @@ -231,7 +235,7 @@ function registerAssistant(context: vscode.ExtensionContext) { // Commands registerConfigureModelsCommand(context, storage); - registerGenerateCommitMessageCommand(context); + registerGenerateCommitMessageCommand(context, participantService, log); registerExportChatCommands(context); // Register mapped edits provider diff --git a/extensions/positron-assistant/src/git.ts b/extensions/positron-assistant/src/git.ts index e6a35f2105a3..b911e7e9ef28 100644 --- a/extensions/positron-assistant/src/git.ts +++ b/extensions/positron-assistant/src/git.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import * as fs from 'fs'; import * as path from 'path'; +import { ParticipantService } from './participants.js'; import { API as GitAPI, GitExtension, Repository, Status, Change } from '../../git/src/api/git.js'; import { MD_DIR } from './constants'; @@ -131,17 +132,15 @@ export async function getWorkspaceGitChanges(kind: GitRepoChangeKind): Promise { - return model.family !== 'echo' && model.family !== 'error'; - }); - if (models.length === 0) { - vscode.commands.executeCommand('setContext', generatingGitCommitKey, false); - throw new Error('No language models available for commit message generation.'); - } - const model = models[0]; + const model = await getModel(participantService); + log.info(`[git] Generating commit message. Selected model (${model.vendor}) ${model.id} for commit message generation.`); const tokenSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource(); const cancelDisposable = vscode.commands.registerCommand('positron-assistant.cancelGenerateCommitMessage', () => { @@ -153,6 +152,7 @@ export async function generateCommitMessage(context: vscode.ExtensionContext) { const allChanges = await getWorkspaceGitChanges(GitRepoChangeKind.All); const stagedChanges = await getWorkspaceGitChanges(GitRepoChangeKind.Staged); const gitChanges = stagedChanges.length > 0 ? stagedChanges : allChanges; + log.trace(`[git] Sending changes ${JSON.stringify(gitChanges)} to model provider.`); const system: string = await fs.promises.readFile(`${MD_DIR}/prompts/git/commit.md`, 'utf8'); try { @@ -171,8 +171,31 @@ export async function generateCommitMessage(context: vscode.ExtensionContext) { } } })); + } catch (e) { + const error = e as Error; + log.error(`[git] Error generating commit message: ${error.message}`); + void vscode.window.showErrorMessage(`Error generating commit message: ${error.message}`); + throw e; } finally { cancelDisposable.dispose(); vscode.commands.executeCommand('setContext', generatingGitCommitKey, false); } } + +async function getModel(participantService: ParticipantService): Promise { + // Check for the latest chat session and use its model. + const sessionModelId = participantService.getCurrentSessionModel(); + if (sessionModelId) { + const models = await vscode.lm.selectChatModels({ 'id': sessionModelId }); + if (models && models.length > 0) { + return models[0]; + } + } + + // Fall back to the first available model. + const models = await vscode.lm.selectChatModels(); + if (models.length === 0) { + throw new Error('No language models available for git commit message generation'); + } + return models.filter((model) => model.family !== 'echo' && model.family !== 'error')[0]; +} diff --git a/extensions/positron-assistant/src/participants.ts b/extensions/positron-assistant/src/participants.ts index 4391c09419f0..110fd2da5a3d 100644 --- a/extensions/positron-assistant/src/participants.ts +++ b/extensions/positron-assistant/src/participants.ts @@ -55,6 +55,7 @@ export interface IPositronAssistantParticipant extends vscode.ChatParticipant { export class ParticipantService implements vscode.Disposable { private readonly _participants = new Map(); private readonly _sessionModels = new Map(); // sessionId -> modelId + private _lastSessionModel: string | undefined; registerParticipant(participant: IPositronAssistantParticipant): void { this._participants.set(participant.id, participant); @@ -90,6 +91,7 @@ export class ParticipantService implements vscode.Disposable { * @param modelId The language model ID used for this session */ trackSessionModel(sessionId: string, modelId: string): void { + this._lastSessionModel = modelId; this._sessionModels.set(sessionId, modelId); } @@ -103,6 +105,15 @@ export class ParticipantService implements vscode.Disposable { return this._sessionModels.get(sessionId); } + /** + * Get the most recently used model ID. + * + * @returns The model ID if exists, undefined otherwise + */ + getCurrentSessionModel(): string | undefined { + return this._lastSessionModel; + } + dispose() { this._participants.forEach((participant) => participant.dispose()); this._sessionModels.clear(); From 0a933cfd51c00a1e4963659063614a75eee387c0 Mon Sep 17 00:00:00 2001 From: George Stagg Date: Mon, 8 Sep 2025 10:05:56 +0100 Subject: [PATCH 2/3] Assistant: Expose current provider on the Positron extension API --- src/positron-dts/positron.d.ts | 23 ++++++++++++++++ .../browser/positron/mainThreadAiFeatures.ts | 27 ++++++++++++++++++- .../positron/extHost.positron.api.impl.ts | 9 +++++++ .../positron/extHost.positron.protocol.ts | 7 +++++ .../api/common/positron/extHostAiFeatures.ts | 14 ++++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/src/positron-dts/positron.d.ts b/src/positron-dts/positron.d.ts index 988c1f2fbdcb..1aadd519c967 100644 --- a/src/positron-dts/positron.d.ts +++ b/src/positron-dts/positron.d.ts @@ -2269,6 +2269,29 @@ declare module 'positron' { shell?: string; } + /** + * A chat language model provider. + */ + export interface ChatProvider { + readonly id: string; + readonly displayName: string; + } + + /** + * Get the current langauge model provider. + */ + export function getCurrentProvider(): Thenable; + + /** + * Get all the available langauge model providers. + */ + export function getProviders(): Thenable; + + /** + * Set the current language chat provider. + */ + export function setCurrentProvider(id: string): Thenable; + /** * Checks if completions are enabled for the given file. * @param uri The file URI to check if completions are enabled. diff --git a/src/vs/workbench/api/browser/positron/mainThreadAiFeatures.ts b/src/vs/workbench/api/browser/positron/mainThreadAiFeatures.ts index 8dc6b6f7cb62..331c16f47bed 100644 --- a/src/vs/workbench/api/browser/positron/mainThreadAiFeatures.ts +++ b/src/vs/workbench/api/browser/positron/mainThreadAiFeatures.ts @@ -9,6 +9,7 @@ import { URI, UriComponents } from '../../../../base/common/uri.js'; import { IChatAgentData, IChatAgentService } from '../../../contrib/chat/common/chatAgents.js'; import { ChatModel, IExportableChatData } from '../../../contrib/chat/common/chatModel.js'; import { IChatProgress, IChatService } from '../../../contrib/chat/common/chatService.js'; +import { ILanguageModelsService, IPositronChatProvider } from '../../../contrib/chat/common/languageModels.js'; import { IChatRequestData, IPositronAssistantService, IPositronChatContext, IPositronLanguageModelSource } from '../../../contrib/positronAssistant/common/interfaces/positronAssistantService.js'; import { extHostNamedCustomer, IExtHostContext } from '../../../services/extensions/common/extHostCustomers.js'; import { IChatProgressDto } from '../../common/extHost.protocol.js'; @@ -24,7 +25,8 @@ export class MainThreadAiFeatures extends Disposable implements MainThreadAiFeat extHostContext: IExtHostContext, @IPositronAssistantService private readonly _positronAssistantService: IPositronAssistantService, @IChatService private readonly _chatService: IChatService, - @IChatAgentService private readonly _chatAgentService: IChatAgentService + @IChatAgentService private readonly _chatAgentService: IChatAgentService, + @ILanguageModelsService private readonly _languageModelsService: ILanguageModelsService, ) { super(); // Create the proxy for the extension host. @@ -127,4 +129,27 @@ export class MainThreadAiFeatures extends Disposable implements MainThreadAiFeat // Use the language model ignored files service to check if the file should be excluded return this._positronAssistantService.areCompletionsEnabled(uri); } + + /** + * Get the current langauge model provider. + */ + async $getCurrentProvider(): Promise { + return this._languageModelsService.currentProvider; + } + + /** + * Get all the available langauge model providers. + */ + async $getProviders(): Promise { + return this._languageModelsService.getLanguageModelProviders(); + } + + /** + * Set the current language chat provider. + */ + async $setCurrentProvider(id: string): Promise { + const provider = this._languageModelsService.getLanguageModelProviders().find(p => p.id === id); + this._languageModelsService.currentProvider = provider; + return provider; + } } diff --git a/src/vs/workbench/api/common/positron/extHost.positron.api.impl.ts b/src/vs/workbench/api/common/positron/extHost.positron.api.impl.ts index fcb011296423..20f094b0e895 100644 --- a/src/vs/workbench/api/common/positron/extHost.positron.api.impl.ts +++ b/src/vs/workbench/api/common/positron/extHost.positron.api.impl.ts @@ -293,6 +293,15 @@ export function createPositronApiFactoryAndRegisterActors(accessor: ServicesAcce areCompletionsEnabled(file: vscode.Uri): Promise { return extHostAiFeatures.areCompletionsEnabled(file); }, + getCurrentProvider(): Promise { + return extHostAiFeatures.getCurrentProvider(); + }, + getProviders(): Promise { + return extHostAiFeatures.getProviders(); + }, + setCurrentProvider(id: string): Promise { + return extHostAiFeatures.setCurrentProvider(id); + }, }; // --- End Positron --- diff --git a/src/vs/workbench/api/common/positron/extHost.positron.protocol.ts b/src/vs/workbench/api/common/positron/extHost.positron.protocol.ts index 5bc1aa1803e1..8afa55040fad 100644 --- a/src/vs/workbench/api/common/positron/extHost.positron.protocol.ts +++ b/src/vs/workbench/api/common/positron/extHost.positron.protocol.ts @@ -18,6 +18,7 @@ import { IChatAgentData } from '../../../contrib/chat/common/chatAgents.js'; import { PlotRenderSettings } from '../../../services/positronPlots/common/positronPlots.js'; import { QueryTableSummaryResult, Variable } from '../../../services/languageRuntime/common/positronVariablesComm.js'; import { ILanguageRuntimeCodeExecutedEvent } from '../../../services/positronConsole/common/positronConsoleCodeExecution.js'; +import { IPositronChatProvider } from '../../../contrib/chat/common/languageModels.js'; // NOTE: This check is really to ensure that extHost.protocol is included by the TypeScript compiler // as a dependency of this module, and therefore that it's initialized first. This is to avoid a @@ -165,11 +166,17 @@ export interface MainThreadAiFeaturesShape { $addLanguageModelConfig(source: IPositronLanguageModelSource): void; $removeLanguageModelConfig(source: IPositronLanguageModelSource): void; $areCompletionsEnabled(file: UriComponents): Thenable; + $getCurrentProvider(): Thenable; + $getProviders(): Thenable; + $setCurrentProvider(id: string): Thenable; } export interface ExtHostAiFeaturesShape { $responseLanguageModelConfig(id: string, config: IPositronLanguageModelConfig, action: string): Thenable; $onCompleteLanguageModelConfig(id: string): void; + getCurrentProvider(): Thenable; + getProviders(): Thenable; + setCurrentProvider(id: string): Thenable; } export interface MainThreadPlotsServiceShape { diff --git a/src/vs/workbench/api/common/positron/extHostAiFeatures.ts b/src/vs/workbench/api/common/positron/extHostAiFeatures.ts index d963d6b63668..d0dfb2c23081 100644 --- a/src/vs/workbench/api/common/positron/extHostAiFeatures.ts +++ b/src/vs/workbench/api/common/positron/extHostAiFeatures.ts @@ -16,6 +16,7 @@ import { IChatRequestData, IPositronChatContext, IPositronLanguageModelConfig, I import { IExtensionDescription } from '../../../../platform/extensions/common/extensions.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { ChatAgentLocation, ChatModeKind } from '../../../contrib/chat/common/constants.js'; +import { IPositronChatProvider } from '../../../contrib/chat/common/languageModels.js'; export class ExtHostAiFeatures implements extHostProtocol.ExtHostAiFeaturesShape { @@ -108,4 +109,17 @@ export class ExtHostAiFeatures implements extHostProtocol.ExtHostAiFeaturesShape async areCompletionsEnabled(file: vscode.Uri): Promise { return this._proxy.$areCompletionsEnabled(file); } + + async getCurrentProvider(): Promise { + return this._proxy.$getCurrentProvider(); + } + + async getProviders(): Promise { + return this._proxy.$getProviders(); + } + + async setCurrentProvider(id: string): Promise { + return this._proxy.$setCurrentProvider(id); + } + } From 7ba213715a5fd2d762e15738c83c74116310762d Mon Sep 17 00:00:00 2001 From: George Stagg Date: Mon, 8 Sep 2025 10:06:59 +0100 Subject: [PATCH 3/3] Assistant: Use current provider for first selection fallback --- extensions/positron-assistant/src/git.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/extensions/positron-assistant/src/git.ts b/extensions/positron-assistant/src/git.ts index b911e7e9ef28..9220f8bacdf1 100644 --- a/extensions/positron-assistant/src/git.ts +++ b/extensions/positron-assistant/src/git.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import * as positron from 'positron'; import * as fs from 'fs'; import * as path from 'path'; @@ -192,7 +193,14 @@ async function getModel(participantService: ParticipantService): Promise