Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/vscode-extension-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ export type ExtensionState = Pick<
| "customModePrompts"
| "customSupportPrompts"
| "enhancementApiConfigId"
| "condensingApiConfigId"
| "customCondensingPrompt"
| "codebaseIndexConfig"
| "codebaseIndexModels"
Expand Down Expand Up @@ -485,6 +486,7 @@ export interface WebviewMessage {
| "copySystemPrompt"
| "systemPrompt"
| "enhancementApiConfigId"
| "condensingApiConfigId"
| "autoApprovalEnabled"
| "updateCustomMode"
| "deleteCustomMode"
Expand Down
51 changes: 48 additions & 3 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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[] = []
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2170,6 +2170,7 @@ export class ClineProvider
customModePrompts,
customSupportPrompts,
enhancementApiConfigId,
condensingApiConfigId,
autoApprovalEnabled,
customModes,
experiments,
Expand Down Expand Up @@ -2290,6 +2291,7 @@ export class ClineProvider
customModePrompts: customModePrompts ?? {},
customSupportPrompts: customSupportPrompts ?? {},
enhancementApiConfigId,
condensingApiConfigId,
autoApprovalEnabled: autoApprovalEnabled ?? false,
customModes,
experiments: experiments ?? experimentDefault,
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1653,6 +1653,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -67,6 +69,8 @@ export const ContextManagementSettings = ({
autoCondenseContext,
autoCondenseContextPercent,
listApiConfigMeta,
condensingApiConfigId,
setCondensingApiConfigId,
maxOpenTabsContext,
maxWorkspaceFiles,
showRooIgnoredFiles,
Expand Down Expand Up @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions webview-ui/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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", () => {
Expand Down
Loading