Skip to content

Commit c2d7d41

Browse files
committed
[Condense] Add button for basic condensing of task message history
1 parent f916927 commit c2d7d41

File tree

6 files changed

+82
-43
lines changed

6 files changed

+82
-43
lines changed

src/core/task/Task.ts

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,13 @@ export class Task extends EventEmitter<ClineEvents> {
355355
}
356356
}
357357

358+
/* Condenses a task's message history to use fewer tokens. */
359+
async condenseHistory() {
360+
// TODO(canyon): Replace with LLM summarization a la https://github.com/cline/cline/pull/3086
361+
const previousApiReqIndex = findLastIndex(this.clineMessages, (m) => m.say === "api_req_started")
362+
await this.maybeTruncateConversationHistory(previousApiReqIndex)
363+
}
364+
358365
// Note that `partial` has three valid states true (partial message),
359366
// false (completion of partial message), undefined (individual complete
360367
// message).
@@ -1419,49 +1426,7 @@ export class Task extends EventEmitter<ClineEvents> {
14191426
)
14201427
})()
14211428

1422-
// If the previous API request's total token usage is close to the
1423-
// context window, truncate the conversation history to free up space
1424-
// for the new request.
1425-
if (previousApiReqIndex >= 0) {
1426-
const previousRequest = this.clineMessages[previousApiReqIndex]?.text
1427-
1428-
if (!previousRequest) {
1429-
return
1430-
}
1431-
1432-
const {
1433-
tokensIn = 0,
1434-
tokensOut = 0,
1435-
cacheWrites = 0,
1436-
cacheReads = 0,
1437-
}: ClineApiReqInfo = JSON.parse(previousRequest)
1438-
1439-
const totalTokens = tokensIn + tokensOut + cacheWrites + cacheReads
1440-
1441-
// Default max tokens value for thinking models when no specific
1442-
// value is set.
1443-
const DEFAULT_THINKING_MODEL_MAX_TOKENS = 16_384
1444-
1445-
const modelInfo = this.api.getModel().info
1446-
1447-
const maxTokens = modelInfo.thinking
1448-
? this.apiConfiguration.modelMaxTokens || DEFAULT_THINKING_MODEL_MAX_TOKENS
1449-
: modelInfo.maxTokens
1450-
1451-
const contextWindow = modelInfo.contextWindow
1452-
1453-
const trimmedMessages = await truncateConversationIfNeeded({
1454-
messages: this.apiConversationHistory,
1455-
totalTokens,
1456-
maxTokens,
1457-
contextWindow,
1458-
apiHandler: this.api,
1459-
})
1460-
1461-
if (trimmedMessages !== this.apiConversationHistory) {
1462-
await this.overwriteApiConversationHistory(trimmedMessages)
1463-
}
1464-
}
1429+
await this.maybeTruncateConversationHistory(previousApiReqIndex)
14651430

14661431
// Clean conversation history by:
14671432
// 1. Converting to Anthropic.MessageParam by spreading only the API-required properties.
@@ -1585,6 +1550,52 @@ export class Task extends EventEmitter<ClineEvents> {
15851550
yield* iterator
15861551
}
15871552

1553+
private async maybeTruncateConversationHistory(previousApiReqIndex: number) {
1554+
// If the previous API request's total token usage is close to the
1555+
// context window, truncate the conversation history to free up space
1556+
// for the new request.
1557+
if (previousApiReqIndex >= 0) {
1558+
const previousRequest = this.clineMessages[previousApiReqIndex]?.text
1559+
1560+
if (!previousRequest) {
1561+
return
1562+
}
1563+
1564+
const {
1565+
tokensIn = 0,
1566+
tokensOut = 0,
1567+
cacheWrites = 0,
1568+
cacheReads = 0,
1569+
}: ClineApiReqInfo = JSON.parse(previousRequest)
1570+
1571+
const totalTokens = tokensIn + tokensOut + cacheWrites + cacheReads
1572+
1573+
// Default max tokens value for thinking models when no specific
1574+
// value is set.
1575+
const DEFAULT_THINKING_MODEL_MAX_TOKENS = 16_384
1576+
1577+
const modelInfo = this.api.getModel().info
1578+
1579+
const maxTokens = modelInfo.thinking
1580+
? this.apiConfiguration.modelMaxTokens || DEFAULT_THINKING_MODEL_MAX_TOKENS
1581+
: modelInfo.maxTokens
1582+
1583+
const contextWindow = modelInfo.contextWindow
1584+
1585+
const trimmedMessages = await truncateConversationIfNeeded({
1586+
messages: this.apiConversationHistory,
1587+
totalTokens,
1588+
maxTokens,
1589+
contextWindow,
1590+
apiHandler: this.api,
1591+
})
1592+
1593+
if (trimmedMessages !== this.apiConversationHistory) {
1594+
await this.overwriteApiConversationHistory(trimmedMessages)
1595+
}
1596+
}
1597+
}
1598+
15881599
// Checkpoints
15891600

15901601
public async checkpointSave() {

src/core/webview/ClineProvider.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,6 +1181,22 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
11811181
}
11821182
}
11831183

1184+
/* Condenses a task's message history to use fewer tokens. */
1185+
async condenseTaskHistory(id: string) {
1186+
let task = undefined
1187+
for (let i = this.clineStack.length - 1; i >= 0; i--) {
1188+
if (this.clineStack[i].taskId === id) {
1189+
task = this.clineStack[i]
1190+
break
1191+
}
1192+
}
1193+
if (!task) {
1194+
const { historyItem } = await this.getTaskWithId(id)
1195+
task = await this.initClineWithHistoryItem(historyItem)
1196+
}
1197+
await task.condenseHistory()
1198+
}
1199+
11841200
async deleteTaskFromState(id: string) {
11851201
const taskHistory = this.getGlobalState("taskHistory") ?? []
11861202
const updatedTaskHistory = taskHistory.filter((task) => task.id !== id)

src/core/webview/webviewMessageHandler.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
198198
case "deleteTaskWithId":
199199
provider.deleteTaskWithId(message.text!)
200200
break
201+
case "condenseTaskHistory":
202+
provider.condenseTaskHistory(message.text!)
203+
break
201204
case "deleteMultipleTasksWithIds": {
202205
const ids = message.ids
203206

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export interface WebviewMessage {
3838
| "showTaskWithId"
3939
| "deleteTaskWithId"
4040
| "exportTaskWithId"
41+
| "condenseTaskHistory"
4142
| "importSettings"
4243
| "exportSettings"
4344
| "resetState"

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ export const TaskActions = ({ item }: { item: HistoryItem | undefined }) => {
2424
</Button>
2525
{!!item?.size && item.size > 0 && (
2626
<>
27+
<Button
28+
variant="ghost"
29+
size="sm"
30+
title={t("chat:task.condenseTaskHistory")}
31+
onClick={() => vscode.postMessage({ type: "condenseTaskHistory", text: item?.id })}>
32+
<span className="codicon codicon-file-zip" />
33+
</Button>
2734
<Button
2835
variant="ghost"
2936
size="sm"

webview-ui/src/i18n/locales/en/chat.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"contextWindow": "Context Length:",
1111
"closeAndStart": "Close task and start a new one",
1212
"export": "Export task history",
13+
"condenseTaskHistory": "Condense task message history",
1314
"delete": "Delete Task (Shift + Click to skip confirmation)"
1415
},
1516
"unpin": "Unpin",

0 commit comments

Comments
 (0)