Skip to content

Commit 2899984

Browse files
kitlangtonopencode
authored andcommitted
refactor(config): migrate provider (Model + Info) to Effect Schema (#23197)
1 parent eafbe5c commit 2899984

2 files changed

Lines changed: 109 additions & 113 deletions

File tree

packages/opencode/src/config/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export const Info = z
171171
.optional()
172172
.describe("Agent configuration, see https://opencode.ai/docs/agents"),
173173
provider: z
174-
.record(z.string(), ConfigProvider.Info)
174+
.record(z.string(), ConfigProvider.Info.zod)
175175
.optional()
176176
.describe("Custom provider configurations and model overrides"),
177177
mcp: z
Lines changed: 108 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,116 @@
1+
import { Schema } from "effect"
12
import z from "zod"
3+
import { zod, ZodOverride } from "@/util/effect-zod"
4+
import { withStatics } from "@/util/schema"
25

3-
export const Model = z
4-
.object({
5-
id: z.string(),
6-
name: z.string(),
7-
family: z.string().optional(),
8-
release_date: z.string(),
9-
attachment: z.boolean(),
10-
reasoning: z.boolean(),
11-
temperature: z.boolean(),
12-
tool_call: z.boolean(),
13-
interleaved: z
14-
.union([
15-
z.literal(true),
16-
z
17-
.object({
18-
field: z.enum(["reasoning_content", "reasoning_details"]),
19-
})
20-
.strict(),
21-
])
22-
.optional(),
23-
cost: z
24-
.object({
25-
input: z.number(),
26-
output: z.number(),
27-
cache_read: z.number().optional(),
28-
cache_write: z.number().optional(),
29-
context_over_200k: z
30-
.object({
31-
input: z.number(),
32-
output: z.number(),
33-
cache_read: z.number().optional(),
34-
cache_write: z.number().optional(),
35-
})
36-
.optional(),
37-
})
38-
.optional(),
39-
limit: z.object({
40-
context: z.number(),
41-
input: z.number().optional(),
42-
output: z.number(),
6+
// Positive integer preserving exact Zod JSON Schema (type: integer, exclusiveMinimum: 0).
7+
const PositiveInt = Schema.Number.annotate({
8+
[ZodOverride]: z.number().int().positive(),
9+
})
10+
11+
export const Model = Schema.Struct({
12+
id: Schema.optional(Schema.String),
13+
name: Schema.optional(Schema.String),
14+
family: Schema.optional(Schema.String),
15+
release_date: Schema.optional(Schema.String),
16+
attachment: Schema.optional(Schema.Boolean),
17+
reasoning: Schema.optional(Schema.Boolean),
18+
temperature: Schema.optional(Schema.Boolean),
19+
tool_call: Schema.optional(Schema.Boolean),
20+
interleaved: Schema.optional(
21+
Schema.Union([
22+
Schema.Literal(true),
23+
Schema.Struct({
24+
field: Schema.Literals(["reasoning_content", "reasoning_details"]),
25+
}),
26+
]),
27+
),
28+
cost: Schema.optional(
29+
Schema.Struct({
30+
input: Schema.Number,
31+
output: Schema.Number,
32+
cache_read: Schema.optional(Schema.Number),
33+
cache_write: Schema.optional(Schema.Number),
34+
context_over_200k: Schema.optional(
35+
Schema.Struct({
36+
input: Schema.Number,
37+
output: Schema.Number,
38+
cache_read: Schema.optional(Schema.Number),
39+
cache_write: Schema.optional(Schema.Number),
40+
}),
41+
),
42+
}),
43+
),
44+
limit: Schema.optional(
45+
Schema.Struct({
46+
context: Schema.Number,
47+
input: Schema.optional(Schema.Number),
48+
output: Schema.Number,
4349
}),
44-
modalities: z
45-
.object({
46-
input: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
47-
output: z.array(z.enum(["text", "audio", "image", "video", "pdf"])),
48-
})
49-
.optional(),
50-
experimental: z.boolean().optional(),
51-
status: z.enum(["alpha", "beta", "deprecated"]).optional(),
52-
provider: z.object({ npm: z.string().optional(), api: z.string().optional() }).optional(),
53-
options: z.record(z.string(), z.any()),
54-
headers: z.record(z.string(), z.string()).optional(),
55-
variants: z
56-
.record(
57-
z.string(),
58-
z
59-
.object({
60-
disabled: z.boolean().optional().describe("Disable this variant for the model"),
61-
})
62-
.catchall(z.any()),
63-
)
64-
.optional()
65-
.describe("Variant-specific configuration"),
66-
})
67-
.partial()
50+
),
51+
modalities: Schema.optional(
52+
Schema.Struct({
53+
input: Schema.mutable(Schema.Array(Schema.Literals(["text", "audio", "image", "video", "pdf"]))),
54+
output: Schema.mutable(Schema.Array(Schema.Literals(["text", "audio", "image", "video", "pdf"]))),
55+
}),
56+
),
57+
experimental: Schema.optional(Schema.Boolean),
58+
status: Schema.optional(Schema.Literals(["alpha", "beta", "deprecated"])),
59+
provider: Schema.optional(Schema.Struct({ npm: Schema.optional(Schema.String), api: Schema.optional(Schema.String) })),
60+
options: Schema.optional(Schema.Record(Schema.String, Schema.Any)),
61+
headers: Schema.optional(Schema.Record(Schema.String, Schema.String)),
62+
variants: Schema.optional(
63+
Schema.Record(
64+
Schema.String,
65+
Schema.StructWithRest(
66+
Schema.Struct({
67+
disabled: Schema.optional(Schema.Boolean).annotate({ description: "Disable this variant for the model" }),
68+
}),
69+
[Schema.Record(Schema.String, Schema.Any)],
70+
),
71+
).annotate({ description: "Variant-specific configuration" }),
72+
),
73+
}).pipe(withStatics((s) => ({ zod: zod(s) })))
6874

69-
export const Info = z
70-
.object({
71-
api: z.string().optional(),
72-
name: z.string(),
73-
env: z.array(z.string()),
74-
id: z.string(),
75-
npm: z.string().optional(),
76-
whitelist: z.array(z.string()).optional(),
77-
blacklist: z.array(z.string()).optional(),
78-
options: z
79-
.object({
80-
apiKey: z.string().optional(),
81-
baseURL: z.string().optional(),
82-
enterpriseUrl: z.string().optional().describe("GitHub Enterprise URL for copilot authentication"),
83-
setCacheKey: z.boolean().optional().describe("Enable promptCacheKey for this provider (default false)"),
84-
timeout: z
85-
.union([
86-
z
87-
.number()
88-
.int()
89-
.positive()
90-
.describe(
91-
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
92-
),
93-
z.literal(false).describe("Disable timeout for this provider entirely."),
94-
])
95-
.optional()
96-
.describe(
75+
export class Info extends Schema.Class<Info>("ProviderConfig")({
76+
api: Schema.optional(Schema.String),
77+
name: Schema.optional(Schema.String),
78+
env: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
79+
id: Schema.optional(Schema.String),
80+
npm: Schema.optional(Schema.String),
81+
whitelist: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
82+
blacklist: Schema.optional(Schema.mutable(Schema.Array(Schema.String))),
83+
options: Schema.optional(
84+
Schema.StructWithRest(
85+
Schema.Struct({
86+
apiKey: Schema.optional(Schema.String),
87+
baseURL: Schema.optional(Schema.String),
88+
enterpriseUrl: Schema.optional(Schema.String).annotate({
89+
description: "GitHub Enterprise URL for copilot authentication",
90+
}),
91+
setCacheKey: Schema.optional(Schema.Boolean).annotate({
92+
description: "Enable promptCacheKey for this provider (default false)",
93+
}),
94+
timeout: Schema.optional(
95+
Schema.Union([PositiveInt, Schema.Literal(false)]).annotate({
96+
description:
97+
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
98+
}),
99+
).annotate({
100+
description:
97101
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
98-
),
99-
chunkTimeout: z
100-
.number()
101-
.int()
102-
.positive()
103-
.optional()
104-
.describe(
102+
}),
103+
chunkTimeout: Schema.optional(PositiveInt).annotate({
104+
description:
105105
"Timeout in milliseconds between streamed SSE chunks for this provider. If no chunk arrives within this window, the request is aborted.",
106-
),
107-
})
108-
.catchall(z.any())
109-
.optional(),
110-
models: z.record(z.string(), Model).optional(),
111-
})
112-
.partial()
113-
.strict()
114-
.meta({
115-
ref: "ProviderConfig",
116-
})
117-
118-
export type Info = z.infer<typeof Info>
106+
}),
107+
}),
108+
[Schema.Record(Schema.String, Schema.Any)],
109+
),
110+
),
111+
models: Schema.optional(Schema.Record(Schema.String, Model)),
112+
}) {
113+
static readonly zod = zod(this)
114+
}
119115

120116
export * as ConfigProvider from "./provider"

0 commit comments

Comments
 (0)