provider-cleanup
Dax Raad 2025-12-03 13:42:55 -05:00
parent a844eb2429
commit 8b1c55f9fa
2 changed files with 97 additions and 85 deletions

View File

@ -470,6 +470,50 @@ export namespace Config {
})
export type Layout = z.infer<typeof Layout>
export const Provider = ModelsDev.Provider.partial()
.extend({
whitelist: z.array(z.string()).optional(),
blacklist: z.array(z.string()).optional(),
models: z
.record(
z.string(),
ModelsDev.Model.partial().refine(
(input) => input.id === undefined,
"The model.id field can no longer be specified. Use model.target to specify an alternate model id to use when calling the provider.",
),
)
.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(
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
),
})
.catchall(z.any())
.optional(),
})
.strict()
.meta({
ref: "ProviderConfig",
})
export type Provider = z.infer<typeof Provider>
export const Info = z
.object({
$schema: z.string().optional().describe("JSON schema reference for configuration validation"),
@ -536,51 +580,7 @@ export namespace Config {
.optional()
.describe("Agent configuration, see https://opencode.ai/docs/agent"),
provider: z
.record(
z.string(),
ModelsDev.Provider.partial()
.extend({
whitelist: z.array(z.string()).optional(),
blacklist: z.array(z.string()).optional(),
models: z
.record(
z.string(),
ModelsDev.Model.partial().refine(
(input) => input.id === undefined,
"The model.id field can no longer be specified. Use model.target to specify an alternate model id to use when calling the provider.",
),
)
.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(
"Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout.",
),
})
.catchall(z.any())
.optional(),
})
.strict(),
)
.record(z.string(), Provider)
.optional()
.describe("Custom provider configurations and model overrides"),
mcp: z.record(z.string(), Mcp).optional().describe("MCP (Model Context Protocol) server configurations"),

View File

@ -1,7 +1,7 @@
import z from "zod"
import fuzzysort from "fuzzysort"
import { Config } from "../config/config"
import { entries, mapValues, mergeDeep, pipe, sortBy } from "remeda"
import { mapValues, mergeDeep, sortBy } from "remeda"
import { NoSuchModelError, type LanguageModel, type Provider as SDK } from "ai"
import { Log } from "../util/log"
import { BunProc } from "../bun"
@ -360,20 +360,25 @@ export namespace Provider {
})
export type Model = z.infer<typeof Model>
export const Info = z.object({
id: z.string(),
name: z.string(),
source: z.enum(["env", "config", "custom", "api"]),
env: z.string().array(),
key: z.string().optional(),
options: z.record(z.string(), z.any()),
models: z.record(z.string(), Model),
})
export const Info = z
.object({
id: z.string(),
name: z.string(),
source: z.enum(["env", "config", "custom", "api"]),
env: z.string().array(),
key: z.string().optional(),
options: z.record(z.string(), z.any()),
models: z.record(z.string(), Model),
})
.meta({
ref: "Provider",
})
export type Info = z.infer<typeof Info>
function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model): Model {
return {
id: model.id,
providerID: provider.id,
name: model.name,
api: {
id: model.id,
@ -483,16 +488,16 @@ export namespace Provider {
providers[providerID] = mergeDeep(match, provider)
}
// TODO: load config
// extend database from config
for (const [providerID, provider] of configProviders) {
const existing = database[providerID]
const parsed: ModelsDev.Provider = {
const parsed: Info = {
id: providerID,
npm: provider.npm ?? existing?.npm,
name: provider.name ?? existing?.name ?? providerID,
env: provider.env ?? existing?.env ?? [],
api: provider.api ?? existing?.api,
models: existing?.models ?? {},
options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
source: "config",
models: {},
}
for (const [modelID, model] of Object.entries(provider.models ?? {})) {
@ -504,44 +509,51 @@ export namespace Provider {
})
const parsedModel: Model = {
id: modelID,
apiID: model.target ?? existing?.target ?? modelID,
status: model.status ?? existing?.status ?? "alpha",
api: {
id: model.id ?? existing?.api.id ?? modelID,
npm: model.provider?.npm ?? provider.npm ?? existing?.api.npm ?? providerID,
url: provider?.api ?? existing?.api.url,
},
status: model.status ?? existing?.status ?? "active",
name,
providerID,
npm: model.provider?.npm ?? existing?.provider?.npm ?? provider.npm ?? providerID,
support: {
temperature: model.temperature ?? existing?.temperature ?? false,
reasoning: model.reasoning ?? existing?.reasoning ?? false,
attachment: model.attachment ?? existing?.attachment ?? false,
toolcall: model.tool_call ?? existing?.tool_call ?? true,
capabilities: {
temperature: model.temperature ?? existing?.capabilities.temperature ?? false,
reasoning: model.reasoning ?? existing?.capabilities.reasoning ?? false,
attachment: model.attachment ?? existing?.capabilities.attachment ?? false,
toolcall: model.tool_call ?? existing?.capabilities.toolcall ?? true,
input: {
text: model.modalities?.input?.includes("text") ?? false,
audio: model.modalities?.input?.includes("audio") ?? false,
image: model.modalities?.input?.includes("image") ?? false,
video: model.modalities?.input?.includes("video") ?? false,
pdf: model.modalities?.input?.includes("pdf") ?? false,
},
output: {
text: model.modalities?.output?.includes("text") ?? false,
audio: model.modalities?.output?.includes("audio") ?? false,
image: model.modalities?.output?.includes("image") ?? false,
video: model.modalities?.output?.includes("video") ?? false,
pdf: model.modalities?.output?.includes("pdf") ?? false,
},
},
cost: {
input: model?.cost?.input ?? existing?.cost?.input ?? 0,
output: model?.cost?.output ?? existing?.cost?.output ?? 0,
cache: {
read: model?.cost?.cache_read ?? existing?.cost?.cache_read ?? 0,
write: model?.cost?.cache_write ?? existing?.cost?.cache_write ?? 0,
read: model?.cost?.cache_read ?? existing?.cost?.cache.read ?? 0,
write: model?.cost?.cache_write ?? existing?.cost?.cache.write ?? 0,
},
},
options: {
...existing?.options,
...model.options,
options: mergeDeep(existing?.options ?? {}, model.options ?? {}),
limit: {
context: model.limit?.context ?? existing?.limit?.context ?? 0,
output: model.limit?.output ?? existing?.limit?.output ?? 0,
},
limit: model.limit ??
existing?.limit ?? {
context: 0,
output: 0,
},
modalities: model.modalities ??
existing?.modalities ?? {
input: ["text"],
output: ["text"],
},
headers: model.headers ?? {},
headers: mergeDeep(existing?.headers ?? {}, model.headers ?? {}),
}
parsed.models[modelID] = parsedModel
}
database[providerID] = parsed
}