| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- import { describe, expect } from "bun:test"
- import { AppFileSystem } from "@opencode-ai/core/filesystem"
- import { Effect, Layer } from "effect"
- import { HttpClient, HttpClientRequest } from "effect/unstable/http"
- import { Provider } from "../../../src/provider/provider"
- import { SimulationFileSystem } from "../../../src/testing/simulation/filesystem"
- import { SimulationNetwork } from "../../../src/testing/simulation/network"
- import { SimulationProvider } from "../../../src/testing/simulation/provider"
- import { Simulation } from "../../../src/testing/simulation/service"
- import { testEffect } from "../../lib/effect"
- const fsLayer = SimulationFileSystem.layer({ root: "/opencode" })
- const networkLayer = SimulationNetwork.layer({ allowLoopback: false })
- const simulationLayer = Simulation.layer.pipe(Layer.provide(fsLayer), Layer.provide(networkLayer))
- const providerLayer = SimulationProvider.layer.pipe(Layer.provide(simulationLayer))
- const it = testEffect(Layer.mergeAll(fsLayer, networkLayer, simulationLayer, providerLayer))
- describe("Simulation", () => {
- it.effect("seeds files into the simulated filesystem", () =>
- Effect.gen(function* () {
- const simulation = yield* Simulation.Service
- const fs = yield* AppFileSystem.Service
- expect(yield* simulation.seedFilesystem({ files: { "opencode.json": "{}" } })).toEqual({
- files: ["opencode.json"],
- })
- expect(yield* fs.readFileString("/opencode/opencode.json")).toBe("{}")
- }),
- )
- it.effect("registers network responses through control state", () =>
- Effect.gen(function* () {
- const simulation = yield* Simulation.Service
- const http = yield* HttpClient.HttpClient
- expect(
- yield* simulation.registerNetwork({
- kind: "json",
- method: "GET",
- url: "https://example.com/data",
- body: { ok: true },
- }),
- ).toEqual({ registered: "https://example.com/data" })
- const response = yield* http.execute(HttpClientRequest.get("https://example.com/data"))
- expect(yield* response.json).toEqual({ ok: true })
- }),
- )
- it.effect("snapshots and resets simulation state", () =>
- Effect.gen(function* () {
- const simulation = yield* Simulation.Service
- const http = yield* HttpClient.HttpClient
- yield* simulation.seedFilesystem({ files: { "README.md": "hello" } })
- yield* simulation.registerNetwork({ kind: "text", url: "https://example.com/page", body: "hello" })
- const snapshot = yield* simulation.snapshot()
- expect(snapshot.files).toEqual(["README.md"])
- expect(snapshot.networkRegistrations).toEqual(["* https://example.com/page"])
- expect(snapshot.network.routes.some((route) => route.matcher === "https://example.com/page")).toBe(true)
- yield* simulation.reset()
- expect((yield* simulation.snapshot()).files).toEqual([])
- const exit = yield* http.execute(HttpClientRequest.get("https://example.com/page")).pipe(Effect.exit)
- expect(exit._tag).toBe("Failure")
- }),
- )
- it.effect("queues and consumes LLM scripts", () =>
- Effect.gen(function* () {
- const simulation = yield* Simulation.Service
- expect(
- yield* simulation.enqueueLLM({
- scripts: [{ steps: [[{ type: "text", content: "hello" }]], finish: "stop" }],
- }),
- ).toEqual({ queued: 1 })
- expect((yield* simulation.snapshot()).llmQueued).toBe(1)
- expect(yield* simulation.nextLLM()).toEqual({ steps: [[{ type: "text", content: "hello" }]], finish: "stop" })
- expect((yield* simulation.snapshot()).llmConsumed).toBe(1)
- }),
- )
- it.effect("simulation provider consumes queued text scripts", () =>
- Effect.gen(function* () {
- const simulation = yield* Simulation.Service
- const provider = yield* Provider.Service
- const model = yield* provider.defaultModel().pipe(Effect.flatMap((item) => provider.getModel(item.providerID, item.modelID)))
- const language = yield* provider.getLanguage(model)
- yield* simulation.enqueueLLM({ scripts: [{ steps: [[{ type: "text", content: "assistant text" }]] }] })
- const result = yield* Effect.promise(() => language.doGenerate({ prompt: [], abortSignal: undefined }))
- expect(result.content).toEqual([{ type: "text", text: "assistant text" }])
- expect((yield* simulation.snapshot()).llmConsumed).toBe(1)
- }),
- )
- it.effect("simulation provider returns a default response when no script is queued", () =>
- Effect.gen(function* () {
- const provider = yield* Provider.Service
- const model = yield* provider.defaultModel().pipe(Effect.flatMap((item) => provider.getModel(item.providerID, item.modelID)))
- const language = yield* provider.getLanguage(model)
- const result = yield* Effect.promise(() => language.doGenerate({ prompt: [], abortSignal: undefined }))
- expect(result.content).toEqual([{ type: "text", text: "Simulation mock response." }])
- }),
- )
- it.effect("simulation provider streams a default response when no script is queued", () =>
- Effect.gen(function* () {
- const provider = yield* Provider.Service
- const model = yield* provider.defaultModel().pipe(Effect.flatMap((item) => provider.getModel(item.providerID, item.modelID)))
- const language = yield* provider.getLanguage(model)
- const result = yield* Effect.promise(() => language.doStream({ prompt: [], abortSignal: undefined }))
- const reader = result.stream.getReader()
- const parts: unknown[] = []
- while (true) {
- const next = yield* Effect.promise(() => reader.read())
- if (next.done) break
- parts.push(next.value)
- }
- expect(parts).toContainEqual({ type: "text-delta", id: "simulation-text-1", delta: "Simulation mock response." })
- }),
- )
- it.effect("simulation provider streams queued script actions", () =>
- Effect.gen(function* () {
- const simulation = yield* Simulation.Service
- const provider = yield* Provider.Service
- const model = yield* provider.defaultModel().pipe(Effect.flatMap((item) => provider.getModel(item.providerID, item.modelID)))
- const language = yield* provider.getLanguage(model)
- yield* simulation.enqueueLLM({
- scripts: [
- {
- steps: [
- [
- { type: "thinking", content: "thinking" },
- { type: "text", content: "answer" },
- ],
- ],
- },
- ],
- })
- const result = yield* Effect.promise(() => language.doStream({ prompt: [], abortSignal: undefined }))
- const reader = result.stream.getReader()
- const parts: unknown[] = []
- while (true) {
- const next = yield* Effect.promise(() => reader.read())
- if (next.done) break
- parts.push(next.value)
- }
- expect(parts).toEqual([
- { type: "stream-start", warnings: [] },
- { type: "reasoning-start", id: "simulation-thinking-1" },
- { type: "reasoning-delta", id: "simulation-thinking-1", delta: "thinking" },
- { type: "reasoning-end", id: "simulation-thinking-1" },
- { type: "text-start", id: "simulation-text-2" },
- { type: "text-delta", id: "simulation-text-2", delta: "answer" },
- { type: "text-end", id: "simulation-text-2" },
- {
- type: "finish",
- finishReason: { unified: "stop", raw: undefined },
- usage: {
- inputTokens: { total: 0, noCache: 0, cacheRead: undefined, cacheWrite: undefined },
- outputTokens: { total: "thinkinganswer".length, text: "thinkinganswer".length, reasoning: undefined },
- raw: undefined,
- },
- },
- ])
- expect((yield* simulation.snapshot()).llmConsumed).toBe(1)
- }),
- )
- })
|