Refactor Copilot CLI loading and session management (#1730)

* Move Copilot CLI session out into own file

* Remvoe a few methods

* Updates
This commit is contained in:
Don Jayamanne
2025-10-31 15:23:45 +11:00
committed by GitHub
parent e03b73d0fd
commit f9ed6387cd
4 changed files with 264 additions and 242 deletions
@@ -3,24 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { AgentOptions, Attachment, ModelProvider, PostToolUseHookInput, PreToolUseHookInput, Session, SessionEvent } from '@github/copilot/sdk';
import type { ModelProvider } from '@github/copilot/sdk';
import type * as vscode from 'vscode';
import { IAuthenticationService } from '../../../../platform/authentication/common/authentication';
import { ILogService } from '../../../../platform/log/common/logService';
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
import { Disposable } from '../../../../util/vs/base/common/lifecycle';
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
import { ChatResponseThinkingProgressPart, LanguageModelTextPart } from '../../../../vscodeTypes';
import { IToolsService } from '../../../tools/common/toolsService';
import { ExternalEditTracker } from '../../common/externalEditTracker';
import { getAffectedUrisForEditTool } from '../common/copilotcliTools';
import { ICopilotCLISDK } from './copilotCli';
import { CopilotCLIPromptResolver } from './copilotcliPromptResolver';
import { CopilotCLISession } from './copilotcliSession';
import { ICopilotCLISessionService } from './copilotcliSessionService';
import { processToolExecutionComplete, processToolExecutionStart } from './copilotcliToolInvocationFormatter';
import { getCopilotLogger } from './logger';
import { getConfirmationToolParams, PermissionRequest } from './permissionHelpers';
export class CopilotCLIAgentManager extends Disposable {
constructor(
@@ -47,7 +37,6 @@ export class CopilotCLIAgentManager extends Disposable {
modelId: ModelProvider | undefined,
token: vscode.CancellationToken
): Promise<{ copilotcliSessionId: string | undefined }> {
const isNewSession = !copilotcliSessionId;
const sessionIdForLog = copilotcliSessionId ?? 'new';
this.logService.trace(`[CopilotCLIAgentManager] Handling request for sessionId=${sessionIdForLog}.`);
@@ -63,197 +52,8 @@ export class CopilotCLIAgentManager extends Disposable {
this.sessionService.trackSessionWrapper(sdkSession.sessionId, session);
}
if (isNewSession) {
this.sessionService.setPendingRequest(session.sessionId);
}
await session.invoke(prompt, attachments, request.toolInvocationToken, stream, modelId, token);
return { copilotcliSessionId: session.sessionId };
}
}
export class CopilotCLISession extends Disposable {
private _abortController = new AbortController();
private _pendingToolInvocations = new Map<string, vscode.ChatToolInvocationPart>();
private _editTracker = new ExternalEditTracker();
public readonly sessionId: string;
constructor(
private readonly _sdkSession: Session,
@ILogService private readonly logService: ILogService,
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
@IAuthenticationService private readonly _authenticationService: IAuthenticationService,
@IToolsService private readonly toolsService: IToolsService,
@ICopilotCLISDK private readonly copilotCLISDK: ICopilotCLISDK
) {
super();
this.sessionId = _sdkSession.sessionId;
}
public override dispose(): void {
this._abortController.abort();
super.dispose();
}
async *query(prompt: string, attachments: Attachment[], options: AgentOptions): AsyncGenerator<SessionEvent> {
// Dynamically import the SDK
const { Agent } = await this.copilotCLISDK.getPackage();
const agent = new Agent(options);
yield* agent.query(prompt, attachments);
}
public async invoke(
prompt: string,
attachments: Attachment[],
toolInvocationToken: vscode.ChatParticipantToolToken,
stream: vscode.ChatResponseStream,
modelId: ModelProvider | undefined,
token: vscode.CancellationToken
): Promise<void> {
if (this._store.isDisposed) {
throw new Error('Session disposed');
}
this.logService.trace(`[CopilotCLISession] Invoking session ${this.sessionId}`);
const copilotToken = await this._authenticationService.getCopilotToken();
const options: AgentOptions = {
modelProvider: modelId ?? {
type: 'anthropic',
model: 'claude-sonnet-4.5',
},
abortController: this._abortController,
// TODO@rebornix handle workspace properly
workingDirectory: this.workspaceService.getWorkspaceFolders().at(0)?.fsPath,
copilotToken: copilotToken.token,
env: {
...process.env,
COPILOTCLI_DISABLE_NONESSENTIAL_TRAFFIC: '1'
},
requestPermission: async (permissionRequest) => {
return await this.requestPermission(permissionRequest, toolInvocationToken);
},
logger: getCopilotLogger(this.logService),
session: this._sdkSession,
hooks: {
preToolUse: [
async (input: PreToolUseHookInput) => {
const editKey = getEditOperationKey(input.toolName, input.toolArgs);
await this._onWillEditTool(input, editKey, stream);
}
],
postToolUse: [
async (input: PostToolUseHookInput) => {
const editKey = getEditOperationKey(input.toolName, input.toolArgs);
void this._onDidEditTool(editKey);
}
]
}
};
try {
for await (const event of this.query(prompt, attachments, options)) {
if (token.isCancellationRequested) {
break;
}
this._processEvent(event, stream, toolInvocationToken);
}
} catch (error) {
this.logService.error(`CopilotCLI session error: ${error}`);
stream.markdown(`\n\n❌ Error: ${error instanceof Error ? error.message : String(error)}`);
}
}
private _toolNames = new Map<string, string>();
private _processEvent(
event: SessionEvent,
stream: vscode.ChatResponseStream,
toolInvocationToken: vscode.ChatParticipantToolToken
): void {
this.logService.trace(`CopilotCLI Event: ${JSON.stringify(event, null, 2)}`);
switch (event.type) {
case 'assistant.turn_start':
case 'assistant.turn_end': {
this._toolNames.clear();
break;
}
case 'assistant.message': {
if (event.data.content.length) {
stream.markdown(event.data.content);
}
break;
}
case 'tool.execution_start': {
const responsePart = processToolExecutionStart(event, this._toolNames, this._pendingToolInvocations);
const toolName = this._toolNames.get(event.data.toolCallId);
if (responsePart instanceof ChatResponseThinkingProgressPart) {
stream.push(responsePart);
}
this.logService.trace(`Start Tool ${toolName || '<unknown>'}`);
break;
}
case 'tool.execution_complete': {
const responsePart = processToolExecutionComplete(event, this._pendingToolInvocations);
if (responsePart && !(responsePart instanceof ChatResponseThinkingProgressPart)) {
stream.push(responsePart);
}
const toolName = this._toolNames.get(event.data.toolCallId) || '<unknown>';
const success = `success: ${event.data.success}`;
const error = event.data.error ? `error: ${event.data.error.code},${event.data.error.message}` : '';
const result = event.data.result ? `result: ${event.data.result?.content}` : '';
const parts = [success, error, result].filter(part => part.length > 0).join(', ');
this.logService.trace(`Complete Tool ${toolName}, ${parts}`);
break;
}
case 'session.error': {
this.logService.error(`CopilotCLI error: (${event.data.errorType}), ${event.data.message}`);
stream.markdown(`\n\n❌ Error: ${event.data.message}`);
break;
}
}
}
private async requestPermission(
permissionRequest: PermissionRequest,
toolInvocationToken: vscode.ChatParticipantToolToken
): Promise<{ kind: 'approved' } | { kind: 'denied-interactively-by-user' }> {
try {
const { tool, input } = getConfirmationToolParams(permissionRequest);
const result = await this.toolsService.invokeTool(tool,
{ input, toolInvocationToken },
CancellationToken.None);
const firstResultPart = result.content.at(0);
if (firstResultPart instanceof LanguageModelTextPart && firstResultPart.value === 'yes') {
return { kind: 'approved' };
}
} catch (error) {
this.logService.error(`[CopilotCLISession] Permission request error: ${error}`);
}
return { kind: 'denied-interactively-by-user' };
}
private async _onWillEditTool(input: PreToolUseHookInput, editKey: string, stream: vscode.ChatResponseStream): Promise<void> {
const uris = getAffectedUrisForEditTool(input.toolName, input.toolArgs);
return this._editTracker.trackEdit(editKey, uris, stream);
}
private async _onDidEditTool(editKey: string): Promise<void> {
return this._editTracker.completeEdit(editKey);
}
}
function getEditOperationKey(toolName: string, toolArgs: unknown): string {
// todo@connor4312: get copilot CLI to surface the tool call ID instead?
return `${toolName}:${JSON.stringify(toolArgs)}`;
}
@@ -0,0 +1,219 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { AgentOptions, Attachment, ModelProvider, PostToolUseHookInput, PreToolUseHookInput, Session, SessionEvent } from '@github/copilot/sdk';
import type * as vscode from 'vscode';
import { IAuthenticationService } from '../../../../platform/authentication/common/authentication';
import { ILogService } from '../../../../platform/log/common/logService';
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
import { DisposableStore } from '../../../../util/vs/base/common/lifecycle';
import { ChatResponseThinkingProgressPart, ChatSessionStatus, EventEmitter, LanguageModelTextPart } from '../../../../vscodeTypes';
import { IToolsService } from '../../../tools/common/toolsService';
import { ExternalEditTracker } from '../../common/externalEditTracker';
import { getAffectedUrisForEditTool } from '../common/copilotcliTools';
import { ICopilotCLISDK } from './copilotCli';
import { processToolExecutionComplete, processToolExecutionStart } from './copilotcliToolInvocationFormatter';
import { getCopilotLogger } from './logger';
import { getConfirmationToolParams, PermissionRequest } from './permissionHelpers';
export class CopilotCLISession extends DisposableStore {
private _abortController = new AbortController();
private _pendingToolInvocations = new Map<string, vscode.ChatToolInvocationPart>();
private _editTracker = new ExternalEditTracker();
public readonly sessionId: string;
private _status?: vscode.ChatSessionStatus;
public get status(): vscode.ChatSessionStatus | undefined {
return this._status;
}
private readonly _statusChange = this.add(new EventEmitter<vscode.ChatSessionStatus | undefined>());
public readonly onDidChangeStatus = this._statusChange.event;
constructor(
private readonly _sdkSession: Session,
@ILogService private readonly logService: ILogService,
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
@IAuthenticationService private readonly _authenticationService: IAuthenticationService,
@IToolsService private readonly toolsService: IToolsService,
@ICopilotCLISDK private readonly copilotCLISDK: ICopilotCLISDK
) {
super();
this.sessionId = _sdkSession.sessionId;
}
public override dispose(): void {
this._abortController.abort();
super.dispose();
}
async *query(prompt: string, attachments: Attachment[], options: AgentOptions): AsyncGenerator<SessionEvent> {
// Dynamically import the SDK
const { Agent } = await this.copilotCLISDK.getPackage();
const agent = new Agent(options);
yield* agent.query(prompt, attachments);
}
public async invoke(
prompt: string,
attachments: Attachment[],
toolInvocationToken: vscode.ChatParticipantToolToken,
stream: vscode.ChatResponseStream,
modelId: ModelProvider | undefined,
token: vscode.CancellationToken
): Promise<void> {
if (this.isDisposed) {
throw new Error('Session disposed');
}
this._status = ChatSessionStatus.InProgress;
this._statusChange.fire(this._status);
this.logService.trace(`[CopilotCLISession] Invoking session ${this.sessionId}`);
const copilotToken = await this._authenticationService.getCopilotToken();
const options: AgentOptions = {
modelProvider: modelId ?? {
type: 'anthropic',
model: 'claude-sonnet-4.5',
},
abortController: this._abortController,
// TODO@rebornix handle workspace properly
workingDirectory: this.workspaceService.getWorkspaceFolders().at(0)?.fsPath,
copilotToken: copilotToken.token,
env: {
...process.env,
COPILOTCLI_DISABLE_NONESSENTIAL_TRAFFIC: '1'
},
requestPermission: async (permissionRequest) => {
return await this.requestPermission(permissionRequest, toolInvocationToken);
},
logger: getCopilotLogger(this.logService),
session: this._sdkSession,
hooks: {
preToolUse: [
async (input: PreToolUseHookInput) => {
const editKey = getEditOperationKey(input.toolName, input.toolArgs);
await this._onWillEditTool(input, editKey, stream);
}
],
postToolUse: [
async (input: PostToolUseHookInput) => {
const editKey = getEditOperationKey(input.toolName, input.toolArgs);
void this._onDidEditTool(editKey);
}
]
}
};
try {
for await (const event of this.query(prompt, attachments, options)) {
if (token.isCancellationRequested) {
break;
}
this._processEvent(event, stream, toolInvocationToken);
}
this._status = ChatSessionStatus.Completed;
this._statusChange.fire(this._status);
} catch (error) {
this._status = ChatSessionStatus.Failed;
this._statusChange.fire(this._status);
this.logService.error(`CopilotCLI session error: ${error}`);
stream.markdown(`\n\n❌ Error: ${error instanceof Error ? error.message : String(error)}`);
}
}
private _toolNames = new Map<string, string>();
private _processEvent(
event: SessionEvent,
stream: vscode.ChatResponseStream,
toolInvocationToken: vscode.ChatParticipantToolToken
): void {
this.logService.trace(`CopilotCLI Event: ${JSON.stringify(event, null, 2)}`);
switch (event.type) {
case 'assistant.turn_start':
case 'assistant.turn_end': {
this._toolNames.clear();
break;
}
case 'assistant.message': {
if (event.data.content.length) {
stream.markdown(event.data.content);
}
break;
}
case 'tool.execution_start': {
const responsePart = processToolExecutionStart(event, this._toolNames, this._pendingToolInvocations);
const toolName = this._toolNames.get(event.data.toolCallId);
if (responsePart instanceof ChatResponseThinkingProgressPart) {
stream.push(responsePart);
}
this.logService.trace(`Start Tool ${toolName || '<unknown>'}`);
break;
}
case 'tool.execution_complete': {
const responsePart = processToolExecutionComplete(event, this._pendingToolInvocations);
if (responsePart && !(responsePart instanceof ChatResponseThinkingProgressPart)) {
stream.push(responsePart);
}
const toolName = this._toolNames.get(event.data.toolCallId) || '<unknown>';
const success = `success: ${event.data.success}`;
const error = event.data.error ? `error: ${event.data.error.code},${event.data.error.message}` : '';
const result = event.data.result ? `result: ${event.data.result?.content}` : '';
const parts = [success, error, result].filter(part => part.length > 0).join(', ');
this.logService.trace(`Complete Tool ${toolName}, ${parts}`);
break;
}
case 'session.error': {
this.logService.error(`CopilotCLI error: (${event.data.errorType}), ${event.data.message}`);
stream.markdown(`\n\n❌ Error: ${event.data.message}`);
break;
}
}
}
private async requestPermission(
permissionRequest: PermissionRequest,
toolInvocationToken: vscode.ChatParticipantToolToken
): Promise<{ kind: 'approved' } | { kind: 'denied-interactively-by-user' }> {
try {
const { tool, input } = getConfirmationToolParams(permissionRequest);
const result = await this.toolsService.invokeTool(tool,
{ input, toolInvocationToken },
CancellationToken.None);
const firstResultPart = result.content.at(0);
if (firstResultPart instanceof LanguageModelTextPart && firstResultPart.value === 'yes') {
return { kind: 'approved' };
}
} catch (error) {
this.logService.error(`[CopilotCLISession] Permission request error: ${error}`);
}
return { kind: 'denied-interactively-by-user' };
}
private async _onWillEditTool(input: PreToolUseHookInput, editKey: string, stream: vscode.ChatResponseStream): Promise<void> {
const uris = getAffectedUrisForEditTool(input.toolName, input.toolArgs);
return this._editTracker.trackEdit(editKey, uris, stream);
}
private async _onDidEditTool(editKey: string): Promise<void> {
return this._editTracker.completeEdit(editKey);
}
}
function getEditOperationKey(toolName: string, toolArgs: unknown): string {
// todo@connor4312: get copilot CLI to surface the tool call ID instead?
return `${toolName}:${JSON.stringify(toolArgs)}`;
}
@@ -15,12 +15,13 @@ import { ICopilotCLISDK } from './copilotCli';
import { stripReminders } from './copilotcliToolInvocationFormatter';
import { getCopilotLogger } from './logger';
export interface ICopilotCLISession {
export interface ICopilotCLISessionItem {
readonly id: string;
readonly sdkSession: Session;
readonly label: string;
readonly isEmpty: boolean;
readonly timestamp: Date;
readonly status?: ChatSessionStatus;
}
export type ExtendedChatRequest = ChatRequest & { prompt: string };
@@ -31,23 +32,20 @@ export interface ICopilotCLISessionService {
onDidChangeSessions: Event<void>;
// Session metadata querying
getAllSessions(token: CancellationToken): Promise<readonly ICopilotCLISession[]>;
getSession(sessionId: string, token: CancellationToken): Promise<ICopilotCLISession | undefined>;
getAllSessions(token: CancellationToken): Promise<readonly ICopilotCLISessionItem[]>;
getSession(sessionId: string, token: CancellationToken): Promise<ICopilotCLISessionItem | undefined>;
// SDK session management
getSessionManager(): Promise<SessionManager>;
getOrCreateSDKSession(sessionId: string | undefined, prompt: string): Promise<Session>;
deleteSession(sessionId: string): Promise<boolean>;
setSessionStatus(sessionId: string, status: ChatSessionStatus): void;
getSessionStatus(sessionId: string): ChatSessionStatus | undefined;
// Session wrapper tracking
trackSessionWrapper<T extends IDisposable>(sessionId: string, wrapper: T): void;
findSessionWrapper<T extends IDisposable>(sessionId: string): T | undefined;
// Pending request tracking (for untitled sessions)
setPendingRequest(sessionId: string): void;
isPendingRequest(sessionId: string): boolean;
clearPendingRequest(sessionId: string): void;
}
@@ -58,7 +56,7 @@ export class CopilotCLISessionService implements ICopilotCLISessionService {
private _sessionManager: SessionManager | undefined;
private _sessionWrappers = new DisposableMap<string, IDisposable>();
private _sessions = new Map<string, ICopilotCLISession>();
private _sessions = new Map<string, ICopilotCLISessionItem>();
private _pendingRequests = new Set<string>();
@@ -81,14 +79,17 @@ export class CopilotCLISessionService implements ICopilotCLISessionService {
return this._sessionManager;
}
async getAllSessions(token: CancellationToken): Promise<readonly ICopilotCLISession[]> {
async getAllSessions(token: CancellationToken): Promise<readonly ICopilotCLISessionItem[]> {
try {
const sessionManager = await this.getSessionManager();
const sessionMetadataList = await sessionManager.listSessions();
// Convert SessionMetadata to ICopilotCLISession
const diskSessions: ICopilotCLISession[] = coalesce(await Promise.all(
const diskSessions: ICopilotCLISessionItem[] = coalesce(await Promise.all(
sessionMetadataList.map(async (metadata) => {
if (this.isPendingRequest(metadata.sessionId)) {
return undefined;
}
try {
// Get the full session to access chat messages
const sdkSession = await sessionManager.getSession(metadata.sessionId);
@@ -129,14 +130,21 @@ export class CopilotCLISessionService implements ICopilotCLISessionService {
const cachedSessions = Array.from(this._sessions.values()).filter(s => !diskSessionIds.has(s.id));
const allSessions = [...diskSessions, ...cachedSessions];
return allSessions;
return allSessions
.filter(session => !this.isPendingRequest(session.id) && !session.isEmpty)
.map(session => {
return {
...session,
status: this._sessionStatuses.get(session.id)
};
});
} catch (error) {
this.logService.error(`Failed to get all sessions: ${error}`);
return Array.from(this._sessions.values());
}
}
async getSession(sessionId: string, token: CancellationToken): Promise<ICopilotCLISession | undefined> {
async getSession(sessionId: string, token: CancellationToken): Promise<ICopilotCLISessionItem | undefined> {
const cached = this._sessions.get(sessionId);
if (cached) {
return cached;
@@ -167,12 +175,12 @@ export class CopilotCLISessionService implements ICopilotCLISessionService {
}
const sdkSession = await sessionManager.createSession();
this.setPendingRequest(sdkSession.sessionId);
// Cache the new session immediately
const chatMessages = await sdkSession.getChatMessages();
const noUserMessages = !chatMessages.find(message => message.role === 'user');
const label = await this._generateSessionLabel(sdkSession.sessionId, chatMessages, prompt);
const newSession: ICopilotCLISession = {
const newSession: ICopilotCLISessionItem = {
id: sdkSession.sessionId,
sdkSession,
label,
@@ -189,10 +197,6 @@ export class CopilotCLISessionService implements ICopilotCLISessionService {
this._onDidChangeSessions.fire();
}
public getSessionStatus(sessionId: string): ChatSessionStatus | undefined {
return this._sessionStatuses.get(sessionId);
}
public trackSessionWrapper<T extends IDisposable>(sessionId: string, wrapper: T): void {
this._sessionWrappers.set(sessionId, wrapper);
}
@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type { Session } from '@github/copilot/sdk';
import * as vscode from 'vscode';
import { ChatExtendedRequestHandler, l10n, Uri } from 'vscode';
import { IGitService } from '../../../platform/git/common/gitService';
@@ -98,14 +99,14 @@ export class CopilotCLIChatSessionItemProvider extends Disposable implements vsc
public async provideChatSessionItems(token: vscode.CancellationToken): Promise<vscode.ChatSessionItem[]> {
const sessions = await this.copilotcliSessionService.getAllSessions(token);
const diskSessions = sessions.filter(session => !this.copilotcliSessionService.isPendingRequest(session.id) && !session.isEmpty).map(session => ({
const diskSessions = sessions.map(session => ({
resource: SessionIdForCLI.getResource(session.id),
label: session.label,
tooltip: `Copilot CLI session: ${session.label}`,
timing: {
startTime: session.timestamp.getTime()
},
status: this.copilotcliSessionService.getSessionStatus(session.id) ?? vscode.ChatSessionStatus.Completed,
status: session.status ?? vscode.ChatSessionStatus.Completed,
} satisfies vscode.ChatSessionItem));
const count = diskSessions.length;
@@ -238,23 +239,26 @@ export class CopilotCLIChatSessionParticipant {
const { resource } = chatSessionContext.chatSessionItem;
const id = SessionIdForCLI.parse(resource);
if (request.acceptedConfirmationData || request.rejectedConfirmationData) {
return await this.handleConfirmationData(id, request, context, stream, token);
}
if (request.prompt.startsWith('/delegate')) {
await this.handleDelegateCommand(id, request, context, stream, token);
const session = await this.sessionService.getSession(id, token);
if (!session) {
stream.warning(vscode.l10n.t('Chat session not found.'));
return {};
}
if (request.acceptedConfirmationData || request.rejectedConfirmationData) {
return await this.handleConfirmationData(session.sdkSession, request, context, stream, token);
}
if (request.prompt.startsWith('/delegate')) {
await this.handleDelegateCommand(session.sdkSession, request, context, stream, token);
return {};
}
this.sessionService.setSessionStatus(id, vscode.ChatSessionStatus.InProgress);
await this.copilotcliAgentManager.handleRequest(id, request, context, stream, getModelProvider(_sessionModel.get(id)?.id), token);
this.sessionService.setSessionStatus(id, vscode.ChatSessionStatus.Completed);
return {};
}
private async handleDelegateCommand(id: string, request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) {
private async handleDelegateCommand(session: Session, request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) {
if (!this.cloudSessionProvider) {
stream.warning(localize('copilotcli.missingCloudAgent', "No cloud agent available"));
return {};
@@ -281,12 +285,12 @@ export class CopilotCLIChatSessionParticipant {
chatContext: context
}, stream, token);
if (prInfo) {
await this.recordPushToSession(id, request.prompt, prInfo, token);
await this.recordPushToSession(session, request.prompt, prInfo, token);
}
}
}
private async handleConfirmationData(id: string, request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) {
private async handleConfirmationData(session: Session, request: vscode.ChatRequest, context: vscode.ChatContext, stream: vscode.ChatResponseStream, token: vscode.CancellationToken) {
const results: ConfirmationResult[] = [];
results.push(...(request.acceptedConfirmationData?.map(data => ({ step: data.step, accepted: true, metadata: data?.metadata })) ?? []));
results.push(...((request.rejectedConfirmationData ?? []).filter(data => !results.some(r => r.step === data.step)).map(data => ({ step: data.step, accepted: false, metadata: data?.metadata }))));
@@ -305,7 +309,7 @@ export class CopilotCLIChatSessionParticipant {
chatContext: context
}, stream, token);
if (prInfo) {
await this.recordPushToSession(id, request.prompt, prInfo, token);
await this.recordPushToSession(session, request.prompt, prInfo, token);
}
return {};
}
@@ -335,18 +339,13 @@ export class CopilotCLIChatSessionParticipant {
}
private async recordPushToSession(
sessionId: string,
session: Session,
userPrompt: string,
prInfo: { uri: string; title: string; description: string; author: string; linkTag: string },
token: vscode.CancellationToken
): Promise<void> {
const session = await this.sessionService.getSession(sessionId, token);
if (!session) {
return;
}
// Add user message event
session.sdkSession.addEvent({
session.addEvent({
type: 'user.message',
data: {
content: userPrompt
@@ -355,7 +354,7 @@ export class CopilotCLIChatSessionParticipant {
// Add assistant message event with embedded PR metadata
const assistantMessage = `GitHub Copilot cloud agent has begun working on your request. Follow its progress in the associated chat and pull request.\n<pr_metadata uri="${prInfo.uri}" title="${escapeXml(prInfo.title)}" description="${escapeXml(prInfo.description)}" author="${escapeXml(prInfo.author)}" linkTag="${escapeXml(prInfo.linkTag)}"/>`;
session.sdkSession.addEvent({
session.addEvent({
type: 'assistant.message',
data: {
messageId: `msg_${Date.now()}`,