refactor(config): migrate model-id and command to Effect Schema (#23175)

This commit is contained in:
Kit Langton
2026-04-17 20:59:24 +00:00
committed by GitHub
parent 999d8651aa
commit ce69bd97b9
4 changed files with 30 additions and 15 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ const log = Log.create({ service: "config" })
export const Info = z export const Info = z
.object({ .object({
model: ConfigModelID.optional(), model: ConfigModelID.zod.optional(),
variant: z variant: z
.string() .string()
.optional() .optional()
+12 -10
View File
@@ -1,10 +1,12 @@
export * as ConfigCommand from "./command" export * as ConfigCommand from "./command"
import { Log } from "../util" import { Log } from "../util"
import z from "zod" import { Schema } from "effect"
import { NamedError } from "@opencode-ai/shared/util/error" import { NamedError } from "@opencode-ai/shared/util/error"
import { Glob } from "@opencode-ai/shared/util/glob" import { Glob } from "@opencode-ai/shared/util/glob"
import { Bus } from "@/bus" import { Bus } from "@/bus"
import { zod } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
import { configEntryNameFromPath } from "./entry-name" import { configEntryNameFromPath } from "./entry-name"
import { InvalidError } from "./error" import { InvalidError } from "./error"
import * as ConfigMarkdown from "./markdown" import * as ConfigMarkdown from "./markdown"
@@ -12,15 +14,15 @@ import { ConfigModelID } from "./model-id"
const log = Log.create({ service: "config" }) const log = Log.create({ service: "config" })
export const Info = z.object({ export const Info = Schema.Struct({
template: z.string(), template: Schema.String,
description: z.string().optional(), description: Schema.optional(Schema.String),
agent: z.string().optional(), agent: Schema.optional(Schema.String),
model: ConfigModelID.optional(), model: Schema.optional(ConfigModelID),
subtask: z.boolean().optional(), subtask: Schema.optional(Schema.Boolean),
}) }).pipe(withStatics((s) => ({ zod: zod(s) })))
export type Info = z.infer<typeof Info> export type Info = Schema.Schema.Type<typeof Info>
export async function load(dir: string) { export async function load(dir: string) {
const result: Record<string, Info> = {} const result: Record<string, Info> = {}
@@ -49,7 +51,7 @@ export async function load(dir: string) {
...md.data, ...md.data,
template: md.content.trim(), template: md.content.trim(),
} }
const parsed = Info.safeParse(config) const parsed = Info.zod.safeParse(config)
if (parsed.success) { if (parsed.success) {
result[config.name] = parsed.data result[config.name] = parsed.data
continue continue
+5 -3
View File
@@ -97,7 +97,7 @@ export const Info = z
logLevel: Log.Level.optional().describe("Log level"), logLevel: Log.Level.optional().describe("Log level"),
server: Server.optional().describe("Server configuration for opencode serve and web commands"), server: Server.optional().describe("Server configuration for opencode serve and web commands"),
command: z command: z
.record(z.string(), ConfigCommand.Info) .record(z.string(), ConfigCommand.Info.zod)
.optional() .optional()
.describe("Command configuration, see https://opencode.ai/docs/commands"), .describe("Command configuration, see https://opencode.ai/docs/commands"),
skills: ConfigSkills.Info.zod.optional().describe("Additional skill folder paths"), skills: ConfigSkills.Info.zod.optional().describe("Additional skill folder paths"),
@@ -135,8 +135,10 @@ export const Info = z
.array(z.string()) .array(z.string())
.optional() .optional()
.describe("When set, ONLY these providers will be enabled. All other providers will be ignored"), .describe("When set, ONLY these providers will be enabled. All other providers will be ignored"),
model: ConfigModelID.describe("Model to use in the format of provider/model, eg anthropic/claude-2").optional(), model: ConfigModelID.zod
small_model: ConfigModelID.describe( .describe("Model to use in the format of provider/model, eg anthropic/claude-2")
.optional(),
small_model: ConfigModelID.zod.describe(
"Small model to use for tasks like title generation in the format of provider/model", "Small model to use for tasks like title generation in the format of provider/model",
).optional(), ).optional(),
default_agent: z default_agent: z
+12 -1
View File
@@ -1,3 +1,14 @@
import { Schema } from "effect"
import z from "zod" import z from "zod"
import { zod, ZodOverride } from "@/util/effect-zod"
import { withStatics } from "@/util/schema"
export const ConfigModelID = z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" }) // The original Zod schema carried an external $ref pointing at the models.dev
// JSON schema. That external reference is not a named SDK component — it is a
// literal pointer to an outside schema — so the walker cannot re-derive it
// from AST metadata. Preserve the exact original Zod via ZodOverride.
export const ConfigModelID = Schema.String.annotate({
[ZodOverride]: z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" }),
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export type ConfigModelID = Schema.Schema.Type<typeof ConfigModelID>