This commit is contained in:
Dax Raad 2025-12-03 19:00:22 -05:00
commit bbbffbf928
3 changed files with 32 additions and 34 deletions

View file

@ -474,15 +474,7 @@ export namespace Config {
.extend({ .extend({
whitelist: z.array(z.string()).optional(), whitelist: z.array(z.string()).optional(),
blacklist: z.array(z.string()).optional(), blacklist: z.array(z.string()).optional(),
models: z models: z.record(z.string(), ModelsDev.Model.partial()).optional(),
.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 options: z
.object({ .object({
apiKey: z.string().optional(), apiKey: z.string().optional(),

View file

@ -481,8 +481,14 @@ export namespace Provider {
function mergeProvider(providerID: string, provider: Partial<Info>) { function mergeProvider(providerID: string, provider: Partial<Info>) {
const match = database[providerID] const match = database[providerID]
if (!match) return if (!match) return
// @ts-expect-error const existing = providers[providerID]
providers[providerID] = mergeDeep(match, provider) if (existing) {
// @ts-expect-error
providers[providerID] = mergeDeep(existing, provider)
} else {
// @ts-expect-error
providers[providerID] = mergeDeep(match, provider)
}
} }
// extend database from config // extend database from config
@ -494,7 +500,7 @@ export namespace Provider {
env: provider.env ?? existing?.env ?? [], env: provider.env ?? existing?.env ?? [],
options: mergeDeep(existing?.options ?? {}, provider.options ?? {}), options: mergeDeep(existing?.options ?? {}, provider.options ?? {}),
source: "config", source: "config",
models: {}, models: existing?.models ?? {},
} }
for (const [modelID, model] of Object.entries(provider.models ?? {})) { for (const [modelID, model] of Object.entries(provider.models ?? {})) {
@ -520,18 +526,18 @@ export namespace Provider {
attachment: model.attachment ?? existing?.capabilities.attachment ?? false, attachment: model.attachment ?? existing?.capabilities.attachment ?? false,
toolcall: model.tool_call ?? existing?.capabilities.toolcall ?? true, toolcall: model.tool_call ?? existing?.capabilities.toolcall ?? true,
input: { input: {
text: model.modalities?.input?.includes("text") ?? false, text: model.modalities?.input?.includes("text") ?? existing?.capabilities.input.text ?? true,
audio: model.modalities?.input?.includes("audio") ?? false, audio: model.modalities?.input?.includes("audio") ?? existing?.capabilities.input.audio ?? false,
image: model.modalities?.input?.includes("image") ?? false, image: model.modalities?.input?.includes("image") ?? existing?.capabilities.input.image ?? false,
video: model.modalities?.input?.includes("video") ?? false, video: model.modalities?.input?.includes("video") ?? existing?.capabilities.input.video ?? false,
pdf: model.modalities?.input?.includes("pdf") ?? false, pdf: model.modalities?.input?.includes("pdf") ?? existing?.capabilities.input.pdf ?? false,
}, },
output: { output: {
text: model.modalities?.output?.includes("text") ?? false, text: model.modalities?.output?.includes("text") ?? existing?.capabilities.output.text ?? true,
audio: model.modalities?.output?.includes("audio") ?? false, audio: model.modalities?.output?.includes("audio") ?? existing?.capabilities.output.audio ?? false,
image: model.modalities?.output?.includes("image") ?? false, image: model.modalities?.output?.includes("image") ?? existing?.capabilities.output.image ?? false,
video: model.modalities?.output?.includes("video") ?? false, video: model.modalities?.output?.includes("video") ?? existing?.capabilities.output.video ?? false,
pdf: model.modalities?.output?.includes("pdf") ?? false, pdf: model.modalities?.output?.includes("pdf") ?? existing?.capabilities.output.pdf ?? false,
}, },
}, },
cost: { cost: {
@ -638,12 +644,11 @@ export namespace Provider {
// load config // load config
for (const [providerID, provider] of configProviders) { for (const [providerID, provider] of configProviders) {
mergeProvider(providerID, { const partial: Partial<Info> = { source: "config" }
source: "config", if (provider.env) partial.env = provider.env
env: provider.env, if (provider.name) partial.name = provider.name
name: provider.name, if (provider.options) partial.options = provider.options
options: provider.options, mergeProvider(providerID, partial)
})
} }
for (const [providerID, provider] of Object.entries(providers)) { for (const [providerID, provider] of Object.entries(providers)) {

View file

@ -634,7 +634,7 @@ test("getModel uses realIdByKey for aliased models", async () => {
}) })
}) })
test("provider api field sets default baseURL", async () => { test("provider api field sets model api.url", async () => {
await using tmp = await tmpdir({ await using tmp = await tmpdir({
init: async (dir) => { init: async (dir) => {
await Bun.write( await Bun.write(
@ -667,7 +667,8 @@ test("provider api field sets default baseURL", async () => {
directory: tmp.path, directory: tmp.path,
fn: async () => { fn: async () => {
const providers = await Provider.list() const providers = await Provider.list()
expect(providers["custom-api"].options.baseURL).toBe("https://api.example.com/v1") // api field is stored on model.api.url, used by getSDK to set baseURL
expect(providers["custom-api"].models["model-1"].api.url).toBe("https://api.example.com/v1")
}, },
}) })
}) })
@ -1122,8 +1123,8 @@ test("provider with multiple env var options only includes apiKey when single en
fn: async () => { fn: async () => {
const providers = await Provider.list() const providers = await Provider.list()
expect(providers["multi-env"]).toBeDefined() expect(providers["multi-env"]).toBeDefined()
// When multiple env options exist, apiKey should NOT be auto-set // When multiple env options exist, key should NOT be auto-set
expect(providers["multi-env"].options.apiKey).toBeUndefined() expect(providers["multi-env"].key).toBeUndefined()
}, },
}) })
}) })
@ -1164,8 +1165,8 @@ test("provider with single env var includes apiKey automatically", async () => {
fn: async () => { fn: async () => {
const providers = await Provider.list() const providers = await Provider.list()
expect(providers["single-env"]).toBeDefined() expect(providers["single-env"]).toBeDefined()
// Single env option should auto-set apiKey // Single env option should auto-set key
expect(providers["single-env"].options.apiKey).toBe("my-api-key") expect(providers["single-env"].key).toBe("my-api-key")
}, },
}) })
}) })