import {
assert,
assertEquals,
assertMatch,
assertRejects,
assertStringIncludes,
} from "@std/assert";
import { BACKGROUND_CONTEXT as context } from "@earendil-works/chord/context";
import {
type ImageContent,
type Message,
type ToolResultMessage,
Type,
} from "@earendil-works/pi-ai";
import { createModels } from "@earendil-works/pi-ai/models";
import {
fauxAssistantMessage,
fauxProvider,
fauxToolCall,
} from "@earendil-works/pi-ai/providers/faux";
import { MemoryStorage } from "@earendil-works/pi-durable";
import { defineExtension } from "@berry/api";
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { userInfo } from "node:os";
import code from "../extensions/code/mod.ts";
import { BerryError, Conversations, openBerry, Settings } from "./berry.ts";
import { dir, lastToolResult, sectionsSeen, setup, toolsOffered, turn } from "./fixture.ts";
import { TID_PATTERN } from "./tid.ts";
import { localOnly } from "./environment.ts";
import { tempDir } from "@berry/util";
Deno.test("configured and emitted state reaches the prompt, as an extension's section and as named ones", async () => {
await using harness = await setup();
const { berry, model } = harness;
await assertRejects(
() => berry.create({ extensions: { dir: { cwd: 5 } }, model }),
BerryError,
"dir: cwd:",
);
await assertRejects(
() => berry.create({ extensions: { dir: { cwd: "here" } }, model }),
BerryError,
"dir: not absolute: here",
);
assertEquals(Object.keys(berry.configurationParameters("dir")?.properties ?? {}), ["cwd"]);
assertEquals(berry.configurationParameters("gate"), null);
const tid = await berry.create({ extensions: { dir: { cwd: "/seed" } }, model });
assertMatch(tid, TID_PATTERN);
const seen = await turn(harness, tid, [
fauxAssistantMessage(fauxToolCall("cd", { path: "/next" }), { stopReason: "toolUse" }),
]);
assertEquals(sectionsSeen(seen[0])[0].dir, "
\ncwd: /seed\n");
assertEquals(sectionsSeen(seen[1]).at(-1)?.dir, "\ncwd: /next\n");
assertEquals(await berry.state(tid, "dir"), { cwd: "/next" });
await berry.install(defineExtension({
name: "sections",
initial: () => ({ text: "first" }),
apply: (_state, event: { text: string }) => event,
setup(ext) {
ext.prompt("extra", ({ state }) => state.text || undefined);
return (conv) => conv.prompt(() => "stable");
},
}));
const named = await berry.create({ extensions: { sections: {} }, model });
const [initial] = await turn(harness, named, []);
const shown = sectionsSeen(initial)[0];
assertEquals(shown.sections, "\nstable\n");
assertEquals(shown["sections_extra"], "\nfirst\n");
for (const text of ["second", ""]) {
await (await berry.emit(named, "sections", { text })).wait(context);
const [seen] = await turn(harness, named, []);
assertEquals(sectionsSeen(seen).at(-1), {
"sections_extra": text ? `\n${text}\n` : null,
});
}
});
Deno.test("global prompt edits reach existing conversations and code prose follows the code extension", async () => {
await using harness = await setup();
const { berry, model } = harness;
await berry.install(code);
const coding = await berry.create({ extensions: { code: {} }, model });
const plain = await berry.create({ extensions: {}, model });
await turn(harness, coding, []);
await turn(harness, plain, []);
await berry.setSystemPrompt({
userName: "Alex",
generalGuidelines: "Ask $USER before deleting anything.",
promptProse: { code: "Keep $USER's APIs small." },
});
const [changed] = await turn(harness, coding, []);
const patch = sectionsSeen(changed).at(-1)!;
assertStringIncludes(patch.lede!, "(Alex)");
assertStringIncludes(patch.guidelines!, "Ask Alex before deleting anything.");
assertStringIncludes(patch.code!, "Keep Alex's APIs small.");
assertStringIncludes(patch.code!, "current working directory:");
assert(!Object.hasOwn(patch, "code_guidelines"));
assert(!Object.hasOwn(patch, "code_project") || patch["code_project"] === null);
const [withoutCode] = await turn(harness, plain, []);
const plainSections = Object.assign({}, ...sectionsSeen(withoutCode));
assertStringIncludes(plainSections.guidelines!, "Ask Alex before deleting anything.");
assert(!plainSections.code);
await berry.setSystemPrompt({ generalGuidelines: "", promptProse: { code: "" } });
const [cleared] = await turn(harness, coding, []);
const removed = sectionsSeen(cleared).at(-1)!;
assertEquals(removed.guidelines, null);
// blank prose leaves the dynamic part of the section behind
assertStringIncludes(removed.code!, "current working directory:");
assert(!removed.code!.includes("Keep"));
assert(!Object.hasOwn(removed, "lede"));
});
Deno.test("startup fills missing prompt settings without replacing saved fields or other settings", async () => {
await using directory = await tempDir();
const path = join(directory.path, "settings.sqlite");
const { openNodeSqliteStorage } = await import("@earendil-works/pi-durable/storage/sqlite/node");
const models = createModels();
const berry = await openBerry(await openNodeSqliteStorage(path), models);
assertEquals(
(await berry.harness.snapshot(Settings, context))?.systemPrompt?.userName,
userInfo().username,
);
await berry.harness.commit(async (tx) => {
const settings = await tx.doc(Settings);
settings.models = [{ provider: "offline", modelId: "kept" }];
settings.environments = { remote: { ssh: "host" } };
settings.systemPrompt = { userName: "Sam", generalGuidelines: "" } as NonNullable<
typeof settings.systemPrompt
>;
}, context);
await berry.close();
const reopened = await openBerry(await openNodeSqliteStorage(path), models);
try {
const settings = (await reopened.harness.snapshot(Settings, context))!;
assertEquals(settings.systemPrompt?.userName, "Sam");
assertEquals(settings.systemPrompt?.generalGuidelines, "");
assertEquals(settings.systemPrompt?.promptProse ?? {}, {});
assertEquals(settings.models, [{ provider: "offline", modelId: "kept" }]);
assertEquals(settings.environments, { remote: { ssh: "host" } });
} finally {
await reopened.close();
}
});
Deno.test("AGENTS.md edits update the project section without repeating code guidelines", async () => {
await using rootDir = await tempDir();
const root = rootDir.path;
await using harness = await setup();
const { berry, model } = harness;
await mkdir(join(root, "inner"));
await writeFile(join(root, "AGENTS.md"), "outer rules");
await writeFile(join(root, "inner", "AGENTS.md"), "inner rules");
await berry.install(code);
const tid = await berry.create({ extensions: { environment: { cwd: root }, code: {} }, model });
const [initial] = await turn(harness, tid, []);
const shown = sectionsSeen(initial)[0];
assertStringIncludes(shown.code!, `current working directory: ${root}`);
assertStringIncludes(shown.code!, "guidelines:");
assert(!shown.code!.includes("outer rules"));
assertStringIncludes(shown["code_project"]!, "outer rules");
assert(!shown["code_project"]!.includes("current working directory:"));
await writeFile(join(root, "AGENTS.md"), "revised rules");
const [edited] = await turn(harness, tid, []);
const patch = sectionsSeen(edited).at(-1)!;
assertEquals(Object.keys(patch), ["code_project"]);
assertStringIncludes(patch["code_project"]!, "revised rules");
await berry.command(tid, "cd", "inner");
const [moved] = await turn(harness, tid, []);
const changed = sectionsSeen(moved).at(-1)!;
assertStringIncludes(changed.code!, `current working directory: ${join(root, "inner")}`);
assertStringIncludes(changed["code_project"]!, "inner rules");
});
Deno.test("direct tools are offered beside codemode, and the model reads their text or their images", async () => {
const image: ImageContent = { type: "image", data: "bytes", mimeType: "image/png" };
const values = ["plain text", { ...image, id: "capture" }, { type: "image", data: "bytes" }];
await using harness = await setup();
const { berry, model } = harness;
await berry.install(defineExtension({
name: "results",
initial: () => null,
setup(ext) {
ext.tool({
name: "result",
description: "Return a value",
parameters: Type.Object({ index: Type.Integer() }),
expose: "model",
execute: ({ index }) => values[index],
});
},
}));
const tid = await berry.create({ extensions: { dir: {}, results: {} }, model });
const call = (name: string, args = {}) =>
fauxAssistantMessage(fauxToolCall(name, args), { stopReason: "toolUse" });
const seen = await turn(harness, tid, [
call("shout"),
...values.map((_, index) => call("result", { index })),
]);
assertEquals(toolsOffered(seen[0]).sort(), ["cd", "codemode", "result", "shout"]);
assertEquals(lastToolResult(seen[1]).text, "HEY!");
const expected: ToolResultMessage["content"][] = [
[{ type: "text", text: "plain text" }],
[image],
[{ type: "text", text: JSON.stringify(values[2], null, 2) }],
];
for (const [index, content] of expected.entries()) {
const result = seen[index + 2].findLast((message) => message.role === "toolResult");
assert(result?.role === "toolResult");
assertEquals(result.content, content);
}
});
Deno.test("an extension installs whole or not at all, and a new reducer re-derives state from history", async () => {
await using harness = await setup();
const { berry, model } = harness;
for (const name of ["berry", "environment", "subagent"]) {
await assertRejects(
() => berry.install(defineExtension({ name, initial: () => null, setup() {} })),
BerryError,
);
}
let disposed = false;
const shadow = (expose: "model" | "codemode") =>
defineExtension({
name: "shadow",
initial: () => null,
setup(ext) {
ext.tool({
name: "codemode",
description: "",
parameters: Type.Object({}),
expose,
execute: () => "",
});
ext.onDispose(() => void (disposed = true));
},
});
await assertRejects(() => berry.install(shadow("model")), BerryError);
assert(disposed, "a refused extension is disposed");
assertEquals(berry.extensions().map((extension) => extension.name), ["dir", "gate"]);
// scripts may have a tool by that name; it cannot reach the model
await berry.install(shadow("codemode"));
const tid = await berry.create({ extensions: { dir: { cwd: "/a" } }, model });
await (await berry.emit(tid, "dir", { type: "cd", cwd: "b" })).wait(context);
await assertRejects(() =>
berry.install(defineExtension({
name: "dir",
initial: () => ({}),
setup() {
throw new Error("broken");
},
}))
);
assertEquals(await berry.state(tid, "dir"), { cwd: "b" });
// v2 resolves relative paths against the current cwd
await berry.install(
dir((s, e) => ({ cwd: e.cwd.startsWith("/") ? e.cwd : `${s.cwd}/${e.cwd}` })),
);
assertEquals(await berry.state(tid, "dir"), { cwd: "/a/b" });
});
Deno.test("a conversation has its chosen extensions, the always-on ones, and the harness's own, under any name they have had", async () => {
await using harness = await setup();
const { berry, model } = harness;
const names = async (id: string) =>
(await (await berry.conversation(id))!.agent(context)).extensions.map((e) => e.name);
// naming the harness's own changes nothing: every conversation has it
const tid = await berry.create({ extensions: { berry: {}, dir: { cwd: "/a" } }, model });
assertEquals(await names(tid), ["berry", "environment", "dir"]);
await berry.install(defineExtension({
name: "always",
alwaysOn: true,
initial: () => null,
setup() {
return (conv) => {
conv.command({ name: "ping", description: "always there", execute: () => "pong" });
};
},
}));
assertEquals(await names(tid), ["berry", "environment", "always", "dir"]);
const named = await berry.create({ extensions: { always: {}, dir: {} }, model });
assertEquals(await names(named), ["berry", "environment", "always", "dir"]);
assertEquals((await berry.commands(tid)).map(({ name }) => name), ["cd", "ping", "go"]);
assertEquals(await berry.command(tid, "ping", ""), "pong");
await berry.configure(named, { extensions: [] });
assertEquals(await names(named), ["berry", "environment", "always"]);
const conversation = (await berry.conversation(tid))!;
const [, seed] =
(await conversation.entries({ order: "ascending" }, 10, undefined, context)).items;
await (await berry.emit(tid, "dir", { type: "cd", cwd: "/b" })).wait(context);
berry.uninstall("dir");
await berry.install({ ...dir(), name: "place", formerly: ["dir"] });
assertEquals(await names(tid), ["berry", "environment", "always", "place"]);
assertEquals(await berry.state(tid, "place"), { cwd: "/b" });
// a fork from before the rename, and a conversation asked for by the old name
const fork = await conversation.fork(seed.id, { ownership: { kind: "ownerless" } }, context);
const forkTid = await berry.tidOf(fork.id);
assertEquals(await names(forkTid), ["berry", "environment", "always", "place"]);
assertEquals(await berry.state(forkTid, "place"), { cwd: "/a" });
const asked = await berry.create({ extensions: { dir: { cwd: "/c" } }, model });
assertEquals(await berry.state(asked, "place"), { cwd: "/c" });
});
Deno.test("a replaced load closes its channels and is disposed once the calls it started end", async () => {
const { faux, berry, model } = await setup();
const held = { entered: Promise.withResolvers(), release: Promise.withResolvers() };
const opened: number[] = [];
let disposed = 0;
const version = (mark: number) =>
defineExtension({
name: "slow",
alwaysOn: true,
initial: () => null,
setup(ext) {
ext.channel({
name: "line",
parameters: Type.Object({}),
open: () => void opened.push(mark),
});
ext.tool({
name: "hold",
description: "wait",
parameters: Type.Object({}),
expose: "model",
execute: () => {
held.entered.resolve();
return held.release.promise;
},
});
ext.onDispose(() => void (disposed += mark));
},
});
await berry.install(version(1));
const tid = await berry.create({ extensions: {}, model });
let closed = 0;
const channel = { send() {}, messages: new ReadableStream(), close: () => void closed++ };
await berry.channel(tid, "slow", "line", {}, channel, new AbortController().signal);
faux.setResponses([
fauxAssistantMessage(fauxToolCall("hold", {}), { stopReason: "toolUse" }),
fauxAssistantMessage("done"),
]);
const conversation = (await berry.conversation(tid))!;
const submission = await conversation.submit({ type: "input", content: "go" }, context);
await held.entered.promise;
await berry.install(version(10));
assertEquals(disposed, 0);
held.release.resolve("released");
await submission.wait(context);
await conversation.waitForIdle(context);
assertEquals({ opened, closed, disposed }, { opened: [1], closed: 1, disposed: 1 });
await berry.close();
assertEquals(disposed, 11);
});
Deno.test("handlers answer only in conversations that have their extension", async () => {
await using harness = await setup();
const { berry, model } = harness;
await berry.install(defineExtension({
name: "asked",
initial: () => "folded",
setup(ext) {
ext.handle({
name: "echo",
parameters: Type.Object({ times: Type.Integer({ default: 1 }) }),
execute: ({ times }, ctx) => ({ state: ctx.state, times, aborted: ctx.signal.aborted }),
});
ext.handle({
name: "fail",
parameters: Type.Object({}),
execute: () => {
throw new Error("no such thing");
},
});
},
}));
const tid = await berry.create({ extensions: { asked: {} }, model });
assertEquals(await berry.request(tid, "asked", "echo", undefined), {
state: "folded",
times: 1,
aborted: false,
});
assertEquals(
(await berry.request(tid, "asked", "echo", { times: 3 }) as { times: number }).times,
3,
);
await assertRejects(
() => berry.request(tid, "asked", "echo", { times: "3" }),
BerryError,
"times",
);
await assertRejects(() => berry.request(tid, "asked", "fail", {}), BerryError, "no such thing");
await assertRejects(() => berry.request(tid, "asked", "nope", {}), BerryError, "no handler");
const without = await berry.create({ extensions: {}, model });
await assertRejects(
() => berry.request(without, "asked", "echo", {}),
BerryError,
"not in this conversation",
);
});
Deno.test("forks and branches start from the state at their entry, and a branch keeps the conversation's tid, agent, and name", async () => {
await using harness = await setup();
const { faux, berry, model, gate } = harness;
const tid = await berry.create({ extensions: { dir: { cwd: "/a" } }, model });
const conversation = (await berry.conversation(tid))!;
const [start, seed] =
(await conversation.entries({ order: "ascending" }, 10, undefined, context)).items;
assertEquals([start.kind, seed.kind], ["berry.start", "berry.event"]);
await (await berry.emit(tid, "dir", { type: "cd", cwd: "/b" })).wait(context);
const fork = await conversation.fork(seed.id, { ownership: { kind: "ownerless" } }, context);
const forkTid = await berry.tidOf(fork.id);
assert(forkTid !== tid);
assertEquals(await berry.state(forkTid, "dir"), { cwd: "/a" });
assertEquals(await berry.state(tid, "dir"), { cwd: "/b" });
await berry.configure(tid, { extensions: ["dir", "gate"] });
faux.setResponses([
fauxAssistantMessage(fauxToolCall("hold", {}), { stopReason: "toolUse" }),
fauxAssistantMessage("done"),
]);
const submission = await conversation.submit({ type: "input", content: "go" }, context);
await gate.entered.promise;
await assertRejects(() => berry.branch(tid, seed.id), BerryError, "busy");
gate.release.resolve("released");
await submission.wait(context);
await conversation.waitForIdle(context);
const tip = (await conversation.context(context)).entries.at(-1)!.id;
await berry.markRead(tid, tip);
await berry.rename(tid, " a custom name ");
const feed = (await berry.watchState(tid, "dir"))!;
const moved = Promise.withResolvers();
feed.start((value) => Promise.resolve(moved.resolve(value)));
await berry.branch(tid, seed.id);
const branch = (await berry.conversation(tid))!;
assert(branch.id !== conversation.id);
assertEquals(await berry.tidOf(branch.id), tid);
const index = (await berry.harness.snapshot(Conversations, context))!;
assertEquals(Object.keys(index).sort(), [tid, forkTid].sort());
assertEquals([index[tid].title, index[tid].readThrough], ["a custom name", tip]);
assertEquals(await berry.state(tid, "dir"), { cwd: "/a" });
assertEquals(await moved.promise, { cwd: "/a" });
assertEquals(
(await branch.agent(context)).extensions.map((extension) => extension.name),
["berry", "environment", "dir", "gate"],
);
await feed.stop();
await berry.branch(tid, tip);
assertEquals((await berry.harness.snapshot(Conversations, context))![tid].title, "a custom name");
await berry.rename(tid, " ");
assertEquals((await berry.harness.snapshot(Conversations, context))![tid].title, undefined);
await assertRejects(() => berry.rename("missing", "name"), BerryError, "No conversation");
});
Deno.test("commands are listed per conversation and run as the user, landing at once while busy", async () => {
await using harness = await setup();
const { faux, berry, model, gate } = harness;
const tid = await berry.create({ extensions: { dir: {}, gate: {} }, model });
const conversation = (await berry.conversation(tid))!;
assertEquals((await berry.commands(tid)).map(({ name }) => name), ["cd", "go"]);
assertEquals(await berry.command(tid, "go", "/cmd"), "/cmd");
await conversation.waitForIdle(context);
assertEquals(await berry.state(tid, "dir"), { cwd: "/cmd" });
await assertRejects(() => berry.command(tid, "go", "relative"), BerryError, "not absolute");
await assertRejects(() => berry.command(tid, "nope", ""), BerryError, "No command /nope");
const other = await berry.create({ extensions: {}, model });
assertEquals((await berry.commands(other)).map(({ name }) => name), ["cd"]);
let next: readonly Message[] = [];
faux.setResponses([
fauxAssistantMessage(fauxToolCall("hold", {}), { stopReason: "toolUse" }),
(ctx) => (next = ctx.messages, fauxAssistantMessage("done")),
]);
const submission = await conversation.submit({ type: "input", content: "go" }, context);
await gate.entered.promise;
assertEquals(await berry.command(tid, "go", "/mid"), "/mid");
assertEquals(await berry.state(tid, "dir"), { cwd: "/mid" });
gate.release.resolve("released");
await submission.wait(context);
await conversation.waitForIdle(context);
assertEquals(await berry.state(tid, "dir"), { cwd: "/mid" });
// and the model hears of it at the next request, not only on the next input
assertEquals(sectionsSeen(next).at(-1)?.dir, "\ncwd: /mid\n");
});
Deno.test("conversations use only models enabled in settings, at a thinking level the model supports", async () => {
await using harness = await setup(undefined, {
models: [{ id: "thinker", reasoning: true }, { id: "plain", reasoning: false }],
});
const { berry, model } = harness;
const plain = { provider: "faux", modelId: "plain" };
await assertRejects(
() => berry.create({ extensions: {}, model: plain }),
BerryError,
"not enabled",
);
const tid = await berry.create({ extensions: {}, model, thinkingLevel: "high" });
await assertRejects(() => berry.configure(tid, { model: plain }), BerryError, "not enabled");
await berry.enableModels([model, plain, model]);
assertEquals((await berry.harness.snapshot(Settings, context))?.models, [model, plain]);
const agent = async (id: string) => await (await berry.conversation(id))!.agent(context);
assertEquals((await agent(tid)).thinkingLevel, "high");
await berry.configure(tid, { model: plain });
assertEquals([(await agent(tid)).model, (await agent(tid)).thinkingLevel], [plain, "off"]);
await berry.configure(tid, { model, thinkingLevel: "max" });
assertEquals((await agent(tid)).thinkingLevel, "high");
const created = await berry.create({ extensions: {}, model: plain, thinkingLevel: "high" });
assertEquals((await agent(created)).thinkingLevel, "off");
});
Deno.test("extension prose defaults, overrides, blanks, selection, and $USER", async () => {
await using harness = await setup();
const { berry, model } = harness;
await berry.install(defineExtension({
name: "notes",
initial: () => null,
setup(ext) {
ext.promptProse({ default: "Greet $USER warmly." });
return (conv) => conv.prompt(() => "dynamic content");
},
}));
assertEquals(berry.promptProseDefault("notes"), "Greet $USER warmly.");
assertEquals(berry.promptProseDefault("dir"), null);
const withNotes = await berry.create({ extensions: { notes: {} }, model });
const withoutNotes = await berry.create({ extensions: {}, model });
const [first] = await turn(harness, withNotes, []);
const shown = sectionsSeen(first)[0];
assertStringIncludes(shown.notes!, "Greet");
assert(!shown.notes!.includes("$USER"));
assert(!shown.notes!.includes("guidelines:"));
assertStringIncludes(shown.notes!, "dynamic content");
const [absent] = await turn(harness, withoutNotes, []);
assert(!Object.assign({}, ...sectionsSeen(absent)).notes);
await berry.setSystemPrompt({ userName: "Sam", promptProse: { notes: "Bid $USER farewell." } });
const [overrode] = await turn(harness, withNotes, []);
assertStringIncludes(sectionsSeen(overrode).at(-1)!.notes!, "Bid Sam farewell.");
assertStringIncludes(sectionsSeen(overrode).at(-1)!.notes!, "dynamic content");
await berry.setSystemPrompt({ promptProse: { notes: "" } });
const [blanked] = await turn(harness, withNotes, []);
assertStringIncludes(sectionsSeen(blanked).at(-1)!.notes!, "dynamic content");
assert(!sectionsSeen(blanked).at(-1)!.notes!.includes("Bid"));
const stored = (await berry.harness.snapshot(Settings, context))?.systemPrompt;
assertEquals(stored?.promptProse?.notes, "");
});
Deno.test("startup migrates legacy code prose, preserving blank values and newer overrides", async () => {
const { openNodeSqliteStorage } = await import("@earendil-works/pi-durable/storage/sqlite/node");
for (
const { legacy, override, expected } of [
{
legacy: "Legacy for $USER.",
override: undefined,
expected: "guidelines:\nLegacy for $USER.",
},
{ legacy: "", override: undefined, expected: "" },
{ legacy: "Stale legacy.", override: "", expected: "" },
]
) {
await using directory = await tempDir();
const path = join(directory.path, "settings.sqlite");
const models = createModels();
const seed = await openBerry(await openNodeSqliteStorage(path), models);
try {
await seed.harness.commit(async (tx) => {
const settings = await tx.doc(Settings);
const stored = {
userName: "Sam",
generalGuidelines: "",
codingGuidelines: legacy,
promptProse: {
other: "untouched",
...(override === undefined ? {} : { code: override }),
},
};
settings.systemPrompt = stored;
}, context);
} finally {
await seed.close();
}
const berry = await openBerry(await openNodeSqliteStorage(path), models);
try {
const stored = (await berry.harness.snapshot(Settings, context))?.systemPrompt;
assertEquals(stored?.promptProse, { code: expected, other: "untouched" });
assertEquals(stored?.userName, "Sam");
assertEquals(stored?.generalGuidelines, "");
} finally {
await berry.close();
}
}
});
Deno.test("prompt prose overrides survive a missing extension", async () => {
await using harness = await setup();
const { berry } = harness;
await berry.install(defineExtension({
name: "ephemeral",
initial: () => null,
setup(ext) {
ext.promptProse({ default: "default prose" });
},
}));
await berry.setSystemPrompt({ promptProse: { ephemeral: "kept override" } });
berry.uninstall("ephemeral");
assertEquals(berry.promptProseDefault("ephemeral"), null);
assertEquals(
(await berry.harness.snapshot(Settings, context))?.systemPrompt?.promptProse?.ephemeral,
"kept override",
);
await berry.install(defineExtension({
name: "ephemeral",
initial: () => null,
setup(ext) {
ext.promptProse({ default: "default prose" });
},
}));
assertEquals(berry.promptProseDefault("ephemeral"), "default prose");
assertEquals(
(await berry.harness.snapshot(Settings, context))?.systemPrompt?.promptProse?.ephemeral,
"kept override",
);
});
Deno.test("a host lends /reload its extensions, and the prompt a guide to berry's source", async () => {
const faux = fauxProvider();
const models = createModels();
models.setProvider(faux.provider);
const model = { provider: "faux", modelId: faux.getModel().id };
const reloaded: string[] = [];
const loaded = (name: string) =>
({ name, description: null, status: "loaded", error: null }) as const;
const failed = { ...loaded("two"), status: "failed", error: "no mod.ts" } as const;
const berry = await openBerry(new MemoryStorage(), models, {
root: "/somewhere/berry",
check: () => Promise.resolve(null),
restart: () => {},
extensions: () => [loaded("one"), failed],
reload: (name) => {
reloaded.push(name);
return Promise.resolve(name === "two" ? failed : loaded(name));
},
}, {
async open(name, cwd, during) {
return Object.assign(await localOnly.open("local", cwd, during), { name });
},
close: () => Promise.resolve(),
});
try {
await berry.enableModels([model]);
const tid = await berry.create({ extensions: {}, model });
const seen = await turn({ faux, berry }, tid, []);
assertStringIncludes(sectionsSeen(seen[0])[0].berry ?? "", "/somewhere/berry");
assertEquals(toolsOffered(seen[0]), ["codemode"]);
assertEquals(await berry.command(tid, "reload", " one "), "reloaded one");
await assertRejects(() => berry.command(tid, "reload", ""), BerryError, "two: no mod.ts");
assertEquals(reloaded, ["one", "one", "two"]);
await berry.configureExtension(tid, "environment", { environment: "remote" });
const [remote] = await turn({ faux, berry }, tid, []);
assertEquals(sectionsSeen(remote).at(-1)?.berry, null);
const fresh = await berry.create({
extensions: { environment: { environment: "remote" } },
model,
});
const [firstRemote] = await turn({ faux, berry }, fresh, []);
assertEquals(sectionsSeen(firstRemote).at(-1)?.berry, undefined);
await berry.configureExtension(tid, "environment", { environment: "local" });
const [local] = await turn({ faux, berry }, tid, []);
assertStringIncludes(sectionsSeen(local).at(-1)?.berry ?? "", "/somewhere/berry");
} finally {
await berry.close();
}
});