refactor(provider): stop custom loaders using facades (#20776)
Co-authored-by: luanweslley77 <213105503+luanweslley77@users.noreply.github.com>pull/20752/head^2
parent
650d0dbe54
commit
59ca4543d8
File diff suppressed because it is too large
Load Diff
|
|
@ -1,12 +1,22 @@
|
||||||
import { test, expect } from "bun:test"
|
import { test, expect } from "bun:test"
|
||||||
|
import { mkdir, unlink } from "fs/promises"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
||||||
import { tmpdir } from "../fixture/fixture"
|
import { tmpdir } from "../fixture/fixture"
|
||||||
|
import { Global } from "../../src/global"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
|
import { Plugin } from "../../src/plugin/index"
|
||||||
import { Provider } from "../../src/provider/provider"
|
import { Provider } from "../../src/provider/provider"
|
||||||
import { ProviderID, ModelID } from "../../src/provider/schema"
|
import { ProviderID, ModelID } from "../../src/provider/schema"
|
||||||
|
import { Filesystem } from "../../src/util/filesystem"
|
||||||
import { Env } from "../../src/env"
|
import { Env } from "../../src/env"
|
||||||
|
|
||||||
|
function paid(providers: Awaited<ReturnType<typeof Provider.list>>) {
|
||||||
|
const item = providers[ProviderID.make("opencode")]
|
||||||
|
expect(item).toBeDefined()
|
||||||
|
return Object.values(item.models).filter((model) => model.cost.input > 0).length
|
||||||
|
}
|
||||||
|
|
||||||
test("provider loaded from env variable", async () => {
|
test("provider loaded from env variable", async () => {
|
||||||
await using tmp = await tmpdir({
|
await using tmp = await tmpdir({
|
||||||
init: async (dir) => {
|
init: async (dir) => {
|
||||||
|
|
@ -2282,3 +2292,203 @@ test("cloudflare-ai-gateway forwards config metadata options", async () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("plugin config providers persist after instance dispose", async () => {
|
||||||
|
await using tmp = await tmpdir({
|
||||||
|
init: async (dir) => {
|
||||||
|
const root = path.join(dir, ".opencode", "plugin")
|
||||||
|
await mkdir(root, { recursive: true })
|
||||||
|
await Bun.write(
|
||||||
|
path.join(root, "demo-provider.ts"),
|
||||||
|
[
|
||||||
|
"export default {",
|
||||||
|
' id: "demo.plugin-provider",',
|
||||||
|
" server: async () => ({",
|
||||||
|
" async config(cfg) {",
|
||||||
|
" cfg.provider ??= {}",
|
||||||
|
" cfg.provider.demo = {",
|
||||||
|
' name: "Demo Provider",',
|
||||||
|
' npm: "@ai-sdk/openai-compatible",',
|
||||||
|
' api: "https://example.com/v1",',
|
||||||
|
" models: {",
|
||||||
|
" chat: {",
|
||||||
|
' name: "Demo Chat",',
|
||||||
|
" tool_call: true,",
|
||||||
|
" limit: { context: 128000, output: 4096 },",
|
||||||
|
" },",
|
||||||
|
" },",
|
||||||
|
" }",
|
||||||
|
" },",
|
||||||
|
" }),",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const first = await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => {
|
||||||
|
await Plugin.init()
|
||||||
|
return Provider.list()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(first[ProviderID.make("demo")]).toBeDefined()
|
||||||
|
expect(first[ProviderID.make("demo")].models[ModelID.make("chat")]).toBeDefined()
|
||||||
|
|
||||||
|
await Instance.disposeAll()
|
||||||
|
|
||||||
|
const second = await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
fn: async () => Provider.list(),
|
||||||
|
})
|
||||||
|
expect(second[ProviderID.make("demo")]).toBeDefined()
|
||||||
|
expect(second[ProviderID.make("demo")].models[ModelID.make("chat")]).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("plugin config enabled and disabled providers are honored", async () => {
|
||||||
|
await using tmp = await tmpdir({
|
||||||
|
init: async (dir) => {
|
||||||
|
const root = path.join(dir, ".opencode", "plugin")
|
||||||
|
await mkdir(root, { recursive: true })
|
||||||
|
await Bun.write(
|
||||||
|
path.join(root, "provider-filter.ts"),
|
||||||
|
[
|
||||||
|
"export default {",
|
||||||
|
' id: "demo.provider-filter",',
|
||||||
|
" server: async () => ({",
|
||||||
|
" async config(cfg) {",
|
||||||
|
' cfg.enabled_providers = ["anthropic", "openai"]',
|
||||||
|
' cfg.disabled_providers = ["openai"]',
|
||||||
|
" },",
|
||||||
|
" }),",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await Instance.provide({
|
||||||
|
directory: tmp.path,
|
||||||
|
init: async () => {
|
||||||
|
Env.set("ANTHROPIC_API_KEY", "test-anthropic-key")
|
||||||
|
Env.set("OPENAI_API_KEY", "test-openai-key")
|
||||||
|
},
|
||||||
|
fn: async () => {
|
||||||
|
const providers = await Provider.list()
|
||||||
|
expect(providers[ProviderID.anthropic]).toBeDefined()
|
||||||
|
expect(providers[ProviderID.openai]).toBeUndefined()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test("opencode loader keeps paid models when config apiKey is present", async () => {
|
||||||
|
await using base = await tmpdir({
|
||||||
|
init: async (dir) => {
|
||||||
|
await Bun.write(
|
||||||
|
path.join(dir, "opencode.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
$schema: "https://opencode.ai/config.json",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const none = await Instance.provide({
|
||||||
|
directory: base.path,
|
||||||
|
fn: async () => paid(await Provider.list()),
|
||||||
|
})
|
||||||
|
|
||||||
|
await using keyed = await tmpdir({
|
||||||
|
init: async (dir) => {
|
||||||
|
await Bun.write(
|
||||||
|
path.join(dir, "opencode.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
$schema: "https://opencode.ai/config.json",
|
||||||
|
provider: {
|
||||||
|
opencode: {
|
||||||
|
options: {
|
||||||
|
apiKey: "test-key",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const keyedCount = await Instance.provide({
|
||||||
|
directory: keyed.path,
|
||||||
|
fn: async () => paid(await Provider.list()),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(none).toBe(0)
|
||||||
|
expect(keyedCount).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("opencode loader keeps paid models when auth exists", async () => {
|
||||||
|
await using base = await tmpdir({
|
||||||
|
init: async (dir) => {
|
||||||
|
await Bun.write(
|
||||||
|
path.join(dir, "opencode.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
$schema: "https://opencode.ai/config.json",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const none = await Instance.provide({
|
||||||
|
directory: base.path,
|
||||||
|
fn: async () => paid(await Provider.list()),
|
||||||
|
})
|
||||||
|
|
||||||
|
await using keyed = await tmpdir({
|
||||||
|
init: async (dir) => {
|
||||||
|
await Bun.write(
|
||||||
|
path.join(dir, "opencode.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
$schema: "https://opencode.ai/config.json",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const authPath = path.join(Global.Path.data, "auth.json")
|
||||||
|
let prev: string | undefined
|
||||||
|
|
||||||
|
try {
|
||||||
|
prev = await Filesystem.readText(authPath)
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Filesystem.write(
|
||||||
|
authPath,
|
||||||
|
JSON.stringify({
|
||||||
|
opencode: {
|
||||||
|
type: "api",
|
||||||
|
key: "test-key",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const keyedCount = await Instance.provide({
|
||||||
|
directory: keyed.path,
|
||||||
|
fn: async () => paid(await Provider.list()),
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(none).toBe(0)
|
||||||
|
expect(keyedCount).toBeGreaterThan(0)
|
||||||
|
} finally {
|
||||||
|
if (prev !== undefined) {
|
||||||
|
await Filesystem.write(authPath, prev)
|
||||||
|
}
|
||||||
|
if (prev === undefined) {
|
||||||
|
try {
|
||||||
|
await unlink(authPath)
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue