diff --git a/packages/core/src/aisdk.ts b/packages/core/src/aisdk.ts index 8640e3cf1..d8962d250 100644 --- a/packages/core/src/aisdk.ts +++ b/packages/core/src/aisdk.ts @@ -164,7 +164,7 @@ export const layer = Layer.effect( {}, ) .pipe(initError(model.providerID)) - const language = yield* Effect.sync(() => result.language ?? sdk.languageModel(model.apiID)).pipe( + const language = yield* Effect.sync(() => result.language ?? sdk.languageModel(model.api.id)).pipe( initError(model.providerID), ) languages.set(key, language) diff --git a/packages/core/src/catalog.ts b/packages/core/src/catalog.ts index d5633a82e..e7103627a 100644 --- a/packages/core/src/catalog.ts +++ b/packages/core/src/catalog.ts @@ -99,7 +99,7 @@ export const layer = Layer.effect( const provider = state.get().providers.get(model.providerID)!.provider const api = model.api.type === "native" && !model.api.url && Object.keys(model.api.settings).length === 0 - ? provider.api + ? { ...provider.api, id: model.api.id } : 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.type === "aisdk" && provider.api.type === "aisdk" diff --git a/packages/core/src/config/plugin/provider.ts b/packages/core/src/config/plugin/provider.ts index 42eb17686..6127c877f 100644 --- a/packages/core/src/config/plugin/provider.ts +++ b/packages/core/src/config/plugin/provider.ts @@ -32,10 +32,9 @@ export const Plugin = PluginV2.define({ for (const [id, config] of Object.entries(item.models ?? {})) { catalog.model.update(providerID, ModelV2.ID.make(id), (model) => { - if (config.api_id !== undefined) model.apiID = config.api_id if (config.family !== undefined) model.family = config.family if (config.name !== undefined) model.name = config.name - if (config.api !== undefined) model.api = { ...config.api } + if (config.api !== undefined) model.api = { ...model.api, ...config.api } if (config.capabilities !== undefined) { model.capabilities = { tools: config.capabilities.tools, diff --git a/packages/core/src/config/provider.ts b/packages/core/src/config/provider.ts index 94e7546dd..1b5475707 100644 --- a/packages/core/src/config/provider.ts +++ b/packages/core/src/config/provider.ts @@ -30,11 +30,24 @@ class Limit extends Schema.Class("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("ConfigV2.Model")({ - api_id: ModelV2.ID.pipe(Schema.optional), family: ModelV2.Family.pipe(Schema.optional), name: Schema.String.pipe(Schema.optional), - api: ProviderV2.Api.pipe(Schema.optional), + api: ModelApi.pipe(Schema.optional), capabilities: ModelV2.Capabilities.pipe(Schema.optional), request: Schema.Struct({ ...Request.fields, diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index ed1741341..a57647c4d 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -40,13 +40,24 @@ export const Ref = Schema.Struct({ }) export type Ref = typeof Ref.Type +export const Api = Schema.Union([ + Schema.Struct({ + id: ID, + ...ProviderV2.AISDK.fields, + }), + Schema.Struct({ + id: ID, + ...ProviderV2.Native.fields, + }), +]).pipe(Schema.toTaggedUnion("type")) +export type Api = typeof Api.Type + export class Info extends Schema.Class("ModelV2.Info")({ id: ID, - apiID: ID, providerID: ProviderV2.ID, family: Family.pipe(Schema.optional), name: Schema.String, - api: ProviderV2.Api, + api: Api, capabilities: Capabilities, request: Schema.Struct({ ...ProviderV2.Request.fields, @@ -71,10 +82,10 @@ export class Info extends Schema.Class("ModelV2.Info")({ static empty(providerID: ProviderV2.ID, modelID: ID): Info { return new Info({ id: modelID, - apiID: modelID, providerID, name: modelID, api: { + id: modelID, type: "native", settings: {}, }, diff --git a/packages/core/src/plugin/models-dev.ts b/packages/core/src/plugin/models-dev.ts index 9523a2ede..d223e59f6 100644 --- a/packages/core/src/plugin/models-dev.ts +++ b/packages/core/src/plugin/models-dev.ts @@ -82,11 +82,13 @@ export const ModelsDevPlugin = PluginV2.define({ draft.family = model.family ? ModelV2.Family.make(model.family) : undefined draft.api = model.provider?.npm ? { + id: draft.api.id, type: "aisdk", package: model.provider?.npm, url: model.provider.api, } : { + id: draft.api.id, type: "native", url: model.provider?.api, settings: {}, diff --git a/packages/core/src/plugin/provider/amazon-bedrock.ts b/packages/core/src/plugin/provider/amazon-bedrock.ts index 31df9ca12..daa0fd9f8 100644 --- a/packages/core/src/plugin/provider/amazon-bedrock.ts +++ b/packages/core/src/plugin/provider/amazon-bedrock.ts @@ -92,7 +92,7 @@ export const AmazonBedrockPlugin = PluginV2.define({ "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.amazonBedrock) return const region = typeof evt.options.region === "string" ? evt.options.region : process.env.AWS_REGION - evt.language = evt.sdk.languageModel(resolveModelID(evt.model.apiID, region)) + evt.language = evt.sdk.languageModel(resolveModelID(evt.model.api.id, region)) }), } }), diff --git a/packages/core/src/plugin/provider/azure.ts b/packages/core/src/plugin/provider/azure.ts index cc8cf3c65..173fd3662 100644 --- a/packages/core/src/plugin/provider/azure.ts +++ b/packages/core/src/plugin/provider/azure.ts @@ -45,7 +45,7 @@ export const AzurePlugin = PluginV2.define({ }), "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.azure) return - evt.language = selectLanguage(evt.sdk, evt.model.apiID, Boolean(evt.options.useCompletionUrls)) + evt.language = selectLanguage(evt.sdk, evt.model.api.id, Boolean(evt.options.useCompletionUrls)) }), } }), @@ -69,7 +69,7 @@ export const AzureCognitiveServicesPlugin = PluginV2.define({ }), "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("azure-cognitive-services")) return - evt.language = selectLanguage(evt.sdk, evt.model.apiID, Boolean(evt.options.useCompletionUrls)) + evt.language = selectLanguage(evt.sdk, evt.model.api.id, Boolean(evt.options.useCompletionUrls)) }), } }), diff --git a/packages/core/src/plugin/provider/cloudflare-workers-ai.ts b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts index 1ad3834c9..ca19e63c0 100644 --- a/packages/core/src/plugin/provider/cloudflare-workers-ai.ts +++ b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts @@ -30,7 +30,7 @@ export const CloudflareWorkersAIPlugin = PluginV2.define({ }), "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== providerID) return - evt.language = evt.sdk.languageModel(evt.model.apiID) + evt.language = evt.sdk.languageModel(evt.model.api.id) }), } }), diff --git a/packages/core/src/plugin/provider/github-copilot.ts b/packages/core/src/plugin/provider/github-copilot.ts index 20b1ad6d6..1fc7c0c79 100644 --- a/packages/core/src/plugin/provider/github-copilot.ts +++ b/packages/core/src/plugin/provider/github-copilot.ts @@ -23,12 +23,12 @@ export const GithubCopilotPlugin = PluginV2.define({ "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.githubCopilot) return if (evt.sdk.responses === undefined && evt.sdk.chat === undefined) { - evt.language = evt.sdk.languageModel(evt.model.apiID) + evt.language = evt.sdk.languageModel(evt.model.api.id) return } - evt.language = shouldUseResponses(evt.model.apiID) - ? evt.sdk.responses(evt.model.apiID) - : evt.sdk.chat(evt.model.apiID) + evt.language = shouldUseResponses(evt.model.api.id) + ? evt.sdk.responses(evt.model.api.id) + : evt.sdk.chat(evt.model.api.id) }), "catalog.transform": Effect.fn(function* (evt) { const item = evt.provider.get(ProviderV2.ID.githubCopilot) diff --git a/packages/core/src/plugin/provider/gitlab.ts b/packages/core/src/plugin/provider/gitlab.ts index f9a0be6aa..9de090a95 100644 --- a/packages/core/src/plugin/provider/gitlab.ts +++ b/packages/core/src/plugin/provider/gitlab.ts @@ -34,7 +34,7 @@ export const GitLabPlugin = PluginV2.define({ if (evt.model.providerID !== ProviderV2.ID.gitlab) return const featureFlags = typeof evt.options.featureFlags === "object" && evt.options.featureFlags ? evt.options.featureFlags : {} - if (evt.model.apiID.startsWith("duo-workflow-")) { + if (evt.model.api.id.startsWith("duo-workflow-")) { const gitlab = yield* Effect.promise(() => import("gitlab-ai-provider")).pipe(Effect.orDie) const workflowRef = typeof evt.model.request.body.workflowRef === "string" ? evt.model.request.body.workflowRef : undefined @@ -43,7 +43,7 @@ export const GitLabPlugin = PluginV2.define({ ? evt.model.request.body.workflowDefinition : undefined const language = evt.sdk.workflowChat( - gitlab.isWorkflowModel(evt.model.apiID) ? evt.model.apiID : "duo-workflow", + gitlab.isWorkflowModel(evt.model.api.id) ? evt.model.api.id : "duo-workflow", { featureFlags, workflowDefinition, @@ -53,7 +53,7 @@ export const GitLabPlugin = PluginV2.define({ evt.language = language return } - evt.language = evt.sdk.agenticChat(evt.model.apiID, { + evt.language = evt.sdk.agenticChat(evt.model.api.id, { aiGatewayHeaders: evt.options.aiGatewayHeaders, featureFlags, }) diff --git a/packages/core/src/plugin/provider/google-vertex.ts b/packages/core/src/plugin/provider/google-vertex.ts index abf6c0e0c..d996a1f24 100644 --- a/packages/core/src/plugin/provider/google-vertex.ts +++ b/packages/core/src/plugin/provider/google-vertex.ts @@ -99,7 +99,7 @@ export const GoogleVertexPlugin = PluginV2.define({ }), "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.googleVertex) return - evt.language = evt.sdk.languageModel(String(evt.model.apiID).trim()) + evt.language = evt.sdk.languageModel(String(evt.model.api.id).trim()) }), } }), @@ -155,7 +155,7 @@ export const GoogleVertexAnthropicPlugin = PluginV2.define({ }), "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("google-vertex-anthropic")) return - evt.language = evt.sdk.languageModel(String(evt.model.apiID).trim()) + evt.language = evt.sdk.languageModel(String(evt.model.api.id).trim()) }), } }), diff --git a/packages/core/src/plugin/provider/openai.ts b/packages/core/src/plugin/provider/openai.ts index 61f409ee6..1218625a4 100644 --- a/packages/core/src/plugin/provider/openai.ts +++ b/packages/core/src/plugin/provider/openai.ts @@ -14,7 +14,7 @@ export const OpenAIPlugin = PluginV2.define({ }), "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.openai) return - evt.language = evt.sdk.responses(evt.model.apiID) + evt.language = evt.sdk.responses(evt.model.api.id) }), "catalog.transform": Effect.fn(function* (evt) { for (const item of evt.provider.list()) { diff --git a/packages/core/src/plugin/provider/sap-ai-core.ts b/packages/core/src/plugin/provider/sap-ai-core.ts index 7c57b785b..47c8b7eaa 100644 --- a/packages/core/src/plugin/provider/sap-ai-core.ts +++ b/packages/core/src/plugin/provider/sap-ai-core.ts @@ -37,7 +37,7 @@ export const SapAICorePlugin = PluginV2.define({ }), "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return - evt.language = evt.sdk(evt.model.apiID) + evt.language = evt.sdk(evt.model.api.id) }), } }), diff --git a/packages/core/src/plugin/provider/xai.ts b/packages/core/src/plugin/provider/xai.ts index b54aa7374..4e9d53e47 100644 --- a/packages/core/src/plugin/provider/xai.ts +++ b/packages/core/src/plugin/provider/xai.ts @@ -13,7 +13,7 @@ export const XAIPlugin = PluginV2.define({ }), "aisdk.language": Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("xai")) return - evt.language = evt.sdk.responses(evt.model.apiID) + evt.language = evt.sdk.responses(evt.model.api.id) }), } }), diff --git a/packages/core/src/provider.ts b/packages/core/src/provider.ts index eae4ab990..3f99cedc1 100644 --- a/packages/core/src/provider.ts +++ b/packages/core/src/provider.ts @@ -25,14 +25,14 @@ export type ID = typeof ID.Type export const ModelID = Schema.String.pipe(Schema.brand("ModelID")) export type ModelID = typeof ModelID.Type -const AISDK = Schema.Struct({ +export const AISDK = Schema.Struct({ type: Schema.Literal("aisdk"), package: Schema.String, url: Schema.String.pipe(Schema.optional), settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), }) -const Native = Schema.Struct({ +export const Native = Schema.Struct({ type: Schema.Literal("native"), url: Schema.String.pipe(Schema.optional), settings: Schema.Record(Schema.String, Schema.Unknown), diff --git a/packages/core/src/v1/config/migrate.ts b/packages/core/src/v1/config/migrate.ts index 4b666dfd8..7c254b86e 100644 --- a/packages/core/src/v1/config/migrate.ts +++ b/packages/core/src/v1/config/migrate.ts @@ -205,12 +205,19 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st : undefined const lowerer = ConfigProviderOptionsV1.get(info.provider?.npm ?? packageName) return { - api_id: info.id, family: info.family, name: info.name, api: info.provider?.npm - ? { type: "aisdk" as const, package: info.provider.npm, url: info.provider.api, settings: {} } - : undefined, + ? { + ...(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 }, capabilities, request: (info.headers || info.options) && { headers: info.headers, diff --git a/packages/core/test/catalog.test.ts b/packages/core/test/catalog.test.ts index 08cbb4dbc..2f247ed0f 100644 --- a/packages/core/test/catalog.test.ts +++ b/packages/core/test/catalog.test.ts @@ -61,12 +61,18 @@ describe("CatalogV2", () => { } }) catalog.model.update(providerID, modelID, (model) => { - model.api = { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://model.example.com" } + model.api = { + id: modelID, + type: "aisdk", + package: "@ai-sdk/openai-compatible", + url: "https://model.example.com", + } model.request.body.baseURL = "https://override.example.com" }) }) expect((yield* catalog.model.get(providerID, modelID)).api).toEqual({ + id: modelID, type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://override.example.com", @@ -94,6 +100,7 @@ describe("CatalogV2", () => { }) expect((yield* catalog.model.get(providerID, modelID)).api).toEqual({ + id: modelID, type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://provider.example.com", diff --git a/packages/core/test/config/provider.test.ts b/packages/core/test/config/provider.test.ts index 273739662..2c3f0357f 100644 --- a/packages/core/test/config/provider.test.ts +++ b/packages/core/test/config/provider.test.ts @@ -66,7 +66,7 @@ describe("ConfigProviderPlugin.Plugin", () => { request: request({ last: "last", shared: "last" }), models: { chat: { - api_id: "api-chat", + api: { id: "api-chat" }, name: "Last", limit: { output: 75 }, request: request({ last: "last", shared: "last" }), @@ -112,7 +112,7 @@ describe("ConfigProviderPlugin.Plugin", () => { expect(provider.enabled).toEqual({ via: "custom", data: {} }) expect(provider.api).toEqual({ type: "aisdk", package: "custom-sdk", url: "https://example.test" }) expect(provider.request.headers).toEqual({ first: "first", shared: "last", last: "last" }) - expect(model.apiID).toBe(ModelV2.ID.make("api-chat")) + expect(model.api.id).toBe(ModelV2.ID.make("api-chat")) expect(model.name).toBe("Last") expect(model.capabilities).toEqual({ tools: true, input: ["text"], output: ["text"] }) expect(model.enabled).toBe(false) diff --git a/packages/core/test/plugin/provider-alibaba.test.ts b/packages/core/test/plugin/provider-alibaba.test.ts index 06e6f969f..e2fbb8061 100644 --- a/packages/core/test/plugin/provider-alibaba.test.ts +++ b/packages/core/test/plugin/provider-alibaba.test.ts @@ -53,13 +53,13 @@ describe("AlibabaPlugin", () => { }), ) - it.effect("uses the old default languageModel(apiID) behavior", () => + it.effect("uses the old default languageModel(api.id) behavior", () => Effect.gen(function* () { const plugin = yield* PluginV2.Service yield* plugin.add(AlibabaPlugin) - const item = model("alibaba", "alias", { apiID: ModelV2.ID.make("qwen-plus") }) + const item = model("alibaba", "alias", { api: { id: ModelV2.ID.make("qwen-plus") } }) const result = yield* plugin.trigger("aisdk.sdk", { model: item, package: "@ai-sdk/alibaba", options: {} }, {}) - const language = result.sdk?.languageModel(item.apiID) + const language = result.sdk?.languageModel(item.api.id) expect(language?.modelId).toBe("qwen-plus") expect(language?.provider).toBe("alibaba.chat") }), diff --git a/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts b/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts index cd71cea54..d0a05e67a 100644 --- a/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts +++ b/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts @@ -253,7 +253,7 @@ describe("CloudflareWorkersAIPlugin", () => { const result = yield* plugin.trigger( "aisdk.language", { - model: model("cloudflare-workers-ai", "alias", { apiID: ModelV2.ID.make("@cf/api-model") }), + model: model("cloudflare-workers-ai", "alias", { api: { id: ModelV2.ID.make("@cf/api-model") } }), sdk: fakeSelectorSdk(calls), options: {}, }, diff --git a/packages/core/test/plugin/provider-cohere.test.ts b/packages/core/test/plugin/provider-cohere.test.ts index 54bec2cec..a646c3eb6 100644 --- a/packages/core/test/plugin/provider-cohere.test.ts +++ b/packages/core/test/plugin/provider-cohere.test.ts @@ -73,7 +73,7 @@ describe("CoherePlugin", () => { yield* plugin.add(CoherePlugin) const result = yield* plugin.trigger( "aisdk.language", - { model: model("cohere", "alias", { apiID: ModelV2.ID.make("command-r-plus") }), sdk, options: {} }, + { model: model("cohere", "alias", { api: { id: ModelV2.ID.make("command-r-plus") } }), sdk, options: {} }, {}, ) diff --git a/packages/core/test/plugin/provider-dynamic.test.ts b/packages/core/test/plugin/provider-dynamic.test.ts index 7ffa89986..2b0be314b 100644 --- a/packages/core/test/plugin/provider-dynamic.test.ts +++ b/packages/core/test/plugin/provider-dynamic.test.ts @@ -158,15 +158,14 @@ describe("DynamicProviderPlugin", () => { }), ) - itWithAISDK.effect("uses the model apiID for the default language model", () => + itWithAISDK.effect("uses the model api.id for the default language model", () => Effect.gen(function* () { const plugin = yield* PluginV2.Service const aisdk = yield* AISDK.Service yield* plugin.add(dynamicPlugin()) const language = yield* aisdk.language( model("custom", "alias", { - apiID: ModelV2.ID.make("test-model-api"), - api: { type: "aisdk", package: fixtureProvider }, + api: { id: ModelV2.ID.make("test-model-api"), type: "aisdk", package: fixtureProvider }, }), ) expect(language).toMatchObject({ modelID: "test-model-api", options: { name: "custom" } }) diff --git a/packages/core/test/plugin/provider-github-copilot.test.ts b/packages/core/test/plugin/provider-github-copilot.test.ts index c07f70597..f16b177e6 100644 --- a/packages/core/test/plugin/provider-github-copilot.test.ts +++ b/packages/core/test/plugin/provider-github-copilot.test.ts @@ -61,7 +61,7 @@ describe("GithubCopilotPlugin", () => { yield* plugin.trigger( "aisdk.language", { - model: model("github-copilot", "alias", { apiID: ModelV2.ID.make("claude-sonnet-4") }), + model: model("github-copilot", "alias", { api: { id: ModelV2.ID.make("claude-sonnet-4") } }), sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, options: {}, }, @@ -119,7 +119,7 @@ describe("GithubCopilotPlugin", () => { yield* plugin.trigger( "aisdk.language", { - model: model("github-copilot", "default", { apiID: ModelV2.ID.make("gpt-5") }), + model: model("github-copilot", "default", { api: { id: ModelV2.ID.make("gpt-5") } }), sdk: fakeSelectorSdk(calls), options: {}, }, @@ -128,7 +128,7 @@ describe("GithubCopilotPlugin", () => { yield* plugin.trigger( "aisdk.language", { - model: model("github-copilot", "small", { apiID: ModelV2.ID.make("gpt-5-mini") }), + model: model("github-copilot", "small", { api: { id: ModelV2.ID.make("gpt-5-mini") } }), sdk: fakeSelectorSdk(calls), options: {}, }, @@ -137,7 +137,7 @@ describe("GithubCopilotPlugin", () => { yield* plugin.trigger( "aisdk.language", { - model: model("github-copilot", "sonnet", { apiID: ModelV2.ID.make("claude-sonnet-4") }), + model: model("github-copilot", "sonnet", { api: { id: ModelV2.ID.make("claude-sonnet-4") } }), sdk: fakeSelectorSdk(calls), options: {}, }, diff --git a/packages/core/test/plugin/provider-google.test.ts b/packages/core/test/plugin/provider-google.test.ts index 0ff78bc18..9880ff3ae 100644 --- a/packages/core/test/plugin/provider-google.test.ts +++ b/packages/core/test/plugin/provider-google.test.ts @@ -51,8 +51,8 @@ describe("GooglePlugin", () => { yield* plugin.add(GooglePlugin) const language = yield* aisdk.language( model("custom-google", "alias", { - apiID: ModelV2.ID.make("gemini-api"), api: { + id: ModelV2.ID.make("gemini-api"), type: "aisdk", package: "@ai-sdk/google", }, diff --git a/packages/core/test/plugin/provider-groq.test.ts b/packages/core/test/plugin/provider-groq.test.ts index 099e4eed7..c6db66b1c 100644 --- a/packages/core/test/plugin/provider-groq.test.ts +++ b/packages/core/test/plugin/provider-groq.test.ts @@ -75,15 +75,15 @@ describe("GroqPlugin", () => { }), ) - aisdkIt.effect("uses the default languageModel(apiID) behavior", () => + aisdkIt.effect("uses the default languageModel(api.id) behavior", () => Effect.gen(function* () { const plugin = yield* PluginV2.Service const aisdk = yield* AISDK.Service yield* plugin.add(GroqPlugin) const result = yield* aisdk.language( model("groq", "alias", { - apiID: ModelV2.ID.make("llama-api"), api: { + id: ModelV2.ID.make("llama-api"), type: "aisdk", package: "@ai-sdk/groq", }, diff --git a/packages/core/test/plugin/provider-helper.ts b/packages/core/test/plugin/provider-helper.ts index 212fbf5a5..1f4dbfb5a 100644 --- a/packages/core/test/plugin/provider-helper.ts +++ b/packages/core/test/plugin/provider-helper.ts @@ -60,7 +60,7 @@ type ProviderInput = Partial> & { } type ModelInput = Partial> & { - api?: ProviderV2.Api + api?: (ProviderV2.Api & { id?: ModelV2.ID }) | { id: ModelV2.ID } request?: ModelV2.Info["request"] } @@ -83,12 +83,16 @@ export function provider(providerID: string, options?: ProviderInput) { export function model(providerID: string, modelID: string, options?: ModelInput) { return new ModelV2.Info({ ...ModelV2.Info.empty(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)), - apiID: ModelV2.ID.make(modelID), - api: options?.api ?? { - type: "aisdk", - package: "test-provider", - }, ...options, + api: + options?.api && "type" in options.api + ? { id: ModelV2.ID.make(modelID), ...options.api } + : { + id: ModelV2.ID.make(modelID), + ...options?.api, + type: "aisdk", + package: "test-provider", + }, request: { headers: {}, body: {}, diff --git a/packages/core/test/plugin/provider-mistral.test.ts b/packages/core/test/plugin/provider-mistral.test.ts index f24ff53e5..b442d4f4d 100644 --- a/packages/core/test/plugin/provider-mistral.test.ts +++ b/packages/core/test/plugin/provider-mistral.test.ts @@ -87,7 +87,7 @@ describe("MistralPlugin", () => { }), ) - it.effect("leaves Mistral language selection on the default sdk.languageModel(apiID) path", () => + it.effect("leaves Mistral language selection on the default sdk.languageModel(api.id) path", () => Effect.gen(function* () { const plugin = yield* PluginV2.Service const calls: string[] = [] @@ -95,10 +95,10 @@ describe("MistralPlugin", () => { yield* plugin.add(MistralPlugin) const result = yield* plugin.trigger( "aisdk.language", - { model: model("mistral", "alias", { apiID: ModelV2.ID.make("mistral-large") }), sdk, options: {} }, + { model: model("mistral", "alias", { api: { id: ModelV2.ID.make("mistral-large") } }), sdk, options: {} }, {}, ) - const language = result.language ?? sdk.languageModel(result.model.apiID) + const language = result.language ?? sdk.languageModel(result.model.api.id) expect(calls).toEqual(["languageModel:mistral-large"]) expect(language).toBeDefined() }), diff --git a/packages/core/test/plugin/provider-openai.test.ts b/packages/core/test/plugin/provider-openai.test.ts index 28a048e64..3aa38a4b1 100644 --- a/packages/core/test/plugin/provider-openai.test.ts +++ b/packages/core/test/plugin/provider-openai.test.ts @@ -46,7 +46,9 @@ describe("OpenAIPlugin", () => { const result = yield* plugin.trigger( "aisdk.language", { - model: model("openai", "alias", { apiID: ModelV2.ID.make("gpt-5") }), + model: model("openai", "alias", { + api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" }, + }), sdk: fakeSelectorSdk(calls), options: {}, }, diff --git a/packages/core/test/plugin/provider-perplexity.test.ts b/packages/core/test/plugin/provider-perplexity.test.ts index d03f58337..444badd85 100644 --- a/packages/core/test/plugin/provider-perplexity.test.ts +++ b/packages/core/test/plugin/provider-perplexity.test.ts @@ -94,7 +94,7 @@ describe("PerplexityPlugin", () => { const result = yield* plugin.trigger( "aisdk.language", { - model: model("perplexity", "alias", { apiID: ModelV2.ID.make("sonar") }), + model: model("perplexity", "alias", { api: { id: ModelV2.ID.make("sonar") } }), sdk: fakeSelectorSdk(calls), options: {}, }, diff --git a/packages/core/test/plugin/provider-togetherai.test.ts b/packages/core/test/plugin/provider-togetherai.test.ts index 65090037b..3457c2ac8 100644 --- a/packages/core/test/plugin/provider-togetherai.test.ts +++ b/packages/core/test/plugin/provider-togetherai.test.ts @@ -90,7 +90,7 @@ describe("TogetherAIPlugin", () => { expect(result.language).toBeUndefined() expect(calls).toEqual([]) - expect(result.language ?? fakeSelectorSdk(calls).languageModel(result.model.apiID)).toBeDefined() + expect(result.language ?? fakeSelectorSdk(calls).languageModel(result.model.api.id)).toBeDefined() expect(calls).toEqual(["languageModel:meta-llama/Llama-3.3-70B-Instruct-Turbo"]) }), ) diff --git a/packages/core/test/plugin/provider-xai.test.ts b/packages/core/test/plugin/provider-xai.test.ts index 6181fcccc..e505f8538 100644 --- a/packages/core/test/plugin/provider-xai.test.ts +++ b/packages/core/test/plugin/provider-xai.test.ts @@ -12,8 +12,8 @@ const it = testEffect(PluginV2.locationLayer.pipe(Layer.provide(EventV2.defaultL const model = new ModelV2.Info({ ...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")), - apiID: ModelV2.ID.make("grok-4"), api: { + id: ModelV2.ID.make("grok-4"), type: "aisdk", package: "@ai-sdk/xai", }, @@ -72,7 +72,7 @@ describe("XAIPlugin", () => { }), ) - it.effect("uses responses with the model apiID for xAI language models", () => + it.effect("uses responses with the model api.id for xAI language models", () => Effect.gen(function* () { const plugin = yield* PluginV2.Service const calls: string[] = [] @@ -81,7 +81,7 @@ describe("XAIPlugin", () => { const result = yield* plugin.trigger( "aisdk.language", { - model: new ModelV2.Info({ ...model, id: ModelV2.ID.make("alias"), apiID: ModelV2.ID.make("grok-4") }), + model: new ModelV2.Info({ ...model, id: ModelV2.ID.make("alias") }), sdk: fakeSelectorSdk(calls), options: {}, }, diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 7d914f85e..c738c3871 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2685,12 +2685,12 @@ export type EventTuiSessionSelect2 = { export type ModelV2Info = { id: string - apiID: string providerID: string family?: string name: string api: | { + id: string type: "aisdk" package: string url?: string @@ -2699,6 +2699,7 @@ export type ModelV2Info = { } } | { + id: string type: "native" url?: string settings: { @@ -3694,12 +3695,12 @@ export type EventPluginAdded = { export type ModelV2Info1 = { id: string - apiID: string providerID: string family?: string name: string api: | { + id: string type: "aisdk" package: string url?: string @@ -3708,6 +3709,7 @@ export type ModelV2Info1 = { } } | { + id: string type: "native" url?: string settings: { diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 1766883e6..ddceaa0b4 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -19300,9 +19300,6 @@ "id": { "type": "string" }, - "apiID": { - "type": "string" - }, "providerID": { "type": "string" }, @@ -19317,6 +19314,9 @@ { "type": "object", "properties": { + "id": { + "type": "string" + }, "type": { "type": "string", "enum": ["aisdk"] @@ -19331,12 +19331,15 @@ "type": "object" } }, - "required": ["type", "package"], + "required": ["id", "type", "package"], "additionalProperties": false }, { "type": "object", "properties": { + "id": { + "type": "string" + }, "type": { "type": "string", "enum": ["native"] @@ -19348,7 +19351,7 @@ "type": "object" } }, - "required": ["type", "settings"], + "required": ["id", "type", "settings"], "additionalProperties": false } ] @@ -19515,7 +19518,6 @@ }, "required": [ "id", - "apiID", "providerID", "name", "api", @@ -22371,9 +22373,6 @@ "id": { "type": "string" }, - "apiID": { - "type": "string" - }, "providerID": { "type": "string" }, @@ -22388,6 +22387,9 @@ { "type": "object", "properties": { + "id": { + "type": "string" + }, "type": { "type": "string", "enum": ["aisdk"] @@ -22402,12 +22404,15 @@ "type": "object" } }, - "required": ["type", "package"], + "required": ["id", "type", "package"], "additionalProperties": false }, { "type": "object", "properties": { + "id": { + "type": "string" + }, "type": { "type": "string", "enum": ["native"] @@ -22419,7 +22424,7 @@ "type": "object" } }, - "required": ["type", "settings"], + "required": ["id", "type", "settings"], "additionalProperties": false } ] @@ -22582,7 +22587,6 @@ }, "required": [ "id", - "apiID", "providerID", "name", "api", diff --git a/specs/v2/config.md b/specs/v2/config.md index 5edfc1e4d..378ccfbd1 100644 --- a/specs/v2/config.md +++ b/specs/v2/config.md @@ -211,7 +211,7 @@ Provider, model, variant, and provisional agent `options` are authored as partia Keep provider `env` as an authored list of recognized credential environment variable names. Built-in catalog providers already carry this metadata for automatic environment-backed availability, and configured providers may need to declare the same source. For a configured provider this is additive metadata, not a requirement that one of the variables exists: the provider may instead be usable through configured options, a stored account, or an endpoint that needs no credential. -Within configured models, rename legacy upstream model identifier `id` to `api_id` rather than exposing camelCase runtime `apiID`. Model `limit` is an authored patch, so an override may change only `context`, `input`, or `output`. Model `cost` accepts one simple pricing object or an array of tiered pricing entries; omitted cache prices default to zero. +Within configured models, nest the legacy upstream model identifier `id` under `api.id` with the rest of the model API override. Model `limit` is an authored patch, so an override may change only `context`, `input`, or `output`. Model `cost` accepts one simple pricing object or an array of tiered pricing entries; omitted cache prices default to zero. Do not port legacy provider model `reasoning`, `temperature`, or `interleaved` flags as first-class config fields; provider/request behavior belongs in structured `options` or model variants. Do not port `release_date`, `status`, `experimental`, `whitelist`, or `blacklist` in this v2 surface. @@ -223,7 +223,7 @@ Do not port legacy provider model `reasoning`, `temperature`, or `interleaved` f "options": { "headers": { "Authorization": "Bearer {env:API_KEY}" } }, "models": { "chat": { - "api_id": "upstream-chat-model", + "api": { "id": "upstream-chat-model" }, "limit": { "output": 32768 }, "cost": { "input": 1.25, "output": 10 }, "variants": [{ "id": "high", "aisdk": { "request": { "reasoningEffort": "high" } } }],