1+ import fs from "fs" ;
2+ import path from "path" ;
3+
4+ // Function to parse agents.ts file and extract agent keys without executing
5+ function parseAgentsFile ( ) : Array < { id : string , agentKeys : string [ ] } > {
6+ const agentsFilePath = path . join ( __dirname , '../src/agents.ts' ) ;
7+ const agentsContent = fs . readFileSync ( agentsFilePath , 'utf8' ) ;
8+
9+ const agentConfigs : Array < { id : string , agentKeys : string [ ] } > = [ ] ;
10+
11+ // Split the content to process each agent configuration individually
12+ const agentBlocks = agentsContent . split ( / (? = \s * { \s * i d : \s * [ " ' ] ) / ) ;
13+
14+ for ( const block of agentBlocks ) {
15+ // Extract the ID
16+ const idMatch = block . match ( / i d : \s * [ " ' ] ( [ ^ " ' ] + ) [ " ' ] / ) ;
17+ if ( ! idMatch ) continue ;
18+
19+ const id = idMatch [ 1 ] ;
20+
21+ // Find the return object by looking for the pattern and then manually parsing balanced braces
22+ const returnMatch = block . match ( / a g e n t s : \s * a s y n c \s * \( \) \s * = > \s * { \s * r e t u r n \s * { / ) ;
23+ if ( ! returnMatch ) continue ;
24+
25+ const startIndex = returnMatch . index ! + returnMatch [ 0 ] . length ;
26+ const returnObjectContent = extractBalancedBraces ( block , startIndex ) ;
27+
28+
29+ // Extract keys from the return object - only capture keys that are followed by a colon and then 'new'
30+ // This ensures we only get the top-level keys like "agentic_chat: new ..." not nested keys like "url: ..."
31+ const keyRegex = / ^ \s * ( \w + ) : \s * n e w \s + \w + / gm;
32+ const keys : string [ ] = [ ] ;
33+ let keyMatch ;
34+ while ( ( keyMatch = keyRegex . exec ( returnObjectContent ) ) !== null ) {
35+ keys . push ( keyMatch [ 1 ] ) ;
36+ }
37+
38+ agentConfigs . push ( { id, agentKeys : keys } ) ;
39+ }
40+
41+ return agentConfigs ;
42+ }
43+
44+ // Helper function to extract content between balanced braces
45+ function extractBalancedBraces ( text : string , startIndex : number ) : string {
46+ let braceCount = 0 ;
47+ let i = startIndex ;
48+
49+ while ( i < text . length ) {
50+ if ( text [ i ] === '{' ) {
51+ braceCount ++ ;
52+ } else if ( text [ i ] === '}' ) {
53+ if ( braceCount === 0 ) {
54+ // Found the closing brace for the return object
55+ return text . substring ( startIndex , i ) ;
56+ }
57+ braceCount -- ;
58+ }
59+ i ++ ;
60+ }
61+
62+ return '' ;
63+ }
64+
65+ const agentConfigs = parseAgentsFile ( ) ;
66+
67+ const featureFiles = [ "page.tsx" , "style.css" , "README.mdx" ]
68+
69+ async function getFile ( _filePath : string | undefined , _fileName ?: string ) {
70+ if ( ! _filePath ) {
71+ console . warn ( `File path is undefined, skipping.` ) ;
72+ return { }
73+ }
74+
75+ const fileName = _fileName ?? _filePath . split ( '/' ) . pop ( ) ?? ''
76+ const filePath = _fileName ? path . join ( _filePath , fileName ) : _filePath ;
77+
78+ // Check if it's a remote URL
79+ const isRemoteUrl = _filePath . startsWith ( 'http://' ) || _filePath . startsWith ( 'https://' ) ;
80+
81+ let content : string ;
82+
83+ try {
84+ if ( isRemoteUrl ) {
85+ // Convert GitHub URLs to raw URLs for direct file access
86+ let fetchUrl = _filePath ;
87+ if ( _filePath . includes ( 'github.com' ) && _filePath . includes ( '/blob/' ) ) {
88+ fetchUrl = _filePath . replace ( 'github.com' , 'raw.githubusercontent.com' ) . replace ( '/blob/' , '/' ) ;
89+ }
90+
91+ // Fetch remote file content
92+ console . log ( `Fetching remote file: ${ fetchUrl } ` ) ;
93+ const response = await fetch ( fetchUrl ) ;
94+ if ( ! response . ok ) {
95+ console . warn ( `Failed to fetch remote file: ${ fetchUrl } , status: ${ response . status } ` ) ;
96+ return { }
97+ }
98+ content = await response . text ( ) ;
99+ } else {
100+ // Handle local file
101+ if ( ! fs . existsSync ( filePath ) ) {
102+ console . warn ( `File not found: ${ filePath } , skipping.` ) ;
103+ return { }
104+ }
105+ content = fs . readFileSync ( filePath , "utf8" ) ;
106+ }
107+
108+ const extension = fileName . split ( "." ) . pop ( ) ;
109+ let language = extension ;
110+ if ( extension === "py" ) language = "python" ;
111+ else if ( extension === "css" ) language = "css" ;
112+ else if ( extension === "md" || extension === "mdx" ) language = "markdown" ;
113+ else if ( extension === "tsx" ) language = "typescript" ;
114+ else if ( extension === "js" ) language = "javascript" ;
115+ else if ( extension === "json" ) language = "json" ;
116+ else if ( extension === "yaml" || extension === "yml" ) language = "yaml" ;
117+ else if ( extension === "toml" ) language = "toml" ;
118+
119+ return {
120+ name : fileName ,
121+ content,
122+ language,
123+ type : 'file'
124+ }
125+ } catch ( error ) {
126+ console . error ( `Error reading file ${ filePath } :` , error ) ;
127+ return { }
128+ }
129+ }
130+
131+ async function getFeatureFrontendFiles ( featureId : string ) {
132+ const featurePath = path . join ( __dirname , `../src/app/[integrationId]/feature/${ featureId as string } ` ) ;
133+ const retrievedFiles = [ ]
134+
135+ for ( const fileName of featureFiles ) {
136+ retrievedFiles . push ( await getFile ( featurePath , fileName ) )
137+ }
138+
139+ return retrievedFiles ;
140+ }
141+
142+ const integrationsFolderPath = '../../../integrations'
143+ const agentFilesMapper : Record < string , ( agentKeys : string [ ] ) => Record < string , string > > = {
144+ 'middleware-starter' : ( ) => ( {
145+ agentic_chat : path . join ( __dirname , integrationsFolderPath , `/middleware-starter/src/index.ts` )
146+ } ) ,
147+ 'pydantic-ai' : ( agentKeys : string [ ] ) => {
148+ return agentKeys . reduce ( ( acc , agentId ) => ( {
149+ ...acc ,
150+ [ agentId ] : `https://github.com/pydantic/pydantic-ai/blob/main/examples/pydantic_ai_examples/ag_ui/api/${ agentId } .py`
151+ } ) , { } )
152+ } ,
153+ 'server-starter' : ( ) => ( {
154+ agentic_chat : path . join ( __dirname , integrationsFolderPath , `/server-starter/server/python/example_server/__init__.py` )
155+ } ) ,
156+ 'server-starter-all-features' : ( agentKeys : string [ ] ) => {
157+ return agentKeys . reduce ( ( acc , agentId ) => ( {
158+ ...acc ,
159+ [ agentId ] : path . join ( __dirname , integrationsFolderPath , `/server-starter/server/python/example_server/${ agentId } .py` )
160+ } ) , { } )
161+ } ,
162+ 'mastra' : ( ) => ( {
163+ agentic_chat : path . join ( __dirname , integrationsFolderPath , `/mastra/example/src/mastra/agents/weather-agent.ts` )
164+ } ) ,
165+ 'mastra-agent-lock' : ( ) => ( {
166+ agentic_chat : path . join ( __dirname , integrationsFolderPath , `/mastra/example/src/mastra/agents/weather-agent.ts` )
167+ } ) ,
168+ 'vercel-ai-sdk' : ( ) => ( {
169+ agentic_chat : path . join ( __dirname , integrationsFolderPath , `/vercel-ai-sdk/src/index.ts` )
170+ } ) ,
171+ 'langgraph' : ( agentKeys : string [ ] ) => {
172+ return agentKeys . reduce ( ( acc , agentId ) => ( {
173+ ...acc ,
174+ [ agentId ] : path . join ( __dirname , integrationsFolderPath , `/langgraph/examples/agents/${ agentId } /agent.py` )
175+ } ) , { } )
176+ } ,
177+ 'langgraph-fastapi' : ( agentKeys : string [ ] ) => {
178+ return agentKeys . reduce ( ( acc , agentId ) => ( {
179+ ...acc ,
180+ [ agentId ] : path . join ( __dirname , integrationsFolderPath , `/langgraph/python/ag_ui_langgraph/examples/agents/${ agentId } .py` )
181+ } ) , { } )
182+ } ,
183+ 'agno' : ( ) => ( { } ) ,
184+ 'llama-index' : ( agentKeys : string [ ] ) => {
185+ return agentKeys . reduce ( ( acc , agentId ) => ( {
186+ ...acc ,
187+ [ agentId ] : path . join ( __dirname , integrationsFolderPath , `/llamaindex/server-py/server/routers/${ agentId } .py` )
188+ } ) , { } )
189+ } ,
190+ 'crewai' : ( agentKeys : string [ ] ) => {
191+ return agentKeys . reduce ( ( acc , agentId ) => ( {
192+ ...acc ,
193+ [ agentId ] : path . join ( __dirname , integrationsFolderPath , `/crewai/python/ag_ui_crewai/examples/${ agentId } .py` )
194+ } ) , { } )
195+ }
196+ }
197+
198+ async function runGenerateContent ( ) {
199+ const result = { }
200+ for ( const agentConfig of agentConfigs ) {
201+ // Use the parsed agent keys instead of executing the agents function
202+ const agentsPerFeatures = agentConfig . agentKeys
203+
204+ const agentFilePaths = agentFilesMapper [ agentConfig . id ] ( agentConfig . agentKeys )
205+ // Per feature, assign all the frontend files like page.tsx as well as all agent files
206+ for ( const featureId of agentsPerFeatures ) {
207+ // @ts -expect-error -- redundant error about indexing of a new object.
208+ result [ `${ agentConfig . id } ::${ featureId } ` ] = [
209+ // Get all frontend files for the feature
210+ ...( await getFeatureFrontendFiles ( featureId ) ) ,
211+ // Get the agent (python/TS) file
212+ await getFile ( agentFilePaths [ featureId ] )
213+ ]
214+ }
215+ }
216+
217+ return result
218+ }
219+
220+ ( async ( ) => {
221+ const result = await runGenerateContent ( ) ;
222+ fs . writeFileSync (
223+ path . join ( __dirname , "../src/files.json" ) ,
224+ JSON . stringify ( result , null , 2 )
225+ ) ;
226+
227+ console . log ( "Successfully generated src/files.json" ) ;
228+ } ) ( ) ;
0 commit comments