Skip to content

Commit 07ad537

Browse files
authored
streaming in setup logs from session log api properly (#7435)
1 parent d9c1eb5 commit 07ad537

File tree

2 files changed

+95
-28
lines changed

2 files changed

+95
-28
lines changed

src/github/copilotRemoteAgent.ts

Lines changed: 84 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,35 +1082,60 @@ export class CopilotRemoteAgentManager extends Disposable {
10821082
};
10831083
}
10841084

1085-
private async streamNewLogContent(stream: vscode.ChatResponseStream, newLogContent: string): Promise<boolean> {
1085+
private async streamNewLogContent(stream: vscode.ChatResponseStream, newLogContent: string): Promise<{ hasStreamedContent: boolean; hasSetupStepProgress: boolean }> {
10861086
try {
10871087
if (!newLogContent.trim()) {
1088-
return false;
1088+
return { hasStreamedContent: false, hasSetupStepProgress: false };
10891089
}
10901090

10911091
// Parse the new log content
10921092
const logChunks = parseSessionLogs(newLogContent);
10931093
let hasStreamedContent = false;
1094+
let hasSetupStepProgress = false;
10941095

10951096
for (const chunk of logChunks) {
10961097
for (const choice of chunk.choices) {
10971098
const delta = choice.delta;
10981099

10991100
if (delta.role === 'assistant') {
1100-
if (delta.content) {
1101-
if (!delta.content.startsWith('<pr_title>')) {
1102-
stream.markdown(delta.content);
1103-
hasStreamedContent = true;
1101+
// Handle special case for run_custom_setup_step
1102+
if (choice.finish_reason === 'tool_calls' && delta.tool_calls?.length && delta.tool_calls[0].function.name === 'run_custom_setup_step') {
1103+
const toolCall = delta.tool_calls[0];
1104+
let args: any = {};
1105+
try {
1106+
args = JSON.parse(toolCall.function.arguments);
1107+
} catch {
1108+
// fallback to empty args
11041109
}
1105-
}
11061110

1107-
if (delta.tool_calls) {
1108-
for (const toolCall of delta.tool_calls) {
1109-
const toolPart = this.createToolInvocationPart(toolCall, delta.content || '');
1111+
if (delta.content && delta.content.trim()) {
1112+
// Finished setup step - create/update tool part
1113+
const toolPart = this.createToolInvocationPart(toolCall, args.name || delta.content);
11101114
if (toolPart) {
11111115
stream.push(toolPart);
11121116
hasStreamedContent = true;
11131117
}
1118+
} else {
1119+
// Running setup step - just track progress
1120+
hasSetupStepProgress = true;
1121+
Logger.appendLine(`Setup step in progress: ${args.name || 'Unknown step'}`, CopilotRemoteAgentManager.ID);
1122+
}
1123+
} else {
1124+
if (delta.content) {
1125+
if (!delta.content.startsWith('<pr_title>')) {
1126+
stream.markdown(delta.content);
1127+
hasStreamedContent = true;
1128+
}
1129+
}
1130+
1131+
if (delta.tool_calls) {
1132+
for (const toolCall of delta.tool_calls) {
1133+
const toolPart = this.createToolInvocationPart(toolCall, delta.content || '');
1134+
if (toolPart) {
1135+
stream.push(toolPart);
1136+
hasStreamedContent = true;
1137+
}
1138+
}
11141139
}
11151140
}
11161141
}
@@ -1124,13 +1149,15 @@ export class CopilotRemoteAgentManager extends Disposable {
11241149

11251150
if (hasStreamedContent) {
11261151
Logger.appendLine(`Streamed content (markdown or tool parts), progress should be cleared`, CopilotRemoteAgentManager.ID);
1152+
} else if (hasSetupStepProgress) {
1153+
Logger.appendLine(`Setup step progress detected, keeping progress indicator`, CopilotRemoteAgentManager.ID);
11271154
} else {
11281155
Logger.appendLine(`No actual content streamed, progress may still be showing`, CopilotRemoteAgentManager.ID);
11291156
}
1130-
return hasStreamedContent;
1157+
return { hasStreamedContent, hasSetupStepProgress };
11311158
} catch (error) {
11321159
Logger.error(`Error streaming new log content: ${error}`, CopilotRemoteAgentManager.ID);
1133-
return false;
1160+
return { hasStreamedContent: false, hasSetupStepProgress: false };
11341161
}
11351162
}
11361163

@@ -1181,8 +1208,8 @@ export class CopilotRemoteAgentManager extends Disposable {
11811208
if (sessionInfo.state !== 'in_progress') {
11821209
if (logs.length > lastProcessedLength) {
11831210
const newLogContent = logs.slice(lastProcessedLength);
1184-
const didStreamContent = await this.streamNewLogContent(stream, newLogContent);
1185-
if (didStreamContent) {
1211+
const streamResult = await this.streamNewLogContent(stream, newLogContent);
1212+
if (streamResult.hasStreamedContent) {
11861213
hasActiveProgress = false;
11871214
}
11881215
}
@@ -1194,12 +1221,15 @@ export class CopilotRemoteAgentManager extends Disposable {
11941221
if (logs.length > lastLogLength) {
11951222
Logger.appendLine(`New logs detected, attempting to stream content`, CopilotRemoteAgentManager.ID);
11961223
const newLogContent = logs.slice(lastProcessedLength);
1197-
const didStreamContent = await this.streamNewLogContent(stream, newLogContent);
1224+
const streamResult = await this.streamNewLogContent(stream, newLogContent);
11981225
lastProcessedLength = logs.length;
11991226

1200-
if (didStreamContent) {
1227+
if (streamResult.hasStreamedContent) {
12011228
Logger.appendLine(`Content was streamed, resetting hasActiveProgress to false`, CopilotRemoteAgentManager.ID);
12021229
hasActiveProgress = false;
1230+
} else if (streamResult.hasSetupStepProgress) {
1231+
Logger.appendLine(`Setup step progress detected, keeping progress active`, CopilotRemoteAgentManager.ID);
1232+
// Keep hasActiveProgress as is, don't reset it
12031233
} else {
12041234
Logger.appendLine(`No content was streamed, keeping hasActiveProgress as ${hasActiveProgress}`, CopilotRemoteAgentManager.ID);
12051235
}
@@ -1343,25 +1373,51 @@ export class CopilotRemoteAgentManager extends Disposable {
13431373
const delta = choice.delta;
13441374

13451375
if (delta.role === 'assistant') {
1346-
if (delta.content) {
1347-
if (!delta.content.startsWith('<pr_title>')) {
1348-
currentResponseContent += delta.content;
1376+
// Handle special case for run_custom_setup_step
1377+
if (choice.finish_reason === 'tool_calls' && delta.tool_calls?.length && delta.tool_calls[0].function.name === 'run_custom_setup_step') {
1378+
const toolCall = delta.tool_calls[0];
1379+
let args: any = {};
1380+
try {
1381+
args = JSON.parse(toolCall.function.arguments);
1382+
} catch {
1383+
// fallback to empty args
13491384
}
1350-
}
13511385

1352-
if (delta.tool_calls) {
1353-
// Add any accumulated content as markdown first
1354-
if (currentResponseContent.trim()) {
1355-
responseParts.push(new vscode.ChatResponseMarkdownPart(currentResponseContent.trim()));
1356-
currentResponseContent = '';
1357-
}
1386+
// Ignore if delta.content is empty/undefined (running state)
1387+
if (delta.content && delta.content.trim()) {
1388+
// Add any accumulated content as markdown first
1389+
if (currentResponseContent.trim()) {
1390+
responseParts.push(new vscode.ChatResponseMarkdownPart(currentResponseContent.trim()));
1391+
currentResponseContent = '';
1392+
}
13581393

1359-
for (const toolCall of delta.tool_calls) {
1360-
const toolPart = this.createToolInvocationPart(toolCall, delta.content || '');
1394+
const toolPart = this.createToolInvocationPart(toolCall, args.name || delta.content);
13611395
if (toolPart) {
13621396
responseParts.push(toolPart);
13631397
}
13641398
}
1399+
// Skip if content is empty (running state)
1400+
} else {
1401+
if (delta.content) {
1402+
if (!delta.content.startsWith('<pr_title>')) {
1403+
currentResponseContent += delta.content;
1404+
}
1405+
}
1406+
1407+
if (delta.tool_calls) {
1408+
// Add any accumulated content as markdown first
1409+
if (currentResponseContent.trim()) {
1410+
responseParts.push(new vscode.ChatResponseMarkdownPart(currentResponseContent.trim()));
1411+
currentResponseContent = '';
1412+
}
1413+
1414+
for (const toolCall of delta.tool_calls) {
1415+
const toolPart = this.createToolInvocationPart(toolCall, delta.content || '');
1416+
if (toolPart) {
1417+
responseParts.push(toolPart);
1418+
}
1419+
}
1420+
}
13651421
}
13661422
}
13671423
}

webviews/sessionLogView/sessionView.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,17 @@ const SessionLog: React.FC<SessionLogProps> = ({ logs }) => {
112112
if (choice.delta.role === 'assistant') {
113113
if (choice.finish_reason === 'stop' && choice.delta.content.startsWith('<pr_title>')) {
114114
return;
115+
} if (choice.finish_reason === 'tool_calls' && choice.delta.tool_calls?.length && choice.delta.tool_calls[0].function.name === 'run_custom_setup_step') {
116+
const toolCall = choice.delta.tool_calls[0];
117+
const args = JSON.parse(toolCall.function.arguments);
118+
119+
return (
120+
<CodeView
121+
key={`setup-steps-${index}`}
122+
label={args.name || 'Setup Step'}
123+
content={{ value: choice.delta.content, lang: 'markdown' }}
124+
/>
125+
);
115126
} else {
116127
// For markdown content, use a custom renderer component
117128
return (

0 commit comments

Comments
 (0)