From fe01fa7249f84100e97d97f346dcda4647e5bc5b Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 15 Apr 2026 10:55:50 -0400 Subject: [PATCH] remove makeRuntime facade from Env (#22523) --- packages/opencode/src/env/index.ts | 19 --- .../test/provider/amazon-bedrock.test.ts | 40 +++--- .../opencode/test/provider/provider.test.ts | 122 +++++++++--------- 3 files changed, 85 insertions(+), 96 deletions(-) diff --git a/packages/opencode/src/env/index.ts b/packages/opencode/src/env/index.ts index 930287899c..b9efb68520 100644 --- a/packages/opencode/src/env/index.ts +++ b/packages/opencode/src/env/index.ts @@ -1,6 +1,5 @@ import { Context, Effect, Layer } from "effect" import { InstanceState } from "@/effect/instance-state" -import { makeRuntime } from "@/effect/run-service" export namespace Env { type State = Record @@ -35,22 +34,4 @@ export namespace Env { ) export const defaultLayer = layer - - const rt = makeRuntime(Service, defaultLayer) - - export function get(key: string) { - return rt.runSync((svc) => svc.get(key)) - } - - export function all() { - return rt.runSync((svc) => svc.all()) - } - - export function set(key: string, value: string) { - return rt.runSync((svc) => svc.set(key, value)) - } - - export function remove(key: string) { - return rt.runSync((svc) => svc.remove(key)) - } } diff --git a/packages/opencode/test/provider/amazon-bedrock.test.ts b/packages/opencode/test/provider/amazon-bedrock.test.ts index 712f36086f..6783ff5889 100644 --- a/packages/opencode/test/provider/amazon-bedrock.test.ts +++ b/packages/opencode/test/provider/amazon-bedrock.test.ts @@ -11,6 +11,10 @@ import { Global } from "../../src/global" import { Filesystem } from "../../src/util/filesystem" import { Effect } from "effect" import { AppRuntime } from "../../src/effect/app-runtime" +import { makeRuntime } from "../../src/effect/run-service" + +const env = makeRuntime(Env.Service, Env.defaultLayer) +const set = (k: string, v: string) => env.runSync((svc) => svc.set(k, v)) async function list() { return AppRuntime.runPromise( @@ -42,8 +46,8 @@ test("Bedrock: config region takes precedence over AWS_REGION env var", async () await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_REGION", "us-east-1") - Env.set("AWS_PROFILE", "default") + set("AWS_REGION", "us-east-1") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() @@ -67,8 +71,8 @@ test("Bedrock: falls back to AWS_REGION env var when no config region", async () await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_REGION", "eu-west-1") - Env.set("AWS_PROFILE", "default") + set("AWS_REGION", "eu-west-1") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() @@ -122,9 +126,9 @@ test("Bedrock: loads when bearer token from auth.json is present", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "") - Env.set("AWS_ACCESS_KEY_ID", "") - Env.set("AWS_BEARER_TOKEN_BEDROCK", "") + set("AWS_PROFILE", "") + set("AWS_ACCESS_KEY_ID", "") + set("AWS_BEARER_TOKEN_BEDROCK", "") }, fn: async () => { const providers = await list() @@ -168,8 +172,8 @@ test("Bedrock: config profile takes precedence over AWS_PROFILE env var", async await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "default") - Env.set("AWS_ACCESS_KEY_ID", "test-key-id") + set("AWS_PROFILE", "default") + set("AWS_ACCESS_KEY_ID", "test-key-id") }, fn: async () => { const providers = await list() @@ -200,7 +204,7 @@ test("Bedrock: includes custom endpoint in options when specified", async () => await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "default") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() @@ -233,10 +237,10 @@ test("Bedrock: autoloads when AWS_WEB_IDENTITY_TOKEN_FILE is present", async () await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_WEB_IDENTITY_TOKEN_FILE", "/var/run/secrets/eks.amazonaws.com/serviceaccount/token") - Env.set("AWS_ROLE_ARN", "arn:aws:iam::123456789012:role/my-eks-role") - Env.set("AWS_PROFILE", "") - Env.set("AWS_ACCESS_KEY_ID", "") + 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", "") }, fn: async () => { const providers = await list() @@ -276,7 +280,7 @@ test("Bedrock: model with us. prefix should not be double-prefixed", async () => await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "default") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() @@ -313,7 +317,7 @@ test("Bedrock: model with global. prefix should not be prefixed", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "default") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() @@ -349,7 +353,7 @@ test("Bedrock: model with eu. prefix should not be double-prefixed", async () => await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "default") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() @@ -385,7 +389,7 @@ test("Bedrock: model without prefix in US region should get us. prefix added", a await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("AWS_PROFILE", "default") + set("AWS_PROFILE", "default") }, fn: async () => { const providers = await list() diff --git a/packages/opencode/test/provider/provider.test.ts b/packages/opencode/test/provider/provider.test.ts index ac990bc0fb..dafa9dd822 100644 --- a/packages/opencode/test/provider/provider.test.ts +++ b/packages/opencode/test/provider/provider.test.ts @@ -13,6 +13,10 @@ import { Filesystem } from "../../src/util/filesystem" import { Env } from "../../src/env" import { Effect } from "effect" import { AppRuntime } from "../../src/effect/app-runtime" +import { makeRuntime } from "../../src/effect/run-service" + +const env = makeRuntime(Env.Service, Env.defaultLayer) +const set = (k: string, v: string) => env.runSync((svc) => svc.set(k, v)) async function run(fn: (provider: Provider.Interface) => Effect.Effect) { return AppRuntime.runPromise( @@ -71,7 +75,7 @@ test("provider loaded from env variable", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -126,7 +130,7 @@ test("disabled_providers excludes provider", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -150,8 +154,8 @@ test("enabled_providers restricts to only listed providers", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") - Env.set("OPENAI_API_KEY", "test-openai-key") + set("ANTHROPIC_API_KEY", "test-api-key") + set("OPENAI_API_KEY", "test-openai-key") }, fn: async () => { const providers = await list() @@ -180,7 +184,7 @@ test("model whitelist filters models for provider", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -211,7 +215,7 @@ test("model blacklist excludes specific models", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -246,7 +250,7 @@ test("custom model alias via config", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -322,7 +326,7 @@ test("env variable takes precedence, config merges options", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "env-api-key") + set("ANTHROPIC_API_KEY", "env-api-key") }, fn: async () => { const providers = await list() @@ -348,7 +352,7 @@ test("getModel returns model for valid provider/model", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const model = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514")) @@ -375,7 +379,7 @@ test("getModel throws ModelNotFoundError for invalid model", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { expect(getModel(ProviderID.anthropic, ModelID.make("nonexistent-model"))).rejects.toThrow() @@ -428,7 +432,7 @@ test("defaultModel returns first available model when no config set", async () = await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const model = await defaultModel() @@ -453,7 +457,7 @@ test("defaultModel respects config model setting", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const model = await defaultModel() @@ -568,7 +572,7 @@ test("model options are merged from existing model", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -597,7 +601,7 @@ test("provider removed when all models filtered out", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -620,7 +624,7 @@ test("closest finds model by partial match", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const result = await closest(ProviderID.anthropic, ["sonnet-4"]) @@ -675,7 +679,7 @@ test("getModel uses realIdByKey for aliased models", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -790,7 +794,7 @@ test("model inherits properties from existing database model", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -818,7 +822,7 @@ test("disabled_providers prevents loading even with env var", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("OPENAI_API_KEY", "test-openai-key") + set("OPENAI_API_KEY", "test-openai-key") }, fn: async () => { const providers = await list() @@ -842,8 +846,8 @@ test("enabled_providers with empty array allows no providers", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") - Env.set("OPENAI_API_KEY", "test-openai-key") + set("ANTHROPIC_API_KEY", "test-api-key") + set("OPENAI_API_KEY", "test-openai-key") }, fn: async () => { const providers = await list() @@ -872,7 +876,7 @@ test("whitelist and blacklist can be combined", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -981,7 +985,7 @@ test("getSmallModel returns appropriate small model", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const model = await getSmallModel(ProviderID.anthropic) @@ -1006,7 +1010,7 @@ test("getSmallModel respects config small_model override", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const model = await getSmallModel(ProviderID.anthropic) @@ -1054,8 +1058,8 @@ test("multiple providers can be configured simultaneously", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-anthropic-key") - Env.set("OPENAI_API_KEY", "test-openai-key") + set("ANTHROPIC_API_KEY", "test-anthropic-key") + set("OPENAI_API_KEY", "test-openai-key") }, fn: async () => { const providers = await list() @@ -1133,7 +1137,7 @@ test("model alias name defaults to alias key when id differs", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -1173,7 +1177,7 @@ test("provider with multiple env var options only includes apiKey when single en await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("MULTI_ENV_KEY_1", "test-key") + set("MULTI_ENV_KEY_1", "test-key") }, fn: async () => { const providers = await list() @@ -1215,7 +1219,7 @@ test("provider with single env var includes apiKey automatically", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("SINGLE_ENV_KEY", "my-api-key") + set("SINGLE_ENV_KEY", "my-api-key") }, fn: async () => { const providers = await list() @@ -1252,7 +1256,7 @@ test("model cost overrides existing cost values", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -1331,9 +1335,9 @@ test("disabled_providers and enabled_providers interaction", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-anthropic") - Env.set("OPENAI_API_KEY", "test-openai") - Env.set("GOOGLE_GENERATIVE_AI_API_KEY", "test-google") + set("ANTHROPIC_API_KEY", "test-anthropic") + set("OPENAI_API_KEY", "test-openai") + set("GOOGLE_GENERATIVE_AI_API_KEY", "test-google") }, fn: async () => { const providers = await list() @@ -1490,7 +1494,7 @@ test("provider env fallback - second env var used if first missing", async () => directory: tmp.path, init: async () => { // Only set fallback, not primary - Env.set("FALLBACK_KEY", "fallback-api-key") + set("FALLBACK_KEY", "fallback-api-key") }, fn: async () => { const providers = await list() @@ -1514,7 +1518,7 @@ test("getModel returns consistent results", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const model1 = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514")) @@ -1575,7 +1579,7 @@ test("ModelNotFoundError includes suggestions for typos", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { try { @@ -1603,7 +1607,7 @@ test("ModelNotFoundError for provider includes suggestions", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { try { @@ -1651,7 +1655,7 @@ test("getProvider returns provider info", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const provider = await getProvider(ProviderID.anthropic) @@ -1675,7 +1679,7 @@ test("closest returns undefined when no partial match found", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const result = await closest(ProviderID.anthropic, ["nonexistent-xyz-model"]) @@ -1698,7 +1702,7 @@ test("closest checks multiple query terms in order", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { // First term won't match, second will @@ -1770,7 +1774,7 @@ test("provider options are deeply merged", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -1808,7 +1812,7 @@ test("custom model inherits npm package from models.dev provider config", async await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("OPENAI_API_KEY", "test-api-key") + set("OPENAI_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -1843,7 +1847,7 @@ test("custom model inherits api.url from models.dev provider", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("OPENROUTER_API_KEY", "test-api-key") + set("OPENROUTER_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -1944,7 +1948,7 @@ test("model variants are generated for reasoning models", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -1982,7 +1986,7 @@ test("model variants can be disabled via config", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -2025,7 +2029,7 @@ test("model variants can be customized via config", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -2064,7 +2068,7 @@ test("disabled key is stripped from variant config", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -2102,7 +2106,7 @@ test("all variants can be disabled via config", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -2140,7 +2144,7 @@ test("variant config merges with generated variants", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-api-key") + set("ANTHROPIC_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -2178,7 +2182,7 @@ test("variants filtered in second pass for database models", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("OPENAI_API_KEY", "test-api-key") + set("OPENAI_API_KEY", "test-api-key") }, fn: async () => { const providers = await list() @@ -2282,7 +2286,7 @@ test("Google Vertex: retains baseURL for custom proxy", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds") + set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds") }, fn: async () => { const providers = await list() @@ -2327,7 +2331,7 @@ test("Google Vertex: supports OpenAI compatible models", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds") + set("GOOGLE_APPLICATION_CREDENTIALS", "test-creds") }, fn: async () => { const providers = await list() @@ -2353,9 +2357,9 @@ test("cloudflare-ai-gateway loads with env variables", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("CLOUDFLARE_ACCOUNT_ID", "test-account") - Env.set("CLOUDFLARE_GATEWAY_ID", "test-gateway") - Env.set("CLOUDFLARE_API_TOKEN", "test-token") + set("CLOUDFLARE_ACCOUNT_ID", "test-account") + set("CLOUDFLARE_GATEWAY_ID", "test-gateway") + set("CLOUDFLARE_API_TOKEN", "test-token") }, fn: async () => { const providers = await list() @@ -2385,9 +2389,9 @@ test("cloudflare-ai-gateway forwards config metadata options", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("CLOUDFLARE_ACCOUNT_ID", "test-account") - Env.set("CLOUDFLARE_GATEWAY_ID", "test-gateway") - Env.set("CLOUDFLARE_API_TOKEN", "test-token") + set("CLOUDFLARE_ACCOUNT_ID", "test-account") + set("CLOUDFLARE_GATEWAY_ID", "test-gateway") + set("CLOUDFLARE_API_TOKEN", "test-token") }, fn: async () => { const providers = await list() @@ -2485,8 +2489,8 @@ test("plugin config enabled and disabled providers are honored", async () => { await Instance.provide({ directory: tmp.path, init: async () => { - Env.set("ANTHROPIC_API_KEY", "test-anthropic-key") - Env.set("OPENAI_API_KEY", "test-openai-key") + set("ANTHROPIC_API_KEY", "test-anthropic-key") + set("OPENAI_API_KEY", "test-openai-key") }, fn: async () => { const providers = await list()