@@ -10,8 +10,8 @@ import remarkStringify from "remark-stringify";
1010import { type DocsFile } from "@/lib/ai" ;
1111
1212// Note: these map to the folder names in the `docs` directory
13- // We **don't** want to include the `reference/endpoints` folder
1413export const DOCS_CATEGORIES = {
14+ reference : "API Reference Documentation" ,
1515 competitions : "Competitions guides and usage" ,
1616 quickstart : "Quickstart guides for building agents with Recall" ,
1717 root : "Introduction to the Recall Network" ,
@@ -31,27 +31,52 @@ export function getCategoryDisplayName(filePath: string): string {
3131export async function getDocsContent ( docsDir : string ) : Promise < DocsFile [ ] > {
3232 const files = await fg ( [ "**/*.mdx" ] , {
3333 cwd : docsDir ,
34- ignore : [ "reference/endpoints/**" , " **/_*.mdx"] ,
34+ ignore : [ "**/_*.mdx" ] ,
3535 absolute : true ,
3636 dot : false ,
3737 } ) ;
3838
39+ const specPath = path . join ( docsDir , ".." , "specs" , "competitions.json" ) ;
40+
3941 const scanned = await Promise . all (
4042 files . map ( async ( file ) => {
41- const fileContent = await fs . readFile ( file ) ;
42- const { content, data } = matter ( fileContent . toString ( ) ) ;
4343 const relativePath = path . relative ( docsDir , file ) ;
4444 const category = getCategoryDisplayName ( relativePath ) ;
45- const processed = await processContent ( content ) ;
45+
46+ // Check if this is an API reference page (not the index page)
47+ const isApiReferencePage = relativePath . includes ( "reference/endpoints/" ) &&
48+ ! relativePath . endsWith ( "endpoints/index.mdx" ) ;
49+
50+ let title : string ;
51+ let description : string ;
52+ let keywords : string ;
53+ let processed : string ;
54+
55+ if ( isApiReferencePage ) {
56+ // For API pages, use getApiDocContent to generate markdown from OpenAPI spec
57+ const apiContent = await getApiDocContent ( file , specPath ) ;
58+ title = apiContent . title ;
59+ description = apiContent . description ;
60+ keywords = "" ;
61+ processed = apiContent . content ;
62+ } else {
63+ // For regular pages, use the existing method
64+ const fileContent = await fs . readFile ( file ) ;
65+ const { content, data } = matter ( fileContent . toString ( ) ) ;
66+ title = data . title || relativePath ;
67+ description = data . description || "" ;
68+ keywords = data . keywords || "" ;
69+ processed = await processContent ( content ) ;
70+ }
4671
4772 // Make sure `index` is removed from the filename and strip the suffix—creating the slug
4873 const filename = relativePath . replace ( / \. m d x $ / , "" ) . replace ( / \/ i n d e x $ / , "" ) ;
4974 return {
5075 file : filename ,
5176 category,
52- title : data . title || filename ,
53- description : data . description || "" ,
54- keywords : data . keywords || "" ,
77+ title,
78+ description,
79+ keywords,
5580 content : processed ,
5681 } ;
5782 } )
@@ -87,3 +112,108 @@ async function processContent(content: string): Promise<string> {
87112
88113 return String ( file ) ;
89114}
115+
116+ interface Operation {
117+ path : string ;
118+ method : string ;
119+ }
120+
121+ export async function getApiDocContent (
122+ file : string ,
123+ specPath : string
124+ ) : Promise < {
125+ title : string ;
126+ description : string ;
127+ content : string ;
128+ } > {
129+ const fileExists = await fs
130+ . access ( file )
131+ . then ( ( ) => true )
132+ . catch ( ( ) => false ) ;
133+ if ( ! fileExists ) {
134+ throw new Error ( "File not found" ) ;
135+ }
136+
137+ const fileContent = await fs . readFile ( file ) ;
138+ const { data, content } = matter ( fileContent . toString ( ) ) ;
139+
140+ // Extract operations from the JSX content
141+ const operationsMatch = content . match ( / o p e r a t i o n s = \{ ( \[ [ \s \S ] * ?\] ) \} / ) ;
142+ if ( ! operationsMatch || ! operationsMatch [ 1 ] ) {
143+ // Fall back to regular content if no operations found
144+ return getRawDocContent ( file ) ;
145+ }
146+
147+ let operations : Operation [ ] ;
148+ try {
149+ // Parse the operations array (it's in JSON-like format)
150+ const parsed = eval ( operationsMatch [ 1 ] ) ;
151+ if ( ! Array . isArray ( parsed ) ) {
152+ return getRawDocContent ( file ) ;
153+ }
154+ operations = parsed as Operation [ ] ;
155+ } catch {
156+ // Fall back if parsing fails
157+ return getRawDocContent ( file ) ;
158+ }
159+
160+ // Read the OpenAPI spec
161+ const specFile = await fs . readFile ( specPath , "utf8" ) ;
162+ const spec = JSON . parse ( specFile ) ;
163+
164+ // Generate markdown content from the spec
165+ let markdown = "" ;
166+
167+ operations . forEach ( ( { path : opPath , method } ) => {
168+ const op = spec . paths ?. [ opPath ] ?. [ method ] ;
169+ if ( ! op ) return ;
170+
171+ markdown += `## ${ method . toUpperCase ( ) } ${ opPath } \n\n` ;
172+
173+ if ( op . summary ) {
174+ markdown += `**${ op . summary } **\n\n` ;
175+ }
176+
177+ if ( op . description ) {
178+ markdown += `${ op . description } \n\n` ;
179+ }
180+
181+ // Parameters
182+ if ( op . parameters ?. length ) {
183+ markdown += "**Parameters:**\n\n" ;
184+ op . parameters . forEach ( ( param : { name : string ; in : string ; description ?: string ; required ?: boolean ; schema ?: { type : string } } ) => {
185+ const required = param . required ? " (required)" : "" ;
186+ const type = param . schema ?. type ? `: ${ param . schema . type } ` : "" ;
187+ markdown += `- \`${ param . name } \` (${ param . in } ${ type } )${ required } : ${ param . description || "" } \n` ;
188+ } ) ;
189+ markdown += "\n" ;
190+ }
191+
192+ // Request body
193+ if ( op . requestBody ?. content ?. [ "application/json" ] ?. schema ) {
194+ markdown += "**Request Body:**\n\n" ;
195+ markdown += `\`\`\`json\n${ JSON . stringify ( op . requestBody . content [ "application/json" ] . schema , null , 2 ) } \n\`\`\`\n\n` ;
196+ }
197+
198+ // Response
199+ const responses = op . responses || { } ;
200+ const successResponse = responses [ "200" ] || responses [ "201" ] || responses [ "204" ] ;
201+ if ( successResponse ) {
202+ markdown += "**Success Response:**\n\n" ;
203+ if ( successResponse . description ) {
204+ markdown += `${ successResponse . description } \n\n` ;
205+ }
206+ if ( successResponse . content ?. [ "application/json" ] ?. schema ) {
207+ markdown += `\`\`\`json\n${ JSON . stringify ( successResponse . content [ "application/json" ] . schema , null , 2 ) } \n\`\`\`\n\n` ;
208+ }
209+ }
210+
211+ markdown += "---\n\n" ;
212+ } ) ;
213+
214+ return {
215+ title : data . title || file ,
216+ description : data . description || "" ,
217+ content : markdown . trim ( ) ,
218+ } ;
219+ }
0 commit comments