Skip to content

Commit f45d9be

Browse files
roomote[bot]roomotedaniel-lxs
authored
fix: respect maxReadFileLine setting for file mentions to prevent context exhaustion (#6073)
Co-authored-by: Roo Code <[email protected]> Co-authored-by: Daniel Riccio <[email protected]>
1 parent d62a260 commit f45d9be

File tree

6 files changed

+629
-4
lines changed

6 files changed

+629
-4
lines changed
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
// npx vitest core/mentions/__tests__/processUserContentMentions.spec.ts
2+
3+
import { describe, it, expect, vi, beforeEach } from "vitest"
4+
import { processUserContentMentions } from "../processUserContentMentions"
5+
import { parseMentions } from "../index"
6+
import { UrlContentFetcher } from "../../../services/browser/UrlContentFetcher"
7+
import { FileContextTracker } from "../../context-tracking/FileContextTracker"
8+
9+
// Mock the parseMentions function
10+
vi.mock("../index", () => ({
11+
parseMentions: vi.fn(),
12+
}))
13+
14+
describe("processUserContentMentions", () => {
15+
let mockUrlContentFetcher: UrlContentFetcher
16+
let mockFileContextTracker: FileContextTracker
17+
let mockRooIgnoreController: any
18+
19+
beforeEach(() => {
20+
vi.clearAllMocks()
21+
22+
mockUrlContentFetcher = {} as UrlContentFetcher
23+
mockFileContextTracker = {} as FileContextTracker
24+
mockRooIgnoreController = {}
25+
26+
// Default mock implementation
27+
vi.mocked(parseMentions).mockImplementation(async (text) => `parsed: ${text}`)
28+
})
29+
30+
describe("maxReadFileLine parameter", () => {
31+
it("should pass maxReadFileLine to parseMentions when provided", async () => {
32+
const userContent = [
33+
{
34+
type: "text" as const,
35+
text: "<task>Read file with limit</task>",
36+
},
37+
]
38+
39+
await processUserContentMentions({
40+
userContent,
41+
cwd: "/test",
42+
urlContentFetcher: mockUrlContentFetcher,
43+
fileContextTracker: mockFileContextTracker,
44+
rooIgnoreController: mockRooIgnoreController,
45+
maxReadFileLine: 100,
46+
})
47+
48+
expect(parseMentions).toHaveBeenCalledWith(
49+
"<task>Read file with limit</task>",
50+
"/test",
51+
mockUrlContentFetcher,
52+
mockFileContextTracker,
53+
mockRooIgnoreController,
54+
true,
55+
true, // includeDiagnosticMessages
56+
50, // maxDiagnosticMessages
57+
100,
58+
)
59+
})
60+
61+
it("should pass undefined maxReadFileLine when not provided", async () => {
62+
const userContent = [
63+
{
64+
type: "text" as const,
65+
text: "<task>Read file without limit</task>",
66+
},
67+
]
68+
69+
await processUserContentMentions({
70+
userContent,
71+
cwd: "/test",
72+
urlContentFetcher: mockUrlContentFetcher,
73+
fileContextTracker: mockFileContextTracker,
74+
rooIgnoreController: mockRooIgnoreController,
75+
})
76+
77+
expect(parseMentions).toHaveBeenCalledWith(
78+
"<task>Read file without limit</task>",
79+
"/test",
80+
mockUrlContentFetcher,
81+
mockFileContextTracker,
82+
mockRooIgnoreController,
83+
true,
84+
true, // includeDiagnosticMessages
85+
50, // maxDiagnosticMessages
86+
undefined,
87+
)
88+
})
89+
90+
it("should handle UNLIMITED_LINES constant correctly", async () => {
91+
const userContent = [
92+
{
93+
type: "text" as const,
94+
text: "<task>Read unlimited lines</task>",
95+
},
96+
]
97+
98+
await processUserContentMentions({
99+
userContent,
100+
cwd: "/test",
101+
urlContentFetcher: mockUrlContentFetcher,
102+
fileContextTracker: mockFileContextTracker,
103+
rooIgnoreController: mockRooIgnoreController,
104+
maxReadFileLine: -1,
105+
})
106+
107+
expect(parseMentions).toHaveBeenCalledWith(
108+
"<task>Read unlimited lines</task>",
109+
"/test",
110+
mockUrlContentFetcher,
111+
mockFileContextTracker,
112+
mockRooIgnoreController,
113+
true,
114+
true, // includeDiagnosticMessages
115+
50, // maxDiagnosticMessages
116+
-1,
117+
)
118+
})
119+
})
120+
121+
describe("content processing", () => {
122+
it("should process text blocks with <task> tags", async () => {
123+
const userContent = [
124+
{
125+
type: "text" as const,
126+
text: "<task>Do something</task>",
127+
},
128+
]
129+
130+
const result = await processUserContentMentions({
131+
userContent,
132+
cwd: "/test",
133+
urlContentFetcher: mockUrlContentFetcher,
134+
fileContextTracker: mockFileContextTracker,
135+
})
136+
137+
expect(parseMentions).toHaveBeenCalled()
138+
expect(result[0]).toEqual({
139+
type: "text",
140+
text: "parsed: <task>Do something</task>",
141+
})
142+
})
143+
144+
it("should process text blocks with <feedback> tags", async () => {
145+
const userContent = [
146+
{
147+
type: "text" as const,
148+
text: "<feedback>Fix this issue</feedback>",
149+
},
150+
]
151+
152+
const result = await processUserContentMentions({
153+
userContent,
154+
cwd: "/test",
155+
urlContentFetcher: mockUrlContentFetcher,
156+
fileContextTracker: mockFileContextTracker,
157+
})
158+
159+
expect(parseMentions).toHaveBeenCalled()
160+
expect(result[0]).toEqual({
161+
type: "text",
162+
text: "parsed: <feedback>Fix this issue</feedback>",
163+
})
164+
})
165+
166+
it("should not process text blocks without task or feedback tags", async () => {
167+
const userContent = [
168+
{
169+
type: "text" as const,
170+
text: "Regular text without special tags",
171+
},
172+
]
173+
174+
const result = await processUserContentMentions({
175+
userContent,
176+
cwd: "/test",
177+
urlContentFetcher: mockUrlContentFetcher,
178+
fileContextTracker: mockFileContextTracker,
179+
})
180+
181+
expect(parseMentions).not.toHaveBeenCalled()
182+
expect(result[0]).toEqual(userContent[0])
183+
})
184+
185+
it("should process tool_result blocks with string content", async () => {
186+
const userContent = [
187+
{
188+
type: "tool_result" as const,
189+
tool_use_id: "123",
190+
content: "<feedback>Tool feedback</feedback>",
191+
},
192+
]
193+
194+
const result = await processUserContentMentions({
195+
userContent,
196+
cwd: "/test",
197+
urlContentFetcher: mockUrlContentFetcher,
198+
fileContextTracker: mockFileContextTracker,
199+
})
200+
201+
expect(parseMentions).toHaveBeenCalled()
202+
expect(result[0]).toEqual({
203+
type: "tool_result",
204+
tool_use_id: "123",
205+
content: "parsed: <feedback>Tool feedback</feedback>",
206+
})
207+
})
208+
209+
it("should process tool_result blocks with array content", async () => {
210+
const userContent = [
211+
{
212+
type: "tool_result" as const,
213+
tool_use_id: "123",
214+
content: [
215+
{
216+
type: "text" as const,
217+
text: "<task>Array task</task>",
218+
},
219+
{
220+
type: "text" as const,
221+
text: "Regular text",
222+
},
223+
],
224+
},
225+
]
226+
227+
const result = await processUserContentMentions({
228+
userContent,
229+
cwd: "/test",
230+
urlContentFetcher: mockUrlContentFetcher,
231+
fileContextTracker: mockFileContextTracker,
232+
})
233+
234+
expect(parseMentions).toHaveBeenCalledTimes(1)
235+
expect(result[0]).toEqual({
236+
type: "tool_result",
237+
tool_use_id: "123",
238+
content: [
239+
{
240+
type: "text",
241+
text: "parsed: <task>Array task</task>",
242+
},
243+
{
244+
type: "text",
245+
text: "Regular text",
246+
},
247+
],
248+
})
249+
})
250+
251+
it("should handle mixed content types", async () => {
252+
const userContent = [
253+
{
254+
type: "text" as const,
255+
text: "<task>First task</task>",
256+
},
257+
{
258+
type: "image" as const,
259+
source: {
260+
type: "base64" as const,
261+
media_type: "image/png" as const,
262+
data: "base64data",
263+
},
264+
},
265+
{
266+
type: "tool_result" as const,
267+
tool_use_id: "456",
268+
content: "<feedback>Feedback</feedback>",
269+
},
270+
]
271+
272+
const result = await processUserContentMentions({
273+
userContent,
274+
cwd: "/test",
275+
urlContentFetcher: mockUrlContentFetcher,
276+
fileContextTracker: mockFileContextTracker,
277+
maxReadFileLine: 50,
278+
})
279+
280+
expect(parseMentions).toHaveBeenCalledTimes(2)
281+
expect(result).toHaveLength(3)
282+
expect(result[0]).toEqual({
283+
type: "text",
284+
text: "parsed: <task>First task</task>",
285+
})
286+
expect(result[1]).toEqual(userContent[1]) // Image block unchanged
287+
expect(result[2]).toEqual({
288+
type: "tool_result",
289+
tool_use_id: "456",
290+
content: "parsed: <feedback>Feedback</feedback>",
291+
})
292+
})
293+
})
294+
295+
describe("showRooIgnoredFiles parameter", () => {
296+
it("should default showRooIgnoredFiles to true", async () => {
297+
const userContent = [
298+
{
299+
type: "text" as const,
300+
text: "<task>Test default</task>",
301+
},
302+
]
303+
304+
await processUserContentMentions({
305+
userContent,
306+
cwd: "/test",
307+
urlContentFetcher: mockUrlContentFetcher,
308+
fileContextTracker: mockFileContextTracker,
309+
})
310+
311+
expect(parseMentions).toHaveBeenCalledWith(
312+
"<task>Test default</task>",
313+
"/test",
314+
mockUrlContentFetcher,
315+
mockFileContextTracker,
316+
undefined,
317+
true, // showRooIgnoredFiles should default to true
318+
true, // includeDiagnosticMessages
319+
50, // maxDiagnosticMessages
320+
undefined,
321+
)
322+
})
323+
324+
it("should respect showRooIgnoredFiles when explicitly set to false", async () => {
325+
const userContent = [
326+
{
327+
type: "text" as const,
328+
text: "<task>Test explicit false</task>",
329+
},
330+
]
331+
332+
await processUserContentMentions({
333+
userContent,
334+
cwd: "/test",
335+
urlContentFetcher: mockUrlContentFetcher,
336+
fileContextTracker: mockFileContextTracker,
337+
showRooIgnoredFiles: false,
338+
})
339+
340+
expect(parseMentions).toHaveBeenCalledWith(
341+
"<task>Test explicit false</task>",
342+
"/test",
343+
mockUrlContentFetcher,
344+
mockFileContextTracker,
345+
undefined,
346+
false,
347+
true, // includeDiagnosticMessages
348+
50, // maxDiagnosticMessages
349+
undefined,
350+
)
351+
})
352+
})
353+
})

0 commit comments

Comments
 (0)