mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-07 17:31:54 +00:00
Migrate test inits from Promise to Effect (#25377)
This commit is contained in:
parent
becf57ee6a
commit
d99dde6306
6 changed files with 199 additions and 160 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { Effect, Layer, ManagedRuntime } from "effect"
|
||||
import { Effect, Fiber, Layer, ManagedRuntime } from "effect"
|
||||
import * as Context from "effect/Context"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { LocalContext } from "@/util/local-context"
|
||||
|
|
@ -24,15 +24,20 @@ export function attachWith<A, E, R>(effect: Effect.Effect<A, E, R>, refs: Refs):
|
|||
}
|
||||
|
||||
export function attach<A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> {
|
||||
try {
|
||||
return attachWith(effect, {
|
||||
instance: Instance.current,
|
||||
workspace: WorkspaceContext.workspaceID,
|
||||
})
|
||||
} catch (err) {
|
||||
if (!(err instanceof LocalContext.NotFound)) throw err
|
||||
}
|
||||
return effect
|
||||
const workspace = WorkspaceContext.workspaceID
|
||||
const instance = (() => {
|
||||
try {
|
||||
return Instance.current
|
||||
} catch (err) {
|
||||
if (!(err instanceof LocalContext.NotFound)) throw err
|
||||
}
|
||||
})()
|
||||
if (instance && workspace !== undefined) return attachWith(effect, { instance, workspace })
|
||||
const fiber = Fiber.getCurrent()
|
||||
return attachWith(effect, {
|
||||
instance: instance ?? (fiber ? Context.getReferenceUnsafe(fiber.context, InstanceRef) : undefined),
|
||||
workspace: workspace ?? (fiber ? Context.getReferenceUnsafe(fiber.context, WorkspaceRef) : undefined),
|
||||
})
|
||||
}
|
||||
|
||||
export function makeRuntime<I, S, E>(service: Context.Service<I, S>, layer: Layer.Layer<I, E>) {
|
||||
|
|
|
|||
|
|
@ -1,42 +1,18 @@
|
|||
import { Effect } from "effect"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import * as Project from "./project"
|
||||
import { context, type InstanceContext } from "./instance-context"
|
||||
import { InstanceStore } from "./instance-store"
|
||||
|
||||
export type { InstanceContext } from "./instance-context"
|
||||
export type { LoadInput } from "./instance-store"
|
||||
|
||||
type LegacyLoadInput = {
|
||||
directory: string
|
||||
init?: () => Promise<unknown>
|
||||
project?: Project.Info
|
||||
worktree?: string
|
||||
}
|
||||
|
||||
// Promise-style legacy inits often read Instance.directory etc. from the ALS context.
|
||||
// The new Effect-typed init path doesn't bind ALS — it provides InstanceRef. To keep
|
||||
// legacy inits working without forcing every test to convert, bind ALS around the
|
||||
// Promise call here using the instance ctx that the store provides via InstanceRef.
|
||||
const liftLegacyInput = (input: LegacyLoadInput): InstanceStore.LoadInput => {
|
||||
const { init, ...rest } = input
|
||||
if (!init) return rest
|
||||
return {
|
||||
...rest,
|
||||
init: Effect.gen(function* () {
|
||||
const ctx = yield* InstanceRef
|
||||
yield* Effect.promise(() => (ctx ? context.provide(ctx, init) : init()))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
export const Instance = {
|
||||
load(input: LegacyLoadInput): Promise<InstanceContext> {
|
||||
return InstanceStore.runtime.runPromise((store) => store.load(liftLegacyInput(input)))
|
||||
load(input: InstanceStore.LoadInput): Promise<InstanceContext> {
|
||||
return InstanceStore.runtime.runPromise((store) => store.load(input))
|
||||
},
|
||||
async provide<R>(input: { directory: string; init?: () => Promise<unknown>; fn: () => R }): Promise<R> {
|
||||
return context.provide(await Instance.load({ directory: input.directory, init: input.init }), async () =>
|
||||
input.fn(),
|
||||
async provide<R>(input: { directory: string; init?: Effect.Effect<void>; fn: () => R }): Promise<R> {
|
||||
return context.provide(
|
||||
await Instance.load({ directory: input.directory, init: input.init }),
|
||||
async () => input.fn(),
|
||||
)
|
||||
},
|
||||
get current() {
|
||||
|
|
@ -69,8 +45,8 @@ export const Instance = {
|
|||
restore<R>(ctx: InstanceContext, fn: () => R): R {
|
||||
return context.provide(ctx, fn)
|
||||
},
|
||||
async reload(input: LegacyLoadInput) {
|
||||
return InstanceStore.runtime.runPromise((store) => store.reload(liftLegacyInput(input)))
|
||||
async reload(input: InstanceStore.LoadInput) {
|
||||
return InstanceStore.runtime.runPromise((store) => store.reload(input))
|
||||
},
|
||||
async dispose() {
|
||||
return InstanceStore.runtime.runPromise((store) => store.dispose(Instance.current))
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { expect } from "bun:test"
|
||||
import { Effect, Layer, Context } from "effect"
|
||||
import { InstanceRef } from "../../src/effect/instance-ref"
|
||||
import { makeRuntime } from "../../src/effect/run-service"
|
||||
import { ProjectID } from "../../src/project/schema"
|
||||
import { it } from "../lib/effect"
|
||||
|
||||
class Shared extends Context.Service<Shared, { readonly id: number }>()("@test/Shared") {}
|
||||
const testDirectory = "/tmp/opencode-test"
|
||||
|
||||
it.live("makeRuntime shares dependent layers through the shared memo map", () =>
|
||||
Effect.gen(function* () {
|
||||
|
|
@ -47,3 +50,40 @@ it.live("makeRuntime shares dependent layers through the shared memo map", () =>
|
|||
expect(n).toBe(1)
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("makeRuntime inherits InstanceRef from the current fiber", () =>
|
||||
Effect.gen(function* () {
|
||||
class NeedsInstance extends Context.Service<
|
||||
NeedsInstance,
|
||||
{ readonly directory: () => Effect.Effect<string | undefined> }
|
||||
>()("@test/NeedsInstance") {}
|
||||
|
||||
const runtime = makeRuntime(
|
||||
NeedsInstance,
|
||||
Layer.succeed(
|
||||
NeedsInstance,
|
||||
NeedsInstance.of({
|
||||
directory: () =>
|
||||
Effect.gen(function* () {
|
||||
return (yield* InstanceRef)?.directory
|
||||
}),
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const actual = yield* Effect.promise(() => runtime.runPromise((svc) => svc.directory()))
|
||||
|
||||
expect(actual).toBe(testDirectory)
|
||||
}).pipe(
|
||||
Effect.provideService(InstanceRef, {
|
||||
directory: testDirectory,
|
||||
worktree: testDirectory,
|
||||
project: {
|
||||
id: ProjectID.global,
|
||||
worktree: testDirectory,
|
||||
time: { created: 0, updated: 0 },
|
||||
sandboxes: [],
|
||||
},
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -252,4 +252,22 @@ describe("InstanceStore", () => {
|
|||
expect(() => Instance.current).toThrow()
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("does not install legacy ALS around Effect init", () =>
|
||||
Effect.gen(function* () {
|
||||
const dir = yield* tmpdirScoped()
|
||||
|
||||
const directory = yield* Effect.promise(() =>
|
||||
Instance.provide({
|
||||
directory: dir,
|
||||
init: Effect.sync(() => {
|
||||
expect(() => Instance.current).toThrow()
|
||||
}),
|
||||
fn: () => Instance.directory,
|
||||
}),
|
||||
)
|
||||
|
||||
expect(directory).toBe(dir)
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -82,9 +82,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()
|
||||
|
|
@ -137,9 +137,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()
|
||||
|
|
@ -161,10 +161,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()
|
||||
|
|
@ -191,9 +191,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()
|
||||
|
|
@ -222,9 +222,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()
|
||||
|
|
@ -257,9 +257,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()
|
||||
|
|
@ -394,9 +394,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()
|
||||
|
|
@ -420,9 +420,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()
|
||||
|
|
@ -447,9 +447,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()
|
||||
},
|
||||
|
|
@ -500,9 +500,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()
|
||||
|
|
@ -525,9 +525,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")
|
||||
|
|
@ -640,9 +640,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"]
|
||||
|
|
@ -669,9 +669,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()
|
||||
|
|
@ -692,9 +692,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()
|
||||
|
|
@ -747,9 +747,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()
|
||||
|
|
@ -862,9 +862,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"]
|
||||
|
|
@ -890,9 +890,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()
|
||||
|
|
@ -914,10 +914,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)
|
||||
|
|
@ -944,9 +944,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()
|
||||
|
|
@ -1053,9 +1053,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()
|
||||
|
|
@ -1078,9 +1078,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()
|
||||
|
|
@ -1126,10 +1126,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()
|
||||
|
|
@ -1205,9 +1205,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")
|
||||
|
|
@ -1245,9 +1245,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()
|
||||
|
|
@ -1287,9 +1287,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()
|
||||
|
|
@ -1324,9 +1324,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"]
|
||||
|
|
@ -1403,11 +1403,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
|
||||
|
|
@ -1561,10 +1561,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
|
||||
|
|
@ -1586,9 +1586,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"))
|
||||
|
|
@ -1647,9 +1647,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
|
||||
|
|
@ -1675,9 +1675,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
|
||||
|
|
@ -1723,9 +1723,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()
|
||||
|
|
@ -1747,9 +1747,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()
|
||||
|
|
@ -1770,9 +1770,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"])
|
||||
|
|
@ -1842,9 +1842,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
|
||||
|
|
@ -1880,9 +1880,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"]
|
||||
|
|
@ -1915,9 +1915,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()
|
||||
|
|
@ -2048,9 +2048,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
|
||||
|
|
@ -2086,9 +2086,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"]
|
||||
|
|
@ -2129,9 +2129,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"]
|
||||
|
|
@ -2168,9 +2168,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"]
|
||||
|
|
@ -2206,9 +2206,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"]
|
||||
|
|
@ -2244,9 +2244,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"]
|
||||
|
|
@ -2282,9 +2282,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"]
|
||||
|
|
@ -2386,9 +2386,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()
|
||||
|
|
@ -2431,9 +2431,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"]
|
||||
|
|
@ -2457,11 +2457,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()
|
||||
|
|
@ -2489,11 +2489,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()
|
||||
|
|
@ -2592,10 +2592,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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue