refactor(core): move database schema ownership (#29068)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -25,7 +25,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem"
|
||||
import { isRecord } from "@/util/record"
|
||||
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
|
||||
import * as ProviderTransform from "./transform"
|
||||
import { ModelID, ProviderID } from "./schema"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelStatus } from "./model-status"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { ProviderError } from "./error"
|
||||
@@ -663,8 +663,8 @@ function custom(dep: CustomDep): Record<string, CustomLoader> {
|
||||
for (const m of result.models) {
|
||||
if (!input.models[m.id]) {
|
||||
models[m.id] = {
|
||||
id: ModelID.make(m.id),
|
||||
providerID: ProviderID.make("gitlab"),
|
||||
id: ProviderV2.ModelID.make(m.id),
|
||||
providerID: ProviderV2.ID.make("gitlab"),
|
||||
name: `Agent Platform (${m.name})`,
|
||||
family: "",
|
||||
api: {
|
||||
@@ -928,8 +928,8 @@ const ProviderLimit = Schema.Struct({
|
||||
})
|
||||
|
||||
export const Model = Schema.Struct({
|
||||
id: ModelID,
|
||||
providerID: ProviderID,
|
||||
id: ProviderV2.ModelID,
|
||||
providerID: ProviderV2.ID,
|
||||
api: ProviderApiInfo,
|
||||
name: Schema.String,
|
||||
family: optionalOmitUndefined(Schema.String),
|
||||
@@ -945,7 +945,7 @@ export const Model = Schema.Struct({
|
||||
export type Model = Types.DeepMutable<Schema.Schema.Type<typeof Model>>
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
id: ProviderID,
|
||||
id: ProviderV2.ID,
|
||||
name: Schema.String,
|
||||
source: Schema.Literals(["env", "config", "custom", "api"]),
|
||||
env: Schema.Array(Schema.String),
|
||||
@@ -985,8 +985,8 @@ export function defaultModelIDs<T extends { models: Record<string, { id: string
|
||||
}
|
||||
|
||||
export class ModelNotFoundError extends Schema.TaggedErrorClass<ModelNotFoundError>()("ProviderModelNotFoundError", {
|
||||
providerID: ProviderID,
|
||||
modelID: ModelID,
|
||||
providerID: ProviderV2.ID,
|
||||
modelID: ProviderV2.ModelID,
|
||||
suggestions: Schema.optional(Schema.Array(Schema.String)),
|
||||
cause: Schema.optional(Schema.Defect),
|
||||
}) {
|
||||
@@ -996,7 +996,7 @@ export class ModelNotFoundError extends Schema.TaggedErrorClass<ModelNotFoundErr
|
||||
}
|
||||
|
||||
export class InitError extends Schema.TaggedErrorClass<InitError>()("ProviderInitError", {
|
||||
providerID: ProviderID,
|
||||
providerID: ProviderV2.ID,
|
||||
cause: Schema.optional(Schema.Defect),
|
||||
}) {
|
||||
static isInstance(input: unknown): input is InitError {
|
||||
@@ -1011,7 +1011,7 @@ export class NoProvidersError extends Schema.TaggedErrorClass<NoProvidersError>(
|
||||
}
|
||||
|
||||
export class NoModelsError extends Schema.TaggedErrorClass<NoModelsError>()("ProviderNoModelsError", {
|
||||
providerID: ProviderID,
|
||||
providerID: ProviderV2.ID,
|
||||
}) {
|
||||
static isInstance(input: unknown): input is NoModelsError {
|
||||
return input instanceof NoModelsError
|
||||
@@ -1022,22 +1022,22 @@ export type DefaultModelError = ModelNotFoundError | NoProvidersError | NoModels
|
||||
export type Error = ModelNotFoundError | InitError | NoProvidersError | NoModelsError
|
||||
|
||||
export interface Interface {
|
||||
readonly list: () => Effect.Effect<Record<ProviderID, Info>>
|
||||
readonly getProvider: (providerID: ProviderID) => Effect.Effect<Info>
|
||||
readonly getModel: (providerID: ProviderID, modelID: ModelID) => Effect.Effect<Model, ModelNotFoundError>
|
||||
readonly list: () => Effect.Effect<Record<ProviderV2.ID, Info>>
|
||||
readonly getProvider: (providerID: ProviderV2.ID) => Effect.Effect<Info>
|
||||
readonly getModel: (providerID: ProviderV2.ID, modelID: ProviderV2.ModelID) => Effect.Effect<Model, ModelNotFoundError>
|
||||
readonly getLanguage: (model: Model) => Effect.Effect<LanguageModelV3, ModelNotFoundError>
|
||||
readonly closest: (
|
||||
providerID: ProviderID,
|
||||
providerID: ProviderV2.ID,
|
||||
query: string[],
|
||||
) => Effect.Effect<{ providerID: ProviderID; modelID: string } | undefined>
|
||||
readonly getSmallModel: (providerID: ProviderID) => Effect.Effect<Model | undefined>
|
||||
readonly defaultModel: () => Effect.Effect<{ providerID: ProviderID; modelID: ModelID }, DefaultModelError>
|
||||
) => Effect.Effect<{ providerID: ProviderV2.ID; modelID: string } | undefined>
|
||||
readonly getSmallModel: (providerID: ProviderV2.ID) => Effect.Effect<Model | undefined>
|
||||
readonly defaultModel: () => Effect.Effect<{ providerID: ProviderV2.ID; modelID: ProviderV2.ModelID }, DefaultModelError>
|
||||
}
|
||||
|
||||
interface State {
|
||||
models: Map<string, LanguageModelV3>
|
||||
providers: Record<ProviderID, Info>
|
||||
catalog: Record<ProviderID, Info>
|
||||
providers: Record<ProviderV2.ID, Info>
|
||||
catalog: Record<ProviderV2.ID, Info>
|
||||
sdk: Map<string, BundledSDK>
|
||||
modelLoaders: Record<string, CustomModelLoader>
|
||||
varsLoaders: Record<string, CustomVarsLoader>
|
||||
@@ -1082,8 +1082,8 @@ function cost(c: ModelsDev.Model["cost"]): Model["cost"] {
|
||||
|
||||
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
|
||||
const base: Model = {
|
||||
id: ModelID.make(model.id),
|
||||
providerID: ProviderID.make(provider.id),
|
||||
id: ProviderV2.ModelID.make(model.id),
|
||||
providerID: ProviderV2.ID.make(provider.id),
|
||||
name: model.name,
|
||||
family: model.family,
|
||||
api: {
|
||||
@@ -1140,7 +1140,7 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
|
||||
const base = fromModelsDevModel(provider, model)
|
||||
models[id] = {
|
||||
...base,
|
||||
id: ModelID.make(id),
|
||||
id: ProviderV2.ModelID.make(id),
|
||||
name: `${model.name} ${mode[0].toUpperCase()}${mode.slice(1)}`,
|
||||
cost: opts.cost ? mergeDeep(base.cost, cost(opts.cost)) : base.cost,
|
||||
options: opts.provider?.body
|
||||
@@ -1156,7 +1156,7 @@ export function fromModelsDevProvider(provider: ModelsDev.Provider): Info {
|
||||
}
|
||||
}
|
||||
return {
|
||||
id: ProviderID.make(provider.id),
|
||||
id: ProviderV2.ID.make(provider.id),
|
||||
source: "custom",
|
||||
name: provider.name,
|
||||
env: [...(provider.env ?? [])],
|
||||
@@ -1175,7 +1175,7 @@ function suggestionModelIDs(provider: Info | undefined, enableExperimentalModels
|
||||
})
|
||||
}
|
||||
|
||||
function modelSuggestions(provider: Info | undefined, modelID: ModelID, enableExperimentalModels: boolean) {
|
||||
function modelSuggestions(provider: Info | undefined, modelID: ProviderV2.ModelID, enableExperimentalModels: boolean) {
|
||||
const available = suggestionModelIDs(provider, enableExperimentalModels)
|
||||
const fuzzy = fuzzysort.go(modelID, available, { limit: 3, threshold: -10000 }).map((m) => m.target)
|
||||
if (fuzzy.length) return fuzzy
|
||||
@@ -1217,7 +1217,7 @@ export const layer = Layer.effect(
|
||||
const catalog = mapValues(modelsDev, fromModelsDevProvider)
|
||||
const database = mapValues(catalog, toPublicInfo)
|
||||
|
||||
const providers: Record<ProviderID, Info> = {} as Record<ProviderID, Info>
|
||||
const providers: Record<ProviderV2.ID, Info> = {} as Record<ProviderV2.ID, Info>
|
||||
const languages = new Map<string, LanguageModelV3>()
|
||||
const modelLoaders: {
|
||||
[providerID: string]: CustomModelLoader
|
||||
@@ -1238,7 +1238,7 @@ export const layer = Layer.effect(
|
||||
|
||||
log.info("init")
|
||||
|
||||
function mergeProvider(providerID: ProviderID, provider: Partial<Info>) {
|
||||
function mergeProvider(providerID: ProviderV2.ID, provider: Partial<Info>) {
|
||||
const existing = providers[providerID]
|
||||
if (existing) {
|
||||
// @ts-expect-error
|
||||
@@ -1259,7 +1259,7 @@ export const layer = Layer.effect(
|
||||
const disabled = new Set(cfg.disabled_providers ?? [])
|
||||
const enabled = cfg.enabled_providers ? new Set(cfg.enabled_providers) : null
|
||||
|
||||
function isProviderAllowed(providerID: ProviderID): boolean {
|
||||
function isProviderAllowed(providerID: ProviderV2.ID): boolean {
|
||||
if (enabled && !enabled.has(providerID)) return false
|
||||
if (disabled.has(providerID)) return false
|
||||
return true
|
||||
@@ -1270,7 +1270,7 @@ export const layer = Layer.effect(
|
||||
const models = p?.models
|
||||
if (!p || !models) continue
|
||||
|
||||
const providerID = ProviderID.make(p.id)
|
||||
const providerID = ProviderV2.ID.make(p.id)
|
||||
if (disabled.has(providerID)) continue
|
||||
|
||||
const provider = database[providerID]
|
||||
@@ -1284,7 +1284,7 @@ export const layer = Layer.effect(
|
||||
id,
|
||||
{
|
||||
...model,
|
||||
id: ModelID.make(id),
|
||||
id: ProviderV2.ModelID.make(id),
|
||||
providerID,
|
||||
},
|
||||
]),
|
||||
@@ -1296,7 +1296,7 @@ export const layer = Layer.effect(
|
||||
for (const [providerID, provider] of configProviders) {
|
||||
const existing = database[providerID]
|
||||
const parsed: Info = {
|
||||
id: ProviderID.make(providerID),
|
||||
id: ProviderV2.ID.make(providerID),
|
||||
name: provider.name ?? existing?.name ?? providerID,
|
||||
env: provider.env ?? existing?.env ?? [],
|
||||
options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
|
||||
@@ -1319,7 +1319,7 @@ export const layer = Layer.effect(
|
||||
return existingModel?.name ?? modelID
|
||||
})
|
||||
const parsedModel: Model = {
|
||||
id: ModelID.make(modelID),
|
||||
id: ProviderV2.ModelID.make(modelID),
|
||||
api: {
|
||||
id: apiID,
|
||||
npm: apiNpm,
|
||||
@@ -1327,7 +1327,7 @@ export const layer = Layer.effect(
|
||||
},
|
||||
status: model.status ?? existingModel?.status ?? "active",
|
||||
name,
|
||||
providerID: ProviderID.make(providerID),
|
||||
providerID: ProviderV2.ID.make(providerID),
|
||||
capabilities: {
|
||||
temperature: model.temperature ?? existingModel?.capabilities.temperature ?? false,
|
||||
reasoning: model.reasoning ?? existingModel?.capabilities.reasoning ?? false,
|
||||
@@ -1389,7 +1389,7 @@ export const layer = Layer.effect(
|
||||
// load env
|
||||
const envs = yield* env.all()
|
||||
for (const [id, provider] of Object.entries(database)) {
|
||||
const providerID = ProviderID.make(id)
|
||||
const providerID = ProviderV2.ID.make(id)
|
||||
if (disabled.has(providerID)) continue
|
||||
const apiKey = provider.env.map((item) => envs[item]).find(Boolean)
|
||||
if (!apiKey) continue
|
||||
@@ -1402,7 +1402,7 @@ export const layer = Layer.effect(
|
||||
// load apikeys
|
||||
const auths = yield* auth.all().pipe(Effect.orDie)
|
||||
for (const [id, provider] of Object.entries(auths)) {
|
||||
const providerID = ProviderID.make(id)
|
||||
const providerID = ProviderV2.ID.make(id)
|
||||
if (disabled.has(providerID)) continue
|
||||
if (provider.type === "api") {
|
||||
mergeProvider(providerID, {
|
||||
@@ -1415,7 +1415,7 @@ export const layer = Layer.effect(
|
||||
// plugin auth loader - database now has entries for config providers
|
||||
for (const plugin of plugins) {
|
||||
if (!plugin.auth) continue
|
||||
const providerID = ProviderID.make(plugin.auth.provider)
|
||||
const providerID = ProviderV2.ID.make(plugin.auth.provider)
|
||||
if (disabled.has(providerID)) continue
|
||||
|
||||
const stored = yield* auth.get(providerID).pipe(Effect.orDie)
|
||||
@@ -1434,7 +1434,7 @@ export const layer = Layer.effect(
|
||||
}
|
||||
|
||||
for (const [id, fn] of Object.entries(custom(dep))) {
|
||||
const providerID = ProviderID.make(id)
|
||||
const providerID = ProviderV2.ID.make(id)
|
||||
if (disabled.has(providerID)) continue
|
||||
const data = database[providerID]
|
||||
if (!data) {
|
||||
@@ -1454,7 +1454,7 @@ export const layer = Layer.effect(
|
||||
|
||||
// load config - re-apply with updated data
|
||||
for (const [id, provider] of configProviders) {
|
||||
const providerID = ProviderID.make(id)
|
||||
const providerID = ProviderV2.ID.make(id)
|
||||
const partial: Partial<Info> = { source: "config" }
|
||||
if (provider.env) partial.env = provider.env
|
||||
if (provider.name) partial.name = provider.name
|
||||
@@ -1462,7 +1462,7 @@ export const layer = Layer.effect(
|
||||
mergeProvider(providerID, partial)
|
||||
}
|
||||
|
||||
const gitlab = ProviderID.make("gitlab")
|
||||
const gitlab = ProviderV2.ID.make("gitlab")
|
||||
if (discoveryLoaders[gitlab] && providers[gitlab] && isProviderAllowed(gitlab)) {
|
||||
yield* Effect.promise(async () => {
|
||||
try {
|
||||
@@ -1479,7 +1479,7 @@ export const layer = Layer.effect(
|
||||
}
|
||||
|
||||
for (const [id, provider] of Object.entries(providers)) {
|
||||
const providerID = ProviderID.make(id)
|
||||
const providerID = ProviderV2.ID.make(id)
|
||||
if (!isProviderAllowed(providerID)) {
|
||||
delete providers[providerID]
|
||||
continue
|
||||
@@ -1493,10 +1493,10 @@ export const layer = Layer.effect(
|
||||
// These chat aliases are invalid for the special handling in the
|
||||
// built-in providers below, but custom providers may support them.
|
||||
(modelID === "gpt-5-chat-latest" &&
|
||||
(providerID === ProviderID.openai ||
|
||||
providerID === ProviderID.githubCopilot ||
|
||||
providerID === ProviderID.openrouter)) ||
|
||||
(providerID === ProviderID.openrouter && modelID === "openai/gpt-5-chat")
|
||||
(providerID === ProviderV2.ID.openai ||
|
||||
providerID === ProviderV2.ID.githubCopilot ||
|
||||
providerID === ProviderV2.ID.openrouter)) ||
|
||||
(providerID === ProviderV2.ID.openrouter && modelID === "openai/gpt-5-chat")
|
||||
)
|
||||
delete provider.models[modelID]
|
||||
if (model.status === "alpha" && !runtimeFlags.enableExperimentalModels) delete provider.models[modelID]
|
||||
@@ -1702,11 +1702,11 @@ export const layer = Layer.effect(
|
||||
}
|
||||
}
|
||||
|
||||
const getProvider = Effect.fn("Provider.getProvider")((providerID: ProviderID) =>
|
||||
const getProvider = Effect.fn("Provider.getProvider")((providerID: ProviderV2.ID) =>
|
||||
InstanceState.use(state, (s) => s.providers[providerID]),
|
||||
)
|
||||
|
||||
const getModel = Effect.fn("Provider.getModel")(function* (providerID: ProviderID, modelID: ModelID) {
|
||||
const getModel = Effect.fn("Provider.getModel")(function* (providerID: ProviderV2.ID, modelID: ProviderV2.ModelID) {
|
||||
const s = yield* InstanceState.get(state)
|
||||
const provider = s.providers[providerID]
|
||||
if (!provider) {
|
||||
@@ -1756,7 +1756,7 @@ export const layer = Layer.effect(
|
||||
)
|
||||
})
|
||||
|
||||
const closest = Effect.fn("Provider.closest")(function* (providerID: ProviderID, query: string[]) {
|
||||
const closest = Effect.fn("Provider.closest")(function* (providerID: ProviderV2.ID, query: string[]) {
|
||||
const s = yield* InstanceState.get(state)
|
||||
const provider = s.providers[providerID]
|
||||
if (!provider) return undefined
|
||||
@@ -1768,7 +1768,7 @@ export const layer = Layer.effect(
|
||||
return undefined
|
||||
})
|
||||
|
||||
const getSmallModel = Effect.fn("Provider.getSmallModel")(function* (providerID: ProviderID) {
|
||||
const getSmallModel = Effect.fn("Provider.getSmallModel")(function* (providerID: ProviderV2.ID) {
|
||||
const cfg = yield* config.get()
|
||||
|
||||
if (cfg.small_model) {
|
||||
@@ -1798,7 +1798,7 @@ export const layer = Layer.effect(
|
||||
priority = ["gpt-5-mini", "claude-haiku-4.5", ...priority]
|
||||
}
|
||||
for (const item of priority) {
|
||||
if (providerID === ProviderID.amazonBedrock) {
|
||||
if (providerID === ProviderV2.ID.amazonBedrock) {
|
||||
const crossRegionPrefixes = ["global.", "us.", "eu."]
|
||||
const candidates = Object.keys(provider.models).filter((m) => m.includes(item))
|
||||
|
||||
@@ -1832,16 +1832,16 @@ export const layer = Layer.effect(
|
||||
|
||||
const s = yield* InstanceState.get(state)
|
||||
const recent = yield* fs.readJson(path.join(Global.Path.state, "model.json")).pipe(
|
||||
Effect.map((x): { providerID: ProviderID; modelID: ModelID }[] => {
|
||||
Effect.map((x): { providerID: ProviderV2.ID; modelID: ProviderV2.ModelID }[] => {
|
||||
if (!isRecord(x) || !Array.isArray(x.recent)) return []
|
||||
return x.recent.flatMap((item) => {
|
||||
if (!isRecord(item)) return []
|
||||
if (typeof item.providerID !== "string") return []
|
||||
if (typeof item.modelID !== "string") return []
|
||||
return [{ providerID: ProviderID.make(item.providerID), modelID: ModelID.make(item.modelID) }]
|
||||
return [{ providerID: ProviderV2.ID.make(item.providerID), modelID: ProviderV2.ModelID.make(item.modelID) }]
|
||||
})
|
||||
}),
|
||||
Effect.catch(() => Effect.succeed([] as { providerID: ProviderID; modelID: ModelID }[])),
|
||||
Effect.catch(() => Effect.succeed([] as { providerID: ProviderV2.ID; modelID: ProviderV2.ModelID }[])),
|
||||
)
|
||||
for (const entry of recent) {
|
||||
const provider = s.providers[entry.providerID]
|
||||
@@ -1889,8 +1889,8 @@ export function sort<T extends { id: string }>(models: T[]) {
|
||||
export function parseModel(model: string) {
|
||||
const [providerID, ...rest] = model.split("/")
|
||||
return {
|
||||
providerID: ProviderID.make(providerID),
|
||||
modelID: ModelID.make(rest.join("/")),
|
||||
providerID: ProviderV2.ID.make(providerID),
|
||||
modelID: ProviderV2.ModelID.make(rest.join("/")),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user