Skip to content
Merged
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
29 changes: 29 additions & 0 deletions docs/cli/configuration.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: "CLI Configuration"
description: "Configure Continue CLI behavior with environment variables"
sidebarTitle: "Configuration"
---

Continue CLI tools automatically truncate large outputs to prevent excessive token usage. You can customize these limits using environment variables.

## Environment Variables

| Environment Variable | Tool | Default |
|---------------------|------|--------:|
| `CONTINUE_CLI_BASH_MAX_OUTPUT_CHARS` | Bash | 50,000 |
| `CONTINUE_CLI_BASH_MAX_OUTPUT_LINES` | Bash | 1,000 |
| `CONTINUE_CLI_READ_FILE_MAX_OUTPUT_CHARS` | Read | 100,000 |
| `CONTINUE_CLI_READ_FILE_MAX_OUTPUT_LINES` | Read | 5,000 |
| `CONTINUE_CLI_FETCH_MAX_OUTPUT_LENGTH` | Fetch | 20,000 |
| `CONTINUE_CLI_DIFF_MAX_OUTPUT_LENGTH` | Diff | 50,000 |
| `CONTINUE_CLI_SEARCH_CODE_MAX_RESULTS` | Search | 100 |
| `CONTINUE_CLI_SEARCH_CODE_MAX_RESULT_CHARS` | Search | 1,000 |

## Example

