char-slop/berry

multipurpose self-modifiable agentic harness

git clone https://git.t4t.associates/char-slop/berry

Charlotte Somdiff: add (codemirror) inline editing of diffsc5dd844

main
3.1 KiB79 linesraw
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<
7  string,
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(
18  extensions: readonly { name: string; web: WebEntry | null; formerly: readonly string[] }[],
19) {
20  let changed = false;
21  formerly.clear();
22  for (const { name, formerly: names } of extensions) {
23    for (const former of names) formerly.set(former, name);
24  }
25  const named = new Map(extensions.map((extension) => [extension.name, extension.web]));
26  for (const [name, entry] of loaded) {
27    if (named.has(name)) continue;
28    for (const link of entry.links) link.remove();
29    loaded.delete(name);
30    changed = true;
31  }
32  await Promise.all([...named].map(async ([name, web]) => {
33    const current = loaded.get(name);
34    const stylesheets = (hrefs: readonly string[]) =>
35      hrefs.map((href) => {
36        const link = document.createElement("link");
37        link.rel = "stylesheet";
38        link.href = href;
39        document.head.append(link);
40        return link;
41      });
42    if ((current?.module ?? null) === (web?.module ?? null)) {
43      // a module's url follows its scripts, so a change only to its styles keeps it
44      const hrefs = current?.links.map((link) => link.getAttribute("href"));
45      if (current && web && hrefs!.join("\n") !== web.styles.join("\n")) {
46        for (const link of current.links) link.remove();
47        current.links = stylesheets(web.styles);
48      }
49      return;
50    }
51    changed = true;
52    for (const link of current?.links ?? []) link.remove();
53    if (web === null) return void loaded.delete(name);
54    const links = stylesheets(web.styles);
55    let renderers: Renderers | null = null;
56    try {
57      renderers = (await import(/* @vite-ignore */ web.module)).default;
58    } catch (error) {
59      console.error(`[berry] ${name}'s frontend:`, error);
60    }
61    loaded.set(name, { module: web.module, renderers, links });
62  }));
63  return changed;
64}
65
66export function extensionRenderers(extension: string): Renderers | undefined {
67  return (loaded.get(extension) ?? loaded.get(formerly.get(extension) ?? ""))?.renderers ??
68    undefined;
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 {
73  if (extension !== undefined) return extensionRenderers(extension)?.tools?.[name];
74  for (const { renderers } of loaded.values()) {
75    const found = renderers?.tools?.[name];
76    if (found) return found;
77  }
78  return undefined;
79}