mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
resolve merge conflicts
This commit is contained in:
@@ -45,10 +45,10 @@ test("Bedrock: config region takes precedence over AWS_REGION env var", async ()
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("AWS_REGION", "us-east-1")
|
||||
set("AWS_PROFILE", "default")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.amazonBedrock]).toBeDefined()
|
||||
@@ -70,10 +70,10 @@ test("Bedrock: falls back to AWS_REGION env var when no config region", async ()
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("AWS_REGION", "eu-west-1")
|
||||
set("AWS_PROFILE", "default")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.amazonBedrock]).toBeDefined()
|
||||
@@ -125,11 +125,11 @@ test("Bedrock: loads when bearer token from auth.json is present", async () => {
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("AWS_PROFILE", "")
|
||||
set("AWS_ACCESS_KEY_ID", "")
|
||||
set("AWS_BEARER_TOKEN_BEDROCK", "")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.amazonBedrock]).toBeDefined()
|
||||
@@ -171,10 +171,10 @@ test("Bedrock: config profile takes precedence over AWS_PROFILE env var", async
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("AWS_PROFILE", "default")
|
||||
set("AWS_ACCESS_KEY_ID", "test-key-id")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.amazonBedrock]).toBeDefined()
|
||||
@@ -203,9 +203,9 @@ test("Bedrock: includes custom endpoint in options when specified", async () =>
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("AWS_PROFILE", "default")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.amazonBedrock]).toBeDefined()
|
||||
@@ -236,12 +236,12 @@ test("Bedrock: autoloads when AWS_WEB_IDENTITY_TOKEN_FILE is present", async ()
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("AWS_WEB_IDENTITY_TOKEN_FILE", "/var/run/secrets/eks.amazonaws.com/serviceaccount/token")
|
||||
set("AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/my-eks-role")
|
||||
set("AWS_PROFILE", "")
|
||||
set("AWS_ACCESS_KEY_ID", "")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.amazonBedrock]).toBeDefined()
|
||||
@@ -279,9 +279,9 @@ test("Bedrock: model with us. prefix should not be double-prefixed", async () =>
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("AWS_PROFILE", "default")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.amazonBedrock]).toBeDefined()
|
||||
@@ -316,9 +316,9 @@ test("Bedrock: model with global. prefix should not be prefixed", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("AWS_PROFILE", "default")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.amazonBedrock]).toBeDefined()
|
||||
@@ -352,9 +352,9 @@ test("Bedrock: model with eu. prefix should not be double-prefixed", async () =>
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("AWS_PROFILE", "default")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.amazonBedrock]).toBeDefined()
|
||||
@@ -388,9 +388,9 @@ test("Bedrock: model without prefix in US region should get us. prefix added", a
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("AWS_PROFILE", "default")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.amazonBedrock]).toBeDefined()
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
import { describe, expect, beforeAll, beforeEach, afterAll } from "bun:test"
|
||||
import { Effect, Layer, Ref } from "effect"
|
||||
import { HttpClient, HttpClientResponse } from "effect/unstable/http"
|
||||
import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { ModelsDev } from "../../src/provider/models"
|
||||
import { it } from "../lib/effect"
|
||||
import { rm, writeFile, utimes, mkdir } from "fs/promises"
|
||||
import path from "path"
|
||||
|
||||
// test/preload.ts pins KILO_MODELS_PATH to a fixture so other tests can
|
||||
// resolve providers without network. These tests need to drive the on-disk
|
||||
// cache themselves and silence the eager refresh fork. Save/restore around
|
||||
// the suite — never leak the mutation to subsequent test files in the same
|
||||
// bun process.
|
||||
const ORIGINAL_MODELS_PATH = Flag.KILO_MODELS_PATH
|
||||
const ORIGINAL_DISABLE_FETCH = Flag.KILO_DISABLE_MODELS_FETCH
|
||||
beforeAll(() => {
|
||||
Flag.KILO_MODELS_PATH = undefined
|
||||
Flag.KILO_DISABLE_MODELS_FETCH = true
|
||||
})
|
||||
afterAll(() => {
|
||||
Flag.KILO_MODELS_PATH = ORIGINAL_MODELS_PATH
|
||||
Flag.KILO_DISABLE_MODELS_FETCH = ORIGINAL_DISABLE_FETCH
|
||||
})
|
||||
|
||||
const cacheFile = path.join(Global.Path.cache, "models.json")
|
||||
|
||||
const fixture: Record<string, ModelsDev.Provider> = {
|
||||
acme: {
|
||||
id: "acme",
|
||||
name: "Acme",
|
||||
env: ["ACME_API_KEY"],
|
||||
models: {
|
||||
"acme-1": {
|
||||
id: "acme-1",
|
||||
name: "Acme One",
|
||||
release_date: "2026-01-01",
|
||||
attachment: false,
|
||||
reasoning: false,
|
||||
temperature: true,
|
||||
tool_call: true,
|
||||
limit: { context: 128000, output: 8192 },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const fixture2: Record<string, ModelsDev.Provider> = {
|
||||
beta: {
|
||||
id: "beta",
|
||||
name: "Beta",
|
||||
env: ["BETA_API_KEY"],
|
||||
models: {
|
||||
"beta-1": {
|
||||
id: "beta-1",
|
||||
name: "Beta One",
|
||||
release_date: "2026-02-01",
|
||||
attachment: false,
|
||||
reasoning: true,
|
||||
temperature: false,
|
||||
tool_call: false,
|
||||
limit: { context: 64000, output: 4096 },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
interface MockState {
|
||||
body: string
|
||||
status: number
|
||||
calls: Array<{ url: string }>
|
||||
}
|
||||
|
||||
const makeMockClient = (state: Ref.Ref<MockState>) =>
|
||||
HttpClient.make((request) =>
|
||||
Effect.gen(function* () {
|
||||
yield* Ref.update(state, (s) => ({ ...s, calls: [...s.calls, { url: request.url }] }))
|
||||
const s = yield* Ref.get(state)
|
||||
return HttpClientResponse.fromWeb(request, new Response(s.body, { status: s.status }))
|
||||
}),
|
||||
)
|
||||
|
||||
const buildLayer = (state: Ref.Ref<MockState>) =>
|
||||
// Layer.fresh is required: ModelsDev.layer is a module-level Layer constant,
|
||||
// and Effect.provide uses a process-global MemoMap by default — without fresh,
|
||||
// every test would reuse the cachedInvalidateWithTTL state from the first run.
|
||||
Layer.fresh(ModelsDev.layer).pipe(
|
||||
Layer.provide(Layer.succeed(HttpClient.HttpClient, makeMockClient(state))),
|
||||
Layer.provide(AppFileSystem.defaultLayer),
|
||||
)
|
||||
|
||||
const writeCache = (data: object, mtimeMs?: number) =>
|
||||
Effect.promise(async () => {
|
||||
await mkdir(Global.Path.cache, { recursive: true })
|
||||
await writeFile(cacheFile, JSON.stringify(data))
|
||||
if (mtimeMs !== undefined) {
|
||||
const t = mtimeMs / 1000
|
||||
await utimes(cacheFile, t, t)
|
||||
}
|
||||
})
|
||||
|
||||
const provided = <A, E>(state: Ref.Ref<MockState>, eff: Effect.Effect<A, E, ModelsDev.Service>) =>
|
||||
eff.pipe(Effect.provide(buildLayer(state)))
|
||||
|
||||
beforeEach(async () => {
|
||||
await rm(cacheFile, { force: true })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await rm(cacheFile, { force: true })
|
||||
})
|
||||
|
||||
const initialState: MockState = {
|
||||
body: JSON.stringify(fixture),
|
||||
status: 200,
|
||||
calls: [],
|
||||
}
|
||||
|
||||
describe("ModelsDev Service", () => {
|
||||
it.live("get() returns providers from disk when cache file exists", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* writeCache(fixture)
|
||||
const state = yield* Ref.make(initialState)
|
||||
const result = yield* provided(
|
||||
state,
|
||||
ModelsDev.Service.use((s) => s.get()),
|
||||
)
|
||||
expect(result).toEqual(fixture)
|
||||
const final = yield* Ref.get(state)
|
||||
expect(final.calls).toEqual([])
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("get() returns {} when disk empty and fetch disabled", () =>
|
||||
Effect.gen(function* () {
|
||||
const state = yield* Ref.make(initialState)
|
||||
const result = yield* provided(
|
||||
state,
|
||||
ModelsDev.Service.use((s) => s.get()),
|
||||
)
|
||||
expect(result).toEqual({})
|
||||
const final = yield* Ref.get(state)
|
||||
expect(final.calls).toEqual([])
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("get() is single-flight under concurrent calls", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* writeCache(fixture)
|
||||
const state = yield* Ref.make(initialState)
|
||||
const results = yield* provided(
|
||||
state,
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* ModelsDev.Service
|
||||
return yield* Effect.all([svc.get(), svc.get(), svc.get(), svc.get(), svc.get()], {
|
||||
concurrency: "unbounded",
|
||||
})
|
||||
}),
|
||||
)
|
||||
for (const result of results) expect(result).toEqual(fixture)
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("get() caches across calls (later disk writes are ignored until invalidate)", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* writeCache(fixture)
|
||||
const state = yield* Ref.make(initialState)
|
||||
const first = yield* provided(
|
||||
state,
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* ModelsDev.Service
|
||||
const a = yield* svc.get()
|
||||
// mutate disk between calls — cache should mask the change
|
||||
yield* writeCache(fixture2)
|
||||
const b = yield* svc.get()
|
||||
return { a, b }
|
||||
}),
|
||||
)
|
||||
expect(first.a).toEqual(fixture)
|
||||
expect(first.b).toEqual(fixture)
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("refresh(true) fetches via HttpClient and updates the cache", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* writeCache(fixture)
|
||||
const state = yield* Ref.make({ ...initialState, body: JSON.stringify(fixture2) })
|
||||
const result = yield* provided(
|
||||
state,
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* ModelsDev.Service
|
||||
const before = yield* svc.get()
|
||||
yield* svc.refresh(true)
|
||||
const after = yield* svc.get()
|
||||
return { before, after }
|
||||
}),
|
||||
)
|
||||
expect(result.before).toEqual(fixture)
|
||||
expect(result.after).toEqual(fixture2)
|
||||
const final = yield* Ref.get(state)
|
||||
expect(final.calls.length).toBe(1)
|
||||
expect(final.calls[0].url).toContain("/api.json")
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("refresh(false) skips fetch when on-disk file is fresh", () =>
|
||||
Effect.gen(function* () {
|
||||
// Fresh: mtime within the 5-minute TTL.
|
||||
yield* writeCache(fixture, Date.now() - 1000)
|
||||
const state = yield* Ref.make({ ...initialState, body: JSON.stringify(fixture2) })
|
||||
yield* provided(
|
||||
state,
|
||||
ModelsDev.Service.use((s) => s.refresh(false)),
|
||||
)
|
||||
const final = yield* Ref.get(state)
|
||||
expect(final.calls).toEqual([])
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("refresh(false) fetches when on-disk file is stale", () =>
|
||||
Effect.gen(function* () {
|
||||
// Stale: mtime 10 minutes ago, beyond the 5-minute TTL.
|
||||
yield* writeCache(fixture, Date.now() - 10 * 60 * 1000)
|
||||
const state = yield* Ref.make({ ...initialState, body: JSON.stringify(fixture2) })
|
||||
const after = yield* provided(
|
||||
state,
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* ModelsDev.Service
|
||||
yield* svc.refresh(false)
|
||||
return yield* svc.get()
|
||||
}),
|
||||
)
|
||||
const final = yield* Ref.get(state)
|
||||
expect(final.calls.length).toBe(1)
|
||||
expect(after).toEqual(fixture2)
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("refresh swallows HTTP errors and leaves cache intact", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* writeCache(fixture)
|
||||
const state = yield* Ref.make({ ...initialState, status: 500, body: "boom" })
|
||||
const result = yield* provided(
|
||||
state,
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* ModelsDev.Service
|
||||
yield* svc.refresh(true)
|
||||
return yield* svc.get()
|
||||
}),
|
||||
)
|
||||
expect(result).toEqual(fixture)
|
||||
// withTransientReadRetry retries 5xx, so calls may be > 1.
|
||||
const final = yield* Ref.get(state)
|
||||
expect(final.calls.length).toBeGreaterThanOrEqual(1)
|
||||
}),
|
||||
)
|
||||
})
|
||||
@@ -5,6 +5,7 @@ import path from "path"
|
||||
import { tmpdir } from "../fixture/fixture"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Instance } from "../../src/project/instance"
|
||||
import { InstanceStore } from "../../src/project/instance-store"
|
||||
import { Plugin } from "../../src/plugin/index"
|
||||
import { ModelsDev } from "@/provider/models"
|
||||
import { Provider } from "@/provider/provider"
|
||||
@@ -85,9 +86,9 @@ test("provider loaded from env variable", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.anthropic]).toBeDefined()
|
||||
@@ -122,9 +123,9 @@ test("provider OAuth auth overrides inherited env variable", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("OPENAI_API_KEY", "test-openai-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
const provider = providers[ProviderID.openai]
|
||||
@@ -184,9 +185,9 @@ test("disabled_providers excludes provider", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.anthropic]).toBeUndefined()
|
||||
@@ -208,10 +209,10 @@ test("enabled_providers restricts to only listed providers", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
set("OPENAI_API_KEY", "test-openai-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.anthropic]).toBeDefined()
|
||||
@@ -238,9 +239,9 @@ test("model whitelist filters models for provider", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.anthropic]).toBeDefined()
|
||||
@@ -269,9 +270,9 @@ test("model blacklist excludes specific models", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.anthropic]).toBeDefined()
|
||||
@@ -304,9 +305,9 @@ test("custom model alias via config", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.anthropic]).toBeDefined()
|
||||
@@ -441,9 +442,9 @@ test("env variable takes precedence, config merges options", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "env-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.anthropic]).toBeDefined()
|
||||
@@ -467,9 +468,9 @@ test("getModel returns model for valid provider/model", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const model = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
|
||||
expect(model).toBeDefined()
|
||||
@@ -494,9 +495,9 @@ test("getModel throws ModelNotFoundError for invalid model", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
expect(getModel(ProviderID.anthropic, ModelID.make("nonexistent-model"))).rejects.toThrow()
|
||||
},
|
||||
@@ -547,9 +548,9 @@ test("defaultModel returns first available model when no config set", async () =
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const model = await defaultModel()
|
||||
expect(model.providerID).toBeDefined()
|
||||
@@ -572,9 +573,9 @@ test("defaultModel respects config model setting", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const model = await defaultModel()
|
||||
expect(String(model.providerID)).toBe("anthropic")
|
||||
@@ -687,9 +688,9 @@ test("model options are merged from existing model", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
||||
@@ -716,9 +717,9 @@ test("provider removed when all models filtered out", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.anthropic]).toBeUndefined()
|
||||
@@ -739,9 +740,9 @@ test("closest finds model by partial match", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const result = await closest(ProviderID.anthropic, ["sonnet-4"])
|
||||
expect(result).toBeDefined()
|
||||
@@ -794,9 +795,9 @@ test("getModel uses realIdByKey for aliased models", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.anthropic].models["my-sonnet"]).toBeDefined()
|
||||
@@ -909,9 +910,9 @@ test("model inherits properties from existing database model", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
||||
@@ -937,9 +938,9 @@ test("disabled_providers prevents loading even with env var", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("OPENAI_API_KEY", "test-openai-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.openai]).toBeUndefined()
|
||||
@@ -961,10 +962,10 @@ test("enabled_providers with empty array allows no providers", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
set("OPENAI_API_KEY", "test-openai-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(Object.keys(providers).length).toBe(0)
|
||||
@@ -991,9 +992,9 @@ test("whitelist and blacklist can be combined", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.anthropic]).toBeDefined()
|
||||
@@ -1100,9 +1101,9 @@ test("getSmallModel returns appropriate small model", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const model = await getSmallModel(ProviderID.anthropic)
|
||||
expect(model).toBeDefined()
|
||||
@@ -1125,9 +1126,9 @@ test("getSmallModel respects config small_model override", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const model = await getSmallModel(ProviderID.anthropic)
|
||||
expect(model).toBeDefined()
|
||||
@@ -1173,10 +1174,10 @@ test("multiple providers can be configured simultaneously", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-anthropic-key")
|
||||
set("OPENAI_API_KEY", "test-openai-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.anthropic]).toBeDefined()
|
||||
@@ -1252,9 +1253,9 @@ test("model alias name defaults to alias key when id differs", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.anthropic].models["sonnet"].name).toBe("sonnet")
|
||||
@@ -1292,9 +1293,9 @@ test("provider with multiple env var options only includes apiKey when single en
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("MULTI_ENV_KEY_1", "test-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.make("multi-env")]).toBeDefined()
|
||||
@@ -1334,9 +1335,9 @@ test("provider with single env var includes apiKey automatically", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("SINGLE_ENV_KEY", "my-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.make("single-env")]).toBeDefined()
|
||||
@@ -1371,9 +1372,9 @@ test("model cost overrides existing cost values", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
||||
@@ -1450,11 +1451,11 @@ test("disabled_providers and enabled_providers interaction", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-anthropic")
|
||||
set("OPENAI_API_KEY", "test-openai")
|
||||
set("GOOGLE_GENERATIVE_AI_API_KEY", "test-google")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
// anthropic: in enabled, not in disabled = allowed
|
||||
@@ -1608,10 +1609,10 @@ test("provider env fallback - second env var used if first missing", async () =>
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
// Only set fallback, not primary
|
||||
set("FALLBACK_KEY", "fallback-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
// Provider should load because fallback env var is set
|
||||
@@ -1633,9 +1634,9 @@ test("getModel returns consistent results", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const model1 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
|
||||
const model2 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
|
||||
@@ -1694,9 +1695,9 @@ test("ModelNotFoundError includes suggestions for typos", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
try {
|
||||
await getModel(ProviderID.anthropic, ModelID.make("claude-sonet-4")) // typo: sonet instead of sonnet
|
||||
@@ -1722,9 +1723,9 @@ test("ModelNotFoundError for provider includes suggestions", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
try {
|
||||
await getModel(ProviderID.make("antropic"), ModelID.make("claude-sonnet-4")) // typo: antropic
|
||||
@@ -1770,9 +1771,9 @@ test("getProvider returns provider info", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const provider = await getProvider(ProviderID.anthropic)
|
||||
expect(provider).toBeDefined()
|
||||
@@ -1794,9 +1795,9 @@ test("closest returns undefined when no partial match found", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const result = await closest(ProviderID.anthropic, ["nonexistent-xyz-model"])
|
||||
expect(result).toBeUndefined()
|
||||
@@ -1817,9 +1818,9 @@ test("closest checks multiple query terms in order", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
// First term won't match, second will
|
||||
const result = await closest(ProviderID.anthropic, ["nonexistent", "haiku"])
|
||||
@@ -1889,9 +1890,9 @@ test("provider options are deeply merged", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
// Custom options should be merged
|
||||
@@ -1927,9 +1928,9 @@ test("custom model inherits npm package from models.dev provider config", async
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("OPENAI_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
const model = providers[ProviderID.openai].models["my-custom-model"]
|
||||
@@ -1962,9 +1963,9 @@ test("custom model inherits api.url from models.dev provider", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("OPENROUTER_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.openrouter]).toBeDefined()
|
||||
@@ -2095,9 +2096,9 @@ test("model variants are generated for reasoning models", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
// Claude sonnet 4 has reasoning capability
|
||||
@@ -2133,9 +2134,9 @@ test("model variants can be disabled via config", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
||||
@@ -2176,9 +2177,9 @@ test("model variants can be customized via config", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
||||
@@ -2215,9 +2216,9 @@ test("disabled key is stripped from variant config", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
||||
@@ -2253,9 +2254,9 @@ test("all variants can be disabled via config", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
||||
@@ -2291,9 +2292,9 @@ test("variant config merges with generated variants", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
||||
@@ -2329,9 +2330,9 @@ test("variants filtered in second pass for database models", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("OPENAI_API_KEY", "test-api-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
const model = providers[ProviderID.openai].models["gpt-5"]
|
||||
@@ -2433,9 +2434,9 @@ test("Google Vertex: retains baseURL for custom proxy", async () => {
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.make("vertex-proxy")]).toBeDefined()
|
||||
@@ -2478,9 +2479,9 @@ test("Google Vertex: supports OpenAI compatible models", async () => {
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
const model = providers[ProviderID.make("vertex-openai")].models["gpt-4"]
|
||||
@@ -2504,11 +2505,11 @@ test("cloudflare-ai-gateway loads with env variables", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("CLOUDFLARE_ACCOUNT_ID", "test-account")
|
||||
set("CLOUDFLARE_GATEWAY_ID", "test-gateway")
|
||||
set("CLOUDFLARE_API_TOKEN", "test-token")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.make("cloudflare-ai-gateway")]).toBeDefined()
|
||||
@@ -2536,11 +2537,11 @@ test("cloudflare-ai-gateway forwards config metadata options", async () => {
|
||||
})
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("CLOUDFLARE_ACCOUNT_ID", "test-account")
|
||||
set("CLOUDFLARE_GATEWAY_ID", "test-gateway")
|
||||
set("CLOUDFLARE_API_TOKEN", "test-token")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.make("cloudflare-ai-gateway")]).toBeDefined()
|
||||
@@ -2609,7 +2610,7 @@ test("plugin config providers persist after instance dispose", async () => {
|
||||
// kilocode_change start
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
fn: () => Instance.dispose(),
|
||||
fn: () => InstanceStore.disposeInstance(Instance.current),
|
||||
})
|
||||
// kilocode_change end
|
||||
|
||||
@@ -2646,10 +2647,10 @@ test("plugin config enabled and disabled providers are honored", async () => {
|
||||
|
||||
await Instance.provide({
|
||||
directory: tmp.path,
|
||||
init: async () => {
|
||||
init: Effect.promise(async () => {
|
||||
set("ANTHROPIC_API_KEY", "test-anthropic-key")
|
||||
set("OPENAI_API_KEY", "test-openai-key")
|
||||
},
|
||||
}).pipe(Effect.asVoid),
|
||||
fn: async () => {
|
||||
const providers = await list()
|
||||
expect(providers[ProviderID.anthropic]).toBeDefined()
|
||||
|
||||
Reference in New Issue
Block a user