import { Npm } from "@opencode-ai/core/npm" import type { LanguageModelV3 } from "@ai-sdk/provider" import { expect } from "bun:test" import { Effect, Layer, Option } from "effect" import { Catalog } from "@opencode-ai/core/catalog" import { Integration } from "@opencode-ai/core/integration" import { Credential } from "@opencode-ai/core/credential" import { EventV2 } from "@opencode-ai/core/event" import { Location } from "@opencode-ai/core/location" import { ModelV2 } from "@opencode-ai/core/model" import { PluginV2 } from "@opencode-ai/core/plugin" import { ProviderV2 } from "@opencode-ai/core/provider" import { AbsolutePath } from "@opencode-ai/core/schema" import { location } from "../fixture/location" import { testEffect } from "../lib/effect" export const fixtureProvider = new URL("./fixtures/provider-factory.ts", import.meta.url).href const locationLayer = Layer.succeed( Location.Service, Location.Service.of(location({ directory: AbsolutePath.make("test") })), ) export const npmLayer = Layer.succeed( Npm.Service, Npm.Service.of({ add: () => Effect.succeed({ directory: "", entrypoint: Option.none() }), install: () => Effect.void, which: () => Effect.succeed(Option.none()), }), ) export const catalogLayer = Layer.succeed( Catalog.Service, Catalog.Service.of({ transform: () => Effect.die("unexpected catalog.transform"), provider: { get: () => Effect.die("unexpected provider.get"), all: () => Effect.succeed([]), available: () => Effect.succeed([]), }, model: { get: () => Effect.die("unexpected model.get"), all: () => Effect.succeed([]), available: () => Effect.succeed([]), default: () => Effect.succeed(Option.none()), small: () => Effect.succeed(Option.none()), }, }), ) const integrations = Integration.locationLayer.pipe( Layer.provide(EventV2.defaultLayer), Layer.provide( Layer.mock(Credential.Service)({ create: () => Effect.die("unexpected credential creation"), all: () => Effect.succeed([]), list: () => Effect.succeed([]), }), ), ) export const it = testEffect( Catalog.locationLayer.pipe( Layer.provideMerge(integrations), Layer.provideMerge( Layer.mock(Credential.Service)({ all: () => Effect.succeed([]), }), ), Layer.provideMerge(EventV2.defaultLayer), Layer.provideMerge(locationLayer), Layer.provideMerge(npmLayer), ), ) type ProviderInput = Partial> & { api?: ProviderV2.Api request?: ProviderV2.Request } type ModelInput = Partial> & { api?: (ProviderV2.Api & { id?: ModelV2.ID }) | { id: ModelV2.ID } request?: ModelV2.Info["request"] } export function provider(providerID: string, options?: ProviderInput) { return new ProviderV2.Info({ ...ProviderV2.Info.empty(ProviderV2.ID.make(providerID)), api: options?.api ?? { type: "aisdk", package: "test-provider", }, ...options, request: { headers: {}, body: {}, ...options?.request, }, }) } export function model(providerID: string, modelID: string, options?: ModelInput) { return new ModelV2.Info({ ...ModelV2.Info.empty(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)), ...options, api: options?.api && "type" in options.api ? { id: ModelV2.ID.make(modelID), ...options.api } : { id: ModelV2.ID.make(modelID), ...options?.api, type: "aisdk", package: "test-provider", }, request: { headers: {}, body: {}, ...options?.request, }, }) } export function withEnv(vars: Record, fx: () => Effect.Effect) { return Effect.acquireUseRelease( Effect.sync(() => { const previous = Object.fromEntries(Object.keys(vars).map((key) => [key, process.env[key]])) for (const [key, value] of Object.entries(vars)) { if (value === undefined) delete process.env[key] else process.env[key] = value } return previous }), () => fx(), (previous) => Effect.sync(() => { for (const [key, value] of Object.entries(previous)) { if (value === undefined) delete process.env[key] else process.env[key] = value } }), ) } export function fakeSelectorSdk(calls: string[]) { const make = (method: string) => (id: string) => { calls.push(`${method}:${id}`) return { modelId: id, provider: method, specificationVersion: "v3" } as unknown as LanguageModelV3 } return { responses: make("responses"), messages: make("messages"), chat: make("chat"), languageModel: make("languageModel"), } } export function expectPluginRegistered(ids: string[], id: string) { expect(ids).toContain(PluginV2.ID.make(id)) }