@@ -748,4 +748,201 @@ export class CopilotRemoteAgentManager extends Disposable {
748748 }
749749 return [ ] ;
750750 }
751- }
751+
752+ public async provideChatSessionContent ( id : string , token : vscode . CancellationToken ) : Promise < vscode . ChatSession > {
753+ try {
754+ const capi = await this . copilotApi ;
755+ if ( ! capi || token . isCancellationRequested ) {
756+ return this . createEmptySession ( ) ;
757+ }
758+
759+ const pullRequestId = parseInt ( id ) ;
760+ if ( isNaN ( pullRequestId ) ) {
761+ Logger . error ( `Invalid pull request ID: ${ id } ` , CopilotRemoteAgentManager . ID ) ;
762+ return this . createEmptySession ( ) ;
763+ }
764+
765+ // Find the pull request model
766+ const pullRequest = this . findPullRequestById ( pullRequestId ) ;
767+ if ( ! pullRequest ) {
768+ Logger . error ( `Pull request not found: ${ pullRequestId } ` , CopilotRemoteAgentManager . ID ) ;
769+ return this . createEmptySession ( ) ;
770+ }
771+
772+ // Get session logs
773+ const sessionLogs = await this . getSessionLogFromPullRequest ( pullRequest ) ;
774+ if ( ! sessionLogs ) {
775+ Logger . warn ( `No session logs found for pull request ${ pullRequestId } ` , CopilotRemoteAgentManager . ID ) ;
776+ return this . createEmptySession ( ) ;
777+ }
778+
779+ // Parse logs and create chat history
780+ const history = await this . parseChatHistoryFromLogs ( sessionLogs . logs , pullRequest . title ) ;
781+
782+ return {
783+ history,
784+ requestHandler : undefined // Read-only session
785+ } ;
786+ } catch ( error ) {
787+ Logger . error ( `Failed to provide chat session content: ${ error } ` , CopilotRemoteAgentManager . ID ) ;
788+ return this . createEmptySession ( ) ;
789+ }
790+ }
791+
792+ private createEmptySession ( ) : vscode . ChatSession {
793+ return {
794+ history : [ ] ,
795+ requestHandler : undefined
796+ } ;
797+ }
798+
799+ private findPullRequestById ( id : number ) : PullRequestModel | undefined {
800+ for ( const folderManager of this . repositoriesManager . folderManagers ) {
801+ for ( const githubRepo of folderManager . gitHubRepositories ) {
802+ const pullRequest = githubRepo . pullRequestModels . find ( pr => pr . id === id ) ;
803+ if ( pullRequest ) {
804+ return pullRequest ;
805+ }
806+ }
807+ }
808+ return undefined ;
809+ }
810+
811+ private async parseChatHistoryFromLogs ( logs : string , prTitle : string ) : Promise < ReadonlyArray < vscode . ChatRequestTurn | vscode . ChatResponseTurn2 > > {
812+ try {
813+ const logChunks = parseSessionLogs ( logs ) ;
814+
815+ const history : Array < vscode . ChatRequestTurn | vscode . ChatResponseTurn2 > = [ ] ;
816+
817+ // Insert the initial user request with the PR title
818+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
819+ // @ts -ignore - Constructor will be made public
820+ const initialUserRequest = new vscode . ChatRequestTurn (
821+ prTitle ,
822+ undefined , // command
823+ [ ] , // references
824+ 'copilot-swe-agent' ,
825+ [ ] // toolReferences
826+ ) ;
827+ history . push ( initialUserRequest ) ;
828+
829+ let currentRequestContent = '' ;
830+ let currentResponseContent = '' ;
831+ let isCollectingUserMessage = false ;
832+
833+ for ( const chunk of logChunks ) {
834+ for ( const choice of chunk . choices ) {
835+ const delta = choice . delta ;
836+
837+ if ( delta . role === 'user' ) {
838+ // If we were collecting a response, finalize it
839+ if ( currentResponseContent . trim ( ) ) {
840+ const responseParts = [ new vscode . ChatResponseMarkdownPart ( currentResponseContent . trim ( ) ) ] ;
841+ const responseResult : vscode . ChatResult = { } ;
842+ history . push ( new vscode . ChatResponseTurn2 ( responseParts , responseResult , 'copilot-swe-agent' ) ) ;
843+ currentResponseContent = '' ;
844+ }
845+
846+ isCollectingUserMessage = true ;
847+ if ( delta . content ) {
848+ currentRequestContent += delta . content ;
849+ }
850+ } else if ( delta . role === 'assistant' ) {
851+ // If we were collecting a user message, finalize it
852+ if ( isCollectingUserMessage && currentRequestContent . trim ( ) ) {
853+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
854+ // @ts -ignore - Constructor will be made public
855+ const userMessage = new vscode . ChatRequestTurn (
856+ currentRequestContent . trim ( ) ,
857+ undefined , // command
858+ [ ] , // references
859+ 'copilot-swe-agent' ,
860+ [ ] // toolReferences
861+ ) ;
862+ history . push ( userMessage ) ;
863+ currentRequestContent = '' ;
864+ isCollectingUserMessage = false ;
865+ }
866+
867+ if ( delta . content ) {
868+ currentResponseContent += delta . content ;
869+ }
870+
871+ // Handle tool calls as code blocks in the response
872+ if ( delta . tool_calls ) {
873+ for ( const toolCall of delta . tool_calls ) {
874+ if ( toolCall . function ?. name && toolCall . function ?. arguments ) {
875+ currentResponseContent += `\n\n**Tool Call: ${ toolCall . function . name } **\n\`\`\`json\n${ toolCall . function . arguments } \n\`\`\`\n` ;
876+ }
877+ }
878+ }
879+ }
880+ }
881+ }
882+
883+ // Finalize any remaining content
884+ if ( isCollectingUserMessage && currentRequestContent . trim ( ) ) {
885+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
886+ // @ts -ignore - Constructor will be made public
887+ const userMessage = new vscode . ChatRequestTurn (
888+ currentRequestContent . trim ( ) ,
889+ undefined , // command
890+ [ ] , // references
891+ 'copilot-swe-agent' ,
892+ [ ] // toolReferences
893+ ) ;
894+ history . push ( userMessage ) ;
895+ } else if ( currentResponseContent . trim ( ) ) {
896+ const responseParts = [ new vscode . ChatResponseMarkdownPart ( currentResponseContent . trim ( ) ) ] ;
897+ const responseResult : vscode . ChatResult = { } ;
898+ history . push ( new vscode . ChatResponseTurn2 ( responseParts , responseResult , 'copilot-swe-agent' ) ) ;
899+ }
900+
901+ return history ;
902+ } catch ( error ) {
903+ Logger . error ( `Failed to parse chat history from logs: ${ error } ` , CopilotRemoteAgentManager . ID ) ;
904+ return [ ] ;
905+ }
906+ }
907+ }
908+
909+ function parseSessionLogs ( rawText : string ) : SessionResponseLogChunk [ ] {
910+ const parts = rawText
911+ . split ( / \r ? \n / )
912+ . filter ( part => part . startsWith ( 'data: ' ) )
913+ . map ( part => part . slice ( 'data: ' . length ) . trim ( ) )
914+ . map ( part => JSON . parse ( part ) ) ;
915+
916+ return parts as SessionResponseLogChunk [ ] ;
917+ }
918+
919+ export interface SessionResponseLogChunk {
920+ choices : Array < {
921+ finish_reason : string ;
922+ delta : {
923+ content ?: string ;
924+ role : string ;
925+ tool_calls ?: Array < {
926+ function : {
927+ arguments : string ;
928+ name : string ;
929+ } ;
930+ id : string ;
931+ type : string ;
932+ index : number ;
933+ } > ;
934+ } ;
935+ } > ;
936+ created : number ;
937+ id : string ;
938+ usage : {
939+ completion_tokens : number ;
940+ prompt_tokens : number ;
941+ prompt_tokens_details : {
942+ cached_tokens : number ;
943+ } ;
944+ total_tokens : number ;
945+ } ;
946+ model : string ;
947+ object : string ;
948+ }
0 commit comments