opencode/packages/opencode/test/tool/task_status.test.ts

232 lines
6.1 KiB
TypeScript

import { describe, expect, test } from "bun:test"
import { tmpdir } from "../fixture/fixture"
import { Instance } from "../../src/project/instance"
import { Session } from "../../src/session"
import { Identifier } from "../../src/id/id"
import { SessionStatus } from "../../src/session/status"
import { TaskStatusTool } from "../../src/tool/task_status"
import { MessageV2 } from "../../src/session/message-v2"
const ctx = {
sessionID: "session_test",
messageID: "message_test",
callID: "call_test",
agent: "build",
abort: AbortSignal.any([]),
messages: [],
metadata: () => {},
ask: async () => {},
}
async function user(sessionID: string) {
await Session.updateMessage({
id: Identifier.ascending("message"),
sessionID,
role: "user",
time: {
created: Date.now(),
},
agent: "build",
model: {
providerID: "test-provider",
modelID: "test-model",
},
})
}
async function assistant(input: { sessionID: string; text: string; error?: string }) {
const msg = await Session.updateMessage({
id: Identifier.ascending("message"),
sessionID: input.sessionID,
role: "assistant",
time: {
created: Date.now(),
completed: Date.now(),
},
parentID: Identifier.ascending("message"),
modelID: "test-model",
providerID: "test-provider",
mode: "build",
agent: "build",
path: {
cwd: process.cwd(),
root: process.cwd(),
},
cost: 0,
tokens: {
input: 0,
output: 0,
reasoning: 0,
cache: {
read: 0,
write: 0,
},
},
finish: "stop",
...(input.error
? {
error: new MessageV2.APIError({
message: input.error,
isRetryable: false,
}).toObject(),
}
: {}),
})
await Session.updatePart({
id: Identifier.ascending("part"),
sessionID: input.sessionID,
messageID: msg.id,
type: "text",
text: input.text,
})
}
describe("tool.task_status", () => {
test("returns running while session status is busy", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const session = await Session.create({})
const tool = await TaskStatusTool.init()
SessionStatus.set(session.id, { type: "busy" })
const result = await tool.execute({ task_id: session.id }, ctx)
expect(result.output).toContain("state: running")
SessionStatus.set(session.id, { type: "idle" })
},
})
})
test("returns completed with final task output", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const session = await Session.create({})
const tool = await TaskStatusTool.init()
await assistant({
sessionID: session.id,
text: "all done",
})
const result = await tool.execute({ task_id: session.id }, ctx)
expect(result.output).toContain("state: completed")
expect(result.output).toContain("all done")
},
})
})
test("wait=true blocks until terminal status", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const session = await Session.create({})
const tool = await TaskStatusTool.init()
SessionStatus.set(session.id, { type: "busy" })
const transition = Bun.sleep(150).then(async () => {
SessionStatus.set(session.id, { type: "idle" })
await assistant({
sessionID: session.id,
text: "finished later",
})
})
const result = await tool.execute(
{
task_id: session.id,
wait: true,
timeout_ms: 4_000,
},
ctx,
)
await transition
expect(result.output).toContain("state: completed")
expect(result.output).toContain("finished later")
},
})
})
test("returns error when child run fails", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const session = await Session.create({})
const tool = await TaskStatusTool.init()
await assistant({
sessionID: session.id,
text: "",
error: "child failed",
})
const result = await tool.execute({ task_id: session.id }, ctx)
expect(result.output).toContain("state: error")
expect(result.output).toContain("child failed")
expect(result.metadata.state).toBe("error")
},
})
})
test("wait=true times out with timed_out metadata", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const session = await Session.create({})
const tool = await TaskStatusTool.init()
SessionStatus.set(session.id, { type: "busy" })
const result = await tool.execute(
{
task_id: session.id,
wait: true,
timeout_ms: 80,
},
ctx,
)
expect(result.output).toContain("Timed out after 80ms")
expect(result.metadata.timed_out).toBe(true)
expect(result.metadata.state).toBe("running")
SessionStatus.set(session.id, { type: "idle" })
},
})
})
test("returns running for resumed task with a newer user turn", async () => {
await using tmp = await tmpdir({ git: true })
await Instance.provide({
directory: tmp.path,
fn: async () => {
const session = await Session.create({})
const tool = await TaskStatusTool.init()
await user(session.id)
await assistant({
sessionID: session.id,
text: "old done",
})
await user(session.id)
const result = await tool.execute({ task_id: session.id }, ctx)
expect(result.output).toContain("state: running")
expect(result.output).toContain("Task is starting.")
},
})
})
})