|
|
@@ -24,7 +24,7 @@ import { InstanceState } from "@/effect"
|
|
|
import { Context, Duration, Effect, Exit, Fiber, Layer, Option, Schema } from "effect"
|
|
|
import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
|
|
|
import { InstanceRef } from "@/effect/instance-ref"
|
|
|
-import { zod, ZodOverride } from "@/util/effect-zod"
|
|
|
+import { zod } from "@/util/effect-zod"
|
|
|
import { NonNegativeInt, PositiveInt, withStatics, type DeepMutable } from "@/util/schema"
|
|
|
import { ConfigAgent } from "./agent"
|
|
|
import { ConfigCommand } from "./command"
|
|
|
@@ -81,12 +81,10 @@ export const Server = ConfigServer.Server.zod
|
|
|
export const Layout = ConfigLayout.Layout.zod
|
|
|
export type Layout = ConfigLayout.Layout
|
|
|
|
|
|
-// Schemas that still live at the zod layer (have .transform / .preprocess /
|
|
|
-// .meta not expressible in current Effect Schema) get referenced via a
|
|
|
-// ZodOverride-annotated Schema.Any. Walker sees the annotation and emits the
|
|
|
-// exact zod directly, preserving component $refs.
|
|
|
-const AgentRef = Schema.Any.annotate({ [ZodOverride]: ConfigAgent.Info })
|
|
|
-const LogLevelRef = Schema.Any.annotate({ [ZodOverride]: Log.Level })
|
|
|
+const LogLevelRef = Schema.Literals(["DEBUG", "INFO", "WARN", "ERROR"]).annotate({
|
|
|
+ identifier: "LogLevel",
|
|
|
+ description: "Log level",
|
|
|
+})
|
|
|
|
|
|
// The Effect Schema is the canonical source of truth. The `.zod` compatibility
|
|
|
// surface is derived so existing Hono validators keep working without a parallel
|
|
|
@@ -152,27 +150,27 @@ export const Info = Schema.Struct({
|
|
|
mode: Schema.optional(
|
|
|
Schema.StructWithRest(
|
|
|
Schema.Struct({
|
|
|
- build: Schema.optional(AgentRef),
|
|
|
- plan: Schema.optional(AgentRef),
|
|
|
+ build: Schema.optional(ConfigAgent.Info),
|
|
|
+ plan: Schema.optional(ConfigAgent.Info),
|
|
|
}),
|
|
|
- [Schema.Record(Schema.String, AgentRef)],
|
|
|
+ [Schema.Record(Schema.String, ConfigAgent.Info)],
|
|
|
),
|
|
|
).annotate({ description: "@deprecated Use `agent` field instead." }),
|
|
|
agent: Schema.optional(
|
|
|
Schema.StructWithRest(
|
|
|
Schema.Struct({
|
|
|
// primary
|
|
|
- plan: Schema.optional(AgentRef),
|
|
|
- build: Schema.optional(AgentRef),
|
|
|
+ plan: Schema.optional(ConfigAgent.Info),
|
|
|
+ build: Schema.optional(ConfigAgent.Info),
|
|
|
// subagent
|
|
|
- general: Schema.optional(AgentRef),
|
|
|
- explore: Schema.optional(AgentRef),
|
|
|
+ general: Schema.optional(ConfigAgent.Info),
|
|
|
+ explore: Schema.optional(ConfigAgent.Info),
|
|
|
// specialized
|
|
|
- title: Schema.optional(AgentRef),
|
|
|
- summary: Schema.optional(AgentRef),
|
|
|
- compaction: Schema.optional(AgentRef),
|
|
|
+ title: Schema.optional(ConfigAgent.Info),
|
|
|
+ summary: Schema.optional(ConfigAgent.Info),
|
|
|
+ compaction: Schema.optional(ConfigAgent.Info),
|
|
|
}),
|
|
|
- [Schema.Record(Schema.String, AgentRef)],
|
|
|
+ [Schema.Record(Schema.String, ConfigAgent.Info)],
|
|
|
),
|
|
|
).annotate({ description: "Agent configuration, see https://opencode.ai/docs/agents" }),
|
|
|
provider: Schema.optional(Schema.Record(Schema.String, ConfigProvider.Info)).annotate({
|
|
|
@@ -184,7 +182,7 @@ export const Info = Schema.Struct({
|
|
|
Schema.Union([
|
|
|
ConfigMCP.Info,
|
|
|
// Matches the legacy `{ enabled: false }` form used to disable a server.
|
|
|
- Schema.Any.annotate({ [ZodOverride]: z.object({ enabled: z.boolean() }).strict() }),
|
|
|
+ Schema.Struct({ enabled: Schema.Boolean }),
|
|
|
]),
|
|
|
),
|
|
|
).annotate({ description: "MCP (Model Context Protocol) server configurations" }),
|
|
|
@@ -362,7 +360,7 @@ export const layer = Layer.effect(
|
|
|
),
|
|
|
)
|
|
|
const parsed = ConfigParse.jsonc(expanded, source)
|
|
|
- const data = ConfigParse.schema(Info.zod, normalizeLoadedConfig(parsed, source), source)
|
|
|
+ const data = ConfigParse.effectSchema(Info, normalizeLoadedConfig(parsed, source), source)
|
|
|
if (!("path" in options)) return data
|
|
|
|
|
|
yield* Effect.promise(() => resolveLoadedPlugins(data, options.path))
|
|
|
@@ -754,13 +752,13 @@ export const layer = Layer.effect(
|
|
|
|
|
|
let next: Info
|
|
|
if (!file.endsWith(".jsonc")) {
|
|
|
- const existing = ConfigParse.schema(Info.zod, ConfigParse.jsonc(before, file), file)
|
|
|
+ const existing = ConfigParse.effectSchema(Info, ConfigParse.jsonc(before, file), file)
|
|
|
const merged = mergeDeep(writable(existing), writable(config))
|
|
|
yield* fs.writeFileString(file, JSON.stringify(merged, null, 2)).pipe(Effect.orDie)
|
|
|
next = merged
|
|
|
} else {
|
|
|
const updated = patchJsonc(before, writable(config))
|
|
|
- next = ConfigParse.schema(Info.zod, ConfigParse.jsonc(updated, file), file)
|
|
|
+ next = ConfigParse.effectSchema(Info, ConfigParse.jsonc(updated, file), file)
|
|
|
yield* fs.writeFileString(file, updated).pipe(Effect.orDie)
|
|
|
}
|
|
|
|