effect: add RuntimeFlags service (#27181)
This commit is contained in:
27
packages/opencode/src/effect/runtime-flags.ts
Normal file
27
packages/opencode/src/effect/runtime-flags.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Config, ConfigProvider, Context, Effect, Layer } from "effect"
|
||||||
|
import { ConfigService } from "@/effect/config-service"
|
||||||
|
|
||||||
|
export class Service extends ConfigService.Service<Service>()("@opencode/RuntimeFlags", {
|
||||||
|
pure: Config.boolean("OPENCODE_PURE").pipe(Config.withDefault(false)),
|
||||||
|
disableDefaultPlugins: Config.boolean("OPENCODE_DISABLE_DEFAULT_PLUGINS").pipe(Config.withDefault(false)),
|
||||||
|
}) {}
|
||||||
|
|
||||||
|
export type Info = Context.Service.Shape<typeof Service>
|
||||||
|
|
||||||
|
const emptyConfigLayer = Service.defaultLayer.pipe(
|
||||||
|
Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown({}))),
|
||||||
|
Layer.orDie,
|
||||||
|
)
|
||||||
|
|
||||||
|
export const layer = (overrides: Partial<Info> = {}) =>
|
||||||
|
Layer.effect(
|
||||||
|
Service,
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const flags = yield* Service
|
||||||
|
return Service.of({ ...flags, ...overrides })
|
||||||
|
}),
|
||||||
|
).pipe(Layer.provide(emptyConfigLayer))
|
||||||
|
|
||||||
|
export const defaultLayer = Service.defaultLayer.pipe(Layer.orDie)
|
||||||
|
|
||||||
|
export * as RuntimeFlags from "./runtime-flags"
|
||||||
@@ -9,7 +9,6 @@ import { Config } from "@/config/config"
|
|||||||
import { Bus } from "../bus"
|
import { Bus } from "../bus"
|
||||||
import * as Log from "@opencode-ai/core/util/log"
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
import { createOpencodeClient } from "@opencode-ai/sdk"
|
import { createOpencodeClient } from "@opencode-ai/sdk"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
|
||||||
import { ServerAuth } from "@/server/auth"
|
import { ServerAuth } from "@/server/auth"
|
||||||
import { CodexAuthPlugin } from "./codex"
|
import { CodexAuthPlugin } from "./codex"
|
||||||
import { Session } from "@/session/session"
|
import { Session } from "@/session/session"
|
||||||
@@ -28,6 +27,7 @@ import { PluginLoader } from "./loader"
|
|||||||
import { parsePluginSpecifier, readPluginId, readV1Plugin, resolvePluginId } from "./shared"
|
import { parsePluginSpecifier, readPluginId, readV1Plugin, resolvePluginId } from "./shared"
|
||||||
import { registerAdapter } from "@/control-plane/adapters"
|
import { registerAdapter } from "@/control-plane/adapters"
|
||||||
import type { WorkspaceAdapter } from "@/control-plane/types"
|
import type { WorkspaceAdapter } from "@/control-plane/types"
|
||||||
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
|
|
||||||
const log = Log.create({ service: "plugin" })
|
const log = Log.create({ service: "plugin" })
|
||||||
|
|
||||||
@@ -112,6 +112,7 @@ export const layer = Layer.effect(
|
|||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const bus = yield* Bus.Service
|
const bus = yield* Bus.Service
|
||||||
const config = yield* Config.Service
|
const config = yield* Config.Service
|
||||||
|
const flags = yield* RuntimeFlags.Service
|
||||||
|
|
||||||
const state = yield* InstanceState.make<State>(
|
const state = yield* InstanceState.make<State>(
|
||||||
Effect.fn("Plugin.state")(function* (ctx) {
|
Effect.fn("Plugin.state")(function* (ctx) {
|
||||||
@@ -148,7 +149,7 @@ export const layer = Layer.effect(
|
|||||||
$: typeof Bun === "undefined" ? undefined : Bun.$,
|
$: typeof Bun === "undefined" ? undefined : Bun.$,
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const plugin of INTERNAL_PLUGINS) {
|
for (const plugin of flags.disableDefaultPlugins ? [] : INTERNAL_PLUGINS) {
|
||||||
log.info("loading internal plugin", { name: plugin.name })
|
log.info("loading internal plugin", { name: plugin.name })
|
||||||
const init = yield* Effect.tryPromise({
|
const init = yield* Effect.tryPromise({
|
||||||
try: () => plugin(input),
|
try: () => plugin(input),
|
||||||
@@ -159,8 +160,8 @@ export const layer = Layer.effect(
|
|||||||
if (init._tag === "Some") hooks.push(init.value)
|
if (init._tag === "Some") hooks.push(init.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const plugins = Flag.OPENCODE_PURE ? [] : (cfg.plugin_origins ?? [])
|
const plugins = flags.pure ? [] : (cfg.plugin_origins ?? [])
|
||||||
if (Flag.OPENCODE_PURE && cfg.plugin_origins?.length) {
|
if (flags.pure && cfg.plugin_origins?.length) {
|
||||||
log.info("skipping external plugins in pure mode", { count: cfg.plugin_origins.length })
|
log.info("skipping external plugins in pure mode", { count: cfg.plugin_origins.length })
|
||||||
}
|
}
|
||||||
if (plugins.length) yield* config.waitForDependencies()
|
if (plugins.length) yield* config.waitForDependencies()
|
||||||
@@ -285,6 +286,10 @@ export const layer = Layer.effect(
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const defaultLayer = layer.pipe(Layer.provide(Bus.layer), Layer.provide(Config.defaultLayer))
|
export const defaultLayer = layer.pipe(
|
||||||
|
Layer.provide(Bus.layer),
|
||||||
|
Layer.provide(Config.defaultLayer),
|
||||||
|
Layer.provide(RuntimeFlags.defaultLayer),
|
||||||
|
)
|
||||||
|
|
||||||
export * as Plugin from "."
|
export * as Plugin from "."
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Agent } from "../../src/agent/agent"
|
|||||||
import { Bus } from "../../src/bus"
|
import { Bus } from "../../src/bus"
|
||||||
import { Config } from "../../src/config/config"
|
import { Config } from "../../src/config/config"
|
||||||
import { Env } from "../../src/env"
|
import { Env } from "../../src/env"
|
||||||
|
import { RuntimeFlags } from "../../src/effect/runtime-flags"
|
||||||
import { Plugin } from "../../src/plugin"
|
import { Plugin } from "../../src/plugin"
|
||||||
import { AccountTest } from "../fake/account"
|
import { AccountTest } from "../fake/account"
|
||||||
import { AuthTest } from "../fake/auth"
|
import { AuthTest } from "../fake/auth"
|
||||||
@@ -29,7 +30,11 @@ const configLayer = Config.layer.pipe(
|
|||||||
Layer.provide(AccountTest.empty),
|
Layer.provide(AccountTest.empty),
|
||||||
Layer.provide(NpmTest.noop),
|
Layer.provide(NpmTest.noop),
|
||||||
)
|
)
|
||||||
const pluginLayer = Plugin.layer.pipe(Layer.provide(Bus.layer), Layer.provide(configLayer))
|
const pluginLayer = Plugin.layer.pipe(
|
||||||
|
Layer.provide(Bus.layer),
|
||||||
|
Layer.provide(configLayer),
|
||||||
|
Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })),
|
||||||
|
)
|
||||||
const agentLayer = Agent.layer.pipe(
|
const agentLayer = Agent.layer.pipe(
|
||||||
Layer.provide(configLayer),
|
Layer.provide(configLayer),
|
||||||
Layer.provide(AuthTest.empty),
|
Layer.provide(AuthTest.empty),
|
||||||
|
|||||||
55
packages/opencode/test/effect/runtime-flags.test.ts
Normal file
55
packages/opencode/test/effect/runtime-flags.test.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { describe, expect } from "bun:test"
|
||||||
|
import { ConfigProvider, Effect, Layer } from "effect"
|
||||||
|
import { RuntimeFlags } from "../../src/effect/runtime-flags"
|
||||||
|
import { it } from "../lib/effect"
|
||||||
|
|
||||||
|
const fromConfig = (input: Record<string, unknown>) =>
|
||||||
|
RuntimeFlags.defaultLayer.pipe(Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown(input))))
|
||||||
|
|
||||||
|
const readFlags = RuntimeFlags.Service.useSync((flags) => flags)
|
||||||
|
|
||||||
|
describe("RuntimeFlags", () => {
|
||||||
|
it.effect("defaultLayer parses plugin flags from the active ConfigProvider", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const flags = yield* readFlags.pipe(
|
||||||
|
Effect.provide(
|
||||||
|
fromConfig({
|
||||||
|
OPENCODE_PURE: "true",
|
||||||
|
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(flags.pure).toBe(true)
|
||||||
|
expect(flags.disableDefaultPlugins).toBe(true)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("layer accepts partial test overrides and fills defaults from Config definitions", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const flags = yield* readFlags.pipe(Effect.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })))
|
||||||
|
|
||||||
|
expect(flags.pure).toBe(false)
|
||||||
|
expect(flags.disableDefaultPlugins).toBe(true)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("layer ignores the active ConfigProvider for omitted test overrides", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const flags = yield* readFlags.pipe(
|
||||||
|
Effect.provide(RuntimeFlags.layer()),
|
||||||
|
Effect.provide(
|
||||||
|
ConfigProvider.layer(
|
||||||
|
ConfigProvider.fromUnknown({
|
||||||
|
OPENCODE_PURE: "true",
|
||||||
|
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(flags.pure).toBe(false)
|
||||||
|
expect(flags.disableDefaultPlugins).toBe(false)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
@@ -7,6 +7,7 @@ import { provideInstance, TestInstance, tmpdirScoped } from "../fixture/fixture"
|
|||||||
import { ProviderAuth } from "@/provider/auth"
|
import { ProviderAuth } from "@/provider/auth"
|
||||||
import { ProviderID } from "../../src/provider/schema"
|
import { ProviderID } from "../../src/provider/schema"
|
||||||
import { Plugin } from "@/plugin"
|
import { Plugin } from "@/plugin"
|
||||||
|
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||||
import { Auth } from "@/auth"
|
import { Auth } from "@/auth"
|
||||||
import { Bus } from "@/bus"
|
import { Bus } from "@/bus"
|
||||||
import { TestConfig } from "../fixture/config"
|
import { TestConfig } from "../fixture/config"
|
||||||
@@ -21,6 +22,7 @@ function layer(directory: string, plugins: string[]) {
|
|||||||
Layer.provide(
|
Layer.provide(
|
||||||
Plugin.layer.pipe(
|
Plugin.layer.pipe(
|
||||||
Layer.provide(Bus.layer),
|
Layer.provide(Bus.layer),
|
||||||
|
Layer.provide(RuntimeFlags.layer()),
|
||||||
Layer.provide(
|
Layer.provide(
|
||||||
TestConfig.layer({
|
TestConfig.layer({
|
||||||
get: () =>
|
get: () =>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { afterAll, afterEach, describe, expect, spyOn } from "bun:test"
|
import { afterEach, describe, expect, spyOn } from "bun:test"
|
||||||
import { Effect, Layer } from "effect"
|
import { Effect, Layer } from "effect"
|
||||||
import fs from "fs/promises"
|
import fs from "fs/promises"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
@@ -8,23 +8,13 @@ import { disposeAllInstances, provideInstance, tmpdirScoped } from "../fixture/f
|
|||||||
import { testEffect } from "../lib/effect"
|
import { testEffect } from "../lib/effect"
|
||||||
import { Filesystem } from "@/util/filesystem"
|
import { Filesystem } from "@/util/filesystem"
|
||||||
|
|
||||||
const disableDefault = process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS
|
|
||||||
process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = "1"
|
|
||||||
|
|
||||||
const { Plugin } = await import("../../src/plugin/index")
|
const { Plugin } = await import("../../src/plugin/index")
|
||||||
const { PluginLoader } = await import("../../src/plugin/loader")
|
const { PluginLoader } = await import("../../src/plugin/loader")
|
||||||
const { readPackageThemes } = await import("../../src/plugin/shared")
|
const { readPackageThemes } = await import("../../src/plugin/shared")
|
||||||
const { Bus } = await import("../../src/bus")
|
const { Bus } = await import("../../src/bus")
|
||||||
const { Npm } = await import("@opencode-ai/core/npm")
|
const { Npm } = await import("@opencode-ai/core/npm")
|
||||||
const { TestConfig } = await import("../fixture/config")
|
const { TestConfig } = await import("../fixture/config")
|
||||||
|
const { RuntimeFlags } = await import("../../src/effect/runtime-flags")
|
||||||
afterAll(() => {
|
|
||||||
if (disableDefault === undefined) {
|
|
||||||
delete process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS
|
|
||||||
return
|
|
||||||
}
|
|
||||||
process.env.OPENCODE_DISABLE_DEFAULT_PLUGINS = disableDefault
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await disposeAllInstances()
|
await disposeAllInstances()
|
||||||
@@ -43,7 +33,7 @@ function withTmp<T, A, E, R>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function load(dir: string) {
|
function load(dir: string, flags?: Parameters<typeof RuntimeFlags.layer>[0]) {
|
||||||
const source = path.join(dir, "opencode.json")
|
const source = path.join(dir, "opencode.json")
|
||||||
return Effect.gen(function* () {
|
return Effect.gen(function* () {
|
||||||
const config = yield* Effect.promise(
|
const config = yield* Effect.promise(
|
||||||
@@ -57,6 +47,7 @@ function load(dir: string) {
|
|||||||
Effect.provide(
|
Effect.provide(
|
||||||
Plugin.layer.pipe(
|
Plugin.layer.pipe(
|
||||||
Layer.provide(Bus.layer),
|
Layer.provide(Bus.layer),
|
||||||
|
Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true, ...flags })),
|
||||||
Layer.provide(
|
Layer.provide(
|
||||||
TestConfig.layer({
|
TestConfig.layer({
|
||||||
get: () =>
|
get: () =>
|
||||||
@@ -934,25 +925,14 @@ export default {
|
|||||||
},
|
},
|
||||||
(tmp) =>
|
(tmp) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const pure = process.env.OPENCODE_PURE
|
yield* load(tmp.path, { pure: true })
|
||||||
process.env.OPENCODE_PURE = "1"
|
const called = yield* Effect.promise(() =>
|
||||||
|
fs
|
||||||
try {
|
.readFile(tmp.extra.mark, "utf8")
|
||||||
yield* load(tmp.path)
|
.then(() => true)
|
||||||
const called = yield* Effect.promise(() =>
|
.catch(() => false),
|
||||||
fs
|
)
|
||||||
.readFile(tmp.extra.mark, "utf8")
|
expect(called).toBe(false)
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false),
|
|
||||||
)
|
|
||||||
expect(called).toBe(false)
|
|
||||||
} finally {
|
|
||||||
if (pure === undefined) {
|
|
||||||
delete process.env.OPENCODE_PURE
|
|
||||||
} else {
|
|
||||||
process.env.OPENCODE_PURE = pure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { Auth } from "../../src/auth"
|
|||||||
import { Bus } from "../../src/bus"
|
import { Bus } from "../../src/bus"
|
||||||
import { Config } from "../../src/config/config"
|
import { Config } from "../../src/config/config"
|
||||||
import { Env } from "../../src/env"
|
import { Env } from "../../src/env"
|
||||||
|
import { RuntimeFlags } from "../../src/effect/runtime-flags"
|
||||||
import { Plugin } from "../../src/plugin/index"
|
import { Plugin } from "../../src/plugin/index"
|
||||||
import { ModelID, ProviderID } from "../../src/provider/schema"
|
import { ModelID, ProviderID } from "../../src/provider/schema"
|
||||||
import { provideTmpdirInstance } from "../fixture/fixture"
|
import { provideTmpdirInstance } from "../fixture/fixture"
|
||||||
@@ -33,7 +34,11 @@ const configLayer = Config.layer.pipe(
|
|||||||
)
|
)
|
||||||
const it = testEffect(
|
const it = testEffect(
|
||||||
Layer.mergeAll(
|
Layer.mergeAll(
|
||||||
Plugin.layer.pipe(Layer.provide(Bus.layer), Layer.provide(configLayer)),
|
Plugin.layer.pipe(
|
||||||
|
Layer.provide(Bus.layer),
|
||||||
|
Layer.provide(configLayer),
|
||||||
|
Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })),
|
||||||
|
),
|
||||||
CrossSpawnSpawner.defaultLayer,
|
CrossSpawnSpawner.defaultLayer,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { Auth } from "../../src/auth"
|
|||||||
import { Bus } from "../../src/bus"
|
import { Bus } from "../../src/bus"
|
||||||
import { Config } from "../../src/config/config"
|
import { Config } from "../../src/config/config"
|
||||||
import { Env } from "../../src/env"
|
import { Env } from "../../src/env"
|
||||||
|
import { RuntimeFlags } from "../../src/effect/runtime-flags"
|
||||||
import { Workspace } from "../../src/control-plane/workspace"
|
import { Workspace } from "../../src/control-plane/workspace"
|
||||||
import { Plugin } from "../../src/plugin/index"
|
import { Plugin } from "../../src/plugin/index"
|
||||||
import { InstanceBootstrap } from "../../src/project/bootstrap-service"
|
import { InstanceBootstrap } from "../../src/project/bootstrap-service"
|
||||||
@@ -35,7 +36,11 @@ const configLayer = Config.layer.pipe(
|
|||||||
Layer.provide(emptyAccount),
|
Layer.provide(emptyAccount),
|
||||||
Layer.provide(NpmTest.noop),
|
Layer.provide(NpmTest.noop),
|
||||||
)
|
)
|
||||||
const pluginLayer = Plugin.layer.pipe(Layer.provide(Bus.layer), Layer.provide(configLayer))
|
const pluginLayer = Plugin.layer.pipe(
|
||||||
|
Layer.provide(Bus.layer),
|
||||||
|
Layer.provide(configLayer),
|
||||||
|
Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })),
|
||||||
|
)
|
||||||
const noopBootstrapLayer = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void }))
|
const noopBootstrapLayer = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void }))
|
||||||
const workspaceLayer = Workspace.defaultLayer.pipe(
|
const workspaceLayer = Workspace.defaultLayer.pipe(
|
||||||
Layer.provide(InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrapLayer))),
|
Layer.provide(InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrapLayer))),
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ process.env["OPENCODE_TEST_HOME"] = testHome
|
|||||||
// Set test managed config directory to isolate tests from system managed settings
|
// Set test managed config directory to isolate tests from system managed settings
|
||||||
const testManagedConfigDir = path.join(dir, "managed")
|
const testManagedConfigDir = path.join(dir, "managed")
|
||||||
process.env["OPENCODE_TEST_MANAGED_CONFIG_DIR"] = testManagedConfigDir
|
process.env["OPENCODE_TEST_MANAGED_CONFIG_DIR"] = testManagedConfigDir
|
||||||
process.env["OPENCODE_DISABLE_DEFAULT_PLUGINS"] = "true"
|
|
||||||
|
|
||||||
// Write the cache version file to prevent global/index.ts from clearing the cache
|
// Write the cache version file to prevent global/index.ts from clearing the cache
|
||||||
const cacheDir = path.join(dir, "cache", "opencode")
|
const cacheDir = path.join(dir, "cache", "opencode")
|
||||||
|
|||||||
Reference in New Issue
Block a user