mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-08 03:07:53 +00:00
Merge 4d8071551c into 2bb826039b
This commit is contained in:
commit
ce9322f8d8
9 changed files with 148 additions and 118 deletions
|
|
@ -164,6 +164,7 @@ export const globalSettingsSchema = z.object({
|
||||||
maxWorkspaceFiles: z.number().optional(),
|
maxWorkspaceFiles: z.number().optional(),
|
||||||
showRooIgnoredFiles: z.boolean().optional(),
|
showRooIgnoredFiles: z.boolean().optional(),
|
||||||
enableSubfolderRules: z.boolean().optional(),
|
enableSubfolderRules: z.boolean().optional(),
|
||||||
|
maxReadFileLine: z.number().optional(),
|
||||||
maxImageFileSize: z.number().optional(),
|
maxImageFileSize: z.number().optional(),
|
||||||
maxTotalImageSize: z.number().optional(),
|
maxTotalImageSize: z.number().optional(),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,122 +1,53 @@
|
||||||
import type OpenAI from "openai"
|
import { createReadFileTool, DEFAULT_LINE_LIMIT } from "../read_file"
|
||||||
import { createReadFileTool } from "../read_file"
|
|
||||||
|
|
||||||
// Helper type to access function tools
|
|
||||||
type FunctionTool = OpenAI.Chat.ChatCompletionTool & { type: "function" }
|
|
||||||
|
|
||||||
// Helper to get function definition from tool
|
|
||||||
const getFunctionDef = (tool: OpenAI.Chat.ChatCompletionTool) => (tool as FunctionTool).function
|
|
||||||
|
|
||||||
describe("createReadFileTool", () => {
|
describe("createReadFileTool", () => {
|
||||||
describe("single-file-per-call documentation", () => {
|
function getToolDesc(options = {}): string {
|
||||||
it("should indicate single-file-per-call and suggest parallel tool calls", () => {
|
const tool = createReadFileTool(options) as any
|
||||||
const tool = createReadFileTool()
|
return tool.function.description ?? ""
|
||||||
const description = getFunctionDef(tool).description
|
}
|
||||||
|
|
||||||
expect(description).toContain("exactly one file per call")
|
function getToolLimitParamDesc(options = {}): string {
|
||||||
expect(description).toContain("multiple parallel read_file calls")
|
const tool = createReadFileTool(options) as any
|
||||||
})
|
return tool.function.parameters.properties.limit.description ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
it("uses DEFAULT_LINE_LIMIT in description when maxReadFileLine is undefined", () => {
|
||||||
|
const desc = getToolDesc()
|
||||||
|
expect(desc).toContain(`returns up to ${DEFAULT_LINE_LIMIT} lines per file`)
|
||||||
|
expect(desc).not.toContain("no line limit")
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("indentation mode", () => {
|
it("uses DEFAULT_LINE_LIMIT in description when maxReadFileLine is -1", () => {
|
||||||
it("should always include indentation mode in description", () => {
|
const desc = getToolDesc({ maxReadFileLine: -1 })
|
||||||
const tool = createReadFileTool()
|
expect(desc).toContain(`returns up to ${DEFAULT_LINE_LIMIT} lines per file`)
|
||||||
const description = getFunctionDef(tool).description
|
|
||||||
|
|
||||||
expect(description).toContain("indentation")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should always include indentation parameter in schema", () => {
|
|
||||||
const tool = createReadFileTool()
|
|
||||||
const schema = getFunctionDef(tool).parameters as any
|
|
||||||
|
|
||||||
expect(schema.properties).toHaveProperty("indentation")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should include mode parameter in schema", () => {
|
|
||||||
const tool = createReadFileTool()
|
|
||||||
const schema = getFunctionDef(tool).parameters as any
|
|
||||||
|
|
||||||
expect(schema.properties).toHaveProperty("mode")
|
|
||||||
expect(schema.properties.mode.enum).toContain("slice")
|
|
||||||
expect(schema.properties.mode.enum).toContain("indentation")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should include offset and limit parameters in schema", () => {
|
|
||||||
const tool = createReadFileTool()
|
|
||||||
const schema = getFunctionDef(tool).parameters as any
|
|
||||||
|
|
||||||
expect(schema.properties).toHaveProperty("offset")
|
|
||||||
expect(schema.properties).toHaveProperty("limit")
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("supportsImages option", () => {
|
it("indicates no limit in description when maxReadFileLine is 0", () => {
|
||||||
it("should include image format documentation when supportsImages is true", () => {
|
const desc = getToolDesc({ maxReadFileLine: 0 })
|
||||||
const tool = createReadFileTool({ supportsImages: true })
|
expect(desc).toContain("no line limit")
|
||||||
const description = getFunctionDef(tool).description
|
expect(desc).not.toContain(`returns up to ${DEFAULT_LINE_LIMIT}`)
|
||||||
|
|
||||||
expect(description).toContain(
|
const limitDesc = getToolLimitParamDesc({ maxReadFileLine: 0 })
|
||||||
"Automatically processes and returns image files (PNG, JPG, JPEG, GIF, BMP, SVG, WEBP, ICO, AVIF) for visual analysis",
|
expect(limitDesc).toContain("no limit")
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should not include image format documentation when supportsImages is false", () => {
|
|
||||||
const tool = createReadFileTool({ supportsImages: false })
|
|
||||||
const description = getFunctionDef(tool).description
|
|
||||||
|
|
||||||
expect(description).not.toContain(
|
|
||||||
"Automatically processes and returns image files (PNG, JPG, JPEG, GIF, BMP, SVG, WEBP, ICO, AVIF) for visual analysis",
|
|
||||||
)
|
|
||||||
expect(description).toContain("may not handle other binary files properly")
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should default supportsImages to false", () => {
|
|
||||||
const tool = createReadFileTool({})
|
|
||||||
const description = getFunctionDef(tool).description
|
|
||||||
|
|
||||||
expect(description).not.toContain(
|
|
||||||
"Automatically processes and returns image files (PNG, JPG, JPEG, GIF, BMP, SVG, WEBP, ICO, AVIF) for visual analysis",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should always include PDF and DOCX support in description", () => {
|
|
||||||
const toolWithImages = createReadFileTool({ supportsImages: true })
|
|
||||||
const toolWithoutImages = createReadFileTool({ supportsImages: false })
|
|
||||||
|
|
||||||
expect(getFunctionDef(toolWithImages).description).toContain(
|
|
||||||
"Supports text extraction from PDF and DOCX files",
|
|
||||||
)
|
|
||||||
expect(getFunctionDef(toolWithoutImages).description).toContain(
|
|
||||||
"Supports text extraction from PDF and DOCX files",
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("tool structure", () => {
|
it("uses custom limit in description when maxReadFileLine is a positive number", () => {
|
||||||
it("should have correct tool name", () => {
|
const desc = getToolDesc({ maxReadFileLine: 500 })
|
||||||
const tool = createReadFileTool()
|
expect(desc).toContain("returns up to 500 lines per file")
|
||||||
|
// Should not mention DEFAULT_LINE_LIMIT as the line limit (but MAX_LINE_LENGTH=2000 chars is ok)
|
||||||
|
expect(desc).not.toContain(`up to ${DEFAULT_LINE_LIMIT} lines`)
|
||||||
|
|
||||||
expect(getFunctionDef(tool).name).toBe("read_file")
|
const limitDesc = getToolLimitParamDesc({ maxReadFileLine: 500 })
|
||||||
})
|
expect(limitDesc).toContain("500")
|
||||||
|
})
|
||||||
|
|
||||||
it("should be a function type tool", () => {
|
it("includes image support note when supportsImages is true", () => {
|
||||||
const tool = createReadFileTool()
|
const desc = getToolDesc({ supportsImages: true })
|
||||||
|
expect(desc).toContain("image files")
|
||||||
|
})
|
||||||
|
|
||||||
expect(tool.type).toBe("function")
|
it("does not include image support note when supportsImages is false", () => {
|
||||||
})
|
const desc = getToolDesc({ supportsImages: false })
|
||||||
|
expect(desc).not.toContain("image files")
|
||||||
it("should have strict mode enabled", () => {
|
|
||||||
const tool = createReadFileTool()
|
|
||||||
|
|
||||||
expect(getFunctionDef(tool).strict).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should require path parameter", () => {
|
|
||||||
const tool = createReadFileTool()
|
|
||||||
const schema = getFunctionDef(tool).parameters as any
|
|
||||||
|
|
||||||
expect(schema.required).toContain("path")
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ export type { ReadFileToolOptions } from "./read_file"
|
||||||
export interface NativeToolsOptions {
|
export interface NativeToolsOptions {
|
||||||
/** Whether the model supports image processing (default: false) */
|
/** Whether the model supports image processing (default: false) */
|
||||||
supportsImages?: boolean
|
supportsImages?: boolean
|
||||||
|
/** Maximum line limit for read_file tool. -1 or undefined = DEFAULT_LINE_LIMIT, 0 = no limit */
|
||||||
|
maxReadFileLine?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -40,10 +42,11 @@ export interface NativeToolsOptions {
|
||||||
* @returns Array of native tool definitions
|
* @returns Array of native tool definitions
|
||||||
*/
|
*/
|
||||||
export function getNativeTools(options: NativeToolsOptions = {}): OpenAI.Chat.ChatCompletionTool[] {
|
export function getNativeTools(options: NativeToolsOptions = {}): OpenAI.Chat.ChatCompletionTool[] {
|
||||||
const { supportsImages = false } = options
|
const { supportsImages = false, maxReadFileLine } = options
|
||||||
|
|
||||||
const readFileOptions: ReadFileToolOptions = {
|
const readFileOptions: ReadFileToolOptions = {
|
||||||
supportsImages,
|
supportsImages,
|
||||||
|
maxReadFileLine,
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ function getReadFileSupportsNote(supportsImages: boolean): string {
|
||||||
export interface ReadFileToolOptions {
|
export interface ReadFileToolOptions {
|
||||||
/** Whether the model supports image processing (default: false) */
|
/** Whether the model supports image processing (default: false) */
|
||||||
supportsImages?: boolean
|
supportsImages?: boolean
|
||||||
|
/** Maximum line limit for read_file tool. -1 or undefined = DEFAULT_LINE_LIMIT, 0 = no limit */
|
||||||
|
maxReadFileLine?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Schema Builder ───────────────────────────────────────────────────────────
|
// ─── Schema Builder ───────────────────────────────────────────────────────────
|
||||||
|
|
@ -58,7 +60,21 @@ export interface ReadFileToolOptions {
|
||||||
* @returns Native tool definition for read_file
|
* @returns Native tool definition for read_file
|
||||||
*/
|
*/
|
||||||
export function createReadFileTool(options: ReadFileToolOptions = {}): OpenAI.Chat.ChatCompletionTool {
|
export function createReadFileTool(options: ReadFileToolOptions = {}): OpenAI.Chat.ChatCompletionTool {
|
||||||
const { supportsImages = false } = options
|
const { supportsImages = false, maxReadFileLine } = options
|
||||||
|
|
||||||
|
// Resolve the effective line limit for the tool description.
|
||||||
|
// -1 or undefined: use DEFAULT_LINE_LIMIT (2000)
|
||||||
|
// 0: no limit (read entire file)
|
||||||
|
// positive number: use that value
|
||||||
|
const hasNoLimit = maxReadFileLine === 0
|
||||||
|
const effectiveLimit =
|
||||||
|
maxReadFileLine === undefined || maxReadFileLine === -1
|
||||||
|
? DEFAULT_LINE_LIMIT
|
||||||
|
: maxReadFileLine === 0
|
||||||
|
? undefined // no limit
|
||||||
|
: maxReadFileLine > 0
|
||||||
|
? maxReadFileLine
|
||||||
|
: DEFAULT_LINE_LIMIT
|
||||||
|
|
||||||
// Build description based on capabilities
|
// Build description based on capabilities
|
||||||
const descriptionIntro =
|
const descriptionIntro =
|
||||||
|
|
@ -70,7 +86,9 @@ export function createReadFileTool(options: ReadFileToolOptions = {}): OpenAI.Ch
|
||||||
` PREFER indentation mode when you have a specific line number from search results, error messages, or definition lookups - it guarantees complete, syntactically valid code blocks without mid-function truncation.` +
|
` PREFER indentation mode when you have a specific line number from search results, error messages, or definition lookups - it guarantees complete, syntactically valid code blocks without mid-function truncation.` +
|
||||||
` IMPORTANT: Indentation mode requires anchor_line to be useful. Without it, only header content (imports) is returned.`
|
` IMPORTANT: Indentation mode requires anchor_line to be useful. Without it, only header content (imports) is returned.`
|
||||||
|
|
||||||
const limitNote = ` By default, returns up to ${DEFAULT_LINE_LIMIT} lines per file. Lines longer than ${MAX_LINE_LENGTH} characters are truncated.`
|
const limitNote = hasNoLimit
|
||||||
|
? ` Reads the entire file by default (no line limit). Lines longer than ${MAX_LINE_LENGTH} characters are truncated.`
|
||||||
|
: ` By default, returns up to ${effectiveLimit} lines per file. Lines longer than ${MAX_LINE_LENGTH} characters are truncated.`
|
||||||
|
|
||||||
const description =
|
const description =
|
||||||
descriptionIntro +
|
descriptionIntro +
|
||||||
|
|
@ -125,7 +143,9 @@ export function createReadFileTool(options: ReadFileToolOptions = {}): OpenAI.Ch
|
||||||
},
|
},
|
||||||
limit: {
|
limit: {
|
||||||
type: "integer",
|
type: "integer",
|
||||||
description: `Maximum number of lines to return (slice mode, default: ${DEFAULT_LINE_LIMIT})`,
|
description: hasNoLimit
|
||||||
|
? `Maximum number of lines to return (slice mode, default: no limit - reads entire file)`
|
||||||
|
: `Maximum number of lines to return (slice mode, default: ${effectiveLimit})`,
|
||||||
},
|
},
|
||||||
indentation: {
|
indentation: {
|
||||||
type: "object",
|
type: "object",
|
||||||
|
|
|
||||||
|
|
@ -1673,6 +1673,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
||||||
apiConfiguration,
|
apiConfiguration,
|
||||||
disabledTools: state?.disabledTools,
|
disabledTools: state?.disabledTools,
|
||||||
modelInfo,
|
modelInfo,
|
||||||
|
maxReadFileLine: state?.maxReadFileLine,
|
||||||
includeAllToolsWithRestrictions: false,
|
includeAllToolsWithRestrictions: false,
|
||||||
})
|
})
|
||||||
allTools = toolsResult.tools
|
allTools = toolsResult.tools
|
||||||
|
|
@ -3865,6 +3866,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
||||||
apiConfiguration,
|
apiConfiguration,
|
||||||
disabledTools: state?.disabledTools,
|
disabledTools: state?.disabledTools,
|
||||||
modelInfo,
|
modelInfo,
|
||||||
|
maxReadFileLine: state?.maxReadFileLine,
|
||||||
includeAllToolsWithRestrictions: false,
|
includeAllToolsWithRestrictions: false,
|
||||||
})
|
})
|
||||||
allTools = toolsResult.tools
|
allTools = toolsResult.tools
|
||||||
|
|
@ -4079,6 +4081,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
||||||
apiConfiguration,
|
apiConfiguration,
|
||||||
disabledTools: state?.disabledTools,
|
disabledTools: state?.disabledTools,
|
||||||
modelInfo,
|
modelInfo,
|
||||||
|
maxReadFileLine: state?.maxReadFileLine,
|
||||||
includeAllToolsWithRestrictions: false,
|
includeAllToolsWithRestrictions: false,
|
||||||
})
|
})
|
||||||
contextMgmtTools = toolsResult.tools
|
contextMgmtTools = toolsResult.tools
|
||||||
|
|
@ -4243,6 +4246,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
||||||
apiConfiguration,
|
apiConfiguration,
|
||||||
disabledTools: state?.disabledTools,
|
disabledTools: state?.disabledTools,
|
||||||
modelInfo,
|
modelInfo,
|
||||||
|
maxReadFileLine: state?.maxReadFileLine,
|
||||||
includeAllToolsWithRestrictions: supportsAllowedFunctionNames,
|
includeAllToolsWithRestrictions: supportsAllowedFunctionNames,
|
||||||
})
|
})
|
||||||
allTools = toolsResult.tools
|
allTools = toolsResult.tools
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ interface BuildToolsOptions {
|
||||||
apiConfiguration: ProviderSettings | undefined
|
apiConfiguration: ProviderSettings | undefined
|
||||||
disabledTools?: string[]
|
disabledTools?: string[]
|
||||||
modelInfo?: ModelInfo
|
modelInfo?: ModelInfo
|
||||||
|
/** Maximum line limit for read_file tool. -1 or undefined = DEFAULT_LINE_LIMIT, 0 = no limit */
|
||||||
|
maxReadFileLine?: number
|
||||||
/**
|
/**
|
||||||
* If true, returns all tools without mode filtering, but also includes
|
* If true, returns all tools without mode filtering, but also includes
|
||||||
* the list of allowed tool names for use with allowedFunctionNames.
|
* the list of allowed tool names for use with allowedFunctionNames.
|
||||||
|
|
@ -89,6 +91,7 @@ export async function buildNativeToolsArrayWithRestrictions(options: BuildToolsO
|
||||||
apiConfiguration,
|
apiConfiguration,
|
||||||
disabledTools,
|
disabledTools,
|
||||||
modelInfo,
|
modelInfo,
|
||||||
|
maxReadFileLine,
|
||||||
includeAllToolsWithRestrictions,
|
includeAllToolsWithRestrictions,
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
|
|
@ -111,6 +114,7 @@ export async function buildNativeToolsArrayWithRestrictions(options: BuildToolsO
|
||||||
// Build native tools with dynamic read_file tool based on settings.
|
// Build native tools with dynamic read_file tool based on settings.
|
||||||
const nativeTools = getNativeTools({
|
const nativeTools = getNativeTools({
|
||||||
supportsImages,
|
supportsImages,
|
||||||
|
maxReadFileLine,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Filter native tools based on mode restrictions.
|
// Filter native tools based on mode restrictions.
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,27 @@ interface FileResult {
|
||||||
entry?: InternalFileEntry
|
entry?: InternalFileEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the effective line limit from the maxReadFileLine user setting.
|
||||||
|
*
|
||||||
|
* @param maxReadFileLine - The user setting value:
|
||||||
|
* - `-1` or `undefined`: use DEFAULT_LINE_LIMIT (2000)
|
||||||
|
* - `0`: no limit (read entire file) — returns `Infinity`
|
||||||
|
* - positive number: use that value as the limit
|
||||||
|
* @returns The resolved line limit
|
||||||
|
*/
|
||||||
|
export function resolveLineLimit(maxReadFileLine?: number): number {
|
||||||
|
if (maxReadFileLine === undefined || maxReadFileLine === null || maxReadFileLine === -1) {
|
||||||
|
return DEFAULT_LINE_LIMIT
|
||||||
|
}
|
||||||
|
if (maxReadFileLine === 0) {
|
||||||
|
return Infinity
|
||||||
|
}
|
||||||
|
return maxReadFileLine > 0 ? maxReadFileLine : DEFAULT_LINE_LIMIT
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Tool Implementation ──────────────────────────────────────────────────────
|
// ─── Tool Implementation ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
export class ReadFileTool extends BaseTool<"read_file"> {
|
export class ReadFileTool extends BaseTool<"read_file"> {
|
||||||
|
|
@ -169,10 +190,17 @@ export class ReadFileTool extends BaseTool<"read_file"> {
|
||||||
const imageMemoryTracker = new ImageMemoryTracker()
|
const imageMemoryTracker = new ImageMemoryTracker()
|
||||||
const state = await task.providerRef.deref()?.getState()
|
const state = await task.providerRef.deref()?.getState()
|
||||||
const {
|
const {
|
||||||
|
maxReadFileLine,
|
||||||
maxImageFileSize = DEFAULT_MAX_IMAGE_FILE_SIZE_MB,
|
maxImageFileSize = DEFAULT_MAX_IMAGE_FILE_SIZE_MB,
|
||||||
maxTotalImageSize = DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB,
|
maxTotalImageSize = DEFAULT_MAX_TOTAL_IMAGE_SIZE_MB,
|
||||||
} = state ?? {}
|
} = state ?? {}
|
||||||
|
|
||||||
|
// Resolve the effective line limit from the maxReadFileLine setting.
|
||||||
|
// -1 (default): use DEFAULT_LINE_LIMIT (2000)
|
||||||
|
// 0: no limit (read entire file)
|
||||||
|
// positive number: use that as the limit
|
||||||
|
const effectiveLineLimit = resolveLineLimit(maxReadFileLine)
|
||||||
|
|
||||||
for (const fileResult of fileResults) {
|
for (const fileResult of fileResults) {
|
||||||
if (fileResult.status !== "approved") continue
|
if (fileResult.status !== "approved") continue
|
||||||
|
|
||||||
|
|
@ -216,7 +244,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
|
||||||
// (they become U+FFFD replacement characters instead of throwing)
|
// (they become U+FFFD replacement characters instead of throwing)
|
||||||
const buffer = await fs.readFile(fullPath)
|
const buffer = await fs.readFile(fullPath)
|
||||||
const fileContent = buffer.toString("utf-8")
|
const fileContent = buffer.toString("utf-8")
|
||||||
const result = this.processTextFile(fileContent, entry)
|
const result = this.processTextFile(fileContent, entry, effectiveLineLimit)
|
||||||
|
|
||||||
await task.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)
|
await task.fileContextTracker.trackFileContext(relPath, "read_tool" as RecordSource)
|
||||||
|
|
||||||
|
|
@ -266,7 +294,11 @@ export class ReadFileTool extends BaseTool<"read_file"> {
|
||||||
/**
|
/**
|
||||||
* Process a text file according to the requested mode.
|
* Process a text file according to the requested mode.
|
||||||
*/
|
*/
|
||||||
private processTextFile(content: string, entry: InternalFileEntry): string {
|
private processTextFile(
|
||||||
|
content: string,
|
||||||
|
entry: InternalFileEntry,
|
||||||
|
effectiveLineLimit: number = DEFAULT_LINE_LIMIT,
|
||||||
|
): string {
|
||||||
const mode = entry.mode || "slice"
|
const mode = entry.mode || "slice"
|
||||||
|
|
||||||
if (mode === "indentation") {
|
if (mode === "indentation") {
|
||||||
|
|
@ -278,7 +310,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
|
||||||
maxLevels: entry.max_levels,
|
maxLevels: entry.max_levels,
|
||||||
includeSiblings: entry.include_siblings,
|
includeSiblings: entry.include_siblings,
|
||||||
includeHeader: entry.include_header,
|
includeHeader: entry.include_header,
|
||||||
limit: entry.limit ?? DEFAULT_LINE_LIMIT,
|
limit: entry.limit ?? effectiveLineLimit,
|
||||||
maxLines: entry.max_lines,
|
maxLines: entry.max_lines,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -287,7 +319,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
|
||||||
if (result.wasTruncated && result.includedRanges.length > 0) {
|
if (result.wasTruncated && result.includedRanges.length > 0) {
|
||||||
const [start, end] = result.includedRanges[0]
|
const [start, end] = result.includedRanges[0]
|
||||||
const nextOffset = end + 1
|
const nextOffset = end + 1
|
||||||
const effectiveLimit = entry.limit ?? DEFAULT_LINE_LIMIT
|
const effectiveLimit = entry.limit ?? effectiveLineLimit
|
||||||
// Put truncation warning at TOP (before content) to match @ mention format
|
// Put truncation warning at TOP (before content) to match @ mention format
|
||||||
output = `IMPORTANT: File content truncated.
|
output = `IMPORTANT: File content truncated.
|
||||||
Status: Showing lines ${start}-${end} of ${result.totalLines} total lines.
|
Status: Showing lines ${start}-${end} of ${result.totalLines} total lines.
|
||||||
|
|
@ -306,7 +338,7 @@ export class ReadFileTool extends BaseTool<"read_file"> {
|
||||||
// NOTE: read_file offset is 1-based externally; convert to 0-based for readWithSlice.
|
// NOTE: read_file offset is 1-based externally; convert to 0-based for readWithSlice.
|
||||||
const offset1 = entry.offset ?? 1
|
const offset1 = entry.offset ?? 1
|
||||||
const offset0 = Math.max(0, offset1 - 1)
|
const offset0 = Math.max(0, offset1 - 1)
|
||||||
const limit = entry.limit ?? DEFAULT_LINE_LIMIT
|
const limit = entry.limit ?? effectiveLineLimit
|
||||||
|
|
||||||
const result = readWithSlice(content, offset0, limit)
|
const result = readWithSlice(content, offset0, limit)
|
||||||
|
|
||||||
|
|
@ -786,8 +818,10 @@ export class ReadFileTool extends BaseTool<"read_file"> {
|
||||||
}
|
}
|
||||||
content = selectedLines.join("\n")
|
content = selectedLines.join("\n")
|
||||||
} else {
|
} else {
|
||||||
// Read with default limits using slice mode
|
// Read with effective limits using slice mode
|
||||||
const result = readWithSlice(rawContent, 0, DEFAULT_LINE_LIMIT)
|
const legacyState = await task.providerRef.deref()?.getState()
|
||||||
|
const legacyLineLimit = resolveLineLimit(legacyState?.maxReadFileLine)
|
||||||
|
const result = readWithSlice(rawContent, 0, legacyLineLimit)
|
||||||
content = result.content
|
content = result.content
|
||||||
if (result.wasTruncated) {
|
if (result.wasTruncated) {
|
||||||
content += `\n\n[File truncated: showing ${result.returnedLines} of ${result.totalLines} total lines]`
|
content += `\n\n[File truncated: showing ${result.returnedLines} of ${result.totalLines} total lines]`
|
||||||
|
|
|
||||||
30
src/core/tools/__tests__/resolveLineLimit.spec.ts
Normal file
30
src/core/tools/__tests__/resolveLineLimit.spec.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { resolveLineLimit } from "../ReadFileTool"
|
||||||
|
import { DEFAULT_LINE_LIMIT } from "../../prompts/tools/native-tools/read_file"
|
||||||
|
|
||||||
|
describe("resolveLineLimit", () => {
|
||||||
|
it("returns DEFAULT_LINE_LIMIT when undefined", () => {
|
||||||
|
expect(resolveLineLimit(undefined)).toBe(DEFAULT_LINE_LIMIT)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns DEFAULT_LINE_LIMIT when -1 (default setting)", () => {
|
||||||
|
expect(resolveLineLimit(-1)).toBe(DEFAULT_LINE_LIMIT)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns Infinity when 0 (no limit / read entire file)", () => {
|
||||||
|
expect(resolveLineLimit(0)).toBe(Infinity)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the positive value as-is", () => {
|
||||||
|
expect(resolveLineLimit(500)).toBe(500)
|
||||||
|
expect(resolveLineLimit(10000)).toBe(10000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns DEFAULT_LINE_LIMIT for negative values other than -1", () => {
|
||||||
|
expect(resolveLineLimit(-5)).toBe(DEFAULT_LINE_LIMIT)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns DEFAULT_LINE_LIMIT when null-ish (null cast as number)", () => {
|
||||||
|
// In practice, state?.maxReadFileLine could be null
|
||||||
|
expect(resolveLineLimit(null as unknown as number)).toBe(DEFAULT_LINE_LIMIT)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -2180,6 +2180,7 @@ export class ClineProvider
|
||||||
showRooIgnoredFiles,
|
showRooIgnoredFiles,
|
||||||
enableSubfolderRules,
|
enableSubfolderRules,
|
||||||
language,
|
language,
|
||||||
|
maxReadFileLine,
|
||||||
maxImageFileSize,
|
maxImageFileSize,
|
||||||
maxTotalImageSize,
|
maxTotalImageSize,
|
||||||
historyPreviewCollapsed,
|
historyPreviewCollapsed,
|
||||||
|
|
@ -2305,6 +2306,7 @@ export class ClineProvider
|
||||||
enableSubfolderRules: enableSubfolderRules ?? false,
|
enableSubfolderRules: enableSubfolderRules ?? false,
|
||||||
language: language ?? formatLanguage(vscode.env.language),
|
language: language ?? formatLanguage(vscode.env.language),
|
||||||
renderContext: this.renderContext,
|
renderContext: this.renderContext,
|
||||||
|
maxReadFileLine: maxReadFileLine ?? -1,
|
||||||
maxImageFileSize: maxImageFileSize ?? 5,
|
maxImageFileSize: maxImageFileSize ?? 5,
|
||||||
maxTotalImageSize: maxTotalImageSize ?? 20,
|
maxTotalImageSize: maxTotalImageSize ?? 20,
|
||||||
settingsImportedAt: this.settingsImportedAt,
|
settingsImportedAt: this.settingsImportedAt,
|
||||||
|
|
@ -2529,6 +2531,7 @@ export class ClineProvider
|
||||||
telemetrySetting: stateValues.telemetrySetting || "unset",
|
telemetrySetting: stateValues.telemetrySetting || "unset",
|
||||||
showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? false,
|
showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? false,
|
||||||
enableSubfolderRules: stateValues.enableSubfolderRules ?? false,
|
enableSubfolderRules: stateValues.enableSubfolderRules ?? false,
|
||||||
|
maxReadFileLine: stateValues.maxReadFileLine ?? -1,
|
||||||
maxImageFileSize: stateValues.maxImageFileSize ?? 5,
|
maxImageFileSize: stateValues.maxImageFileSize ?? 5,
|
||||||
maxTotalImageSize: stateValues.maxTotalImageSize ?? 20,
|
maxTotalImageSize: stateValues.maxTotalImageSize ?? 20,
|
||||||
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
|
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue