@@ -27,7 +27,7 @@ import { isUntitledSessionId } from '../common/utils';
2727import { IChatDelegationSummaryService } from '../copilotcli/common/delegationSummaryService' ;
2828import { body_suffix , CONTINUE_TRUNCATION , extractTitle , formatBodyPlaceholder , getAuthorDisplayName , getRepoId , JOBS_API_VERSION , SessionIdForPr , toOpenPullRequestWebviewUri , truncatePrompt } from '../vscode/copilotCodingAgentUtils' ;
2929import { CopilotCloudGitOperationsManager } from './copilotCloudGitOperationsManager' ;
30- import { ChatSessionContentBuilder } from './copilotCloudSessionContentBuilder' ;
30+ import { ChatSessionContentBuilder , SessionResponseLogChunk } from './copilotCloudSessionContentBuilder' ;
3131import { IPullRequestFileChangesService } from './pullRequestFileChangesService' ;
3232import MarkdownIt = require( 'markdown-it' ) ;
3333
@@ -39,6 +39,11 @@ interface ConfirmationMetadata {
3939 chatContext : vscode . ChatContext ;
4040}
4141
42+ type InitialSessionOption = {
43+ readonly optionId : string ;
44+ readonly value : string | vscode . ChatSessionProviderOptionItem ;
45+ } ;
46+
4247function validateMetadata ( metadata : unknown ) : asserts metadata is ConfirmationMetadata {
4348 if ( typeof metadata !== 'object' ) {
4449 throw new Error ( 'Invalid confirmation metadata: not an object.' ) ;
@@ -54,6 +59,82 @@ function validateMetadata(metadata: unknown): asserts metadata is ConfirmationMe
5459 }
5560}
5661
62+ function describeRuntimeValue ( value : unknown ) : string {
63+ if ( Array . isArray ( value ) ) {
64+ return `array(length=${ value . length } )` ;
65+ }
66+
67+ if ( value === null ) {
68+ return 'null' ;
69+ }
70+
71+ if ( value === undefined ) {
72+ return 'undefined' ;
73+ }
74+
75+ if ( typeof value === 'object' ) {
76+ const keys = Object . keys ( value ) ;
77+ return `object(keys=${ keys . slice ( 0 , 5 ) . join ( ',' ) } ${ keys . length > 5 ? ',…' : '' } )` ;
78+ }
79+
80+ return typeof value ;
81+ }
82+
83+ function isOptionItemValue ( value : unknown ) : value is vscode . ChatSessionProviderOptionItem {
84+ return typeof value === 'object' && value !== null && 'id' in value && typeof value . id === 'string' ;
85+ }
86+
87+ function isInitialSessionOption ( value : unknown ) : value is InitialSessionOption {
88+ if ( typeof value !== 'object' || value === null || ! ( 'optionId' in value ) || typeof value . optionId !== 'string' || ! ( 'value' in value ) ) {
89+ return false ;
90+ }
91+
92+ return typeof value . value === 'string' || isOptionItemValue ( value . value ) ;
93+ }
94+
95+ export function normalizeInitialSessionOptions ( initialOptions : unknown , logService ?: ILogService , chatResource ?: vscode . Uri ) : readonly InitialSessionOption [ ] {
96+ if ( ! initialOptions ) {
97+ return [ ] ;
98+ }
99+
100+ if ( Array . isArray ( initialOptions ) ) {
101+ const normalized = initialOptions . filter ( isInitialSessionOption ) ;
102+ if ( logService && normalized . length !== initialOptions . length ) {
103+ logService . warn ( `[chatParticipantImpl] Ignoring ${ initialOptions . length - normalized . length } malformed initialSessionOptions entries for ${ chatResource ?. toString ( ) ?? 'unknown-resource' } . Received ${ describeRuntimeValue ( initialOptions ) } .` ) ;
104+ }
105+
106+ return normalized ;
107+ }
108+
109+ if ( typeof initialOptions === 'object' ) {
110+ const normalized : InitialSessionOption [ ] = [ ] ;
111+ for ( const [ optionId , value ] of Object . entries ( initialOptions ) ) {
112+ if ( isInitialSessionOption ( value ) ) {
113+ normalized . push ( value ) ;
114+ } else if ( typeof value === 'string' || isOptionItemValue ( value ) ) {
115+ normalized . push ( { optionId, value } ) ;
116+ }
117+ }
118+
119+ if ( normalized . length > 0 ) {
120+ logService ?. warn ( `[chatParticipantImpl] Coerced object-shaped initialSessionOptions for ${ chatResource ?. toString ( ) ?? 'unknown-resource' } . Received ${ describeRuntimeValue ( initialOptions ) } and recovered ${ normalized . length } entries.` ) ;
121+ return normalized ;
122+ }
123+ }
124+
125+ logService ?. warn ( `[chatParticipantImpl] Ignoring unsupported initialSessionOptions for ${ chatResource ?. toString ( ) ?? 'unknown-resource' } . Received ${ describeRuntimeValue ( initialOptions ) } .` ) ;
126+ return [ ] ;
127+ }
128+
129+ export function parseSessionLogChunksSafely ( rawText : string , logService : ILogService , parser : ( value : string ) => SessionResponseLogChunk [ ] ) : SessionResponseLogChunk [ ] {
130+ try {
131+ return parser ( rawText ) ;
132+ } catch ( error ) {
133+ logService . error ( error instanceof Error ? error : new Error ( String ( error ) ) , `[streamNewLogContent] Failed to parse streamed log content (${ rawText . length } chars).` ) ;
134+ return [ ] ;
135+ }
136+ }
137+
57138const CUSTOM_AGENTS_OPTION_GROUP_ID = 'customAgents' ;
58139const MODELS_OPTION_GROUP_ID = 'models' ;
59140const PARTNER_AGENTS_OPTION_GROUP_ID = 'partnerAgents' ;
@@ -1841,8 +1922,11 @@ export class CopilotCloudSessionsProvider extends Disposable implements vscode.C
18411922 const chatResource = context . chatSessionContext ?. chatSessionItem ?. resource ;
18421923
18431924 const initialOptions = context . chatSessionContext ?. initialSessionOptions ;
1844- if ( chatResource && initialOptions ) {
1845- for ( const opt of initialOptions ) {
1925+ if ( chatResource ) {
1926+ this . logService . trace ( `[chatParticipantImpl] initialSessionOptions for ${ chatResource . toString ( ) } : ${ describeRuntimeValue ( initialOptions ) } ` ) ;
1927+ }
1928+ if ( chatResource ) {
1929+ for ( const opt of normalizeInitialSessionOptions ( initialOptions , this . logService , chatResource ) ) {
18461930 const value = typeof opt . value === 'string' ? opt . value : opt . value . id ;
18471931 if ( opt . optionId === CUSTOM_AGENTS_OPTION_GROUP_ID ) {
18481932 this . sessionCustomAgentMap . set ( chatResource , value ) ;
@@ -2152,19 +2236,32 @@ export class CopilotCloudSessionsProvider extends Disposable implements vscode.C
21522236
21532237 // Parse the new log content
21542238 const contentBuilder = new ChatSessionContentBuilder ( CopilotCloudSessionsProvider . TYPE , this . _gitService ) ;
2155-
2156- const logChunks = contentBuilder . parseSessionLogs ( newLogContent ) ;
2239+ const logChunks = parseSessionLogChunksSafely ( newLogContent , this . logService , value => contentBuilder . parseSessionLogs ( value ) ) ;
21572240 let hasStreamedContent = false ;
21582241 let hasSetupStepProgress = false ;
21592242
2160- for ( const chunk of logChunks ) {
2243+ for ( const [ chunkIndex , chunk ] of logChunks . entries ( ) ) {
2244+ if ( ! Array . isArray ( chunk . choices ) ) {
2245+ this . logService . warn ( `[streamNewLogContent] Ignoring chunk ${ chunkIndex } with non-array choices for PR #${ pullRequest . number } .` ) ;
2246+ continue ;
2247+ }
2248+
21612249 for ( const choice of chunk . choices ) {
2250+ if ( ! choice ?. delta ) {
2251+ this . logService . warn ( `[streamNewLogContent] Ignoring chunk ${ chunkIndex } with missing delta for PR #${ pullRequest . number } .` ) ;
2252+ continue ;
2253+ }
2254+
21622255 const delta = choice . delta ;
2256+ const toolCalls = Array . isArray ( delta . tool_calls ) ? delta . tool_calls : undefined ;
2257+ if ( delta . tool_calls && ! toolCalls ) {
2258+ this . logService . warn ( `[streamNewLogContent] Ignoring non-array tool_calls for PR #${ pullRequest . number } .` ) ;
2259+ }
21632260
21642261 if ( delta . role === 'assistant' ) {
21652262 // Handle special case for run_custom_setup_step/run_setup
2166- if ( choice . finish_reason === 'tool_calls' && delta . tool_calls ?. length && ( delta . tool_calls [ 0 ] . function . name === 'run_custom_setup_step' || delta . tool_calls [ 0 ] . function . name === 'run_setup' ) ) {
2167- const toolCall = delta . tool_calls [ 0 ] ;
2263+ if ( choice . finish_reason === 'tool_calls' && toolCalls ?. length && ( toolCalls [ 0 ] . function . name === 'run_custom_setup_step' || toolCalls [ 0 ] . function . name === 'run_setup' ) ) {
2264+ const toolCall = toolCalls [ 0 ] ;
21682265 let args : any = { } ;
21692266 try {
21702267 args = JSON . parse ( toolCall . function . arguments ) ;
@@ -2195,8 +2292,8 @@ export class CopilotCloudSessionsProvider extends Disposable implements vscode.C
21952292 }
21962293 }
21972294
2198- if ( delta . tool_calls ) {
2199- for ( const toolCall of delta . tool_calls ) {
2295+ if ( toolCalls ) {
2296+ for ( const toolCall of toolCalls ) {
22002297 const toolPart = contentBuilder . createToolInvocationPart ( pullRequest , toolCall , delta . content || '' ) ;
22012298 if ( toolPart ) {
22022299 stream . push ( toolPart ) ;
0 commit comments