Skip to content

Commit ec023aa

Browse files
committed
sdk: include parsed fileTree, token scores, token callers
1 parent b0fd88a commit ec023aa

File tree

6 files changed

+26620
-5284
lines changed

6 files changed

+26620
-5284
lines changed

bun.lock

Lines changed: 26485 additions & 5266 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/code-map/src/languages.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import * as path from 'path'
22

3+
// Import some types for wasm & .scm files
4+
import './types.d.ts'
5+
36
/* ------------------------------------------------------------------ */
47
/* 1 . WASM files
58
/* ------------------------------------------------------------------ */
@@ -129,8 +132,13 @@ export async function getLanguageConfig(
129132
const parser = new Parser()
130133
// NOTE (James): For some reason, Bun gives the wrong path to the imported WASM file,
131134
// so we need to delete one level of ../.
132-
const actualPath = cfg.wasmFile.replace('../', '')
133-
const lang = await Language.load(actualPath)
135+
let lang
136+
try {
137+
const actualPath = cfg.wasmFile.replace('../', '')
138+
lang = await Language.load(actualPath)
139+
} catch (err) {
140+
lang = await Language.load(cfg.wasmFile)
141+
}
134142
parser.setLanguage(lang)
135143

136144
cfg.language = lang

packages/code-map/src/parse.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface FileTokenData {
2727
export async function getFileTokenScores(
2828
projectRoot: string,
2929
filePaths: string[],
30+
readFile?: (filePath: string) => string | null,
3031
): Promise<FileTokenData> {
3132
const startTime = Date.now()
3233
const tokenScores: { [filePath: string]: { [token: string]: number } } = {}
@@ -38,9 +39,10 @@ export async function getFileTokenScores(
3839
const fullPath = path.join(projectRoot, filePath)
3940
const languageConfig = await getLanguageConfig(fullPath)
4041
if (languageConfig) {
41-
const { identifiers, calls, numLines } = await parseTokens(
42-
fullPath,
42+
const { identifiers, calls, numLines } = parseTokens(
43+
filePath,
4344
languageConfig,
45+
readFile,
4446
)
4547

4648
const tokenScoresForFile: { [token: string]: number } = {}
@@ -146,14 +148,22 @@ export async function getFileTokenScores(
146148
return { tokenScores, tokenCallers }
147149
}
148150

149-
export async function parseTokens(
151+
export function parseTokens(
150152
filePath: string,
151153
languageConfig: LanguageConfig,
154+
readFile?: (filePath: string) => string | null,
152155
) {
153156
const { parser, query } = languageConfig
154157

155158
try {
156-
const sourceCode = fs.readFileSync(filePath, 'utf8')
159+
const sourceCode = readFile ? readFile(filePath) : fs.readFileSync(filePath, 'utf8')
160+
if (sourceCode === null) {
161+
return {
162+
numLines: 0,
163+
identifiers: [] as string[],
164+
calls: [] as string[],
165+
}
166+
}
157167
const numLines = sourceCode.match(/\n/g)?.length ?? 0 + 1
158168
if (!parser || !query) {
159169
throw new Error('Parser or query not found')

sdk/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@
5353
},
5454
"dependencies": {
5555
"ai": "^5.0.0",
56-
"zod": "^4.0.0"
56+
"zod": "^4.0.0",
57+
"@vscode/tree-sitter-wasm": "0.1.4",
58+
"web-tree-sitter": "0.25.6"
5759
},
5860
"devDependencies": {
5961
"@types/node": "22",

sdk/src/client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,13 @@ export class CodebuffClient {
144144
const promptId = Math.random().toString(36).substring(2, 15)
145145
const sessionState =
146146
previousRun?.sessionState ??
147-
initialSessionState(this.cwd, {
147+
(await initialSessionState(this.cwd, {
148148
knowledgeFiles,
149149
agentDefinitions,
150150
customToolDefinitions,
151151
projectFiles,
152152
maxAgentSteps,
153-
})
153+
}))
154154
sessionState.mainAgentState.stepsRemaining = maxAgentSteps
155155
const toolResults = previousRun?.toolResults ?? []
156156
if (handleEvent) {

sdk/src/run-state.ts

Lines changed: 106 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
import * as os from 'os'
22

33
import { type CustomToolDefinition } from './custom-tool'
4+
import { getFileTokenScores } from '../../packages/code-map/src/parse'
45
import { getInitialSessionState } from '../../common/src/types/session-state'
56

67
import type { ServerAction } from '../../common/src/actions'
78
import type { AgentDefinition } from '../../common/src/templates/initial-agents-dir/types/agent-definition'
89
import type { CodebuffMessage } from '../../common/src/types/message'
910
import type { SessionState } from '../../common/src/types/session-state'
10-
import type { CustomToolDefinitions } from '../../common/src/util/file'
11+
import type {
12+
CustomToolDefinitions,
13+
FileTreeNode,
14+
} from '../../common/src/util/file'
1115

1216
export type RunState = {
1317
sessionState: SessionState
1418
toolResults: ServerAction<'prompt-response'>['toolResults']
1519
}
1620

17-
export function initialSessionState(
21+
export async function initialSessionState(
1822
cwd: string,
1923
options: {
20-
// TODO: Parse projectFiles into fileTree, fileTokenScores, tokenCallers
2124
projectFiles?: Record<string, string>
2225
knowledgeFiles?: Record<string, string>
2326
agentDefinitions?: AgentDefinition[]
@@ -76,12 +79,35 @@ export function initialSessionState(
7679
]),
7780
)
7881

82+
// Generate file tree and token scores from projectFiles
83+
const filePaths = Object.keys(projectFiles).sort()
84+
85+
// Build hierarchical file tree with directories
86+
const fileTree = buildFileTree(filePaths)
87+
let fileTokenScores = {}
88+
let tokenCallers = {}
89+
90+
if (filePaths.length > 0) {
91+
try {
92+
const tokenData = await getFileTokenScores(
93+
cwd,
94+
filePaths,
95+
(filePath: string) => projectFiles[filePath] || null,
96+
)
97+
fileTokenScores = tokenData.tokenScores
98+
tokenCallers = tokenData.tokenCallers
99+
} catch (error) {
100+
// If token scoring fails, continue with empty scores
101+
console.warn('Failed to generate parsed symbol scores:', error)
102+
}
103+
}
104+
79105
const initialState = getInitialSessionState({
80106
projectRoot: cwd,
81107
cwd,
82-
fileTree: [],
83-
fileTokenScores: {},
84-
tokenCallers: {},
108+
fileTree,
109+
fileTokenScores,
110+
tokenCallers,
85111
knowledgeFiles,
86112
userKnowledgeFiles: {},
87113
agentTemplates: processedAgentTemplates,
@@ -111,7 +137,7 @@ export function initialSessionState(
111137
return initialState
112138
}
113139

114-
export function generateInitialRunState({
140+
export async function generateInitialRunState({
115141
cwd,
116142
projectFiles,
117143
knowledgeFiles,
@@ -125,9 +151,9 @@ export function generateInitialRunState({
125151
agentDefinitions?: AgentDefinition[]
126152
customToolDefinitions?: CustomToolDefinition[]
127153
maxAgentSteps?: number
128-
}): RunState {
154+
}): Promise<RunState> {
129155
return {
130-
sessionState: initialSessionState(cwd, {
156+
sessionState: await initialSessionState(cwd, {
131157
projectFiles,
132158
knowledgeFiles,
133159
agentDefinitions,
@@ -167,3 +193,74 @@ export function withMessageHistory({
167193

168194
return newRunState
169195
}
196+
197+
/**
198+
* Builds a hierarchical file tree from a flat list of file paths
199+
*/
200+
function buildFileTree(filePaths: string[]): FileTreeNode[] {
201+
const tree: Record<string, FileTreeNode> = {}
202+
203+
// Build the tree structure
204+
for (const filePath of filePaths) {
205+
const parts = filePath.split('/')
206+
207+
for (let i = 0; i < parts.length; i++) {
208+
const currentPath = parts.slice(0, i + 1).join('/')
209+
const isFile = i === parts.length - 1
210+
211+
if (!tree[currentPath]) {
212+
tree[currentPath] = {
213+
name: parts[i],
214+
type: isFile ? 'file' : 'directory',
215+
filePath: currentPath,
216+
children: isFile ? undefined : [],
217+
}
218+
}
219+
}
220+
}
221+
222+
// Organize into hierarchical structure
223+
const rootNodes: FileTreeNode[] = []
224+
const processed = new Set<string>()
225+
226+
for (const [path, node] of Object.entries(tree)) {
227+
if (processed.has(path)) continue
228+
229+
const parentPath = path.substring(0, path.lastIndexOf('/'))
230+
if (parentPath && tree[parentPath]) {
231+
// This node has a parent, add it to parent's children
232+
const parent = tree[parentPath]
233+
if (
234+
parent.children &&
235+
!parent.children.some((child) => child.filePath === path)
236+
) {
237+
parent.children.push(node)
238+
}
239+
} else {
240+
// This is a root node
241+
rootNodes.push(node)
242+
}
243+
processed.add(path)
244+
}
245+
246+
// Sort function for nodes
247+
function sortNodes(nodes: FileTreeNode[]): void {
248+
nodes.sort((a, b) => {
249+
// Directories first, then files
250+
if (a.type !== b.type) {
251+
return a.type === 'directory' ? -1 : 1
252+
}
253+
return a.name.localeCompare(b.name)
254+
})
255+
256+
// Recursively sort children
257+
for (const node of nodes) {
258+
if (node.children) {
259+
sortNodes(node.children)
260+
}
261+
}
262+
}
263+
264+
sortNodes(rootNodes)
265+
return rootNodes
266+
}

0 commit comments

Comments
 (0)