refactor(provider): migrate provider domain to Effect Schema (#24027)

This commit is contained in:
Kit Langton 2026-04-23 13:17:33 -04:00 committed by GitHub
parent 1e439b8226
commit 93940a1859
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 93 additions and 113 deletions

View file

@ -274,9 +274,9 @@ Possible later tightening after the Schema-first migration is stable:
### Provider domain
- [ ] `src/provider/auth.ts`
- [ ] `src/provider/models.ts`
- [ ] `src/provider/provider.ts`
- [x] `src/provider/auth.ts`
- [x] `src/provider/models.ts`
- [x] `src/provider/provider.ts`
### Tool schemas

View file

@ -1,13 +1,12 @@
import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin"
import { NamedError } from "@opencode-ai/shared/util/error"
import { Auth } from "@/auth"
import { InstanceState } from "@/effect"
import { zod } from "@/util/effect-zod"
import { namedSchemaError } from "@/util/named-schema-error"
import { withStatics } from "@/util/schema"
import { Plugin } from "../plugin"
import { ProviderID } from "./schema"
import { Array as Arr, Effect, Layer, Record, Result, Context, Schema } from "effect"
import z from "zod"
const When = Schema.Struct({
key: Schema.String,
@ -70,22 +69,16 @@ export const CallbackInput = Schema.Struct({
}).pipe(withStatics((s) => ({ zod: zod(s) })))
export type CallbackInput = Schema.Schema.Type<typeof CallbackInput>
export const OauthMissing = NamedError.create("ProviderAuthOauthMissing", z.object({ providerID: ProviderID.zod }))
export const OauthMissing = namedSchemaError("ProviderAuthOauthMissing", { providerID: ProviderID })
export const OauthCodeMissing = NamedError.create(
"ProviderAuthOauthCodeMissing",
z.object({ providerID: ProviderID.zod }),
)
export const OauthCodeMissing = namedSchemaError("ProviderAuthOauthCodeMissing", { providerID: ProviderID })
export const OauthCallbackFailed = NamedError.create("ProviderAuthOauthCallbackFailed", z.object({}))
export const OauthCallbackFailed = namedSchemaError("ProviderAuthOauthCallbackFailed", {})
export const ValidationFailed = NamedError.create(
"ProviderAuthValidationFailed",
z.object({
field: z.string(),
message: z.string(),
}),
)
export const ValidationFailed = namedSchemaError("ProviderAuthValidationFailed", {
field: Schema.String,
message: Schema.String,
})
export type Error =
| Auth.AuthError

View file

@ -1,7 +1,7 @@
import { Global } from "../global"
import { Log } from "../util"
import path from "path"
import z from "zod"
import { Schema } from "effect"
import { Installation } from "../installation"
import { Flag } from "../flag/flag"
import { lazy } from "@/util/lazy"
@ -21,91 +21,85 @@ const filepath = path.join(
)
const ttl = 5 * 60 * 1000
type JsonValue = string | number | boolean | null | { [key: string]: JsonValue } | JsonValue[]
const JsonValue: z.ZodType<JsonValue> = z.lazy(() =>
z.union([z.string(), z.number(), z.boolean(), z.null(), z.array(JsonValue), z.record(z.string(), JsonValue)]),
)
const 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(),
const Cost = 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),
}),
),
})
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: Cost.optional(),
limit: z.object({
context: z.number(),
input: z.number().optional(),
output: z.number(),
export const Model = Schema.Struct({
id: Schema.String,
name: Schema.String,
family: Schema.optional(Schema.String),
release_date: Schema.String,
attachment: Schema.Boolean,
reasoning: Schema.Boolean,
temperature: Schema.Boolean,
tool_call: Schema.Boolean,
interleaved: Schema.optional(
Schema.Union([
Schema.Literal(true),
Schema.Struct({
field: Schema.Literals(["reasoning_content", "reasoning_details"]),
}),
]),
),
cost: Schema.optional(Cost),
limit: 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
.object({
modes: z
.record(
z.string(),
z.object({
cost: Cost.optional(),
provider: z
.object({
body: z.record(z.string(), JsonValue).optional(),
headers: z.record(z.string(), z.string()).optional(),
})
.optional(),
modalities: Schema.optional(
Schema.Struct({
input: Schema.Array(Schema.Literals(["text", "audio", "image", "video", "pdf"])),
output: Schema.Array(Schema.Literals(["text", "audio", "image", "video", "pdf"])),
}),
),
experimental: Schema.optional(
Schema.Struct({
modes: Schema.optional(
Schema.Record(
Schema.String,
Schema.Struct({
cost: Schema.optional(Cost),
provider: Schema.optional(
Schema.Struct({
body: Schema.optional(Schema.Record(Schema.String, Schema.MutableJson)),
headers: Schema.optional(Schema.Record(Schema.String, Schema.String)),
}),
),
}),
)
.optional(),
})
.optional(),
status: z.enum(["alpha", "beta", "deprecated"]).optional(),
provider: z.object({ npm: z.string().optional(), api: z.string().optional() }).optional(),
),
),
}),
),
status: Schema.optional(Schema.Literals(["alpha", "beta", "deprecated"])),
provider: Schema.optional(
Schema.Struct({ npm: Schema.optional(Schema.String), api: Schema.optional(Schema.String) }),
),
})
export type Model = z.infer<typeof Model>
export type Model = Schema.Schema.Type<typeof Model>
export const Provider = z.object({
api: z.string().optional(),
name: z.string(),
env: z.array(z.string()),
id: z.string(),
npm: z.string().optional(),
models: z.record(z.string(), Model),
export const Provider = Schema.Struct({
api: Schema.optional(Schema.String),
name: Schema.String,
env: Schema.Array(Schema.String),
id: Schema.String,
npm: Schema.optional(Schema.String),
models: Schema.Record(Schema.String, Model),
})
export type Provider = z.infer<typeof Provider>
export type Provider = Schema.Schema.Type<typeof Provider>
function url() {
return Flag.OPENCODE_MODELS_URL || "https://models.dev"

View file

@ -1,4 +1,3 @@
import z from "zod"
import os from "os"
import fuzzysort from "fuzzysort"
import { Config } from "../config"
@ -8,7 +7,6 @@ import { Log } from "../util"
import { Npm } from "../npm"
import { Hash } from "@opencode-ai/shared/util/hash"
import { Plugin } from "../plugin"
import { NamedError } from "@opencode-ai/shared/util/error"
import { type LanguageModelV3 } from "@ai-sdk/provider"
import * as ModelsDev from "./models"
import { Auth } from "../auth"
@ -16,6 +14,7 @@ import { Env } from "../env"
import { InstallationVersion } from "../installation/version"
import { Flag } from "../flag/flag"
import { zod } from "@/util/effect-zod"
import { namedSchemaError } from "@/util/named-schema-error"
import { iife } from "@/util/iife"
import { Global } from "../global"
import path from "path"
@ -1047,7 +1046,7 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
id: ProviderID.make(provider.id),
source: "custom",
name: provider.name,
env: provider.env ?? [],
env: [...(provider.env ?? [])],
options: {},
models,
}
@ -1713,18 +1712,12 @@ export function parseModel(model: string) {
}
}
export const ModelNotFoundError = NamedError.create(
"ProviderModelNotFoundError",
z.object({
providerID: ProviderID.zod,
modelID: ModelID.zod,
suggestions: z.array(z.string()).optional(),
}),
)
export const ModelNotFoundError = namedSchemaError("ProviderModelNotFoundError", {
providerID: ProviderID,
modelID: ModelID,
suggestions: Schema.optional(Schema.Array(Schema.String)),
})
export const InitError = NamedError.create(
"ProviderInitError",
z.object({
providerID: ProviderID.zod,
}),
)
export const InitError = namedSchemaError("ProviderInitError", {
providerID: ProviderID,
})