|
|
@@ -5,6 +5,7 @@ import path from "path"
|
|
|
import { disposeAllInstances, tmpdir } from "../fixture/fixture"
|
|
|
import { Global } from "@opencode-ai/core/global"
|
|
|
import { Instance } from "../../src/project/instance"
|
|
|
+import { WithInstance } from "../../src/project/with-instance"
|
|
|
import { Plugin } from "../../src/plugin/index"
|
|
|
import { ModelsDev } from "@/provider/models"
|
|
|
import { Provider } from "@/provider/provider"
|
|
|
@@ -80,12 +81,10 @@ test("provider loaded from env variable", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.anthropic]).toBeDefined()
|
|
|
// Provider should retain its connection source even if custom loaders
|
|
|
@@ -114,7 +113,7 @@ test("provider loaded from config with apiKey option", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -135,12 +134,10 @@ test("disabled_providers excludes provider", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.anthropic]).toBeUndefined()
|
|
|
},
|
|
|
@@ -159,13 +156,11 @@ test("enabled_providers restricts to only listed providers", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
+ fn: 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()
|
|
|
expect(providers[ProviderID.openai]).toBeUndefined()
|
|
|
@@ -189,12 +184,10 @@ test("model whitelist filters models for provider", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.anthropic]).toBeDefined()
|
|
|
const models = Object.keys(providers[ProviderID.anthropic].models)
|
|
|
@@ -220,12 +213,10 @@ test("model blacklist excludes specific models", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.anthropic]).toBeDefined()
|
|
|
const models = Object.keys(providers[ProviderID.anthropic].models)
|
|
|
@@ -255,12 +246,10 @@ test("custom model alias via config", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.anthropic]).toBeDefined()
|
|
|
expect(providers[ProviderID.anthropic].models["my-alias"]).toBeDefined()
|
|
|
@@ -301,7 +290,7 @@ test("custom provider with npm package", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -358,7 +347,7 @@ test("custom DeepSeek openai-compatible model defaults interleaved reasoning fie
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -392,12 +381,10 @@ test("env variable takes precedence, config merges options", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "env-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "env-api-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.anthropic]).toBeDefined()
|
|
|
// Config options should be merged
|
|
|
@@ -418,12 +405,10 @@ test("getModel returns model for valid provider/model", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const model = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
|
|
|
expect(model).toBeDefined()
|
|
|
expect(String(model.providerID)).toBe("anthropic")
|
|
|
@@ -445,12 +430,10 @@ test("getModel throws ModelNotFoundError for invalid model", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
expect(getModel(ProviderID.anthropic, ModelID.make("nonexistent-model"))).rejects.toThrow()
|
|
|
},
|
|
|
})
|
|
|
@@ -467,7 +450,7 @@ test("getModel throws ModelNotFoundError for invalid provider", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
expect(getModel(ProviderID.make("nonexistent-provider"), ModelID.make("some-model"))).rejects.toThrow()
|
|
|
@@ -498,12 +481,10 @@ test("defaultModel returns first available model when no config set", async () =
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const model = await defaultModel()
|
|
|
expect(model.providerID).toBeDefined()
|
|
|
expect(model.modelID).toBeDefined()
|
|
|
@@ -523,12 +504,10 @@ test("defaultModel respects config model setting", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const model = await defaultModel()
|
|
|
expect(String(model.providerID)).toBe("anthropic")
|
|
|
expect(String(model.modelID)).toBe("claude-sonnet-4-20250514")
|
|
|
@@ -565,7 +544,7 @@ test("provider with baseURL from config", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -603,7 +582,7 @@ test("model cost defaults to zero when not specified", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -638,12 +617,10 @@ test("model options are merged from existing model", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
|
|
expect(model.options.customOption).toBe("custom-value")
|
|
|
@@ -667,12 +644,10 @@ test("provider removed when all models filtered out", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.anthropic]).toBeUndefined()
|
|
|
},
|
|
|
@@ -690,12 +665,10 @@ test("closest finds model by partial match", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const result = await closest(ProviderID.anthropic, ["sonnet-4"])
|
|
|
expect(result).toBeDefined()
|
|
|
expect(String(result?.providerID)).toBe("anthropic")
|
|
|
@@ -715,7 +688,7 @@ test("closest returns undefined for nonexistent provider", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const result = await closest(ProviderID.make("nonexistent"), ["model"])
|
|
|
@@ -745,12 +718,10 @@ test("getModel uses realIdByKey for aliased models", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.anthropic].models["my-sonnet"]).toBeDefined()
|
|
|
|
|
|
@@ -791,7 +762,7 @@ test("provider api field sets model api.url", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -831,7 +802,7 @@ test("explicit baseURL overrides api field", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -860,12 +831,10 @@ test("model inherits properties from existing database model", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
|
|
expect(model.name).toBe("Custom Name for Sonnet")
|
|
|
@@ -888,12 +857,10 @@ test("disabled_providers prevents loading even with env var", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("OPENAI_API_KEY", "test-openai-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("OPENAI_API_KEY", "test-openai-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.openai]).toBeUndefined()
|
|
|
},
|
|
|
@@ -912,13 +879,11 @@ test("enabled_providers with empty array allows no providers", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
+ fn: 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)
|
|
|
},
|
|
|
@@ -942,12 +907,10 @@ test("whitelist and blacklist can be combined", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.anthropic]).toBeDefined()
|
|
|
const models = Object.keys(providers[ProviderID.anthropic].models)
|
|
|
@@ -984,7 +947,7 @@ test("model modalities default correctly", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -1027,7 +990,7 @@ test("model with custom cost values", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -1051,12 +1014,10 @@ test("getSmallModel returns appropriate small model", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const model = await getSmallModel(ProviderID.anthropic)
|
|
|
expect(model).toBeDefined()
|
|
|
expect(model?.id).toContain("haiku")
|
|
|
@@ -1076,12 +1037,10 @@ test("getSmallModel respects config small_model override", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const model = await getSmallModel(ProviderID.anthropic)
|
|
|
expect(model).toBeDefined()
|
|
|
expect(String(model?.providerID)).toBe("anthropic")
|
|
|
@@ -1124,13 +1083,11 @@ test("multiple providers can be configured simultaneously", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
+ fn: 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()
|
|
|
expect(providers[ProviderID.openai]).toBeDefined()
|
|
|
@@ -1169,7 +1126,7 @@ test("provider with custom npm package", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -1203,12 +1160,10 @@ test("model alias name defaults to alias key when id differs", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.anthropic].models["sonnet"].name).toBe("sonnet")
|
|
|
},
|
|
|
@@ -1243,12 +1198,10 @@ test("provider with multiple env var options only includes apiKey when single en
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("MULTI_ENV_KEY_1", "test-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("MULTI_ENV_KEY_1", "test-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.make("multi-env")]).toBeDefined()
|
|
|
// When multiple env options exist, key should NOT be auto-set
|
|
|
@@ -1285,12 +1238,10 @@ test("provider with single env var includes apiKey automatically", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("SINGLE_ENV_KEY", "my-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("SINGLE_ENV_KEY", "my-api-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.make("single-env")]).toBeDefined()
|
|
|
// Single env option should auto-set key
|
|
|
@@ -1322,12 +1273,10 @@ test("model cost overrides existing cost values", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
|
|
expect(model.cost.input).toBe(999)
|
|
|
@@ -1372,7 +1321,7 @@ test("completely new provider not in database can be configured", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -1401,14 +1350,12 @@ test("disabled_providers and enabled_providers interaction", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
+ fn: 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
|
|
|
expect(providers[ProviderID.anthropic]).toBeDefined()
|
|
|
@@ -1446,7 +1393,7 @@ test("model with tool_call false", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -1481,7 +1428,7 @@ test("model defaults tool_call to true when not specified", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -1520,7 +1467,7 @@ test("model headers are preserved", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -1559,13 +1506,11 @@ test("provider env fallback - second env var used if first missing", async () =>
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
+ fn: 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
|
|
|
expect(providers[ProviderID.make("fallback-env")]).toBeDefined()
|
|
|
@@ -1584,12 +1529,10 @@ test("getModel returns consistent results", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const model1 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
|
|
|
const model2 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
|
|
|
expect(model1.providerID).toEqual(model2.providerID)
|
|
|
@@ -1625,7 +1568,7 @@ test("provider name defaults to id when not in database", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -1645,12 +1588,10 @@ test("ModelNotFoundError includes suggestions for typos", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
try {
|
|
|
await getModel(ProviderID.anthropic, ModelID.make("claude-sonet-4")) // typo: sonet instead of sonnet
|
|
|
expect(true).toBe(false) // Should not reach here
|
|
|
@@ -1673,12 +1614,10 @@ test("ModelNotFoundError for provider includes suggestions", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
try {
|
|
|
await getModel(ProviderID.make("antropic"), ModelID.make("claude-sonnet-4")) // typo: antropic
|
|
|
expect(true).toBe(false) // Should not reach here
|
|
|
@@ -1701,7 +1640,7 @@ test("getProvider returns undefined for nonexistent provider", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const provider = await getProvider(ProviderID.make("nonexistent"))
|
|
|
@@ -1721,12 +1660,10 @@ test("getProvider returns provider info", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const provider = await getProvider(ProviderID.anthropic)
|
|
|
expect(provider).toBeDefined()
|
|
|
expect(String(provider?.id)).toBe("anthropic")
|
|
|
@@ -1745,12 +1682,10 @@ test("closest returns undefined when no partial match found", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const result = await closest(ProviderID.anthropic, ["nonexistent-xyz-model"])
|
|
|
expect(result).toBeUndefined()
|
|
|
},
|
|
|
@@ -1768,12 +1703,10 @@ test("closest checks multiple query terms in order", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
// First term won't match, second will
|
|
|
const result = await closest(ProviderID.anthropic, ["nonexistent", "haiku"])
|
|
|
expect(result).toBeDefined()
|
|
|
@@ -1808,7 +1741,7 @@ test("model limit defaults to zero when not specified", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -1840,12 +1773,10 @@ test("provider options are deeply merged", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
// Custom options should be merged
|
|
|
expect(providers[ProviderID.anthropic].options.timeout).toBe(30000)
|
|
|
@@ -1878,12 +1809,10 @@ test("custom model inherits npm package from models.dev provider config", async
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("OPENAI_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("OPENAI_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
const model = providers[ProviderID.openai].models["my-custom-model"]
|
|
|
expect(model).toBeDefined()
|
|
|
@@ -1913,12 +1842,10 @@ test("custom model inherits api.url from models.dev provider", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("OPENROUTER_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("OPENROUTER_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.openrouter]).toBeDefined()
|
|
|
|
|
|
@@ -2046,12 +1973,10 @@ test("model variants are generated for reasoning models", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
// Claude sonnet 4 has reasoning capability
|
|
|
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
|
|
@@ -2084,12 +2009,10 @@ test("model variants can be disabled via config", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
|
|
expect(model.variants).toBeDefined()
|
|
|
@@ -2127,12 +2050,10 @@ test("model variants can be customized via config", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
|
|
expect(model.variants!["high"]).toBeDefined()
|
|
|
@@ -2166,12 +2087,10 @@ test("disabled key is stripped from variant config", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
|
|
expect(model.variants!["max"]).toBeDefined()
|
|
|
@@ -2204,12 +2123,10 @@ test("all variants can be disabled via config", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
|
|
expect(model.variants).toBeDefined()
|
|
|
@@ -2242,12 +2159,10 @@ test("variant config merges with generated variants", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("ANTHROPIC_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
const model = providers[ProviderID.anthropic].models["claude-sonnet-4-20250514"]
|
|
|
expect(model.variants!["high"]).toBeDefined()
|
|
|
@@ -2280,12 +2195,10 @@ test("variants filtered in second pass for database models", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("OPENAI_API_KEY", "test-api-key")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("OPENAI_API_KEY", "test-api-key")
|
|
|
const providers = await list()
|
|
|
const model = providers[ProviderID.openai].models["gpt-5"]
|
|
|
expect(model.variants).toBeDefined()
|
|
|
@@ -2329,7 +2242,7 @@ test("custom model with variants enabled and disabled", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => {
|
|
|
const providers = await list()
|
|
|
@@ -2384,12 +2297,10 @@ test("Google Vertex: retains baseURL for custom proxy", async () => {
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
|
|
|
const providers = await list()
|
|
|
expect(providers[ProviderID.make("vertex-proxy")]).toBeDefined()
|
|
|
expect(providers[ProviderID.make("vertex-proxy")].options.baseURL).toBe("https://my-proxy.com/v1")
|
|
|
@@ -2429,12 +2340,10 @@ test("Google Vertex: supports OpenAI compatible models", async () => {
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
- set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
|
|
|
- }).pipe(Effect.asVoid),
|
|
|
fn: async () => {
|
|
|
+ set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds")
|
|
|
const providers = await list()
|
|
|
const model = providers[ProviderID.make("vertex-openai")].models["gpt-4"]
|
|
|
|
|
|
@@ -2455,14 +2364,12 @@ test("cloudflare-ai-gateway loads with env variables", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
+ fn: 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()
|
|
|
},
|
|
|
@@ -2487,14 +2394,12 @@ test("cloudflare-ai-gateway forwards config metadata options", async () => {
|
|
|
)
|
|
|
},
|
|
|
})
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
+ fn: 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()
|
|
|
expect(providers[ProviderID.make("cloudflare-ai-gateway")].options.metadata).toEqual({
|
|
|
@@ -2542,7 +2447,7 @@ test("plugin config providers persist after instance dispose", async () => {
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- const first = await Instance.provide({
|
|
|
+ const first = await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () =>
|
|
|
AppRuntime.runPromise(
|
|
|
@@ -2559,7 +2464,7 @@ test("plugin config providers persist after instance dispose", async () => {
|
|
|
|
|
|
await disposeAllInstances()
|
|
|
|
|
|
- const second = await Instance.provide({
|
|
|
+ const second = await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
fn: async () => list(),
|
|
|
})
|
|
|
@@ -2590,13 +2495,11 @@ test("plugin config enabled and disabled providers are honored", async () => {
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- await Instance.provide({
|
|
|
+ await WithInstance.provide({
|
|
|
directory: tmp.path,
|
|
|
- init: Effect.promise(async () => {
|
|
|
+ fn: 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()
|
|
|
expect(providers[ProviderID.openai]).toBeUndefined()
|
|
|
@@ -2616,7 +2519,7 @@ test("opencode loader keeps paid models when config apiKey is present", async ()
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- const none = await Instance.provide({
|
|
|
+ const none = await WithInstance.provide({
|
|
|
directory: base.path,
|
|
|
fn: async () => paid(await list()),
|
|
|
})
|
|
|
@@ -2639,7 +2542,7 @@ test("opencode loader keeps paid models when config apiKey is present", async ()
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- const keyedCount = await Instance.provide({
|
|
|
+ const keyedCount = await WithInstance.provide({
|
|
|
directory: keyed.path,
|
|
|
fn: async () => paid(await list()),
|
|
|
})
|
|
|
@@ -2660,7 +2563,7 @@ test("opencode loader keeps paid models when auth exists", async () => {
|
|
|
},
|
|
|
})
|
|
|
|
|
|
- const none = await Instance.provide({
|
|
|
+ const none = await WithInstance.provide({
|
|
|
directory: base.path,
|
|
|
fn: async () => paid(await list()),
|
|
|
})
|
|
|
@@ -2694,7 +2597,7 @@ test("opencode loader keeps paid models when auth exists", async () => {
|
|
|
}),
|
|
|
)
|
|
|
|
|
|
- const keyedCount = await Instance.provide({
|
|
|
+ const keyedCount = await WithInstance.provide({
|
|
|
directory: keyed.path,
|
|
|
fn: async () => paid(await list()),
|
|
|
})
|