summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-17 19:29:53 -0400
committeropencode <[email protected]>2026-04-18 00:29:26 +0000
commit2899984819f49b2e1119021d313f78caa0db0e2f (patch)
treed933485b6c1b4a7620c18ffbffeeda8f8082e672 /packages
parenteafbe5c57c41528b52974b671793896e1aa98e96 (diff)
downloadopencode-2899984819f49b2e1119021d313f78caa0db0e2f.tar.gz
opencode-2899984819f49b2e1119021d313f78caa0db0e2f.zip
refactor(config): migrate provider (Model + Info) to Effect Schema (#23197)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/config/config.ts2
-rw-r--r--packages/opencode/src/config/provider.ts220
2 files changed, 109 insertions, 113 deletions
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index a2d62eaa5..bfb0c2f1f 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -171,7 +171,7 @@ export const Info = z
.optional()
.describe("Agent configuration, see https://opencode.ai/docs/agents"),
provider: z
- .record(z.string(), ConfigProvider.Info)
+ .record(z.string(), ConfigProvider.Info.zod)
.optional()
.describe("Custom provider configurations and model overrides"),
mcp: z
diff --git a/packages/opencode/src/config/provider.ts b/packages/opencode/src/config/provider.ts
index 877677519..4664999de 100644
--- a/packages/opencode/src/config/provider.ts
+++ b/packages/opencode/src/config/provider.ts
@@ -1,120 +1,116 @@
+import { Schema } from "effect"
import z from "zod"
+import { zod, ZodOverride } from "@/util/effect-zod"
+import { withStatics } from "@/util/schema"
-export const Model = z
- .object({
- id: z.string(),
- name: z.string(),
- family: z.string().optional(),
- release_date: z.string(),
- attachment: z.boolean(),
- reasoning: z.boolean(),
- temperature: z.boolean(),
- tool_call: z.boolean(),
- interleaved: z
- .union([
- z.literal(true),
- z
- .object({
- field: z.enum(["reasoning_content", "reasoning_details"]),
- })
- .strict(),
- ])
- .optional(),
- cost: z
- .object({
- input: z.number(),
- output: z.number(),
- cache_read: z.number().optional(),
- cache_write: z.number().optional(),
- context_over_200k: z
- .object({
- input: z.number(),
- output: z.number(),
- cache_read: z.number().optional(),
- cache_write: z.number().optional(),
- })
- .optional(),
- })
- .optional(),
- limit: z.object({
- context: z.number(),
- input: z.number().optional(),
- output: z.number(),
+// Positive integer preserving exact Zod JSON Schema (type: integer, exclusiveMinimum: 0).
+const PositiveInt = Schema.Number.annotate({
+ [ZodOverride]: z.number().int().positive(),
+})
+
+export const Model = Schema.Struct({
+ id: Schema.optional(Schema.String),
+ name: Schema.optional(Schema.String),
+ family: Schema.optional(Schema.String),
+ release_date: Schema.optional(Schema.String),
+ attachment: Schema.optional(Schema.Boolean),
+ reasoning: Schema.optional(Schema.Boolean),
+ temperature: Schema.optional(Schema.Boolean),
+ tool_call: Schema.optional(Schema.Boolean),
+ interleaved: Schema.optional(
+ Schema.Union([
+ Schema.Literal(true),
+ Schema.Struct({
+ field: Schema.Literals(["reasoning_content", "reasoning_details"]),
+ }),
+ ]),
+ ),
+ cost: Schema.optional(
+ Schema.Struct({
+ input: Schema.Number,
+ output: Schema.Number,
+ cache_read: Schema.optional(Schema.Number),
+ cache_write: Schema.optional(Schema.Number),
+ context_over_200k: Schema.optional(
+ Schema.Struct({
+ input: Schema.Number,
+ output: Schema.Number,
+ cache_read: Schema.optional(Schema.Number),
+ cache_write: Schema.optional(Schema.Number),
+ }),
+ ),
+ }),
+ ),
+ limit: Schema.optional(
+ Schema.Struct({
+ context: Schema.Number,
+ input: Schema.optional(Schema.Number),
+ output: Schema.Number,
}),
- modalities: z
- .object({
- input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
- output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
- })
- .optional(),
- experimental: z.boolean().optional(),
- status: z.enum(["alpha", "beta", "deprecated"]).optional(),
- provider: z.object({ npm: z.string().optional(), api: z.string().optional() }).optional(),
- options: z.record(z.string(), z.any()),
- headers: z.record(z.string(), z.string()).optional(),
- variants: z
- .record(
- z.string(),
- z
- .object({
- disabled: z.boolean().optional().describe("Disable this variant for the model"),
- })
- .catchall(z.any()),
- )
- .optional()
- .describe("Variant-specific configuration"),
- })
- .partial()
+ ),
+ modalities: Schema.optional(
+ Schema.Struct({
+ input: Schema.mutable(Schema.Array(Schema.Literals(["text", "audio", "image", "video", "pdf"]))),
+ output: Schema.mutable(Schema.Array(Schema.Literals(["text", "audio", "image", "video", "pdf"]))),
+ }),
+ ),
+ experimental: Schema.optional(Schema.Boolean),
+ status: Schema.optional(Schema.Literals(["alpha", "beta", "deprecated"])),
+ provider: Schema.optional(Schema.Struct({ npm: Schema.optional(Schema.String), api: Schema.optional(Schema.String) })),
+ options: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
+ headers: Schema.optional(Schema.Record(Schema.String, Schema.String)),
+ variants: Schema.optional(
+ Schema.Record(
+ Schema.String,
+ Schema.StructWithRest(
+ Schema.Struct({
+ disabled: Schema.optional(Schema.Boolean).annotate({ description: "Disable this variant for the model" }),
+ }),
+ [Schema.Record(Schema.String, Schema.Any)],
+ ),
+ ).annotate({ description: "Variant-specific configuration" }),
+ ),
+}).pipe(withStatics((s) => ({ zod: zod(s) })))
-export const Info = z
- .object({
- api: z.string().optional(),
- name: z.string(),
- env: z.array(z.string()),
- id: z.string(),
- npm: z.string().optional(),
- whitelist: z.array(z.string()).optional(),
- blacklist: z.array(z.string()).optional(),
- options: z
- .object({
- apiKey: z.string().optional(),
- baseURL: z.string().optional(),
- enterpriseUrl: z.string().optional().describe("GitHub Enterprise URL for copilot authentication"),
- setCacheKey: z.boolean().optional().describe("Enable promptCacheKey for this provider (default false)"),
- timeout: z
- .union([
- z
- .number()
- .int()
- .positive()
- .describe(
- "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
- ),
- z.literal(false).describe("Disable timeout for this provider entirely."),
- ])
- .optional()
- .describe(
+export class Info extends Schema.Class<Info>("ProviderConfig")({
+ api: Schema.optional(Schema.String),
+ name: Schema.optional(Schema.String),
+ env: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
+ id: Schema.optional(Schema.String),
+ npm: Schema.optional(Schema.String),
+ whitelist: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
+ blacklist: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
+ options: Schema.optional(
+ Schema.StructWithRest(
+ Schema.Struct({
+ apiKey: Schema.optional(Schema.String),
+ baseURL: Schema.optional(Schema.String),
+ enterpriseUrl: Schema.optional(Schema.String).annotate({
+ description: "GitHub Enterprise URL for copilot authentication",
+ }),
+ setCacheKey: Schema.optional(Schema.Boolean).annotate({
+ description: "Enable promptCacheKey for this provider (default false)",
+ }),
+ timeout: Schema.optional(
+ Schema.Union([PositiveInt, Schema.Literal(false)]).annotate({
+ description:
+ "Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
+ }),
+ ).annotate({
+ description:
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
- ),
- chunkTimeout: z
- .number()
- .int()
- .positive()
- .optional()
- .describe(
+ }),
+ chunkTimeout: Schema.optional(PositiveInt).annotate({
+ description:
"Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted.",
- ),
- })
- .catchall(z.any())
- .optional(),
- models: z.record(z.string(), Model).optional(),
- })
- .partial()
- .strict()
- .meta({
- ref: "ProviderConfig",
- })
-
-export type Info = z.infer<typeof Info>
+ }),
+ }),
+ [Schema.Record(Schema.String, Schema.Any)],
+ ),
+ ),
+ models: Schema.optional(Schema.Record(Schema.String, Model)),
+}) {
+ static readonly zod = zod(this)
+}
export * as ConfigProvider from "./provider"