Skip to content

Commit b0362be

Browse files
committed
feat(read_file): implement CodexCLI-inspired slice/indentation reading modes
Replace line_ranges with new offset/limit/mode API for improved file reading: - Slice mode: simple line-by-line reading with offset pagination - Indentation mode: smart code block extraction based on indentation - Add rich metadata for pagination awareness (hasMoreBefore/After, etc.) - Remove deprecated truncateDefinitions helper - Update tool definition with new parameters and examples - Default maxReadFileLine from 500 to 2000 lines
1 parent 2bb3755 commit b0362be

14 files changed

Lines changed: 1291 additions & 621 deletions

File tree

packages/types/src/tool-params.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,37 @@
22
* Tool parameter type definitions for native protocol
33
*/
44

5-
export interface LineRange {
6-
start: number
7-
end: number
5+
/**
6+
* Configuration for indentation-aware block extraction
7+
*/
8+
export interface IndentationConfig {
9+
/** The line to anchor the block expansion from (defaults to offset) */
10+
anchorLine?: number
11+
/** Maximum indentation depth to collect; 0 = unlimited */
12+
maxLevels?: number
13+
/** Whether to include sibling blocks at same indentation level */
14+
includeSiblings?: boolean
15+
/** Whether to include comment headers above the anchor block */
16+
includeHeader?: boolean
17+
/** Hard cap on returned lines (defaults to limit) */
18+
maxLines?: number
819
}
920

21+
/**
22+
* Read mode for file content extraction
23+
*/
24+
export type ReadMode = "slice" | "indentation"
25+
1026
export interface FileEntry {
1127
path: string
12-
lineRanges?: LineRange[]
28+
/** 1-indexed line number to start reading from (default: 1) */
29+
offset?: number
30+
/** Maximum number of lines to return (default: 2000) */
31+
limit?: number
32+
/** Reading mode: "slice" for simple reading, "indentation" for smart block extraction */
33+
mode?: ReadMode
34+
/** Configuration for indentation mode */
35+
indentation?: IndentationConfig
1336
}
1437

1538
export interface Coordinate {

src/core/assistant-message/NativeToolCallParser.ts

Lines changed: 63 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -298,40 +298,75 @@ export class NativeToolCallParser {
298298
}
299299

300300
/**
301-
* Convert raw file entries from API (with line_ranges) to FileEntry objects
302-
* (with lineRanges). Handles multiple formats for compatibility:
301+
* Convert raw file entries from API to FileEntry objects.
302+
* Supports the new slice/indentation API:
303303
*
304-
* New tuple format: { path: string, line_ranges: [[1, 50], [100, 150]] }
305-
* Object format: { path: string, line_ranges: [{ start: 1, end: 50 }] }
306-
* Legacy string format: { path: string, line_ranges: ["1-50"] }
307-
*
308-
* Returns: { path: string, lineRanges: [{ start: 1, end: 50 }] }
304+
* { path: string, offset?: number, limit?: number, mode?: "slice" | "indentation", indentation?: {...} }
309305
*/
310306
private static convertFileEntries(files: any[]): FileEntry[] {
311307
return files.map((file: any) => {
312308
const entry: FileEntry = { path: file.path }
313-
if (file.line_ranges && Array.isArray(file.line_ranges)) {
314-
entry.lineRanges = file.line_ranges
315-
.map((range: any) => {
316-
// Handle tuple format: [start, end]
317-
if (Array.isArray(range) && range.length >= 2) {
318-
return { start: Number(range[0]), end: Number(range[1]) }
319-
}
320-
// Handle object format: { start: number, end: number }
321-
if (typeof range === "object" && range !== null && "start" in range && "end" in range) {
322-
return { start: Number(range.start), end: Number(range.end) }
323-
}
324-
// Handle legacy string format: "1-50"
325-
if (typeof range === "string") {
326-
const match = range.match(/^(\d+)-(\d+)$/)
327-
if (match) {
328-
return { start: parseInt(match[1], 10), end: parseInt(match[2], 10) }
329-
}
330-
}
331-
return null
332-
})
333-
.filter(Boolean)
309+
310+
// Map offset parameter
311+
if (file.offset !== undefined) {
312+
const offset = Number(file.offset)
313+
if (!isNaN(offset) && offset > 0) {
314+
entry.offset = offset
315+
}
316+
}
317+
318+
// Map limit parameter
319+
if (file.limit !== undefined) {
320+
const limit = Number(file.limit)
321+
if (!isNaN(limit) && limit > 0) {
322+
entry.limit = limit
323+
}
334324
}
325+
326+
// Map mode parameter
327+
if (file.mode === "slice" || file.mode === "indentation") {
328+
entry.mode = file.mode
329+
}
330+
331+
// Map indentation configuration
332+
if (file.indentation && typeof file.indentation === "object") {
333+
const indent = file.indentation
334+
const indentConfig: FileEntry["indentation"] = {}
335+
336+
if (indent.anchor_line !== undefined) {
337+
const anchorLine = Number(indent.anchor_line)
338+
if (!isNaN(anchorLine) && anchorLine > 0) {
339+
indentConfig.anchorLine = anchorLine
340+
}
341+
}
342+
343+
if (indent.max_levels !== undefined) {
344+
const maxLevels = Number(indent.max_levels)
345+
if (!isNaN(maxLevels) && maxLevels >= 0) {
346+
indentConfig.maxLevels = maxLevels
347+
}
348+
}
349+
350+
if (indent.include_siblings !== undefined) {
351+
indentConfig.includeSiblings = Boolean(indent.include_siblings)
352+
}
353+
354+
if (indent.include_header !== undefined) {
355+
indentConfig.includeHeader = Boolean(indent.include_header)
356+
}
357+
358+
if (indent.max_lines !== undefined) {
359+
const maxLines = Number(indent.max_lines)
360+
if (!isNaN(maxLines) && maxLines > 0) {
361+
indentConfig.maxLines = maxLines
362+
}
363+
}
364+
365+
if (Object.keys(indentConfig).length > 0) {
366+
entry.indentation = indentConfig
367+
}
368+
}
369+
335370
return entry
336371
})
337372
}

0 commit comments

Comments
 (0)