Skip to content

Commit 4487c4e

Browse files
committed
Fix file picker
1 parent 32197cb commit 4487c4e

File tree

2 files changed

+423
-23
lines changed

2 files changed

+423
-23
lines changed
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
import { API_KEY_ENV_VAR } from '@codebuff/common/old-constants'
2+
import { describe, expect, it } from 'bun:test'
3+
4+
import { CodebuffClient } from '@codebuff/sdk'
5+
import filePickerDefinition from '../file-explorer/file-picker'
6+
import fileListerDefinition from '../file-explorer/file-lister'
7+
8+
import type { PrintModeEvent } from '@codebuff/common/types/print-mode'
9+
10+
/**
11+
* Integration tests for agents that use the read_subtree tool.
12+
* These tests verify that the SDK properly initializes the session state
13+
* with project files and that agents can access the file tree through
14+
* the read_subtree tool.
15+
*
16+
* The file-lister agent is used directly instead of file-picker because:
17+
* - file-lister directly uses the read_subtree tool
18+
* - file-picker spawns file-lister as a subagent, adding complexity
19+
* - Testing file-lister directly verifies the core functionality
20+
*/
21+
describe('File Lister Agent Integration - read_subtree tool', () => {
22+
it(
23+
'should find relevant files using read_subtree tool',
24+
async () => {
25+
const apiKey = process.env[API_KEY_ENV_VAR]
26+
if (!apiKey) {
27+
throw new Error('API key not found')
28+
}
29+
30+
// Create mock project files that the file-lister should be able to find
31+
const projectFiles: Record<string, string> = {
32+
'src/index.ts': `
33+
import { UserService } from './services/user-service'
34+
import { AuthService } from './services/auth-service'
35+
36+
export function main() {
37+
const userService = new UserService()
38+
const authService = new AuthService()
39+
console.log('Application started')
40+
}
41+
`,
42+
'src/services/user-service.ts': `
43+
export class UserService {
44+
async getUser(id: string) {
45+
return { id, name: 'John Doe' }
46+
}
47+
48+
async createUser(name: string) {
49+
return { id: 'new-user-id', name }
50+
}
51+
52+
async deleteUser(id: string) {
53+
console.log('User deleted:', id)
54+
}
55+
}
56+
`,
57+
'src/services/auth-service.ts': `
58+
export class AuthService {
59+
async login(email: string, password: string) {
60+
return { token: 'mock-token' }
61+
}
62+
63+
async logout() {
64+
console.log('Logged out')
65+
}
66+
67+
async validateToken(token: string) {
68+
return token === 'mock-token'
69+
}
70+
}
71+
`,
72+
'src/utils/logger.ts': `
73+
export function log(message: string) {
74+
console.log('[LOG]', message)
75+
}
76+
77+
export function error(message: string) {
78+
console.error('[ERROR]', message)
79+
}
80+
`,
81+
'src/types/user.ts': `
82+
export interface User {
83+
id: string
84+
name: string
85+
email?: string
86+
}
87+
`,
88+
'package.json': JSON.stringify({
89+
name: 'test-project',
90+
version: '1.0.0',
91+
dependencies: {},
92+
}),
93+
'README.md':
94+
'# Test Project\n\nA simple test project for integration testing.',
95+
}
96+
97+
const client = new CodebuffClient({
98+
apiKey,
99+
cwd: '/tmp/test-project',
100+
projectFiles,
101+
})
102+
103+
const events: PrintModeEvent[] = []
104+
105+
// Run the file-lister agent to find files related to user service
106+
// The file-lister agent uses the read_subtree tool directly
107+
const run = await client.run({
108+
agent: 'file-lister',
109+
prompt: 'Find files related to user authentication and user management',
110+
handleEvent: (event) => {
111+
events.push(event)
112+
},
113+
})
114+
115+
// The output should not be an error
116+
expect(run.output.type).not.toEqual('error')
117+
118+
// Verify we got some output
119+
expect(run.output).toBeDefined()
120+
121+
// The file-lister should have found relevant files
122+
const outputStr =
123+
typeof run.output === 'string' ? run.output : JSON.stringify(run.output)
124+
125+
// Verify that the file-lister found some relevant files
126+
const relevantFiles = [
127+
'user-service',
128+
'auth-service',
129+
'user',
130+
'auth',
131+
'services',
132+
]
133+
const foundRelevantFile = relevantFiles.some((file) =>
134+
outputStr.toLowerCase().includes(file.toLowerCase()),
135+
)
136+
137+
expect(foundRelevantFile).toBe(true)
138+
},
139+
{ timeout: 60_000 },
140+
)
141+
142+
it(
143+
'should use the file tree from session state',
144+
async () => {
145+
const apiKey = process.env[API_KEY_ENV_VAR]
146+
if (!apiKey) {
147+
throw new Error('API key not found')
148+
}
149+
150+
// Create a different set of project files with a specific structure
151+
const projectFiles: Record<string, string> = {
152+
'packages/core/src/index.ts': 'export const VERSION = "1.0.0"',
153+
'packages/core/src/api/server.ts':
154+
'export function startServer() { console.log("started") }',
155+
'packages/core/src/api/routes.ts':
156+
'export const routes = { health: "/health" }',
157+
'packages/utils/src/helpers.ts':
158+
'export function formatDate(d: Date) { return d.toISOString() }',
159+
'docs/api.md': '# API Documentation\n\nAPI docs here.',
160+
'package.json': JSON.stringify({ name: 'mono-repo', version: '2.0.0' }),
161+
}
162+
163+
const client = new CodebuffClient({
164+
apiKey,
165+
cwd: '/tmp/test-project',
166+
projectFiles,
167+
})
168+
169+
const events: PrintModeEvent[] = []
170+
171+
// Run file-lister to find API-related files
172+
const run = await client.run({
173+
agent: 'file-lister',
174+
prompt: 'Find files related to the API server implementation',
175+
handleEvent: (event) => {
176+
events.push(event)
177+
},
178+
})
179+
180+
expect(run.output.type).not.toEqual('error')
181+
182+
const outputStr =
183+
typeof run.output === 'string' ? run.output : JSON.stringify(run.output)
184+
185+
// Should find API-related files
186+
const apiRelatedTerms = ['server', 'routes', 'api', 'core']
187+
const foundApiFile = apiRelatedTerms.some((term) =>
188+
outputStr.toLowerCase().includes(term.toLowerCase()),
189+
)
190+
191+
expect(foundApiFile).toBe(true)
192+
},
193+
{ timeout: 60_000 },
194+
)
195+
196+
it(
197+
'should respect directories parameter',
198+
async () => {
199+
const apiKey = process.env[API_KEY_ENV_VAR]
200+
if (!apiKey) {
201+
throw new Error('API key not found')
202+
}
203+
204+
// Create project with multiple top-level directories
205+
const projectFiles: Record<string, string> = {
206+
'frontend/src/App.tsx':
207+
'export function App() { return <div>App</div> }',
208+
'frontend/src/components/Button.tsx':
209+
'export function Button() { return <button>Click</button> }',
210+
'backend/src/server.ts':
211+
'export function start() { console.log("started") }',
212+
'backend/src/routes/users.ts':
213+
'export function getUsers() { return [] }',
214+
'shared/types/common.ts': 'export type ID = string',
215+
'package.json': JSON.stringify({ name: 'full-stack-app' }),
216+
}
217+
218+
const client = new CodebuffClient({
219+
apiKey,
220+
cwd: '/tmp/test-project',
221+
projectFiles,
222+
})
223+
224+
// Run file-lister with directories parameter to limit to frontend only
225+
const run = await client.run({
226+
agent: 'file-lister',
227+
prompt: 'Find React component files',
228+
params: {
229+
directories: ['frontend'],
230+
},
231+
handleEvent: () => {},
232+
})
233+
234+
expect(run.output.type).not.toEqual('error')
235+
236+
const outputStr =
237+
typeof run.output === 'string' ? run.output : JSON.stringify(run.output)
238+
239+
// Should find frontend files
240+
const frontendTerms = ['app', 'button', 'component', 'frontend']
241+
const foundFrontendFile = frontendTerms.some((term) =>
242+
outputStr.toLowerCase().includes(term.toLowerCase()),
243+
)
244+
245+
expect(foundFrontendFile).toBe(true)
246+
},
247+
{ timeout: 60_000 },
248+
)
249+
})
250+
251+
/**
252+
* Integration tests for the file-picker agent that spawns subagents.
253+
* The file-picker spawns file-lister as a subagent to find files.
254+
* This tests the spawn_agents tool functionality through the SDK.
255+
*/
256+
describe('File Picker Agent Integration - spawn_agents tool', () => {
257+
// Note: This test requires the local agent definitions to be used for both
258+
// file-picker AND its spawned file-lister subagent. Currently, the spawned
259+
// agent may resolve to the server version which has the old parsing bug.
260+
// Skip until we have a way to ensure spawned agents use local definitions.
261+
it.skip(
262+
'should spawn file-lister subagent and find relevant files',
263+
async () => {
264+
const apiKey = process.env[API_KEY_ENV_VAR]
265+
if (!apiKey) {
266+
throw new Error('API key not found')
267+
}
268+
269+
// Create mock project files
270+
const projectFiles: Record<string, string> = {
271+
'src/index.ts': `
272+
import { UserService } from './services/user-service'
273+
export function main() {
274+
const userService = new UserService()
275+
console.log('Application started')
276+
}
277+
`,
278+
'src/services/user-service.ts': `
279+
export class UserService {
280+
async getUser(id: string) {
281+
return { id, name: 'John Doe' }
282+
}
283+
}
284+
`,
285+
'src/services/auth-service.ts': `
286+
export class AuthService {
287+
async login(email: string, password: string) {
288+
return { token: 'mock-token' }
289+
}
290+
}
291+
`,
292+
'package.json': JSON.stringify({
293+
name: 'test-project',
294+
version: '1.0.0',
295+
}),
296+
}
297+
298+
// Use local agent definitions to test the updated handleSteps
299+
const localFilePickerDef = filePickerDefinition as unknown as any
300+
const localFileListerDef = fileListerDefinition as unknown as any
301+
302+
const client = new CodebuffClient({
303+
apiKey,
304+
cwd: '/tmp/test-project-picker',
305+
projectFiles,
306+
agentDefinitions: [localFilePickerDef, localFileListerDef],
307+
})
308+
309+
const events: PrintModeEvent[] = []
310+
311+
// Run the file-picker agent which spawns file-lister as a subagent
312+
const run = await client.run({
313+
agent: localFilePickerDef.id,
314+
prompt: 'Find files related to user authentication',
315+
handleEvent: (event) => {
316+
events.push(event)
317+
},
318+
})
319+
320+
// Check for errors in the output
321+
if (run.output.type === 'error') {
322+
console.error('File picker error:', run.output)
323+
}
324+
325+
console.log('File picker output type:', run.output.type)
326+
console.log('File picker output:', JSON.stringify(run.output, null, 2))
327+
328+
// The output should not be an error
329+
expect(run.output.type).not.toEqual('error')
330+
331+
// Verify we got some output
332+
expect(run.output).toBeDefined()
333+
334+
// The file-picker should have found relevant files via its spawned file-lister
335+
const outputStr =
336+
typeof run.output === 'string' ? run.output : JSON.stringify(run.output)
337+
338+
// Verify that the file-picker found some relevant files
339+
const relevantFiles = ['user', 'auth', 'service']
340+
const foundRelevantFile = relevantFiles.some((file) =>
341+
outputStr.toLowerCase().includes(file.toLowerCase()),
342+
)
343+
344+
expect(foundRelevantFile).toBe(true)
345+
},
346+
{ timeout: 90_000 },
347+
)
348+
})

0 commit comments

Comments
 (0)