Skip to content
Closed
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/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const baseProviderSettingsSchema = z.object({
modelTemperature: z.number().nullish(),
rateLimitSeconds: z.number().optional(),
consecutiveMistakeLimit: z.number().min(0).optional(),
compactPromptMode: z.boolean().optional(),

// Model reasoning.
enableReasoningEffort: z.boolean().optional(),
Expand Down
188 changes: 188 additions & 0 deletions src/core/prompts/__tests__/system-prompt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,194 @@ describe("SYSTEM_PROMPT", () => {
expect(prompt).toContain("## update_todo_list")
})

describe("Compact Prompt Mode", () => {
it("should generate a compact prompt when compactPromptMode is true", async () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
undefined, // settings
undefined, // todoList
undefined, // modelId
true, // compactPromptMode
)

// Compact prompt should be significantly shorter
const normalPrompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
undefined, // settings
undefined, // todoList
undefined, // modelId
false, // compactPromptMode
)

// Compact prompt should be shorter
expect(prompt.length).toBeLessThan(normalPrompt.length)

// Should still contain essential sections
expect(prompt).toContain("You are Roo")
expect(prompt).toContain("## read_file")
expect(prompt).toContain("## write_to_file")
expect(prompt).toContain("## list_files")
expect(prompt).toContain("## search_files")

// Should NOT contain non-essential sections
expect(prompt).not.toContain("MCP")
expect(prompt).not.toContain("browser")
expect(prompt).not.toContain("CAPABILITIES")
expect(prompt).not.toContain("MODES")
expect(prompt).not.toContain("execute_command") // Execute command is not included in compact mode for architect mode
})

it("should generate a normal prompt when compactPromptMode is false", async () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
undefined, // settings
undefined, // todoList
undefined, // modelId
false, // compactPromptMode
)

// Normal prompt should contain all sections
expect(prompt).toContain("CAPABILITIES")
expect(prompt).toContain("MODES")
expect(prompt).toContain("RULES")
expect(prompt).toContain("SYSTEM INFORMATION")
expect(prompt).toContain("OBJECTIVE")
})

it("should generate a normal prompt when compactPromptMode is undefined", async () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
undefined, // settings
undefined, // todoList
undefined, // modelId
undefined, // compactPromptMode
)

// Should generate normal prompt by default
expect(prompt).toContain("CAPABILITIES")
expect(prompt).toContain("MODES")
expect(prompt).toContain("RULES")
expect(prompt).toContain("SYSTEM INFORMATION")
expect(prompt).toContain("OBJECTIVE")
})

it("should not include diff tool in compact mode even when diffEnabled is true", async () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
new MultiSearchReplaceDiffStrategy(), // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
undefined, // globalCustomInstructions
true, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
undefined, // settings
undefined, // todoList
undefined, // modelId
true, // compactPromptMode
)

// Compact mode doesn't include diff tool to keep prompt minimal
expect(prompt).not.toContain("apply_diff")
})

it("should maintain custom instructions in compact mode", async () => {
const prompt = await SYSTEM_PROMPT(
mockContext,
"/test/path",
false, // supportsComputerUse
undefined, // mcpHub
undefined, // diffStrategy
undefined, // browserViewportSize
defaultModeSlug, // mode
undefined, // customModePrompts
undefined, // customModes
"Test global instructions", // globalCustomInstructions
undefined, // diffEnabled
experiments,
true, // enableMcpServerCreation
undefined, // language
undefined, // rooIgnoreInstructions
undefined, // partialReadsEnabled
undefined, // settings
undefined, // todoList
undefined, // modelId
true, // compactPromptMode
)

// Should still include custom instructions
expect(prompt).toContain("Test global instructions")
})
})

