@@ -2,7 +2,14 @@ import { execFileSync } from "node:child_process";
22import { mkdir , rm , writeFile } from "node:fs/promises" ;
33import { join } from "node:path" ;
44import { afterAll , beforeAll , describe , expect , it } from "vitest" ;
5- import { buildRepoTools , disposeRepoWorkspaces , workdirFor , type RepoSnapshot } from "./repo-tools" ;
5+ import {
6+ buildRepoTools ,
7+ disposeRepoWorkspaces ,
8+ MAX_READ_BYTES ,
9+ MAX_READ_LINES ,
10+ workdirFor ,
11+ type RepoSnapshot ,
12+ } from "./repo-tools" ;
613
714// The code tools normally download + extract a tarball. Here we pre-seed the
815// deterministic workspace path with a `.ready` marker, so `ensureWorkspace`
@@ -49,6 +56,17 @@ beforeAll(async () => {
4956 'import { task } from "@trigger.dev/sdk";\nconst LIMIT = 10000;\nexport const order = task({ id: "order" });\n'
5057 ) ;
5158 await writeFile ( join ( dir , "README.md" ) , "# demo\n" ) ;
59+ // Both ceilings blown: 4000 lines and ~200KB. Line 3000 is findable so a range
60+ // read can be checked past the line cap.
61+ await writeFile (
62+ join ( dir , "src/trigger/huge.ts" ) ,
63+ Array . from ( { length : 4000 } , ( _ , i ) => `// line ${ i + 1 } ${ "x" . repeat ( 40 ) } ` ) . join ( "\n" )
64+ ) ;
65+ // 4000 short lines: under the byte ceiling, so the line ceiling is what bites.
66+ await writeFile (
67+ join ( dir , "src/trigger/narrow.ts" ) ,
68+ Array . from ( { length : 4000 } , ( ) => "//" ) . join ( "\n" )
69+ ) ;
5270 await writeFile ( join ( dir , ".ready" ) , snapshot . sha ) ;
5371
5472 // The pinned commit's workspace, with a different LIMIT.
@@ -93,6 +111,40 @@ describe("repo-tools", () => {
93111 expect ( res . endLine ) . toBe ( 2 ) ;
94112 } ) ;
95113
114+ it ( "read_file caps a big file and tells the model how to get the rest" , async ( ) => {
115+ const res : any = await call ( tools . read_file , { path : "src/trigger/huge.ts" } ) ;
116+ expect ( res . truncated ) . toBe ( true ) ;
117+ expect ( res . notice ) . toMatch ( / s t a r t L i n e a n d e n d L i n e / ) ;
118+ // Long lines, so the byte ceiling bites before the line ceiling.
119+ expect ( res . content . split ( "\n" ) . length ) . toBeLessThanOrEqual ( MAX_READ_LINES ) ;
120+ expect ( Buffer . byteLength ( res . content , "utf8" ) ) . toBeLessThanOrEqual ( MAX_READ_BYTES ) ;
121+ expect ( res . content ) . not . toContain ( "// line 1501 " ) ;
122+ } ) ;
123+
124+ it ( "read_file caps a long file of short lines at the line ceiling" , async ( ) => {
125+ const res : any = await call ( tools . read_file , { path : "src/trigger/narrow.ts" } ) ;
126+ expect ( res . truncated ) . toBe ( true ) ;
127+ expect ( res . content . split ( "\n" ) ) . toHaveLength ( MAX_READ_LINES ) ;
128+ expect ( Buffer . byteLength ( res . content , "utf8" ) ) . toBeLessThan ( MAX_READ_BYTES ) ;
129+ } ) ;
130+
131+ it ( "read_file serves a range past the line cap, since the cap is applied after it" , async ( ) => {
132+ const res : any = await call ( tools . read_file , {
133+ path : "src/trigger/huge.ts" ,
134+ startLine : 3000 ,
135+ endLine : 3002 ,
136+ } ) ;
137+ expect ( res . truncated ) . toBeUndefined ( ) ;
138+ expect ( res . content . split ( "\n" ) [ 0 ] ) . toContain ( "// line 3000 " ) ;
139+ expect ( res . startLine ) . toBe ( 3000 ) ;
140+ } ) ;
141+
142+ it ( "read_file leaves a small file untruncated" , async ( ) => {
143+ const res : any = await call ( tools . read_file , { path : "src/trigger/order.ts" } ) ;
144+ expect ( res . truncated ) . toBe ( false ) ;
145+ expect ( res . notice ) . toBeUndefined ( ) ;
146+ } ) ;
147+
96148 it ( "read_file refuses to escape the repository root" , async ( ) => {
97149 for ( const path of [ "../../../etc/passwd" , "src/../../escape" , "../outside.txt" ] ) {
98150 const res : any = await call ( tools . read_file , { path } ) ;
0 commit comments