Files

127 lines
4.7 KiB
TypeScript

import fs from "node:fs/promises"
import path from "node:path"
import { expect } from "bun:test"
import { Config } from "@opencode/core/config"
import { Generate } from "@opencode/core/generate"
import { makeLocationNode } from "@opencode/util/effect/app-node"
import { Effect, Layer, Predicate } from "effect"
import { tmpdir } from "../../core/test/fixture/tmpdir"
import { it } from "../../core/test/lib/effect"
import { ServerFetch } from "../src/fetch"
const generate = makeLocationNode({
service: Generate.Service,
layer: Layer.effect(
Generate.Service,
Effect.gen(function* () {
const config = yield* Config.Service
return Generate.Service.of({
text: () =>
config.entries().pipe(
Effect.map((entries) =>
JSON.stringify({
model: Config.latest(entries, "model"),
}),
),
),
image: () => Effect.die("unused"),
decisions: () => Effect.die("unused"),
})
}),
),
deps: [Config.node],
})
it.live("uses base configuration without depending on process.cwd()", () =>
Effect.gen(function* () {
const tmp = yield* Effect.acquireDisposable(Effect.promise(() => tmpdir("opencode-generate-endpoint-")))
const global = path.join(tmp.path, "global")
const project = path.join(tmp.path, "project")
yield* Effect.promise(() => Promise.all([fs.mkdir(global), fs.mkdir(project)]))
yield* Effect.promise(() =>
Promise.all([
fs.writeFile(path.join(global, "opencode.json"), JSON.stringify({ model: "base/default" })),
fs.writeFile(path.join(project, "opencode.json"), JSON.stringify({ model: "project/default" })),
]),
)
const handler = yield* ServerFetch.make(
{
database: { path: ":memory:" },
config: { directory: global },
fs: { filewatcher: false },
models: { fetch: false },
},
{ overrides: [Generate.node.replace(generate)] },
)
expect(global).not.toBe(process.cwd())
expect(yield* request(handler, new URL("http://opencode.local/api/experimental/generate/text"))).toEqual({
model: { providerID: "base", model: "default" },
})
const legacy = new URL("http://opencode.local/api/experimental/generate/text")
legacy.searchParams.set("location[directory]", project)
expect(yield* request(handler, legacy)).toEqual({
model: { providerID: "base", model: "default" },
})
const deprecatedPath = new URL("http://opencode.local/api/experimental/generate")
expect(yield* request(handler, deprecatedPath)).toEqual({
model: { providerID: "base", model: "default" },
})
}),
)
it.live("rejects unknown image and decision models and duplicate question names", () =>
Effect.gen(function* () {
const tmp = yield* Effect.acquireDisposable(Effect.promise(() => tmpdir("opencode-generate-media-")))
const handler = yield* ServerFetch.make({
database: { path: ":memory:" },
config: { directory: tmp.path },
fs: { filewatcher: false },
models: { fetch: false },
})
const model = { providerID: "nowhere", id: "nothing" }
const question = { type: "predicate", name: "refund", instructions: "Is this a refund request?" }
const post = (path: string, body: unknown) =>
Effect.promise(() =>
handler(
new Request(`http://opencode.local/api/experimental/generate/${path}`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
}),
),
)
for (const [path, body] of [
["image", { model, prompt: "a red square" }],
["decisions", { model, input: [{ type: "text", text: "I was charged twice." }], questions: [question] }],
] as const) {
const response = yield* post(path, body)
expect(response.status).toBe(404)
expect(yield* Effect.promise(() => response.json())).toMatchObject({ _tag: "ModelNotFoundError" })
}
const duplicate = yield* post("decisions", { model, input: [], questions: [question, question] })
expect(duplicate.status).toBe(400)
}),
)
function request(handler: (request: Request) => Promise<Response>, url: URL) {
return Effect.promise(() =>
handler(
new Request(url, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ prompt: "hello" }),
}),
).then(async (response) => {
expect(response.status).toBe(200)
const body: unknown = await response.json()
if (!Predicate.isObject(body) || !Predicate.isObject(body.data) || typeof body.data.text !== "string")
throw new Error("Expected a generate response")
const result: unknown = JSON.parse(body.data.text)
return result
}),
)
}