feat(core): migrate native provider config

This commit is contained in:
Shoubhit Dash 2026-06-25 00:17:32 +05:30
commit 62e44a80e7
16 changed files with 358 additions and 99 deletions

View file

@ -78,12 +78,28 @@ export const layer = Layer.effect(
const projectModel = (model: ModelV2.Info, provider: ProviderV2.Info) => {
const api =
model.api.type === "native" && !model.api.url && Object.keys(model.api.settings).length === 0
? { ...provider.api, id: model.api.id }
model.api.type === "native" && !model.api.package
? {
...provider.api,
id: model.api.id,
url: model.api.url ?? provider.api.url,
settings: ModelRequest.mergeRecords(provider.api.settings, model.api.settings),
}
: model.api.type === "native" && provider.api.type === "native" && !model.api.url
? {
...model.api,
package: model.api.package ?? provider.api.package,
url: provider.api.url,
settings: ModelRequest.mergeRecords(provider.api.settings, model.api.settings),
}
: model.api.type === "aisdk" && provider.api.type === "aisdk" && !model.api.url
? { ...model.api, url: provider.api.url, settings: { ...provider.api.settings, ...model.api.settings } }
? {
...model.api,
url: provider.api.url,
settings: ModelRequest.mergeRecords(provider.api.settings, model.api.settings),
}
: model.api.type === "aisdk" && provider.api.type === "aisdk"
? { ...model.api, settings: { ...provider.api.settings, ...model.api.settings } }
? { ...model.api, settings: ModelRequest.mergeRecords(provider.api.settings, model.api.settings) }
: model.api
const request = {
...ModelRequest.merge({ ...provider.request, generation: {}, options: {} }, model.request),

View file

@ -48,26 +48,95 @@ export const Plugin = define({
const model = ModelV2.parse(configuredDefault)
catalog.model.default.set(model.providerID, model.modelID)
}
const providerAiSDK = new Map<string, boolean>()
const modelAiSDK = new Map<string, boolean>()
for (const file of files) {
for (const [id, item] of Object.entries(file.info.providers ?? {})) {
const providerID = id
if (item.aiSDK !== undefined) providerAiSDK.set(providerID, item.aiSDK)
catalog.provider.update(providerID, (provider) => {
if (item.name !== undefined) provider.name = item.name
if (item.api !== undefined) provider.api = { ...item.api }
if (item.request !== undefined) {
Object.assign(provider.request.headers, item.request.headers)
Object.assign(provider.request.body, item.request.body)
if (item.package !== undefined) {
const settings = ModelRequest.mergeRecords(provider.api.settings, item.settings)
const url =
item.settings && Object.hasOwn(item.settings, "baseURL")
? typeof settings.baseURL === "string"
? settings.baseURL
: undefined
: provider.api.url
provider.api = (providerAiSDK.get(providerID) ?? provider.api.type === "aisdk")
? { type: "aisdk", package: item.package, ...(url === undefined ? {} : { url }), settings }
: { type: "native", package: item.package, ...(url === undefined ? {} : { url }), settings }
} else if (item.settings !== undefined) {
provider.api.settings = ModelRequest.mergeRecords(provider.api.settings, item.settings)
if (Object.hasOwn(item.settings, "baseURL")) {
provider.api.url =
typeof provider.api.settings.baseURL === "string" ? provider.api.settings.baseURL : undefined
}
}
if (item.package === undefined && item.aiSDK !== undefined) {
if (item.aiSDK && provider.api.type === "native" && provider.api.package !== undefined) {
provider.api = { ...provider.api, type: "aisdk", package: provider.api.package }
}
if (!item.aiSDK && provider.api.type === "aisdk") {
provider.api = { ...provider.api, type: "native", settings: provider.api.settings ?? {} }
}
}
ModelRequest.assign(provider.request, { headers: item.headers, body: item.body })
})
const providerApi = catalog.provider.get(providerID)?.provider.api
const providerPackage = providerApi?.type === "aisdk" ? providerApi.package : undefined
for (const [id, config] of Object.entries(item.models ?? {})) {
const modelKey = `${providerID}/${id}`
if (config.aiSDK !== undefined) modelAiSDK.set(modelKey, config.aiSDK)
catalog.model.update(providerID, id, (model) => {
if (config.family !== undefined) model.family = config.family
if (config.name !== undefined) model.name = config.name
if (config.api !== undefined) model.api = { ...model.api, ...config.api }
if (config.id !== undefined) model.api.id = config.id
if (config.package !== undefined) {
const aiSDK =
modelAiSDK.get(modelKey) ?? providerAiSDK.get(providerID) ?? providerApi?.type === "aisdk"
const settings = ModelRequest.mergeRecords(model.api.settings, config.settings)
const url =
config.settings && Object.hasOwn(config.settings, "baseURL")
? typeof settings.baseURL === "string"
? settings.baseURL
: undefined
: model.api.url
model.api = aiSDK
? {
id: model.api.id,
type: "aisdk",
package: config.package,
...(url === undefined ? {} : { url }),
settings,
}
: {
id: model.api.id,
type: "native",
package: config.package,
...(url === undefined ? {} : { url }),
settings,
}
} else if (config.settings !== undefined) {
model.api.settings = ModelRequest.mergeRecords(model.api.settings, config.settings)
if (Object.hasOwn(config.settings, "baseURL")) {
model.api.url =
typeof model.api.settings.baseURL === "string" ? model.api.settings.baseURL : undefined
}
}
if (config.package === undefined && config.aiSDK !== undefined) {
if (config.aiSDK && model.api.type === "native" && model.api.package !== undefined) {
model.api = { ...model.api, type: "aisdk", package: model.api.package }
}
if (!config.aiSDK && model.api.type === "aisdk") {
model.api = { ...model.api, type: "native", settings: model.api.settings ?? {} }
}
}
const packageName = model.api.type === "aisdk" ? model.api.package : providerPackage
const aiSDK =
modelAiSDK.get(modelKey) ?? providerAiSDK.get(providerID) ?? providerApi?.type === "aisdk"
if (config.capabilities !== undefined) {
model.capabilities = {
tools: config.capabilities.tools,
@ -75,19 +144,22 @@ export const Plugin = define({
output: [...config.capabilities.output],
}
}
if (config.request !== undefined) {
if (config.headers !== undefined || config.body !== undefined) {
ModelRequest.assign(model.request, {
headers: config.request.headers,
...ModelRequest.normalizeAiSdkOptions(packageName, config.request.body ?? {}),
headers: config.headers,
...(aiSDK
? ModelRequest.normalizeAiSdkOptions(packageName, config.body ?? {})
: { body: config.body }),
})
if (config.request.variant !== undefined) model.request.variant = config.request.variant
}
if (config.variant !== undefined) model.request.variant = config.variant
if (config.variants !== undefined) {
for (const variant of config.variants) {
let existing = model.variants.find((item) => item.id === variant.id)
if (!existing) {
existing = {
id: variant.id,
settings: {},
headers: {},
body: {},
generation: {},
@ -95,9 +167,12 @@ export const Plugin = define({
}
model.variants.push(existing)
}
existing.settings = ModelRequest.mergeRecords(existing.settings, variant.settings)
ModelRequest.assign(existing, {
headers: variant.headers,
...ModelRequest.normalizeAiSdkOptions(packageName, variant.body ?? {}),
...(aiSDK
? ModelRequest.normalizeAiSdkOptions(packageName, variant.body ?? {})
: { body: variant.body }),
})
}
}

View file

@ -1,7 +1,6 @@
export * as ConfigProvider from "./provider"
import { Schema } from "effect"
import { ProviderV2 } from "../provider"
import { ModelV2 } from "../model"
export class Request extends Schema.Class<Request>("ConfigV2.Provider.Request")({
@ -9,6 +8,11 @@ export class Request extends Schema.Class<Request>("ConfigV2.Provider.Request")(
body: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
}) {}
const Overlays = {
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
...Request.fields,
}
class Cache extends Schema.Class<Cache>("ConfigV2.Model.Cost.Cache")({
read: Schema.Finite.pipe(Schema.optional),
write: Schema.Finite.pipe(Schema.optional),
@ -30,32 +34,18 @@ class Limit extends Schema.Class<Limit>("ConfigV2.Model.Limit")({
output: Schema.Int.pipe(Schema.optional),
}) {}
const ModelApi = Schema.Union([
Schema.Struct({
id: ModelV2.ID.pipe(Schema.optional),
...ProviderV2.AISDK.fields,
}),
Schema.Struct({
id: ModelV2.ID.pipe(Schema.optional),
...ProviderV2.Native.fields,
}),
Schema.Struct({
id: ModelV2.ID,
}),
])
class Model extends Schema.Class<Model>("ConfigV2.Model")({
id: ModelV2.ID.pipe(Schema.optional),
package: Schema.String.pipe(Schema.optional),
aiSDK: Schema.Boolean.pipe(Schema.optional),
...Overlays,
family: ModelV2.Family.pipe(Schema.optional),
name: Schema.String.pipe(Schema.optional),
api: ModelApi.pipe(Schema.optional),
capabilities: ModelV2.Capabilities.pipe(Schema.optional),
request: Schema.Struct({
...Request.fields,
variant: Schema.String.pipe(Schema.optional),
}).pipe(Schema.optional),
variant: Schema.String.pipe(Schema.optional),
variants: Schema.Struct({
id: ModelV2.VariantID,
...Request.fields,
...Overlays,
}).pipe(Schema.Array, Schema.optional),
cost: Schema.Union([Cost, Cost.pipe(Schema.Array)]).pipe(Schema.optional),
disabled: Schema.Boolean.pipe(Schema.optional),
@ -65,7 +55,8 @@ class Model extends Schema.Class<Model>("ConfigV2.Model")({
export class Info extends Schema.Class<Info>("ConfigV2.Provider")({
name: Schema.String.pipe(Schema.optional),
env: Schema.String.pipe(Schema.Array, Schema.optional),
api: ProviderV2.Api.pipe(Schema.optional),
request: Request.pipe(Schema.optional),
package: Schema.String.pipe(Schema.optional),
aiSDK: Schema.Boolean.pipe(Schema.optional),
...Overlays,
models: Schema.Record(Schema.String, Model).pipe(Schema.optional),
}) {}

View file

@ -67,16 +67,41 @@ const profiles = new Map<string, Profile>([
export const namespace = (packageName: string) => profiles.get(packageName)?.namespace
const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value)
export const mergeRecords = (...items: ReadonlyArray<Readonly<Record<string, unknown>> | undefined>) => {
const result: Record<string, unknown> = {}
for (const item of items) {
for (const [key, value] of Object.entries(item ?? {})) {
result[key] = isRecord(result[key]) && isRecord(value) ? mergeRecords(result[key], value) : value
}
}
return result
}
export const mergeHeaders = (...items: ReadonlyArray<Readonly<Record<string, string>> | undefined>) => {
const result = new Map<string, readonly [string, string]>()
for (const item of items) {
for (const entry of Object.entries(item ?? {})) result.set(entry[0].toLowerCase(), entry)
}
return Object.fromEntries(result.values())
}
export const merge = (base: Request, override: Partial<Request>) => ({
headers: { ...base.headers, ...override.headers },
body: { ...base.body, ...override.body },
headers: mergeHeaders(base.headers, override.headers),
body: mergeRecords(base.body, override.body),
generation: { ...base.generation, ...override.generation },
options: { ...base.options, ...override.options },
})
export const assign = (target: MutableRequest, override: Partial<Request>) => {
Object.assign(target.headers, override.headers)
Object.assign(target.body, override.body)
const headers = mergeHeaders(target.headers, override.headers)
Object.keys(target.headers).forEach((key) => delete target.headers[key])
Object.assign(target.headers, headers)
const body = mergeRecords(target.body, override.body)
Object.keys(target.body).forEach((key) => delete target.body[key])
Object.assign(target.body, body)
Object.assign((target.generation ??= {}), override.generation)
Object.assign((target.options ??= {}), override.options)
}

View file

@ -8,6 +8,9 @@ export type ID = typeof ID.Type
export const VariantID = Model.VariantID
export type VariantID = typeof VariantID.Type
export const Package = Model.Package
export type Package = Model.Package
// Grouping of models, eg claude opus, claude sonnet
export const Family = Model.Family
export type Family = Model.Family

View file

@ -6,6 +6,11 @@ import { Provider } from "@opencode-ai/schema/provider"
export const ID = Provider.ID
export type ID = typeof ID.Type
export const Overlays = Provider.Overlays
export const Package = Provider.Package
export type Package = Provider.Package
export const AISDK = Provider.AISDK
export const Native = Provider.Native

View file

@ -122,6 +122,12 @@ const withVariant = (
return Effect.succeed(
variant
? produce(model, (draft) => {
if (variant.settings !== undefined) {
draft.api.settings = ModelRequest.mergeRecords(draft.api.settings, variant.settings)
if (Object.hasOwn(variant.settings, "baseURL")) {
draft.api.url = typeof draft.api.settings.baseURL === "string" ? draft.api.settings.baseURL : undefined
}
}
ModelRequest.assign(draft.request, variant)
})
: model,

View file

@ -174,15 +174,14 @@ function migrateProvider(info: ConfigProviderV1.Info) {
return {
name: info.name,
env: info.env,
api: info.npm
? {
type: "aisdk" as const,
package: info.npm,
url: info.api ?? options.url,
settings: options.settings ?? {},
}
: undefined,
request: info.options && { headers: options.headers, body: options.body },
package: info.npm,
aiSDK: info.npm ? true : undefined,
settings: {
...options.settings,
...(info.api ?? options.url ? { baseURL: info.api ?? options.url } : {}),
},
headers: options.headers,
body: options.body,
models:
info.models &&
Object.fromEntries(Object.entries(info.models).map(([name, model]) => [name, migrateModel(model, info.npm)])),
@ -221,22 +220,13 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st
return {
family: info.family,
name: info.name,
api: info.provider?.npm
? {
...(info.id === undefined ? {} : { id: info.id }),
type: "aisdk" as const,
package: info.provider.npm,
url: info.provider.api,
settings: {},
}
: info.id === undefined
? undefined
: { id: info.id },
id: info.id,
package: info.provider?.npm,
aiSDK: info.provider?.npm ? true : undefined,
settings: info.provider?.api ? { baseURL: info.provider.api } : undefined,
capabilities,
request: (info.headers || request) && {
headers: info.headers,
body: request,
},
headers: info.headers,
body: request,
variants:
info.variants &&
Object.entries(info.variants).map(([id, options]) => ({

View file

@ -213,6 +213,7 @@ describe("CatalogV2", () => {
type: "aisdk",
package: "@ai-sdk/openai-compatible",
url: "https://provider.example.com",
settings: {},
})
}),
)

View file

@ -103,13 +103,10 @@ describe("Config", () => {
},
})
expect(migrated.providers?.bedrock?.api).toEqual({
type: "aisdk",
expect(migrated.providers?.bedrock).toMatchObject({
package: "@ai-sdk/amazon-bedrock",
url: undefined,
aiSDK: true,
settings: { region: "us-east-1", profile: "dev" },
})
expect(migrated.providers?.bedrock?.request).toEqual({
headers: { "x-test": "1" },
body: { trace: true },
})
@ -585,34 +582,34 @@ describe("Config", () => {
})
expect(documents[0]?.info.attachments).toEqual({ image: { auto_resize: false, max_width: 1200 } })
expect(documents[0]?.info.providers?.custom).toMatchObject({
request: { body: { apiKey: "secret" } },
body: { apiKey: "secret" },
models: {
model: {
request: { body: { reasoningEffort: "high" } },
body: { reasoningEffort: "high" },
variants: [{ id: "fast", body: { temperature: 0.2 } }],
},
},
})
expect(documents[0]?.info.providers?.openai).toMatchObject({
api: { settings: {} },
request: { headers: { Authorization: "Bearer secret", "OpenAI-Organization": "org" } },
package: "@ai-sdk/openai",
aiSDK: true,
settings: {},
headers: { Authorization: "Bearer secret", "OpenAI-Organization": "org" },
models: {
model: {
request: {
body: { temperature: 0.3, reasoningEffort: "high", serviceTier: "priority" },
},
body: { temperature: 0.3, reasoningEffort: "high", serviceTier: "priority" },
variants: [{ id: "high", body: { reasoningEffort: "high", reasoningSummary: "auto" } }],
},
},
})
expect(documents[0]?.info.providers?.anthropic).toMatchObject({
package: "@ai-sdk/anthropic",
aiSDK: true,
models: {
model: {
request: {
body: {
output_config: { effort: "high", task_budget: 4096 },
metadata: { user_id: "user-1" },
},
body: {
output_config: { effort: "high", task_budget: 4096 },
metadata: { user_id: "user-1" },
},
},
},

View file

@ -45,16 +45,90 @@ function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () =
)
}
function request(headers: Record<string, string>, variant?: string) {
return {
headers,
variant,
}
}
const decode = Schema.decodeUnknownSync(Config.Info)
describe("ConfigProviderPlugin.Plugin", () => {
it.effect("merges flat provider and model overlays", () =>
Effect.gen(function* () {
const catalog = yield* Catalog.Service
const providerID = ProviderV2.ID.make("custom")
const modelID = ModelV2.ID.make("chat")
const config = Config.Service.of({
entries: () =>
Effect.succeed([
new Config.Document({
type: "document",
info: decode({ providers: { custom: { aiSDK: true } } }),
}),
new Config.Document({
type: "document",
info: decode({
providers: {
custom: {
package: "custom-provider",
settings: { auth: { type: "token", region: "us-east-1" } },
headers: { "X-Test": "provider" },
body: { reasoning: { type: "enabled", budget: 8_000 }, tags: ["provider"] },
models: {
chat: {
package: "custom-model-provider",
settings: { auth: { region: "us-west-2" } },
headers: { "x-test": "model" },
body: { reasoning: { budget: 32_000 }, tags: ["model"] },
},
inherit: {
settings: { auth: { region: "eu-west-1" }, baseURL: "https://model.example/v1" },
},
clear: { settings: { baseURL: "https://old.example/v1" } },
},
},
},
}),
}),
new Config.Document({
type: "document",
info: decode({
providers: {
custom: { models: { clear: { package: "custom-provider", settings: { baseURL: null } } } },
},
}),
}),
]),
})
yield* addPlugin(config)
const model = required(yield* catalog.model.get(providerID, modelID))
expect(model.api).toEqual({
id: modelID,
type: "aisdk",
package: "custom-model-provider",
settings: { auth: { type: "token", region: "us-west-2" } },
})
expect(model.request.headers).toEqual({ "x-test": "model" })
expect(model.request.body).toEqual({
reasoning: { type: "enabled", budget: 32_000 },
tags: ["model"],
})
expect(required(yield* catalog.model.get(providerID, ModelV2.ID.make("inherit"))).api).toEqual({
id: ModelV2.ID.make("inherit"),
type: "aisdk",
package: "custom-provider",
url: "https://model.example/v1",
settings: {
auth: { type: "token", region: "eu-west-1" },
baseURL: "https://model.example/v1",
},
})
expect(required(yield* catalog.model.get(providerID, ModelV2.ID.make("clear"))).api).toEqual({
id: ModelV2.ID.make("clear"),
type: "aisdk",
package: "custom-provider",
settings: { auth: { type: "token", region: "us-east-1" }, baseURL: null },
})
}),
)
it.effect("partitions existing model variant bodies without changing config shape", () =>
Effect.gen(function* () {
const catalog = yield* Catalog.Service
@ -68,7 +142,9 @@ describe("ConfigProviderPlugin.Plugin", () => {
info: decode({
providers: {
opencode: {
api: { type: "aisdk", package: "@ai-sdk/openai", url: "https://opencode.test/v1" },
package: "@ai-sdk/openai",
aiSDK: true,
settings: { baseURL: "https://opencode.test/v1" },
models: {
"alpha-gpt-next": {
variants: [
@ -120,7 +196,9 @@ describe("ConfigProviderPlugin.Plugin", () => {
info: decode({
providers: {
opencode: {
api: { type: "aisdk", package: "@ai-sdk/openai", url: "https://opencode.test/v1" },
package: "@ai-sdk/openai",
aiSDK: true,
settings: { baseURL: "https://opencode.test/v1" },
},
},
}),
@ -171,8 +249,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
custom: {
name: "Configured",
env: ["CUSTOM_API_KEY"],
api: { type: "native", settings: {} },
request: request({ first: "first", shared: "first" }),
package: "custom-native",
headers: { first: "first", shared: "first" },
models: {
chat: {
name: "First",
@ -180,7 +258,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
disabled: true,
limit: { context: 100, output: 50 },
cost: { input: 1, output: 2 },
request: request({ first: "first", shared: "first" }, "retained"),
headers: { first: "first", shared: "first" },
variant: "retained",
variants: [
{
id: "fast",
@ -199,17 +278,19 @@ describe("ConfigProviderPlugin.Plugin", () => {
model: "custom/default",
providers: {
custom: {
api: { type: "aisdk", package: "custom-sdk", url: "https://example.test" },
request: request({ last: "last", shared: "last" }),
package: "custom-sdk",
aiSDK: true,
settings: { baseURL: "https://example.test" },
headers: { last: "last", shared: "last" },
models: {
default: {
name: "Default",
},
chat: {
api: { id: "api-chat" },
id: "api-chat",
name: "Last",
limit: { output: 75 },
request: request({ last: "last", shared: "last" }),
headers: { last: "last", shared: "last" },
variants: [
{
id: "fast",
@ -249,7 +330,12 @@ describe("ConfigProviderPlugin.Plugin", () => {
})
expect((yield* integrations.get(Integration.ID.make("custom")))?.name).toBe("Renamed")
expect(provider.disabled).toBeUndefined()
expect(provider.api).toEqual({ type: "aisdk", package: "custom-sdk", url: "https://example.test" })
expect(provider.api).toEqual({
type: "aisdk",
package: "custom-sdk",
url: "https://example.test",
settings: { baseURL: "https://example.test" },
})
expect(provider.request.headers).toEqual({ first: "first", shared: "last", last: "last" })
expect(model.api.id).toBe(ModelV2.ID.make("api-chat"))
expect(model.name).toBe("Last")

View file

@ -148,6 +148,40 @@ describe("SessionRunnerModel", () => {
}),
)
it.effect("applies a selected variant base URL", () =>
Effect.gen(function* () {
const base = model(
{ type: "aisdk", package: "@ai-sdk/openai", url: "https://default.example/v1" },
[
{
id: ModelV2.VariantID.make("regional"),
settings: { baseURL: "https://regional.example/v1" },
headers: {},
body: {},
generation: {},
options: {},
},
],
)
const session = SessionV2.Info.make({
id: SessionV2.ID.make("ses_regional_variant"),
projectID: ProjectV2.ID.global,
title: "test",
model: { id: base.id, providerID: base.providerID, variant: ModelV2.VariantID.make("regional") },
cost: 0,
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
time: { created: DateTime.makeUnsafe(0), updated: DateTime.makeUnsafe(0) },
location: { directory: AbsolutePath.make("/project") },
})
const resolved = yield* SessionRunnerModel.resolve(
session,
base,
)
expect(resolved.route.endpoint.baseURL).toBe("https://regional.example/v1")
}),
)
it.effect("lowers selected OpenAI-compatible Session variants into Chat options", () =>
Effect.gen(function* () {
const catalog = model(

View file

@ -99,6 +99,7 @@ test("Core reuses the canonical shared schemas", async () => {
[coreLLM.ToolContent, LLM.ToolContent],
[ModelV2.ID, Model.ID],
[ModelV2.VariantID, Model.VariantID],
[ModelV2.Package, Model.Package],
[ModelV2.Ref, Model.Ref],
[ModelV2.Family, Model.Family],
[ModelV2.Capabilities, Model.Capabilities],
@ -106,6 +107,7 @@ test("Core reuses the canonical shared schemas", async () => {
[ModelV2.Api, Model.Api],
[ModelV2.Info, Model.Info],
[ProviderV2.ID, Provider.ID],
[ProviderV2.Package, Provider.Package],
[ProviderV2.AISDK, Provider.AISDK],
[ProviderV2.Native, Provider.Native],
[ProviderV2.Api, Provider.Api],

View file

@ -11,6 +11,14 @@ export type ID = typeof ID.Type
export const VariantID = Schema.String.pipe(Schema.brand("VariantID"))
export type VariantID = typeof VariantID.Type
export const Package = Schema.Struct({
modelID: ID,
package: Schema.String.pipe(Schema.optional),
aiSDK: Schema.Boolean.pipe(Schema.optional),
...Provider.Overlays,
})
export type Package = typeof Package.Type
export const Ref = Schema.Struct({
id: ID,
providerID: Provider.ID,
@ -68,6 +76,7 @@ export const Info = Schema.Struct({
}),
variants: Schema.Struct({
id: VariantID,
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
...ModelRequest.Request.fields,
}).pipe(Schema.Array, Schema.mutable),
time: Schema.Struct({

View file

@ -22,6 +22,19 @@ export const ID = Schema.String.pipe(
)
export type ID = typeof ID.Type
export const Overlays = {
settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
headers: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
body: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
}
export const Package = Schema.Struct({
package: Schema.String,
aiSDK: Schema.Boolean.pipe(Schema.optional),
...Overlays,
})
export type Package = typeof Package.Type
export interface AISDK extends Schema.Schema.Type<typeof AISDK> {}
export const AISDK = Schema.Struct({
type: Schema.Literal("aisdk"),
@ -33,6 +46,7 @@ export const AISDK = Schema.Struct({
export interface Native extends Schema.Schema.Type<typeof Native> {}
export const Native = Schema.Struct({
type: Schema.Literal("native"),
package: Schema.String.pipe(Schema.optional),
url: Schema.String.pipe(Schema.optional),
settings: Schema.Record(Schema.String, Schema.Unknown),
})

View file

@ -4002,6 +4002,7 @@ export type ModelV2Info = {
| {
id: string
type: "native"
package?: string
url?: string
settings: {
[key: string]: unknown
@ -4036,6 +4037,9 @@ export type ModelV2Info = {
}
variants: Array<{
id: string
settings?: {
[key: string]: unknown
}
headers: {
[key: string]: string
}
@ -4096,6 +4100,7 @@ export type ProviderV2Info = {
}
| {
type: "native"
package?: string
url?: string
settings: {
[key: string]: unknown