char-slop/berry

multipurpose self-modifiable agentic harness

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

Charlotte Somtweak the mechanics of extension configuration a little6ae9dd3

main
4.9 KiB148 linesraw
1import type { JsonValue } from "@berry/api";
2import { type ConfigurationContext, defineRenderers } from "@berry/api/web.ts";
3
4type Control = HTMLInputElement | HTMLSelectElement;
5
6export default defineRenderers({
7  event(event) {
8    if (
9      typeof event === "object" && event !== null && !Array.isArray(event) &&
10      event.type === "configured"
11    ) return null;
12  },
13  configuration() {
14    let context: ConfigurationContext | null = null;
15    let shown: Readonly<Record<string, JsonValue>> = {};
16    const controls = new Map<string, Control>();
17    const labels = new Map<string, HTMLElement>();
18    const wide = { gridColumn: "1 / -1" };
19
20    const field = (
21      key: string,
22      label: string,
23      input: Control,
24      span = false,
25    ) => {
26      controls.set(key, input);
27      const element = (
28        <label
29          class="field"
30          style={span ? { ...wide, minWidth: "0" } : { minWidth: "0" }}
31        >
32          <span>{label}</span>
33          {input}
34        </label>
35      ) as HTMLElement;
36      labels.set(key, element);
37      return element;
38    };
39    const select = (choices: string[]) =>
40      (
41        <select style={{ minWidth: "0" }}>
42          {choices.map((choice) => <option value={choice}>{choice}</option>)}
43        </select>
44      ) as HTMLSelectElement;
45    const text = (placeholder: string, decimal = false) =>
46      (
47        <input
48          type="text"
49          placeholder={placeholder}
50          inputMode={decimal ? "decimal" : "text"}
51          style={{ minWidth: "0" }}
52        />
53      ) as HTMLInputElement;
54
55    const routing = (
56      <div
57        style={{
58          display: "grid",
59          gridTemplateColumns: "1fr 1fr",
60          gap: "0.35rem 0.6rem",
61          minWidth: "0",
62        }}
63      >
64        {field(
65          "sort",
66          "Sort providers",
67          select(["default", "throughput", "latency", "price"]),
68          true,
69        )}
70        {field("only", "Allowed providers", text("any"))}
71        {field("ignore", "Excluded providers", text("none"))}
72        {field("maxPromptPrice", "Max input $/M", text("no cap", true))}
73        {field("maxCompletionPrice", "Max output $/M", text("no cap", true))}
74        {field("allowFallbacks", "Fallbacks", select(["default", "yes", "no"]), true)}
75      </div>
76    ) as HTMLElement;
77    const status = <p class="configuration-status" role="status" aria-live="polite" />;
78    const form = (
79      <fieldset
80        class="configuration"
81        style={{
82          border: "1px solid var(--line)",
83          borderRadius: "0.35rem",
84          padding: "0.5rem 0.6rem 0.6rem",
85        }}
86        disabled
87      >
88        <legend style={{ padding: "0 0.35rem", marginBottom: "-0.2rem" }}>openrouter</legend>
89        {routing}
90        {status}
91      </fieldset>
92    ) as HTMLFieldSetElement;
93    const element = <div hidden>{form}</div> as HTMLElement;
94
95    const read = (input: Control): JsonValue =>
96      input instanceof HTMLInputElement && input.type === "checkbox" ? input.checked : input.value;
97    const commit = (event: Event) => {
98      const key = [...controls].find(([, input]) => input === event.target)
99        ?.[0];
100      if (!context?.values || key === undefined) return;
101      const value = read(controls.get(key)!);
102      if (value === context.values[key]) return;
103      // the error is shown from the context, and the field keeps what was entered until it is fixed
104      void context.save({ [key]: value }).catch(() => {});
105    };
106    form.addEventListener("input", commit);
107    form.addEventListener("change", commit);
108    form.addEventListener("keydown", (event) => {
109      if (event.key !== "Enter" || context?.tid === null) return;
110      event.preventDefault();
111      (event.target as HTMLElement).blur();
112    });
113
114    return {
115      element,
116      update(next) {
117        context = next;
118        element.hidden = next.model?.provider !== "openrouter";
119        for (const [key, label] of labels) {
120          label.title = next.schema.properties[key]?.description ?? "";
121        }
122        const values = next.values;
123        form.disabled = values === null;
124        if (values) {
125          for (const [key, input] of controls) {
126            if (
127              Object.is(values[key], shown[key]) ||
128              input === document.activeElement
129            ) continue;
130            const value = values[key] ?? next.schema.properties[key]?.default ??
131              null;
132            if (
133              input instanceof HTMLInputElement && input.type === "checkbox"
134            ) {
135              input.checked = value === true;
136            } else input.value = value === null ? "" : String(value);
137          }
138          shown = values;
139        }
140        status.classList.toggle("error", next.error !== null);
141        status.textContent = next.error ??
142          (next.saveStatus === "saving" && next.tid !== null
143            ? "applying configuration (pending if agent is busy)"
144            : "");
145      },
146    };
147  },
148});