Kit Langton 4 дней назад
Родитель
Сommit
6a99079012

+ 428 - 430
packages/opencode/src/config/config.ts

@@ -1161,502 +1161,500 @@ export namespace Config {
     }),
   )
 
-  export const layer: Layer.Layer<Service, never, AppFileSystem.Service | Auth.Service | Account.Service> =
-    Layer.effect(
-      Service,
-      Effect.gen(function* () {
-        const fs = yield* AppFileSystem.Service
-        const authSvc = yield* Auth.Service
-        const accountSvc = yield* Account.Service
-
-        const readConfigFile = Effect.fnUntraced(function* (filepath: string) {
-          return yield* fs.readFileString(filepath).pipe(
-            Effect.catchIf(
-              (e) => e.reason._tag === "NotFound",
-              () => Effect.succeed(undefined),
-            ),
-            Effect.orDie,
-          )
-        })
+  export const layer: Layer.Layer<
+    Service,
+    never,
+    AppFileSystem.Service | Auth.Service | Account.Service | Env.Service
+  > = Layer.effect(
+    Service,
+    Effect.gen(function* () {
+      const fs = yield* AppFileSystem.Service
+      const authSvc = yield* Auth.Service
+      const accountSvc = yield* Account.Service
+      const env = yield* Env.Service
+
+      const readConfigFile = Effect.fnUntraced(function* (filepath: string) {
+        return yield* fs.readFileString(filepath).pipe(
+          Effect.catchIf(
+            (e) => e.reason._tag === "NotFound",
+            () => Effect.succeed(undefined),
+          ),
+          Effect.orDie,
+        )
+      })
 
-        const loadConfig = Effect.fnUntraced(function* (
-          text: string,
-          options: { path: string } | { dir: string; source: string },
-        ) {
-          const original = text
-          const source = "path" in options ? options.path : options.source
-          const isFile = "path" in options
-          const data = yield* Effect.promise(() =>
-            ConfigPaths.parseText(
-              text,
-              "path" in options ? options.path : { source: options.source, dir: options.dir },
-            ),
-          )
+      const loadConfig = Effect.fnUntraced(function* (
+        text: string,
+        options: { path: string } | { dir: string; source: string },
+      ) {
+        const original = text
+        const source = "path" in options ? options.path : options.source
+        const isFile = "path" in options
+        const data = yield* Effect.promise(() =>
+          ConfigPaths.parseText(text, "path" in options ? options.path : { source: options.source, dir: options.dir }),
+        )
 
-          const normalized = (() => {
-            if (!data || typeof data !== "object" || Array.isArray(data)) return data
-            const copy = { ...(data as Record<string, unknown>) }
-            const hadLegacy = "theme" in copy || "keybinds" in copy || "tui" in copy
-            if (!hadLegacy) return copy
-            delete copy.theme
-            delete copy.keybinds
-            delete copy.tui
-            log.warn("tui keys in opencode config are deprecated; move them to tui.json", { path: source })
-            return copy
-          })()
-
-          const parsed = Info.safeParse(normalized)
-          if (parsed.success) {
-            if (!parsed.data.$schema && isFile) {
-              parsed.data.$schema = "https://opencode.ai/config.json"
-              const updated = original.replace(/^\s*\{/, '{\n  "$schema": "https://opencode.ai/config.json",')
-              yield* fs.writeFileString(options.path, updated).pipe(Effect.catch(() => Effect.void))
-            }
-            const data = parsed.data
-            if (data.plugin && isFile) {
-              const list = data.plugin
-              for (let i = 0; i < list.length; i++) {
-                list[i] = yield* Effect.promise(() => resolvePluginSpec(list[i], options.path))
-              }
+        const normalized = (() => {
+          if (!data || typeof data !== "object" || Array.isArray(data)) return data
+          const copy = { ...(data as Record<string, unknown>) }
+          const hadLegacy = "theme" in copy || "keybinds" in copy || "tui" in copy
+          if (!hadLegacy) return copy
+          delete copy.theme
+          delete copy.keybinds
+          delete copy.tui
+          log.warn("tui keys in opencode config are deprecated; move them to tui.json", { path: source })
+          return copy
+        })()
+
+        const parsed = Info.safeParse(normalized)
+        if (parsed.success) {
+          if (!parsed.data.$schema && isFile) {
+            parsed.data.$schema = "https://opencode.ai/config.json"
+            const updated = original.replace(/^\s*\{/, '{\n  "$schema": "https://opencode.ai/config.json",')
+            yield* fs.writeFileString(options.path, updated).pipe(Effect.catch(() => Effect.void))
+          }
+          const data = parsed.data
+          if (data.plugin && isFile) {
+            const list = data.plugin
+            for (let i = 0; i < list.length; i++) {
+              list[i] = yield* Effect.promise(() => resolvePluginSpec(list[i], options.path))
             }
-            return data
           }
+          return data
+        }
 
-          throw new InvalidError({
-            path: source,
-            issues: parsed.error.issues,
-          })
-        })
-
-        const loadFile = Effect.fnUntraced(function* (filepath: string) {
-          log.info("loading", { path: filepath })
-          const text = yield* readConfigFile(filepath)
-          if (!text) return {} as Info
-          return yield* loadConfig(text, { path: filepath })
+        throw new InvalidError({
+          path: source,
+          issues: parsed.error.issues,
         })
+      })
 
-        const loadGlobal = Effect.fnUntraced(function* () {
-          let result: Info = pipe(
-            {},
-            mergeDeep(yield* loadFile(path.join(Global.Path.config, "config.json"))),
-            mergeDeep(yield* loadFile(path.join(Global.Path.config, "opencode.json"))),
-            mergeDeep(yield* loadFile(path.join(Global.Path.config, "opencode.jsonc"))),
-          )
-
-          const legacy = path.join(Global.Path.config, "config")
-          if (existsSync(legacy)) {
-            yield* Effect.promise(() =>
-              import(pathToFileURL(legacy).href, { with: { type: "toml" } })
-                .then(async (mod) => {
-                  const { provider, model, ...rest } = mod.default
-                  if (provider && model) result.model = `${provider}/${model}`
-                  result["$schema"] = "https://opencode.ai/config.json"
-                  result = mergeDeep(result, rest)
-                  await fsNode.writeFile(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2))
-                  await fsNode.unlink(legacy)
-                })
-                .catch(() => {}),
-            )
-          }
-
-          return result
-        })
+      const loadFile = Effect.fnUntraced(function* (filepath: string) {
+        log.info("loading", { path: filepath })
+        const text = yield* readConfigFile(filepath)
+        if (!text) return {} as Info
+        return yield* loadConfig(text, { path: filepath })
+      })
 
-        const [cachedGlobal, invalidateGlobal] = yield* Effect.cachedInvalidateWithTTL(
-          loadGlobal().pipe(
-            Effect.tapError((error) =>
-              Effect.sync(() => log.error("failed to load global config, using defaults", { error: String(error) })),
-            ),
-            Effect.orElseSucceed((): Info => ({})),
-          ),
-          Duration.infinity,
+      const loadGlobal = Effect.fnUntraced(function* () {
+        let result: Info = pipe(
+          {},
+          mergeDeep(yield* loadFile(path.join(Global.Path.config, "config.json"))),
+          mergeDeep(yield* loadFile(path.join(Global.Path.config, "opencode.json"))),
+          mergeDeep(yield* loadFile(path.join(Global.Path.config, "opencode.jsonc"))),
         )
 
-        const getGlobal = Effect.fn("Config.getGlobal")(function* () {
-          return yield* cachedGlobal
-        })
-
-        const install = Effect.fnUntraced(function* (dir: string) {
-          const pkg = path.join(dir, "package.json")
-          const gitignore = path.join(dir, ".gitignore")
-          const plugin = path.join(dir, "node_modules", "@opencode-ai", "plugin", "package.json")
-          const target = Installation.isLocal() ? "*" : Installation.VERSION
-          const json = yield* fs.readJson(pkg).pipe(
-            Effect.catch(() => Effect.succeed({} satisfies Package)),
-            Effect.map((x): Package => (isRecord(x) ? (x as Package) : {})),
+        const legacy = path.join(Global.Path.config, "config")
+        if (existsSync(legacy)) {
+          yield* Effect.promise(() =>
+            import(pathToFileURL(legacy).href, { with: { type: "toml" } })
+              .then(async (mod) => {
+                const { provider, model, ...rest } = mod.default
+                if (provider && model) result.model = `${provider}/${model}`
+                result["$schema"] = "https://opencode.ai/config.json"
+                result = mergeDeep(result, rest)
+                await fsNode.writeFile(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2))
+                await fsNode.unlink(legacy)
+              })
+              .catch(() => {}),
           )
-          const hasDep = json.dependencies?.["@opencode-ai/plugin"] === target
-          const hasIgnore = yield* fs.existsSafe(gitignore)
-          const hasPkg = yield* fs.existsSafe(plugin)
-
-          if (!hasDep) {
-            yield* fs.writeJson(pkg, {
-              ...json,
-              dependencies: {
-                ...json.dependencies,
-                "@opencode-ai/plugin": target,
-              },
-            })
-          }
+        }
 
-          if (!hasIgnore) {
-            yield* fs.writeFileString(
-              gitignore,
-              ["node_modules", "package.json", "package-lock.json", "bun.lock", ".gitignore"].join("\n"),
-            )
-          }
+        return result
+      })
 
-          if (hasDep && hasIgnore && hasPkg) return
+      const [cachedGlobal, invalidateGlobal] = yield* Effect.cachedInvalidateWithTTL(
+        loadGlobal().pipe(
+          Effect.tapError((error) =>
+            Effect.sync(() => log.error("failed to load global config, using defaults", { error: String(error) })),
+          ),
+          Effect.orElseSucceed((): Info => ({})),
+        ),
+        Duration.infinity,
+      )
 
-          yield* Effect.promise(() => Npm.install(dir))
-        })
+      const getGlobal = Effect.fn("Config.getGlobal")(function* () {
+        return yield* cachedGlobal
+      })
 
-        const installDependencies = Effect.fn("Config.installDependencies")(function* (
-          dir: string,
-          input?: InstallInput,
-        ) {
-          if (
-            !(yield* fs.access(dir, { writable: true }).pipe(
-              Effect.as(true),
-              Effect.orElseSucceed(() => false),
-            ))
-          )
-            return
-
-          const key =
-            process.platform === "win32" ? "config-install:win32" : `config-install:${AppFileSystem.resolve(dir)}`
-
-          yield* Effect.acquireUseRelease(
-            Effect.promise((signal) =>
-              Flock.acquire(key, {
-                signal,
-                onWait: (tick) =>
-                  input?.waitTick?.({
-                    dir,
-                    attempt: tick.attempt,
-                    delay: tick.delay,
-                    waited: tick.waited,
-                  }),
-              }),
-            ),
-            () => install(dir),
-            (lease) => Effect.promise(() => lease.release()),
+      const install = Effect.fnUntraced(function* (dir: string) {
+        const pkg = path.join(dir, "package.json")
+        const gitignore = path.join(dir, ".gitignore")
+        const plugin = path.join(dir, "node_modules", "@opencode-ai", "plugin", "package.json")
+        const target = Installation.isLocal() ? "*" : Installation.VERSION
+        const json = yield* fs.readJson(pkg).pipe(
+          Effect.catch(() => Effect.succeed({} satisfies Package)),
+          Effect.map((x): Package => (isRecord(x) ? (x as Package) : {})),
+        )
+        const hasDep = json.dependencies?.["@opencode-ai/plugin"] === target
+        const hasIgnore = yield* fs.existsSafe(gitignore)
+        const hasPkg = yield* fs.existsSafe(plugin)
+
+        if (!hasDep) {
+          yield* fs.writeJson(pkg, {
+            ...json,
+            dependencies: {
+              ...json.dependencies,
+              "@opencode-ai/plugin": target,
+            },
+          })
+        }
+
+        if (!hasIgnore) {
+          yield* fs.writeFileString(
+            gitignore,
+            ["node_modules", "package.json", "package-lock.json", "bun.lock", ".gitignore"].join("\n"),
           )
-        })
+        }
 
-        const loadInstanceState = Effect.fnUntraced(function* (ctx: InstanceContext) {
-          const auth = yield* authSvc.all().pipe(Effect.orDie)
+        if (hasDep && hasIgnore && hasPkg) return
 
-          let result: Info = {}
-          const consoleManagedProviders = new Set<string>()
-          let activeOrgName: string | undefined
+        yield* Effect.promise(() => Npm.install(dir))
+      })
 
-          const scope = Effect.fnUntraced(function* (source: string) {
-            if (source.startsWith("http://") || source.startsWith("https://")) return "global"
-            if (source === "OPENCODE_CONFIG_CONTENT") return "local"
-            if (yield* InstanceRef.use((ctx) => Effect.succeed(Instance.containsPath(source, ctx)))) return "local"
-            return "global"
-          })
+      const installDependencies = Effect.fn("Config.installDependencies")(function* (
+        dir: string,
+        input?: InstallInput,
+      ) {
+        if (
+          !(yield* fs.access(dir, { writable: true }).pipe(
+            Effect.as(true),
+            Effect.orElseSucceed(() => false),
+          ))
+        )
+          return
+
+        const key =
+          process.platform === "win32" ? "config-install:win32" : `config-install:${AppFileSystem.resolve(dir)}`
+
+        yield* Effect.acquireUseRelease(
+          Effect.promise((signal) =>
+            Flock.acquire(key, {
+              signal,
+              onWait: (tick) =>
+                input?.waitTick?.({
+                  dir,
+                  attempt: tick.attempt,
+                  delay: tick.delay,
+                  waited: tick.waited,
+                }),
+            }),
+          ),
+          () => install(dir),
+          (lease) => Effect.promise(() => lease.release()),
+        )
+      })
 
-          const track = Effect.fnUntraced(function* (
-            source: string,
-            list: PluginSpec[] | undefined,
-            kind?: PluginScope,
-          ) {
-            if (!list?.length) return
-            const hit = kind ?? (yield* scope(source))
-            const plugins = deduplicatePluginOrigins([
-              ...(result.plugin_origins ?? []),
-              ...list.map((spec) => ({ spec, source, scope: hit })),
-            ])
-            result.plugin = plugins.map((item) => item.spec)
-            result.plugin_origins = plugins
-          })
+      const loadInstanceState = Effect.fnUntraced(function* (ctx: InstanceContext) {
+        const auth = yield* authSvc.all().pipe(Effect.orDie)
 
-          const merge = (source: string, next: Info, kind?: PluginScope) => {
-            result = mergeConfigConcatArrays(result, next)
-            return track(source, next.plugin, kind)
-          }
+        let result: Info = {}
+        const consoleManagedProviders = new Set<string>()
+        let activeOrgName: string | undefined
 
-          for (const [key, value] of Object.entries(auth)) {
-            if (value.type === "wellknown") {
-              const url = key.replace(/\/+$/, "")
-              process.env[value.key] = value.token
-              log.debug("fetching remote config", { url: `${url}/.well-known/opencode` })
-              const response = yield* Effect.promise(() => fetch(`${url}/.well-known/opencode`))
-              if (!response.ok) {
-                throw new Error(`failed to fetch remote config from ${url}: ${response.status}`)
-              }
-              const wellknown = (yield* Effect.promise(() => response.json())) as any
-              const remoteConfig = wellknown.config ?? {}
-              if (!remoteConfig.$schema) remoteConfig.$schema = "https://opencode.ai/config.json"
-              const source = `${url}/.well-known/opencode`
-              const next = yield* loadConfig(JSON.stringify(remoteConfig), {
-                dir: path.dirname(source),
-                source,
-              })
-              yield* merge(source, next, "global")
-              log.debug("loaded remote config from well-known", { url })
-            }
-          }
+        const scope = Effect.fnUntraced(function* (source: string) {
+          if (source.startsWith("http://") || source.startsWith("https://")) return "global"
+          if (source === "OPENCODE_CONFIG_CONTENT") return "local"
+          if (yield* InstanceRef.use((ctx) => Effect.succeed(Instance.containsPath(source, ctx)))) return "local"
+          return "global"
+        })
 
-          const global = yield* getGlobal()
-          yield* merge(Global.Path.config, global, "global")
+        const track = Effect.fnUntraced(function* (source: string, list: PluginSpec[] | undefined, kind?: PluginScope) {
+          if (!list?.length) return
+          const hit = kind ?? (yield* scope(source))
+          const plugins = deduplicatePluginOrigins([
+            ...(result.plugin_origins ?? []),
+            ...list.map((spec) => ({ spec, source, scope: hit })),
+          ])
+          result.plugin = plugins.map((item) => item.spec)
+          result.plugin_origins = plugins
+        })
 
-          if (Flag.OPENCODE_CONFIG) {
-            yield* merge(Flag.OPENCODE_CONFIG, yield* loadFile(Flag.OPENCODE_CONFIG))
-            log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
-          }
+        const merge = (source: string, next: Info, kind?: PluginScope) => {
+          result = mergeConfigConcatArrays(result, next)
+          return track(source, next.plugin, kind)
+        }
 
-          if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) {
-            for (const file of yield* Effect.promise(() =>
-              ConfigPaths.projectFiles("opencode", ctx.directory, ctx.worktree),
-            )) {
-              yield* merge(file, yield* loadFile(file), "local")
+        for (const [key, value] of Object.entries(auth)) {
+          if (value.type === "wellknown") {
+            const url = key.replace(/\/+$/, "")
+            process.env[value.key] = value.token
+            log.debug("fetching remote config", { url: `${url}/.well-known/opencode` })
+            const response = yield* Effect.promise(() => fetch(`${url}/.well-known/opencode`))
+            if (!response.ok) {
+              throw new Error(`failed to fetch remote config from ${url}: ${response.status}`)
             }
+            const wellknown = (yield* Effect.promise(() => response.json())) as any
+            const remoteConfig = wellknown.config ?? {}
+            if (!remoteConfig.$schema) remoteConfig.$schema = "https://opencode.ai/config.json"
+            const source = `${url}/.well-known/opencode`
+            const next = yield* loadConfig(JSON.stringify(remoteConfig), {
+              dir: path.dirname(source),
+              source,
+            })
+            yield* merge(source, next, "global")
+            log.debug("loaded remote config from well-known", { url })
           }
+        }
 
-          result.agent = result.agent || {}
-          result.mode = result.mode || {}
-          result.plugin = result.plugin || []
+        const global = yield* getGlobal()
+        yield* merge(Global.Path.config, global, "global")
 
-          const directories = yield* Effect.promise(() => ConfigPaths.directories(ctx.directory, ctx.worktree))
+        if (Flag.OPENCODE_CONFIG) {
+          yield* merge(Flag.OPENCODE_CONFIG, yield* loadFile(Flag.OPENCODE_CONFIG))
+          log.debug("loaded custom config", { path: Flag.OPENCODE_CONFIG })
+        }
 
-          if (Flag.OPENCODE_CONFIG_DIR) {
-            log.debug("loading config from OPENCODE_CONFIG_DIR", { path: Flag.OPENCODE_CONFIG_DIR })
+        if (!Flag.OPENCODE_DISABLE_PROJECT_CONFIG) {
+          for (const file of yield* Effect.promise(() =>
+            ConfigPaths.projectFiles("opencode", ctx.directory, ctx.worktree),
+          )) {
+            yield* merge(file, yield* loadFile(file), "local")
           }
+        }
 
-          const deps: Fiber.Fiber<void, never>[] = []
-
-          for (const dir of unique(directories)) {
-            if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
-              for (const file of ["opencode.json", "opencode.jsonc"]) {
-                const source = path.join(dir, file)
-                log.debug(`loading config from ${source}`)
-                yield* merge(source, yield* loadFile(source))
-                result.agent ??= {}
-                result.mode ??= {}
-                result.plugin ??= []
-              }
-            }
+        result.agent = result.agent || {}
+        result.mode = result.mode || {}
+        result.plugin = result.plugin || []
 
-            const dep = yield* installDependencies(dir).pipe(
-              Effect.exit,
-              Effect.tap((exit) =>
-                Exit.isFailure(exit)
-                  ? Effect.sync(() => {
-                      log.warn("background dependency install failed", { dir, error: String(exit.cause) })
-                    })
-                  : Effect.void,
-              ),
-              Effect.asVoid,
-              Effect.forkScoped,
-            )
-            deps.push(dep)
+        const directories = yield* Effect.promise(() => ConfigPaths.directories(ctx.directory, ctx.worktree))
 
-            result.command = mergeDeep(result.command ?? {}, yield* Effect.promise(() => loadCommand(dir)))
-            result.agent = mergeDeep(result.agent, yield* Effect.promise(() => loadAgent(dir)))
-            result.agent = mergeDeep(result.agent, yield* Effect.promise(() => loadMode(dir)))
-            const list = yield* Effect.promise(() => loadPlugin(dir))
-            yield* track(dir, list)
-          }
+        if (Flag.OPENCODE_CONFIG_DIR) {
+          log.debug("loading config from OPENCODE_CONFIG_DIR", { path: Flag.OPENCODE_CONFIG_DIR })
+        }
 
-          if (process.env.OPENCODE_CONFIG_CONTENT) {
-            const source = "OPENCODE_CONFIG_CONTENT"
-            const next = yield* loadConfig(process.env.OPENCODE_CONFIG_CONTENT, {
-              dir: ctx.directory,
-              source,
-            })
-            yield* merge(source, next, "local")
-            log.debug("loaded custom config from OPENCODE_CONFIG_CONTENT")
+        const deps: Fiber.Fiber<void, never>[] = []
+
+        for (const dir of unique(directories)) {
+          if (dir.endsWith(".opencode") || dir === Flag.OPENCODE_CONFIG_DIR) {
+            for (const file of ["opencode.json", "opencode.jsonc"]) {
+              const source = path.join(dir, file)
+              log.debug(`loading config from ${source}`)
+              yield* merge(source, yield* loadFile(source))
+              result.agent ??= {}
+              result.mode ??= {}
+              result.plugin ??= []
+            }
           }
 
-          const activeOrg = Option.getOrUndefined(
-            yield* accountSvc.activeOrg().pipe(Effect.catch(() => Effect.succeed(Option.none()))),
+          const dep = yield* installDependencies(dir).pipe(
+            Effect.exit,
+            Effect.tap((exit) =>
+              Exit.isFailure(exit)
+                ? Effect.sync(() => {
+                    log.warn("background dependency install failed", { dir, error: String(exit.cause) })
+                  })
+                : Effect.void,
+            ),
+            Effect.asVoid,
+            Effect.forkScoped,
           )
-          if (activeOrg) {
-            yield* Effect.gen(function* () {
-              const [configOpt, tokenOpt] = yield* Effect.all(
-                [accountSvc.config(activeOrg.account.id, activeOrg.org.id), accountSvc.token(activeOrg.account.id)],
-                { concurrency: 2 },
-              )
-              if (Option.isSome(tokenOpt)) {
-                process.env["OPENCODE_CONSOLE_TOKEN"] = tokenOpt.value
-                Env.set("OPENCODE_CONSOLE_TOKEN", tokenOpt.value)
-              }
+          deps.push(dep)
 
-              activeOrgName = activeOrg.org.name
-
-              if (Option.isSome(configOpt)) {
-                const source = `${activeOrg.account.url}/api/config`
-                const next = yield* loadConfig(JSON.stringify(configOpt.value), {
-                  dir: path.dirname(source),
-                  source,
-                })
-                for (const providerID of Object.keys(next.provider ?? {})) {
-                  consoleManagedProviders.add(providerID)
-                }
-                yield* merge(source, next, "global")
-              }
-            }).pipe(
-              Effect.catch((err) => {
-                log.debug("failed to fetch remote account config", {
-                  error: err instanceof Error ? err.message : String(err),
-                })
-                return Effect.void
-              }),
+          result.command = mergeDeep(result.command ?? {}, yield* Effect.promise(() => loadCommand(dir)))
+          result.agent = mergeDeep(result.agent, yield* Effect.promise(() => loadAgent(dir)))
+          result.agent = mergeDeep(result.agent, yield* Effect.promise(() => loadMode(dir)))
+          const list = yield* Effect.promise(() => loadPlugin(dir))
+          yield* track(dir, list)
+        }
+
+        if (process.env.OPENCODE_CONFIG_CONTENT) {
+          const source = "OPENCODE_CONFIG_CONTENT"
+          const next = yield* loadConfig(process.env.OPENCODE_CONFIG_CONTENT, {
+            dir: ctx.directory,
+            source,
+          })
+          yield* merge(source, next, "local")
+          log.debug("loaded custom config from OPENCODE_CONFIG_CONTENT")
+        }
+
+        const activeOrg = Option.getOrUndefined(
+          yield* accountSvc.activeOrg().pipe(Effect.catch(() => Effect.succeed(Option.none()))),
+        )
+        if (activeOrg) {
+          yield* Effect.gen(function* () {
+            const [configOpt, tokenOpt] = yield* Effect.all(
+              [accountSvc.config(activeOrg.account.id, activeOrg.org.id), accountSvc.token(activeOrg.account.id)],
+              { concurrency: 2 },
             )
-          }
+            if (Option.isSome(tokenOpt)) {
+              process.env["OPENCODE_CONSOLE_TOKEN"] = tokenOpt.value
+              yield* env.set("OPENCODE_CONSOLE_TOKEN", tokenOpt.value)
+            }
 
-          if (existsSync(managedDir)) {
-            for (const file of ["opencode.json", "opencode.jsonc"]) {
-              const source = path.join(managedDir, file)
-              yield* merge(source, yield* loadFile(source), "global")
+            activeOrgName = activeOrg.org.name
+
+            if (Option.isSome(configOpt)) {
+              const source = `${activeOrg.account.url}/api/config`
+              const next = yield* loadConfig(JSON.stringify(configOpt.value), {
+                dir: path.dirname(source),
+                source,
+              })
+              for (const providerID of Object.keys(next.provider ?? {})) {
+                consoleManagedProviders.add(providerID)
+              }
+              yield* merge(source, next, "global")
             }
+          }).pipe(
+            Effect.catch((err) => {
+              log.debug("failed to fetch remote account config", {
+                error: err instanceof Error ? err.message : String(err),
+              })
+              return Effect.void
+            }),
+          )
+        }
+
+        if (existsSync(managedDir)) {
+          for (const file of ["opencode.json", "opencode.jsonc"]) {
+            const source = path.join(managedDir, file)
+            yield* merge(source, yield* loadFile(source), "global")
           }
+        }
 
-          // macOS managed preferences (.mobileconfig deployed via MDM) override everything
-          result = mergeConfigConcatArrays(result, yield* Effect.promise(() => readManagedPreferences()))
+        // macOS managed preferences (.mobileconfig deployed via MDM) override everything
+        result = mergeConfigConcatArrays(result, yield* Effect.promise(() => readManagedPreferences()))
 
-          for (const [name, mode] of Object.entries(result.mode ?? {})) {
-            result.agent = mergeDeep(result.agent ?? {}, {
-              [name]: {
-                ...mode,
-                mode: "primary" as const,
-              },
-            })
-          }
+        for (const [name, mode] of Object.entries(result.mode ?? {})) {
+          result.agent = mergeDeep(result.agent ?? {}, {
+            [name]: {
+              ...mode,
+              mode: "primary" as const,
+            },
+          })
+        }
 
-          if (Flag.OPENCODE_PERMISSION) {
-            result.permission = mergeDeep(result.permission ?? {}, JSON.parse(Flag.OPENCODE_PERMISSION))
-          }
+        if (Flag.OPENCODE_PERMISSION) {
+          result.permission = mergeDeep(result.permission ?? {}, JSON.parse(Flag.OPENCODE_PERMISSION))
+        }
 
-          if (result.tools) {
-            const perms: Record<string, Config.PermissionAction> = {}
-            for (const [tool, enabled] of Object.entries(result.tools)) {
-              const action: Config.PermissionAction = enabled ? "allow" : "deny"
-              if (tool === "write" || tool === "edit" || tool === "patch" || tool === "multiedit") {
-                perms.edit = action
-                continue
-              }
-              perms[tool] = action
+        if (result.tools) {
+          const perms: Record<string, Config.PermissionAction> = {}
+          for (const [tool, enabled] of Object.entries(result.tools)) {
+            const action: Config.PermissionAction = enabled ? "allow" : "deny"
+            if (tool === "write" || tool === "edit" || tool === "patch" || tool === "multiedit") {
+              perms.edit = action
+              continue
             }
-            result.permission = mergeDeep(perms, result.permission ?? {})
+            perms[tool] = action
           }
+          result.permission = mergeDeep(perms, result.permission ?? {})
+        }
 
-          if (!result.username) result.username = os.userInfo().username
+        if (!result.username) result.username = os.userInfo().username
 
-          if (result.autoshare === true && !result.share) {
-            result.share = "auto"
-          }
+        if (result.autoshare === true && !result.share) {
+          result.share = "auto"
+        }
 
-          if (Flag.OPENCODE_DISABLE_AUTOCOMPACT) {
-            result.compaction = { ...result.compaction, auto: false }
-          }
-          if (Flag.OPENCODE_DISABLE_PRUNE) {
-            result.compaction = { ...result.compaction, prune: false }
-          }
+        if (Flag.OPENCODE_DISABLE_AUTOCOMPACT) {
+          result.compaction = { ...result.compaction, auto: false }
+        }
+        if (Flag.OPENCODE_DISABLE_PRUNE) {
+          result.compaction = { ...result.compaction, prune: false }
+        }
 
-          return {
-            config: result,
-            directories,
-            deps,
-            consoleState: {
-              consoleManagedProviders: Array.from(consoleManagedProviders),
-              activeOrgName,
-              switchableOrgCount: 0,
-            },
-          }
-        })
+        return {
+          config: result,
+          directories,
+          deps,
+          consoleState: {
+            consoleManagedProviders: Array.from(consoleManagedProviders),
+            activeOrgName,
+            switchableOrgCount: 0,
+          },
+        }
+      })
 
-        const state = yield* InstanceState.make<State>(
-          Effect.fn("Config.state")(function* (ctx) {
-            return yield* loadInstanceState(ctx)
-          }),
-        )
+      const state = yield* InstanceState.make<State>(
+        Effect.fn("Config.state")(function* (ctx) {
+          return yield* loadInstanceState(ctx)
+        }),
+      )
 
-        const get = Effect.fn("Config.get")(function* () {
-          return yield* InstanceState.use(state, (s) => s.config)
-        })
+      const get = Effect.fn("Config.get")(function* () {
+        return yield* InstanceState.use(state, (s) => s.config)
+      })
 
-        const directories = Effect.fn("Config.directories")(function* () {
-          return yield* InstanceState.use(state, (s) => s.directories)
-        })
+      const directories = Effect.fn("Config.directories")(function* () {
+        return yield* InstanceState.use(state, (s) => s.directories)
+      })
 
-        const getConsoleState = Effect.fn("Config.getConsoleState")(function* () {
-          return yield* InstanceState.use(state, (s) => s.consoleState)
-        })
+      const getConsoleState = Effect.fn("Config.getConsoleState")(function* () {
+        return yield* InstanceState.use(state, (s) => s.consoleState)
+      })
 
-        const waitForDependencies = Effect.fn("Config.waitForDependencies")(function* () {
-          yield* InstanceState.useEffect(state, (s) =>
-            Effect.forEach(s.deps, Fiber.join, { concurrency: "unbounded" }).pipe(Effect.asVoid),
-          )
-        })
+      const waitForDependencies = Effect.fn("Config.waitForDependencies")(function* () {
+        yield* InstanceState.useEffect(state, (s) =>
+          Effect.forEach(s.deps, Fiber.join, { concurrency: "unbounded" }).pipe(Effect.asVoid),
+        )
+      })
 
-        const update = Effect.fn("Config.update")(function* (config: Info) {
-          const dir = yield* InstanceState.directory
-          const file = path.join(dir, "config.json")
-          const existing = yield* loadFile(file)
-          yield* fs
-            .writeFileString(file, JSON.stringify(mergeDeep(writable(existing), writable(config)), null, 2))
-            .pipe(Effect.orDie)
-          yield* Effect.promise(() => Instance.dispose())
-        })
+      const update = Effect.fn("Config.update")(function* (config: Info) {
+        const dir = yield* InstanceState.directory
+        const file = path.join(dir, "config.json")
+        const existing = yield* loadFile(file)
+        yield* fs
+          .writeFileString(file, JSON.stringify(mergeDeep(writable(existing), writable(config)), null, 2))
+          .pipe(Effect.orDie)
+        yield* Effect.promise(() => Instance.dispose())
+      })
 
-        const invalidate = Effect.fn("Config.invalidate")(function* (wait?: boolean) {
-          yield* invalidateGlobal
-          const task = Instance.disposeAll()
-            .catch(() => undefined)
-            .finally(() =>
-              GlobalBus.emit("event", {
-                directory: "global",
-                payload: {
-                  type: Event.Disposed.type,
-                  properties: {},
-                },
-              }),
-            )
-          if (wait) yield* Effect.promise(() => task)
-          else void task
-        })
+      const invalidate = Effect.fn("Config.invalidate")(function* (wait?: boolean) {
+        yield* invalidateGlobal
+        const task = Instance.disposeAll()
+          .catch(() => undefined)
+          .finally(() =>
+            GlobalBus.emit("event", {
+              directory: "global",
+              payload: {
+                type: Event.Disposed.type,
+                properties: {},
+              },
+            }),
+          )
+        if (wait) yield* Effect.promise(() => task)
+        else void task
+      })
 
-        const updateGlobal = Effect.fn("Config.updateGlobal")(function* (config: Info) {
-          const file = globalConfigFile()
-          const before = (yield* readConfigFile(file)) ?? "{}"
-          const input = writable(config)
-
-          let next: Info
-          if (!file.endsWith(".jsonc")) {
-            const existing = parseConfig(before, file)
-            const merged = mergeDeep(writable(existing), input)
-            yield* fs.writeFileString(file, JSON.stringify(merged, null, 2)).pipe(Effect.orDie)
-            next = merged
-          } else {
-            const updated = patchJsonc(before, input)
-            next = parseConfig(updated, file)
-            yield* fs.writeFileString(file, updated).pipe(Effect.orDie)
-          }
+      const updateGlobal = Effect.fn("Config.updateGlobal")(function* (config: Info) {
+        const file = globalConfigFile()
+        const before = (yield* readConfigFile(file)) ?? "{}"
+        const input = writable(config)
+
+        let next: Info
+        if (!file.endsWith(".jsonc")) {
+          const existing = parseConfig(before, file)
+          const merged = mergeDeep(writable(existing), input)
+          yield* fs.writeFileString(file, JSON.stringify(merged, null, 2)).pipe(Effect.orDie)
+          next = merged
+        } else {
+          const updated = patchJsonc(before, input)
+          next = parseConfig(updated, file)
+          yield* fs.writeFileString(file, updated).pipe(Effect.orDie)
+        }
 
-          yield* invalidate()
-          return next
-        })
+        yield* invalidate()
+        return next
+      })
 
-        return Service.of({
-          get,
-          getGlobal,
-          getConsoleState,
-          installDependencies,
-          update,
-          updateGlobal,
-          invalidate,
-          directories,
-          waitForDependencies,
-        })
-      }),
-    )
+      return Service.of({
+        get,
+        getGlobal,
+        getConsoleState,
+        installDependencies,
+        update,
+        updateGlobal,
+        invalidate,
+        directories,
+        waitForDependencies,
+      })
+    }),
+  )
 
   export const defaultLayer = layer.pipe(
     Layer.provide(AppFileSystem.defaultLayer),
+    Layer.provide(Env.defaultLayer),
     Layer.provide(Auth.defaultLayer),
     Layer.provide(Account.defaultLayer),
   )

+ 41 - 13
packages/opencode/src/env/index.ts

@@ -1,28 +1,56 @@
-import { Instance } from "../project/instance"
+import { Context, Effect, Layer } from "effect"
+import { InstanceState } from "@/effect/instance-state"
+import { makeRuntime } from "@/effect/run-service"
 
 export namespace Env {
-  const state = Instance.state(() => {
-    // Create a shallow copy to isolate environment per instance
-    // Prevents parallel tests from interfering with each other's env vars
-    return { ...process.env } as Record<string, string | undefined>
-  })
+  type State = Record<string, string | undefined>
+
+  export interface Interface {
+    readonly get: (key: string) => Effect.Effect<string | undefined>
+    readonly all: () => Effect.Effect<State>
+    readonly set: (key: string, value: string) => Effect.Effect<void>
+    readonly remove: (key: string) => Effect.Effect<void>
+  }
+
+  export class Service extends Context.Service<Service, Interface>()("@opencode/Env") {}
+
+  export const layer = Layer.effect(
+    Service,
+    Effect.gen(function* () {
+      const state = yield* InstanceState.make<State>(Effect.fn("Env.state")(() => Effect.succeed({ ...process.env })))
+
+      const get = Effect.fn("Env.get")((key: string) => InstanceState.use(state, (env) => env[key]))
+      const all = Effect.fn("Env.all")(() => InstanceState.get(state))
+      const set = Effect.fn("Env.set")(function* (key: string, value: string) {
+        const env = yield* InstanceState.get(state)
+        env[key] = value
+      })
+      const remove = Effect.fn("Env.remove")(function* (key: string) {
+        const env = yield* InstanceState.get(state)
+        delete env[key]
+      })
+
+      return Service.of({ get, all, set, remove })
+    }),
+  )
+
+  export const defaultLayer = layer
+
+  const rt = makeRuntime(Service, defaultLayer)
 
   export function get(key: string) {
-    const env = state()
-    return env[key]
+    return rt.runSync((svc) => svc.get(key))
   }
 
   export function all() {
-    return state()
+    return rt.runSync((svc) => svc.all())
   }
 
   export function set(key: string, value: string) {
-    const env = state()
-    env[key] = value
+    return rt.runSync((svc) => svc.set(key, value))
   }
 
   export function remove(key: string) {
-    const env = state()
-    delete env[key]
+    return rt.runSync((svc) => svc.remove(key))
   }
 }

+ 618 - 622
packages/opencode/src/provider/provider.ts

@@ -116,12 +116,6 @@ export namespace Provider {
     })
   }
 
-  function e2eURL() {
-    const url = Env.get("OPENCODE_E2E_LLM_URL")
-    if (typeof url !== "string" || url === "") return
-    return url
-  }
-
   type BundledSDK = {
     languageModel(modelId: string): LanguageModelV3
   }
@@ -166,6 +160,8 @@ export namespace Provider {
   type CustomDep = {
     auth: (id: string) => Effect.Effect<Auth.Info | undefined>
     config: () => Effect.Effect<Config.Info>
+    env: () => Effect.Effect<Record<string, string | undefined>>
+    get: (key: string) => Effect.Effect<string | undefined>
   }
 
   function useLanguageModel(sdk: any) {
@@ -184,7 +180,7 @@ export namespace Provider {
           },
         }),
       opencode: Effect.fnUntraced(function* (input: Info) {
-        const env = Env.all()
+        const env = yield* dep.env()
         const hasKey = iife(() => {
           if (input.env.some((item) => env[item])) return true
           return false
@@ -231,14 +227,15 @@ export namespace Provider {
           },
           options: {},
         }),
-      azure: (provider) => {
+      azure: Effect.fnUntraced(function* (provider: Info) {
+        const env = yield* dep.env()
         const resource = iife(() => {
           const name = provider.options?.resourceName
           if (typeof name === "string" && name.trim() !== "") return name
-          return Env.get("AZURE_RESOURCE_NAME")
+          return env["AZURE_RESOURCE_NAME"]
         })
 
-        return Effect.succeed({
+        return {
           autoload: false,
           async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
             if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
@@ -254,11 +251,11 @@ export namespace Provider {
               ...(resource && { AZURE_RESOURCE_NAME: resource }),
             }
           },
-        })
-      },
-      "azure-cognitive-services": () => {
-        const resourceName = Env.get("AZURE_COGNITIVE_SERVICES_RESOURCE_NAME")
-        return Effect.succeed({
+        }
+      }),
+      "azure-cognitive-services": Effect.fnUntraced(function* () {
+        const resourceName = yield* dep.get("AZURE_COGNITIVE_SERVICES_RESOURCE_NAME")
+        return {
           autoload: false,
           async getModel(sdk: any, modelID: string, options?: Record<string, any>) {
             if (useLanguageModel(sdk)) return sdk.languageModel(modelID)
@@ -271,23 +268,24 @@ export namespace Provider {
           options: {
             baseURL: resourceName ? `https://${resourceName}.cognitiveservices.azure.com/openai` : undefined,
           },
-        })
-      },
+        }
+      }),
       "amazon-bedrock": Effect.fnUntraced(function* () {
         const providerConfig = (yield* dep.config()).provider?.["amazon-bedrock"]
         const auth = yield* dep.auth("amazon-bedrock")
+        const env = yield* dep.env()
 
         // Region precedence: 1) config file, 2) env var, 3) default
         const configRegion = providerConfig?.options?.region
-        const envRegion = Env.get("AWS_REGION")
+        const envRegion = env["AWS_REGION"]
         const defaultRegion = configRegion ?? envRegion ?? "us-east-1"
 
         // Profile: config file takes precedence over env var
         const configProfile = providerConfig?.options?.profile
-        const envProfile = Env.get("AWS_PROFILE")
+        const envProfile = env["AWS_PROFILE"]
         const profile = configProfile ?? envProfile
 
-        const awsAccessKeyId = Env.get("AWS_ACCESS_KEY_ID")
+        const awsAccessKeyId = env["AWS_ACCESS_KEY_ID"]
 
         // TODO: Using process.env directly because Env.set only updates a process.env shallow copy,
         // until the scope of the Env API is clarified (test only or runtime?)
@@ -301,7 +299,7 @@ export namespace Provider {
           return undefined
         })
 
-        const awsWebIdentityTokenFile = Env.get("AWS_WEB_IDENTITY_TOKEN_FILE")
+        const awsWebIdentityTokenFile = env["AWS_WEB_IDENTITY_TOKEN_FILE"]
 
         const containerCreds = Boolean(
           process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI || process.env.AWS_CONTAINER_CREDENTIALS_FULL_URI,
@@ -439,24 +437,22 @@ export namespace Provider {
             },
           },
         }),
-      "google-vertex": (provider) => {
+      "google-vertex": Effect.fnUntraced(function* (provider: Info) {
+        const env = yield* dep.env()
         const project =
-          provider.options?.project ??
-          Env.get("GOOGLE_CLOUD_PROJECT") ??
-          Env.get("GCP_PROJECT") ??
-          Env.get("GCLOUD_PROJECT")
+          provider.options?.project ?? env["GOOGLE_CLOUD_PROJECT"] ?? env["GCP_PROJECT"] ?? env["GCLOUD_PROJECT"]
 
         const location = String(
           provider.options?.location ??
-            Env.get("GOOGLE_VERTEX_LOCATION") ??
-            Env.get("GOOGLE_CLOUD_LOCATION") ??
-            Env.get("VERTEX_LOCATION") ??
+            env["GOOGLE_VERTEX_LOCATION"] ??
+            env["GOOGLE_CLOUD_LOCATION"] ??
+            env["VERTEX_LOCATION"] ??
             "us-central1",
         )
 
         const autoload = Boolean(project)
-        if (!autoload) return Effect.succeed({ autoload: false })
-        return Effect.succeed({
+        if (!autoload) return { autoload: false }
+        return {
           autoload: true,
           vars(_options: Record<string, any>) {
             const endpoint =
@@ -485,14 +481,15 @@ export namespace Provider {
             const id = String(modelID).trim()
             return sdk.languageModel(id)
           },
-        })
-      },
-      "google-vertex-anthropic": () => {
-        const project = Env.get("GOOGLE_CLOUD_PROJECT") ?? Env.get("GCP_PROJECT") ?? Env.get("GCLOUD_PROJECT")
-        const location = Env.get("GOOGLE_CLOUD_LOCATION") ?? Env.get("VERTEX_LOCATION") ?? "global"
+        }
+      }),
+      "google-vertex-anthropic": Effect.fnUntraced(function* () {
+        const env = yield* dep.env()
+        const project = env["GOOGLE_CLOUD_PROJECT"] ?? env["GCP_PROJECT"] ?? env["GCLOUD_PROJECT"]
+        const location = env["GOOGLE_CLOUD_LOCATION"] ?? env["VERTEX_LOCATION"] ?? "global"
         const autoload = Boolean(project)
-        if (!autoload) return Effect.succeed({ autoload: false })
-        return Effect.succeed({
+        if (!autoload) return { autoload: false }
+        return {
           autoload: true,
           options: {
             project,
@@ -502,8 +499,8 @@ export namespace Provider {
             const id = String(modelID).trim()
             return sdk.languageModel(id)
           },
-        })
-      },
+        }
+      }),
       "sap-ai-core": Effect.fnUntraced(function* () {
         const auth = yield* dep.auth("sap-ai-core")
         // TODO: Using process.env directly because Env.set only updates a shallow copy (not process.env),
@@ -539,14 +536,15 @@ export namespace Provider {
           },
         }),
       gitlab: Effect.fnUntraced(function* (input: Info) {
-        const instanceUrl = Env.get("GITLAB_INSTANCE_URL") || "https://gitlab.com"
+        const instanceUrl = (yield* dep.get("GITLAB_INSTANCE_URL")) || "https://gitlab.com"
 
         const auth = yield* dep.auth(input.id)
         const apiKey = yield* Effect.sync(() => {
           if (auth?.type === "oauth") return auth.access
           if (auth?.type === "api") return auth.key
-          return Env.get("GITLAB_TOKEN")
+          return undefined
         })
+        const token = apiKey ?? (yield* dep.get("GITLAB_TOKEN"))
 
         const providerConfig = (yield* dep.config()).provider?.["gitlab"]
 
@@ -563,10 +561,10 @@ export namespace Provider {
         }
 
         return {
-          autoload: !!apiKey,
+          autoload: !!token,
           options: {
             instanceUrl,
-            apiKey,
+            apiKey: token,
             aiGatewayHeaders,
             featureFlags,
           },
@@ -681,8 +679,8 @@ export namespace Provider {
         if (input.options?.baseURL) return { autoload: false }
 
         const auth = yield* dep.auth(input.id)
-        const accountId =
-          Env.get("CLOUDFLARE_ACCOUNT_ID") || (auth?.type === "api" ? auth.metadata?.accountId : undefined)
+        const env = yield* dep.env()
+        const accountId = env["CLOUDFLARE_ACCOUNT_ID"] || (auth?.type === "api" ? auth.metadata?.accountId : undefined)
         if (!accountId)
           return {
             autoload: false,
@@ -694,7 +692,7 @@ export namespace Provider {
           }
 
         const apiKey = yield* Effect.gen(function* () {
-          const envToken = Env.get("CLOUDFLARE_API_KEY")
+          const envToken = env["CLOUDFLARE_API_KEY"]
           if (envToken) return envToken
           if (auth?.type === "api") return auth.key
           return undefined
@@ -723,10 +721,9 @@ export namespace Provider {
         if (input.options?.baseURL) return { autoload: false }
 
         const auth = yield* dep.auth(input.id)
-        const accountId =
-          Env.get("CLOUDFLARE_ACCOUNT_ID") || (auth?.type === "api" ? auth.metadata?.accountId : undefined)
-        const gateway =
-          Env.get("CLOUDFLARE_GATEWAY_ID") || (auth?.type === "api" ? auth.metadata?.gatewayId : undefined)
+        const env = yield* dep.env()
+        const accountId = env["CLOUDFLARE_ACCOUNT_ID"] || (auth?.type === "api" ? auth.metadata?.accountId : undefined)
+        const gateway = env["CLOUDFLARE_GATEWAY_ID"] || (auth?.type === "api" ? auth.metadata?.gatewayId : undefined)
 
         if (!accountId || !gateway) {
           const missing = [
@@ -745,7 +742,7 @@ export namespace Provider {
 
         // Get API token from env or auth - required for authenticated gateways
         const apiToken = yield* Effect.gen(function* () {
-          const envToken = Env.get("CLOUDFLARE_API_TOKEN") || Env.get("CF_AIG_TOKEN")
+          const envToken = env["CLOUDFLARE_API_TOKEN"] || env["CF_AIG_TOKEN"]
           if (envToken) return envToken
           if (auth?.type === "api") return auth.key
           return undefined
@@ -1030,662 +1027,661 @@ export namespace Provider {
     }
   }
 
-  const layer: Layer.Layer<Service, never, Config.Service | Auth.Service | Plugin.Service | AppFileSystem.Service> =
-    Layer.effect(
-      Service,
-      Effect.gen(function* () {
-        const fs = yield* AppFileSystem.Service
-        const config = yield* Config.Service
-        const auth = yield* Auth.Service
-        const plugin = yield* Plugin.Service
-
-        const state = yield* InstanceState.make<State>(() =>
-          Effect.gen(function* () {
-            using _ = log.time("state")
-            const cfg = yield* config.get()
-            const modelsDev = yield* Effect.promise(() => ModelsDev.get())
-            const database = mapValues(modelsDev, fromModelsDevProvider)
-
-            const providers: Record<ProviderID, Info> = {} as Record<ProviderID, Info>
-            const languages = new Map<string, LanguageModelV3>()
-            const modelLoaders: {
-              [providerID: string]: CustomModelLoader
-            } = {}
-            const varsLoaders: {
-              [providerID: string]: CustomVarsLoader
-            } = {}
-            const sdk = new Map<string, BundledSDK>()
-            const discoveryLoaders: {
-              [providerID: string]: CustomDiscoverModels
-            } = {}
-            const dep = {
-              auth: (id: string) => auth.get(id).pipe(Effect.orDie),
-              config: () => config.get(),
-            }
+  const layer: Layer.Layer<
+    Service,
+    never,
+    Config.Service | Auth.Service | Plugin.Service | AppFileSystem.Service | Env.Service
+  > = Layer.effect(
+    Service,
+    Effect.gen(function* () {
+      const fs = yield* AppFileSystem.Service
+      const config = yield* Config.Service
+      const auth = yield* Auth.Service
+      const env = yield* Env.Service
+      const plugin = yield* Plugin.Service
+
+      const state = yield* InstanceState.make<State>(() =>
+        Effect.gen(function* () {
+          using _ = log.time("state")
+          const cfg = yield* config.get()
+          const modelsDev = yield* Effect.promise(() => ModelsDev.get())
+          const database = mapValues(modelsDev, fromModelsDevProvider)
+
+          const providers: Record<ProviderID, Info> = {} as Record<ProviderID, Info>
+          const languages = new Map<string, LanguageModelV3>()
+          const modelLoaders: {
+            [providerID: string]: CustomModelLoader
+          } = {}
+          const varsLoaders: {
+            [providerID: string]: CustomVarsLoader
+          } = {}
+          const sdk = new Map<string, BundledSDK>()
+          const discoveryLoaders: {
+            [providerID: string]: CustomDiscoverModels
+          } = {}
+          const dep = {
+            auth: (id: string) => auth.get(id).pipe(Effect.orDie),
+            config: () => config.get(),
+            env: () => env.all(),
+            get: (key: string) => env.get(key),
+          }
 
-            log.info("init")
+          log.info("init")
 
-            function mergeProvider(providerID: ProviderID, provider: Partial<Info>) {
-              const existing = providers[providerID]
-              if (existing) {
-                // @ts-expect-error
-                providers[providerID] = mergeDeep(existing, provider)
-                return
-              }
-              const match = database[providerID]
-              if (!match) return
+          function mergeProvider(providerID: ProviderID, provider: Partial<Info>) {
+            const existing = providers[providerID]
+            if (existing) {
               // @ts-expect-error
-              providers[providerID] = mergeDeep(match, provider)
+              providers[providerID] = mergeDeep(existing, provider)
+              return
             }
+            const match = database[providerID]
+            if (!match) return
+            // @ts-expect-error
+            providers[providerID] = mergeDeep(match, provider)
+          }
 
-            // load plugins first so config() hook runs before reading cfg.provider
-            const plugins = yield* plugin.list()
+          // load plugins first so config() hook runs before reading cfg.provider
+          const plugins = yield* plugin.list()
 
-            // now read config providers - includes any modifications from plugin config() hook
-            const configProviders = Object.entries(cfg.provider ?? {})
-            const disabled = new Set(cfg.disabled_providers ?? [])
-            const enabled = cfg.enabled_providers ? new Set(cfg.enabled_providers) : null
+          // now read config providers - includes any modifications from plugin config() hook
+          const configProviders = Object.entries(cfg.provider ?? {})
+          const disabled = new Set(cfg.disabled_providers ?? [])
+          const enabled = cfg.enabled_providers ? new Set(cfg.enabled_providers) : null
 
-            function isProviderAllowed(providerID: ProviderID): boolean {
-              if (enabled && !enabled.has(providerID)) return false
-              if (disabled.has(providerID)) return false
-              return true
-            }
+          function isProviderAllowed(providerID: ProviderID): boolean {
+            if (enabled && !enabled.has(providerID)) return false
+            if (disabled.has(providerID)) return false
+            return true
+          }
 
-            // extend database from config
-            for (const [providerID, provider] of configProviders) {
-              const existing = database[providerID]
-              const parsed: Info = {
-                id: ProviderID.make(providerID),
-                name: provider.name ?? existing?.name ?? providerID,
-                env: provider.env ?? existing?.env ?? [],
-                options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
-                source: "config",
-                models: existing?.models ?? {},
-              }
+          // extend database from config
+          for (const [providerID, provider] of configProviders) {
+            const existing = database[providerID]
+            const parsed: Info = {
+              id: ProviderID.make(providerID),
+              name: provider.name ?? existing?.name ?? providerID,
+              env: provider.env ?? existing?.env ?? [],
+              options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
+              source: "config",
+              models: existing?.models ?? {},
+            }
 
-              for (const [modelID, model] of Object.entries(provider.models ?? {})) {
-                const existingModel = parsed.models[model.id ?? modelID]
-                const name = iife(() => {
-                  if (model.name) return model.name
-                  if (model.id && model.id !== modelID) return modelID
-                  return existingModel?.name ?? modelID
-                })
-                const parsedModel: Model = {
-                  id: ModelID.make(modelID),
-                  api: {
-                    id: model.id ?? existingModel?.api.id ?? modelID,
-                    npm:
-                      model.provider?.npm ??
-                      provider.npm ??
-                      existingModel?.api.npm ??
-                      modelsDev[providerID]?.npm ??
-                      "@ai-sdk/openai-compatible",
-                    url: model.provider?.api ?? provider?.api ?? existingModel?.api.url ?? modelsDev[providerID]?.api,
+            for (const [modelID, model] of Object.entries(provider.models ?? {})) {
+              const existingModel = parsed.models[model.id ?? modelID]
+              const name = iife(() => {
+                if (model.name) return model.name
+                if (model.id && model.id !== modelID) return modelID
+                return existingModel?.name ?? modelID
+              })
+              const parsedModel: Model = {
+                id: ModelID.make(modelID),
+                api: {
+                  id: model.id ?? existingModel?.api.id ?? modelID,
+                  npm:
+                    model.provider?.npm ??
+                    provider.npm ??
+                    existingModel?.api.npm ??
+                    modelsDev[providerID]?.npm ??
+                    "@ai-sdk/openai-compatible",
+                  url: model.provider?.api ?? provider?.api ?? existingModel?.api.url ?? modelsDev[providerID]?.api,
+                },
+                status: model.status ?? existingModel?.status ?? "active",
+                name,
+                providerID: ProviderID.make(providerID),
+                capabilities: {
+                  temperature: model.temperature ?? existingModel?.capabilities.temperature ?? false,
+                  reasoning: model.reasoning ?? existingModel?.capabilities.reasoning ?? false,
+                  attachment: model.attachment ?? existingModel?.capabilities.attachment ?? false,
+                  toolcall: model.tool_call ?? existingModel?.capabilities.toolcall ?? true,
+                  input: {
+                    text: model.modalities?.input?.includes("text") ?? existingModel?.capabilities.input.text ?? true,
+                    audio:
+                      model.modalities?.input?.includes("audio") ?? existingModel?.capabilities.input.audio ?? false,
+                    image:
+                      model.modalities?.input?.includes("image") ?? existingModel?.capabilities.input.image ?? false,
+                    video:
+                      model.modalities?.input?.includes("video") ?? existingModel?.capabilities.input.video ?? false,
+                    pdf: model.modalities?.input?.includes("pdf") ?? existingModel?.capabilities.input.pdf ?? false,
                   },
-                  status: model.status ?? existingModel?.status ?? "active",
-                  name,
-                  providerID: ProviderID.make(providerID),
-                  capabilities: {
-                    temperature: model.temperature ?? existingModel?.capabilities.temperature ?? false,
-                    reasoning: model.reasoning ?? existingModel?.capabilities.reasoning ?? false,
-                    attachment: model.attachment ?? existingModel?.capabilities.attachment ?? false,
-                    toolcall: model.tool_call ?? existingModel?.capabilities.toolcall ?? true,
-                    input: {
-                      text: model.modalities?.input?.includes("text") ?? existingModel?.capabilities.input.text ?? true,
-                      audio:
-                        model.modalities?.input?.includes("audio") ?? existingModel?.capabilities.input.audio ?? false,
-                      image:
-                        model.modalities?.input?.includes("image") ?? existingModel?.capabilities.input.image ?? false,
-                      video:
-                        model.modalities?.input?.includes("video") ?? existingModel?.capabilities.input.video ?? false,
-                      pdf: model.modalities?.input?.includes("pdf") ?? existingModel?.capabilities.input.pdf ?? false,
-                    },
-                    output: {
-                      text:
-                        model.modalities?.output?.includes("text") ?? existingModel?.capabilities.output.text ?? true,
-                      audio:
-                        model.modalities?.output?.includes("audio") ??
-                        existingModel?.capabilities.output.audio ??
-                        false,
-                      image:
-                        model.modalities?.output?.includes("image") ??
-                        existingModel?.capabilities.output.image ??
-                        false,
-                      video:
-                        model.modalities?.output?.includes("video") ??
-                        existingModel?.capabilities.output.video ??
-                        false,
-                      pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false,
-                    },
-                    interleaved: model.interleaved ?? false,
+                  output: {
+                    text: model.modalities?.output?.includes("text") ?? existingModel?.capabilities.output.text ?? true,
+                    audio:
+                      model.modalities?.output?.includes("audio") ?? existingModel?.capabilities.output.audio ?? false,
+                    image:
+                      model.modalities?.output?.includes("image") ?? existingModel?.capabilities.output.image ?? false,
+                    video:
+                      model.modalities?.output?.includes("video") ?? existingModel?.capabilities.output.video ?? false,
+                    pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false,
                   },
-                  cost: {
-                    input: model?.cost?.input ?? existingModel?.cost?.input ?? 0,
-                    output: model?.cost?.output ?? existingModel?.cost?.output ?? 0,
-                    cache: {
-                      read: model?.cost?.cache_read ?? existingModel?.cost?.cache.read ?? 0,
-                      write: model?.cost?.cache_write ?? existingModel?.cost?.cache.write ?? 0,
-                    },
+                  interleaved: model.interleaved ?? false,
+                },
+                cost: {
+                  input: model?.cost?.input ?? existingModel?.cost?.input ?? 0,
+                  output: model?.cost?.output ?? existingModel?.cost?.output ?? 0,
+                  cache: {
+                    read: model?.cost?.cache_read ?? existingModel?.cost?.cache.read ?? 0,
+                    write: model?.cost?.cache_write ?? existingModel?.cost?.cache.write ?? 0,
                   },
-                  options: mergeDeep(existingModel?.options ?? {}, model.options ?? {}),
-                  limit: {
-                    context: model.limit?.context ?? existingModel?.limit?.context ?? 0,
-                    input: model.limit?.input ?? existingModel?.limit?.input,
-                    output: model.limit?.output ?? existingModel?.limit?.output ?? 0,
-                  },
-                  headers: mergeDeep(existingModel?.headers ?? {}, model.headers ?? {}),
-                  family: model.family ?? existingModel?.family ?? "",
-                  release_date: model.release_date ?? existingModel?.release_date ?? "",
-                  variants: {},
-                }
-                const merged = mergeDeep(ProviderTransform.variants(parsedModel), model.variants ?? {})
-                parsedModel.variants = mapValues(
-                  pickBy(merged, (v) => !v.disabled),
-                  (v) => omit(v, ["disabled"]),
-                )
-                parsed.models[modelID] = parsedModel
+                },
+                options: mergeDeep(existingModel?.options ?? {}, model.options ?? {}),
+                limit: {
+                  context: model.limit?.context ?? existingModel?.limit?.context ?? 0,
+                  input: model.limit?.input ?? existingModel?.limit?.input,
+                  output: model.limit?.output ?? existingModel?.limit?.output ?? 0,
+                },
+                headers: mergeDeep(existingModel?.headers ?? {}, model.headers ?? {}),
+                family: model.family ?? existingModel?.family ?? "",
+                release_date: model.release_date ?? existingModel?.release_date ?? "",
+                variants: {},
               }
-              database[providerID] = parsed
+              const merged = mergeDeep(ProviderTransform.variants(parsedModel), model.variants ?? {})
+              parsedModel.variants = mapValues(
+                pickBy(merged, (v) => !v.disabled),
+                (v) => omit(v, ["disabled"]),
+              )
+              parsed.models[modelID] = parsedModel
             }
+            database[providerID] = parsed
+          }
+
+          // load env
+          const envs = yield* env.all()
+          for (const [id, provider] of Object.entries(database)) {
+            const providerID = ProviderID.make(id)
+            if (disabled.has(providerID)) continue
+            const apiKey = provider.env.map((item) => envs[item]).find(Boolean)
+            if (!apiKey) continue
+            mergeProvider(providerID, {
+              source: "env",
+              key: provider.env.length === 1 ? apiKey : undefined,
+            })
+          }
 
-            // load env
-            const env = Env.all()
-            for (const [id, provider] of Object.entries(database)) {
-              const providerID = ProviderID.make(id)
-              if (disabled.has(providerID)) continue
-              const apiKey = provider.env.map((item) => env[item]).find(Boolean)
-              if (!apiKey) continue
+          // load apikeys
+          const auths = yield* auth.all().pipe(Effect.orDie)
+          for (const [id, provider] of Object.entries(auths)) {
+            const providerID = ProviderID.make(id)
+            if (disabled.has(providerID)) continue
+            if (provider.type === "api") {
               mergeProvider(providerID, {
-                source: "env",
-                key: provider.env.length === 1 ? apiKey : undefined,
+                source: "api",
+                key: provider.key,
               })
             }
+          }
 
-            // load apikeys
-            const auths = yield* auth.all().pipe(Effect.orDie)
-            for (const [id, provider] of Object.entries(auths)) {
-              const providerID = ProviderID.make(id)
-              if (disabled.has(providerID)) continue
-              if (provider.type === "api") {
-                mergeProvider(providerID, {
-                  source: "api",
-                  key: provider.key,
-                })
-              }
-            }
+          // plugin auth loader - database now has entries for config providers
+          for (const plugin of plugins) {
+            if (!plugin.auth) continue
+            const providerID = ProviderID.make(plugin.auth.provider)
+            if (disabled.has(providerID)) continue
+
+            const stored = yield* auth.get(providerID).pipe(Effect.orDie)
+            if (!stored) continue
+            if (!plugin.auth.loader) continue
+
+            const options = yield* Effect.promise(() =>
+              plugin.auth!.loader!(
+                () =>
+                  Effect.runPromise(auth.get(providerID).pipe(Effect.orDie, Effect.provide(EffectLogger.layer))) as any,
+                database[plugin.auth!.provider],
+              ),
+            )
+            const opts = options ?? {}
+            const patch: Partial<Info> = providers[providerID] ? { options: opts } : { source: "custom", options: opts }
+            mergeProvider(providerID, patch)
+          }
 
-            // plugin auth loader - database now has entries for config providers
-            for (const plugin of plugins) {
-              if (!plugin.auth) continue
-              const providerID = ProviderID.make(plugin.auth.provider)
-              if (disabled.has(providerID)) continue
-
-              const stored = yield* auth.get(providerID).pipe(Effect.orDie)
-              if (!stored) continue
-              if (!plugin.auth.loader) continue
-
-              const options = yield* Effect.promise(() =>
-                plugin.auth!.loader!(
-                  () =>
-                    Effect.runPromise(
-                      auth.get(providerID).pipe(Effect.orDie, Effect.provide(EffectLogger.layer)),
-                    ) as any,
-                  database[plugin.auth!.provider],
-                ),
-              )
-              const opts = options ?? {}
+          for (const [id, fn] of Object.entries(custom(dep))) {
+            const providerID = ProviderID.make(id)
+            if (disabled.has(providerID)) continue
+            const data = database[providerID]
+            if (!data) {
+              log.error("Provider does not exist in model list " + providerID)
+              continue
+            }
+            const result = yield* fn(data)
+            if (result && (result.autoload || providers[providerID])) {
+              if (result.getModel) modelLoaders[providerID] = result.getModel
+              if (result.vars) varsLoaders[providerID] = result.vars
+              if (result.discoverModels) discoveryLoaders[providerID] = result.discoverModels
+              const opts = result.options ?? {}
               const patch: Partial<Info> = providers[providerID]
                 ? { options: opts }
                 : { source: "custom", options: opts }
               mergeProvider(providerID, patch)
             }
+          }
 
-            for (const [id, fn] of Object.entries(custom(dep))) {
-              const providerID = ProviderID.make(id)
-              if (disabled.has(providerID)) continue
-              const data = database[providerID]
-              if (!data) {
-                log.error("Provider does not exist in model list " + providerID)
-                continue
-              }
-              const result = yield* fn(data)
-              if (result && (result.autoload || providers[providerID])) {
-                if (result.getModel) modelLoaders[providerID] = result.getModel
-                if (result.vars) varsLoaders[providerID] = result.vars
-                if (result.discoverModels) discoveryLoaders[providerID] = result.discoverModels
-                const opts = result.options ?? {}
-                const patch: Partial<Info> = providers[providerID]
-                  ? { options: opts }
-                  : { source: "custom", options: opts }
-                mergeProvider(providerID, patch)
-              }
-            }
-
-            // load config - re-apply with updated data
-            for (const [id, provider] of configProviders) {
-              const providerID = ProviderID.make(id)
-              const partial: Partial<Info> = { source: "config" }
-              if (provider.env) partial.env = provider.env
-              if (provider.name) partial.name = provider.name
-              if (provider.options) partial.options = provider.options
-              mergeProvider(providerID, partial)
-            }
+          // load config - re-apply with updated data
+          for (const [id, provider] of configProviders) {
+            const providerID = ProviderID.make(id)
+            const partial: Partial<Info> = { source: "config" }
+            if (provider.env) partial.env = provider.env
+            if (provider.name) partial.name = provider.name
+            if (provider.options) partial.options = provider.options
+            mergeProvider(providerID, partial)
+          }
 
-            const gitlab = ProviderID.make("gitlab")
-            if (discoveryLoaders[gitlab] && providers[gitlab] && isProviderAllowed(gitlab)) {
-              yield* Effect.promise(async () => {
-                try {
-                  const discovered = await discoveryLoaders[gitlab]()
-                  for (const [modelID, model] of Object.entries(discovered)) {
-                    if (!providers[gitlab].models[modelID]) {
-                      providers[gitlab].models[modelID] = model
-                    }
+          const gitlab = ProviderID.make("gitlab")
+          if (discoveryLoaders[gitlab] && providers[gitlab] && isProviderAllowed(gitlab)) {
+            yield* Effect.promise(async () => {
+              try {
+                const discovered = await discoveryLoaders[gitlab]()
+                for (const [modelID, model] of Object.entries(discovered)) {
+                  if (!providers[gitlab].models[modelID]) {
+                    providers[gitlab].models[modelID] = model
                   }
-                } catch (e) {
-                  log.warn("state discovery error", { id: "gitlab", error: e })
                 }
-              })
-            }
+              } catch (e) {
+                log.warn("state discovery error", { id: "gitlab", error: e })
+              }
+            })
+          }
 
-            for (const hook of plugins) {
-              const p = hook.provider
-              const models = p?.models
-              if (!p || !models) continue
-
-              const providerID = ProviderID.make(p.id)
-              if (disabled.has(providerID)) continue
-
-              const provider = providers[providerID]
-              if (!provider) continue
-              const pluginAuth = yield* auth.get(providerID).pipe(Effect.orDie)
-
-              provider.models = yield* Effect.promise(async () => {
-                const next = await models(provider, { auth: pluginAuth })
-                return Object.fromEntries(
-                  Object.entries(next).map(([id, model]) => [
-                    id,
-                    {
-                      ...model,
-                      id: ModelID.make(id),
-                      providerID,
-                    },
-                  ]),
-                )
-              })
-            }
+          for (const hook of plugins) {
+            const p = hook.provider
+            const models = p?.models
+            if (!p || !models) continue
 
-            for (const [id, provider] of Object.entries(providers)) {
-              const providerID = ProviderID.make(id)
-              if (!isProviderAllowed(providerID)) {
-                delete providers[providerID]
-                continue
-              }
+            const providerID = ProviderID.make(p.id)
+            if (disabled.has(providerID)) continue
 
-              const configProvider = cfg.provider?.[providerID]
+            const provider = providers[providerID]
+            if (!provider) continue
+            const pluginAuth = yield* auth.get(providerID).pipe(Effect.orDie)
 
-              for (const [modelID, model] of Object.entries(provider.models)) {
-                model.api.id = model.api.id ?? model.id ?? modelID
-                if (
-                  modelID === "gpt-5-chat-latest" ||
-                  (providerID === ProviderID.openrouter && modelID === "openai/gpt-5-chat")
-                )
-                  delete provider.models[modelID]
-                if (model.status === "alpha" && !Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS)
-                  delete provider.models[modelID]
-                if (model.status === "deprecated") delete provider.models[modelID]
-                if (
-                  (configProvider?.blacklist && configProvider.blacklist.includes(modelID)) ||
-                  (configProvider?.whitelist && !configProvider.whitelist.includes(modelID))
-                )
-                  delete provider.models[modelID]
+            provider.models = yield* Effect.promise(async () => {
+              const next = await models(provider, { auth: pluginAuth })
+              return Object.fromEntries(
+                Object.entries(next).map(([id, model]) => [
+                  id,
+                  {
+                    ...model,
+                    id: ModelID.make(id),
+                    providerID,
+                  },
+                ]),
+              )
+            })
+          }
 
-                model.variants = mapValues(ProviderTransform.variants(model), (v) => v)
+          for (const [id, provider] of Object.entries(providers)) {
+            const providerID = ProviderID.make(id)
+            if (!isProviderAllowed(providerID)) {
+              delete providers[providerID]
+              continue
+            }
 
-                const configVariants = configProvider?.models?.[modelID]?.variants
-                if (configVariants && model.variants) {
-                  const merged = mergeDeep(model.variants, configVariants)
-                  model.variants = mapValues(
-                    pickBy(merged, (v) => !v.disabled),
-                    (v) => omit(v, ["disabled"]),
-                  )
-                }
-              }
+            const configProvider = cfg.provider?.[providerID]
 
-              if (Object.keys(provider.models).length === 0) {
-                delete providers[providerID]
-                continue
-              }
+            for (const [modelID, model] of Object.entries(provider.models)) {
+              model.api.id = model.api.id ?? model.id ?? modelID
+              if (
+                modelID === "gpt-5-chat-latest" ||
+                (providerID === ProviderID.openrouter && modelID === "openai/gpt-5-chat")
+              )
+                delete provider.models[modelID]
+              if (model.status === "alpha" && !Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS) delete provider.models[modelID]
+              if (model.status === "deprecated") delete provider.models[modelID]
+              if (
+                (configProvider?.blacklist && configProvider.blacklist.includes(modelID)) ||
+                (configProvider?.whitelist && !configProvider.whitelist.includes(modelID))
+              )
+                delete provider.models[modelID]
+
+              model.variants = mapValues(ProviderTransform.variants(model), (v) => v)
 
-              log.info("found", { providerID })
+              const configVariants = configProvider?.models?.[modelID]?.variants
+              if (configVariants && model.variants) {
+                const merged = mergeDeep(model.variants, configVariants)
+                model.variants = mapValues(
+                  pickBy(merged, (v) => !v.disabled),
+                  (v) => omit(v, ["disabled"]),
+                )
+              }
             }
 
-            return {
-              models: languages,
-              providers,
-              sdk,
-              modelLoaders,
-              varsLoaders,
+            if (Object.keys(provider.models).length === 0) {
+              delete providers[providerID]
+              continue
             }
-          }),
-        )
 
-        const list = Effect.fn("Provider.list")(() => InstanceState.use(state, (s) => s.providers))
+            log.info("found", { providerID })
+          }
 
-        async function resolveSDK(model: Model, s: State) {
-          try {
-            using _ = log.time("getSDK", {
-              providerID: model.providerID,
-            })
-            const provider = s.providers[model.providerID]
-            const options = { ...provider.options }
+          return {
+            models: languages,
+            providers,
+            sdk,
+            modelLoaders,
+            varsLoaders,
+          }
+        }),
+      )
 
-            if (model.providerID === "google-vertex" && !model.api.npm.includes("@ai-sdk/openai-compatible")) {
-              delete options.fetch
-            }
+      const list = Effect.fn("Provider.list")(() => InstanceState.use(state, (s) => s.providers))
 
-            if (model.api.npm.includes("@ai-sdk/openai-compatible") && options["includeUsage"] !== false) {
-              options["includeUsage"] = true
-            }
+      async function resolveSDK(model: Model, s: State, envs: Record<string, string | undefined>) {
+        try {
+          using _ = log.time("getSDK", {
+            providerID: model.providerID,
+          })
+          const provider = s.providers[model.providerID]
+          const options = { ...provider.options }
 
-            const baseURL = iife(() => {
-              let url =
-                typeof options["baseURL"] === "string" && options["baseURL"] !== "" ? options["baseURL"] : model.api.url
-              if (!url) return
-
-              const loader = s.varsLoaders[model.providerID]
-              if (loader) {
-                const vars = loader(options)
-                for (const [key, value] of Object.entries(vars)) {
-                  const field = "${" + key + "}"
-                  url = url.replaceAll(field, value)
-                }
+          if (model.providerID === "google-vertex" && !model.api.npm.includes("@ai-sdk/openai-compatible")) {
+            delete options.fetch
+          }
+
+          if (model.api.npm.includes("@ai-sdk/openai-compatible") && options["includeUsage"] !== false) {
+            options["includeUsage"] = true
+          }
+
+          const baseURL = iife(() => {
+            let url =
+              typeof options["baseURL"] === "string" && options["baseURL"] !== "" ? options["baseURL"] : model.api.url
+            if (!url) return
+
+            const loader = s.varsLoaders[model.providerID]
+            if (loader) {
+              const vars = loader(options)
+              for (const [key, value] of Object.entries(vars)) {
+                const field = "${" + key + "}"
+                url = url.replaceAll(field, value)
               }
+            }
 
-              url = url.replace(/\$\{([^}]+)\}/g, (item, key) => {
-                const val = Env.get(String(key))
-                return val ?? item
-              })
-              return url
+            url = url.replace(/\$\{([^}]+)\}/g, (item, key) => {
+              const val = envs[String(key)]
+              return val ?? item
             })
+            return url
+          })
 
-            if (baseURL !== undefined) options["baseURL"] = baseURL
-            if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
-            if (model.headers)
-              options["headers"] = {
-                ...options["headers"],
-                ...model.headers,
-              }
+          if (baseURL !== undefined) options["baseURL"] = baseURL
+          if (options["apiKey"] === undefined && provider.key) options["apiKey"] = provider.key
+          if (model.headers)
+            options["headers"] = {
+              ...options["headers"],
+              ...model.headers,
+            }
 
-            const key = Hash.fast(
-              JSON.stringify({
-                providerID: model.providerID,
-                npm: model.api.npm,
-                options,
-              }),
-            )
-            const existing = s.sdk.get(key)
-            if (existing) return existing
-
-            const customFetch = options["fetch"]
-            const chunkTimeout = options["chunkTimeout"]
-            delete options["chunkTimeout"]
-
-            options["fetch"] = async (input: any, init?: BunFetchRequestInit) => {
-              const fetchFn = customFetch ?? fetch
-              const opts = init ?? {}
-              const chunkAbortCtl =
-                typeof chunkTimeout === "number" && chunkTimeout > 0 ? new AbortController() : undefined
-              const signals: AbortSignal[] = []
-
-              if (opts.signal) signals.push(opts.signal)
-              if (chunkAbortCtl) signals.push(chunkAbortCtl.signal)
-              if (options["timeout"] !== undefined && options["timeout"] !== null && options["timeout"] !== false)
-                signals.push(AbortSignal.timeout(options["timeout"]))
-
-              const combined =
-                signals.length === 0 ? null : signals.length === 1 ? signals[0] : AbortSignal.any(signals)
-              if (combined) opts.signal = combined
-
-              // Strip openai itemId metadata following what codex does
-              if (model.api.npm === "@ai-sdk/openai" && opts.body && opts.method === "POST") {
-                const body = JSON.parse(opts.body as string)
-                const isAzure = model.providerID.includes("azure")
-                const keepIds = isAzure && body.store === true
-                if (!keepIds && Array.isArray(body.input)) {
-                  for (const item of body.input) {
-                    if ("id" in item) {
-                      delete item.id
-                    }
+          const key = Hash.fast(
+            JSON.stringify({
+              providerID: model.providerID,
+              npm: model.api.npm,
+              options,
+            }),
+          )
+          const existing = s.sdk.get(key)
+          if (existing) return existing
+
+          const customFetch = options["fetch"]
+          const chunkTimeout = options["chunkTimeout"]
+          delete options["chunkTimeout"]
+
+          options["fetch"] = async (input: any, init?: BunFetchRequestInit) => {
+            const fetchFn = customFetch ?? fetch
+            const opts = init ?? {}
+            const chunkAbortCtl =
+              typeof chunkTimeout === "number" && chunkTimeout > 0 ? new AbortController() : undefined
+            const signals: AbortSignal[] = []
+
+            if (opts.signal) signals.push(opts.signal)
+            if (chunkAbortCtl) signals.push(chunkAbortCtl.signal)
+            if (options["timeout"] !== undefined && options["timeout"] !== null && options["timeout"] !== false)
+              signals.push(AbortSignal.timeout(options["timeout"]))
+
+            const combined = signals.length === 0 ? null : signals.length === 1 ? signals[0] : AbortSignal.any(signals)
+            if (combined) opts.signal = combined
+
+            // Strip openai itemId metadata following what codex does
+            if (model.api.npm === "@ai-sdk/openai" && opts.body && opts.method === "POST") {
+              const body = JSON.parse(opts.body as string)
+              const isAzure = model.providerID.includes("azure")
+              const keepIds = isAzure && body.store === true
+              if (!keepIds && Array.isArray(body.input)) {
+                for (const item of body.input) {
+                  if ("id" in item) {
+                    delete item.id
                   }
-                  opts.body = JSON.stringify(body)
                 }
+                opts.body = JSON.stringify(body)
               }
-
-              const res = await fetchFn(input, {
-                ...opts,
-                // @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
-                timeout: false,
-              })
-
-              if (!chunkAbortCtl) return res
-              return wrapSSE(res, chunkTimeout, chunkAbortCtl)
             }
 
-            const bundledFn = BUNDLED_PROVIDERS[model.api.npm]
-            if (bundledFn) {
-              log.info("using bundled provider", {
-                providerID: model.providerID,
-                pkg: model.api.npm,
-              })
-              const loaded = bundledFn({
-                name: model.providerID,
-                ...options,
-              })
-              s.sdk.set(key, loaded)
-              return loaded as SDK
-            }
-
-            let installedPath: string
-            if (!model.api.npm.startsWith("file://")) {
-              const item = await Npm.add(model.api.npm)
-              if (!item.entrypoint) throw new Error(`Package ${model.api.npm} has no import entrypoint`)
-              installedPath = item.entrypoint
-            } else {
-              log.info("loading local provider", { pkg: model.api.npm })
-              installedPath = model.api.npm
-            }
+            const res = await fetchFn(input, {
+              ...opts,
+              // @ts-ignore see here: https://github.com/oven-sh/bun/issues/16682
+              timeout: false,
+            })
 
-            const mod = await import(installedPath)
+            if (!chunkAbortCtl) return res
+            return wrapSSE(res, chunkTimeout, chunkAbortCtl)
+          }
 
-            const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
-            const loaded = fn({
+          const bundledFn = BUNDLED_PROVIDERS[model.api.npm]
+          if (bundledFn) {
+            log.info("using bundled provider", {
+              providerID: model.providerID,
+              pkg: model.api.npm,
+            })
+            const loaded = bundledFn({
               name: model.providerID,
               ...options,
             })
             s.sdk.set(key, loaded)
             return loaded as SDK
-          } catch (e) {
-            throw new InitError({ providerID: model.providerID }, { cause: e })
           }
-        }
 
-        const getProvider = Effect.fn("Provider.getProvider")((providerID: ProviderID) =>
-          InstanceState.use(state, (s) => s.providers[providerID]),
-        )
-
-        const getModel = Effect.fn("Provider.getModel")(function* (providerID: ProviderID, modelID: ModelID) {
-          const s = yield* InstanceState.get(state)
-          const provider = s.providers[providerID]
-          if (!provider) {
-            const available = Object.keys(s.providers)
-            const matches = fuzzysort.go(providerID, available, { limit: 3, threshold: -10000 })
-            throw new ModelNotFoundError({ providerID, modelID, suggestions: matches.map((m) => m.target) })
+          let installedPath: string
+          if (!model.api.npm.startsWith("file://")) {
+            const item = await Npm.add(model.api.npm)
+            if (!item.entrypoint) throw new Error(`Package ${model.api.npm} has no import entrypoint`)
+            installedPath = item.entrypoint
+          } else {
+            log.info("loading local provider", { pkg: model.api.npm })
+            installedPath = model.api.npm
           }
 
-          const info = provider.models[modelID]
-          if (!info) {
-            const available = Object.keys(provider.models)
-            const matches = fuzzysort.go(modelID, available, { limit: 3, threshold: -10000 })
-            throw new ModelNotFoundError({ providerID, modelID, suggestions: matches.map((m) => m.target) })
-          }
-          return info
-        })
+          const mod = await import(installedPath)
 
-        const getLanguage = Effect.fn("Provider.getLanguage")(function* (model: Model) {
-          const s = yield* InstanceState.get(state)
-          const key = `${model.providerID}/${model.id}`
-          if (s.models.has(key)) return s.models.get(key)!
-
-          return yield* Effect.promise(async () => {
-            const url = e2eURL()
-            if (url) {
-              const language = createOpenAICompatible({
-                name: model.providerID,
-                apiKey: "test-key",
-                baseURL: url,
-              }).chatModel(model.api.id)
-              s.models.set(key, language)
-              return language
-            }
+          const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
+          const loaded = fn({
+            name: model.providerID,
+            ...options,
+          })
+          s.sdk.set(key, loaded)
+          return loaded as SDK
+        } catch (e) {
+          throw new InitError({ providerID: model.providerID }, { cause: e })
+        }
+      }
 
-            const provider = s.providers[model.providerID]
-            const sdk = await resolveSDK(model, s)
+      const getProvider = Effect.fn("Provider.getProvider")((providerID: ProviderID) =>
+        InstanceState.use(state, (s) => s.providers[providerID]),
+      )
+
+      const getModel = Effect.fn("Provider.getModel")(function* (providerID: ProviderID, modelID: ModelID) {
+        const s = yield* InstanceState.get(state)
+        const provider = s.providers[providerID]
+        if (!provider) {
+          const available = Object.keys(s.providers)
+          const matches = fuzzysort.go(providerID, available, { limit: 3, threshold: -10000 })
+          throw new ModelNotFoundError({ providerID, modelID, suggestions: matches.map((m) => m.target) })
+        }
 
-            try {
-              const language = s.modelLoaders[model.providerID]
-                ? await s.modelLoaders[model.providerID](sdk, model.api.id, {
-                    ...provider.options,
-                    ...model.options,
-                  })
-                : sdk.languageModel(model.api.id)
-              s.models.set(key, language)
-              return language
-            } catch (e) {
-              if (e instanceof NoSuchModelError)
-                throw new ModelNotFoundError(
-                  {
-                    modelID: model.id,
-                    providerID: model.providerID,
-                  },
-                  { cause: e },
-                )
-              throw e
-            }
-          })
-        })
+        const info = provider.models[modelID]
+        if (!info) {
+          const available = Object.keys(provider.models)
+          const matches = fuzzysort.go(modelID, available, { limit: 3, threshold: -10000 })
+          throw new ModelNotFoundError({ providerID, modelID, suggestions: matches.map((m) => m.target) })
+        }
+        return info
+      })
+
+      const getLanguage = Effect.fn("Provider.getLanguage")(function* (model: Model) {
+        const s = yield* InstanceState.get(state)
+        const envs = yield* env.all()
+        const key = `${model.providerID}/${model.id}`
+        if (s.models.has(key)) return s.models.get(key)!
+
+        return yield* Effect.promise(async () => {
+          const url = (() => {
+            const item = envs["OPENCODE_E2E_LLM_URL"]
+            if (typeof item !== "string" || item === "") return
+            return item
+          })()
+          if (url) {
+            const language = createOpenAICompatible({
+              name: model.providerID,
+              apiKey: "test-key",
+              baseURL: url,
+            }).chatModel(model.api.id)
+            s.models.set(key, language)
+            return language
+          }
 
-        const closest = Effect.fn("Provider.closest")(function* (providerID: ProviderID, query: string[]) {
-          const s = yield* InstanceState.get(state)
-          const provider = s.providers[providerID]
-          if (!provider) return undefined
-          for (const item of query) {
-            for (const modelID of Object.keys(provider.models)) {
-              if (modelID.includes(item)) return { providerID, modelID }
-            }
+          const provider = s.providers[model.providerID]
+          const sdk = await resolveSDK(model, s, envs)
+
+          try {
+            const language = s.modelLoaders[model.providerID]
+              ? await s.modelLoaders[model.providerID](sdk, model.api.id, {
+                  ...provider.options,
+                  ...model.options,
+                })
+              : sdk.languageModel(model.api.id)
+            s.models.set(key, language)
+            return language
+          } catch (e) {
+            if (e instanceof NoSuchModelError)
+              throw new ModelNotFoundError(
+                {
+                  modelID: model.id,
+                  providerID: model.providerID,
+                },
+                { cause: e },
+              )
+            throw e
           }
-          return undefined
         })
+      })
+
+      const closest = Effect.fn("Provider.closest")(function* (providerID: ProviderID, query: string[]) {
+        const s = yield* InstanceState.get(state)
+        const provider = s.providers[providerID]
+        if (!provider) return undefined
+        for (const item of query) {
+          for (const modelID of Object.keys(provider.models)) {
+            if (modelID.includes(item)) return { providerID, modelID }
+          }
+        }
+        return undefined
+      })
 
-        const getSmallModel = Effect.fn("Provider.getSmallModel")(function* (providerID: ProviderID) {
-          const cfg = yield* config.get()
+      const getSmallModel = Effect.fn("Provider.getSmallModel")(function* (providerID: ProviderID) {
+        const cfg = yield* config.get()
 
-          if (cfg.small_model) {
-            const parsed = parseModel(cfg.small_model)
-            return yield* getModel(parsed.providerID, parsed.modelID)
-          }
+        if (cfg.small_model) {
+          const parsed = parseModel(cfg.small_model)
+          return yield* getModel(parsed.providerID, parsed.modelID)
+        }
 
-          const s = yield* InstanceState.get(state)
-          const provider = s.providers[providerID]
-          if (!provider) return undefined
-
-          let priority = [
-            "claude-haiku-4-5",
-            "claude-haiku-4.5",
-            "3-5-haiku",
-            "3.5-haiku",
-            "gemini-3-flash",
-            "gemini-2.5-flash",
-            "gpt-5-nano",
-          ]
-          if (providerID.startsWith("opencode")) {
-            priority = ["gpt-5-nano"]
-          }
-          if (providerID.startsWith("github-copilot")) {
-            priority = ["gpt-5-mini", "claude-haiku-4.5", ...priority]
-          }
-          for (const item of priority) {
-            if (providerID === ProviderID.amazonBedrock) {
-              const crossRegionPrefixes = ["global.", "us.", "eu."]
-              const candidates = Object.keys(provider.models).filter((m) => m.includes(item))
-
-              const globalMatch = candidates.find((m) => m.startsWith("global."))
-              if (globalMatch) return yield* getModel(providerID, ModelID.make(globalMatch))
-
-              const region = provider.options?.region
-              if (region) {
-                const regionPrefix = region.split("-")[0]
-                if (regionPrefix === "us" || regionPrefix === "eu") {
-                  const regionalMatch = candidates.find((m) => m.startsWith(`${regionPrefix}.`))
-                  if (regionalMatch) return yield* getModel(providerID, ModelID.make(regionalMatch))
-                }
+        const s = yield* InstanceState.get(state)
+        const provider = s.providers[providerID]
+        if (!provider) return undefined
+
+        let priority = [
+          "claude-haiku-4-5",
+          "claude-haiku-4.5",
+          "3-5-haiku",
+          "3.5-haiku",
+          "gemini-3-flash",
+          "gemini-2.5-flash",
+          "gpt-5-nano",
+        ]
+        if (providerID.startsWith("opencode")) {
+          priority = ["gpt-5-nano"]
+        }
+        if (providerID.startsWith("github-copilot")) {
+          priority = ["gpt-5-mini", "claude-haiku-4.5", ...priority]
+        }
+        for (const item of priority) {
+          if (providerID === ProviderID.amazonBedrock) {
+            const crossRegionPrefixes = ["global.", "us.", "eu."]
+            const candidates = Object.keys(provider.models).filter((m) => m.includes(item))
+
+            const globalMatch = candidates.find((m) => m.startsWith("global."))
+            if (globalMatch) return yield* getModel(providerID, ModelID.make(globalMatch))
+
+            const region = provider.options?.region
+            if (region) {
+              const regionPrefix = region.split("-")[0]
+              if (regionPrefix === "us" || regionPrefix === "eu") {
+                const regionalMatch = candidates.find((m) => m.startsWith(`${regionPrefix}.`))
+                if (regionalMatch) return yield* getModel(providerID, ModelID.make(regionalMatch))
               }
+            }
 
-              const unprefixed = candidates.find((m) => !crossRegionPrefixes.some((p) => m.startsWith(p)))
-              if (unprefixed) return yield* getModel(providerID, ModelID.make(unprefixed))
-            } else {
-              for (const model of Object.keys(provider.models)) {
-                if (model.includes(item)) return yield* getModel(providerID, ModelID.make(model))
-              }
+            const unprefixed = candidates.find((m) => !crossRegionPrefixes.some((p) => m.startsWith(p)))
+            if (unprefixed) return yield* getModel(providerID, ModelID.make(unprefixed))
+          } else {
+            for (const model of Object.keys(provider.models)) {
+              if (model.includes(item)) return yield* getModel(providerID, ModelID.make(model))
             }
           }
+        }
 
-          return undefined
-        })
-
-        const defaultModel = Effect.fn("Provider.defaultModel")(function* () {
-          const cfg = yield* config.get()
-          if (cfg.model) return parseModel(cfg.model)
-
-          const s = yield* InstanceState.get(state)
-          const recent = yield* fs.readJson(path.join(Global.Path.state, "model.json")).pipe(
-            Effect.map((x): { providerID: ProviderID; modelID: ModelID }[] => {
-              if (!isRecord(x) || !Array.isArray(x.recent)) return []
-              return x.recent.flatMap((item) => {
-                if (!isRecord(item)) return []
-                if (typeof item.providerID !== "string") return []
-                if (typeof item.modelID !== "string") return []
-                return [{ providerID: ProviderID.make(item.providerID), modelID: ModelID.make(item.modelID) }]
-              })
-            }),
-            Effect.catch(() => Effect.succeed([] as { providerID: ProviderID; modelID: ModelID }[])),
-          )
-          for (const entry of recent) {
-            const provider = s.providers[entry.providerID]
-            if (!provider) continue
-            if (!provider.models[entry.modelID]) continue
-            return { providerID: entry.providerID, modelID: entry.modelID }
-          }
+        return undefined
+      })
+
+      const defaultModel = Effect.fn("Provider.defaultModel")(function* () {
+        const cfg = yield* config.get()
+        if (cfg.model) return parseModel(cfg.model)
+
+        const s = yield* InstanceState.get(state)
+        const recent = yield* fs.readJson(path.join(Global.Path.state, "model.json")).pipe(
+          Effect.map((x): { providerID: ProviderID; modelID: ModelID }[] => {
+            if (!isRecord(x) || !Array.isArray(x.recent)) return []
+            return x.recent.flatMap((item) => {
+              if (!isRecord(item)) return []
+              if (typeof item.providerID !== "string") return []
+              if (typeof item.modelID !== "string") return []
+              return [{ providerID: ProviderID.make(item.providerID), modelID: ModelID.make(item.modelID) }]
+            })
+          }),
+          Effect.catch(() => Effect.succeed([] as { providerID: ProviderID; modelID: ModelID }[])),
+        )
+        for (const entry of recent) {
+          const provider = s.providers[entry.providerID]
+          if (!provider) continue
+          if (!provider.models[entry.modelID]) continue
+          return { providerID: entry.providerID, modelID: entry.modelID }
+        }
 
-          const provider = Object.values(s.providers).find(
-            (p) => !cfg.provider || Object.keys(cfg.provider).includes(p.id),
-          )
-          if (!provider) throw new Error("no providers found")
-          const [model] = sort(Object.values(provider.models))
-          if (!model) throw new Error("no models found")
-          return {
-            providerID: provider.id,
-            modelID: model.id,
-          }
-        })
+        const provider = Object.values(s.providers).find(
+          (p) => !cfg.provider || Object.keys(cfg.provider).includes(p.id),
+        )
+        if (!provider) throw new Error("no providers found")
+        const [model] = sort(Object.values(provider.models))
+        if (!model) throw new Error("no models found")
+        return {
+          providerID: provider.id,
+          modelID: model.id,
+        }
+      })
 
-        return Service.of({ list, getProvider, getModel, getLanguage, closest, getSmallModel, defaultModel })
-      }),
-    )
+      return Service.of({ list, getProvider, getModel, getLanguage, closest, getSmallModel, defaultModel })
+    }),
+  )
 
   export const defaultLayer = Layer.suspend(() =>
     layer.pipe(
       Layer.provide(AppFileSystem.defaultLayer),
+      Layer.provide(Env.defaultLayer),
       Layer.provide(Config.defaultLayer),
       Layer.provide(Auth.defaultLayer),
       Layer.provide(Plugin.defaultLayer),

+ 5 - 1
packages/opencode/src/tool/registry.ts

@@ -78,6 +78,7 @@ export namespace ToolRegistry {
     Service,
     never,
     | Config.Service
+    | Env.Service
     | Plugin.Service
     | Question.Service
     | Todo.Service
@@ -99,6 +100,7 @@ export namespace ToolRegistry {
     Service,
     Effect.gen(function* () {
       const config = yield* Config.Service
+      const env = yield* Env.Service
       const plugin = yield* Plugin.Service
       const agents = yield* Agent.Service
       const skill = yield* Skill.Service
@@ -272,13 +274,14 @@ export namespace ToolRegistry {
       })
 
       const tools: Interface["tools"] = Effect.fn("ToolRegistry.tools")(function* (input) {
+        const e2e = !!(yield* env.get("OPENCODE_E2E_LLM_URL"))
         const filtered = (yield* all()).filter((tool) => {
           if (tool.id === CodeSearchTool.id || tool.id === WebSearchTool.id) {
             return input.providerID === ProviderID.opencode || Flag.OPENCODE_ENABLE_EXA
           }
 
           const usePatch =
-            !!Env.get("OPENCODE_E2E_LLM_URL") ||
+            e2e ||
             (input.modelID.includes("gpt-") && !input.modelID.includes("oss") && !input.modelID.includes("gpt-4"))
           if (tool.id === ApplyPatchTool.id) return usePatch
           if (tool.id === EditTool.id || tool.id === WriteTool.id) return !usePatch
@@ -325,6 +328,7 @@ export namespace ToolRegistry {
   export const defaultLayer = Layer.suspend(() =>
     layer.pipe(
       Layer.provide(Config.defaultLayer),
+      Layer.provide(Env.defaultLayer),
       Layer.provide(Plugin.defaultLayer),
       Layer.provide(Question.defaultLayer),
       Layer.provide(Todo.defaultLayer),

+ 5 - 0
packages/opencode/test/config/config.test.ts

@@ -6,6 +6,7 @@ import { Instance } from "../../src/project/instance"
 import { Auth } from "../../src/auth"
 import { AccessToken, Account, AccountID, OrgID } from "../../src/account"
 import { AppFileSystem } from "../../src/filesystem"
+import { Env } from "../../src/env"
 import { provideTmpdirInstance } from "../fixture/fixture"
 import { tmpdir, tmpdirScoped } from "../fixture/fixture"
 import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
@@ -35,6 +36,7 @@ const emptyAuth = Layer.mock(Auth.Service)({
 
 const layer = Config.layer.pipe(
   Layer.provide(AppFileSystem.defaultLayer),
+  Layer.provide(Env.defaultLayer),
   Layer.provide(emptyAuth),
   Layer.provide(emptyAccount),
   Layer.provideMerge(infra),
@@ -332,6 +334,7 @@ test("resolves env templates in account config with account token", async () =>
 
   const layer = Config.layer.pipe(
     Layer.provide(AppFileSystem.defaultLayer),
+    Layer.provide(Env.defaultLayer),
     Layer.provide(emptyAuth),
     Layer.provide(fakeAccount),
     Layer.provideMerge(infra),
@@ -1824,6 +1827,7 @@ test("project config overrides remote well-known config", async () => {
 
   const layer = Config.layer.pipe(
     Layer.provide(AppFileSystem.defaultLayer),
+    Layer.provide(Env.defaultLayer),
     Layer.provide(fakeAuth),
     Layer.provide(emptyAccount),
     Layer.provideMerge(infra),
@@ -1879,6 +1883,7 @@ test("wellknown URL with trailing slash is normalized", async () => {
 
   const layer = Config.layer.pipe(
     Layer.provide(AppFileSystem.defaultLayer),
+    Layer.provide(Env.defaultLayer),
     Layer.provide(fakeAuth),
     Layer.provide(emptyAccount),
     Layer.provideMerge(infra),

+ 2 - 0
packages/opencode/test/session/prompt-effect.test.ts

@@ -14,6 +14,7 @@ import { MCP } from "../../src/mcp"
 import { Permission } from "../../src/permission"
 import { Plugin } from "../../src/plugin"
 import { Provider as ProviderSvc } from "../../src/provider/provider"
+import { Env } from "../../src/env"
 import type { Provider } from "../../src/provider/provider"
 import { ModelID, ProviderID } from "../../src/provider/schema"
 import { Question } from "../../src/question"
@@ -167,6 +168,7 @@ function makeHttp() {
     Session.defaultLayer,
     Snapshot.defaultLayer,
     LLM.defaultLayer,
+    Env.defaultLayer,
     AgentSvc.defaultLayer,
     Command.defaultLayer,
     Permission.defaultLayer,

+ 2 - 0
packages/opencode/test/session/snapshot-tool-race.test.ts

@@ -39,6 +39,7 @@ import { MCP } from "../../src/mcp"
 import { Permission } from "../../src/permission"
 import { Plugin } from "../../src/plugin"
 import { Provider as ProviderSvc } from "../../src/provider/provider"
+import { Env } from "../../src/env"
 import { Question } from "../../src/question"
 import { Skill } from "../../src/skill"
 import { SystemPrompt } from "../../src/session/system"
@@ -121,6 +122,7 @@ function makeHttp() {
     Session.defaultLayer,
     Snapshot.defaultLayer,
     LLM.defaultLayer,
+    Env.defaultLayer,
     AgentSvc.defaultLayer,
     Command.defaultLayer,
     Permission.defaultLayer,