Skip to content

Commit ba603e8

Browse files
committed
Add auto-continuation after compaction in chat streaming
1 parent b91c7a6 commit ba603e8

File tree

2 files changed

+112
-32
lines changed

2 files changed

+112
-32
lines changed

extensions/cli/src/stream/streamChatResponse.compactionHelpers.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ export async function handlePostToolValidation(
6666
toolCalls: ToolCall[],
6767
chatHistory: ChatHistoryItem[],
6868
options: CompactionHelperOptions,
69-
): Promise<ChatHistoryItem[]> {
69+
): Promise<{ chatHistory: ChatHistoryItem[]; wasCompacted: boolean }> {
7070
const { model, llmApi, isHeadless, callbacks, systemMessage } = options;
7171

7272
if (toolCalls.length === 0) {
73-
return chatHistory;
73+
return { chatHistory, wasCompacted: false };
7474
}
7575

7676
// Get updated history after tool execution
@@ -150,6 +150,7 @@ export async function handlePostToolValidation(
150150
inputTokens: postCompactionValidation.inputTokens,
151151
contextLimit: postCompactionValidation.contextLimit,
152152
});
153+
return { chatHistory, wasCompacted: true };
153154
} else {
154155
// Compaction failed, cannot continue
155156
logger.error("Failed to compact history after tool execution overflow");
@@ -159,7 +160,7 @@ export async function handlePostToolValidation(
159160
}
160161
}
161162

162-
return chatHistory;
163+
return { chatHistory, wasCompacted: false };
163164
}
164165

