mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 11:39:41 +03:00
205 lines
9.0 KiB
TypeScript
205 lines
9.0 KiB
TypeScript
import { expect, test } from "bun:test"
|
|
import { agent, model, renderLocal, session } from "../fixture/local"
|
|
import { json } from "../fixture/tui-client"
|
|
|
|
test("cycles all recent models in a stable order in both directions", async () => {
|
|
await using setup = await renderLocal({
|
|
models: [model("first"), model("second"), model("third")],
|
|
preferences: { recent: ["first", "second", "third"].map((modelID) => ({ providerID: "provider", modelID })) },
|
|
})
|
|
expect(setup.local.model.current()?.modelID).toBe("first")
|
|
for (const id of ["second", "third", "first"]) {
|
|
setup.local.model.cycle(1)
|
|
expect(setup.local.model.current()?.modelID).toBe(id)
|
|
}
|
|
for (const id of ["third", "second", "first"]) {
|
|
setup.local.model.cycle(-1)
|
|
expect(setup.local.model.current()?.modelID).toBe(id)
|
|
}
|
|
})
|
|
|
|
test("uses the last configured model and variant ahead of recents", async () => {
|
|
await using setup = await renderLocal({
|
|
models: [model("first"), model("second", ["low", "high"]), model("third")],
|
|
preferences: { recent: [{ providerID: "provider", modelID: "third" }] },
|
|
fetch: (url) => {
|
|
if (url.pathname === "/api/config")
|
|
return json([
|
|
{ type: "document", info: { model: "provider/first" } },
|
|
{ type: "document", info: { model: { providerID: "provider", model: "second", variant: "high" } } },
|
|
{ type: "document", info: {} },
|
|
])
|
|
},
|
|
})
|
|
expect(setup.local.model.selection()).toEqual({ providerID: "provider", modelID: "second", variant: "high" })
|
|
})
|
|
|
|
test("switching agents restores their model and variant within the session", async () => {
|
|
await using setup = await renderLocal({
|
|
models: [model("first", ["low", "high"]), model("second", ["low", "high"]), model("third", ["low", "high"])],
|
|
agents: [
|
|
agent("build", { providerID: "provider", id: "first", variant: "high" }),
|
|
agent("plan", { providerID: "provider", id: "second", variant: "low" }),
|
|
],
|
|
sessions: [session("ses_first", { providerID: "provider", id: "first", variant: "low" })],
|
|
})
|
|
await setup.data.session.sync("ses_first")
|
|
setup.route.navigate({ type: "session", sessionID: "ses_first" })
|
|
expect(setup.local.model.selection()).toEqual({ providerID: "provider", modelID: "first", variant: "low" })
|
|
setup.local.agent.move(1)
|
|
expect(setup.local.agent.current()?.id).toBe("plan")
|
|
expect(setup.local.model.selection()).toEqual({ providerID: "provider", modelID: "second", variant: "low" })
|
|
setup.local.model.set({ providerID: "provider", modelID: "third" })
|
|
setup.local.model.variant.set("high")
|
|
setup.local.agent.move(-1)
|
|
expect(setup.local.model.selection()).toEqual({ providerID: "provider", modelID: "first", variant: "low" })
|
|
setup.local.agent.set("plan")
|
|
expect(setup.local.model.selection()).toEqual({ providerID: "provider", modelID: "third", variant: "high" })
|
|
})
|
|
|
|
test("agent and model drafts are isolated across sessions and survive navigation", async () => {
|
|
await using setup = await renderLocal({
|
|
models: [model("first", ["low", "high"]), model("second", ["low", "high"])],
|
|
agents: [agent("build"), agent("plan", { providerID: "provider", id: "second" })],
|
|
sessions: [
|
|
session("ses_first", { providerID: "provider", id: "first", variant: "low" }),
|
|
session("ses_second", { providerID: "provider", id: "second", variant: "high" }, "plan"),
|
|
],
|
|
})
|
|
await Promise.all([setup.data.session.sync("ses_first"), setup.data.session.sync("ses_second")])
|
|
setup.route.navigate({ type: "session", sessionID: "ses_first" })
|
|
setup.local.agent.set("plan")
|
|
setup.local.model.variant.set("low")
|
|
setup.route.navigate({ type: "session", sessionID: "ses_second" })
|
|
expect(setup.local.agent.current()?.id).toBe("plan")
|
|
expect(setup.local.model.variant.current()).toBe("high")
|
|
setup.route.navigate({ type: "session", sessionID: "ses_first" })
|
|
expect(setup.local.agent.current()?.id).toBe("plan")
|
|
expect(setup.local.model.variant.current()).toBe("low")
|
|
setup.local.agent.set("build")
|
|
expect(setup.local.model.selection()).toEqual({ providerID: "provider", modelID: "first", variant: "low" })
|
|
})
|
|
|
|
test("retains an unavailable saved session model instead of selecting a fallback", async () => {
|
|
const selected = { providerID: "provider", id: "missing", variant: "high" }
|
|
await using setup = await renderLocal({
|
|
models: [model("first", ["low", "high"]), model("second")],
|
|
agents: [agent("build", { providerID: "provider", id: "first", variant: "low" })],
|
|
sessions: [session("ses_first", selected)],
|
|
})
|
|
await setup.data.session.sync("ses_first")
|
|
setup.route.navigate({ type: "session", sessionID: "ses_first" })
|
|
expect(setup.local.model.selection()).toEqual({ providerID: "provider", modelID: "missing", variant: "high" })
|
|
expect(setup.local.model.available()).toBe(false)
|
|
expect(setup.data.session.get("ses_first")?.model).toEqual(selected)
|
|
})
|
|
|
|
test("new-session fallback still skips unavailable CLI and recent models", async () => {
|
|
await using setup = await renderLocal({
|
|
models: [model("first"), model("second")],
|
|
args: { model: "provider/missing-cli" },
|
|
preferences: {
|
|
recent: [
|
|
{ providerID: "provider", modelID: "missing-recent" },
|
|
{ providerID: "provider", modelID: "second" },
|
|
],
|
|
},
|
|
})
|
|
expect(setup.local.model.current()?.modelID).toBe("second")
|
|
expect(setup.local.model.available()).toBe(true)
|
|
})
|
|
|
|
test("a manual agent switch supersedes the CLI agent after its commit", async () => {
|
|
await using setup = await renderLocal({
|
|
args: { agent: "build" },
|
|
agents: [agent("build"), agent("plan")],
|
|
sessions: [session("ses_first", { providerID: "provider", id: "first" })],
|
|
fetch: selectionMessage,
|
|
})
|
|
await setup.data.session.sync("ses_first")
|
|
setup.route.navigate({ type: "session", sessionID: "ses_first" })
|
|
setup.local.agent.set("plan")
|
|
await publishSelection(setup, "plan", "first")
|
|
expect(setup.local.agent.current()?.id).toBe("plan")
|
|
setup.route.navigate({ type: "home" })
|
|
setup.route.navigate({ type: "session", sessionID: "ses_first" })
|
|
expect(setup.local.agent.current()?.id).toBe("plan")
|
|
})
|
|
|
|
test("a late inactive-agent acknowledgment preserves its choice after the active agent commits", async () => {
|
|
await using setup = await renderLocal({
|
|
models: [model("first"), model("second"), model("third")],
|
|
agents: [agent("build"), agent("plan", { providerID: "provider", id: "second" })],
|
|
sessions: [session("ses_first", { providerID: "provider", id: "first" })],
|
|
fetch: selectionMessage,
|
|
})
|
|
await setup.data.session.sync("ses_first")
|
|
setup.route.navigate({ type: "session", sessionID: "ses_first" })
|
|
setup.local.agent.set("plan")
|
|
setup.local.model.set({ providerID: "provider", modelID: "third" })
|
|
setup.local.model.trackSessionCommit("ses_first", { providerID: "provider", id: "third" }, "plan")
|
|
setup.local.agent.set("build")
|
|
await publishSelection(setup, "plan", "third")
|
|
setup.local.model.trackSessionCommit("ses_first", { providerID: "provider", id: "first" }, "build")
|
|
await publishSelection(setup, "build", "first")
|
|
setup.local.agent.set("plan")
|
|
expect(setup.local.model.current()?.modelID).toBe("third")
|
|
})
|
|
|
|
test("same-model agent switches clear drafts without a model acknowledgment", async () => {
|
|
await using setup = await renderLocal({
|
|
models: [model("first"), model("second")],
|
|
agents: [agent("build"), agent("plan")],
|
|
sessions: [session("ses_first", { providerID: "provider", id: "first" })],
|
|
fetch: selectionMessage,
|
|
})
|
|
await setup.data.session.sync("ses_first")
|
|
setup.route.navigate({ type: "session", sessionID: "ses_first" })
|
|
setup.local.agent.set("plan")
|
|
setup.local.model.trackSessionCommit("ses_first", { providerID: "provider", id: "first" }, "plan")
|
|
await publishSelection(setup, "plan", "first", false)
|
|
await publishSelection(setup, "plan", "second")
|
|
expect(setup.local.model.current()?.modelID).toBe("second")
|
|
})
|
|
|
|
async function publishSelection(
|
|
setup: Awaited<ReturnType<typeof renderLocal>>,
|
|
agent: string,
|
|
modelID: string,
|
|
changed = true,
|
|
) {
|
|
setup.events.emit({
|
|
id: `evt_${crypto.randomUUID()}`,
|
|
type: "session.agent.selected",
|
|
created: 1,
|
|
durable: { aggregateID: "ses_first", seq: 1, version: 1 },
|
|
data: { sessionID: "ses_first", agent },
|
|
})
|
|
if (changed)
|
|
setup.events.emit({
|
|
id: `evt_${crypto.randomUUID()}_${modelID}`,
|
|
type: "session.model.selected",
|
|
created: 2,
|
|
durable: { aggregateID: "ses_first", seq: 2, version: 1 },
|
|
data: { sessionID: "ses_first", model: { providerID: "provider", id: modelID } },
|
|
})
|
|
await setup.waitFor(async () => {
|
|
await Bun.sleep(10)
|
|
const session = setup.data.session.get("ses_first")
|
|
return session?.agent === agent && session.model?.id === modelID
|
|
})
|
|
}
|
|
|
|
function selectionMessage(url: URL) {
|
|
if (!url.pathname.includes("/message/")) return
|
|
const id = url.pathname.split("/").at(-1)!
|
|
return json({
|
|
data: {
|
|
id,
|
|
type: "model-switched",
|
|
model: { providerID: "provider", id: id.split("_").at(-1) },
|
|
time: { created: 2 },
|
|
},
|
|
})
|
|
}
|