Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
40a7bca
feat(read_file): implement CodexCLI-inspired slice/indentation readin…
hannesrudolph Dec 21, 2025
339c35c
i18n: add read file tool strings
hannesrudolph Dec 21, 2025
c40ca98
fix(read_file): address PR review feedback
hannesrudolph Dec 22, 2025
7a136cd
fix(read_file): normalize indentation config keys and pagination meta…
hannesrudolph Dec 22, 2025
2f2cdc7
fix(read_file): make indentation offset beyond EOF return empty page
hannesrudolph Dec 22, 2025
9702249
fix(read_file): empty files should not report hasMoreBefore: true in …
hannesrudolph Dec 22, 2025
be8b82b
fix: update readFileTool tests to use native format instead of XML
hannesrudolph Dec 23, 2025
a5d23a8
fix: remove unused FileEntry.limit from types and parser
hannesrudolph Dec 23, 2025
3d0a3d8
fix: update type import from ReadFileToolOptions to CreateReadFileToo…
hannesrudolph Jan 7, 2026
51969aa
docs: clarify limit is not exposed to models, controlled by maxReadFi…
hannesrudolph Jan 7, 2026
ada2fd7
test: update read_file.spec.ts tests for new offset/mode/indentation API
hannesrudolph Jan 7, 2026
dbe67cf
fix: add missing mock methods to presentAssistantMessage-custom-tool …
hannesrudolph Jan 7, 2026
853d122
fix: resolve conflicts and address review comments regarding FileEntr…
hannesrudolph Jan 14, 2026
1834bea
fix: update FALLBACK_LIMIT to 2000 lines
hannesrudolph Jan 14, 2026
21f3833
fix: align read_file tool with plan spec
hannesrudolph Jan 15, 2026
4b14363
fix: ensure truncatedByLimit is only true when content is actually ex…
hannesrudolph Jan 15, 2026
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
31 changes: 27 additions & 4 deletions packages/types/src/tool-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,37 @@
* Tool parameter type definitions for native protocol
*/

export interface LineRange {
start: number
end: number
/**
* Configuration for indentation-aware block extraction
*/
export interface IndentationConfig {
/** The line to anchor the block expansion from (defaults to offset) */
anchorLine?: number
/** Maximum indentation depth to collect; 0 = unlimited */
maxLevels?: number
/** Whether to include sibling blocks at same indentation level */
includeSiblings?: boolean
/** Whether to include comment headers above the anchor block */
includeHeader?: boolean
/** Hard cap on returned lines (defaults to limit) */
maxLines?: number
}

/**
* Read mode for file content extraction
*/
export type ReadMode = "slice" | "indentation"

export interface FileEntry {
path: string
lineRanges?: LineRange[]
/** 1-indexed line number to start reading from (default: 1) */
offset?: number
/** Maximum lines to return (default: value of maxReadFileLine setting) */
limit?: number
/** Reading mode: "slice" for simple reading, "indentation" for smart block extraction */
mode?: ReadMode
/** Configuration for indentation mode */
indentation?: IndentationConfig
}

export interface Coordinate {
Expand Down
88 changes: 61 additions & 27 deletions src/core/assistant-message/NativeToolCallParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,40 +298,74 @@ export class NativeToolCallParser {
}

/**
* Convert raw file entries from API (with line_ranges) to FileEntry objects
* (with lineRanges). Handles multiple formats for compatibility:
* Convert raw file entries from API to FileEntry objects.
* Supports the new slice/indentation API:
*
* New tuple format: { path: string, line_ranges: [[1, 50], [100, 150]] }
* Object format: { path: string, line_ranges: [{ start: 1, end: 50 }] }
* Legacy string format: { path: string, line_ranges: ["1-50"] }
* { path: string, offset?: number, mode?: "slice" | "indentation", indentation?: {...} }
*
* Returns: { path: string, lineRanges: [{ start: 1, end: 50 }] }
* Note: limit is intentionally not exposed to models - it's controlled by the maxReadFileLine setting.
*/
private static convertFileEntries(files: any[]): FileEntry[] {
return files.map((file: any) => {
const entry: FileEntry = { path: file.path }
if (file.line_ranges && Array.isArray(file.line_ranges)) {
entry.lineRanges = file.line_ranges
.map((range: any) => {
// Handle tuple format: [start, end]
if (Array.isArray(range) && range.length >= 2) {
return { start: Number(range[0]), end: Number(range[1]) }
}
// Handle object format: { start: number, end: number }
if (typeof range === "object" && range !== null && "start" in range && "end" in range) {
return { start: Number(range.start), end: Number(range.end) }
}
// Handle legacy string format: "1-50"
if (typeof range === "string") {
const match = range.match(/^(\d+)-(\d+)$/)
if (match) {
return { start: parseInt(match[1], 10), end: parseInt(match[2], 10) }
}
}
return null
})
.filter(Boolean)

// Map offset parameter
if (file.offset !== undefined) {
const offset = Number(file.offset)
if (!isNaN(offset) && offset > 0) {
entry.offset = offset
}
}

// Map mode parameter
if (file.mode === "slice" || file.mode === "indentation") {
entry.mode = file.mode
}

// Map indentation configuration
if (file.indentation && typeof file.indentation === "object") {
const indent = file.indentation
const indentConfig: FileEntry["indentation"] = {}

// anchorLine
if (indent.anchorLine !== undefined) {
const anchorLine = Number(indent.anchorLine)
if (!isNaN(anchorLine) && anchorLine > 0) {
indentConfig.anchorLine = anchorLine
}
}

// maxLevels
if (indent.maxLevels !== undefined) {
const maxLevels = Number(indent.maxLevels)
if (!isNaN(maxLevels) && maxLevels >= 0) {
indentConfig.maxLevels = maxLevels
}
}

// includeSiblings
if (indent.includeSiblings !== undefined) {
indentConfig.includeSiblings = Boolean(indent.includeSiblings)
}

// includeHeader
if (indent.includeHeader !== undefined) {
indentConfig.includeHeader = Boolean(indent.includeHeader)
}

// maxLines
if (indent.maxLines !== undefined) {
const maxLines = Number(indent.maxLines)
if (!isNaN(maxLines) && maxLines > 0) {
indentConfig.maxLines = maxLines
}
}

if (Object.keys(indentConfig).length > 0) {
entry.indentation = indentConfig
}
}

return entry
})
}
Expand Down
Loading
Loading