165166
/**
@@ -169,11 +170,11 @@ export async function handleNormalAutoCompaction(
169170
chatHistory: ChatHistoryItem[],
170171
shouldContinue: boolean,
171172
options: CompactionHelperOptions,
172-
): Promise<ChatHistoryItem[]> {
173+
): Promise<{ chatHistory: ChatHistoryItem[]; wasCompacted: boolean }> {
173174
const { model, llmApi, isHeadless, callbacks, systemMessage } = options;
174175

175176
if (!shouldContinue) {
176-
return chatHistory;
177+
return { chatHistory, wasCompacted: false };
177178
}
178179

179180
const chatHistorySvc = services.chatHistory;
@@ -198,11 +199,14 @@ export async function handleNormalAutoCompaction(
198199
// Use the service to update history if available, otherwise use local copy
199200
if (chatHistorySvc && typeof chatHistorySvc.setHistory === "function") {
200201
chatHistorySvc.setHistory(updatedChatHistory);
201-
return chatHistorySvc.getHistory();
202+
return {
203+
chatHistory: chatHistorySvc.getHistory(),
204+
wasCompacted: true,
205+
};
202206
}
203207
// Fallback: return the compacted history directly when service unavailable
204-
return updatedChatHistory;
208+
return { chatHistory: updatedChatHistory, wasCompacted: true };
205209
}
206210

207-
return chatHistory;
211+
return { chatHistory, wasCompacted: false };
208212
}

extensions/cli/src/stream/streamChatResponse.ts

Lines changed: 100 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,63 @@ function handleContentDisplay(
7070
}
7171
}
7272

73+
// Helper function to refresh chat history from service
74+
function refreshChatHistoryFromService(
75+
chatHistory: ChatHistoryItem[],
76+
isCompacting: boolean,
77+
): ChatHistoryItem[] {
78+
const chatHistorySvc = services.chatHistory;
79+
if (
80+
typeof chatHistorySvc?.isReady === "function" &&
81+
chatHistorySvc.isReady()
82+
) {
83+
try {
84+
// use chat history from params when isCompacting is true
85+
// otherwise use the full history
86+
if (!isCompacting) {
87+
return chatHistorySvc.getHistory();
88+
}
89+
} catch {}
90+
}
91+
return chatHistory;
92+
}
93+
94+
// Helper function to handle auto-continuation after compaction
95+
function handleAutoContinuation(
96+
compactionOccurred: boolean,
97+
shouldContinue: boolean,
98+
chatHistory: ChatHistoryItem[],
99+
): { shouldAutoContinue: boolean; chatHistory: ChatHistoryItem[] } {
100+
if (!compactionOccurred || shouldContinue) {
101+
return { shouldAutoContinue: false, chatHistory };
102+
}
103+
104+
logger.debug(
105+
"Auto-compaction occurred during this turn - automatically continuing session",
106+
);
107+
108+
// Add a continuation message to the history
109+
const chatHistorySvc = services.chatHistory;
110+
if (
111+
typeof chatHistorySvc?.isReady === "function" &&
112+
chatHistorySvc.isReady()
113+
) {
114+
chatHistorySvc.addUserMessage("continue");
115+
chatHistory = chatHistorySvc.getHistory();
116+
} else {
117+
chatHistory.push({
118+
message: {
119+
role: "user",
120+
content: "continue",
121+
},
122+
contextItems: [],
123+
});
124+
}
125+
126+
logger.debug("Added continuation message after compaction");
127+
return { shouldAutoContinue: true, chatHistory };
128+
}
129+
73130
// Helper function to process a single chunk
74131
interface ProcessChunkOptions {
75132
chunk: any;
@@ -379,22 +436,11 @@ export async function streamChatResponse(
379436

380437
let fullResponse = "";
381438
let finalResponse = "";
439+
let compactionOccurredThisTurn = false; // Track if compaction happened during this conversation turn
382440

383441
while (true) {
384442
// If ChatHistoryService is available, refresh local chatHistory view
385-
const chatHistorySvc = services.chatHistory;
386-
if (
387-
typeof chatHistorySvc?.isReady === "function" &&
388-
chatHistorySvc.isReady()
389-
) {
390-
try {
391-
// use chat history from params when isCompacting is true
392-
// otherwise use the full history
393-
if (!isCompacting) {
394-
chatHistory = chatHistorySvc.getHistory();
395-
}
396-
} catch {}
397-
}
443+
chatHistory = refreshChatHistoryFromService(chatHistory, isCompacting);
398444
logger.debug("Starting conversation iteration");
399445

400446
logger.debug("debug1 streamChatResponse history", { chatHistory });
@@ -414,6 +460,9 @@ export async function streamChatResponse(
414460
systemMessage,
415461
});
416462
chatHistory = preCompactionResult.chatHistory;
463+
if (preCompactionResult.wasCompacted) {
464+
compactionOccurredThisTurn = true;
465+
}
417466

418467
// Recompute tools on each iteration to handle mode changes during streaming
419468
const tools = await getRequestTools(isHeadless);
@@ -467,17 +516,25 @@ export async function streamChatResponse(
467516
}
468517

469518
// After tool execution, validate that we haven't exceeded context limit
470-
chatHistory = await handlePostToolValidation(toolCalls, chatHistory, {
471-
model,
472-
llmApi,
473-
isCompacting,
474-
isHeadless,
475-
callbacks,
476-
systemMessage,
477-
});
519+
const postToolResult = await handlePostToolValidation(
520+
toolCalls,
521+
chatHistory,
522+
{
523+
model,
524+
llmApi,
525+
isCompacting,
526+
isHeadless,
527+
callbacks,
528+
systemMessage,
529+
},
530+
);
531+
chatHistory = postToolResult.chatHistory;
532+
if (postToolResult.wasCompacted) {
533+
compactionOccurredThisTurn = true;
534+
}
478535

479536
// Normal auto-compaction check at 80% threshold
480-
chatHistory = await handleNormalAutoCompaction(
537+
const compactionResult = await handleNormalAutoCompaction(
481538
chatHistory,
482539
shouldContinue,
483540
{
@@ -489,9 +546,28 @@ export async function streamChatResponse(
489546
systemMessage,
490547
},
491548
);
549+
chatHistory = compactionResult.chatHistory;
550+
if (compactionResult.wasCompacted) {
551+
compactionOccurredThisTurn = true;
552+
}
553+
554+
// If compaction happened during this turn and we're about to stop,
555+
// automatically send a continuation message to keep the agent going
556+
const autoContinueResult = handleAutoContinuation(
557+
compactionOccurredThisTurn,
558+
shouldContinue,
559+
chatHistory,
560+
);
561+
chatHistory = autoContinueResult.chatHistory;
562+
const shouldAutoContinue = autoContinueResult.shouldAutoContinue;
563+
564+
// Reset flag to avoid infinite continuation
565+
if (shouldAutoContinue) {
566+
compactionOccurredThisTurn = false;
567+
}
492568

493-
// Check if we should continue
494-
if (!shouldContinue) {
569+
// Check if we should continue (skip break if auto-continuing after compaction)
570+
if (!shouldContinue && !shouldAutoContinue) {
495571
break;
496572
}
497573
}

0 commit comments

Comments
 (0)