char-slop/berry

multipurpose self-modifiable agentic harness

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

Charlotte Soma bash call that exits nonzero says so in its card's header, rather than ok3339f51

main
2.8 KiB75 linesraw
1import type { JsonValue } from "@berry/api";
2import { code, type ToolRenderer, type ToolView } from "@berry/api/web.ts";
3import * as j from "@char/justin";
4import { ansiOutput } from "./ansi-view.ts";
5
6const argsOf = j.validation.compile(
7  j.obj({ command: j.optional(j.string), timeout: j.optional(j.number) }),
8);
9const outputOf = j.validation.compile(j.obj({ output: j.string }));
10const rawOutputOf = j.validation.compile(j.obj({ rawOutput: j.string }));
11const exitCodeOf = j.validation.compile(j.obj({ exitCode: j.number }));
12
13export function bashView(language = "bash"): ToolView {
14  const prompt = <div class="command" />;
15  const rendered = <pre hidden /> as HTMLPreElement;
16  const showOutput = ansiOutput(rendered);
17  const exit = <span class="exit" hidden /> as HTMLSpanElement;
18  let shown: { command: string; streaming: boolean } | null = null;
19  return {
20    element: (
21      <div class="tool-body">
22        {prompt}
23        {rendered}
24        {exit}
25      </div>
26    ),
27    update(call) {
28      const args = argsOf(call.args).value;
29      const command = args?.command ?? "";
30      const streaming = call.status === "streaming";
31      // highlighted once the model has written all of it
32      if (command !== shown?.command || streaming !== shown.streaming) {
33        prompt.replaceChildren(
34          code(command, {
35            ...(streaming ? {} : { language }),
36            gutter: (i) => (i === 0 ? "$" : ""),
37          }),
38        );
39      }
40      // a direct call's result is the text the model saw, which carries the exit code itself
41      const result = call.result;
42      const output = (typeof result === "string" ? result : outputOf(result).value?.output) ??
43        rawOutputOf(call.details).value?.rawOutput ?? call.output ?? call.error ?? "";
44      showOutput(output);
45      shown = { command, streaming };
46      const exitCode = exitCodeOf(result).value?.exitCode;
47      exit.textContent = exitCode === undefined || exitCode === 0 ? "" : `exit ${exitCode}`;
48      exit.hidden = exit.textContent === "";
49    },
50  };
51}
52
53/** The exit code a call ended with: in a script's result, or at the end of the text a direct call's model saw. */
54function exitCode(result: JsonValue | null): number | null {
55  if (typeof result === "string") {
56    const code = result.match(/\[exit code (-?\d+)(?:;[^\]]*)?\]\s*$/)?.[1];
57    return code === undefined ? null : Number(code);
58  }
59  return exitCodeOf(result).value?.exitCode ?? null;
60}
61
62export const bashRenderer: ToolRenderer = {
63  summary: (args) => argsOf(args).value?.command?.split("\n")[0] ?? null,
64  timeout: (args) => argsOf(args).value?.timeout ?? null,
65  outcome: (call) => {
66    const code = exitCode(call.result);
67    return code === null || code === 0 ? null : `exit ${code}`;
68  },
69  body: () => bashView(),
70};
71
72export const powershellRenderer: ToolRenderer = {
73  ...bashRenderer,
74  body: () => bashView("powershell"),
75};