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