Download packages/core/test/plugin/provider-opencode.test.ts from SaylorTwift/opencode: direct link, hf CLI and curl.
- Browser
- Download file 19.4 kB
-
https://huggingface.co/SaylorTwift/opencode/resolve/main/packages/core/test/plugin/provider-opencode.test.ts
- Command line
-
hf download hf://SaylorTwift/opencode/packages/core/test/plugin/provider-opencode.test.ts
-
curl -L -o provider-opencode.test.ts https://huggingface.co/SaylorTwift/opencode/resolve/main/packages/core/test/plugin/provider-opencode.test.ts
19.4 kB
| import { describe, expect } from "bun:test" | |
| import { Effect } from "effect" | |
| import { HttpClient, HttpClientResponse } from "effect/unstable/http" | |
| import { Catalog } from "@opencode-ai/core/catalog" | |
| import { Credential } from "@opencode-ai/core/credential" | |
| import { EventV2 } from "@opencode-ai/core/event" | |
| import { Integration } from "@opencode-ai/core/integration" | |
| import { ModelV2 } from "@opencode-ai/core/model" | |
| import { PluginV2 } from "@opencode-ai/core/plugin" | |
| import { PluginHost } from "@opencode-ai/core/plugin/host" | |
| import { OpencodePlugin } from "@opencode-ai/core/plugin/provider/opencode" | |
| import { ProviderV2 } from "@opencode-ai/core/provider" | |
| import { testEffect } from "../lib/effect" | |
| import { PluginTestLayer } from "./fixture" | |
| const it = testEffect(PluginTestLayer) | |
| const addPlugin = Effect.fn(function* (http?: HttpClient.HttpClient) { | |
| const plugin = yield* PluginV2.Service | |
| const host = yield* PluginHost.make(plugin) | |
| const events = yield* EventV2.Service | |
| const integration = yield* Integration.Service | |
| const client = yield* HttpClient.HttpClient | |
| yield* OpencodePlugin.effect(host).pipe( | |
| Effect.provideService(EventV2.Service, events), | |
| Effect.provideService(Integration.Service, integration), | |
| Effect.provideService(HttpClient.HttpClient, http ?? client), | |
| ) | |
| }) | |
| function required<T>(value: T | undefined): T { | |
| if (value === undefined) throw new Error("Expected value") | |
| return value | |
| } | |
| function eventually<A>( | |
| effect: Effect.Effect<A>, | |
| predicate: (value: A) => boolean, | |
| remaining = 1000, | |
| ): Effect.Effect<A, Error> { | |
| return Effect.gen(function* () { | |
| const value = yield* effect | |
| if (predicate(value)) return value | |
| if (remaining === 0) return yield* Effect.fail(new Error("Timed out waiting for value")) | |
| yield* Effect.promise(() => Bun.sleep(1)) | |
| return yield* eventually(effect, predicate, remaining - 1) | |
| }) | |
| } | |
| function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () => Effect.Effect<A, E, R>) { | |
| return Effect.acquireUseRelease( | |
| Effect.sync(() => { | |
| const previous = Object.fromEntries(Object.keys(vars).map((key) => [key, process.env[key]])) | |
| Object.entries(vars).forEach(([key, value]) => { | |
| if (value === undefined) delete process.env[key] | |
| else process.env[key] = value | |
| }) | |
| return previous | |
| }), | |
| effect, | |
| (previous) => | |
| Effect.sync(() => | |
| Object.entries(previous).forEach(([key, value]) => { | |
| if (value === undefined) delete process.env[key] | |
| else process.env[key] = value | |
| }), | |
| ), | |
| ) | |
| } | |
| const cost = (input: number, output = 0) => [{ input, output, cache: { read: 0, write: 0 } }] | |
| describe("OpencodePlugin", () => { | |
| it.effect("registers account and service account methods", () => | |
| Effect.gen(function* () { | |
| yield* addPlugin() | |
| expect((yield* (yield* Integration.Service).get(Integration.ID.make("opencode")))?.methods).toEqual([ | |
| { | |
| id: Integration.MethodID.make("device"), | |
| type: "oauth", | |
| label: "OpenCode Console account", | |
| }, | |
| { type: "key", label: "API key (service account)" }, | |
| ]) | |
| }), | |
| ) | |
| it.effect("resolves origin-rooted device verification URLs", () => | |
| Effect.gen(function* () { | |
| const http = HttpClient.make((request) => | |
| Effect.succeed( | |
| HttpClientResponse.fromWeb( | |
| request, | |
| Response.json({ | |
| device_code: "device", | |
| user_code: "user", | |
| verification_uri_complete: "/console/device?user_code=user&client_id=opencode-cli", | |
| expires_in: 60, | |
| interval: 60, | |
| }), | |
| ), | |
| ), | |
| ) | |
| yield* addPlugin(http) | |
| const integration = yield* Integration.Service | |
| const attempt = yield* integration.connection.oauth({ | |
| integrationID: Integration.ID.make("opencode"), | |
| methodID: Integration.MethodID.make("device"), | |
| inputs: {}, | |
| }) | |
| expect(attempt.url).toBe("https://opencode.ai/console/device?user_code=user&client_id=opencode-cli") | |
| }), | |
| ) | |
| it.effect("rejects malformed device verification URLs", () => | |
| Effect.gen(function* () { | |
| const http = HttpClient.make((request) => | |
| Effect.succeed( | |
| HttpClientResponse.fromWeb( | |
| request, | |
| Response.json({ | |
| device_code: "device", | |
| user_code: "user", | |
| verification_uri_complete: "http://[::1", | |
| expires_in: 60, | |
| interval: 60, | |
| }), | |
| ), | |
| ), | |
| ) | |
| yield* addPlugin(http) | |
| const integration = yield* Integration.Service | |
| const error = yield* integration.connection | |
| .oauth({ | |
| integrationID: Integration.ID.make("opencode"), | |
| methodID: Integration.MethodID.make("device"), | |
| inputs: {}, | |
| }) | |
| .pipe(Effect.flip) | |
| expect(error).toBeInstanceOf(Integration.AuthorizationError) | |
| expect(String(error.cause)).toContain("Invalid device verification URL") | |
| }), | |
| ) | |
| it.live("loads providers and models from the connected OpenCode server", () => | |
| Effect.acquireUseRelease( | |
| Effect.sync(() => { | |
| const authorization: Array<string | null> = [] | |
| const gate = Promise.withResolvers<void>() | |
| return { | |
| authorization, | |
| release: gate.resolve, | |
| server: Bun.serve({ | |
| port: 0, | |
| fetch: async (request) => { | |
| await gate.promise | |
| authorization.push(request.headers.get("authorization")) | |
| const origin = new URL(request.url).origin | |
| return Response.json({ | |
| config: { | |
| enterprise: { url: origin }, | |
| provider: { | |
| remote: { | |
| name: "Remote", | |
| npm: "@ai-sdk/openai-compatible", | |
| api: `${origin}/v1`, | |
| env: ["REMOTE_API_KEY"], | |
| options: { | |
| apiKey: "{env:REMOTE_API_KEY}", | |
| headers: { "x-org-id": "org" }, | |
| custom: "value", | |
| }, | |
| models: { | |
| model: { | |
| name: "Remote Model", | |
| family: "remote", | |
| release_date: "2026-01-02", | |
| tool_call: true, | |
| modalities: { input: ["text", "image"], output: ["text"] }, | |
| options: { apiKey: "model-secret", temperature: 0.5 }, | |
| variants: { high: { apiKey: "variant-secret", temperature: 0.2 } }, | |
| cost: { input: 1, output: 2, cache_read: 0.1 }, | |
| limit: { context: 1000, output: 100 }, | |
| }, | |
| disabled: { name: "Disabled", status: "deprecated" }, | |
| }, | |
| }, | |
| }, | |
| }, | |
| }) | |
| }, | |
| }), | |
| } | |
| }), | |
| ({ authorization, release, server }) => | |
| Effect.gen(function* () { | |
| const credentials = yield* Credential.Service | |
| const catalog = yield* Catalog.Service | |
| yield* catalog.transform((draft) => { | |
| draft.provider.update(ProviderV2.ID.make("remote"), () => {}) | |
| draft.model.update(ProviderV2.ID.make("remote"), ModelV2.ID.make("stale"), () => {}) | |
| }) | |
| yield* credentials.create({ | |
| integrationID: Integration.ID.make("opencode"), | |
| value: Credential.Key.make({ | |
| type: "key", | |
| key: "secret", | |
| metadata: { server: server.url.origin }, | |
| }), | |
| }) | |
| yield* addPlugin() | |
| expect(authorization).toEqual([]) | |
| release() | |
| const provider = required( | |
| yield* eventually( | |
| catalog.provider.get(ProviderV2.ID.make("remote")), | |
| (item) => item?.integrationID === Integration.ID.make("opencode"), | |
| ), | |
| ) | |
| expect(provider).toMatchObject({ | |
| name: "Remote", | |
| integrationID: "opencode", | |
| api: { | |
| type: "aisdk", | |
| package: "@ai-sdk/openai-compatible", | |
| url: `${server.url.origin}/v1`, | |
| }, | |
| }) | |
| expect(provider.request).toEqual({ headers: { "x-org-id": "org" }, body: { custom: "value" } }) | |
| expect(yield* (yield* Integration.Service).get(Integration.ID.make("remote"))).toBeUndefined() | |
| const model = required(yield* catalog.model.get(ProviderV2.ID.make("remote"), ModelV2.ID.make("model"))) | |
| expect(model).toMatchObject({ | |
| name: "Remote Model", | |
| family: "remote", | |
| capabilities: { tools: true, input: ["text", "image"], output: ["text"] }, | |
| cost: [{ input: 1, output: 2, cache: { read: 0.1, write: 0 } }], | |
| limit: { context: 1000, output: 100 }, | |
| }) | |
| expect(model.request.body).toEqual({ custom: "value", temperature: 0.5 }) | |
| expect(model.variants).toEqual([ | |
| { | |
| id: ModelV2.VariantID.make("high"), | |
| headers: {}, | |
| body: { temperature: 0.2 }, | |
| }, | |
| ]) | |
| expect( | |
| required(yield* catalog.model.get(ProviderV2.ID.make("remote"), ModelV2.ID.make("disabled"))).enabled, | |
| ).toBe(false) | |
| expect(yield* catalog.model.get(ProviderV2.ID.make("remote"), ModelV2.ID.make("stale"))).toBeDefined() | |
| expect(authorization).toContain("Bearer secret") | |
| }), | |
| ({ server }) => Effect.promise(() => server.stop(true)), | |
| ), | |
| ) | |
| it.effect("uses a public key and disables paid models without credentials", () => | |
| withEnv({ OPENCODE_API_KEY: undefined }, () => | |
| Effect.gen(function* () { | |
| const catalog = yield* Catalog.Service | |
| yield* catalog.transform((catalog) => { | |
| const provider = ProviderV2.Info.make({ | |
| ...ProviderV2.Info.empty(ProviderV2.ID.opencode), | |
| api: { type: "aisdk", package: "test-provider" }, | |
| }) | |
| const model = ModelV2.Info.make({ | |
| ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")), | |
| api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" }, | |
| cost: cost(1), | |
| }) | |
| catalog.provider.update(provider.id, () => {}) | |
| catalog.model.update(provider.id, model.id, (draft) => { | |
| draft.cost = [...model.cost] | |
| }) | |
| }) | |
| yield* addPlugin() | |
| expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBe("public") | |
| expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(false) | |
| }), | |
| ), | |
| ) | |
| it.effect("keeps free models without credentials", () => | |
| withEnv({ OPENCODE_API_KEY: undefined }, () => | |
| Effect.gen(function* () { | |
| const catalog = yield* Catalog.Service | |
| yield* catalog.transform((catalog) => { | |
| const provider = ProviderV2.Info.make({ | |
| ...ProviderV2.Info.empty(ProviderV2.ID.opencode), | |
| api: { type: "aisdk", package: "test-provider" }, | |
| }) | |
| const model = ModelV2.Info.make({ | |
| ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("free")), | |
| api: { id: ModelV2.ID.make("free"), type: "aisdk", package: "test-provider" }, | |
| cost: cost(0), | |
| }) | |
| catalog.provider.update(provider.id, () => {}) | |
| catalog.model.update(provider.id, model.id, (draft) => { | |
| draft.cost = [...model.cost] | |
| }) | |
| }) | |
| yield* addPlugin() | |
| expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBe("public") | |
| expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("free"))).enabled).toBe(true) | |
| }), | |
| ), | |
| ) | |
| it.effect("treats output-only cost as free without credentials", () => | |
| withEnv({ OPENCODE_API_KEY: undefined }, () => | |
| Effect.gen(function* () { | |
| const catalog = yield* Catalog.Service | |
| yield* catalog.transform((catalog) => { | |
| const provider = ProviderV2.Info.make({ | |
| ...ProviderV2.Info.empty(ProviderV2.ID.opencode), | |
| api: { type: "aisdk", package: "test-provider" }, | |
| }) | |
| const model = ModelV2.Info.make({ | |
| ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("output-only")), | |
| api: { id: ModelV2.ID.make("output-only"), type: "aisdk", package: "test-provider" }, | |
| cost: cost(0, 1), | |
| }) | |
| catalog.provider.update(provider.id, () => {}) | |
| catalog.model.update(provider.id, model.id, (draft) => { | |
| draft.cost = [...model.cost] | |
| }) | |
| }) | |
| yield* addPlugin() | |
| expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBe("public") | |
| expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("output-only"))).enabled).toBe( | |
| true, | |
| ) | |
| }), | |
| ), | |
| ) | |
| it.effect("uses OPENCODE_API_KEY as credentials", () => | |
| withEnv({ OPENCODE_API_KEY: "secret" }, () => | |
| Effect.gen(function* () { | |
| const catalog = yield* Catalog.Service | |
| yield* catalog.transform((catalog) => { | |
| const provider = ProviderV2.Info.make({ | |
| ...ProviderV2.Info.empty(ProviderV2.ID.opencode), | |
| api: { type: "aisdk", package: "test-provider" }, | |
| }) | |
| const model = ModelV2.Info.make({ | |
| ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")), | |
| api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" }, | |
| cost: cost(1), | |
| }) | |
| catalog.provider.update(provider.id, () => {}) | |
| catalog.model.update(provider.id, model.id, (draft) => { | |
| draft.cost = [...model.cost] | |
| }) | |
| }) | |
| yield* addPlugin() | |
| expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBeUndefined() | |
| expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true) | |
| }), | |
| ), | |
| ) | |
| it.effect("uses configured provider env vars as credentials", () => | |
| withEnv({ OPENCODE_API_KEY: undefined, CUSTOM_OPENCODE_API_KEY: "secret" }, () => | |
| Effect.gen(function* () { | |
| const catalog = yield* Catalog.Service | |
| const integrations = yield* Integration.Service | |
| yield* integrations.transform((editor) => { | |
| editor.method.update({ | |
| integrationID: Integration.ID.make("opencode"), | |
| method: { type: "env", names: ["CUSTOM_OPENCODE_API_KEY"] }, | |
| }) | |
| }) | |
| yield* catalog.transform((catalog) => { | |
| const provider = ProviderV2.Info.make({ | |
| ...ProviderV2.Info.empty(ProviderV2.ID.opencode), | |
| api: { type: "aisdk", package: "test-provider" }, | |
| }) | |
| const model = ModelV2.Info.make({ | |
| ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")), | |
| api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" }, | |
| cost: cost(1), | |
| }) | |
| catalog.provider.update(provider.id, () => {}) | |
| catalog.model.update(provider.id, model.id, (draft) => { | |
| draft.cost = [...model.cost] | |
| }) | |
| }) | |
| yield* addPlugin() | |
| expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBeUndefined() | |
| expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true) | |
| }), | |
| ), | |
| ) | |
| it.effect("uses configured apiKey as credentials", () => | |
| withEnv({ OPENCODE_API_KEY: undefined }, () => | |
| Effect.gen(function* () { | |
| const catalog = yield* Catalog.Service | |
| yield* catalog.transform((catalog) => { | |
| const provider = ProviderV2.Info.make({ | |
| ...ProviderV2.Info.empty(ProviderV2.ID.opencode), | |
| api: { type: "aisdk", package: "test-provider" }, | |
| request: { | |
| headers: {}, | |
| body: { apiKey: "configured" }, | |
| }, | |
| }) | |
| const model = ModelV2.Info.make({ | |
| ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")), | |
| api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" }, | |
| cost: cost(1), | |
| }) | |
| catalog.provider.update(provider.id, (draft) => { | |
| draft.request = provider.request | |
| }) | |
| catalog.model.update(provider.id, model.id, (draft) => { | |
| draft.cost = [...model.cost] | |
| }) | |
| }) | |
| yield* addPlugin() | |
| expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBe("configured") | |
| expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true) | |
| }), | |
| ), | |
| ) | |
| it.effect("ignores non-opencode providers and models", () => | |
| withEnv({ OPENCODE_API_KEY: undefined }, () => | |
| Effect.gen(function* () { | |
| const catalog = yield* Catalog.Service | |
| yield* catalog.transform((catalog) => { | |
| const provider = ProviderV2.Info.make({ | |
| ...ProviderV2.Info.empty(ProviderV2.ID.openai), | |
| api: { type: "aisdk", package: "test-provider" }, | |
| }) | |
| const model = ModelV2.Info.make({ | |
| ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")), | |
| api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" }, | |
| cost: cost(1), | |
| }) | |
| catalog.provider.update(provider.id, () => {}) | |
| catalog.model.update(provider.id, model.id, (draft) => { | |
| draft.cost = [...model.cost] | |
| }) | |
| }) | |
| yield* addPlugin() | |
| expect(required(yield* catalog.provider.get(ProviderV2.ID.openai)).request.body.apiKey).toBeUndefined() | |
| expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("paid"))).enabled).toBe(true) | |
| }), | |
| ), | |
| ) | |
| it.effect("prefers gpt-5-nano as the opencode small model", () => | |
| Effect.gen(function* () { | |
| const catalog = yield* Catalog.Service | |
| const providerID = ProviderV2.ID.opencode | |
| yield* catalog.transform((catalog) => { | |
| catalog.provider.update(providerID, () => {}) | |
| catalog.model.update(providerID, ModelV2.ID.make("cheap-mini"), (model) => { | |
| model.capabilities.input = ["text"] | |
| model.capabilities.output = ["text"] | |
| model.cost = [...cost(1, 1)] | |
| model.time.released = Date.now() | |
| }) | |
| catalog.model.update(providerID, ModelV2.ID.make("gpt-5-nano"), (model) => { | |
| model.capabilities.input = ["text"] | |
| model.capabilities.output = ["text"] | |
| model.cost = [...cost(10, 10)] | |
| model.time.released = Date.now() | |
| }) | |
| }) | |
| const selected = yield* catalog.model.small(providerID) | |
| expect(selected?.id).toBe(ModelV2.ID.make("gpt-5-nano")) | |
| }), | |
| ) | |
| }) | |