Skip to content

Commit 08adf5f

Browse files
committed
Add context summarization feature with manual trigger option
1 parent ad6b7b9 commit 08adf5f

File tree

8 files changed

+130
-3
lines changed

8 files changed

+130
-3
lines changed

src/core/Cline.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2676,4 +2676,83 @@ export class Cline extends EventEmitter<ClineEvents> {
26762676
}
26772677
return totalTokens
26782678
}
2679+
2680+
/**
2681+
* Manually triggers summarization of the conversation context.
2682+
* @param isManualTrigger Whether this summarization was manually triggered by the user.
2683+
* @returns A promise that resolves when summarization is complete.
2684+
*/
2685+
public async summarizeConversationContext(isManualTrigger: boolean = false): Promise<void> {
2686+
// Skip if summarization is disabled
2687+
if (!this.enableContextSummarization) {
2688+
this.providerRef.deref()?.log("[Summarization] Context summarization is disabled.")
2689+
return
2690+
}
2691+
2692+
const initialMessagesToKeep = this.contextSummarizationInitialStaticTurns
2693+
const recentMessagesToKeep = this.contextSummarizationRecentTurns
2694+
2695+
// Ensure we have enough messages to summarize
2696+
if (this.apiConversationHistory.length <= initialMessagesToKeep + recentMessagesToKeep) {
2697+
this.providerRef
2698+
.deref()
2699+
?.log(
2700+
`[Summarization] Not enough messages to summarize. Need more than ${initialMessagesToKeep + recentMessagesToKeep} messages.`,
2701+
)
2702+
return
2703+
}
2704+
2705+
// Calculate slice points
2706+
const initialSliceEnd = initialMessagesToKeep
2707+
const recentSliceStart = this.apiConversationHistory.length - recentMessagesToKeep
2708+
2709+
// Ensure slice points don't overlap
2710+
if (initialSliceEnd >= recentSliceStart) {
2711+
this.providerRef
2712+
.deref()
2713+
?.log(
2714+
`[Summarization] Skipping: initialSliceEnd (${initialSliceEnd}) >= recentSliceStart (${recentSliceStart}). Not enough messages between initial/recent turns.`,
2715+
)
2716+
return
2717+
}
2718+
2719+
// Slice the conversation history
2720+
const initialMessages = this.apiConversationHistory.slice(0, initialSliceEnd)
2721+
const recentMessages = this.apiConversationHistory.slice(recentSliceStart)
2722+
const messagesToSummarize = this.apiConversationHistory.slice(initialSliceEnd, recentSliceStart)
2723+
2724+
this.providerRef
2725+
.deref()
2726+
?.log(
2727+
`[Summarization] Slicing: Keep Initial ${initialMessages.length}, Summarize ${messagesToSummarize.length}, Keep Recent ${recentMessages.length}`,
2728+
)
2729+
2730+
// Create summarizer and generate summary
2731+
const summarizer = new ContextSummarizer(this.api)
2732+
const summaryMessage = await summarizer.summarize(messagesToSummarize)
2733+
2734+
if (!summaryMessage) {
2735+
this.providerRef.deref()?.log(`[Summarization] Failed to generate summary.`)
2736+
return
2737+
}
2738+
2739+
// Create new history with summary
2740+
const newHistory = [...initialMessages, summaryMessage, ...recentMessages]
2741+
2742+
// Add a system message to notify the user in the UI
2743+
if (isManualTrigger) {
2744+
await this.say("text", "[Conversation history has been summarized to preserve context]")
2745+
} else {
2746+
await this.say("text", "[Older conversation turns summarized to preserve context]")
2747+
}
2748+
2749+
// Update the conversation history
2750+
await this.overwriteApiConversationHistory(newHistory)
2751+
2752+
this.providerRef
2753+
.deref()
2754+
?.log(
2755+
`[Summarization] Successfully summarized ${messagesToSummarize.length} messages. New history length: ${newHistory.length}`,
2756+
)
2757+
}
26792758
}

