char-slop/berry

multipurpose self-modifiable agentic harness

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

Charlotte Somrefactor: use justin instead of ad-hoc type validation760c3b5

main
2.2 KiB67 linesraw
1import { defineRenderers, type ToolView } from "@berry/api/web.ts";
2import * as j from "@char/justin";
3import "./web.css";
4
5const PREVIEW_CHARS = 20_000;
6
7const queryOf = j.validation.compile(j.obj({ query: j.string }));
8const urlOf = j.validation.compile(j.obj({ url: j.string }));
9const formatOf = j.validation.compile(j.obj({ format: j.string }));
10const searchResults = j.validation.compile(j.array(j.obj({
11  title: j.string,
12  url: j.string,
13  snippet: j.string,
14})));
15
16function results(): ToolView {
17  const list = <ol class="search-results" />;
18  return {
19    element: <div class="tool-body">{list}</div>,
20    update({ result, error }) {
21      if (error !== null) return list.replaceChildren(<li class="error">{error}</li>);
22      // a direct call's result is its text; a script's is the value itself
23      let decoded: unknown = result ?? [];
24      try {
25        if (typeof result === "string") decoded = JSON.parse(result);
26      } catch {
27        return list.replaceChildren(<li class="error">Invalid search results</li>);
28      }
29      const found = searchResults(decoded).value;
30      if (!found) return list.replaceChildren(<li class="error">Invalid search results</li>);
31      list.replaceChildren(...found.map(({ title, url, snippet }) => (
32        <li>
33          <a href={url} target="_blank" rel="noopener noreferrer">{title}</a>
34          <span class="url">{url}</span>
35          {snippet ? <p>{snippet}</p> : undefined}
36        </li>
37      )));
38    },
39  };
40}
41
42function page(): ToolView {
43  const text = <pre class="fetched" hidden /> as HTMLPreElement;
44  return {
45    element: <div class="tool-body">{text}</div>,
46    update({ result, error }) {
47      const content = error ?? (typeof result === "string" ? result : "");
48      text.textContent = content.length > PREVIEW_CHARS
49        ? `${content.slice(0, PREVIEW_CHARS)}\n\n… ${
50          content.length - PREVIEW_CHARS
51        } more characters`
52        : content;
53      text.hidden = content === "";
54    },
55  };
56}
57
58export default defineRenderers({
59  tools: {
60    web_search: { summary: (args) => queryOf(args).value?.query ?? null, body: results },
61    web_fetch: {
62      summary: (args) =>
63        `${urlOf(args).value?.url ?? ""}${formatOf(args).value?.format === "raw" ? " · raw" : ""}`,
64      body: page,
65    },
66  },
67});