Files
anomalyco_opencode/packages/opencode/test/cli/run/session.shared.test.ts
T
2026-07-13 23:30:55 +00:00

193 lines
5.8 KiB
TypeScript

import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"
import { OpenCode, type SessionMessageUser } from "@opencode-ai/client/promise"
import {
createSession,
resolveCurrentSession,
sessionHistory,
sessionVariant,
type RunSession,
type SessionMessages,
} from "@opencode-ai/cli/mini/session.shared"
const model = {
providerID: "openai",
modelID: "gpt-5",
}
afterEach(() => {
mock.restore()
})
function userMessage(
id: string,
text: string,
input: Pick<SessionMessageUser, "files" | "agents"> = {},
): SessionMessageUser {
return {
id,
type: "user",
time: { created: 1 },
text,
files: input.files,
agents: input.agents,
}
}
describe("run session shared", () => {
test("builds user prompt text from text, file, and agent attachments", () => {
const msgs: SessionMessages = [
userMessage("msg-user-1", "look @scan @note.ts", {
agents: [{ name: "scan", mention: { start: 5, end: 10, text: "@scan" } }],
files: [
{
data: "",
mime: "text/plain",
name: "note.ts",
source: { type: "uri", uri: "file:///tmp/note.ts" },
mention: { start: 11, end: 19, text: "@note.ts" },
},
],
}),
]
const out = createSession(msgs)
expect(out.first).toBe(false)
expect(out.turns).toHaveLength(1)
expect(out.turns[0]?.prompt).toEqual({
text: "look @scan @note.ts",
parts: [
{ type: "file", url: "file:///tmp/note.ts", mime: "text/plain", filename: "note.ts", source: {
type: "file",
path: "note.ts",
text: { start: 11, end: 19, value: "@note.ts" },
} },
{ type: "agent", name: "scan", source: { start: 5, end: 10, value: "@scan" } },
],
})
})
test("supports attachments without mentions", () => {
const out = createSession([
userMessage("msg-user-1", "look", {
agents: [{ name: "scan" }],
files: [
{
data: "",
mime: "text/plain",
name: "note.ts",
source: { type: "uri", uri: "file:///tmp/note.ts" },
},
],
}),
])
expect(out.turns[0]?.prompt).toEqual({
text: "look",
parts: [
{ type: "file", url: "file:///tmp/note.ts", mime: "text/plain", filename: "note.ts", source: undefined },
{ type: "agent", name: "scan", source: undefined },
],
})
})
test("dedupes consecutive history entries, drops blanks, and copies prompt parts", () => {
const parts = [
{
type: "agent" as const,
name: "scan",
source: { start: 0, end: 5, value: "@scan" },
},
]
const session: RunSession = {
first: false,
turns: [
{ prompt: { text: "one", parts }, provider: "openai", model: "gpt-5", variant: "high" },
{ prompt: { text: "one", parts: structuredClone(parts) }, provider: "openai", model: "gpt-5", variant: "high" },
{ prompt: { text: " ", parts: [] }, provider: "openai", model: "gpt-5", variant: "high" },
{ prompt: { text: "two", parts: [] }, provider: "openai", model: "gpt-5", variant: undefined },
],
}
const out = sessionHistory(session)
expect(out.map((item) => item.text)).toEqual(["one", "two"])
expect(out[0]?.parts).toEqual(parts)
expect(out[0]?.parts).not.toBe(parts)
expect(out[0]?.parts[0]).not.toBe(parts[0])
})
test("returns the latest matching variant for the active model", () => {
const session: RunSession = {
first: false,
turns: [
{ prompt: { text: "one", parts: [] }, provider: "openai", model: "gpt-5", variant: "high" },
{ prompt: { text: "two", parts: [] }, provider: "anthropic", model: "sonnet", variant: "max" },
{ prompt: { text: "three", parts: [] }, provider: "openai", model: "gpt-5", variant: undefined },
],
}
expect(sessionVariant(session, model)).toBeUndefined()
session.turns.push({
prompt: { text: "four", parts: [] },
provider: "openai",
model: "gpt-5",
variant: "minimal",
})
expect(sessionVariant(session, model)).toBe("minimal")
})
test("restores current prompt history from stored text and file references", async () => {
const client = OpenCode.make({ baseUrl: "https://opencode.test" })
spyOn(client.message, "list").mockImplementation(() =>
Promise.resolve({
data: [
userMessage("msg_prompt", "Review @note.ts", {
files: [
{
data: "",
mime: "text/plain",
name: "note.ts",
source: { type: "uri", uri: "file:///tmp/note.ts" },
mention: { start: 7, end: 15, text: "@note.ts" },
},
],
agents: [],
}),
],
cursor: {},
}),
)
spyOn(client.session, "get").mockImplementation(() =>
Promise.resolve({
id: "ses_1",
title: "Session",
projectID: "proj_1",
location: { directory: "/tmp" },
time: { created: 1, updated: 1 },
cost: 0,
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
model: { providerID: "openai", id: "gpt-5", variant: "high" },
}),
)
const out = await resolveCurrentSession(client, "ses_1")
expect(out.model).toEqual({ providerID: "openai", modelID: "gpt-5" })
expect(out.variant).toBe("high")
expect(out.turns[0]?.prompt).toEqual({
text: "Review @note.ts",
parts: [
{
type: "file",
url: "file:///tmp/note.ts",
mime: "text/plain",
filename: "note.ts",
source: {
type: "file",
path: "note.ts",
text: { start: 7, end: 15, value: "@note.ts" },
},
},
],
})
})
})