src/core/webview/webviewMessageHandler.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,27 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
877877
await updateGlobalState("contextSummarizationRecentTurns", message.value ?? 10)
878878
await provider.postStateToWebview()
879879
break
880+
case "manualSummarize":
881+
// Trigger manual summarization of the conversation context
882+
const currentCline = provider.getCurrentCline()
883+
if (currentCline) {
884+
try {
885+
// Notify user that summarization is in progress
886+
vscode.window.showInformationMessage(t("common:info.summarizing_context"))
887+
888+
// Trigger the summarization process
889+
await currentCline.summarizeConversationContext(true) // true indicates manual trigger
890+
891+
// Notify user that summarization is complete
892+
vscode.window.showInformationMessage(t("common:info.summarization_complete"))
893+
} catch (error) {
894+
provider.log(
895+
`Error during manual summarization: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
896+
)
897+
vscode.window.showErrorMessage(t("common:errors.summarization_failed"))
898+
}
899+
}
900+
break
880901
// --- End Context Summarization ---
881902
case "toggleApiConfigPin":
882903
if (message.text) {

src/i18n/locales/en/common.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"create_mcp_json": "Failed to create or open .roo/mcp.json: {{error}}",
4848
"hmr_not_running": "Local development server is not running, HMR will not work. Please run 'npm run dev' before launching the extension to enable HMR.",
4949
"retrieve_current_mode": "Error: failed to retrieve current mode from state.",
50+
"summarization_failed": "Failed to summarize conversation context.",
5051
"failed_delete_repo": "Failed to delete associated shadow repository or branch: {{error}}",
5152
"failed_remove_directory": "Failed to remove task directory: {{error}}",
5253
"custom_storage_path_unusable": "Custom storage path \"{{path}}\" is unusable, will use default path",
@@ -67,7 +68,9 @@
6768
"mcp_server_not_found": "Server \"{{serverName}}\" not found in configuration",
6869
"custom_storage_path_set": "Custom storage path set: {{path}}",
6970
"default_storage_path": "Reverted to using default storage path",
70-
"settings_imported": "Settings imported successfully."
71+
"settings_imported": "Settings imported successfully.",
72+
"summarizing_context": "Summarizing conversation context...",
73+
"summarization_complete": "Conversation context summarization complete."
7174
},
7275
"answers": {
7376
"yes": "Yes",

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export interface WebviewMessage {
132132
| "contextSummarizationTriggerThreshold"
133133
| "contextSummarizationInitialStaticTurns"
134134
| "contextSummarizationRecentTurns"
135+
| "manualSummarize"
135136
text?: string
136137
disabled?: boolean
137138
askResponse?: ClineAskResponse

src/shared/context-mentions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@ Mention regex:
5050
5151
*/
5252
export const mentionRegex =
53-
/@((?:\/|\w+:\/\/)[^\s]+?|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/
53+
/@((?:\/|\w+:\/\/)[^\s]+?|[a-f0-9]{7,40}\b|problems\b|git-changes\b|terminal\b|summarize\b)(?=[.,;:!?]?(?=[\s\r\n]|$))/
5454
export const mentionRegexGlobal = new RegExp(mentionRegex.source, "g")
5555

5656
export interface MentionSuggestion {
57-
type: "file" | "folder" | "git" | "problems"
57+
type: "file" | "folder" | "git" | "problems" | "summarize"
5858
label: string
5959
description?: string
6060
value: string

webview-ui/src/components/chat/ChatTextArea.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,14 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
252252
setShowContextMenu(false)
253253
setSelectedType(null)
254254

255+
if (type === ContextMenuOptionType.Summarize) {
256+
// Handle summarize action - trigger manual summarization
257+
vscode.postMessage({ type: "manualSummarize" })
258+
setShowContextMenu(false)
259+
setSelectedType(null)
260+
return
261+
}
262+
255263
if (textAreaRef.current) {
256264
let insertValue = value || ""
257265

@@ -267,6 +275,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
267275
insertValue = value || ""
268276
}
269277

278+
// Note: The Summarize case is handled above before this block
279+
270280
const { newValue, mentionIndex } = insertMention(
271281
textAreaRef.current.value,
272282
cursorPosition,

webview-ui/src/components/chat/ContextMenu.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
8989
return <span>Problems</span>
9090
case ContextMenuOptionType.Terminal:
9191
return <span>Terminal</span>
92+
case ContextMenuOptionType.Summarize:
93+
return <span>{option.label || "Summarize"}</span>
9294
case ContextMenuOptionType.URL:
9395
return <span>Paste URL to fetch contents</span>
9496
case ContextMenuOptionType.NoResults:
@@ -175,6 +177,8 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
175177
return "link"
176178
case ContextMenuOptionType.Git:
177179
return "git-commit"
180+
case ContextMenuOptionType.Summarize:
181+
return "archive"
178182
case ContextMenuOptionType.NoResults:
179183
return "info"
180184
default:

webview-ui/src/utils/context-mentions.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export enum ContextMenuOptionType {
7777
Git = "git",
7878
NoResults = "noResults",
7979
Mode = "mode", // Add mode type
80+
Summarize = "summarize", // Add summarize type
8081
}
8182

8283
export interface ContextMenuQueryItem {
@@ -170,6 +171,7 @@ export function getContextMenuOptions(
170171
{ type: ContextMenuOptionType.Folder },
171172
{ type: ContextMenuOptionType.File },
172173
{ type: ContextMenuOptionType.Git },
174+
{ type: ContextMenuOptionType.Summarize, label: "Summarize", description: "Compress conversation history" },
173175
]
174176
}
175177

@@ -193,6 +195,13 @@ export function getContextMenuOptions(
193195
if ("terminal".startsWith(lowerQuery)) {
194196
suggestions.push({ type: ContextMenuOptionType.Terminal })
195197
}
198+
if ("summarize".startsWith(lowerQuery)) {
199+
suggestions.push({
200+
type: ContextMenuOptionType.Summarize,
201+
label: "Summarize",
202+
description: "Compress conversation history",
203+
})
204+
}
196205
if (query.startsWith("http")) {
197206
suggestions.push({ type: ContextMenuOptionType.URL, value: query })
198207
}

0 commit comments

Comments
 (0)