Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/opencode/test/fixture/plugins/config-mutator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Shared plugin fixture for tests that need to exercise real plugin.config()
// hook behavior without paying the cost of cold-transpiling a fresh .ts file
// per test. Each test references this same file URL via config.plugin with
// tuple options, so Bun's module cache short-circuits after the first import.
//
// Options schema:
// provider?: Record<string, unknown> // merged into cfg.provider
// enabled_providers?: string[] // replaces cfg.enabled_providers
// disabled_providers?: string[] // replaces cfg.disabled_providers
export default {
id: "test.config-mutator",
server: async (_input: unknown, options: Record<string, unknown> | undefined) => ({
async config(cfg: Record<string, unknown>) {
const opts = options ?? {}
if (opts.provider && typeof opts.provider === "object") {
cfg.provider = Object.assign({}, cfg.provider ?? {}, opts.provider)
}
if (Array.isArray(opts.enabled_providers)) {
cfg.enabled_providers = [...opts.enabled_providers]
}
if (Array.isArray(opts.disabled_providers)) {
cfg.disabled_providers = [...opts.disabled_providers]
}
},
}),
}
13 changes: 13 additions & 0 deletions packages/opencode/test/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,16 @@ void Log.init({
})

initProjectors()

// Warm up Bun's dynamic import pipeline by loading a representative plugin
// .ts fixture once during preload. Bun keeps its module cache across tests, so
// later plugin tests that reference this same file URL hit the cache instead
// of paying the cold TS-transpile cost (which is the main source of timeouts
// in plugin tests on Windows CI).
const warm = path.join(import.meta.dir, "fixture", "plugins", "config-mutator.ts")
const { pathToFileURL } = await import("url")
await import(pathToFileURL(warm).href).catch((err) => {
// Best-effort. If warm-up fails, tests can still run -- they will just pay
// the cold-import cost on their first plugin load.
console.warn("preload: plugin warm-up failed", err)
})
91 changes: 47 additions & 44 deletions packages/opencode/test/provider/provider.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { test, expect } from "bun:test"
import { mkdir, unlink } from "fs/promises"
import { unlink } from "fs/promises"
import path from "path"
import { pathToFileURL } from "url"
import { Global } from "../../src/global"
import { Filesystem } from "../../src/util"

Expand All @@ -16,6 +17,11 @@ import { AppRuntime } from "../../src/effect/app-runtime"
import { makeRuntime } from "../../src/effect/run-service"
import { Auth } from "../../src/auth" // kilocode_change

// Shared plugin fixture. Tests reference this same file so Bun's module cache
// short-circuits after the first import; individual test files no longer pay
// the cold TS-transpile cost.
const configMutator = pathToFileURL(path.join(import.meta.dir, "..", "fixture", "plugins", "config-mutator.ts")).href

const env = makeRuntime(Env.Service, Env.defaultLayer)
const set = (k: string, v: string) => env.runSync((svc) => svc.set(k, v))

Expand Down Expand Up @@ -2484,33 +2490,32 @@ 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"),
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://app.kilo.ai/config.json",
plugin: [
[
configMutator,
{
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 },
},
},
},
},
},
],
],
}),
)
},
})
Expand Down Expand Up @@ -2543,22 +2548,20 @@ test("plugin config providers persist after instance dispose", async () => {
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 Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://app.kilo.ai/config.json",
plugin: [
[
configMutator,
{
enabled_providers: ["anthropic", "openai"],
disabled_providers: ["openai"],
},
],
],
}),
)
},
})
Expand Down
Loading