```bash
# Increase limits for verbose build output
export CONTINUE_CLI_BASH_MAX_OUTPUT_CHARS=100000
export CONTINUE_CLI_BASH_MAX_OUTPUT_LINES=2000
cn
```
3 changes: 2 additions & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@
"cli/overview",
"cli/install",
"cli/quick-start",
"cli/guides"
"cli/guides",
"cli/configuration"
]
},
{
Expand Down
9 changes: 8 additions & 1 deletion extensions/cli/src/commands/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,14 @@ async function processMessage(
telemetryService.logUserPrompt(userInput.length, userInput);

// Check if auto-compacting is needed BEFORE adding user message
if (shouldAutoCompact(services.chatHistory.getHistory(), model)) {
// Note: This is a preliminary check without tools/systemMessage context.
// The streaming path performs a more accurate check with full context.
if (
shouldAutoCompact({
chatHistory: services.chatHistory.getHistory(),
model,
})
) {
const newIndex = await handleAutoCompaction(
chatHistory,
model,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,12 @@ describe("handleAutoCompaction", () => {
wasCompacted: false,
});

expect(shouldAutoCompact).toHaveBeenCalledWith(mockChatHistory, mockModel);
expect(shouldAutoCompact).toHaveBeenCalledWith({
chatHistory: mockChatHistory,
model: mockModel,
systemMessage: undefined,
tools: undefined,
});
});

it("should perform auto-compaction when context limit is approaching", async () => {
Expand Down Expand Up @@ -144,7 +149,12 @@ describe("handleAutoCompaction", () => {
},
);

expect(shouldAutoCompact).toHaveBeenCalledWith(mockChatHistory, mockModel);
expect(shouldAutoCompact).toHaveBeenCalledWith({
chatHistory: mockChatHistory,
model: mockModel,
systemMessage: undefined,
tools: undefined,
});
expect(getAutoCompactMessage).toHaveBeenCalledWith(mockModel);
expect(compactChatHistory).toHaveBeenCalledWith(
mockChatHistory,
Expand Down
13 changes: 12 additions & 1 deletion extensions/cli/src/stream/streamChatResponse.autoCompaction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ModelConfig } from "@continuedev/config-yaml";
import { BaseLlmApi } from "@continuedev/openai-adapters";
import type { ChatHistoryItem } from "core/index.js";
import type { ChatCompletionTool } from "openai/resources/chat/completions.mjs";
import React from "react";

import { compactChatHistory } from "../compaction.js";
Expand All @@ -27,6 +28,7 @@ interface AutoCompactionOptions {
format?: "json";
callbacks?: AutoCompactionCallbacks;
systemMessage?: string;
tools?: ChatCompletionTool[];
}

/**
Expand Down Expand Up @@ -133,9 +135,18 @@ export async function handleAutoCompaction(
isHeadless = false,
callbacks,
systemMessage: providedSystemMessage,
tools,
} = options;

if (!model || !shouldAutoCompact(chatHistory, model)) {
if (
!model ||
!shouldAutoCompact({
chatHistory,
model,
systemMessage: providedSystemMessage,
tools,
})
) {
return { chatHistory, compactionIndex: null, wasCompacted: false };
}

Expand Down
51 changes: 30 additions & 21 deletions extensions/cli/src/stream/streamChatResponse.compactionHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ModelConfig } from "@continuedev/config-yaml";
import { BaseLlmApi } from "@continuedev/openai-adapters";
import type { ChatHistoryItem } from "core/index.js";
import type { ChatCompletionTool } from "openai/resources/chat/completions.mjs";

import { services } from "../services/index.js";
import { ToolCall } from "../tools/index.js";
Expand All @@ -17,6 +18,7 @@ export interface CompactionHelperOptions {
isHeadless: boolean;
callbacks?: StreamCallbacks;
systemMessage: string;
tools?: ChatCompletionTool[];
}

/**
Expand All @@ -26,8 +28,15 @@ export async function handlePreApiCompaction(
chatHistory: ChatHistoryItem[],
options: CompactionHelperOptions,
): Promise<{ chatHistory: ChatHistoryItem[]; wasCompacted: boolean }> {
const { model, llmApi, isCompacting, isHeadless, callbacks, systemMessage } =
options;
const {
model,
llmApi,
isCompacting,
isHeadless,
callbacks,
systemMessage,
tools,
} = options;

if (isCompacting) {
return { chatHistory, wasCompacted: false };
Expand All @@ -41,6 +50,7 @@ export async function handlePreApiCompaction(
onContent: callbacks?.onContent,
},
systemMessage,
tools,
});

if (wasCompacted) {
Expand All @@ -67,7 +77,8 @@ export async function handlePostToolValidation(
chatHistory: ChatHistoryItem[],
options: CompactionHelperOptions,
): Promise<{ chatHistory: ChatHistoryItem[]; wasCompacted: boolean }> {
const { model, llmApi, isHeadless, callbacks, systemMessage } = options;
const { model, llmApi, isHeadless, callbacks, systemMessage, tools } =
options;

if (toolCalls.length === 0) {
return { chatHistory, wasCompacted: false };
Expand All @@ -82,21 +93,14 @@ export async function handlePostToolValidation(
chatHistory = chatHistorySvc.getHistory();
}

// Validate with system message to catch tool result overflow
const postToolSystemItem: ChatHistoryItem = {
message: {
role: "system",
content: systemMessage,
},
contextItems: [],
};

const SAFETY_BUFFER = 100;
const postToolValidation = validateContextLength(
[postToolSystemItem, ...chatHistory],
const postToolValidation = validateContextLength({
chatHistory,
model,
SAFETY_BUFFER,
);
safetyBuffer: SAFETY_BUFFER,
systemMessage,
tools,
});

// If tool results pushed us over limit, force compaction regardless of threshold
if (!postToolValidation.isValid) {
Expand All @@ -114,6 +118,7 @@ export async function handlePostToolValidation(
onContent: callbacks?.onContent,
},
systemMessage,
tools,
});

if (wasCompacted) {
Expand All @@ -127,11 +132,13 @@ export async function handlePostToolValidation(
}

// Verify compaction brought us under the limit
const postCompactionValidation = validateContextLength(
[postToolSystemItem, ...chatHistory],
const postCompactionValidation = validateContextLength({
chatHistory,
model,
SAFETY_BUFFER,
);
safetyBuffer: SAFETY_BUFFER,
systemMessage,
tools,
});

if (!postCompactionValidation.isValid) {
logger.error(
Expand Down Expand Up @@ -171,7 +178,8 @@ export async function handleNormalAutoCompaction(
shouldContinue: boolean,
options: CompactionHelperOptions,
): Promise<{ chatHistory: ChatHistoryItem[]; wasCompacted: boolean }> {
const { model, llmApi, isHeadless, callbacks, systemMessage } = options;
const { model, llmApi, isHeadless, callbacks, systemMessage, tools } =
options;

if (!shouldContinue) {
return { chatHistory, wasCompacted: false };
Expand All @@ -193,6 +201,7 @@ export async function handleNormalAutoCompaction(
onContent: callbacks?.onContent,
},
systemMessage,
tools,
});

if (wasCompacted) {
Expand Down
44 changes: 22 additions & 22 deletions extensions/cli/src/stream/streamChatResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,25 +215,17 @@ export async function processStreamingResponse(

let chatHistory = options.chatHistory;

// Create temporary system message item for validation
const systemMessageItem: ChatHistoryItem = {
message: {
role: "system",
content: systemMessage,
},
contextItems: [],
};

// Safety buffer to account for tokenization estimation errors
const SAFETY_BUFFER = 100;

// Validate context length INCLUDING system message
let historyWithSystem = [systemMessageItem, ...chatHistory];
let validation = validateContextLength(
historyWithSystem,
// Validate context length INCLUDING system message and tools
let validation = validateContextLength({
chatHistory,
model,
SAFETY_BUFFER,
);
safetyBuffer: SAFETY_BUFFER,
systemMessage,
tools,
});

// Prune last messages until valid (excluding system message)
while (chatHistory.length > 1 && !validation.isValid) {
Expand All @@ -243,9 +235,14 @@ export async function processStreamingResponse(
}
chatHistory = prunedChatHistory;

// Re-validate with system message
historyWithSystem = [systemMessageItem, ...chatHistory];
validation = validateContextLength(historyWithSystem, model, SAFETY_BUFFER);
// Re-validate with system message and tools
validation = validateContextLength({
chatHistory,
model,
safetyBuffer: SAFETY_BUFFER,
systemMessage,
tools,
});
}

if (!validation.isValid) {
Expand Down Expand Up @@ -464,23 +461,24 @@ export async function streamChatResponse(
services.toolPermissions.getState().currentMode,
);

// Pre-API auto-compaction checkpoint
// Recompute tools on each iteration to handle mode changes during streaming
const tools = await getRequestTools(isHeadless);

// Pre-API auto-compaction checkpoint (now includes tools)
const preCompactionResult = await handlePreApiCompaction(chatHistory, {
model,
llmApi,
isCompacting,
isHeadless,
callbacks,
systemMessage,
tools,
});
chatHistory = preCompactionResult.chatHistory;
if (preCompactionResult.wasCompacted) {
compactionOccurredThisTurn = true;
}

// Recompute tools on each iteration to handle mode changes during streaming
const tools = await getRequestTools(isHeadless);

logger.debug("Tools prepared", {
toolCount: tools.length,
toolNames: tools.map((t) => t.function.name),
Expand Down Expand Up @@ -541,6 +539,7 @@ export async function streamChatResponse(
isHeadless,
callbacks,
systemMessage,
tools,
},
);
chatHistory = postToolResult.chatHistory;
Expand All @@ -559,6 +558,7 @@ export async function streamChatResponse(
isHeadless,
callbacks,
systemMessage,
tools,
},
);
chatHistory = compactionResult.chatHistory;
Expand Down
7 changes: 3 additions & 4 deletions extensions/cli/src/tools/fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe("fetchTool", () => {
);
});

it("should handle truncation warnings from core implementation", async () => {
it("should filter out truncation warnings from core implementation", async () => {
const mockContextItems: ContextItem[] = [
{
name: "Long Page",
Expand All @@ -68,9 +68,8 @@ describe("fetchTool", () => {

const result = await fetchTool.run({ url: "https://example.com" });

expect(result).toBe(
"This is the main content that was truncated.\n\nThe content from https://example.com was truncated because it exceeded the 20000 character limit.",
);
// Truncation warnings are filtered out - only the main content is returned
expect(result).toBe("This is the main content that was truncated.");
});

it("should handle multiple content items", async () => {
Expand Down
Loading
Loading