@@ -20,6 +20,13 @@ const KV_KEY = "docs-v0";
2020const DOCS_REPO_API =
2121 "https://api.github.com/repos/cloudflare/agents/git/trees/main?recursive=1" ;
2222const TTL_SECONDS = 24 * 60 * 60 ; // 1 day
23+ const CHUNK_SIZE = 2000 ;
24+ const MIN_CHARS_PER_CHUNK = 500 ;
25+
26+ const chunker = await RecursiveChunker . create ( {
27+ chunkSize : CHUNK_SIZE ,
28+ minCharactersPerChunk : MIN_CHARS_PER_CHUNK
29+ } ) ;
2330
2431const fetchWithRetry = ( url : string , useAuth = true ) =>
2532 Effect . tryPromise ( {
@@ -57,8 +64,8 @@ const fetchWithRetry = (url: string, useAuth = true) =>
5764 )
5865 ) ;
5966
60- const fetchDocsFromGitHub = async ( ) : Promise < Document [ ] > => {
61- const treeEffect = fetchWithRetry ( DOCS_REPO_API ) . pipe (
67+ const fetchDocsFromGitHub = Effect . gen ( function * ( ) {
68+ const treeData = yield * fetchWithRetry ( DOCS_REPO_API ) . pipe (
6269 Effect . flatMap ( ( response ) =>
6370 Effect . tryPromise ( {
6471 try : async ( ) => {
@@ -73,89 +80,90 @@ const fetchDocsFromGitHub = async (): Promise<Document[]> => {
7380 )
7481 ) ;
7582
76- const treeData = await Effect . runPromise ( treeEffect ) ;
77-
7883 const docFiles = treeData . tree . filter (
7984 ( item : GitHubTreeItem ) =>
8085 item . path . startsWith ( "docs/" ) && item . path . endsWith ( ".md" )
8186 ) ;
8287
8388 const docs : Document [ ] = [ ] ;
84- const chunker = await RecursiveChunker . create ( {
85- chunkSize : 800
86- } ) ;
8789
8890 for ( const file of docFiles ) {
8991 const contentUrl = `https://raw.githubusercontent.com/cloudflare/agents/main/${ file . path } ` ;
9092
91- const contentEffect = fetchWithRetry ( contentUrl ) . pipe (
93+ const contentResult = yield * fetchWithRetry ( contentUrl ) . pipe (
9294 Effect . flatMap ( ( response ) =>
9395 Effect . tryPromise ( {
9496 try : ( ) => response . text ( ) ,
9597 catch : ( error ) => error as Error
9698 } )
97- )
99+ ) ,
100+ Effect . flatMap ( ( content ) => Effect . promise ( ( ) => chunker . chunk ( content ) ) ) ,
101+ Effect . catchAll ( ( error ) => {
102+ console . error ( `Failed to fetch/chunk ${ file . path } :` , error ) ;
103+ return Effect . succeed ( [ ] ) ;
104+ } )
98105 ) ;
99106
100- try {
101- const content = await Effect . runPromise ( contentEffect ) ;
102- const chunks = await chunker . chunk ( content ) ;
103-
104- for ( const chunk of chunks ) {
105- docs . push ( {
106- fileName : file . path ,
107- content : chunk . text ,
108- url : contentUrl
109- } ) ;
110- }
111- } catch ( error ) {
112- console . error ( `Failed to fetch/chunk ${ file . path } :` , error ) ;
107+ for ( const chunk of contentResult ) {
108+ docs . push ( {
109+ fileName : file . path ,
110+ content : chunk . text ,
111+ url : contentUrl
112+ } ) ;
113113 }
114114 }
115115
116116 return docs ;
117- } ;
118-
119- const getCachedDocs = async ( ) : Promise < Document [ ] | null > => {
120- const cached = await env . DOCS_KV . get ( KV_KEY , "json" ) ;
121-
122- if ( ! cached ) {
123- return null ;
124- }
117+ } ) ;
125118
126- return cached as Document [ ] ;
127- } ;
119+ const getCachedDocs = Effect . tryPromise ( {
120+ try : ( ) => env . DOCS_KV . get ( KV_KEY , "json" ) as Promise < Document [ ] | null > ,
121+ catch : ( error ) => error as Error
122+ } ) ;
128123
129- const cacheDocs = async ( docs : Document [ ] ) : Promise < void > => {
130- await env . DOCS_KV . put ( KV_KEY , JSON . stringify ( docs ) , {
131- expirationTtl : TTL_SECONDS
124+ const cacheDocs = ( docs : Document [ ] ) =>
125+ Effect . tryPromise ( {
126+ try : ( ) =>
127+ env . DOCS_KV . put ( KV_KEY , JSON . stringify ( docs ) , {
128+ expirationTtl : TTL_SECONDS
129+ } ) ,
130+ catch : ( error ) => error as Error
132131 } ) ;
133- } ;
134132
135- export const fetchAndBuildIndex = async ( ) => {
136- let docs = await getCachedDocs ( ) ;
133+ export const fetchAndBuildIndex = Effect . gen ( function * ( ) {
134+ const cached = yield * getCachedDocs ;
135+
136+ let docs : Document [ ] ;
137137
138- if ( ! docs ) {
139- // If not cached, fetch from GitHub, chunk, and cache to KV
140- docs = await fetchDocsFromGitHub ( ) ;
141- await cacheDocs ( docs ) ;
138+ if ( ! cached ) {
139+ docs = yield * fetchDocsFromGitHub ;
140+ yield * cacheDocs ( docs ) ;
141+ } else {
142+ docs = cached ;
142143 }
143144
144- // Build the search index from docs
145- const docsDb = create ( {
146- schema : {
147- fileName : "string" ,
148- content : "string" ,
149- url : "string"
150- } as const
151- } ) ;
145+ const docsDb = yield * Effect . sync ( ( ) =>
146+ create ( {
147+ schema : {
148+ fileName : "string" ,
149+ content : "string" ,
150+ url : "string"
151+ } as const ,
152+ components : {
153+ tokenizer : {
154+ stemming : true ,
155+ language : "english"
156+ }
157+ }
158+ } )
159+ ) ;
152160
153161 for ( const doc of docs ) {
154- await insert ( docsDb , doc ) ;
162+ yield * Effect . sync ( ( ) => insert ( docsDb , doc ) ) ;
155163 }
156164
157165 return docsDb ;
158- } ;
166+ } ) ;
159167
160168export const formatResults = (
161169 results : Awaited < ReturnType < typeof search > > ,
@@ -167,7 +175,13 @@ export const formatResults = (
167175
168176 let output = `**Search Results**\n\n` ;
169177 output += `Found ${ hitCount } result${ hitCount !== 1 ? "s" : "" } for "${ query } " (${ elapsed } )\n\n` ;
170- output += `Showing top ${ k } result${ k !== 1 ? "s" : "" } :\n\n` ;
178+
179+ if ( hitCount === 0 ) {
180+ output += `No results found. Try using different keywords or modify the spelling.` ;
181+ return output ;
182+ }
183+
184+ output += `Showing top ${ Math . min ( k , hitCount ) } result${ Math . min ( k , hitCount ) !== 1 ? "s" : "" } :\n\n` ;
171185 output += `---\n\n` ;
172186
173187 for ( const hit of results . hits ) {
0 commit comments