This commit is contained in:
roomote-v0[bot] 2026-04-07 00:45:07 +00:00 committed by GitHub
commit db1ae3ce6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 118 additions and 7 deletions

View file

@ -198,6 +198,7 @@ export const globalSettingsSchema = z.object({
customModePrompts: customModePromptsSchema.optional(),
customSupportPrompts: customSupportPromptsSchema.optional(),
enhancementApiConfigId: z.string().optional(),
condensingApiConfigId: z.string().optional(),
includeTaskHistoryInEnhance: z.boolean().optional(),
historyPreviewCollapsed: z.boolean().optional(),
reasoningBlockCollapsed: z.boolean().optional(),

View file

@ -289,6 +289,7 @@ export type ExtensionState = Pick<
| "customModePrompts"
| "customSupportPrompts"
| "enhancementApiConfigId"
| "condensingApiConfigId"
| "customCondensingPrompt"
| "codebaseIndexConfig"
| "codebaseIndexModels"
@ -485,6 +486,7 @@ export interface WebviewMessage {
| "copySystemPrompt"
| "systemPrompt"
| "enhancementApiConfigId"
| "condensingApiConfigId"
| "autoApprovalEnabled"
| "updateCustomMode"
| "deleteCustomMode"

View file

@ -1645,6 +1645,42 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}
}
/**
* Builds a dedicated API handler for context condensing if a condensing API config is set.
* Falls back to the task's main API handler if no dedicated config is configured.
*/
private async buildCondensingApiHandler(): Promise<ApiHandler> {
const provider = this.providerRef.deref()
if (!provider) {
return this.api
}
const state = await provider.getState()
const { condensingApiConfigId, listApiConfigMeta } = state ?? {}
if (
condensingApiConfigId &&
listApiConfigMeta?.find(({ id }: { id: string }) => id === condensingApiConfigId)
) {
try {
const { name: _, ...providerSettings } = await provider.providerSettingsManager.getProfile({
id: condensingApiConfigId,
})
if (providerSettings.apiProvider) {
return buildApiHandler(providerSettings)
}
} catch (error) {
console.warn(
`[Task#${this.taskId}] Failed to build condensing API handler, falling back to task handler:`,
error,
)
}
}
return this.api
}
public async condenseContext(): Promise<void> {
// CRITICAL: Flush any pending tool results before condensing
// to ensure tool_use/tool_result pairs are complete in history
@ -1659,6 +1695,9 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
const { contextTokens: prevContextTokens } = this.getTokenUsage()
// Build a dedicated API handler for condensing if configured
const condensingApiHandler = await this.buildCondensingApiHandler()
// Build tools for condensing metadata (same tools used for normal API calls)
const provider = this.providerRef.deref()
let allTools: import("openai").default.Chat.ChatCompletionTool[] = []
@ -1705,7 +1744,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
condenseId,
} = await summarizeConversation({
messages: this.apiConversationHistory,
apiHandler: this.api,
apiHandler: condensingApiHandler,
systemPrompt,
taskId: this.taskId,
isAutomaticTrigger: false,
@ -3887,13 +3926,16 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
// Generate environment details to include in the condensed summary
const environmentDetails = await getEnvironmentDetails(this, true)
// Build a dedicated API handler for condensing if configured
const condensingApiHandler = await this.buildCondensingApiHandler()
// Force aggressive truncation by keeping only 75% of the conversation history
const truncateResult = await manageContext({
messages: this.apiConversationHistory,
totalTokens: contextTokens || 0,
maxTokens,
contextWindow,
apiHandler: this.api,
apiHandler: condensingApiHandler,
autoCondenseContext: true,
autoCondenseContextPercent: FORCED_CONTEXT_REDUCTION_PERCENT,
systemPrompt: await this.getSystemPrompt(),
@ -4112,12 +4154,15 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
: undefined
try {
// Build a dedicated API handler for condensing if configured
const condensingApiHandler = await this.buildCondensingApiHandler()
const truncateResult = await manageContext({
messages: this.apiConversationHistory,
totalTokens: contextTokens,
maxTokens,
contextWindow,
apiHandler: this.api,
apiHandler: condensingApiHandler,
autoCondenseContext,
autoCondenseContextPercent,
systemPrompt,

View file

@ -2170,6 +2170,7 @@ export class ClineProvider
customModePrompts,
customSupportPrompts,
enhancementApiConfigId,
condensingApiConfigId,
autoApprovalEnabled,
customModes,
experiments,
@ -2290,6 +2291,7 @@ export class ClineProvider
customModePrompts: customModePrompts ?? {},
customSupportPrompts: customSupportPrompts ?? {},
enhancementApiConfigId,
condensingApiConfigId,
autoApprovalEnabled: autoApprovalEnabled ?? false,
customModes,
experiments: experiments ?? experimentDefault,
@ -2520,6 +2522,7 @@ export class ClineProvider
customModePrompts: stateValues.customModePrompts ?? {},
customSupportPrompts: stateValues.customSupportPrompts ?? {},
enhancementApiConfigId: stateValues.enhancementApiConfigId,
condensingApiConfigId: stateValues.condensingApiConfigId,
experiments: stateValues.experiments ?? experimentDefault,
autoApprovalEnabled: stateValues.autoApprovalEnabled ?? false,
customModes,

View file

@ -1669,6 +1669,10 @@ export const webviewMessageHandler = async (
await updateGlobalState("enhancementApiConfigId", message.text)
await provider.postStateToWebview()
break
case "condensingApiConfigId":
await updateGlobalState("condensingApiConfigId", message.text)
await provider.postStateToWebview()
break
case "autoApprovalEnabled":
await updateGlobalState("autoApprovalEnabled", message.bool ?? false)

View file

@ -29,6 +29,8 @@ type ContextManagementSettingsProps = HTMLAttributes<HTMLDivElement> & {
autoCondenseContext: boolean
autoCondenseContextPercent: number
listApiConfigMeta: any[]
condensingApiConfigId?: string
setCondensingApiConfigId: (value: string) => void
maxOpenTabsContext: number
maxWorkspaceFiles: number
showRooIgnoredFiles?: boolean
@ -67,6 +69,8 @@ export const ContextManagementSettings = ({
autoCondenseContext,
autoCondenseContextPercent,
listApiConfigMeta,
condensingApiConfigId,
setCondensingApiConfigId,
maxOpenTabsContext,
maxWorkspaceFiles,
showRooIgnoredFiles,
@ -472,6 +476,52 @@ export const ContextManagementSettings = ({
/>
</SearchableSetting>
{/* Condensing API Configuration */}
<SearchableSetting
settingId="context-condensing-api-config"
section="contextManagement"
label={t("settings:contextManagement.condensingApiConfiguration.label")}>
<div>
<label className="block font-medium mb-1">
{t("settings:contextManagement.condensingApiConfiguration.label")}
</label>
<div className="text-sm text-vscode-descriptionForeground mb-2">
{t("settings:contextManagement.condensingApiConfiguration.description")}
</div>
<Select
value={condensingApiConfigId || "-"}
onValueChange={(value) => {
const newConfigId = value === "-" ? "" : value
setCondensingApiConfigId(newConfigId)
vscode.postMessage({
type: "condensingApiConfigId",
text: value === "-" ? "" : value,
})
}}>
<SelectTrigger data-testid="condensing-api-config-select" className="w-full">
<SelectValue
placeholder={t(
"settings:contextManagement.condensingApiConfiguration.useCurrentConfig",
)}
/>
</SelectTrigger>
<SelectContent>
<SelectItem value="-">
{t("settings:contextManagement.condensingApiConfiguration.useCurrentConfig")}
</SelectItem>
{(listApiConfigMeta || []).map((config: any) => (
<SelectItem
key={config.id}
value={config.id}
data-testid={`condensing-${config.id}-option`}>
{config.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</SearchableSetting>
{/* Auto Condense Context */}
<SearchableSetting
settingId="context-auto-condense"

View file

@ -831,6 +831,10 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
autoCondenseContext={autoCondenseContext}
autoCondenseContextPercent={autoCondenseContextPercent}
listApiConfigMeta={listApiConfigMeta ?? []}
condensingApiConfigId={cachedState.condensingApiConfigId}
setCondensingApiConfigId={(value) =>
setCachedState((prev) => ({ ...prev, condensingApiConfigId: value }))
}
maxOpenTabsContext={maxOpenTabsContext}
maxWorkspaceFiles={maxWorkspaceFiles ?? 200}
showRooIgnoredFiles={showRooIgnoredFiles}

View file

@ -89,6 +89,8 @@ describe("ContextManagementSettings", () => {
autoCondenseContext: false,
autoCondenseContextPercent: 80,
listApiConfigMeta: [],
condensingApiConfigId: undefined as string | undefined,
setCondensingApiConfigId: vi.fn(),
maxOpenTabsContext: 20,
maxWorkspaceFiles: 200,
showRooIgnoredFiles: false,
@ -333,9 +335,9 @@ describe("ContextManagementSettings", () => {
const slider = screen.getByTestId("condense-threshold-slider")
expect(slider).toBeInTheDocument()
// Should render the profile select dropdown
// Should render the profile select dropdown and condensing API config dropdown
const selects = screen.getAllByRole("combobox")
expect(selects).toHaveLength(1)
expect(selects).toHaveLength(2)
})
describe("Auto Condense Context functionality", () => {
@ -368,8 +370,8 @@ describe("ContextManagementSettings", () => {
// Threshold settings should be visible
expect(screen.getByTestId("condense-threshold-slider")).toBeInTheDocument()
// One combobox for profile selection
expect(screen.getAllByRole("combobox")).toHaveLength(1)
// Two comboboxes: condensing API config + profile selection
expect(screen.getAllByRole("combobox")).toHaveLength(2)
})
it("updates auto condense context percent", () => {