char-slop/berry
multipurpose self-modifiable agentic harness
git clone https://git.t4t.associates/char-slop/berry
c5dd844
main
1import type { Renderers , ToolRenderer } from "@berry/api/web.ts" ; 2 3/** An extension's frontend as the server built it: a module to import, and its styles. */ 4export type WebEntry = { module :string ; styles :string [] }; 5 6const loaded = new Map < 7string , 8{ module :string ; renderers :Renderers | null ; links :HTMLLinkElement [] } 9>(); 10/** Extensions by the names they went by before, which old history still calls them. */ 11const formerly = new Map < string , string >(); 12 13/** 14* Bring the extensions' frontends to what the server built: import the ones whose module is new, and drop the ones 15* gone. Says whether anything changed, for views to draw again with the new renderers. 16*/ 17export async function syncRenderers ( 18extensions :readonly { name :string ; web :WebEntry | null ; formerly :readonly string [] }[], 19) { 20let changed = false ; 21formerly . clear (); 22for ( const { name, formerly :names } of extensions ) { 23for ( const former of names ) formerly . set ( former , name ); 24} 25const named = new Map ( extensions . map (( extension ) => [ extension . name , extension . web ])); 26for ( const [ name , entry ] of loaded ) { 27if ( named . has ( name )) continue ; 28for ( const link of entry . links ) link . remove (); 29loaded . delete ( name ); 30changed = true ; 31} 32await Promise . all ([ ...named ]. map ( async ([ name , web ]) => { 33const current = loaded . get ( name ); 34const stylesheets = ( hrefs :readonly string []) => 35hrefs . map (( href ) => { 36const link = document . createElement ( "link" ); 37link . rel = "stylesheet" ; 38link . href = href ; 39document . head . append ( link ); 40return link ; 41}); 42if (( current ?. module ?? null ) === ( web ?. module ?? null )) { 43// a module's url follows its scripts, so a change only to its styles keeps it 44const hrefs = current ?. links . map (( link ) => link . getAttribute ( "href" )); 45if ( current && web && hrefs ! . join ( "\n" ) !== web . styles . join ( "\n" )) { 46for ( const link of current . links ) link . remove (); 47current . links = stylesheets ( web . styles ); 48} 49return ; 50} 51changed = true ; 52for ( const link of current ?. links ?? []) link . remove (); 53if ( web === null ) return void loaded . delete ( name ); 54const links = stylesheets ( web . styles ); 55let renderers :Renderers | null = null ; 56try { 57renderers = ( await import ( /* @vite-ignore */ web . module )). default ; 58} catch ( error ) { 59console . error ( `[berry] ${ name } 's frontend:` , error ); 60} 61loaded . set ( name , { module :web . module , renderers, links}); 62})); 63return changed ; 64} 65 66export function extensionRenderers ( extension :string ) :Renderers | undefined { 67return ( loaded . get ( extension ) ?? loaded . get ( formerly . get ( extension ) ?? "" ))?. renderers ?? 68undefined ; 69} 70 71/** A script's call names its extension; the model's direct calls do not, so any extension's renderer of that name. */ 72export function toolRenderer ( name :string , extension ?:string ) :ToolRenderer | undefined { 73if ( extension !== undefined ) return extensionRenderers ( extension )?. tools ?.[ name ]; 74for ( const { renderers} of loaded . values ()) { 75const found = renderers ?. tools ?.[ name ]; 76if ( found ) return found ; 77} 78return undefined ; 79}