Skip to content

Commit 5a94d6f

Browse files
authored
Merge pull request #9467 from continuedev/dallin/context-length-exceeded
fix(cli): context length tweaks and terminal output truncation
2 parents 384cff5 + d7b91f8 commit 5a94d6f

19 files changed

+1463
-131
lines changed

docs/cli/configuration.mdx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
title: "CLI Configuration"
3+
description: "Configure Continue CLI behavior with environment variables"
4+
sidebarTitle: "Configuration"
5+
---
6+
7+
Continue CLI tools automatically truncate large outputs to prevent excessive token usage. You can customize these limits using environment variables.
8+
9+
## Environment Variables
10+
11+
| Environment Variable | Tool | Default |
12+
|---------------------|------|--------:|
13+
| `CONTINUE_CLI_BASH_MAX_OUTPUT_CHARS` | Bash | 50,000 |
14+
| `CONTINUE_CLI_BASH_MAX_OUTPUT_LINES` | Bash | 1,000 |
15+
| `CONTINUE_CLI_READ_FILE_MAX_OUTPUT_CHARS` | Read | 100,000 |
16+
| `CONTINUE_CLI_READ_FILE_MAX_OUTPUT_LINES` | Read | 5,000 |
17+
| `CONTINUE_CLI_FETCH_MAX_OUTPUT_LENGTH` | Fetch | 20,000 |
18+
| `CONTINUE_CLI_DIFF_MAX_OUTPUT_LENGTH` | Diff | 50,000 |
19+
| `CONTINUE_CLI_SEARCH_CODE_MAX_RESULTS` | Search | 100 |
20+
| `CONTINUE_CLI_SEARCH_CODE_MAX_RESULT_CHARS` | Search | 1,000 |
21+
22+
## Example
23+
24+
```bash
25+
# Increase limits for verbose build output
26+
export CONTINUE_CLI_BASH_MAX_OUTPUT_CHARS=100000
27+
export CONTINUE_CLI_BASH_MAX_OUTPUT_LINES=2000
28+
cn
29+
```

