mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-04 21:19:50 +00:00
129 lines
4.6 KiB
TypeScript
129 lines
4.6 KiB
TypeScript
import { describe, expect } from "bun:test"
|
|
import { Effect, Exit, Scope } from "effect"
|
|
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
|
import { Bus } from "@opencode-ai/core/bus"
|
|
import { KV } from "@opencode-ai/core/kv"
|
|
import { WebSearch } from "@opencode-ai/core/websearch"
|
|
import { testEffect } from "./lib/effect"
|
|
|
|
const it = testEffect(AppNodeBuilder.build(LayerNode.group([WebSearch.node, Bus.node, KV.node])))
|
|
|
|
const register = (id: string) =>
|
|
Effect.gen(function* () {
|
|
const websearch = yield* WebSearch.Service
|
|
const providerID = WebSearch.ID.make(id)
|
|
const calls: WebSearch.ProviderInput[] = []
|
|
yield* websearch.transform((draft) => {
|
|
draft.add({
|
|
id: providerID,
|
|
name: id.toUpperCase(),
|
|
execute: (input) =>
|
|
Effect.sync(() => {
|
|
calls.push(input)
|
|
return [
|
|
{
|
|
url: `https://${id}.example.com`,
|
|
title: input.query,
|
|
content: `${id}: ${input.query}`,
|
|
time: {},
|
|
},
|
|
]
|
|
}),
|
|
})
|
|
})
|
|
return { providerID, calls }
|
|
})
|
|
|
|
describe("WebSearch", () => {
|
|
it.effect("executes an explicit provider without changing the default", () =>
|
|
Effect.gen(function* () {
|
|
yield* register("exa")
|
|
const parallel = yield* register("parallel")
|
|
const websearch = yield* WebSearch.Service
|
|
|
|
expect(yield* websearch.query({ query: "effect", providerID: parallel.providerID })).toEqual(
|
|
new WebSearch.Response({
|
|
providerID: parallel.providerID,
|
|
results: [
|
|
{
|
|
url: "https://parallel.example.com",
|
|
title: "effect",
|
|
content: "parallel: effect",
|
|
time: {},
|
|
},
|
|
],
|
|
}),
|
|
)
|
|
expect((yield* websearch.query({ query: "default" }).pipe(Effect.flip))._tag).toBe("WebSearch.ProviderRequired")
|
|
expect(parallel.calls).toEqual([{ query: "effect" }])
|
|
}),
|
|
)
|
|
|
|
it.effect("requires a provider when no default is set", () =>
|
|
Effect.gen(function* () {
|
|
yield* register("exa")
|
|
yield* register("parallel")
|
|
const websearch = yield* WebSearch.Service
|
|
|
|
expect((yield* websearch.query({ query: "layers" }).pipe(Effect.flip))._tag).toBe("WebSearch.ProviderRequired")
|
|
}),
|
|
)
|
|
|
|
it.effect("uses the default set by a transform", () =>
|
|
Effect.gen(function* () {
|
|
yield* register("exa")
|
|
const parallel = yield* register("parallel")
|
|
const websearch = yield* WebSearch.Service
|
|
yield* websearch.transform((draft) => draft.default.set(parallel.providerID))
|
|
|
|
expect((yield* websearch.query({ query: "configured" })).providerID).toBe(parallel.providerID)
|
|
}),
|
|
)
|
|
|
|
it.effect("uses the provider stored in KV", () =>
|
|
Effect.gen(function* () {
|
|
yield* register("exa")
|
|
const parallel = yield* register("parallel")
|
|
const websearch = yield* WebSearch.Service
|
|
const kv = yield* KV.Service
|
|
yield* kv.set("websearch:provider", parallel.providerID)
|
|
|
|
expect((yield* websearch.query({ query: "stored" })).providerID).toBe(parallel.providerID)
|
|
yield* kv.remove("websearch:provider")
|
|
}),
|
|
)
|
|
|
|
it.effect("fails when web search is explicitly disabled", () =>
|
|
Effect.gen(function* () {
|
|
yield* register("exa")
|
|
const websearch = yield* WebSearch.Service
|
|
const kv = yield* KV.Service
|
|
yield* kv.set("websearch:provider", false)
|
|
|
|
expect((yield* websearch.query({ query: "disabled" }).pipe(Effect.flip))._tag).toBe("WebSearch.Disabled")
|
|
yield* kv.remove("websearch:provider")
|
|
}),
|
|
)
|
|
|
|
it.effect("falls back when the configured default is unavailable", () =>
|
|
Effect.gen(function* () {
|
|
yield* register("exa")
|
|
const websearch = yield* WebSearch.Service
|
|
yield* websearch.transform((draft) => draft.default.set(WebSearch.ID.make("missing")))
|
|
|
|
expect((yield* websearch.query({ query: "fallback" }).pipe(Effect.flip))._tag).toBe("WebSearch.ProviderRequired")
|
|
}),
|
|
)
|
|
|
|
it.effect("removes scoped provider registrations", () =>
|
|
Effect.gen(function* () {
|
|
const websearch = yield* WebSearch.Service
|
|
const scope = yield* Scope.fork(yield* Scope.Scope)
|
|
const provider = yield* register("temporary").pipe(Scope.provide(scope))
|
|
expect(yield* websearch.providers()).toContainEqual({ id: provider.providerID, name: "TEMPORARY" })
|
|
yield* Scope.close(scope, Exit.void)
|
|
expect(yield* websearch.providers()).not.toContainEqual({ id: provider.providerID, name: "TEMPORARY" })
|
|
}),
|
|
)
|
|
})
|