Compare commits

...

2 Commits

Author SHA1 Message Date
Kit Langton 601be56b98 test(llm): reproduce nested OpenAI stream error loss 2026-07-09 13:30:16 -04:00
Kit Langton 2ca34a7e63 test(server): reproduce partial startup catalog reads 2026-07-09 13:30:11 -04:00
3 changed files with 97 additions and 0 deletions
@@ -1471,6 +1471,29 @@ describe("OpenAI Responses route", () => {
}),
)
it.effect("surfaces error event details nested under a top-level error field", () =>
Effect.gen(function* () {
const response = yield* LLMClient.generate(request).pipe(
Effect.provide(
fixedResponse(
sseEvents({
type: "error",
error: { code: "context_length_exceeded", message: "prompt too long" },
}),
),
),
)
expect(response.events).toEqual([
{
type: "provider-error",
message: "context_length_exceeded: prompt too long",
classification: "context-overflow",
},
])
}),
)
it.effect("falls back to a stable default when both error and response are absent", () =>
Effect.gen(function* () {
const response = yield* LLMClient.generate(request).pipe(
@@ -0,0 +1,56 @@
import fs from "fs/promises"
import os from "os"
import path from "path"
import { afterEach, describe, expect, test } from "bun:test"
import { Layer } from "effect"
import { HttpRouter, HttpServer } from "effect/unstable/http"
import { createEmbeddedRoutes } from "../src/routes"
const directories: string[] = []
afterEach(async () => {
await Promise.all(directories.splice(0).map((directory) => fs.rm(directory, { recursive: true, force: true })))
})
describe("catalog readiness", () => {
test("catalog reads wait for initial plugin activation", async () => {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-server-catalog-"))
directories.push(directory)
await fs.writeFile(
path.join(directory, "opencode.json"),
JSON.stringify({
plugins: ["-*", path.join(import.meta.dir, "fixtures/delayed-catalog-plugin.ts")],
}),
)
const server = HttpRouter.toWebHandler(createEmbeddedRoutes().pipe(Layer.provide(HttpServer.layerServices)))
const request = (pathname: string) =>
server.handler(
new Request(`http://localhost${pathname}`, {
headers: { "x-opencode-directory": directory },
}),
)
try {
const [providers, models] = await Promise.all([request("/api/provider"), request("/api/model")])
expect(providers.status).toBe(200)
expect(models.status).toBe(200)
const initialProviders = (await providers.json()).data
const initialModels = (await models.json()).data
expect((await request("/api/model/default")).status).toBe(200)
const [readyProviders, readyModels] = await Promise.all([request("/api/provider"), request("/api/model")])
expect((await readyProviders.json()).data).toContainEqual(expect.objectContaining({ id: "delayed-provider" }))
expect((await readyModels.json()).data).toContainEqual(
expect.objectContaining({ providerID: "delayed-provider", id: "delayed-model" }),
)
expect(initialProviders).toContainEqual(expect.objectContaining({ id: "delayed-provider" }))
expect(initialModels).toContainEqual(
expect.objectContaining({ providerID: "delayed-provider", id: "delayed-model" }),
)
} finally {
await server.dispose()
}
})
})
+18
View File
@@ -0,0 +1,18 @@
import { Plugin } from "@opencode-ai/plugin/v2/effect"
import { Effect } from "effect"
export default Plugin.define({
id: "delayed-catalog",
effect: Effect.fn(function* (ctx) {
yield* Effect.sleep("300 millis")
yield* ctx.catalog.transform((catalog) => {
catalog.provider.update("delayed-provider", (provider) => {
provider.name = "Delayed Provider"
})
catalog.model.update("delayed-provider", "delayed-model", (model) => {
model.name = "Delayed Model"
model.enabled = true
})
})
}),
})