afterAll(() => {
vi.restoreAllMocks()
})
Expand Down
57 changes: 57 additions & 0 deletions src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export const SYSTEM_PROMPT = async (
settings?: SystemPromptSettings,
todoList?: TodoItem[],
modelId?: string,
compactPromptMode?: boolean,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding JSDoc comments here to explain when and why to use compact prompt mode:

Suggested change
compactPromptMode?: boolean,
/**
* @param compactPromptMode - When true, generates a minimal prompt with only essential sections
* to reduce token count for local LLMs with slower processing speeds.
* Excludes MCP, browser tools, capabilities, modes, and verbose instructions.
*/
export const SYSTEM_PROMPT = async (

): Promise<string> => {
if (!context) {
throw new Error("Extension context is required for generating system prompt")
Expand Down Expand Up @@ -202,6 +203,62 @@ ${fileCustomSystemPrompt}
${customInstructions}`
}

// If compact prompt mode is enabled, generate a minimal prompt
if (compactPromptMode) {
const { roleDefinition, baseInstructions } = getModeSelection(mode, promptComponent, customModes)
const codeIndexManager = CodeIndexManager.getInstance(context, cwd)

// Generate a compact prompt with only essential sections
const compactPrompt = `${roleDefinition}

====

TOOL USE

You have access to tools that are executed upon user approval. Use one tool per message.

# Tools

${getToolDescriptionsForMode(
mode,
cwd,
false, // Disable computer use for compact mode
codeIndexManager,
undefined, // No diff strategy in compact mode
undefined, // No browser viewport
undefined, // No MCP in compact mode
customModes,
experiments,
partialReadsEnabled,
settings,
false, // No MCP server creation
modelId,
)}

====

RULES

- Project directory: ${cwd.toPosix()}
- Use tools efficiently to accomplish tasks
- Wait for user response after each tool use
- Be concise and direct in responses

====

OBJECTIVE

Complete the user's task efficiently using available tools.

${await addCustomInstructions(baseInstructions, globalCustomInstructions || "", cwd, mode, {
language: language ?? formatLanguage(vscode.env.language),
rooIgnoreInstructions,
settings,
})}`

return compactPrompt
}

// If diff is disabled, don't pass the diffStrategy
const effectiveDiffStrategy = diffEnabled ? diffStrategy : undefined

Expand Down
6 changes: 6 additions & 0 deletions src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2242,6 +2242,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
apiConfiguration,
} = state ?? {}

// Check if we should use compact prompt mode for local LLM providers
const isLocalLLMProvider =
this.apiConfiguration.apiProvider === "lmstudio" || this.apiConfiguration.apiProvider === "ollama"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compact prompt check happens on every getSystemPrompt() call. Since the provider type doesn't change during a task, could this be cached or memoized for better performance?

You could store the result of this check as a class property when the task is initialized.

const shouldUseCompactPrompt = isLocalLLMProvider && this.apiConfiguration.compactPromptMode

return await (async () => {
const provider = this.providerRef.deref()

Expand Down Expand Up @@ -2276,6 +2281,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
},
undefined, // todoList
this.api.getModel().id,
shouldUseCompactPrompt,
)
})()
}
Expand Down
44 changes: 44 additions & 0 deletions webview-ui/src/components/settings/CompactPromptControl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from "react"
import { useAppTranslation } from "@src/i18n/TranslationContext"

interface CompactPromptControlProps {
compactPromptMode?: boolean
onChange: (value: boolean) => void
providerName?: string
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For better type safety, consider using a union type for providerName:

Suggested change
providerName?: string
interface CompactPromptControlProps {
compactPromptMode?: boolean
onChange: (value: boolean) => void
providerName?: 'LM Studio' | 'Ollama'
}

}

export const CompactPromptControl: React.FC<CompactPromptControlProps> = ({
compactPromptMode = false,
onChange,
providerName,
}) => {
const { t } = useAppTranslation()

// Determine the correct translation key prefix based on provider
const translationPrefix = providerName === "LM Studio" ? "providers.lmStudio" : "providers.ollama"

return (
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<label htmlFor="compact-prompt-mode" className="font-medium">
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a tooltip or help icon to explain the trade-offs of enabling compact mode. Users should understand what features are excluded (MCP, browser tools, modes, etc.) when they enable this option.

You could add a small info icon next to the label that shows this information on hover.

{t(`settings:${translationPrefix}.compactPrompt.title`)}
</label>
<input
id="compact-prompt-mode"
type="checkbox"
checked={compactPromptMode}
onChange={(e) => onChange(e.target.checked)}
className="cursor-pointer"
/>
</div>
<p className="text-sm text-vscode-descriptionForeground">
{t(`settings:${translationPrefix}.compactPrompt.description`)}
</p>
{providerName && (
<p className="text-xs text-vscode-descriptionForeground italic">
{t(`settings:${translationPrefix}.compactPrompt.providerNote`, { provider: providerName })}
</p>
)}
</div>
)
}
6 changes: 6 additions & 0 deletions webview-ui/src/components/settings/providers/LMStudio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { vscode } from "@src/utils/vscode"

import { inputEventTransform } from "../transforms"
import { ModelRecord } from "@roo/api"
import { CompactPromptControl } from "../CompactPromptControl"

type LMStudioProps = {
apiConfiguration: ProviderSettings
Expand Down Expand Up @@ -207,6 +208,11 @@ export const LMStudio = ({ apiConfiguration, setApiConfigurationField }: LMStudi
)}
</>
)}
<CompactPromptControl
compactPromptMode={apiConfiguration?.compactPromptMode}
onChange={(value) => setApiConfigurationField("compactPromptMode", value)}
providerName="LM Studio"
/>
<div className="text-sm text-vscode-descriptionForeground">
<Trans
i18nKey="settings:providers.lmStudio.description"
Expand Down
6 changes: 6 additions & 0 deletions webview-ui/src/components/settings/providers/Ollama.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useRouterModels } from "@src/components/ui/hooks/useRouterModels"
import { vscode } from "@src/utils/vscode"

import { inputEventTransform } from "../transforms"
import { CompactPromptControl } from "../CompactPromptControl"

type OllamaProps = {
apiConfiguration: ProviderSettings
Expand Down Expand Up @@ -118,6 +119,11 @@ export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaPro
))}
</VSCodeRadioGroup>
)}
<CompactPromptControl
compactPromptMode={apiConfiguration?.compactPromptMode}
onChange={(value) => setApiConfigurationField("compactPromptMode", value)}
providerName="Ollama"
/>
<div className="text-sm text-vscode-descriptionForeground">
{t("settings:providers.ollama.description")}
<span className="text-vscode-errorForeground ml-1">{t("settings:providers.ollama.warning")}</span>
Expand Down
Loading
Loading