opencode/packages/opencode/test/session/prompt-provider.test.ts
Kit Langton 6ea467b0ac test: add live effect helper mode
Default the shared effect test helper to support both test-clock and live execution, and switch the current opencode effect tests to the live path for real integration behavior.
2026-03-31 16:42:13 -04:00

140 lines
4.5 KiB
TypeScript

import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { NodeFileSystem } from "@effect/platform-node"
import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
import { Session } from "../../src/session"
import { SessionPrompt } from "../../src/session/prompt"
import { Log } from "../../src/util/log"
import { testEffect } from "../lib/effect"
import { provideTmpdirInstance } from "../fixture/fixture"
import { TestLLMServer } from "../lib/llm-server"
import { Layer } from "effect"
Log.init({ print: false })
const baseLayer = Layer.mergeAll(NodeFileSystem.layer, CrossSpawnSpawner.defaultLayer, TestLLMServer.layer)
const it = testEffect(baseLayer)
function makeConfig(url: string) {
return {
provider: {
test: {
name: "Test",
env: [],
npm: "@ai-sdk/openai-compatible",
models: {
"gpt-5-nano": {
id: "gpt-5-nano",
name: "Test Model",
attachment: false,
reasoning: false,
temperature: false,
tool_call: true,
release_date: "2025-01-01",
limit: { context: 100000, output: 10000 },
cost: { input: 0, output: 0 },
options: {},
},
},
options: {
apiKey: "test-key",
baseURL: url,
},
},
},
agent: {
build: {
model: "test/gpt-5-nano",
},
},
}
}
describe("session.prompt provider integration", () => {
it.live("loop returns assistant text through local provider", () =>
Effect.gen(function* () {
const llm = yield* TestLLMServer
return yield* provideTmpdirInstance(
() =>
Effect.gen(function* () {
const session = yield* Effect.promise(() =>
Session.create({
title: "Prompt provider",
permission: [{ permission: "*", pattern: "*", action: "allow" }],
}),
)
yield* Effect.promise(() =>
SessionPrompt.prompt({
sessionID: session.id,
agent: "build",
noReply: true,
parts: [{ type: "text", text: "hello" }],
}),
)
yield* llm.text("world")
const result = yield* Effect.promise(() => SessionPrompt.loop({ sessionID: session.id }))
expect(result.info.role).toBe("assistant")
expect(result.parts.some((part) => part.type === "text" && part.text === "world")).toBe(true)
expect(yield* llm.hits).toHaveLength(1)
expect(yield* llm.pending).toBe(0)
}),
{ git: true, config: makeConfig(llm.url) },
)
}),
)
it.live("loop consumes queued replies across turns", () =>
Effect.gen(function* () {
const llm = yield* TestLLMServer
return yield* provideTmpdirInstance(
() =>
Effect.gen(function* () {
const session = yield* Effect.promise(() =>
Session.create({
title: "Prompt provider turns",
permission: [{ permission: "*", pattern: "*", action: "allow" }],
}),
)
yield* Effect.promise(() =>
SessionPrompt.prompt({
sessionID: session.id,
agent: "build",
noReply: true,
parts: [{ type: "text", text: "hello one" }],
}),
)
yield* llm.text("world one")
const first = yield* Effect.promise(() => SessionPrompt.loop({ sessionID: session.id }))
expect(first.info.role).toBe("assistant")
expect(first.parts.some((part) => part.type === "text" && part.text === "world one")).toBe(true)
yield* Effect.promise(() =>
SessionPrompt.prompt({
sessionID: session.id,
agent: "build",
noReply: true,
parts: [{ type: "text", text: "hello two" }],
}),
)
yield* llm.text("world two")
const second = yield* Effect.promise(() => SessionPrompt.loop({ sessionID: session.id }))
expect(second.info.role).toBe("assistant")
expect(second.parts.some((part) => part.type === "text" && part.text === "world two")).toBe(true)
expect(yield* llm.hits).toHaveLength(2)
expect(yield* llm.pending).toBe(0)
}),
{ git: true, config: makeConfig(llm.url) },
)
}),
)
})