docs/docs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@
9191
"cli/overview",
9292
"cli/install",
9393
"cli/quick-start",
94-
"cli/guides"
94+
"cli/guides",
95+
"cli/configuration"
9596
]
9697
},
9798
{

extensions/cli/src/commands/chat.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,14 @@ async function processMessage(
363363
telemetryService.logUserPrompt(userInput.length, userInput);
364364

365365
// Check if auto-compacting is needed BEFORE adding user message
366-
if (shouldAutoCompact(services.chatHistory.getHistory(), model)) {
366+
// Note: This is a preliminary check without tools/systemMessage context.
367+
// The streaming path performs a more accurate check with full context.
368+
if (
369+
shouldAutoCompact({
370+
chatHistory: services.chatHistory.getHistory(),
371+
model,
372+
})
373+
) {
367374
const newIndex = await handleAutoCompaction(
368375
chatHistory,
369376
model,

extensions/cli/src/stream/streamChatResponse.autoCompaction.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,12 @@ describe("handleAutoCompaction", () => {
9999
wasCompacted: false,
100100
});
101101

102-
expect(shouldAutoCompact).toHaveBeenCalledWith(mockChatHistory, mockModel);
102+
expect(shouldAutoCompact).toHaveBeenCalledWith({
103+
chatHistory: mockChatHistory,
104+
model: mockModel,
105+
systemMessage: undefined,
106+
tools: undefined,
107+
});
103108
});
104109

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

147-
expect(shouldAutoCompact).toHaveBeenCalledWith(mockChatHistory, mockModel);
152+
expect(shouldAutoCompact).toHaveBeenCalledWith({
153+
chatHistory: mockChatHistory,
154+
model: mockModel,
155+
systemMessage: undefined,
156+
tools: undefined,
157+
});
148158
expect(getAutoCompactMessage).toHaveBeenCalledWith(mockModel);
149159
expect(compactChatHistory).toHaveBeenCalledWith(
150160
mockChatHistory,

extensions/cli/src/stream/streamChatResponse.autoCompaction.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ModelConfig } from "@continuedev/config-yaml";
22
import { BaseLlmApi } from "@continuedev/openai-adapters";
33
import type { ChatHistoryItem } from "core/index.js";
4+
import type { ChatCompletionTool } from "openai/resources/chat/completions.mjs";
45
import React from "react";
56

67
import { compactChatHistory } from "../compaction.js";
@@ -27,6 +28,7 @@ interface AutoCompactionOptions {
2728
format?: "json";
2829
callbacks?: AutoCompactionCallbacks;
2930
systemMessage?: string;
31+
tools?: ChatCompletionTool[];
3032
}
3133

3234
/**
@@ -133,9 +135,18 @@ export async function handleAutoCompaction(
133135
isHeadless = false,
134136
callbacks,
135137
systemMessage: providedSystemMessage,
138+
tools,
136139
} = options;
137140

138-
if (!model || !shouldAutoCompact(chatHistory, model)) {
141+
if (
142+
!model ||
143+
!shouldAutoCompact({
144+
chatHistory,
145+
model,
146+
systemMessage: providedSystemMessage,
147+
tools,
148+
})
149+
) {
139150
return { chatHistory, compactionIndex: null, wasCompacted: false };
140151
}
141152

extensions/cli/src/stream/streamChatResponse.compactionHelpers.ts

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ModelConfig } from "@continuedev/config-yaml";
22
import { BaseLlmApi } from "@continuedev/openai-adapters";
33
import type { ChatHistoryItem } from "core/index.js";
4+
import type { ChatCompletionTool } from "openai/resources/chat/completions.mjs";
45

56
import { services } from "../services/index.js";
67
import { ToolCall } from "../tools/index.js";
@@ -17,6 +18,7 @@ export interface CompactionHelperOptions {
1718
isHeadless: boolean;
1819
callbacks?: StreamCallbacks;
1920
systemMessage: string;
21+
tools?: ChatCompletionTool[];
2022
}
2123

2224
/**
@@ -26,8 +28,15 @@ export async function handlePreApiCompaction(
2628
chatHistory: ChatHistoryItem[],
2729
options: CompactionHelperOptions,
2830
): Promise<{ chatHistory: ChatHistoryItem[]; wasCompacted: boolean }> {
29-
const { model, llmApi, isCompacting, isHeadless, callbacks, systemMessage } =
30-
options;
31+
const {
32+
model,
33+
llmApi,
34+
isCompacting,
35+
isHeadless,
36+
callbacks,
37+
systemMessage,
38+
tools,
39+
} = options;
3140

3241
if (isCompacting) {
3342
return { chatHistory, wasCompacted: false };
@@ -41,6 +50,7 @@ export async function handlePreApiCompaction(
4150
onContent: callbacks?.onContent,
4251
},
4352
systemMessage,
53+
tools,
4454
});
4555

4656
if (wasCompacted) {
@@ -67,7 +77,8 @@ export async function handlePostToolValidation(
6777
chatHistory: ChatHistoryItem[],
6878
options: CompactionHelperOptions,
6979
): Promise<{ chatHistory: ChatHistoryItem[]; wasCompacted: boolean }> {
70-
const { model, llmApi, isHeadless, callbacks, systemMessage } = options;
80+
const { model, llmApi, isHeadless, callbacks, systemMessage, tools } =
81+
options;
7182

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

85-
// Validate with system message to catch tool result overflow
86-
const postToolSystemItem: ChatHistoryItem = {
87-
message: {
88-
role: "system",
89-
content: systemMessage,
90-
},
91-
contextItems: [],
92-
};
93-
9496
const SAFETY_BUFFER = 100;
95-
const postToolValidation = validateContextLength(
96-
[postToolSystemItem, ...chatHistory],
97+
const postToolValidation = validateContextLength({
98+
chatHistory,
9799
model,
98-
SAFETY_BUFFER,
99-
);
100+
safetyBuffer: SAFETY_BUFFER,
101+
systemMessage,
102+
tools,
103+
});
100104

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

119124
if (wasCompacted) {
@@ -127,11 +132,13 @@ export async function handlePostToolValidation(
127132
}
128133

129134
// Verify compaction brought us under the limit
130-
const postCompactionValidation = validateContextLength(
131-
[postToolSystemItem, ...chatHistory],
135+
const postCompactionValidation = validateContextLength({
136+
chatHistory,
132137
model,
133-
SAFETY_BUFFER,
134-
);
138+
safetyBuffer: SAFETY_BUFFER,
139+
systemMessage,
140+
tools,
141+
});
135142

136143
if (!postCompactionValidation.isValid) {
137144
logger.error(
@@ -171,7 +178,8 @@ export async function handleNormalAutoCompaction(
171178
shouldContinue: boolean,
172179
options: CompactionHelperOptions,
173180
): Promise<{ chatHistory: ChatHistoryItem[]; wasCompacted: boolean }> {
174-
const { model, llmApi, isHeadless, callbacks, systemMessage } = options;
181+
const { model, llmApi, isHeadless, callbacks, systemMessage, tools } =
182+
options;
175183

176184
if (!shouldContinue) {
177185
return { chatHistory, wasCompacted: false };
@@ -193,6 +201,7 @@ export async function handleNormalAutoCompaction(
193201
onContent: callbacks?.onContent,
194202
},
195203
systemMessage,
204+
tools,
196205
});
197206

198207
if (wasCompacted) {

extensions/cli/src/stream/streamChatResponse.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -215,25 +215,17 @@ export async function processStreamingResponse(
215215

216216
let chatHistory = options.chatHistory;
217217

218-
// Create temporary system message item for validation
219-
const systemMessageItem: ChatHistoryItem = {
220-
message: {
221-
role: "system",
222-
content: systemMessage,
223-
},
224-
contextItems: [],
225-
};
226-
227218
// Safety buffer to account for tokenization estimation errors
228219
const SAFETY_BUFFER = 100;
229220

230-
// Validate context length INCLUDING system message
231-
let historyWithSystem = [systemMessageItem, ...chatHistory];
232-
let validation = validateContextLength(
233-
historyWithSystem,
221+
// Validate context length INCLUDING system message and tools
222+
let validation = validateContextLength({
223+
chatHistory,
234224
model,
235-
SAFETY_BUFFER,
236-
);
225+
safetyBuffer: SAFETY_BUFFER,
226+
systemMessage,
227+
tools,
228+
});
237229

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

246-
// Re-validate with system message
247-
historyWithSystem = [systemMessageItem, ...chatHistory];
248-
validation = validateContextLength(historyWithSystem, model, SAFETY_BUFFER);
238+
// Re-validate with system message and tools
239+
validation = validateContextLength({
240+
chatHistory,
241+
model,
242+
safetyBuffer: SAFETY_BUFFER,
243+
systemMessage,
244+
tools,
245+
});
249246
}
250247

251248
if (!validation.isValid) {
@@ -464,23 +461,24 @@ export async function streamChatResponse(
464461
services.toolPermissions.getState().currentMode,
465462
);
466463

467-
// Pre-API auto-compaction checkpoint
464+
// Recompute tools on each iteration to handle mode changes during streaming
465+
const tools = await getRequestTools(isHeadless);
466+
467+
// Pre-API auto-compaction checkpoint (now includes tools)
468468
const preCompactionResult = await handlePreApiCompaction(chatHistory, {
469469
model,
470470
llmApi,
471471
isCompacting,
472472
isHeadless,
473473
callbacks,
474474
systemMessage,
475+
tools,
475476
});
476477
chatHistory = preCompactionResult.chatHistory;
477478
if (preCompactionResult.wasCompacted) {
478479
compactionOccurredThisTurn = true;
479480
}
480481

481-
// Recompute tools on each iteration to handle mode changes during streaming
482-
const tools = await getRequestTools(isHeadless);
483-
484482
logger.debug("Tools prepared", {
485483
toolCount: tools.length,
486484
toolNames: tools.map((t) => t.function.name),
@@ -541,6 +539,7 @@ export async function streamChatResponse(
541539
isHeadless,
542540
callbacks,
543541
systemMessage,
542+
tools,
544543
},
545544
);
546545
chatHistory = postToolResult.chatHistory;
@@ -559,6 +558,7 @@ export async function streamChatResponse(
559558
isHeadless,
560559
callbacks,
561560
systemMessage,
561+
tools,
562562
},
563563
);
564564
chatHistory = compactionResult.chatHistory;

extensions/cli/src/tools/fetch.test.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ describe("fetchTool", () => {
4848
);
4949
});
5050

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

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

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

7675
it("should handle multiple content items", async () => {

0 commit comments

Comments
 (0)