diff --git a/CONTEXT.md b/CONTEXT.md index 7fe7f3fc8d..4b5fabd93c 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -57,13 +57,6 @@ The bounded projection of a Core-executed tool result persisted in Session histo **Managed Tool Output File**: A temporary file created under OpenCode's shared tool-output directory to retain complete output that was too large for Session history. -**Model Request Options**: -Provider-semantic model settings selected from the Catalog and active Session variant before the LLM protocol adapter encodes them for a provider request. -_Avoid_: Request body, wire options - -**Generation Controls**: -Provider-neutral sampling and output controls, partitioned from provider semantics and compatibility wire fields when model metadata enters the Catalog. - **PTY Environment**: The host-supplied environment overlay applied by the server when creating a PTY, observed for the request Location and resolved PTY working directory. @@ -114,8 +107,6 @@ The host-supplied environment overlay applied by the server when creating a PTY, - A **Baseline System Context** durably preserves the exact joined text used for the active provider-cache prefix. - Completed compaction starts a new **Context Epoch** on the next provider attempt, folding the current complete **System Context** into a fresh baseline and removing earlier **Mid-Conversation System Messages** from active model history. - A model/provider switch preserves the current **Context Epoch** and chronological conversation history; the new selection applies to the next provider turn. -- **Model Request Options** remain provider-semantic through Catalog resolution. The Session runner maps them into the LLM package's provider-option namespace; the selected protocol adapter alone owns provider wire encoding. -- **Generation Controls**, protocol-semantic **Model Request Options**, and compatibility request body fields are separate Catalog domains. A shared ingestion adapter partitions legacy and models.dev AI-SDK-shaped options before routing. - The **PTY Environment** is a server concern rather than a Core PTY concern. PTY creation merges caller values, then the host overlay, then Core-forced terminal invariants such as `TERM` and `OPENCODE_TERMINAL`. - A **PTY Environment** adapter observes plugins in the request Location while passing the resolved PTY working directory to the hook; standalone servers use an empty adapter. - A **Mid-Conversation System Message** lowers to the provider's native chronological instruction role when supported and to a wrapped chronological fallback otherwise. diff --git a/packages/core/src/aisdk.ts b/packages/core/src/aisdk.ts index 9ea79394f3..3aaf5484ee 100644 --- a/packages/core/src/aisdk.ts +++ b/packages/core/src/aisdk.ts @@ -73,10 +73,10 @@ function wrapSSE(res: Response, ms: number, ctl: AbortController) { function prepareOptions(model: ModelV2.Info, pkg: string) { const options: Record = { name: model.providerID, - ...(model.api.type === "aisdk" ? (model.api.settings ?? {}) : {}), - ...model.request.body, + ...(model.settings ?? {}), + headers: model.headers, + body: model.body, } - if (model.api.type === "aisdk" && model.api.url) options.baseURL = model.api.url const customFetch = options.fetch const chunkTimeout = options.chunkTimeout @@ -195,24 +195,26 @@ export const locationLayer = Layer.effect( runSDK: (event) => run(sdkHooks, event), runLanguage: (event) => run(languageHooks, event), language: Effect.fn("AISDK.language")(function* (model) { - const key = `${model.providerID}/${model.id}/${model.request.variant ?? "default"}` + const key = `${model.providerID}/${model.id}/${model.modelID ?? model.id}/${JSON.stringify(model.settings)}` const existing = languages.get(key) if (existing) return existing - if (model.api.type !== "aisdk") + if (!model.aisdk) return yield* new InitError({ providerID: model.providerID, - cause: new Error(`Unsupported api ${model.api.type}`), + cause: new Error(`Unsupported package ${model.package}`), }) - const options = prepareOptions(model, model.api.package) + const options = prepareOptions(model, model.package ?? "") const sdkKey = JSON.stringify({ providerID: model.providerID, - api: model.api, + package: model.package, + settings: model.settings, options, }) const sdk = sdks.get(sdkKey) ?? - (yield* service.runSDK({ model, package: model.api.package, options }).pipe(initError(model.providerID))).sdk + (yield* service.runSDK({ model, package: model.package ?? "", options }).pipe(initError(model.providerID))) + .sdk if (!sdk) return yield* new InitError({ providerID: model.providerID, @@ -220,7 +222,7 @@ export const locationLayer = Layer.effect( }) sdks.set(sdkKey, sdk) const result = yield* service.runLanguage({ model, sdk, options }).pipe(initError(model.providerID)) - const language = yield* Effect.sync(() => result.language ?? sdk.languageModel(model.api.id)).pipe( + const language = yield* Effect.sync(() => result.language ?? sdk.languageModel(model.modelID ?? model.id)).pipe( initError(model.providerID), ) languages.set(key, language) diff --git a/packages/core/src/catalog.ts b/packages/core/src/catalog.ts index 0b274299e5..51b33cea74 100644 --- a/packages/core/src/catalog.ts +++ b/packages/core/src/catalog.ts @@ -2,7 +2,6 @@ export * as Catalog from "./catalog" import { Array, Context, Effect, Layer, Option, Order, pipe, Schema } from "effect" import { ModelV2 } from "./model" -import { ModelRequest } from "./model-request" import { ProviderV2 } from "./provider" import { EventV2 } from "./event" import { Policy } from "./policy" @@ -71,37 +70,22 @@ export const layer = Layer.effect( const available = (provider: ProviderV2.Info, integration: Integration.Info | undefined) => { if (provider.disabled) return false - if (typeof provider.request.body.apiKey === "string") return true + if (typeof provider.settings?.apiKey === "string") return true if (integration?.connections.length) return true return provider.integrationID === undefined && !integration } 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 === "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" - ? { ...model.api, settings: { ...provider.api.settings, ...model.api.settings } } - : model.api - const request = { - ...ModelRequest.merge({ ...provider.request, generation: {}, options: {} }, model.request), - variant: model.request.variant, - } return ModelV2.Info.make({ ...model, - api, - request, + package: model.package ?? provider.package, + aisdk: model.package === undefined ? (model.aisdk ?? provider.aisdk) : model.aisdk, + settings: merge(provider.settings, model.settings), + headers: headers(provider.headers, model.headers), + body: merge(provider.body, model.body), }) } - const normalizeApi = (item: ProviderV2.MutableInfo | ModelV2.MutableInfo) => { - if (typeof item.request.body.baseURL !== "string") return - item.api.url = item.request.body.baseURL - delete item.request.body.baseURL - } - const state = State.create({ initial: () => ({ providers: new Map() }), draft: (draft) => { @@ -119,7 +103,6 @@ export const layer = Layer.effect( draft.providers.set(providerID, current) } fn(current.provider) - normalizeApi(current.provider) }, remove: (providerID) => { draft.providers.delete(providerID) @@ -142,7 +125,6 @@ export const layer = Layer.effect( fn(model) model.id = modelID model.providerID = providerID - normalizeApi(model) }, remove: (providerID, modelID) => { draft.providers.get(providerID)?.models.delete(modelID) @@ -288,6 +270,43 @@ export const layer = Layer.effect( const SMALL_MODEL_RE = /\b(nano|flash|lite|mini|haiku|small|fast)\b/ +function merge( + base: Readonly> | undefined, + overlay: Readonly> | undefined, +): Record | undefined { + if (base === undefined) return overlay && { ...overlay } + if (overlay === undefined) return { ...base } + return Object.fromEntries( + new Set([...Object.keys(base), ...Object.keys(overlay)]).values().map((key) => { + const left = base[key] + const right = overlay[key] + if (right === undefined) return [key, left] + if (plain(left) && plain(right)) return [key, merge(left, right)] + return [key, right] + }), + ) +} + +function plain(input: unknown): input is Readonly> { + if (typeof input !== "object" || input === null || Array.isArray(input)) return false + const prototype = Object.getPrototypeOf(input) + return prototype === Object.prototype || prototype === null +} + +function headers( + base: Readonly> | undefined, + overlay: Readonly> | undefined, +) { + return Object.fromEntries( + [...Object.entries(base ?? {}), ...Object.entries(overlay ?? {})] + .reduce((result, entry) => { + result.set(entry[0].toLowerCase(), entry) + return result + }, new Map()) + .values(), + ) +} + export const locationLayer = layer.pipe( Layer.provideMerge(Integration.locationLayer), Layer.provideMerge(Policy.locationLayer), diff --git a/packages/core/src/config/plugin/provider.ts b/packages/core/src/config/plugin/provider.ts index 6fb13f1c63..3129240639 100644 --- a/packages/core/src/config/plugin/provider.ts +++ b/packages/core/src/config/plugin/provider.ts @@ -4,7 +4,6 @@ import { define } from "../../plugin/internal" import { Effect } from "effect" import { Config } from "../../config" import { ModelV2 } from "../../model" -import { ModelRequest } from "../../model-request" import { ProviderV2 } from "../../provider" export const Plugin = define({ @@ -53,21 +52,22 @@ export const Plugin = define({ const providerID = id 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) provider.package = item.package + if (item.aisdk !== undefined) provider.aisdk = item.aisdk + if (item.settings !== undefined) provider.settings = { ...provider.settings, ...item.settings } + if (item.headers !== undefined) provider.headers = { ...provider.headers, ...item.headers } + if (item.body !== undefined) provider.body = { ...provider.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 ?? {})) { 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 } - const packageName = model.api.type === "aisdk" ? model.api.package : providerPackage + if (config.modelID !== undefined) model.modelID = config.modelID + if (config.package !== undefined) model.package = config.package + if (config.aisdk !== undefined) model.aisdk = config.aisdk + if (config.settings !== undefined) model.settings = { ...model.settings, ...config.settings } + if (config.headers !== undefined) model.headers = { ...model.headers, ...config.headers } + if (config.body !== undefined) model.body = { ...model.body, ...config.body } if (config.capabilities !== undefined) { model.capabilities = { tools: config.capabilities.tools, @@ -75,30 +75,20 @@ export const Plugin = define({ output: [...config.capabilities.output], } } - if (config.request !== undefined) { - ModelRequest.assign(model.request, { - headers: config.request.headers, - ...ModelRequest.normalizeAiSdkOptions(packageName, config.request.body ?? {}), - }) - if (config.request.variant !== undefined) model.request.variant = config.request.variant - } if (config.variants !== undefined) { + model.variants ??= [] for (const variant of config.variants) { let existing = model.variants.find((item) => item.id === variant.id) if (!existing) { existing = { id: variant.id, - headers: {}, - body: {}, - generation: {}, - options: {}, } model.variants.push(existing) } - ModelRequest.assign(existing, { - headers: variant.headers, - ...ModelRequest.normalizeAiSdkOptions(packageName, variant.body ?? {}), - }) + if (variant.settings !== undefined) + existing.settings = { ...existing.settings, ...variant.settings } + if (variant.headers !== undefined) existing.headers = { ...existing.headers, ...variant.headers } + if (variant.body !== undefined) existing.body = { ...existing.body, ...variant.body } } } if (config.cost !== undefined) { diff --git a/packages/core/src/config/provider.ts b/packages/core/src/config/provider.ts index 1b54757078..153ab7fcef 100644 --- a/packages/core/src/config/provider.ts +++ b/packages/core/src/config/provider.ts @@ -1,12 +1,17 @@ export * as ConfigProvider from "./provider" import { Schema } from "effect" -import { ProviderV2 } from "../provider" import { ModelV2 } from "../model" -export class Request extends Schema.Class("ConfigV2.Provider.Request")({ +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 class Request extends Schema.Class("ConfigV2.Provider.Request")({ + headers: Overlays.headers, + body: Overlays.body, }) {} class Cache extends Schema.Class("ConfigV2.Model.Cost.Cache")({ @@ -30,32 +35,17 @@ 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")({ + modelID: ModelV2.ID.pipe(Schema.optional), family: ModelV2.Family.pipe(Schema.optional), name: Schema.String.pipe(Schema.optional), - api: ModelApi.pipe(Schema.optional), + package: Schema.String.pipe(Schema.optional), + aisdk: Schema.Literal(true).pipe(Schema.optional), + ...Overlays, capabilities: ModelV2.Capabilities.pipe(Schema.optional), - request: Schema.Struct({ - ...Request.fields, - variant: Schema.String.pipe(Schema.optional), - }).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("ConfigV2.Model")({ export class Info extends Schema.Class("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.Literal(true).pipe(Schema.optional), + ...Overlays, models: Schema.Record(Schema.String, Model).pipe(Schema.optional), }) {} diff --git a/packages/core/src/model-request.ts b/packages/core/src/model-request.ts deleted file mode 100644 index 06059a9d73..0000000000 --- a/packages/core/src/model-request.ts +++ /dev/null @@ -1,102 +0,0 @@ -export * as ModelRequest from "./model-request" - -import { ModelRequest } from "@opencode-ai/schema/model-request" - -export const Generation = ModelRequest.Generation -export type Generation = ModelRequest.Generation - -export const Request = ModelRequest.Request -export type Request = ModelRequest.Request - -interface MutableRequest { - headers: Record - body: Record - generation?: Record - options?: Record -} - -const generationKeys = new Map([ - ["maxOutputTokens", "maxTokens"], - ["maxTokens", "maxTokens"], - ["temperature", "temperature"], - ["topP", "topP"], - ["topK", "topK"], - ["frequencyPenalty", "frequencyPenalty"], - ["presencePenalty", "presencePenalty"], - ["seed", "seed"], - ["stopSequences", "stop"], - ["stop", "stop"], -]) - -interface Profile { - readonly namespace: string - readonly semantics: ReadonlyMap -} - -const profiles = new Map([ - [ - "@ai-sdk/openai", - { - namespace: "openai", - semantics: new Map([ - ["store", "store"], - ["promptCacheKey", "promptCacheKey"], - ["reasoningEffort", "reasoningEffort"], - ["reasoningSummary", "reasoningSummary"], - ["include", "include"], - ["textVerbosity", "textVerbosity"], - ["serviceTier", "serviceTier"], - ["service_tier", "serviceTier"], - ]), - }, - ], - [ - "@ai-sdk/openai-compatible", - { - namespace: "openai", - semantics: new Map([ - ["store", "store"], - ["promptCacheKey", "promptCacheKey"], - ["reasoningEffort", "reasoningEffort"], - ["reasoning_effort", "reasoningEffort"], - ]), - }, - ], - ["@ai-sdk/anthropic", { namespace: "anthropic", semantics: new Map([["thinking", "thinking"]]) }], -]) - -export const namespace = (packageName: string) => profiles.get(packageName)?.namespace - -export const merge = (base: Request, override: Partial) => ({ - headers: { ...base.headers, ...override.headers }, - body: { ...base.body, ...override.body }, - generation: { ...base.generation, ...override.generation }, - options: { ...base.options, ...override.options }, -}) - -export const assign = (target: MutableRequest, override: Partial) => { - Object.assign(target.headers, override.headers) - Object.assign(target.body, override.body) - Object.assign((target.generation ??= {}), override.generation) - Object.assign((target.options ??= {}), override.options) -} - -/** Partitions AI-SDK-shaped request options before they enter the Catalog. */ -export function normalizeAiSdkOptions(packageName: string | undefined, input: Readonly>) { - const generation: Record> = {} - const options: Record = {} - const body: Record = {} - const semantics = profiles.get(packageName ?? "")?.semantics - - for (const [key, value] of Object.entries(input)) { - const generationKey = generationKeys.get(key) - if (generationKey === "stop" && Array.isArray(value) && value.every((item) => typeof item === "string")) - generation[generationKey] = value - else if (generationKey !== undefined && generationKey !== "stop" && typeof value === "number") - generation[generationKey] = value - else if (semantics?.has(key)) options[semantics.get(key)!] = value - else body[key] = value - } - - return { generation, options, body } -} diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index 52fff98733..2d7c597a44 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -20,15 +20,10 @@ export const Cost = Model.Cost export const Ref = Model.Ref export type Ref = typeof Ref.Type -export const Api = Model.Api -export type Api = Model.Api - export const Info = Model.Info export type Info = Model.Info -export type MutableInfo = Omit, "api"> & { - api: ProviderV2.MutableApi -} +export type MutableInfo = Types.DeepMutable export function parse(input: string): { providerID: ProviderV2.ID; modelID: ID } { const [providerID, ...modelID] = input.split("/") diff --git a/packages/core/src/plugin/internal.ts b/packages/core/src/plugin/internal.ts index 4e80e60337..51c6e279b7 100644 --- a/packages/core/src/plugin/internal.ts +++ b/packages/core/src/plugin/internal.ts @@ -29,6 +29,7 @@ import { CommandPlugin } from "./command" import { ModelsDevPlugin } from "./models-dev" import { ProviderPlugins } from "./provider" import { SkillPlugin } from "./skill" +import { VariantPlugin } from "./variant" export type Requirements = | AgentV2.Service @@ -107,12 +108,13 @@ export const locationLayer = Layer.effectDiscard( yield* add(CommandPlugin.Plugin) yield* add(SkillPlugin.Plugin) yield* add(ModelsDevPlugin) - yield* add(ConfigProviderPlugin.Plugin) yield* add(ConfigAgentPlugin.Plugin) yield* add(ConfigCommandPlugin.Plugin) yield* add(ConfigSkillPlugin.Plugin) for (const item of ProviderPlugins) yield* add(item) yield* add(ConfigExternalPlugin.Plugin) + yield* add(ConfigProviderPlugin.Plugin) + yield* add(VariantPlugin.Plugin) }).pipe(Effect.withSpan("PluginInternal.boot"), Effect.forkScoped({ startImmediately: true })) }), ).pipe( diff --git a/packages/core/src/plugin/models-dev.ts b/packages/core/src/plugin/models-dev.ts index 04f1f092a0..ba6e4a869a 100644 --- a/packages/core/src/plugin/models-dev.ts +++ b/packages/core/src/plugin/models-dev.ts @@ -2,7 +2,6 @@ import { define } from "./internal" import { Effect, Stream } from "effect" import { EventV2 } from "../event" import { ModelV2 } from "../model" -import { ModelRequest } from "../model-request" import { ModelsDev } from "../models-dev" import { ProviderV2 } from "../provider" @@ -38,15 +37,12 @@ function cost(input: ModelsDev.Model["cost"]) { ] } -function variants(model: ModelsDev.Model, packageName?: string) { - return Object.entries(model.experimental?.modes ?? {}).map(([id, item]) => { - const request = ModelRequest.normalizeAiSdkOptions(packageName, item.provider?.body ?? {}) - return { - id: ModelV2.VariantID.make(id), - headers: { ...(item.provider?.headers ?? {}) }, - ...request, - } - }) +function variants(model: ModelsDev.Model) { + return Object.entries(model.experimental?.modes ?? {}).map(([id, item]) => ({ + id: ModelV2.VariantID.make(id), + headers: { ...(item.provider?.headers ?? {}) }, + body: { ...(item.provider?.body ?? {}) }, + })) } export const ModelsDevPlugin = define({ @@ -79,17 +75,9 @@ export const ModelsDevPlugin = define({ const providerID = ProviderV2.ID.make(item.id) catalog.provider.update(providerID, (provider) => { provider.name = item.name - provider.api = item.npm - ? { - type: "aisdk", - package: item.npm, - url: item.api, - } - : { - type: "native", - url: item.api, - settings: {}, - } + provider.package = item.npm ?? "" + provider.aisdk = item.npm ? true : undefined + provider.settings = item.api ? { ...provider.settings, baseURL: item.api } : provider.settings }) for (const model of Object.values(item.models)) { @@ -97,25 +85,15 @@ export const ModelsDevPlugin = define({ catalog.model.update(providerID, modelID, (draft) => { draft.name = model.name 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: {}, - } + draft.package = model.provider?.npm + draft.aisdk = model.provider?.npm ? true : undefined + draft.settings = model.provider?.api ? { ...draft.settings, baseURL: model.provider.api } : draft.settings draft.capabilities = { tools: model.tool_call, input: [...(model.modalities?.input ?? [])], output: [...(model.modalities?.output ?? [])], } - draft.variants = variants(model, model.provider?.npm ?? item.npm) + draft.variants = variants(model) draft.time.released = released(model.release_date) draft.cost = cost(model.cost) draft.status = model.status ?? "active" diff --git a/packages/core/src/plugin/provider/amazon-bedrock.ts b/packages/core/src/plugin/provider/amazon-bedrock.ts index 0995cf1c17..00dc73edb0 100644 --- a/packages/core/src/plugin/provider/amazon-bedrock.ts +++ b/packages/core/src/plugin/provider/amazon-bedrock.ts @@ -65,15 +65,14 @@ export const AmazonBedrockPlugin = define({ yield* ctx.catalog.transform( Effect.fn(function* (evt) { for (const item of evt.provider.list()) { - if (item.provider.api.type !== "aisdk") continue - if (item.provider.api.package !== "@ai-sdk/amazon-bedrock") continue + if (!item.provider.aisdk) continue + if (item.provider.package !== "@ai-sdk/amazon-bedrock") continue evt.provider.update(item.provider.id, (provider) => { - if (provider.api.type !== "aisdk") return - if (typeof provider.request.body.endpoint !== "string") return + if (typeof provider.settings?.endpoint !== "string") return // The AI SDK expects a base URL, but users configure Bedrock private/VPC // endpoints as `endpoint`; move it into the catalog endpoint URL once. - provider.api.url = provider.request.body.endpoint - delete provider.request.body.endpoint + provider.settings.baseURL = provider.settings.endpoint + delete provider.settings.endpoint }) } }), @@ -114,12 +113,12 @@ export const AmazonBedrockPlugin = define({ yield* ctx.aisdk.language( Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.amazonBedrock) return - if (evt.model.api.type === "aisdk" && evt.model.api.package === "@ai-sdk/amazon-bedrock/mantle") { - evt.language = selectMantleModel(evt.sdk, evt.model.api.id) + if (evt.model.aisdk && evt.model.package === "@ai-sdk/amazon-bedrock/mantle") { + evt.language = selectMantleModel(evt.sdk, evt.model.modelID ?? evt.model.id) return } const region = typeof evt.options.region === "string" ? evt.options.region : process.env.AWS_REGION - evt.language = evt.sdk.languageModel(resolveModelID(evt.model.api.id, region)) + evt.language = evt.sdk.languageModel(resolveModelID(evt.model.modelID ?? evt.model.id, region)) }), ) }), diff --git a/packages/core/src/plugin/provider/anthropic.ts b/packages/core/src/plugin/provider/anthropic.ts index cf883a0687..c13fbbfdad 100644 --- a/packages/core/src/plugin/provider/anthropic.ts +++ b/packages/core/src/plugin/provider/anthropic.ts @@ -7,11 +7,13 @@ export const AnthropicPlugin = define({ yield* ctx.catalog.transform( Effect.fn(function* (evt) { for (const item of evt.provider.list()) { - if (item.provider.api.type !== "aisdk") continue - if (item.provider.api.package !== "@ai-sdk/anthropic") continue + if (!item.provider.aisdk) continue + if (item.provider.package !== "@ai-sdk/anthropic") continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["anthropic-beta"] = - "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14" + provider.headers = { + ...provider.headers, + "anthropic-beta": "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14", + } }) } }), diff --git a/packages/core/src/plugin/provider/azure.ts b/packages/core/src/plugin/provider/azure.ts index 2e1f9d9b48..b1e5754606 100644 --- a/packages/core/src/plugin/provider/azure.ts +++ b/packages/core/src/plugin/provider/azure.ts @@ -16,14 +16,14 @@ export const AzurePlugin = define({ yield* ctx.catalog.transform( Effect.fn(function* (evt) { for (const item of evt.provider.list()) { - if (item.provider.api.type !== "aisdk") continue - if (item.provider.api.package !== "@ai-sdk/azure") continue - const configured = item.provider.request.body.resourceName + if (!item.provider.aisdk) continue + if (item.provider.package !== "@ai-sdk/azure") continue + const configured = item.provider.settings?.resourceName const resourceName = typeof configured === "string" && configured.trim() !== "" ? configured : process.env.AZURE_RESOURCE_NAME if (!resourceName) continue evt.provider.update(item.provider.id, (provider) => { - provider.request.body.resourceName = resourceName + provider.settings = { ...provider.settings, resourceName } }) } }), @@ -35,7 +35,7 @@ export const AzurePlugin = define({ if ( !evt.options.resourceName && !evt.options.baseURL && - (evt.model.api.type !== "aisdk" || !evt.model.api.url) + (!evt.model.aisdk || typeof evt.model.settings?.baseURL !== "string") ) { throw new Error( "AZURE_RESOURCE_NAME is missing, set it using env var or reconnecting the azure provider and setting it", @@ -49,7 +49,11 @@ export const AzurePlugin = define({ yield* ctx.aisdk.language( Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.azure) return - evt.language = selectLanguage(evt.sdk, evt.model.api.id, Boolean(evt.options.useCompletionUrls)) + evt.language = selectLanguage( + evt.sdk, + evt.model.modelID ?? evt.model.id, + Boolean(evt.options.useCompletionUrls), + ) }), ) }), @@ -63,11 +67,14 @@ export const AzureCognitiveServicesPlugin = define({ const resourceName = process.env.AZURE_COGNITIVE_SERVICES_RESOURCE_NAME if (!resourceName) return for (const item of evt.provider.list()) { - if (item.provider.api.type !== "aisdk") continue - if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue + if (!item.provider.aisdk) continue + if (item.provider.package !== "@ai-sdk/openai-compatible") continue if (!item.provider.id.includes("azure-cognitive-services")) continue evt.provider.update(item.provider.id, (provider) => { - provider.request.body.baseURL = `https://${resourceName}.cognitiveservices.azure.com/openai` + provider.settings = { + ...provider.settings, + baseURL: `https://${resourceName}.cognitiveservices.azure.com/openai`, + } }) } }), @@ -75,7 +82,11 @@ export const AzureCognitiveServicesPlugin = define({ yield* ctx.aisdk.language( Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("azure-cognitive-services")) return - evt.language = selectLanguage(evt.sdk, evt.model.api.id, Boolean(evt.options.useCompletionUrls)) + evt.language = selectLanguage( + evt.sdk, + evt.model.modelID ?? evt.model.id, + Boolean(evt.options.useCompletionUrls), + ) }), ) }), diff --git a/packages/core/src/plugin/provider/cerebras.ts b/packages/core/src/plugin/provider/cerebras.ts index 0fd651160f..cb15cb9312 100644 --- a/packages/core/src/plugin/provider/cerebras.ts +++ b/packages/core/src/plugin/provider/cerebras.ts @@ -7,10 +7,10 @@ export const CerebrasPlugin = define({ yield* ctx.catalog.transform( Effect.fn(function* (evt) { for (const item of evt.provider.list()) { - if (item.provider.api.type !== "aisdk") continue - if (item.provider.api.package !== "@ai-sdk/cerebras") continue + if (!item.provider.aisdk) continue + if (item.provider.package !== "@ai-sdk/cerebras") continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["X-Cerebras-3rd-Party-Integration"] = "opencode" + provider.headers = { ...provider.headers, "X-Cerebras-3rd-Party-Integration": "opencode" } }) } }), diff --git a/packages/core/src/plugin/provider/cloudflare-workers-ai.ts b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts index 1a1c533eb5..0ed7b0dffa 100644 --- a/packages/core/src/plugin/provider/cloudflare-workers-ai.ts +++ b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts @@ -14,10 +14,10 @@ export const CloudflareWorkersAIPlugin = define({ const item = evt.provider.get(providerID) if (!item) return evt.provider.update(item.provider.id, (provider) => { - if (provider.api.type !== "aisdk") return - if (provider.api.url) return - const accountId = resolveAccountId(provider.request.body) - if (accountId) provider.api.url = workersEndpoint(accountId) + if (!provider.aisdk) return + if (typeof provider.settings?.baseURL === "string") return + const accountId = resolveAccountId(provider.settings ?? {}) + if (accountId) provider.settings = { ...provider.settings, baseURL: workersEndpoint(accountId) } }) }), ) @@ -27,7 +27,7 @@ export const CloudflareWorkersAIPlugin = define({ if (evt.package !== "@ai-sdk/openai-compatible") return const accountId = resolveAccountId(evt.options) - if (!hasWorkersEndpoint(evt.model.api) && !accountId) return + if (!hasWorkersEndpoint(evt.model) && !accountId) return const mod = yield* Effect.promise(() => import("@ai-sdk/openai-compatible")) evt.sdk = mod.createOpenAICompatible( sdkOptions({ @@ -40,7 +40,7 @@ export const CloudflareWorkersAIPlugin = define({ yield* ctx.aisdk.language( Effect.fn(function* (evt) { if (evt.model.providerID !== providerID) return - evt.language = evt.sdk.languageModel(evt.model.api.id) + evt.language = evt.sdk.languageModel(evt.model.modelID ?? evt.model.id) }), ) }), @@ -54,8 +54,8 @@ function workersEndpoint(accountId: string) { return `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1` } -function hasWorkersEndpoint(api: ProviderV2.Api) { - return api.type === "aisdk" && Boolean(api.url) +function hasWorkersEndpoint(model: { readonly aisdk?: true; readonly settings?: Readonly> }) { + return model.aisdk && typeof model.settings?.baseURL === "string" } function sdkOptions(options: Record) { diff --git a/packages/core/src/plugin/provider/github-copilot.ts b/packages/core/src/plugin/provider/github-copilot.ts index 682579d7a9..12f9a12f23 100644 --- a/packages/core/src/plugin/provider/github-copilot.ts +++ b/packages/core/src/plugin/provider/github-copilot.ts @@ -36,12 +36,11 @@ export const GithubCopilotPlugin = define({ 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.api.id) + evt.language = evt.sdk.languageModel(evt.model.modelID ?? evt.model.id) return } - evt.language = shouldUseResponses(evt.model.api.id) - ? evt.sdk.responses(evt.model.api.id) - : evt.sdk.chat(evt.model.api.id) + const id = evt.model.modelID ?? evt.model.id + evt.language = shouldUseResponses(id) ? evt.sdk.responses(id) : evt.sdk.chat(id) }), ) }), diff --git a/packages/core/src/plugin/provider/gitlab.ts b/packages/core/src/plugin/provider/gitlab.ts index 8723cdaac2..56619ff922 100644 --- a/packages/core/src/plugin/provider/gitlab.ts +++ b/packages/core/src/plugin/provider/gitlab.ts @@ -36,26 +36,24 @@ export const GitLabPlugin = 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.api.id.startsWith("duo-workflow-")) { + const id = evt.model.modelID ?? evt.model.id + if (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 + typeof evt.model.settings?.workflowRef === "string" ? evt.model.settings.workflowRef : undefined const workflowDefinition = - typeof evt.model.request.body.workflowDefinition === "string" - ? evt.model.request.body.workflowDefinition + typeof evt.model.settings?.workflowDefinition === "string" + ? evt.model.settings.workflowDefinition : undefined - const language = evt.sdk.workflowChat( - gitlab.isWorkflowModel(evt.model.api.id) ? evt.model.api.id : "duo-workflow", - { - featureFlags, - workflowDefinition, - }, - ) + const language = evt.sdk.workflowChat(gitlab.isWorkflowModel(id) ? id : "duo-workflow", { + featureFlags, + workflowDefinition, + }) if (workflowRef) language.selectedModelRef = workflowRef evt.language = language return } - evt.language = evt.sdk.agenticChat(evt.model.api.id, { + evt.language = evt.sdk.agenticChat(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 4e643c9f51..fa7afc98bf 100644 --- a/packages/core/src/plugin/provider/google-vertex.ts +++ b/packages/core/src/plugin/provider/google-vertex.ts @@ -60,25 +60,28 @@ export const GoogleVertexPlugin = define({ yield* ctx.catalog.transform( Effect.fn(function* (evt) { for (const item of evt.provider.list()) { - if (item.provider.api.type !== "aisdk") continue + if (!item.provider.aisdk) continue if ( - item.provider.api.package !== "@ai-sdk/google-vertex" && + item.provider.package !== "@ai-sdk/google-vertex" && !( item.provider.id === ProviderV2.ID.googleVertex && - item.provider.api.package.includes("@ai-sdk/openai-compatible") + item.provider.package.includes("@ai-sdk/openai-compatible") ) ) continue - const project = resolveProject(item.provider.request.body) - const location = String(resolveLocation(item.provider.request.body)) + const project = resolveProject(item.provider.settings ?? {}) + const location = String(resolveLocation(item.provider.settings ?? {})) evt.provider.update(item.provider.id, (provider) => { - if (project) provider.request.body.project = project - provider.request.body.location = location - if (provider.api.type === "aisdk" && provider.api.url) { - provider.api.url = replaceVertexVars(provider.api.url, project, location) - } - if (provider.api.type === "aisdk" && provider.api.package.includes("@ai-sdk/openai-compatible")) { - provider.request.body.fetch = authFetch(provider.request.body.fetch) + provider.settings = { + ...provider.settings, + ...(project ? { project } : {}), + location, + ...(typeof provider.settings?.baseURL === "string" + ? { baseURL: replaceVertexVars(provider.settings.baseURL, project, location) } + : {}), + ...(provider.package.includes("@ai-sdk/openai-compatible") + ? { fetch: authFetch(provider.settings?.fetch) } + : {}), } }) } @@ -106,7 +109,7 @@ export const GoogleVertexPlugin = define({ yield* ctx.aisdk.language( Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.googleVertex) return - evt.language = evt.sdk.languageModel(String(evt.model.api.id).trim()) + evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim()) }), ) }), @@ -118,21 +121,20 @@ export const GoogleVertexAnthropicPlugin = define({ yield* ctx.catalog.transform( Effect.fn(function* (evt) { for (const item of evt.provider.list()) { - if (item.provider.api.type !== "aisdk") continue - if (item.provider.api.package !== "@ai-sdk/google-vertex/anthropic") continue + if (!item.provider.aisdk) continue + if (item.provider.package !== "@ai-sdk/google-vertex/anthropic") continue const project = - item.provider.request.body.project ?? + item.provider.settings?.project ?? process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCP_PROJECT ?? process.env.GCLOUD_PROJECT const location = - item.provider.request.body.location ?? + item.provider.settings?.location ?? process.env.GOOGLE_CLOUD_LOCATION ?? process.env.VERTEX_LOCATION ?? "global" evt.provider.update(item.provider.id, (provider) => { - if (project) provider.request.body.project = project - provider.request.body.location = location + provider.settings = { ...provider.settings, ...(project ? { project } : {}), location } }) } }), @@ -166,7 +168,7 @@ export const GoogleVertexAnthropicPlugin = define({ yield* ctx.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.api.id).trim()) + evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim()) }), ) }), diff --git a/packages/core/src/plugin/provider/kilo.ts b/packages/core/src/plugin/provider/kilo.ts index 6ee6670ee5..9cfd5b2791 100644 --- a/packages/core/src/plugin/provider/kilo.ts +++ b/packages/core/src/plugin/provider/kilo.ts @@ -7,12 +7,11 @@ export const KiloPlugin = define({ yield* ctx.catalog.transform( Effect.fn(function* (evt) { for (const item of evt.provider.list()) { - if (item.provider.api.type !== "aisdk") continue - if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue - if (item.provider.api.url !== "https://api.kilo.ai/api/gateway") continue + if (!item.provider.aisdk) continue + if (item.provider.package !== "@ai-sdk/openai-compatible") continue + if (item.provider.settings?.baseURL !== "https://api.kilo.ai/api/gateway") continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["HTTP-Referer"] = "https://opencode.ai/" - provider.request.headers["X-Title"] = "opencode" + provider.headers = { ...provider.headers, "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" } }) } }), diff --git a/packages/core/src/plugin/provider/llmgateway.ts b/packages/core/src/plugin/provider/llmgateway.ts index eafc5edd6c..73ee23b876 100644 --- a/packages/core/src/plugin/provider/llmgateway.ts +++ b/packages/core/src/plugin/provider/llmgateway.ts @@ -10,14 +10,17 @@ export const LLMGatewayPlugin = define({ Effect.fn(function* (evt) { for (const item of evt.provider.list()) { if (item.provider.disabled) continue - if (item.provider.api.type !== "aisdk") continue - if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue - if (item.provider.api.url !== "https://api.llmgateway.io/v1") continue + if (!item.provider.aisdk) continue + if (item.provider.package !== "@ai-sdk/openai-compatible") continue + if (item.provider.settings?.baseURL !== "https://api.llmgateway.io/v1") continue if (!(yield* integrations.get(Integration.ID.make(item.provider.id)))) continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["HTTP-Referer"] = "https://opencode.ai/" - provider.request.headers["X-Title"] = "opencode" - provider.request.headers["X-Source"] = "opencode" + provider.headers = { + ...provider.headers, + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "opencode", + "X-Source": "opencode", + } }) } }), diff --git a/packages/core/src/plugin/provider/nvidia.ts b/packages/core/src/plugin/provider/nvidia.ts index 449599727c..5a80180aa6 100644 --- a/packages/core/src/plugin/provider/nvidia.ts +++ b/packages/core/src/plugin/provider/nvidia.ts @@ -7,13 +7,16 @@ export const NvidiaPlugin = define({ yield* ctx.catalog.transform( Effect.fn(function* (evt) { for (const item of evt.provider.list()) { - if (item.provider.api.type !== "aisdk") continue - if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue - if (item.provider.api.url !== "https://integrate.api.nvidia.com/v1") continue + if (!item.provider.aisdk) continue + if (item.provider.package !== "@ai-sdk/openai-compatible") continue + if (item.provider.settings?.baseURL !== "https://integrate.api.nvidia.com/v1") continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["HTTP-Referer"] = "https://opencode.ai/" - provider.request.headers["X-Title"] = "opencode" - provider.request.headers["X-BILLING-INVOKE-ORIGIN"] ??= "OpenCode" + provider.headers = { + ...provider.headers, + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "opencode", + "X-BILLING-INVOKE-ORIGIN": provider.headers?.["X-BILLING-INVOKE-ORIGIN"] ?? "OpenCode", + } }) } }), diff --git a/packages/core/src/plugin/provider/openai.ts b/packages/core/src/plugin/provider/openai.ts index e89382440e..dda85b4688 100644 --- a/packages/core/src/plugin/provider/openai.ts +++ b/packages/core/src/plugin/provider/openai.ts @@ -156,8 +156,8 @@ export const OpenAIPlugin = define({ yield* ctx.catalog.transform( Effect.fn(function* (evt) { for (const item of evt.provider.list()) { - if (item.provider.api.type !== "aisdk") continue - if (item.provider.api.package !== "@ai-sdk/openai") continue + if (!item.provider.aisdk) continue + if (item.provider.package !== "@ai-sdk/openai") continue if (!item.models.has(ModelV2.ID.make("gpt-5-chat-latest"))) continue evt.model.update(item.provider.id, ModelV2.ID.make("gpt-5-chat-latest"), (model) => { // OpenAIPlugin sends OpenAI models through Responses; this alias is a @@ -177,7 +177,7 @@ export const OpenAIPlugin = define({ yield* ctx.aisdk.language( Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.openai) return - evt.language = evt.sdk.responses(evt.model.api.id) + evt.language = evt.sdk.responses(evt.model.modelID ?? evt.model.id) }), ) }), diff --git a/packages/core/src/plugin/provider/opencode.ts b/packages/core/src/plugin/provider/opencode.ts index 691a2b2a28..29e1eb704a 100644 --- a/packages/core/src/plugin/provider/opencode.ts +++ b/packages/core/src/plugin/provider/opencode.ts @@ -8,9 +8,9 @@ import { EventV2 } from "../../event" import { Credential } from "../../credential" import { Integration } from "../../integration" import { ModelV2 } from "../../model" -import { ModelRequest } from "../../model-request" import { ProviderV2 } from "../../provider" import { ConfigProviderV1 } from "../../v1/config/provider" +import { ConfigProviderOptionsV1 } from "../../v1/config/provider-options" import { ConfigV1 } from "../../v1/config/config" const defaultServer = "https://console.opencode.ai" @@ -111,11 +111,14 @@ export const OpencodePlugin = define { provider.integrationID = Integration.ID.make("opencode") if (item.name !== undefined) provider.name = item.name - provider.api = item.npm - ? { type: "aisdk", package: item.npm, url: item.api } - : { type: "native", url: item.api, settings: {} } - Object.assign(provider.request.headers, item.options?.headers) - Object.assign(provider.request.body, withoutCredentials(item.options)) + provider.package = item.npm ?? "" + provider.aisdk = item.npm ? true : undefined + provider.settings = { + ...provider.settings, + ...withoutCredentials(item.options), + ...(item.api ? { baseURL: item.api } : {}), + } + provider.headers = { ...provider.headers, ...item.options?.headers } }) const modelIDs = new Set(Object.keys(item.models ?? {})) @@ -127,30 +130,24 @@ export const OpencodePlugin = define { if (config.family !== undefined) model.family = config.family if (config.name !== undefined) model.name = config.name - if (config.id !== undefined) model.api.id = config.id + if (config.id !== undefined) model.modelID = config.id if (config.provider !== undefined) { - model.api = config.provider.npm - ? { - id: model.api.id, - type: "aisdk", - package: config.provider.npm, - url: config.provider.api, - } - : { id: model.api.id, type: "native", url: config.provider.api, settings: {} } + model.package = config.provider.npm + model.aisdk = config.provider.npm ? true : undefined + if (config.provider.api) model.settings = { ...model.settings, baseURL: config.provider.api } } if (config.tool_call !== undefined) model.capabilities.tools = config.tool_call if (config.modalities?.input !== undefined) model.capabilities.input = [...config.modalities.input] if (config.modalities?.output !== undefined) model.capabilities.output = [...config.modalities.output] const packageName = config.provider?.npm ?? item.npm - ModelRequest.assign(model.request, { - headers: config.headers, - ...ModelRequest.normalizeAiSdkOptions(packageName, withoutCredentials(config.options)), - }) + const lowerer = ConfigProviderOptionsV1.get(packageName) + model.headers = { ...model.headers, ...config.headers } + model.settings = { ...model.settings, ...lowerer.model(withoutCredentials(config.options)) } if (config.variants !== undefined) { model.variants = Object.entries(config.variants).map(([id, options]) => ({ id: ModelV2.VariantID.make(id), headers: { ...(options.headers ?? {}) }, - ...ModelRequest.normalizeAiSdkOptions(packageName, withoutCredentials(options)), + settings: lowerer.model(withoutCredentials(options)), })) } if (config.release_date !== undefined) { @@ -169,9 +166,9 @@ export const OpencodePlugin = define { - if (!hasKey) provider.request.body.apiKey = "public" + if (!hasKey) provider.settings = { ...provider.settings, apiKey: "public" } }) if (hasKey) return for (const model of item.models.values()) { diff --git a/packages/core/src/plugin/provider/openrouter.ts b/packages/core/src/plugin/provider/openrouter.ts index 0f295fb095..eb26dcb08d 100644 --- a/packages/core/src/plugin/provider/openrouter.ts +++ b/packages/core/src/plugin/provider/openrouter.ts @@ -8,11 +8,10 @@ export const OpenRouterPlugin = define({ yield* ctx.catalog.transform( Effect.fn(function* (evt) { for (const item of evt.provider.list()) { - if (item.provider.api.type !== "aisdk") continue - if (item.provider.api.package !== "@openrouter/ai-sdk-provider") continue + if (!item.provider.aisdk) continue + if (item.provider.package !== "@openrouter/ai-sdk-provider") continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["HTTP-Referer"] = "https://opencode.ai/" - provider.request.headers["X-Title"] = "opencode" + provider.headers = { ...provider.headers, "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" } }) for (const modelID of [ModelV2.ID.make("gpt-5-chat-latest"), ModelV2.ID.make("openai/gpt-5-chat")]) { if (!item.models.has(modelID)) continue diff --git a/packages/core/src/plugin/provider/sap-ai-core.ts b/packages/core/src/plugin/provider/sap-ai-core.ts index 8c668d8b41..69956a377d 100644 --- a/packages/core/src/plugin/provider/sap-ai-core.ts +++ b/packages/core/src/plugin/provider/sap-ai-core.ts @@ -39,7 +39,7 @@ export const SapAICorePlugin = define({ yield* ctx.aisdk.language( Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return - evt.language = evt.sdk(evt.model.api.id) + evt.language = evt.sdk(evt.model.modelID ?? evt.model.id) }), ) }), diff --git a/packages/core/src/plugin/provider/vercel.ts b/packages/core/src/plugin/provider/vercel.ts index 00f5601430..da67661abe 100644 --- a/packages/core/src/plugin/provider/vercel.ts +++ b/packages/core/src/plugin/provider/vercel.ts @@ -7,11 +7,10 @@ export const VercelPlugin = define({ yield* ctx.catalog.transform( Effect.fn(function* (evt) { for (const item of evt.provider.list()) { - if (item.provider.api.type !== "aisdk") continue - if (item.provider.api.package !== "@ai-sdk/vercel") continue + if (!item.provider.aisdk) continue + if (item.provider.package !== "@ai-sdk/vercel") continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["http-referer"] = "https://opencode.ai/" - provider.request.headers["x-title"] = "opencode" + provider.headers = { ...provider.headers, "http-referer": "https://opencode.ai/", "x-title": "opencode" } }) } }), diff --git a/packages/core/src/plugin/provider/xai.ts b/packages/core/src/plugin/provider/xai.ts index 8145a3480a..20596a54e8 100644 --- a/packages/core/src/plugin/provider/xai.ts +++ b/packages/core/src/plugin/provider/xai.ts @@ -15,7 +15,7 @@ export const XAIPlugin = define({ yield* ctx.aisdk.language( Effect.fn(function* (evt) { if (evt.model.providerID !== ProviderV2.ID.make("xai")) return - evt.language = evt.sdk.responses(evt.model.api.id) + evt.language = evt.sdk.responses(evt.model.modelID ?? evt.model.id) }), ) }), diff --git a/packages/core/src/plugin/provider/zenmux.ts b/packages/core/src/plugin/provider/zenmux.ts index 29adebc0ee..a37d7f0678 100644 --- a/packages/core/src/plugin/provider/zenmux.ts +++ b/packages/core/src/plugin/provider/zenmux.ts @@ -7,12 +7,15 @@ export const ZenmuxPlugin = define({ yield* ctx.catalog.transform( Effect.fn(function* (evt) { for (const item of evt.provider.list()) { - if (item.provider.api.type !== "aisdk") continue - if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue - if (item.provider.api.url !== "https://zenmux.ai/api/v1") continue + if (!item.provider.aisdk) continue + if (item.provider.package !== "@ai-sdk/openai-compatible") continue + if (item.provider.settings?.baseURL !== "https://zenmux.ai/api/v1") continue evt.provider.update(item.provider.id, (provider) => { - provider.request.headers["HTTP-Referer"] ??= "https://opencode.ai/" - provider.request.headers["X-Title"] ??= "opencode" + provider.headers = { + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "opencode", + ...provider.headers, + } }) } }), diff --git a/packages/core/src/plugin/variant.ts b/packages/core/src/plugin/variant.ts new file mode 100644 index 0000000000..ccdbd5a15c --- /dev/null +++ b/packages/core/src/plugin/variant.ts @@ -0,0 +1,41 @@ +export * as VariantPlugin from "./variant" + +import { Effect } from "effect" +import { ModelV2 } from "../model" +import { define } from "./internal" + +export const Plugin = define({ + id: "variant", + effect: Effect.fn(function* (ctx) { + yield* ctx.catalog.transform((catalog) => { + for (const record of catalog.provider.list()) { + for (const model of record.models.values()) { + catalog.model.update(model.providerID, model.id, (draft) => { + const generated = generate(draft as ModelV2.Info, record.provider) + if (generated.length === 0) return + + const explicit = new Map((draft.variants ?? []).map((variant) => [variant.id, variant])) + const generatedIDs = new Set(generated.map((variant) => variant.id)) + draft.variants = [ + ...generated.map((variant) => explicit.get(variant.id) ?? variant), + ...(draft.variants ?? []).filter((variant) => !generatedIDs.has(variant.id)), + ] + }) + } + } + }) + }), +}) + +export function generate( + model: ModelV2.Info, + provider?: { readonly package: string; readonly aisdk?: true }, +): NonNullable { + if (!(model.aisdk ?? provider?.aisdk) || (model.package ?? provider?.package) !== "@ai-sdk/openai-compatible") return [] + const ids = `${model.id} ${model.modelID ?? ""}`.toLowerCase() + if (!["glm-5.2", "glm-5-2", "glm-5p2"].some((name) => ids.includes(name))) return [] + return ["high", "max"].map((id) => ({ + id: ModelV2.VariantID.make(id), + settings: { reasoningEffort: id }, + })) +} diff --git a/packages/core/src/provider.ts b/packages/core/src/provider.ts index 03f7d7eef3..576abd3df1 100644 --- a/packages/core/src/provider.ts +++ b/packages/core/src/provider.ts @@ -6,20 +6,10 @@ import { Provider } from "@opencode-ai/schema/provider" export const ID = Provider.ID export type ID = typeof ID.Type -export const AISDK = Provider.AISDK - -export const Native = Provider.Native - -export const Api = Provider.Api -export type Api = Provider.Api -export type MutableApi = T extends Api - ? Omit, "settings"> & (undefined extends T["settings"] ? { settings?: any } : { settings: any }) - : never - export const Request = Provider.Request export type Request = Provider.Request export const Info = Provider.Info export type Info = Provider.Info -export type MutableInfo = Omit, "api"> & { api: MutableApi } +export type MutableInfo = Types.DeepMutable diff --git a/packages/core/src/session/runner/model.ts b/packages/core/src/session/runner/model.ts index 97b862e432..f8cc794a86 100644 --- a/packages/core/src/session/runner/model.ts +++ b/packages/core/src/session/runner/model.ts @@ -11,7 +11,6 @@ import { Catalog } from "../../catalog" import { Credential } from "../../credential" import { Integration } from "../../integration" import { ModelV2 } from "../../model" -import { ModelRequest } from "../../model-request" import { ProviderV2 } from "../../provider" import { SessionSchema } from "../schema" @@ -51,16 +50,16 @@ export class VariantUnavailableError extends Schema.TaggedErrorClass()( - "SessionRunnerModel.UnsupportedApiError", +export class UnsupportedPackageError extends Schema.TaggedErrorClass()( + "SessionRunnerModel.UnsupportedPackageError", { providerID: ProviderV2.ID, modelID: ModelV2.ID, - api: Schema.String, + package: Schema.String, }, ) { override get message() { - return `Unsupported API for ${this.providerID}/${this.modelID}: ${this.api}` + return `Unsupported package for ${this.providerID}/${this.modelID}: ${this.package}` } } @@ -68,7 +67,7 @@ export type Error = | ModelNotSelectedError | ModelUnavailableError | VariantUnavailableError - | UnsupportedApiError + | UnsupportedPackageError | Integration.AuthorizationError export interface Interface { @@ -83,23 +82,19 @@ export const layerWith = (resolve: Interface["resolve"]) => Layer.succeed(Servic const apiKey = (model: ModelV2.Info, credential?: Credential.Value) => { if (credential?.type === "key") return Auth.value(credential.key) if (credential?.type === "oauth") return Auth.value(credential.access) - const value = model.request.body.apiKey ?? model.api.settings?.apiKey + const value = model.settings?.apiKey if (typeof value === "string") return Auth.value(value) } const withDefaults = (model: ModelV2.Info, route: AnyRoute) => { - const options = model.request.options ?? {} - const namespace = model.api.type === "aisdk" ? ModelRequest.namespace(model.api.package) : undefined - const body = model.request.body + const body = model.body ?? {} const httpBody = Object.hasOwn(body, "apiKey") ? Object.fromEntries(Object.entries(body).filter(([key]) => key !== "apiKey")) : body return route.with({ provider: model.providerID, - endpoint: model.api.url === undefined ? undefined : { baseURL: model.api.url }, - headers: model.request.headers, - generation: model.request.generation, - providerOptions: namespace && Object.keys(options).length > 0 ? { [namespace]: options } : undefined, + endpoint: typeof model.settings?.baseURL === "string" ? { baseURL: model.settings.baseURL } : undefined, + headers: model.headers, http: { body: httpBody }, limits: { context: model.limit.context, output: model.limit.output }, }) @@ -109,8 +104,8 @@ const withVariant = ( model: ModelV2.Info, variantID: ModelV2.VariantID | undefined, ): Effect.Effect => { - const id = variantID === "default" || variantID === undefined ? model.request.variant : variantID - const variant = model.variants.find((item) => item.id === id) + const id = variantID === "default" ? undefined : variantID + const variant = model.variants?.find((item) => item.id === id) if (!variant && variantID !== undefined && variantID !== "default") return Effect.fail( new VariantUnavailableError({ @@ -122,52 +117,55 @@ const withVariant = ( return Effect.succeed( variant ? produce(model, (draft) => { - ModelRequest.assign(draft.request, variant) + draft.settings = merge(draft.settings, variant.settings) + draft.headers = { ...draft.headers, ...variant.headers } + draft.body = merge(draft.body, variant.body) }) : model, ) } -const apiName = (model: ModelV2.Info) => - model.api.type === "aisdk" ? `${model.api.type}:${model.api.package}` : model.api.type - export const fromCatalogModel = ( model: ModelV2.Info, credential?: Credential.Value, -): Effect.Effect => { +): Effect.Effect => { const resolved = credential?.metadata === undefined ? model : produce(model, (draft) => { - Object.assign(draft.request.body, credential.metadata) + draft.settings = { ...draft.settings, ...credential.metadata } }) const key = apiKey(resolved, credential) - if (resolved.api.type === "aisdk" && resolved.api.package === "@ai-sdk/openai") { + if (resolved.aisdk && resolved.package === "@ai-sdk/openai") { return Effect.succeed( withDefaults(resolved, OpenAIResponses.route) .with({ auth: key === undefined ? Auth.none : Auth.bearer(key) }) - .model({ id: resolved.api.id }), + .model({ id: resolved.modelID ?? resolved.id }), ) } - if (resolved.api.type === "aisdk" && resolved.api.package === "@ai-sdk/anthropic") { + if (resolved.aisdk && resolved.package === "@ai-sdk/anthropic") { return Effect.succeed( withDefaults(resolved, AnthropicMessages.route) .with({ auth: key === undefined ? Auth.none : Auth.header("x-api-key", key) }) - .model({ id: resolved.api.id }), + .model({ id: resolved.modelID ?? resolved.id }), ) } - if (resolved.api.type === "aisdk" && resolved.api.package === "@ai-sdk/openai-compatible" && resolved.api.url) { + if ( + resolved.aisdk && + resolved.package === "@ai-sdk/openai-compatible" && + typeof resolved.settings?.baseURL === "string" + ) { return Effect.succeed( withDefaults(resolved, OpenAICompatibleChat.route) .with({ auth: key === undefined ? Auth.none : Auth.bearer(key) }) - .model({ id: resolved.api.id }), + .model({ id: resolved.modelID ?? resolved.id }), ) } return Effect.fail( - new UnsupportedApiError({ + new UnsupportedPackageError({ providerID: resolved.providerID, modelID: resolved.id, - api: apiName(resolved), + package: resolved.aisdk ? `aisdk:${resolved.package}` : (resolved.package ?? "unknown"), }), ) } @@ -176,10 +174,10 @@ export const resolve = (session: SessionSchema.Info, model: ModelV2.Info, creden withVariant(model, session.model?.variant).pipe(Effect.flatMap((model) => fromCatalogModel(model, credential))) export const supported = (model: ModelV2.Info) => - model.api.type === "aisdk" && - (model.api.package === "@ai-sdk/openai" || - model.api.package === "@ai-sdk/anthropic" || - (model.api.package === "@ai-sdk/openai-compatible" && model.api.url !== undefined)) + model.aisdk === true && + (model.package === "@ai-sdk/openai" || + model.package === "@ai-sdk/anthropic" || + (model.package === "@ai-sdk/openai-compatible" && typeof model.settings?.baseURL === "string")) /** Resolves models from the catalog belonging to the current Location runtime. */ export const locationLayer = Layer.effect( @@ -217,3 +215,26 @@ export const locationLayer = Layer.effect( }) }), ) + +function merge( + base: Readonly> | undefined, + overlay: Readonly> | undefined, +): Record | undefined { + if (base === undefined) return overlay && { ...overlay } + if (overlay === undefined) return { ...base } + return Object.fromEntries( + Array.from(new Set([...Object.keys(base), ...Object.keys(overlay)])).map((key) => { + const left = base[key] + const right = overlay[key] + if (right === undefined) return [key, left] + if (plain(left) && plain(right)) return [key, merge(left, right)] + return [key, right] + }), + ) +} + +function plain(input: unknown): input is Readonly> { + if (typeof input !== "object" || input === null || Array.isArray(input)) return false + const prototype = Object.getPrototypeOf(input) + return prototype === Object.prototype || prototype === null +} diff --git a/packages/core/src/v1/config/migrate.ts b/packages/core/src/v1/config/migrate.ts index c474cac51a..f0fcd1402f 100644 --- a/packages/core/src/v1/config/migrate.ts +++ b/packages/core/src/v1/config/migrate.ts @@ -6,7 +6,6 @@ import { ConfigMCPV1 } from "./mcp" import { ConfigPermissionV1 } from "./permission" import { ConfigProviderV1 } from "./provider" import { ConfigProviderOptionsV1 } from "./provider-options" -import { ModelRequest } from "../../model-request" const keys = new Set([ "logLevel", @@ -174,15 +173,11 @@ 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 as const) : undefined, + settings: info.api ? { ...options.settings, baseURL: info.api } : options.settings, + headers: info.options && options.headers, + body: info.options && options.body, models: info.models && Object.fromEntries(Object.entries(info.models).map(([name, model]) => [name, migrateModel(model, info.npm)])), @@ -192,11 +187,7 @@ function migrateProvider(info: ConfigProviderV1.Info) { function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: string) { const packageID = info.provider?.npm ?? packageName const lowerer = ConfigProviderOptionsV1.get(packageID) - const ingest = (options: Readonly>) => { - const request = ModelRequest.normalizeAiSdkOptions(packageID, options) - return { ...lowerer.request(request.body), ...request.generation, ...request.options } - } - const request = info.options && ingest(info.options) + const settings = info.options && lowerer.model(info.options) const costs = info.cost && [ { input: info.cost.input, @@ -219,29 +210,19 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st ? { tools: info.tool_call ?? false, input: info.modalities?.input ?? [], output: info.modalities?.output ?? [] } : undefined return { + modelID: info.id, 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 }, + package: info.provider?.npm, + aisdk: info.provider?.npm ? (true as const) : undefined, + settings: info.provider?.api ? { ...settings, baseURL: info.provider.api } : settings, capabilities, - request: (info.headers || request) && { - headers: info.headers, - body: request, - }, + headers: info.headers, variants: info.variants && Object.entries(info.variants).map(([id, options]) => ({ id, - body: ingest(options), + settings: lowerer.model(options), })), cost: costs, disabled: info.status === "deprecated" ? true : undefined, diff --git a/packages/core/src/v1/config/provider-options.ts b/packages/core/src/v1/config/provider-options.ts index a441a1a211..16206aa631 100644 --- a/packages/core/src/v1/config/provider-options.ts +++ b/packages/core/src/v1/config/provider-options.ts @@ -3,209 +3,32 @@ export * as ConfigProviderOptionsV1 from "./provider-options" type Options = Readonly> export interface ProviderResult { + readonly settings: Record readonly headers?: Record readonly body?: Record - readonly url?: string - readonly settings?: Record } export interface Lowerer { readonly provider: (options: Options) => ProviderResult - readonly request: (options: Options) => Record + readonly model: (options: Options) => Record } -export function get(packageName?: string): Lowerer { - const key = packageName ?? "" - return Object.hasOwn(lowerers, key) ? lowerers[key]! : raw -} - -const raw: Lowerer = { - provider(options) { - return { body: clone(options) } - }, - request: clone, -} - -const openai: Lowerer = { +const lowerer: Lowerer = { provider(options) { return { - url: string(options.baseURL), - headers: compact({ - Authorization: bearer(options.apiKey), - "OpenAI-Organization": string(options.organization), - "OpenAI-Project": string(options.project), - ...headers(options.headers), - }), - body: body(options.body), - settings: omit(options, ["apiKey", "baseURL", "organization", "project", "headers", "body"]), + settings: Object.fromEntries(Object.entries(options).filter(([key]) => key !== "headers" && key !== "body")), + headers: record(options.headers, (value): value is string => typeof value === "string"), + body: record(options.body, (_value): _value is unknown => true), } }, - request: snake, + model: (options) => ({ ...options }), } -const anthropic: Lowerer = { - provider(options) { - return { - url: string(options.baseURL), - headers: compact({ - "x-api-key": string(options.apiKey), - Authorization: options.authToken ? bearer(options.authToken) : undefined, - ...headers(options.headers), - }), - body: body(options.body), - settings: omit(options, ["apiKey", "authToken", "baseURL", "headers", "body"]), - } - }, - request(options) { - const result = snake(options) - if (options.effort !== undefined || options.taskBudget !== undefined) { - result.output_config = compactUnknown({ effort: options.effort, task_budget: options.taskBudget }) - delete result.effort - delete result.task_budget - } - if (isRecord(options.metadata) && options.metadata.userId !== undefined) { - result.metadata = { ...(isRecord(result.metadata) ? result.metadata : {}), user_id: options.metadata.userId } - } - return result - }, +export function get(_packageName?: string): Lowerer { + return lowerer } -const google: Lowerer = { - provider(options) { - return { - url: string(options.baseURL), - headers: compact({ "x-goog-api-key": string(options.apiKey), ...headers(options.headers) }), - body: body(options.body), - settings: omit(options, ["apiKey", "baseURL", "headers", "body"]), - } - }, - request(options) { - const generationConfig = pick(options, ["thinkingConfig", "responseModalities", "mediaResolution", "imageConfig"]) - return { - ...omit(options, ["thinkingConfig", "responseModalities", "mediaResolution", "imageConfig"]), - ...(Object.keys(generationConfig).length ? { generationConfig } : {}), - } - }, -} - -const azure: Lowerer = { - provider(options) { - return { - url: string(options.baseURL), - headers: compact({ "api-key": string(options.apiKey), ...headers(options.headers) }), - body: body(options.body), - settings: omit(options, ["apiKey", "baseURL", "headers", "body"]), - } - }, - request: openai.request, -} - -const bedrock: Lowerer = { - provider(options) { - return direct(options) - }, - request(options) { - return { additionalModelRequestFields: clone(options) } - }, -} - -const openaiCompatible: Lowerer = { - provider(options) { - return { ...direct(options, ["baseURL"]), url: string(options.baseURL) } - }, - request(options) { - const result = clone(options) - if (options.reasoningEffort !== undefined) { - result.reasoning_effort = options.reasoningEffort - delete result.reasoningEffort - } - return result - }, -} - -const lowerers: Readonly> = { - "@ai-sdk/openai": openai, - "@ai-sdk/anthropic": anthropic, - "@ai-sdk/google-vertex/anthropic": anthropic, - "@ai-sdk/google": google, - "@ai-sdk/google-vertex": google, - "@ai-sdk/azure": azure, - "@ai-sdk/amazon-bedrock": bedrock, - "@ai-sdk/openai-compatible": openaiCompatible, - "@ai-sdk/cerebras": openaiCompatible, - "@ai-sdk/deepinfra": openaiCompatible, - "@ai-sdk/groq": openaiCompatible, - "@ai-sdk/mistral": openaiCompatible, - "@ai-sdk/togetherai": openaiCompatible, - "@ai-sdk/xai": openaiCompatible, - "@openrouter/ai-sdk-provider": openaiCompatible, - "ai-gateway-provider": openaiCompatible, - "venice-ai-sdk-provider": openaiCompatible, -} - -function direct(options: Options, extraKeys: ReadonlyArray = []): ProviderResult { - return { - headers: headers(options.headers), - body: body(options.body), - settings: omit(options, ["headers", "body", ...extraKeys]), - } -} - -function body(input: unknown) { - if (!isRecord(input)) return undefined - return { ...input } -} - -function snake(options: Options) { - return Object.fromEntries(Object.entries(options).map(([key, value]) => [snakeKey(key), snakeValue(value)])) -} - -function snakeValue(value: unknown): unknown { - if (Array.isArray(value)) return value.map(snakeValue) - if (!isRecord(value)) return value - return Object.fromEntries(Object.entries(value).map(([key, value]) => [snakeKey(key), snakeValue(value)])) -} - -function snakeKey(key: string) { - return key.replace(/[A-Z]/g, (match) => "_" + match.toLowerCase()) -} - -function clone(options: Options) { - return { ...options } -} - -function omit(options: Options, keys: ReadonlyArray) { - return Object.fromEntries(Object.entries(options).filter(([key]) => !keys.includes(key))) -} - -function pick(options: Options, keys: ReadonlyArray) { - return Object.fromEntries(Object.entries(options).filter(([key]) => keys.includes(key))) -} - -function headers(input: unknown) { - if (!isRecord(input)) return undefined - return Object.fromEntries( - Object.entries(input).filter((entry): entry is [string, string] => typeof entry[1] === "string"), - ) -} - -function compact(input: Record) { - const entries = Object.entries(input).filter((entry): entry is [string, string] => entry[1] !== undefined) - return entries.length ? Object.fromEntries(entries) : undefined -} - -function compactUnknown(input: Record) { - return Object.fromEntries(Object.entries(input).filter((entry) => entry[1] !== undefined)) -} - -function string(input: unknown) { - return typeof input === "string" && input ? input : undefined -} - -function bearer(input: unknown) { - return typeof input === "string" && input ? `Bearer ${input}` : undefined -} - -function isRecord(input: unknown): input is Record { - return typeof input === "object" && input !== null && !Array.isArray(input) +function record(input: unknown, guard: (value: unknown) => value is T) { + if (typeof input !== "object" || input === null || Array.isArray(input)) return undefined + return Object.fromEntries(Object.entries(input).filter((entry): entry is [string, T] => guard(entry[1]))) } diff --git a/packages/core/test/catalog.test.ts b/packages/core/test/catalog.test.ts index 23a3a0f4aa..ec5cc3010a 100644 --- a/packages/core/test/catalog.test.ts +++ b/packages/core/test/catalog.test.ts @@ -65,14 +65,14 @@ describe("CatalogV2", () => { }) expect((yield* catalog.provider.available()).map((provider) => provider.id)).toEqual([ProviderV2.ID.make("test")]) - expect(required(yield* catalog.provider.get(ProviderV2.ID.make("test"))).request.body).toEqual({}) + expect(required(yield* catalog.provider.get(ProviderV2.ID.make("test"))).body).toBeUndefined() yield* credentials.create({ integrationID, label: "Second", value: Credential.Key.make({ type: "key", key: "second", metadata: { tenant: "two" } }), }) expect((yield* catalog.provider.available()).map((provider) => provider.id)).toEqual([ProviderV2.ID.make("test")]) - expect(required(yield* catalog.provider.get(ProviderV2.ID.make("test"))).request.body).toEqual({}) + expect(required(yield* catalog.provider.get(ProviderV2.ID.make("test"))).body).toBeUndefined() }).pipe(Effect.provide(layer)) }) @@ -135,114 +135,95 @@ describe("CatalogV2", () => { ), ) - it.effect("normalizes provider baseURL into api url", () => + it.effect("keeps provider API settings flat", () => Effect.gen(function* () { const catalog = yield* Catalog.Service const providerID = ProviderV2.ID.make("test") yield* catalog.transform((catalog) => catalog.provider.update(providerID, (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://default.example.com", - } - provider.request.body.baseURL = "https://override.example.com" + provider.package = "@ai-sdk/openai-compatible" + provider.aisdk = true + provider.settings = { baseURL: "https://override.example.com" } }), ) - expect(required(yield* catalog.provider.get(providerID)).api).toEqual({ - type: "aisdk", + expect(required(yield* catalog.provider.get(providerID))).toMatchObject({ package: "@ai-sdk/openai-compatible", - url: "https://override.example.com", + aisdk: true, + settings: { baseURL: "https://override.example.com" }, }) }), ) - it.effect("normalizes model baseURL into api url", () => + it.effect("resolves model settings and upstream model ID", () => Effect.gen(function* () { const catalog = yield* Catalog.Service const providerID = ProviderV2.ID.make("test") const modelID = ModelV2.ID.make("model") yield* catalog.transform((catalog) => { catalog.provider.update(providerID, (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://provider.example.com", - } + provider.package = "@ai-sdk/openai-compatible" + provider.aisdk = true + provider.settings = { baseURL: "https://provider.example.com" } }) catalog.model.update(providerID, modelID, (model) => { - model.api = { - id: modelID, - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://model.example.com", - } - model.request.body.baseURL = "https://override.example.com" + model.modelID = ModelV2.ID.make("upstream-model") + model.settings = { baseURL: "https://override.example.com" } }) }) - expect(required(yield* catalog.model.get(providerID, modelID)).api).toEqual({ + expect(required(yield* catalog.model.get(providerID, modelID))).toMatchObject({ id: modelID, - type: "aisdk", + modelID: ModelV2.ID.make("upstream-model"), package: "@ai-sdk/openai-compatible", - url: "https://override.example.com", - settings: {}, + aisdk: true, + settings: { baseURL: "https://override.example.com" }, }) }), ) - it.effect("resolves default model api from provider api", () => + it.effect("resolves default model transport from provider transport", () => Effect.gen(function* () { const catalog = yield* Catalog.Service const providerID = ProviderV2.ID.make("test") const modelID = ModelV2.ID.make("model") yield* catalog.transform((catalog) => { catalog.provider.update(providerID, (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://provider.example.com", - } + provider.package = "@ai-sdk/openai-compatible" + provider.aisdk = true + provider.settings = { baseURL: "https://provider.example.com" } }) catalog.model.update(providerID, modelID, () => {}) }) - expect(required(yield* catalog.model.get(providerID, modelID)).api).toEqual({ + expect(required(yield* catalog.model.get(providerID, modelID))).toMatchObject({ id: modelID, - type: "aisdk", package: "@ai-sdk/openai-compatible", - url: "https://provider.example.com", + aisdk: true, + settings: { baseURL: "https://provider.example.com" }, }) }), ) - it.effect("resolves provider and model request merges", () => + it.effect("resolves provider and model overlay merges", () => Effect.gen(function* () { const catalog = yield* Catalog.Service const providerID = ProviderV2.ID.make("test") const modelID = ModelV2.ID.make("model") yield* catalog.transform((catalog) => { catalog.provider.update(providerID, (provider) => { - provider.request.headers.provider = "provider" - provider.request.headers.shared = "provider" - provider.request.body.provider = true + provider.headers = { provider: "provider", shared: "provider" } + provider.body = { provider: true } }) catalog.model.update(providerID, modelID, (model) => { - model.request.headers.model = "model" - model.request.headers.shared = "model" - model.request.body.model = true - model.request.body.request = true - const options = (model.request.options ??= {}) - options.shared = "model" - options.model = true + model.headers = { model: "model", shared: "model" } + model.body = { model: true, request: true, shared: "model" } }) }) const model = required(yield* catalog.model.get(providerID, modelID)) - expect(model.request.headers).toEqual({ provider: "provider", shared: "model", model: "model" }) - expect(model.request.body).toEqual({ provider: true, model: true, request: true }) - expect(model.request.options).toEqual({ shared: "model", model: true }) + expect(model.headers).toEqual({ provider: "provider", shared: "model", model: "model" }) + expect(model.body).toEqual({ provider: true, model: true, request: true, shared: "model" }) }), ) diff --git a/packages/core/test/config/config.test.ts b/packages/core/test/config/config.test.ts index 6275d8fed3..d74ea49c79 100644 --- a/packages/core/test/config/config.test.ts +++ b/packages/core/test/config/config.test.ts @@ -43,11 +43,7 @@ function testLayer( } const provider = { - api: { type: "native", settings: {} }, - request: { - headers: {}, - body: {}, - }, + package: "test-provider", models: {}, } @@ -93,6 +89,7 @@ describe("Config", () => { provider: { bedrock: { npm: "@ai-sdk/amazon-bedrock", + api: "https://bedrock.example.com", options: { headers: { "x-test": "1" }, body: { trace: true }, @@ -103,13 +100,10 @@ describe("Config", () => { }, }) - expect(migrated.providers?.bedrock?.api).toEqual({ - type: "aisdk", + expect(migrated.providers?.bedrock).toMatchObject({ package: "@ai-sdk/amazon-bedrock", - url: undefined, - settings: { region: "us-east-1", profile: "dev" }, - }) - expect(migrated.providers?.bedrock?.request).toEqual({ + aisdk: true, + settings: { baseURL: "https://bedrock.example.com", region: "us-east-1", profile: "dev" }, headers: { "x-test": "1" }, body: { trace: true }, }) @@ -585,34 +579,32 @@ 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" } }, + settings: { apiKey: "secret" }, models: { model: { - request: { body: { reasoningEffort: "high" } }, - variants: [{ id: "fast", body: { temperature: 0.2 } }], + settings: { reasoningEffort: "high" }, + variants: [{ id: "fast", settings: { 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: { apiKey: "secret", organization: "org" }, models: { model: { - request: { - body: { temperature: 0.3, reasoningEffort: "high", serviceTier: "priority" }, - }, - variants: [{ id: "high", body: { reasoningEffort: "high", reasoningSummary: "auto" } }], + settings: { temperature: 0.3, reasoningEffort: "high", serviceTier: "priority" }, + variants: [{ id: "high", settings: { reasoningEffort: "high", reasoningSummary: "auto" } }], }, }, }) expect(documents[0]?.info.providers?.anthropic).toMatchObject({ models: { model: { - request: { - body: { - output_config: { effort: "high", task_budget: 4096 }, - metadata: { user_id: "user-1" }, - }, + settings: { + effort: "high", + taskBudget: 4096, + metadata: { userId: "user-1" }, }, }, }, diff --git a/packages/core/test/config/provider-options.test.ts b/packages/core/test/config/provider-options.test.ts index a407353d09..d9fbe2dfd7 100644 --- a/packages/core/test/config/provider-options.test.ts +++ b/packages/core/test/config/provider-options.test.ts @@ -2,20 +2,7 @@ import { describe, expect, test } from "bun:test" import { ConfigProviderOptionsV1 } from "@opencode-ai/core/v1/config/provider-options" describe("ConfigProviderOptionsV1", () => { - test("keeps raw provider and request options unchanged", () => { - const lowerer = ConfigProviderOptionsV1.get("custom-provider") - - expect(lowerer.provider({ apiKey: "secret", headers: { "x-test": "1" }, nested: { camelCase: true } })).toEqual({ - body: { apiKey: "secret", headers: { "x-test": "1" }, nested: { camelCase: true } }, - }) - expect(lowerer.request({ nested: { camelCase: true } })).toEqual({ nested: { camelCase: true } }) - }) - - test("falls back to raw lowering for prototype property package names", () => { - expect(ConfigProviderOptionsV1.get("toString").provider({ enabled: true })).toEqual({ body: { enabled: true } }) - }) - - test("lowers OpenAI provider and request options", () => { + test("splits provider overlays without changing package settings", () => { const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/openai") expect( @@ -23,189 +10,43 @@ describe("ConfigProviderOptionsV1", () => { apiKey: "secret", baseURL: "https://openai.example/v1", organization: "org", - project: "project", - headers: { "x-test": "1" }, + headers: { "x-test": "1", invalid: true }, body: { store: true }, - timeout: 1000, + nested: { camelCase: true }, }), ).toEqual({ - url: "https://openai.example/v1", - headers: { - Authorization: "Bearer secret", - "OpenAI-Organization": "org", - "OpenAI-Project": "project", - "x-test": "1", + settings: { + apiKey: "secret", + baseURL: "https://openai.example/v1", + organization: "org", + nested: { camelCase: true }, }, + headers: { "x-test": "1" }, body: { store: true }, - settings: { timeout: 1000 }, - }) - expect(lowerer.request({ reasoningEffort: "high", nestedValue: { camelCase: true } })).toEqual({ - reasoning_effort: "high", - nested_value: { camel_case: true }, }) }) - test("lowers Anthropic provider and request options", () => { + test("keeps model and variant options unchanged", () => { const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/anthropic") expect( - lowerer.provider({ - apiKey: "secret", - authToken: "token", - baseURL: "https://anthropic.example", - headers: { "x-test": "1" }, - body: { beta: true }, - generateId: "custom", - }), - ).toEqual({ - url: "https://anthropic.example", - headers: { "x-api-key": "secret", Authorization: "Bearer token", "x-test": "1" }, - body: { beta: true }, - settings: { generateId: "custom" }, - }) - expect( - lowerer.request({ - effort: "high", + lowerer.model({ + reasoningEffort: "high", taskBudget: 1024, - metadata: { userId: "user", traceId: "trace" }, - nestedValue: { camelCase: true }, + metadata: { userId: "user" }, }), ).toEqual({ - output_config: { effort: "high", task_budget: 1024 }, - metadata: { user_id: "user", trace_id: "trace" }, - nested_value: { camel_case: true }, + reasoningEffort: "high", + taskBudget: 1024, + metadata: { userId: "user" }, }) }) - test("lowers Google provider and request options", () => { - const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/google") - - expect( - lowerer.provider({ - apiKey: "secret", - baseURL: "https://google.example", - headers: { "x-test": "1" }, - body: { trace: true }, - project: "project", - }), - ).toEqual({ - url: "https://google.example", - headers: { "x-goog-api-key": "secret", "x-test": "1" }, - body: { trace: true }, - settings: { project: "project" }, - }) - expect( - lowerer.request({ - thinkingConfig: { thinkingBudget: 1024 }, - responseModalities: ["TEXT"], - mediaResolution: "high", - imageConfig: { aspectRatio: "16:9" }, - safetySettings: ["safe"], - }), - ).toEqual({ - safetySettings: ["safe"], - generationConfig: { - thinkingConfig: { thinkingBudget: 1024 }, - responseModalities: ["TEXT"], - mediaResolution: "high", - imageConfig: { aspectRatio: "16:9" }, - }, - }) - }) - - test("lowers Azure provider options and uses OpenAI request lowering", () => { - const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/azure") - - expect( - lowerer.provider({ - apiKey: "secret", - baseURL: "https://azure.example", - headers: { "x-test": "1" }, - body: { trace: true }, - resourceName: "resource", - }), - ).toEqual({ - url: "https://azure.example", - headers: { "api-key": "secret", "x-test": "1" }, - body: { trace: true }, - settings: { resourceName: "resource" }, - }) - expect(lowerer.request({ reasoningEffort: "high" })).toEqual({ reasoning_effort: "high" }) - }) - - test("lowers Amazon Bedrock provider and request options", () => { - const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/amazon-bedrock") - - expect( - lowerer.provider({ - headers: { "x-test": "1" }, - body: { trace: true }, - region: "us-east-1", - profile: "dev", - }), - ).toEqual({ - headers: { "x-test": "1" }, - body: { trace: true }, - settings: { region: "us-east-1", profile: "dev" }, - }) - expect(lowerer.request({ temperature: 0.2 })).toEqual({ - additionalModelRequestFields: { temperature: 0.2 }, - }) - }) - - test("lowers OpenAI-compatible provider and request options", () => { - const lowerer = ConfigProviderOptionsV1.get("@ai-sdk/openai-compatible") - - expect( - lowerer.provider({ - baseURL: "https://compatible.example/v1", - headers: { "x-test": "1" }, - body: { trace: true }, - apiKey: "secret", - }), - ).toEqual({ - url: "https://compatible.example/v1", - headers: { "x-test": "1" }, - body: { trace: true }, - settings: { apiKey: "secret" }, - }) - expect(lowerer.request({ reasoningEffort: "high", serviceTier: "priority" })).toEqual({ - reasoning_effort: "high", - serviceTier: "priority", - }) - }) - - test.each([ - "@ai-sdk/cerebras", - "@ai-sdk/deepinfra", - "@ai-sdk/groq", - "@ai-sdk/mistral", - "@ai-sdk/togetherai", - "@ai-sdk/xai", - "@openrouter/ai-sdk-provider", - "ai-gateway-provider", - "venice-ai-sdk-provider", - ])("uses OpenAI-compatible lowering for %s", (packageName) => { - const lowerer = ConfigProviderOptionsV1.get(packageName) - - expect(lowerer.provider({ baseURL: "https://example.test", apiKey: "secret" })).toEqual({ - url: "https://example.test", + test("uses the same mechanical lowering for every package", () => { + expect(ConfigProviderOptionsV1.get("custom-provider").provider({ enabled: true })).toEqual({ + settings: { enabled: true }, headers: undefined, body: undefined, - settings: { apiKey: "secret" }, }) - expect(lowerer.request({ reasoningEffort: "high" })).toEqual({ reasoning_effort: "high" }) }) - - test.each(["@ai-sdk/google-vertex", "@ai-sdk/google-vertex/anthropic"])( - "uses provider family lowering for %s", - (packageName) => { - const lowerer = ConfigProviderOptionsV1.get(packageName) - - expect(lowerer.provider({ baseURL: "https://example.test", profile: "dev" })).toMatchObject({ - url: "https://example.test", - settings: { profile: "dev" }, - }) - }, - ) }) diff --git a/packages/core/test/config/provider.test.ts b/packages/core/test/config/provider.test.ts index 12f4a01c78..9ca8b8ca1e 100644 --- a/packages/core/test/config/provider.test.ts +++ b/packages/core/test/config/provider.test.ts @@ -45,17 +45,10 @@ function withEnv(vars: Record, effect: () = ) } -function request(headers: Record, variant?: string) { - return { - headers, - variant, - } -} - const decode = Schema.decodeUnknownSync(Config.Info) describe("ConfigProviderPlugin.Plugin", () => { - it.effect("partitions existing model variant bodies without changing config shape", () => + it.effect("keeps configured model variant bodies unchanged", () => Effect.gen(function* () { const catalog = yield* Catalog.Service const providerID = ProviderV2.ID.opencode @@ -68,7 +61,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: [ @@ -96,8 +91,7 @@ describe("ConfigProviderPlugin.Plugin", () => { expect(model.variants).toMatchObject([ { id: "high", - body: {}, - options: { + body: { reasoningEffort: "high", reasoningSummary: "auto", include: ["reasoning.encrypted_content"], @@ -107,7 +101,7 @@ describe("ConfigProviderPlugin.Plugin", () => { }), ) - it.effect("uses the effective provider package across layered config", () => + it.effect("keeps layered model variant bodies unchanged", () => Effect.gen(function* () { const catalog = yield* Catalog.Service const providerID = ProviderV2.ID.opencode @@ -120,7 +114,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" }, }, }, }), @@ -145,10 +141,9 @@ describe("ConfigProviderPlugin.Plugin", () => { yield* addPlugin(config) const model = required(yield* catalog.model.get(providerID, modelID)) - expect(model.variants[0]).toMatchObject({ + expect(model.variants?.[0]).toMatchObject({ id: "high", - body: {}, - options: { reasoningEffort: "high" }, + body: { reasoningEffort: "high" }, }) }), ) @@ -171,8 +166,8 @@ describe("ConfigProviderPlugin.Plugin", () => { custom: { name: "Configured", env: ["CUSTOM_API_KEY"], - api: { type: "native", settings: {} }, - request: request({ first: "first", shared: "first" }), + package: "native", + headers: { first: "first", shared: "first" }, models: { chat: { name: "First", @@ -180,7 +175,8 @@ describe("ConfigProviderPlugin.Plugin", () => { disabled: true, limit: { context: 100, output: 50 }, cost: { input: 1, output: 2 }, - request: request({ first: "first", shared: "first" }, "retained"), + settings: { retained: true }, + headers: { first: "first", shared: "first" }, variants: [ { id: "fast", @@ -199,17 +195,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" }, + modelID: "api-chat", name: "Last", limit: { output: 75 }, - request: request({ last: "last", shared: "last" }), + headers: { last: "last", shared: "last" }, variants: [ { id: "fast", @@ -249,22 +247,25 @@ 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.request.headers).toEqual({ first: "first", shared: "last", last: "last" }) - expect(model.api.id).toBe(ModelV2.ID.make("api-chat")) + expect(provider.package).toBe("custom-sdk") + expect(provider.aisdk).toBe(true) + expect(provider.settings).toEqual({ baseURL: "https://example.test" }) + expect(provider.headers).toEqual({ first: "first", shared: "last", last: "last" }) + expect(model.id).toBe(modelID) + expect(model.modelID).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) expect(model.limit).toEqual({ context: 100, output: 75 }) expect(model.cost).toEqual([{ input: 1, output: 2, cache: { read: 0, write: 0 }, tier: undefined }]) - expect(model.request.headers).toEqual({ first: "first", shared: "last", last: "last" }) - expect(model.request.variant).toBe("retained") - expect(model.variants.map((variant) => variant.id)).toEqual([ + expect(model.settings).toEqual({ baseURL: "https://example.test", retained: true }) + expect(model.headers).toEqual({ first: "first", shared: "last", last: "last" }) + expect(model.variants?.map((variant) => variant.id)).toEqual([ ModelV2.VariantID.make("fast"), ModelV2.VariantID.make("slow"), ]) - expect(model.variants[0]?.headers).toEqual({ first: "first", shared: "last", last: "last" }) - expect(model.variants[1]?.headers).toEqual({ slow: "slow" }) + expect(model.variants?.[0]?.headers).toEqual({ first: "first", shared: "last", last: "last" }) + expect(model.variants?.[1]?.headers).toEqual({ slow: "slow" }) }), ), ) diff --git a/packages/core/test/location-layer.test.ts b/packages/core/test/location-layer.test.ts index 14b2ed7378..a33cee0689 100644 --- a/packages/core/test/location-layer.test.ts +++ b/packages/core/test/location-layer.test.ts @@ -168,7 +168,7 @@ describe("LocationServiceMap", () => { providers: { unavailable: { name: "Unavailable", - api: { type: "native", settings: {} }, + package: "test-provider", models: { chat: { disabled: true } }, }, }, diff --git a/packages/core/test/model-request.test.ts b/packages/core/test/model-request.test.ts deleted file mode 100644 index c8ac12d470..0000000000 --- a/packages/core/test/model-request.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { ModelRequest } from "@opencode-ai/core/model-request" - -describe("ModelRequest", () => { - test("partitions AI SDK model and models.dev mode options", () => { - expect( - ModelRequest.normalizeAiSdkOptions("@ai-sdk/openai", { - maxOutputTokens: 4096, - temperature: 0.2, - reasoningEffort: "high", - serviceTier: "priority", - custom_extension: { enabled: true }, - }), - ).toEqual({ - generation: { maxTokens: 4096, temperature: 0.2 }, - options: { reasoningEffort: "high", serviceTier: "priority" }, - body: { custom_extension: { enabled: true } }, - }) - }) - - test("keeps unknown-provider options as compatibility fields", () => { - expect(ModelRequest.normalizeAiSdkOptions(undefined, { temperature: 0.2, reasoningEffort: "high" })).toEqual({ - generation: { temperature: 0.2 }, - options: {}, - body: { reasoningEffort: "high" }, - }) - }) - - test("does not consult inherited package-name properties", () => { - expect(ModelRequest.normalizeAiSdkOptions("__proto__", { reasoningEffort: "high" })).toEqual({ - generation: {}, - options: {}, - body: { reasoningEffort: "high" }, - }) - }) - - test("normalizes models.dev wire aliases owned by native protocols", () => { - expect(ModelRequest.normalizeAiSdkOptions("@ai-sdk/openai", { service_tier: "priority" })).toEqual({ - generation: {}, - options: { serviceTier: "priority" }, - body: {}, - }) - }) -}) diff --git a/packages/core/test/plugin/host.ts b/packages/core/test/plugin/host.ts index 5ec8c16bd0..b1231fe4eb 100644 --- a/packages/core/test/plugin/host.ts +++ b/packages/core/test/plugin/host.ts @@ -118,7 +118,7 @@ export function catalogHost(catalog: Catalog.Interface): PluginContext["catalog" id: ModelV2.ID.make(current.id), providerID: ProviderV2.ID.make(current.providerID), family: current.family === undefined ? undefined : ModelV2.Family.make(current.family), - variants: current.variants.map((variant) => ({ + variants: current.variants?.map((variant) => ({ ...variant, id: ModelV2.VariantID.make(variant.id), })), @@ -272,39 +272,28 @@ function agentInfo(value: AgentV2.Info) { function providerInfo(value: ProviderV2.MutableInfo) { return { ...value, - api: { ...value.api, settings: value.api.settings && { ...value.api.settings } }, - request: { headers: { ...value.request.headers }, body: { ...value.request.body } }, + settings: value.settings && { ...value.settings }, + headers: value.headers && { ...value.headers }, + body: value.body && { ...value.body }, } } function modelInfo(value: ModelV2.Info | ModelV2.MutableInfo) { return { ...value, - api: { ...value.api, settings: value.api.settings && { ...value.api.settings } }, + settings: value.settings && { ...value.settings }, + headers: value.headers && { ...value.headers }, + body: value.body && { ...value.body }, capabilities: { ...value.capabilities, input: [...value.capabilities.input], output: [...value.capabilities.output], }, - request: { - ...value.request, - headers: { ...value.request.headers }, - body: { ...value.request.body }, - generation: value.request.generation && { - ...value.request.generation, - stop: value.request.generation.stop && [...value.request.generation.stop], - }, - options: value.request.options && { ...value.request.options }, - }, - variants: value.variants.map((variant) => ({ + variants: value.variants?.map((variant) => ({ ...variant, - headers: { ...variant.headers }, - body: { ...variant.body }, - generation: variant.generation && { - ...variant.generation, - stop: variant.generation.stop && [...variant.generation.stop], - }, - options: variant.options && { ...variant.options }, + settings: variant.settings && { ...variant.settings }, + headers: variant.headers && { ...variant.headers }, + body: variant.body && { ...variant.body }, })), time: { ...value.time }, cost: value.cost.map((cost) => ({ ...cost, tier: cost.tier && { ...cost.tier }, cache: { ...cost.cache } })), diff --git a/packages/core/test/plugin/provider-alibaba.test.ts b/packages/core/test/plugin/provider-alibaba.test.ts index 25d46bf83c..3bf2e0b325 100644 --- a/packages/core/test/plugin/provider-alibaba.test.ts +++ b/packages/core/test/plugin/provider-alibaba.test.ts @@ -28,7 +28,9 @@ describe("AlibabaPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("alibaba"), ModelV2.ID.make("qwen")), - api: { id: ModelV2.ID.make("qwen"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("qwen"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/alibaba", options: { name: "alibaba" }, @@ -45,7 +47,9 @@ describe("AlibabaPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("alibaba"), ModelV2.ID.make("qwen")), - api: { id: ModelV2.ID.make("qwen"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("qwen"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "alibaba" }, @@ -62,7 +66,9 @@ describe("AlibabaPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-alibaba"), ModelV2.ID.make("qwen")), - api: { id: ModelV2.ID.make("qwen"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("qwen"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/alibaba", options: { name: "custom-alibaba", apiKey: "test" }, @@ -74,17 +80,19 @@ describe("AlibabaPlugin", () => { }), ) - it.effect("uses the old default languageModel(api.id) behavior", () => + it.effect("uses the default languageModel(modelID) behavior", () => Effect.gen(function* () { const plugin = yield* PluginV2.Service const aisdk = yield* AISDK.Service yield* addPlugin() const item = ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("alibaba"), ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("qwen-plus"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("qwen-plus"), + aisdk: true, + package: "test-provider", }) const result = yield* aisdk.runSDK({ model: item, package: "@ai-sdk/alibaba", options: {} }) - const language = result.sdk?.languageModel(item.api.id) + const language = result.sdk?.languageModel(item.modelID ?? item.id) expect(language?.modelId).toBe("qwen-plus") expect(language?.provider).toBe("alibaba.chat") }), diff --git a/packages/core/test/plugin/provider-amazon-bedrock.test.ts b/packages/core/test/plugin/provider-amazon-bedrock.test.ts index 5d24879e49..42e9fc10ec 100644 --- a/packages/core/test/plugin/provider-amazon-bedrock.test.ts +++ b/packages/core/test/plugin/provider-amazon-bedrock.test.ts @@ -79,31 +79,32 @@ function openAIUrl(language: unknown, path: string, modelId: string) { } describe("AmazonBedrockPlugin", () => { - it.effect("moves endpoint option to api URL", () => + it.effect("moves endpoint option to baseURL", () => Effect.gen(function* () { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { const bedrock = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.amazonBedrock), - api: { type: "aisdk", package: "@ai-sdk/amazon-bedrock" }, - request: { - headers: {}, - body: { endpoint: "https://bedrock.example" }, - }, + aisdk: true, + package: "@ai-sdk/amazon-bedrock", + headers: {}, + settings: { endpoint: "https://bedrock.example" }, }) catalog.provider.update(bedrock.id, (item) => { - item.api = bedrock.api - item.request = bedrock.request + item.aisdk = bedrock.aisdk + item.package = bedrock.package + item.headers = bedrock.headers + item.settings = { endpoint: "https://bedrock.example" } }) }) yield* addPlugin() const result = required(yield* catalog.provider.get(ProviderV2.ID.amazonBedrock)) - expect(result.api).toEqual({ - type: "aisdk", + expect(result).toMatchObject({ + aisdk: true, package: "@ai-sdk/amazon-bedrock", - url: "https://bedrock.example", + settings: { baseURL: "https://bedrock.example" }, }) - expect(result.request.body.endpoint).toBeUndefined() + expect(result.settings?.endpoint).toBeUndefined() }), ) @@ -116,7 +117,9 @@ describe("AmazonBedrockPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/amazon-bedrock", options: { @@ -141,7 +144,9 @@ describe("AmazonBedrockPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/amazon-bedrock", options: { @@ -175,11 +180,9 @@ describe("AmazonBedrockPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { - id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), - type: "aisdk", - package: "test-provider", - }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/amazon-bedrock", options: { name: "amazon-bedrock" }, @@ -199,7 +202,9 @@ describe("AmazonBedrockPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/amazon-bedrock", options: { name: "amazon-bedrock", region: "eu-west-1" }, @@ -218,7 +223,9 @@ describe("AmazonBedrockPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/amazon-bedrock", options: { name: "amazon-bedrock" }, @@ -237,7 +244,9 @@ describe("AmazonBedrockPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/amazon-bedrock", options: { name: "amazon-bedrock" }, @@ -257,7 +266,9 @@ describe("AmazonBedrockPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/amazon-bedrock", options: { @@ -286,7 +297,9 @@ describe("AmazonBedrockPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/amazon-bedrock", options: { @@ -314,11 +327,9 @@ describe("AmazonBedrockPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-5.5")), - api: { - id: ModelV2.ID.make("openai.gpt-5.5"), - type: "aisdk", - package: "@ai-sdk/amazon-bedrock/mantle", - }, + modelID: ModelV2.ID.make("openai.gpt-5.5"), + aisdk: true, + package: "@ai-sdk/amazon-bedrock/mantle", }), package: "@ai-sdk/amazon-bedrock/mantle", options: { @@ -345,11 +356,9 @@ describe("AmazonBedrockPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-5.5")), - api: { - id: ModelV2.ID.make("openai.gpt-5.5"), - type: "aisdk", - package: "@ai-sdk/amazon-bedrock/mantle", - }, + modelID: ModelV2.ID.make("openai.gpt-5.5"), + aisdk: true, + package: "@ai-sdk/amazon-bedrock/mantle", }), sdk: fakeSelectorSdk(calls), options: { baseURL: "https://bedrock-mantle.us-east-2.api.aws/openai/v1", region: "us-east-2" }, @@ -357,11 +366,9 @@ describe("AmazonBedrockPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-oss-safeguard-120b")), - api: { - id: ModelV2.ID.make("openai.gpt-oss-safeguard-120b"), - type: "aisdk", - package: "@ai-sdk/amazon-bedrock/mantle", - }, + modelID: ModelV2.ID.make("openai.gpt-oss-safeguard-120b"), + aisdk: true, + package: "@ai-sdk/amazon-bedrock/mantle", }), sdk: fakeSelectorSdk(calls), options: { region: "us-east-1" }, @@ -378,11 +385,9 @@ describe("AmazonBedrockPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { - id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), - type: "aisdk", - package: "@ai-sdk/amazon-bedrock/anthropic", - }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "@ai-sdk/amazon-bedrock/anthropic", }), package: "@ai-sdk/amazon-bedrock/anthropic", options: { name: "amazon-bedrock" }, @@ -409,11 +414,9 @@ describe("AmazonBedrockPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { - id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), - type: "aisdk", - package: "test-provider", - }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/amazon-bedrock", options: { @@ -444,7 +447,9 @@ describe("AmazonBedrockPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, options: {}, @@ -452,7 +457,9 @@ describe("AmazonBedrockPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, options: { region: "eu-west-1" }, @@ -460,11 +467,9 @@ describe("AmazonBedrockPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("global.anthropic.claude-sonnet-4-5")), - api: { - id: ModelV2.ID.make("global.anthropic.claude-sonnet-4-5"), - type: "aisdk", - package: "test-provider", - }, + modelID: ModelV2.ID.make("global.anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, options: { region: "eu-west-1" }, @@ -472,7 +477,9 @@ describe("AmazonBedrockPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, options: { region: "ap-northeast-1" }, @@ -480,7 +487,9 @@ describe("AmazonBedrockPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, options: { region: "ap-southeast-2" }, @@ -505,7 +514,9 @@ describe("AmazonBedrockPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, options: {}, @@ -591,7 +602,9 @@ describe("AmazonBedrockPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.amazonBedrock, ModelV2.ID.make(item.modelID)), - api: { id: ModelV2.ID.make(item.modelID), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make(item.modelID), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, options: { region: item.region }, @@ -610,7 +623,9 @@ describe("AmazonBedrockPlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("anthropic.claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, options: { region: "eu-west-1" }, diff --git a/packages/core/test/plugin/provider-anthropic.test.ts b/packages/core/test/plugin/provider-anthropic.test.ts index a4ad4d7950..53e68a8098 100644 --- a/packages/core/test/plugin/provider-anthropic.test.ts +++ b/packages/core/test/plugin/provider-anthropic.test.ts @@ -31,19 +31,22 @@ describe("AnthropicPlugin", () => { yield* catalog.transform((catalog) => { const item = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.anthropic), - api: { type: "aisdk", package: "@ai-sdk/anthropic" }, - request: { headers: { Existing: "1" }, body: {} }, + aisdk: true, + package: "@ai-sdk/anthropic", + headers: { Existing: "1" }, + settings: {}, }) catalog.provider.update(item.id, (draft) => { - draft.api = item.api - draft.request = item.request + draft.aisdk = item.aisdk + draft.package = item.package + draft.headers = item.headers }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.anthropic)).request.headers["anthropic-beta"]).toBe( + expect(required(yield* catalog.provider.get(ProviderV2.ID.anthropic)).headers?.["anthropic-beta"]).toBe( "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14", ) - expect(required(yield* catalog.provider.get(ProviderV2.ID.anthropic)).request.headers.Existing).toBe("1") + expect(required(yield* catalog.provider.get(ProviderV2.ID.anthropic)).headers?.Existing).toBe("1") }), ) @@ -52,9 +55,7 @@ describe("AnthropicPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.openai, () => {})) yield* addPlugin() - expect( - required(yield* catalog.provider.get(ProviderV2.ID.openai)).request.headers["anthropic-beta"], - ).toBeUndefined() + expect(required(yield* catalog.provider.get(ProviderV2.ID.openai)).headers?.["anthropic-beta"]).toBeUndefined() }), ) @@ -66,7 +67,9 @@ describe("AnthropicPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-anthropic"), ModelV2.ID.make("claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "@ai-sdk/anthropic" }, + modelID: ModelV2.ID.make("claude-sonnet-4-5"), + aisdk: true, + package: "@ai-sdk/anthropic", }), package: "@ai-sdk/anthropic", options: { name: "custom-anthropic", apiKey: "test" }, @@ -83,7 +86,9 @@ describe("AnthropicPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "@ai-sdk/anthropic" }, + modelID: ModelV2.ID.make("claude-sonnet-4-5"), + aisdk: true, + package: "@ai-sdk/anthropic", }), package: "@ai-sdk/anthropic", options: { name: "anthropic", apiKey: "test" }, diff --git a/packages/core/test/plugin/provider-azure-cognitive-services.test.ts b/packages/core/test/plugin/provider-azure-cognitive-services.test.ts index 6b3f6c6a38..f726f8bf90 100644 --- a/packages/core/test/plugin/provider-azure-cognitive-services.test.ts +++ b/packages/core/test/plugin/provider-azure-cognitive-services.test.ts @@ -66,18 +66,18 @@ describe("AzureCognitiveServicesPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("azure-cognitive-services"), (item) => { - item.api = { type: "aisdk", package: "@ai-sdk/openai-compatible" } + item.aisdk = true + item.package = "@ai-sdk/openai-compatible" }) }) yield* addPlugin() const result = required(yield* catalog.provider.get(ProviderV2.ID.make("azure-cognitive-services"))) - expect(result.api).toEqual({ - type: "aisdk", + expect(result).toMatchObject({ + aisdk: true, package: "@ai-sdk/openai-compatible", - url: "https://cognitive.cognitiveservices.azure.com/openai", + settings: { baseURL: "https://cognitive.cognitiveservices.azure.com/openai" }, }) - expect(result.request.body.baseURL).toBeUndefined() - expect(result.request.body.resourceName).toBeUndefined() + expect(result.settings?.resourceName).toBeUndefined() }), ), ) @@ -89,26 +89,30 @@ describe("AzureCognitiveServicesPlugin", () => { yield* catalog.transform((catalog) => { const azure = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services")), - api: { type: "aisdk", package: "@ai-sdk/openai-compatible" }, + aisdk: true, + package: "@ai-sdk/openai-compatible", }) const openai = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.openai), - api: { type: "aisdk", package: "test-provider" }, + aisdk: true, + package: "test-provider", }) catalog.provider.update(azure.id, (item) => { - item.api = azure.api + item.aisdk = azure.aisdk + item.package = azure.package }) catalog.provider.update(openai.id, (item) => { - item.api = openai.api + item.aisdk = openai.aisdk + item.package = openai.package }) }) yield* addPlugin() const azure = required(yield* catalog.provider.get(ProviderV2.ID.make("azure-cognitive-services"))) const openai = required(yield* catalog.provider.get(ProviderV2.ID.openai)) - expect(azure.request.body.baseURL).toBeUndefined() - expect(azure.api).toEqual({ type: "aisdk", package: "@ai-sdk/openai-compatible" }) - expect(openai.request.body.baseURL).toBeUndefined() - expect(openai.api).toEqual({ type: "aisdk", package: "test-provider" }) + expect(azure.settings?.baseURL).toBeUndefined() + expect(azure).toMatchObject({ aisdk: true, package: "@ai-sdk/openai-compatible" }) + expect(openai.settings?.baseURL).toBeUndefined() + expect(openai).toMatchObject({ aisdk: true, package: "test-provider" }) }), ), ) @@ -122,7 +126,9 @@ describe("AzureCognitiveServicesPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("deployment")), - api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("deployment"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: { useCompletionUrls: true }, @@ -140,7 +146,9 @@ describe("AzureCognitiveServicesPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("deployment")), - api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("deployment"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -148,7 +156,9 @@ describe("AzureCognitiveServicesPlugin", () => { const ignored = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("deployment")), - api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("deployment"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -168,7 +178,9 @@ describe("AzureCognitiveServicesPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("messages-deployment")), - api: { id: ModelV2.ID.make("messages-deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("messages-deployment"), + aisdk: true, + package: "test-provider", }), sdk: { messages: sdk.messages, chat: sdk.chat, languageModel: sdk.languageModel }, options: {}, @@ -176,7 +188,9 @@ describe("AzureCognitiveServicesPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("chat-deployment")), - api: { id: ModelV2.ID.make("chat-deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("chat-deployment"), + aisdk: true, + package: "test-provider", }), sdk: { chat: sdk.chat, languageModel: sdk.languageModel }, options: {}, @@ -184,7 +198,9 @@ describe("AzureCognitiveServicesPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("language-deployment")), - api: { id: ModelV2.ID.make("language-deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("language-deployment"), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: sdk.languageModel }, options: {}, diff --git a/packages/core/test/plugin/provider-azure.test.ts b/packages/core/test/plugin/provider-azure.test.ts index 1d8172854c..65bad6e289 100644 --- a/packages/core/test/plugin/provider-azure.test.ts +++ b/packages/core/test/plugin/provider-azure.test.ts @@ -66,11 +66,12 @@ describe("AzurePlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.azure, (item) => { - item.api = { type: "aisdk", package: "@ai-sdk/azure" } + item.aisdk = true + item.package = "@ai-sdk/azure" }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.azure)).request.body.resourceName).toBe("from-env") + expect(required(yield* catalog.provider.get(ProviderV2.ID.azure)).settings?.resourceName).toBe("from-env") }), ), ) @@ -82,18 +83,22 @@ describe("AzurePlugin", () => { yield* catalog.transform((catalog) => { const azure = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.azure), - api: { type: "aisdk", package: "@ai-sdk/azure" }, - request: { headers: {}, body: { resourceName: "from-config" } }, + aisdk: true, + package: "@ai-sdk/azure", + headers: {}, + settings: { resourceName: "from-config" }, }) catalog.provider.update(azure.id, (item) => { - item.api = azure.api - item.request = azure.request + item.aisdk = azure.aisdk + item.package = azure.package + item.headers = azure.headers + item.settings = { resourceName: "from-config" } }) catalog.provider.update(ProviderV2.ID.openai, () => {}) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.azure)).request.body.resourceName).toBe("from-config") - expect(required(yield* catalog.provider.get(ProviderV2.ID.openai)).request.body.resourceName).toBeUndefined() + expect(required(yield* catalog.provider.get(ProviderV2.ID.azure)).settings?.resourceName).toBe("from-config") + expect(required(yield* catalog.provider.get(ProviderV2.ID.openai)).settings?.resourceName).toBeUndefined() }), ), ) @@ -105,16 +110,20 @@ describe("AzurePlugin", () => { yield* catalog.transform((catalog) => { const azure = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.azure), - api: { type: "aisdk", package: "@ai-sdk/azure" }, - request: { headers: {}, body: { resourceName: "" } }, + aisdk: true, + package: "@ai-sdk/azure", + headers: {}, + settings: { resourceName: "" }, }) catalog.provider.update(azure.id, (item) => { - item.api = azure.api - item.request = azure.request + item.aisdk = azure.aisdk + item.package = azure.package + item.headers = azure.headers + item.settings = { resourceName: "" } }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.azure)).request.body.resourceName).toBe("from-env") + expect(required(yield* catalog.provider.get(ProviderV2.ID.azure)).settings?.resourceName).toBe("from-env") }), ), ) @@ -126,16 +135,20 @@ describe("AzurePlugin", () => { yield* catalog.transform((catalog) => { const azure = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.azure), - api: { type: "aisdk", package: "@ai-sdk/azure" }, - request: { headers: {}, body: { resourceName: " " } }, + aisdk: true, + package: "@ai-sdk/azure", + headers: {}, + settings: { resourceName: " " }, }) catalog.provider.update(azure.id, (item) => { - item.api = azure.api - item.request = azure.request + item.aisdk = azure.aisdk + item.package = azure.package + item.headers = azure.headers + item.settings = { resourceName: " " } }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.azure)).request.body.resourceName).toBe("from-env") + expect(required(yield* catalog.provider.get(ProviderV2.ID.azure)).settings?.resourceName).toBe("from-env") }), ), ) @@ -149,7 +162,9 @@ describe("AzurePlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")), - api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("deployment"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/azure", options: { name: "azure", baseURL: "https://proxy.example.com/openai" }, @@ -168,7 +183,9 @@ describe("AzurePlugin", () => { .runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")), - api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("deployment"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/azure", options: { name: "azure" }, @@ -188,7 +205,9 @@ describe("AzurePlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")), - api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("deployment"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: { useCompletionUrls: true }, @@ -206,7 +225,9 @@ describe("AzurePlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")), - api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("deployment"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: { useCompletionUrls: true }, @@ -224,8 +245,11 @@ describe("AzurePlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")), - api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" }, - request: { headers: {}, body: { useCompletionUrls: true } }, + modelID: ModelV2.ID.make("deployment"), + aisdk: true, + package: "test-provider", + headers: {}, + settings: { useCompletionUrls: true }, }), sdk: fakeSelectorSdk(calls), options: {}, @@ -243,7 +267,9 @@ describe("AzurePlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("deployment")), - api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("deployment"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -251,7 +277,9 @@ describe("AzurePlugin", () => { const ignored = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("deployment")), - api: { id: ModelV2.ID.make("deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("deployment"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -274,7 +302,9 @@ describe("AzurePlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("messages-deployment")), - api: { id: ModelV2.ID.make("messages-deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("messages-deployment"), + aisdk: true, + package: "test-provider", }), sdk: { messages: make("messages"), chat: make("chat"), languageModel: make("languageModel") }, options: {}, @@ -282,7 +312,9 @@ describe("AzurePlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.azure, ModelV2.ID.make("language-deployment")), - api: { id: ModelV2.ID.make("language-deployment"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("language-deployment"), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: make("languageModel") }, options: {}, diff --git a/packages/core/test/plugin/provider-cerebras.test.ts b/packages/core/test/plugin/provider-cerebras.test.ts index adcc1779b0..ae58572c64 100644 --- a/packages/core/test/plugin/provider-cerebras.test.ts +++ b/packages/core/test/plugin/provider-cerebras.test.ts @@ -36,12 +36,13 @@ describe("CerebrasPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("cerebras"), (item) => { - item.api = { type: "aisdk", package: "@ai-sdk/cerebras" } - item.request.headers.Existing = "1" + item.aisdk = true + item.package = "@ai-sdk/cerebras" + item.headers = { ...item.headers, Existing: "1" } }) }) yield* addPlugin() - expect((yield* catalog.provider.get(ProviderV2.ID.make("cerebras")))?.request.headers).toEqual({ + expect((yield* catalog.provider.get(ProviderV2.ID.make("cerebras")))?.headers).toEqual({ Existing: "1", "X-Cerebras-3rd-Party-Integration": "opencode", }) @@ -53,7 +54,7 @@ describe("CerebrasPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("groq"), () => {})) yield* addPlugin() - expect((yield* catalog.provider.get(ProviderV2.ID.make("groq")))?.request.headers).toEqual({}) + expect((yield* catalog.provider.get(ProviderV2.ID.make("groq")))?.headers).toBeUndefined() }), ) @@ -69,11 +70,9 @@ describe("CerebrasPlugin", () => { ProviderV2.ID.make("custom-cerebras"), ModelV2.ID.make("llama-4-scout-17b-16e-instruct"), ), - api: { - id: ModelV2.ID.make("llama-4-scout-17b-16e-instruct"), - type: "aisdk", - package: "test-provider", - }, + modelID: ModelV2.ID.make("llama-4-scout-17b-16e-instruct"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/cerebras", options: { name: "custom-cerebras", apiKey: "test" }, @@ -95,11 +94,9 @@ describe("CerebrasPlugin", () => { ProviderV2.ID.make("custom-cerebras"), ModelV2.ID.make("llama-4-scout-17b-16e-instruct"), ), - api: { - id: ModelV2.ID.make("llama-4-scout-17b-16e-instruct"), - type: "aisdk", - package: "test-provider", - }, + modelID: ModelV2.ID.make("llama-4-scout-17b-16e-instruct"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/cerebras", options: { name: "configured-cerebras", apiKey: "test" }, @@ -120,11 +117,9 @@ describe("CerebrasPlugin", () => { ProviderV2.ID.make("custom-cerebras"), ModelV2.ID.make("llama-4-scout-17b-16e-instruct"), ), - api: { - id: ModelV2.ID.make("llama-4-scout-17b-16e-instruct"), - type: "aisdk", - package: "test-provider", - }, + modelID: ModelV2.ID.make("llama-4-scout-17b-16e-instruct"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/groq", options: { name: "custom-cerebras", apiKey: "test" }, diff --git a/packages/core/test/plugin/provider-cloudflare-ai-gateway.test.ts b/packages/core/test/plugin/provider-cloudflare-ai-gateway.test.ts index 008b9a46a3..7259f7ed56 100644 --- a/packages/core/test/plugin/provider-cloudflare-ai-gateway.test.ts +++ b/packages/core/test/plugin/provider-cloudflare-ai-gateway.test.ts @@ -118,7 +118,9 @@ describe("CloudflareAIGatewayPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")), - api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("openai/gpt-5"), + aisdk: true, + package: "test-provider", }), package: "ai-gateway-provider", options: { name: "cloudflare-ai-gateway" }, @@ -139,7 +141,9 @@ describe("CloudflareAIGatewayPlugin", () => { yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")), - api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("openai/gpt-5"), + aisdk: true, + package: "test-provider", }), package: "ai-gateway-provider", options: { @@ -183,7 +187,9 @@ describe("CloudflareAIGatewayPlugin", () => { yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")), - api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("openai/gpt-5"), + aisdk: true, + package: "test-provider", }), package: "ai-gateway-provider", options: { @@ -212,7 +218,9 @@ describe("CloudflareAIGatewayPlugin", () => { yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")), - api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("openai/gpt-5"), + aisdk: true, + package: "test-provider", }), package: "ai-gateway-provider", options: { @@ -249,7 +257,9 @@ describe("CloudflareAIGatewayPlugin", () => { yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")), - api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("openai/gpt-5"), + aisdk: true, + package: "test-provider", }), package: "ai-gateway-provider", options: { @@ -280,7 +290,9 @@ describe("CloudflareAIGatewayPlugin", () => { yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")), - api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("openai/gpt-5"), + aisdk: true, + package: "test-provider", }), package: "ai-gateway-provider", options: { name: "cloudflare-ai-gateway" }, @@ -302,7 +314,9 @@ describe("CloudflareAIGatewayPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")), - api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("openai/gpt-5"), + aisdk: true, + package: "test-provider", }), package: "ai-gateway-provider", options: { name: "cloudflare-ai-gateway" }, @@ -325,7 +339,9 @@ describe("CloudflareAIGatewayPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")), - api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("openai/gpt-5"), + aisdk: true, + package: "test-provider", }), package: "ai-gateway-provider", options: { name: "cloudflare-ai-gateway" }, @@ -354,7 +370,9 @@ describe("CloudflareAIGatewayPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")), - api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("openai/gpt-5"), + aisdk: true, + package: "test-provider", }), package: "ai-gateway-provider", options: { name: "cloudflare-ai-gateway", baseURL: "https://proxy.example/v1" }, @@ -380,11 +398,9 @@ describe("CloudflareAIGatewayPlugin", () => { ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("anthropic/claude-sonnet-4-5"), ), - api: { - id: ModelV2.ID.make("anthropic/claude-sonnet-4-5"), - type: "aisdk", - package: "test-provider", - }, + modelID: ModelV2.ID.make("anthropic/claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "ai-gateway-provider", options: { name: "cloudflare-ai-gateway" }, @@ -412,7 +428,9 @@ describe("CloudflareAIGatewayPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")), - api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("openai/gpt-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "cloudflare-ai-gateway" }, 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 d1eeaa581e..2eeb89caf2 100644 --- a/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts +++ b/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts @@ -87,7 +87,8 @@ describe("CloudflareWorkersAIPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => { - provider.api = { type: "aisdk", package: "test-provider" } + provider.aisdk = true + provider.package = "test-provider" }), ) yield* addPlugin() @@ -95,15 +96,18 @@ describe("CloudflareWorkersAIPlugin", () => { const sdk = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")), - api: { id: ModelV2.ID.make("@cf/model"), ...provider.api }, + modelID: ModelV2.ID.make("@cf/model"), + aisdk: provider.aisdk, + package: provider.package, + settings: provider.settings, }), package: "@ai-sdk/openai-compatible", options: { name: "cloudflare-workers-ai", headers: { custom: "header" } }, }) - expect(provider.api).toEqual({ - type: "aisdk", + expect(provider).toMatchObject({ + aisdk: true, package: "test-provider", - url: "https://api.cloudflare.com/client/v4/accounts/acct/ai/v1", + settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/acct/ai/v1" }, }) expect(sdk.sdk).toBeDefined() }), @@ -116,14 +120,16 @@ describe("CloudflareWorkersAIPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => { - provider.api = { type: "aisdk", package: "test-provider", url: "https://proxy.example/v1" } + provider.aisdk = true + provider.package = "test-provider" + provider.settings = { ...provider.settings, baseURL: "https://proxy.example/v1" } }), ) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).api).toEqual({ - type: "aisdk", + expect(required(yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai")))).toMatchObject({ + aisdk: true, package: "test-provider", - url: "https://proxy.example/v1", + settings: { baseURL: "https://proxy.example/v1" }, }) }), ), @@ -138,12 +144,10 @@ describe("CloudflareWorkersAIPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")), - api: { - id: ModelV2.ID.make("@cf/model"), - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://proxy.example/v1", - }, + modelID: ModelV2.ID.make("@cf/model"), + aisdk: true, + package: "@ai-sdk/openai-compatible", + settings: { baseURL: "https://proxy.example/v1" }, }), package: "@ai-sdk/openai-compatible", options: { name: "cloudflare-workers-ai", baseURL: "https://proxy.example/v1" }, @@ -159,15 +163,16 @@ describe("CloudflareWorkersAIPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => { - provider.api = { type: "aisdk", package: "test-provider" } - provider.request.body.accountId = "configured-acct" + provider.aisdk = true + provider.package = "test-provider" + provider.settings = { ...provider.settings, accountId: "configured-acct" } }), ) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).api).toEqual({ - type: "aisdk", + expect(required(yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai")))).toMatchObject({ + aisdk: true, package: "test-provider", - url: "https://api.cloudflare.com/client/v4/accounts/env-acct/ai/v1", + settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/env-acct/ai/v1" }, }) }), ), @@ -182,12 +187,10 @@ describe("CloudflareWorkersAIPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")), - api: { - id: ModelV2.ID.make("@cf/model"), - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://proxy.example/v1", - }, + modelID: ModelV2.ID.make("@cf/model"), + aisdk: true, + package: "@ai-sdk/openai-compatible", + settings: { baseURL: "https://proxy.example/v1" }, }), package: "@ai-sdk/openai-compatible", options: { @@ -214,12 +217,10 @@ describe("CloudflareWorkersAIPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")), - api: { - id: ModelV2.ID.make("@cf/model"), - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1", - }, + modelID: ModelV2.ID.make("@cf/model"), + aisdk: true, + package: "@ai-sdk/openai-compatible", + settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1" }, }), package: "@ai-sdk/openai-compatible", options: { @@ -243,7 +244,9 @@ describe("CloudflareWorkersAIPlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("@cf/api-model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("@cf/api-model"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -262,12 +265,10 @@ describe("CloudflareWorkersAIPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")), - api: { - id: ModelV2.ID.make("@cf/model"), - type: "aisdk", - package: "@ai-sdk/anthropic", - url: "https://proxy.example/v1", - }, + modelID: ModelV2.ID.make("@cf/model"), + aisdk: true, + package: "@ai-sdk/anthropic", + settings: { baseURL: "https://proxy.example/v1" }, }), package: "@ai-sdk/anthropic", options: { name: "cloudflare-workers-ai" }, diff --git a/packages/core/test/plugin/provider-cohere.test.ts b/packages/core/test/plugin/provider-cohere.test.ts index c001ad935f..fea89915bf 100644 --- a/packages/core/test/plugin/provider-cohere.test.ts +++ b/packages/core/test/plugin/provider-cohere.test.ts @@ -56,7 +56,9 @@ describe("CoherePlugin", () => { const ignored = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cohere"), ModelV2.ID.make("command")), - api: { id: ModelV2.ID.make("command"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("command"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "cohere" }, @@ -66,7 +68,9 @@ describe("CoherePlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cohere"), ModelV2.ID.make("command")), - api: { id: ModelV2.ID.make("command"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("command"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/cohere", options: { name: "cohere" }, @@ -83,7 +87,9 @@ describe("CoherePlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-cohere"), ModelV2.ID.make("command-r-plus")), - api: { id: ModelV2.ID.make("command-r-plus"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("command-r-plus"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/cohere", options: { name: "custom-cohere", apiKey: "test", baseURL: "https://cohere.example" }, @@ -108,7 +114,9 @@ describe("CoherePlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cohere"), ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("command-r-plus"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("command-r-plus"), + aisdk: true, + package: "test-provider", }), sdk, options: {}, diff --git a/packages/core/test/plugin/provider-deepinfra.test.ts b/packages/core/test/plugin/provider-deepinfra.test.ts index 9cb9a4866c..6d1f74157d 100644 --- a/packages/core/test/plugin/provider-deepinfra.test.ts +++ b/packages/core/test/plugin/provider-deepinfra.test.ts @@ -48,7 +48,9 @@ describe("DeepInfraPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("deepinfra"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "@ai-sdk/deepinfra" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "@ai-sdk/deepinfra", }), package: "@ai-sdk/deepinfra", options: { name: "deepinfra" }, @@ -66,7 +68,9 @@ describe("DeepInfraPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-deepinfra"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "@ai-sdk/deepinfra" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "@ai-sdk/deepinfra", }), package: "@ai-sdk/deepinfra", options: { name: "custom-deepinfra", apiKey: "test" }, @@ -85,7 +89,9 @@ describe("DeepInfraPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("deepinfra"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "@ai-sdk/deepinfra" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "@ai-sdk/deepinfra", }), package: "@ai-sdk/deepinfra", options: { name: "deepinfra", apiKey: "test" }, @@ -111,7 +117,9 @@ describe("DeepInfraPlugin", () => { const ignored = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("deepinfra"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "@ai-sdk/deepinfra" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "@ai-sdk/deepinfra", }), package: item, options: { name: "deepinfra" }, @@ -122,7 +130,9 @@ describe("DeepInfraPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("deepinfra"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "@ai-sdk/deepinfra" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "@ai-sdk/deepinfra", }), package: "@ai-sdk/deepinfra", options: { name: "deepinfra" }, @@ -141,17 +151,15 @@ describe("DeepInfraPlugin", () => { const sdkEvent = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("deepinfra"), ModelV2.ID.make("meta-llama/Llama-3.3-70B-Instruct")), - api: { - id: ModelV2.ID.make("meta-llama/Llama-3.3-70B-Instruct"), - type: "aisdk", - package: "@ai-sdk/deepinfra", - }, + modelID: ModelV2.ID.make("meta-llama/Llama-3.3-70B-Instruct"), + aisdk: true, + package: "@ai-sdk/deepinfra", }), package: "@ai-sdk/deepinfra", options: { name: "deepinfra" }, }) const result = yield* aisdk.runLanguage({ model: sdkEvent.model, sdk: sdkEvent.sdk, options: sdkEvent.options }) - const language = result.language ?? result.sdk.languageModel(result.model.api.id) + const language = result.language ?? result.sdk.languageModel(result.model.modelID ?? result.model.id) expect(language.provider).toBe("deepinfra.chat") expect(deepinfraLanguageModels).toEqual(["meta-llama/Llama-3.3-70B-Instruct"]) }), diff --git a/packages/core/test/plugin/provider-dynamic.test.ts b/packages/core/test/plugin/provider-dynamic.test.ts index 3ba6e5aae4..68509bce9a 100644 --- a/packages/core/test/plugin/provider-dynamic.test.ts +++ b/packages/core/test/plugin/provider-dynamic.test.ts @@ -53,7 +53,9 @@ describe("DynamicProviderPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("test-model")), - api: { id: ModelV2.ID.make("test-model"), type: "aisdk", package: fixtureProvider }, + modelID: ModelV2.ID.make("test-model"), + aisdk: true, + package: fixtureProvider, }), package: fixtureProvider, options: { name: "custom", marker: "dynamic" }, @@ -71,7 +73,9 @@ describe("DynamicProviderPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("test-model")), - api: { id: ModelV2.ID.make("test-model"), type: "aisdk", package: fixtureProvider }, + modelID: ModelV2.ID.make("test-model"), + aisdk: true, + package: fixtureProvider, }), package: fixtureProvider, options: { name: "custom", marker: "dynamic" }, @@ -88,7 +92,9 @@ describe("DynamicProviderPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-provider"), ModelV2.ID.make("test-model")), - api: { id: ModelV2.ID.make("test-model"), type: "aisdk", package: fixtureProvider }, + modelID: ModelV2.ID.make("test-model"), + aisdk: true, + package: fixtureProvider, }), package: fixtureProvider, options: { name: "custom-provider", marker: "dynamic" }, @@ -104,7 +110,9 @@ describe("DynamicProviderPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("npm-provider"), ModelV2.ID.make("test-model")), - api: { id: ModelV2.ID.make("test-model"), type: "aisdk", package: "fixture-provider" }, + modelID: ModelV2.ID.make("test-model"), + aisdk: true, + package: "fixture-provider", }), package: "fixture-provider", options: { name: "npm-provider", marker: "npm" }, @@ -121,7 +129,9 @@ describe("DynamicProviderPlugin", () => { .language( ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("missing-entrypoint"), ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("alias"), type: "aisdk", package: "fixture-provider" }, + modelID: ModelV2.ID.make("alias"), + aisdk: true, + package: "fixture-provider", }), ) .pipe(Effect.exit) @@ -138,7 +148,9 @@ describe("DynamicProviderPlugin", () => { .language( ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("bad-import"), ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("alias"), type: "aisdk", package: "file:///missing/provider-factory.js" }, + modelID: ModelV2.ID.make("alias"), + aisdk: true, + package: "file:///missing/provider-factory.js", }), ) .pipe(Effect.exit) @@ -157,7 +169,9 @@ describe("DynamicProviderPlugin", () => { .language( ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("missing-factory"), ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("alias"), type: "aisdk", package: "fixture-provider" }, + modelID: ModelV2.ID.make("alias"), + aisdk: true, + package: "fixture-provider", }), ) .pipe(Effect.exit) @@ -166,7 +180,7 @@ describe("DynamicProviderPlugin", () => { }), ) - itWithAISDK.effect("uses the model api.id for the default language model", () => + itWithAISDK.effect("uses the model modelID for the default language model", () => Effect.gen(function* () { const plugin = yield* PluginV2.Service const aisdk = yield* AISDK.Service @@ -174,7 +188,9 @@ describe("DynamicProviderPlugin", () => { const language = yield* aisdk.language( ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("test-model-api"), type: "aisdk", package: fixtureProvider }, + modelID: ModelV2.ID.make("test-model-api"), + aisdk: true, + package: fixtureProvider, }), ) expect(language).toMatchObject({ modelID: "test-model-api", options: { name: "custom" } }) diff --git a/packages/core/test/plugin/provider-gateway.test.ts b/packages/core/test/plugin/provider-gateway.test.ts index c6a0427cdb..2c49e1a026 100644 --- a/packages/core/test/plugin/provider-gateway.test.ts +++ b/packages/core/test/plugin/provider-gateway.test.ts @@ -45,7 +45,9 @@ describe("GatewayPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("gateway"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/gateway", options: { name: "gateway" }, @@ -65,11 +67,9 @@ describe("GatewayPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("vercel"), ModelV2.ID.make("anthropic/claude-sonnet-4")), - api: { - id: ModelV2.ID.make("anthropic/claude-sonnet-4"), - type: "aisdk", - package: "test-provider", - }, + modelID: ModelV2.ID.make("anthropic/claude-sonnet-4"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/gateway", options: { name: "vercel", apiKey: "test-key" }, @@ -91,7 +91,9 @@ describe("GatewayPlugin", () => { const ignored = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("vercel"), ModelV2.ID.make(modelID)), - api: { id: ModelV2.ID.make(modelID), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make(modelID), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/vercel", options: { name: "vercel" }, @@ -101,7 +103,9 @@ describe("GatewayPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("vercel"), ModelV2.ID.make(modelID)), - api: { id: ModelV2.ID.make(modelID), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make(modelID), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/gateway", options: { name: "vercel" }, diff --git a/packages/core/test/plugin/provider-github-copilot.test.ts b/packages/core/test/plugin/provider-github-copilot.test.ts index beef89ae8c..44bea74ea4 100644 --- a/packages/core/test/plugin/provider-github-copilot.test.ts +++ b/packages/core/test/plugin/provider-github-copilot.test.ts @@ -47,7 +47,9 @@ describe("GithubCopilotPlugin", () => { const ignored = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")), - api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "github-copilot" }, @@ -55,7 +57,9 @@ describe("GithubCopilotPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")), - api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/github-copilot", options: { name: "github-copilot" }, @@ -74,7 +78,9 @@ describe("GithubCopilotPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("claude-sonnet-4")), - api: { id: ModelV2.ID.make("claude-sonnet-4"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude-sonnet-4"), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, options: {}, @@ -92,7 +98,9 @@ describe("GithubCopilotPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("claude-sonnet-4"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude-sonnet-4"), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, options: {}, @@ -110,7 +118,9 @@ describe("GithubCopilotPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")), - api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-5"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -118,7 +128,9 @@ describe("GithubCopilotPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5.1-codex")), - api: { id: ModelV2.ID.make("gpt-5.1-codex"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-5.1-codex"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -126,7 +138,9 @@ describe("GithubCopilotPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-4o")), - api: { id: ModelV2.ID.make("gpt-4o"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-4o"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -134,7 +148,9 @@ describe("GithubCopilotPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-mini")), - api: { id: ModelV2.ID.make("gpt-5-mini"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-5-mini"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -142,7 +158,9 @@ describe("GithubCopilotPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-mini-2025-08-07")), - api: { id: ModelV2.ID.make("gpt-5-mini-2025-08-07"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-5-mini-2025-08-07"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -166,7 +184,9 @@ describe("GithubCopilotPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("default")), - api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-5"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -174,7 +194,9 @@ describe("GithubCopilotPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("small")), - api: { id: ModelV2.ID.make("gpt-5-mini"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-5-mini"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -182,7 +204,9 @@ describe("GithubCopilotPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("sonnet")), - api: { id: ModelV2.ID.make("claude-sonnet-4"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude-sonnet-4"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -230,7 +254,9 @@ describe("GithubCopilotPlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("openai"), ModelV2.ID.make("gpt-5")), - api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-5"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, diff --git a/packages/core/test/plugin/provider-gitlab.test.ts b/packages/core/test/plugin/provider-gitlab.test.ts index f9880844d3..6c72c65301 100644 --- a/packages/core/test/plugin/provider-gitlab.test.ts +++ b/packages/core/test/plugin/provider-gitlab.test.ts @@ -70,7 +70,9 @@ describe("GitLabPlugin", () => { yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")), - api: { id: ModelV2.ID.make("claude"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude"), + aisdk: true, + package: "test-provider", }), package: "gitlab-ai-provider", options: { name: "gitlab" }, @@ -107,7 +109,9 @@ describe("GitLabPlugin", () => { yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")), - api: { id: ModelV2.ID.make("claude"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude"), + aisdk: true, + package: "test-provider", }), package: "gitlab-ai-provider", options: { name: "gitlab" }, @@ -132,7 +136,9 @@ describe("GitLabPlugin", () => { yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")), - api: { id: ModelV2.ID.make("claude"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude"), + aisdk: true, + package: "test-provider", }), package: "gitlab-ai-provider", options: { @@ -173,7 +179,9 @@ describe("GitLabPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")), - api: { id: ModelV2.ID.make("claude"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai", options: { name: "gitlab" }, @@ -192,11 +200,11 @@ describe("GitLabPlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-custom")), - api: { id: ModelV2.ID.make("duo-workflow-custom"), type: "aisdk", package: "test-provider" }, - request: { - headers: {}, - body: { workflowRef: "ref", workflowDefinition: "definition" }, - }, + modelID: ModelV2.ID.make("duo-workflow-custom"), + aisdk: true, + package: "test-provider", + headers: {}, + settings: { workflowRef: "ref", workflowDefinition: "definition" }, }), sdk: { workflowChat: (id: string, options: unknown) => { @@ -227,7 +235,9 @@ describe("GitLabPlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-exact")), - api: { id: ModelV2.ID.make("duo-workflow-exact"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("duo-workflow-exact"), + aisdk: true, + package: "test-provider", }), sdk: { workflowChat: (id: string, options: unknown) => { @@ -245,7 +255,7 @@ describe("GitLabPlugin", () => { }), ) - it.effect("uses provider feature flags instead of request feature flags", () => + it.effect("uses provider feature flags instead of model settings feature flags", () => Effect.gen(function* () { const plugin = yield* PluginV2.Service const aisdk = yield* AISDK.Service @@ -254,11 +264,11 @@ describe("GitLabPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-custom")), - api: { id: ModelV2.ID.make("duo-workflow-custom"), type: "aisdk", package: "test-provider" }, - request: { - headers: {}, - body: { featureFlags: { request_flag: true } }, - }, + modelID: ModelV2.ID.make("duo-workflow-custom"), + aisdk: true, + package: "test-provider", + headers: {}, + settings: { featureFlags: { request_flag: true } }, }), sdk: { workflowChat: (id: string, options: unknown) => { @@ -282,8 +292,11 @@ describe("GitLabPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")), - api: { id: ModelV2.ID.make("claude"), type: "aisdk", package: "test-provider" }, - request: { headers: { h: "v" }, body: {} }, + modelID: ModelV2.ID.make("claude"), + aisdk: true, + package: "test-provider", + headers: { h: "v" }, + settings: {}, }), sdk: { workflowChat: () => undefined, diff --git a/packages/core/test/plugin/provider-google-vertex-anthropic.test.ts b/packages/core/test/plugin/provider-google-vertex-anthropic.test.ts index d94321f3bd..8afb38b1f8 100644 --- a/packages/core/test/plugin/provider-google-vertex-anthropic.test.ts +++ b/packages/core/test/plugin/provider-google-vertex-anthropic.test.ts @@ -64,16 +64,17 @@ describe("GoogleVertexAnthropicPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("google-vertex-anthropic"), (provider) => { - provider.api = { type: "aisdk", package: "@ai-sdk/google-vertex/anthropic" } + provider.aisdk = true + provider.package = "@ai-sdk/google-vertex/anthropic" }), ) yield* addPlugin(GoogleVertexAnthropicPlugin) - expect( - (yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")))?.request.body.project, - ).toBe("cloud-project") - expect( - (yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")))?.request.body.location, - ).toBe("cloud-location") + expect((yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")))?.settings?.project).toBe( + "cloud-project", + ) + expect((yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")))?.settings?.location).toBe( + "cloud-location", + ) }), ), ) @@ -84,18 +85,18 @@ describe("GoogleVertexAnthropicPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("google-vertex-anthropic"), (provider) => { - provider.api = { type: "aisdk", package: "@ai-sdk/google-vertex/anthropic" } - provider.request.body.project = "configured-project" - provider.request.body.location = "configured-location" + provider.aisdk = true + provider.package = "@ai-sdk/google-vertex/anthropic" + provider.settings = { ...provider.settings, project: "configured-project", location: "configured-location" } }), ) yield* addPlugin(GoogleVertexAnthropicPlugin) - expect((yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")))?.request.body.project).toBe( + expect((yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")))?.settings?.project).toBe( "configured-project", ) - expect( - (yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")))?.request.body.location, - ).toBe("configured-location") + expect((yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")))?.settings?.location).toBe( + "configured-location", + ) }), ), ) @@ -121,7 +122,9 @@ describe("GoogleVertexAnthropicPlugin", () => { ProviderV2.ID.make("google-vertex-anthropic"), ModelV2.ID.make("claude-sonnet-4-5"), ), - api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/google-vertex/anthropic", options: { name: "google-vertex-anthropic" }, @@ -147,7 +150,9 @@ describe("GoogleVertexAnthropicPlugin", () => { ProviderV2.ID.make("google-vertex-anthropic"), ModelV2.ID.make("claude-sonnet-4-5"), ), - api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/google-vertex/anthropic", options: { name: "google-vertex-anthropic" }, @@ -167,7 +172,9 @@ describe("GoogleVertexAnthropicPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/google-vertex/anthropic", options: { name: "google-vertex", project: "project", location: "eu" }, @@ -186,7 +193,9 @@ describe("GoogleVertexAnthropicPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/google-vertex/anthropic", options: { name: "google-vertex", project: "project", location: "eu", baseURL: "https://proxy.example/v1" }, @@ -204,7 +213,9 @@ describe("GoogleVertexAnthropicPlugin", () => { const sdkResult = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" claude-sonnet-4-5 ")), - api: { id: ModelV2.ID.make(" claude-sonnet-4-5 "), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make(" claude-sonnet-4-5 "), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/google-vertex/anthropic", options: { name: "google-vertex", project: "project", location: "us" }, @@ -212,7 +223,9 @@ describe("GoogleVertexAnthropicPlugin", () => { const languageResult = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" claude-sonnet-4-5 ")), - api: { id: ModelV2.ID.make(" claude-sonnet-4-5 "), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make(" claude-sonnet-4-5 "), + aisdk: true, + package: "test-provider", }), sdk: sdkResult.sdk, options: {}, @@ -234,7 +247,9 @@ describe("GoogleVertexAnthropicPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex-anthropic"), ModelV2.ID.make(" claude-sonnet-4-5 ")), - api: { id: ModelV2.ID.make(" claude-sonnet-4-5 "), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make(" claude-sonnet-4-5 "), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: selector(calls) }, options: {}, @@ -252,7 +267,9 @@ describe("GoogleVertexAnthropicPlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")), - api: { id: ModelV2.ID.make("claude-sonnet-4-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude-sonnet-4-5"), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: selector(calls) }, options: {}, diff --git a/packages/core/test/plugin/provider-google-vertex.test.ts b/packages/core/test/plugin/provider-google-vertex.test.ts index 388528bd65..026bcd0282 100644 --- a/packages/core/test/plugin/provider-google-vertex.test.ts +++ b/packages/core/test/plugin/provider-google-vertex.test.ts @@ -92,17 +92,15 @@ describe("GoogleVertexPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.opencode, (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://opencode.ai/zen/v1", - } + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { ...provider.settings, baseURL: "https://opencode.ai/zen/v1" } }), ) yield* addPlugin() const provider = required(yield* catalog.provider.get(ProviderV2.ID.opencode)) - expect(provider.request.body).toEqual({}) + expect(provider.settings).toEqual({ baseURL: "https://opencode.ai/zen/v1" }) }), ) @@ -121,21 +119,26 @@ describe("GoogleVertexPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}", + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { + ...provider.settings, + baseURL: + "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}", } }), ) yield* addPlugin() const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))) - expect(provider.request.body.project).toBe("google-cloud-project") - expect(provider.request.body.location).toBe("google-vertex-location") - expect(provider.api).toEqual({ - type: "aisdk", + expect(provider.settings?.project).toBe("google-cloud-project") + expect(provider.settings?.location).toBe("google-vertex-location") + expect(provider).toMatchObject({ + aisdk: true, package: "@ai-sdk/openai-compatible", - url: "https://google-vertex-location-aiplatform.googleapis.com/v1/projects/google-cloud-project/locations/google-vertex-location", + settings: { + baseURL: + "https://google-vertex-location-aiplatform.googleapis.com/v1/projects/google-cloud-project/locations/google-vertex-location", + }, }) }), ), @@ -160,10 +163,12 @@ describe("GoogleVertexPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}", + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { + ...provider.settings, + baseURL: + "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}", } }), ) @@ -172,21 +177,22 @@ describe("GoogleVertexPlugin", () => { yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")), - api: { - id: ModelV2.ID.make("gemini"), - type: "aisdk", - package: "@ai-sdk/google-vertex", - }, + modelID: ModelV2.ID.make("gemini"), + aisdk: true, + package: "@ai-sdk/google-vertex", }), package: "@ai-sdk/google-vertex", options: { name: "google-vertex" }, }) - expect(provider.request.body.project).toBe("vertex-project") - expect(provider.api).toEqual({ - type: "aisdk", + expect(provider.settings?.project).toBe("vertex-project") + expect(provider).toMatchObject({ + aisdk: true, package: "@ai-sdk/openai-compatible", - url: "https://europe-west4-aiplatform.googleapis.com/v1/projects/vertex-project/locations/europe-west4", + settings: { + baseURL: + "https://europe-west4-aiplatform.googleapis.com/v1/projects/vertex-project/locations/europe-west4", + }, }) expect(vertexOptions[0].project).toBe("vertex-project") expect(vertexOptions[0].location).toBe("europe-west4") @@ -209,23 +215,24 @@ describe("GoogleVertexPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}", + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { + ...provider.settings, + baseURL: + "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}", } - provider.request.body.project = "config-project" - provider.request.body.location = "global" + provider.settings = { ...provider.settings, project: "config-project", location: "global" } }), ) yield* addPlugin() const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))) - expect(provider.request.body.project).toBe("config-project") - expect(provider.request.body.location).toBe("global") - expect(provider.api).toEqual({ - type: "aisdk", + expect(provider.settings?.project).toBe("config-project") + expect(provider.settings?.location).toBe("global") + expect(provider).toMatchObject({ + aisdk: true, package: "@ai-sdk/openai-compatible", - url: "https://aiplatform.googleapis.com/v1/projects/config-project/locations/global", + settings: { baseURL: "https://aiplatform.googleapis.com/v1/projects/config-project/locations/global" }, }) }), ), @@ -236,21 +243,22 @@ describe("GoogleVertexPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}", + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { + ...provider.settings, + baseURL: + "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}", } - provider.request.body.project = "config-project" - provider.request.body.location = "eu" + provider.settings = { ...provider.settings, project: "config-project", location: "eu" } }), ) yield* addPlugin() const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))) - expect(provider.api).toEqual({ - type: "aisdk", + expect(provider).toMatchObject({ + aisdk: true, package: "@ai-sdk/openai-compatible", - url: "https://eu-aiplatform.googleapis.com/v1/projects/config-project/locations/eu", + settings: { baseURL: "https://eu-aiplatform.googleapis.com/v1/projects/config-project/locations/eu" }, }) }), ) @@ -270,14 +278,15 @@ describe("GoogleVertexPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => { - provider.api = { type: "aisdk", package: "@ai-sdk/google-vertex" } - provider.request.body.project = "config-project" + provider.aisdk = true + provider.package = "@ai-sdk/google-vertex" + provider.settings = { ...provider.settings, project: "config-project" } }), ) yield* addPlugin() const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))) - expect(provider.request.body.project).toBe("config-project") - expect(provider.request.body.location).toBe("us-central1") + expect(provider.settings?.project).toBe("config-project") + expect(provider.settings?.location).toBe("us-central1") }), ), ) @@ -297,11 +306,9 @@ describe("GoogleVertexPlugin", () => { yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")), - api: { - id: ModelV2.ID.make("gemini"), - type: "aisdk", - package: "@ai-sdk/google-vertex", - }, + modelID: ModelV2.ID.make("gemini"), + aisdk: true, + package: "@ai-sdk/google-vertex", }), package: "@ai-sdk/google-vertex", options: { name: "google-vertex" }, @@ -345,11 +352,9 @@ describe("GoogleVertexPlugin", () => { aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")), - api: { - id: ModelV2.ID.make("gemini"), - type: "aisdk", - package: "@ai-sdk/openai-compatible", - }, + modelID: ModelV2.ID.make("gemini"), + aisdk: true, + package: "@ai-sdk/openai-compatible", }), package: "@ai-sdk/openai-compatible", options: { name: "google-vertex" }, @@ -376,7 +381,9 @@ describe("GoogleVertexPlugin", () => { yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" gemini-2.5-pro ")), - api: { id: ModelV2.ID.make(" gemini-2.5-pro "), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make(" gemini-2.5-pro "), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, options: {}, diff --git a/packages/core/test/plugin/provider-google.test.ts b/packages/core/test/plugin/provider-google.test.ts index 8c10689ca1..f2022d31d1 100644 --- a/packages/core/test/plugin/provider-google.test.ts +++ b/packages/core/test/plugin/provider-google.test.ts @@ -27,7 +27,9 @@ describe("GooglePlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("gemini")), - api: { id: ModelV2.ID.make("gemini"), type: "aisdk", package: "@ai-sdk/google" }, + modelID: ModelV2.ID.make("gemini"), + aisdk: true, + package: "@ai-sdk/google", }), package: "@ai-sdk/google", options: { name: "custom-google", apiKey: "test" }, @@ -45,7 +47,9 @@ describe("GooglePlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("google"), ModelV2.ID.make("gemini")), - api: { id: ModelV2.ID.make("gemini"), type: "aisdk", package: "@ai-sdk/google" }, + modelID: ModelV2.ID.make("gemini"), + aisdk: true, + package: "@ai-sdk/google", }), package: "@ai-sdk/google-vertex", options: { name: "google" }, @@ -62,7 +66,9 @@ describe("GooglePlugin", () => { const sdkEvent = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("gemini-api"), type: "aisdk", package: "@ai-sdk/google" }, + modelID: ModelV2.ID.make("gemini-api"), + aisdk: true, + package: "@ai-sdk/google", }), package: "@ai-sdk/google", options: { name: "custom-google", apiKey: "test" }, @@ -72,7 +78,7 @@ describe("GooglePlugin", () => { sdk: sdkEvent.sdk, options: sdkEvent.options, }) - const language = result.language ?? result.sdk.languageModel(result.model.api.id) + const language = result.language ?? result.sdk.languageModel(result.model.modelID ?? result.model.id) expect(language.modelId).toBe("gemini-api") expect(language.provider).toBe("custom-google") }), diff --git a/packages/core/test/plugin/provider-groq.test.ts b/packages/core/test/plugin/provider-groq.test.ts index ac96ea98a3..2e30264352 100644 --- a/packages/core/test/plugin/provider-groq.test.ts +++ b/packages/core/test/plugin/provider-groq.test.ts @@ -28,7 +28,9 @@ describe("GroqPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("groq"), ModelV2.ID.make("llama")), - api: { id: ModelV2.ID.make("llama"), type: "aisdk", package: "@ai-sdk/groq" }, + modelID: ModelV2.ID.make("llama"), + aisdk: true, + package: "@ai-sdk/groq", }), package: "@ai-sdk/groq", options: { name: "groq" }, @@ -45,7 +47,9 @@ describe("GroqPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("groq"), ModelV2.ID.make("llama")), - api: { id: ModelV2.ID.make("llama"), type: "aisdk", package: "@ai-sdk/groq" }, + modelID: ModelV2.ID.make("llama"), + aisdk: true, + package: "@ai-sdk/groq", }), package: "@ai-sdk/openai-compatible", options: { name: "groq" }, @@ -62,7 +66,9 @@ describe("GroqPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("groq"), ModelV2.ID.make("llama")), - api: { id: ModelV2.ID.make("llama"), type: "aisdk", package: "@ai-sdk/groq" }, + modelID: ModelV2.ID.make("llama"), + aisdk: true, + package: "@ai-sdk/groq", }), package: "@ai-sdk/groq/compat", options: { name: "groq" }, @@ -79,7 +85,9 @@ describe("GroqPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-groq"), ModelV2.ID.make("llama")), - api: { id: ModelV2.ID.make("llama"), type: "aisdk", package: "@ai-sdk/groq" }, + modelID: ModelV2.ID.make("llama"), + aisdk: true, + package: "@ai-sdk/groq", }), package: "@ai-sdk/groq", options: { name: "custom-groq", apiKey: "test" }, @@ -93,7 +101,7 @@ describe("GroqPlugin", () => { }), ) - it.effect("uses the default languageModel(api.id) behavior", () => + it.effect("uses the default languageModel(modelID) behavior", () => Effect.gen(function* () { const plugin = yield* PluginV2.Service const aisdk = yield* AISDK.Service @@ -104,16 +112,14 @@ describe("GroqPlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("groq"), ModelV2.ID.make("alias")), - api: { - id: ModelV2.ID.make("llama-api"), - type: "aisdk", - package: "@ai-sdk/groq", - }, + modelID: ModelV2.ID.make("llama-api"), + aisdk: true, + package: "@ai-sdk/groq", }), sdk, options: { name: "groq", apiKey: "test" }, }) - const language = result.language ?? sdk.languageModel(result.model.api.id) + const language = result.language ?? sdk.languageModel(result.model.modelID ?? result.model.id) expect(language.modelId).toBe("llama-api") expect(language.provider).toBe("groq.chat") }), diff --git a/packages/core/test/plugin/provider-kilo.test.ts b/packages/core/test/plugin/provider-kilo.test.ts index 1df0fd3156..7ad6b00382 100644 --- a/packages/core/test/plugin/provider-kilo.test.ts +++ b/packages/core/test/plugin/provider-kilo.test.ts @@ -27,22 +27,20 @@ describe("KiloPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("kilo"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://api.kilo.ai/api/gateway", - } - provider.request = { headers: { Existing: "value" }, body: {} } + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { ...provider.settings, baseURL: "https://api.kilo.ai/api/gateway" } + provider.headers = { Existing: "value" } }) catalog.provider.update(ProviderV2.ID.openrouter, () => {}) }) yield* addPlugin() - expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.request.headers).toEqual({ + expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.headers).toEqual({ Existing: "value", "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode", }) - expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.request.headers).toEqual({}) + expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.headers).toBeUndefined() }), ) @@ -51,24 +49,20 @@ describe("KiloPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("kilo"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://api.kilo.ai/api/gateway", - } + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { ...provider.settings, baseURL: "https://api.kilo.ai/api/gateway" } }) }) yield* addPlugin() - expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.request.headers).toEqual({ + expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.headers).toEqual({ "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode", }) - expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.request.headers).not.toHaveProperty( - "http-referer", - ) - expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.request.headers).not.toHaveProperty("x-title") - expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.request.headers).not.toHaveProperty("X-Source") + expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.headers).not.toHaveProperty("http-referer") + expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.headers).not.toHaveProperty("x-title") + expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.headers).not.toHaveProperty("X-Source") }), ) @@ -77,23 +71,22 @@ describe("KiloPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("kilo"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://api.kilo.ai/api/gateway", - } + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { ...provider.settings, baseURL: "https://api.kilo.ai/api/gateway" } }) catalog.provider.update(ProviderV2.ID.make("custom-kilo"), (provider) => { - provider.api = { type: "aisdk", package: "kilo" } + provider.aisdk = true + provider.package = "kilo" }) }) yield* addPlugin() - expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.request.headers).toEqual({ + expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.headers).toEqual({ "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode", }) - expect((yield* catalog.provider.get(ProviderV2.ID.make("custom-kilo")))?.request.headers).toEqual({}) + expect((yield* catalog.provider.get(ProviderV2.ID.make("custom-kilo")))?.headers).toBeUndefined() }), ) }) diff --git a/packages/core/test/plugin/provider-llmgateway.test.ts b/packages/core/test/plugin/provider-llmgateway.test.ts index d7f9d0d73d..f0eb026aca 100644 --- a/packages/core/test/plugin/provider-llmgateway.test.ts +++ b/packages/core/test/plugin/provider-llmgateway.test.ts @@ -34,23 +34,21 @@ describe("LLMGatewayPlugin", () => { }) yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("llmgateway"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://api.llmgateway.io/v1", - } - provider.request = { headers: { Existing: "value" }, body: {} } + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { ...provider.settings, baseURL: "https://api.llmgateway.io/v1" } + provider.headers = { Existing: "value" } }) catalog.provider.update(ProviderV2.ID.openrouter, () => {}) }) yield* addPlugin() - expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway")))?.request.headers).toEqual({ + expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway")))?.headers).toEqual({ Existing: "value", "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode", "X-Source": "opencode", }) - expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.request.headers).toEqual({}) + expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.headers).toBeUndefined() }), ) @@ -64,17 +62,15 @@ describe("LLMGatewayPlugin", () => { yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("llmgateway"), (provider) => { provider.disabled = true - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://api.llmgateway.io/v1", - } + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { ...provider.settings, baseURL: "https://api.llmgateway.io/v1" } }) }) yield* addPlugin() expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway")))?.disabled).toBe(true) - expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway")))?.request.headers).toEqual({}) + expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway")))?.headers).toBeUndefined() }), ) }) diff --git a/packages/core/test/plugin/provider-mistral.test.ts b/packages/core/test/plugin/provider-mistral.test.ts index b1b1bc0638..5e549e3ec8 100644 --- a/packages/core/test/plugin/provider-mistral.test.ts +++ b/packages/core/test/plugin/provider-mistral.test.ts @@ -28,7 +28,9 @@ describe("MistralPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("mistral"), ModelV2.ID.make("mistral-large")), - api: { id: ModelV2.ID.make("mistral-large"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("mistral-large"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/mistral", options: { name: "mistral" }, @@ -45,7 +47,9 @@ describe("MistralPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("mistral"), ModelV2.ID.make("mistral-large")), - api: { id: ModelV2.ID.make("mistral-large"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("mistral-large"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "mistral" }, @@ -68,7 +72,9 @@ describe("MistralPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("mistral"), ModelV2.ID.make("mistral-large")), - api: { id: ModelV2.ID.make("mistral-large"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("mistral-large"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/mistral", options: { name: "mistral" }, @@ -92,7 +98,9 @@ describe("MistralPlugin", () => { yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-mistral"), ModelV2.ID.make("mistral-large")), - api: { id: ModelV2.ID.make("mistral-large"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("mistral-large"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/mistral", options: { name: "custom-mistral" }, @@ -101,7 +109,7 @@ describe("MistralPlugin", () => { }), ) - it.effect("leaves Mistral language selection on the default sdk.languageModel(api.id) path", () => + it.effect("leaves Mistral language selection on the default sdk.languageModel(modelID) path", () => Effect.gen(function* () { const plugin = yield* PluginV2.Service const aisdk = yield* AISDK.Service @@ -116,12 +124,14 @@ describe("MistralPlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("mistral"), ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("mistral-large"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("mistral-large"), + aisdk: true, + package: "test-provider", }), sdk, options: {}, }) - const language = result.language ?? sdk.languageModel(result.model.api.id) + const language = result.language ?? sdk.languageModel(result.model.modelID ?? result.model.id) expect(calls).toEqual(["languageModel:mistral-large"]) expect(language).toBeDefined() }), diff --git a/packages/core/test/plugin/provider-nvidia.test.ts b/packages/core/test/plugin/provider-nvidia.test.ts index a1c05df335..b1f051ab9d 100644 --- a/packages/core/test/plugin/provider-nvidia.test.ts +++ b/packages/core/test/plugin/provider-nvidia.test.ts @@ -27,23 +27,21 @@ describe("NvidiaPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("nvidia"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://integrate.api.nvidia.com/v1", - } - provider.request = { headers: { Existing: "value" }, body: {} } + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { ...provider.settings, baseURL: "https://integrate.api.nvidia.com/v1" } + provider.headers = { Existing: "value" } }) catalog.provider.update(ProviderV2.ID.openrouter, () => {}) }) yield* addPlugin() - expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.request.headers).toEqual({ + expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.headers).toEqual({ Existing: "value", "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode", "X-BILLING-INVOKE-ORIGIN": "OpenCode", }) - expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.request.headers).toEqual({}) + expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.headers).toBeUndefined() }), ) @@ -52,16 +50,14 @@ describe("NvidiaPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("nvidia"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://integrate.api.nvidia.com/v1", - } + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { ...provider.settings, baseURL: "https://integrate.api.nvidia.com/v1" } }) }) yield* addPlugin() - expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.request.headers).toEqual({ + expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.headers).toEqual({ "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode", "X-BILLING-INVOKE-ORIGIN": "OpenCode", @@ -74,20 +70,15 @@ describe("NvidiaPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("nvidia"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://integrate.api.nvidia.com/v1", - } - provider.request = { - headers: { "X-BILLING-INVOKE-ORIGIN": "CustomOrigin" }, - body: { baseURL: "https://integrate.api.nvidia.com/v1" }, - } + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { ...provider.settings, baseURL: "https://integrate.api.nvidia.com/v1" } + provider.headers = { "X-BILLING-INVOKE-ORIGIN": "CustomOrigin" } }) }) yield* addPlugin() - expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.request.headers).toEqual({ + expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.headers).toEqual({ "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode", "X-BILLING-INVOKE-ORIGIN": "CustomOrigin", diff --git a/packages/core/test/plugin/provider-openai-compatible.test.ts b/packages/core/test/plugin/provider-openai-compatible.test.ts index 4e74a37125..ac4e782cc6 100644 --- a/packages/core/test/plugin/provider-openai-compatible.test.ts +++ b/packages/core/test/plugin/provider-openai-compatible.test.ts @@ -27,7 +27,9 @@ describe("OpenAICompatiblePlugin", () => { const defaulted = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "custom" }, @@ -35,7 +37,9 @@ describe("OpenAICompatiblePlugin", () => { const disabled = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "custom", includeUsage: false }, @@ -53,7 +57,9 @@ describe("OpenAICompatiblePlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "file:///tmp/@ai-sdk/openai-compatible-provider.js", options: { name: "custom" }, @@ -76,7 +82,9 @@ describe("OpenAICompatiblePlugin", () => { yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-provider"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "custom-provider", baseURL: "https://example.com/v1" }, @@ -96,7 +104,9 @@ describe("OpenAICompatiblePlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "cloudflare-workers-ai" }, diff --git a/packages/core/test/plugin/provider-openai.test.ts b/packages/core/test/plugin/provider-openai.test.ts index 31a80f9319..c9866ea879 100644 --- a/packages/core/test/plugin/provider-openai.test.ts +++ b/packages/core/test/plugin/provider-openai.test.ts @@ -67,7 +67,9 @@ describe("OpenAIPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-openai"), ModelV2.ID.make("gpt-5")), - api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai", options: { name: "custom-openai", apiKey: "test" }, @@ -84,7 +86,9 @@ describe("OpenAIPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5")), - api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "openai" }, @@ -102,7 +106,9 @@ describe("OpenAIPlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-5"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -121,7 +127,9 @@ describe("OpenAIPlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.anthropic, ModelV2.ID.make("gpt-5")), - api: { id: ModelV2.ID.make("gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-5"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -137,10 +145,12 @@ describe("OpenAIPlugin", () => { yield* catalog.transform((catalog) => { const item = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.openai), - api: { type: "aisdk", package: "@ai-sdk/openai" }, + aisdk: true, + package: "@ai-sdk/openai", }) catalog.provider.update(item.id, (draft) => { - draft.api = item.api + draft.aisdk = item.aisdk + draft.package = item.package }) catalog.model.update(item.id, ModelV2.ID.make("gpt-5"), () => {}) catalog.model.update(item.id, ModelV2.ID.make("gpt-5-chat-latest"), () => {}) @@ -159,10 +169,12 @@ describe("OpenAIPlugin", () => { yield* catalog.transform((catalog) => { const item = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.make("custom-openai")), - api: { type: "aisdk", package: "test-provider" }, + aisdk: true, + package: "test-provider", }) catalog.provider.update(item.id, (draft) => { - draft.api = item.api + draft.aisdk = item.aisdk + draft.package = item.package }) catalog.model.update(item.id, ModelV2.ID.make("gpt-5-chat-latest"), () => {}) }) diff --git a/packages/core/test/plugin/provider-opencode.test.ts b/packages/core/test/plugin/provider-opencode.test.ts index a2d450d31b..3d8560b055 100644 --- a/packages/core/test/plugin/provider-opencode.test.ts +++ b/packages/core/test/plugin/provider-opencode.test.ts @@ -133,13 +133,12 @@ describe("OpencodePlugin", () => { expect(provider).toMatchObject({ name: "Remote", integrationID: "opencode", - api: { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: `${server.url.origin}/v1`, - }, + aisdk: true, + package: "@ai-sdk/openai-compatible", + settings: { baseURL: `${server.url.origin}/v1` }, }) - expect(provider.request).toEqual({ headers: { "x-org-id": "org" }, body: { custom: "value" } }) + expect(provider.headers).toEqual({ "x-org-id": "org" }) + expect(provider.settings).toMatchObject({ custom: "value" }) expect(yield* (yield* Integration.Service).get(Integration.ID.make("remote"))).toBeUndefined() const model = required(yield* catalog.model.get(ProviderV2.ID.make("remote"), ModelV2.ID.make("model"))) @@ -150,15 +149,16 @@ describe("OpencodePlugin", () => { cost: [{ input: 1, output: 2, cache: { read: 0.1, write: 0 } }], limit: { context: 1000, output: 100 }, }) - expect(model.request).toMatchObject({ body: { custom: "value" }, generation: { temperature: 0.5 } }) - expect(model.request.body).toEqual({ custom: "value" }) + expect(model.settings).toEqual({ + baseURL: `${server.url.origin}/v1`, + custom: "value", + temperature: 0.5, + }) expect(model.variants).toEqual([ { id: ModelV2.VariantID.make("high"), headers: {}, - body: {}, - generation: { temperature: 0.2 }, - options: {}, + settings: { temperature: 0.2 }, }, ]) expect( @@ -178,11 +178,14 @@ describe("OpencodePlugin", () => { yield* catalog.transform((catalog) => { const provider = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.opencode), - api: { type: "aisdk", package: "test-provider" }, + aisdk: true, + package: "test-provider", }) const model = ModelV2.Info.make({ ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")), - api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("paid"), + aisdk: true, + package: "test-provider", cost: cost(1), }) catalog.provider.update(provider.id, () => {}) @@ -191,7 +194,7 @@ describe("OpencodePlugin", () => { }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBe("public") + expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).settings?.apiKey).toBe("public") expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(false) }), ), @@ -204,11 +207,14 @@ describe("OpencodePlugin", () => { yield* catalog.transform((catalog) => { const provider = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.opencode), - api: { type: "aisdk", package: "test-provider" }, + aisdk: true, + package: "test-provider", }) const model = ModelV2.Info.make({ ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("free")), - api: { id: ModelV2.ID.make("free"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("free"), + aisdk: true, + package: "test-provider", cost: cost(0), }) catalog.provider.update(provider.id, () => {}) @@ -217,7 +223,7 @@ describe("OpencodePlugin", () => { }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBe("public") + expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).settings?.apiKey).toBe("public") expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("free"))).enabled).toBe(true) }), ), @@ -230,11 +236,14 @@ describe("OpencodePlugin", () => { yield* catalog.transform((catalog) => { const provider = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.opencode), - api: { type: "aisdk", package: "test-provider" }, + aisdk: true, + package: "test-provider", }) const model = ModelV2.Info.make({ ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("output-only")), - api: { id: ModelV2.ID.make("output-only"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("output-only"), + aisdk: true, + package: "test-provider", cost: cost(0, 1), }) catalog.provider.update(provider.id, () => {}) @@ -243,7 +252,7 @@ describe("OpencodePlugin", () => { }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBe("public") + expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).settings?.apiKey).toBe("public") expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("output-only"))).enabled).toBe( true, ) @@ -258,11 +267,14 @@ describe("OpencodePlugin", () => { yield* catalog.transform((catalog) => { const provider = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.opencode), - api: { type: "aisdk", package: "test-provider" }, + aisdk: true, + package: "test-provider", }) const model = ModelV2.Info.make({ ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")), - api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("paid"), + aisdk: true, + package: "test-provider", cost: cost(1), }) catalog.provider.update(provider.id, () => {}) @@ -271,7 +283,7 @@ describe("OpencodePlugin", () => { }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBeUndefined() + expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).settings?.apiKey).toBeUndefined() expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true) }), ), @@ -291,11 +303,14 @@ describe("OpencodePlugin", () => { yield* catalog.transform((catalog) => { const provider = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.opencode), - api: { type: "aisdk", package: "test-provider" }, + aisdk: true, + package: "test-provider", }) const model = ModelV2.Info.make({ ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")), - api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("paid"), + aisdk: true, + package: "test-provider", cost: cost(1), }) catalog.provider.update(provider.id, () => {}) @@ -304,7 +319,7 @@ describe("OpencodePlugin", () => { }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBeUndefined() + expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).settings?.apiKey).toBeUndefined() expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true) }), ), @@ -317,26 +332,28 @@ describe("OpencodePlugin", () => { yield* catalog.transform((catalog) => { const provider = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.opencode), - api: { type: "aisdk", package: "test-provider" }, - request: { - headers: {}, - body: { apiKey: "configured" }, - }, + aisdk: true, + package: "test-provider", + headers: {}, + settings: { apiKey: "configured" }, }) const model = ModelV2.Info.make({ ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")), - api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("paid"), + aisdk: true, + package: "test-provider", cost: cost(1), }) catalog.provider.update(provider.id, (draft) => { - draft.request = provider.request + draft.headers = provider.headers + draft.settings = { apiKey: "configured" } }) catalog.model.update(provider.id, model.id, (draft) => { draft.cost = [...model.cost] }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).request.body.apiKey).toBe("configured") + expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).settings?.apiKey).toBe("configured") expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true) }), ), @@ -349,11 +366,14 @@ describe("OpencodePlugin", () => { yield* catalog.transform((catalog) => { const provider = ProviderV2.Info.make({ ...ProviderV2.Info.empty(ProviderV2.ID.openai), - api: { type: "aisdk", package: "test-provider" }, + aisdk: true, + package: "test-provider", }) const model = ModelV2.Info.make({ ...ModelV2.Info.empty(provider.id, ModelV2.ID.make("paid")), - api: { id: ModelV2.ID.make("paid"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("paid"), + aisdk: true, + package: "test-provider", cost: cost(1), }) catalog.provider.update(provider.id, () => {}) @@ -362,7 +382,7 @@ describe("OpencodePlugin", () => { }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.openai)).request.body.apiKey).toBeUndefined() + expect(required(yield* catalog.provider.get(ProviderV2.ID.openai)).settings?.apiKey).toBeUndefined() expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("paid"))).enabled).toBe(true) }), ), diff --git a/packages/core/test/plugin/provider-openrouter.test.ts b/packages/core/test/plugin/provider-openrouter.test.ts index 953943673f..5548b93597 100644 --- a/packages/core/test/plugin/provider-openrouter.test.ts +++ b/packages/core/test/plugin/provider-openrouter.test.ts @@ -30,19 +30,21 @@ describe("OpenRouterPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.openrouter, (provider) => { - provider.api = { type: "aisdk", package: "@openrouter/ai-sdk-provider" } - provider.request = { headers: { Existing: "value" }, body: {} } + provider.aisdk = true + provider.package = "@openrouter/ai-sdk-provider" + provider.headers = { Existing: "value" } + provider.settings = {} }) catalog.provider.update(ProviderV2.ID.make("nvidia"), () => {}) }) yield* addPlugin() - expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.request.headers).toEqual({ + expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.headers).toEqual({ Existing: "value", "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode", }) - expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.request.headers).toEqual({}) + expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.headers).toBeUndefined() }), ) @@ -55,7 +57,9 @@ describe("OpenRouterPlugin", () => { const ignored = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.openrouter, ModelV2.ID.make("openai/gpt-5")), - api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("openai/gpt-5"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "openrouter" }, @@ -65,7 +69,9 @@ describe("OpenRouterPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom"), ModelV2.ID.make("openai/gpt-5")), - api: { id: ModelV2.ID.make("openai/gpt-5"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("openai/gpt-5"), + aisdk: true, + package: "test-provider", }), package: "@openrouter/ai-sdk-provider", options: { name: "custom" }, @@ -79,7 +85,8 @@ describe("OpenRouterPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.openrouter, (provider) => { - provider.api = { type: "aisdk", package: "@openrouter/ai-sdk-provider" } + provider.aisdk = true + provider.package = "@openrouter/ai-sdk-provider" }) catalog.provider.update(ProviderV2.ID.openai, () => {}) catalog.model.update(ProviderV2.ID.openrouter, ModelV2.ID.make("openai/gpt-5-chat"), () => {}) diff --git a/packages/core/test/plugin/provider-perplexity.test.ts b/packages/core/test/plugin/provider-perplexity.test.ts index a2a3969233..121f5ddd54 100644 --- a/packages/core/test/plugin/provider-perplexity.test.ts +++ b/packages/core/test/plugin/provider-perplexity.test.ts @@ -41,7 +41,9 @@ describe("PerplexityPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("perplexity"), ModelV2.ID.make("sonar")), - api: { id: ModelV2.ID.make("sonar"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("sonar"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/perplexity", options: { name: "perplexity" }, @@ -58,7 +60,9 @@ describe("PerplexityPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("perplexity"), ModelV2.ID.make("sonar")), - api: { id: ModelV2.ID.make("sonar"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("sonar"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/perplexity-compatible", options: { name: "perplexity" }, @@ -75,7 +79,9 @@ describe("PerplexityPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("perplexity"), ModelV2.ID.make("sonar")), - api: { id: ModelV2.ID.make("sonar"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("sonar"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/perplexity", options: { name: "perplexity" }, @@ -92,7 +98,9 @@ describe("PerplexityPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-perplexity"), ModelV2.ID.make("sonar")), - api: { id: ModelV2.ID.make("sonar"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("sonar"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/perplexity", options: { name: "custom-perplexity" }, @@ -110,7 +118,9 @@ describe("PerplexityPlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("perplexity"), ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("sonar"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("sonar"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, diff --git a/packages/core/test/plugin/provider-sap-ai-core.test.ts b/packages/core/test/plugin/provider-sap-ai-core.test.ts index 5056c5dbb7..dd9bb4ec25 100644 --- a/packages/core/test/plugin/provider-sap-ai-core.test.ts +++ b/packages/core/test/plugin/provider-sap-ai-core.test.ts @@ -49,7 +49,9 @@ function withEnv(vars: Record, effect: () = function model(providerID: string) { return ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make(providerID), ModelV2.ID.make("sap-model")), - api: { id: ModelV2.ID.make("sap-model"), type: "aisdk", package: fixtureProvider }, + modelID: ModelV2.ID.make("sap-model"), + aisdk: true, + package: fixtureProvider, }) } diff --git a/packages/core/test/plugin/provider-snowflake-cortex.test.ts b/packages/core/test/plugin/provider-snowflake-cortex.test.ts index cd67feb40e..9fece1ecf6 100644 --- a/packages/core/test/plugin/provider-snowflake-cortex.test.ts +++ b/packages/core/test/plugin/provider-snowflake-cortex.test.ts @@ -57,7 +57,9 @@ describe("SnowflakeCortexPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("openai"), ModelV2.ID.make("gpt-4")), - api: { id: ModelV2.ID.make("gpt-4"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("gpt-4"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai", options: { name: "openai" }, @@ -75,7 +77,9 @@ describe("SnowflakeCortexPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")), - api: { id: ModelV2.ID.make("claude-sonnet-4-6"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude-sonnet-4-6"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "snowflake-cortex", baseURL: "https://test.snowflakecomputing.com/api/v2/cortex/v1" }, @@ -94,7 +98,9 @@ describe("SnowflakeCortexPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")), - api: { id: ModelV2.ID.make("claude-sonnet-4-6"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude-sonnet-4-6"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { @@ -117,7 +123,9 @@ describe("SnowflakeCortexPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")), - api: { id: ModelV2.ID.make("claude-sonnet-4-6"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude-sonnet-4-6"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "snowflake-cortex", baseURL: "https://test.snowflakecomputing.com/api/v2/cortex/v1" }, @@ -136,7 +144,9 @@ describe("SnowflakeCortexPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")), - api: { id: ModelV2.ID.make("claude-sonnet-4-6"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude-sonnet-4-6"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { @@ -159,7 +169,9 @@ describe("SnowflakeCortexPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")), - api: { id: ModelV2.ID.make("claude-sonnet-4-6"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("claude-sonnet-4-6"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "snowflake-cortex", baseURL: "https://test.snowflakecomputing.com/api/v2/cortex/v1" }, diff --git a/packages/core/test/plugin/provider-togetherai.test.ts b/packages/core/test/plugin/provider-togetherai.test.ts index 56bf7c9642..fc87a0cfbd 100644 --- a/packages/core/test/plugin/provider-togetherai.test.ts +++ b/packages/core/test/plugin/provider-togetherai.test.ts @@ -41,7 +41,9 @@ describe("TogetherAIPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("togetherai"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/togetherai", options: { name: "togetherai" }, @@ -59,7 +61,9 @@ describe("TogetherAIPlugin", () => { const ignored = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("togetherai"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "file:///tmp/@ai-sdk/togetherai-provider.js", options: { name: "togetherai" }, @@ -69,7 +73,9 @@ describe("TogetherAIPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("togetherai"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/togetherai", options: { name: "togetherai" }, @@ -87,7 +93,9 @@ describe("TogetherAIPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-togetherai"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/togetherai", options: { name: "custom-togetherai" }, @@ -110,11 +118,9 @@ describe("TogetherAIPlugin", () => { ProviderV2.ID.make("togetherai"), ModelV2.ID.make("meta-llama/Llama-3.3-70B-Instruct-Turbo"), ), - api: { - id: ModelV2.ID.make("meta-llama/Llama-3.3-70B-Instruct-Turbo"), - type: "aisdk", - package: "test-provider", - }, + modelID: ModelV2.ID.make("meta-llama/Llama-3.3-70B-Instruct-Turbo"), + aisdk: true, + package: "test-provider", }), sdk: { languageModel: fakeSelectorSdk(calls).languageModel }, options: {}, @@ -122,7 +128,9 @@ describe("TogetherAIPlugin", () => { expect(result.language).toBeUndefined() expect(calls).toEqual([]) - expect(result.language ?? fakeSelectorSdk(calls).languageModel(result.model.api.id)).toBeDefined() + expect( + result.language ?? fakeSelectorSdk(calls).languageModel(result.model.modelID ?? result.model.id), + ).toBeDefined() expect(calls).toEqual(["languageModel:meta-llama/Llama-3.3-70B-Instruct-Turbo"]) }), ) diff --git a/packages/core/test/plugin/provider-venice.test.ts b/packages/core/test/plugin/provider-venice.test.ts index 3ed6c711c4..ba4cfef8af 100644 --- a/packages/core/test/plugin/provider-venice.test.ts +++ b/packages/core/test/plugin/provider-venice.test.ts @@ -41,7 +41,9 @@ describe("VenicePlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("venice"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "venice-ai-sdk-provider", options: { name: "venice" }, @@ -58,7 +60,9 @@ describe("VenicePlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-venice"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "venice-ai-sdk-provider", options: { name: "custom-venice", apiKey: "test" }, @@ -76,7 +80,9 @@ describe("VenicePlugin", () => { const similar = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("venice"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "file:///tmp/venice-ai-sdk-provider.js", options: { name: "venice" }, @@ -84,7 +90,9 @@ describe("VenicePlugin", () => { const other = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("venice"), ModelV2.ID.make("model")), - api: { id: ModelV2.ID.make("model"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("model"), + aisdk: true, + package: "test-provider", }), package: "@ai-sdk/openai-compatible", options: { name: "venice" }, @@ -103,7 +111,9 @@ describe("VenicePlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("venice"), ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("alias"), type: "aisdk", package: "test-provider" }, + modelID: ModelV2.ID.make("alias"), + aisdk: true, + package: "test-provider", }), sdk: fakeSelectorSdk(calls), options: {}, diff --git a/packages/core/test/plugin/provider-vercel.test.ts b/packages/core/test/plugin/provider-vercel.test.ts index eb87b15132..08f169aa6e 100644 --- a/packages/core/test/plugin/provider-vercel.test.ts +++ b/packages/core/test/plugin/provider-vercel.test.ts @@ -25,12 +25,13 @@ describe("VercelPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("vercel"), (provider) => { - provider.api = { type: "aisdk", package: "@ai-sdk/vercel" } - provider.request.headers.Existing = "1" + provider.aisdk = true + provider.package = "@ai-sdk/vercel" + provider.headers = { ...provider.headers, Existing: "1" } }) }) yield* addPlugin() - expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel")))?.request.headers).toEqual({ + expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel")))?.headers).toEqual({ Existing: "1", "http-referer": "https://opencode.ai/", "x-title": "opencode", @@ -43,14 +44,13 @@ describe("VercelPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("vercel"), (provider) => { - provider.api = { type: "aisdk", package: "@ai-sdk/vercel" } + provider.aisdk = true + provider.package = "@ai-sdk/vercel" }), ) yield* addPlugin() - expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel")))?.request.headers).not.toHaveProperty( - "HTTP-Referer", - ) - expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel")))?.request.headers).not.toHaveProperty("X-Title") + expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel")))?.headers).not.toHaveProperty("HTTP-Referer") + expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel")))?.headers).not.toHaveProperty("X-Title") }), ) @@ -62,7 +62,9 @@ describe("VercelPlugin", () => { const event = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-vercel"), ModelV2.ID.make("v0-1.0-md")), - api: { id: ModelV2.ID.make("v0-1.0-md"), type: "aisdk", package: "@ai-sdk/vercel" }, + modelID: ModelV2.ID.make("v0-1.0-md"), + aisdk: true, + package: "@ai-sdk/vercel", }), package: "@ai-sdk/vercel", options: { name: "custom-vercel" }, @@ -77,7 +79,7 @@ describe("VercelPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("gateway"), () => {})) yield* addPlugin() - expect((yield* catalog.provider.get(ProviderV2.ID.make("gateway")))?.request.headers).toEqual({}) + expect((yield* catalog.provider.get(ProviderV2.ID.make("gateway")))?.headers).toBeUndefined() }), ) }) diff --git a/packages/core/test/plugin/provider-xai.test.ts b/packages/core/test/plugin/provider-xai.test.ts index c4d1a5ece3..525b2bbb62 100644 --- a/packages/core/test/plugin/provider-xai.test.ts +++ b/packages/core/test/plugin/provider-xai.test.ts @@ -42,7 +42,9 @@ describe("XAIPlugin", () => { const ignored = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")), - api: { id: ModelV2.ID.make("grok-4"), type: "aisdk", package: "@ai-sdk/xai" }, + modelID: ModelV2.ID.make("grok-4"), + aisdk: true, + package: "@ai-sdk/xai", }), package: "@ai-sdk/openai-compatible", options: {}, @@ -51,7 +53,9 @@ describe("XAIPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")), - api: { id: ModelV2.ID.make("grok-4"), type: "aisdk", package: "@ai-sdk/xai" }, + modelID: ModelV2.ID.make("grok-4"), + aisdk: true, + package: "@ai-sdk/xai", }), package: "@ai-sdk/xai", options: {}, @@ -71,7 +75,9 @@ describe("XAIPlugin", () => { const result = yield* aisdk.runSDK({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("custom-xai"), ModelV2.ID.make("grok-4")), - api: { id: ModelV2.ID.make("grok-4"), type: "aisdk", package: "@ai-sdk/xai" }, + modelID: ModelV2.ID.make("grok-4"), + aisdk: true, + package: "@ai-sdk/xai", }), package: "@ai-sdk/xai", options: {}, @@ -81,7 +87,7 @@ describe("XAIPlugin", () => { }), ) - it.effect("uses responses with the model api.id for xAI language models", () => + it.effect("uses responses with the model modelID for xAI language models", () => Effect.gen(function* () { const plugin = yield* PluginV2.Service const aisdk = yield* AISDK.Service @@ -91,7 +97,9 @@ describe("XAIPlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("alias")), - api: { id: ModelV2.ID.make("grok-4"), type: "aisdk", package: "@ai-sdk/xai" }, + modelID: ModelV2.ID.make("grok-4"), + aisdk: true, + package: "@ai-sdk/xai", }), sdk: fakeSelectorSdk(calls), options: {}, @@ -112,7 +120,9 @@ describe("XAIPlugin", () => { const result = yield* aisdk.runLanguage({ model: ModelV2.Info.make({ ...ModelV2.Info.empty(ProviderV2.ID.openai, ModelV2.ID.make("grok-4")), - api: { id: ModelV2.ID.make("grok-4"), type: "aisdk", package: "@ai-sdk/xai" }, + modelID: ModelV2.ID.make("grok-4"), + aisdk: true, + package: "@ai-sdk/xai", }), sdk: fakeSelectorSdk(calls), options: {}, diff --git a/packages/core/test/plugin/provider-zenmux.test.ts b/packages/core/test/plugin/provider-zenmux.test.ts index b9d34a1f5b..15da40167e 100644 --- a/packages/core/test/plugin/provider-zenmux.test.ts +++ b/packages/core/test/plugin/provider-zenmux.test.ts @@ -32,17 +32,15 @@ describe("ZenmuxPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("zenmux"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://zenmux.ai/api/v1", - } + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { ...provider.settings, baseURL: "https://zenmux.ai/api/v1" } }) }) yield* addPlugin() const result = required(yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))) - expect(result.request.headers).toEqual({ "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" }) - expect(Object.keys(result.request.headers).sort()).toEqual(["HTTP-Referer", "X-Title"]) + expect(result.headers).toEqual({ "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" }) + expect(Object.keys(required(result.headers)).sort()).toEqual(["HTTP-Referer", "X-Title"]) }), ) @@ -51,17 +49,15 @@ describe("ZenmuxPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("zenmux"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://zenmux.ai/api/v1", - } - provider.request.headers.Existing = "value" + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { ...provider.settings, baseURL: "https://zenmux.ai/api/v1" } + provider.headers = { ...provider.headers, Existing: "value" } }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).request.headers).toEqual({ + expect(required(yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).headers).toEqual({ Existing: "value", "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode", @@ -74,17 +70,15 @@ describe("ZenmuxPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.make("zenmux"), (provider) => { - provider.api = { - type: "aisdk", - package: "@ai-sdk/openai-compatible", - url: "https://zenmux.ai/api/v1", - } - provider.request.headers = { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" } + provider.aisdk = true + provider.package = "@ai-sdk/openai-compatible" + provider.settings = { ...provider.settings, baseURL: "https://zenmux.ai/api/v1" } + provider.headers = { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" } }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).request.headers).toEqual({ + expect(required(yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).headers).toEqual({ "HTTP-Referer": "https://example.com/", "X-Title": "custom-title", }) @@ -96,12 +90,12 @@ describe("ZenmuxPlugin", () => { const catalog = yield* Catalog.Service yield* catalog.transform((catalog) => { catalog.provider.update(ProviderV2.ID.openrouter, (provider) => { - provider.request.headers = { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" } + provider.headers = { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" } }) }) yield* addPlugin() - expect(required(yield* catalog.provider.get(ProviderV2.ID.openrouter)).request.headers).toEqual({ + expect(required(yield* catalog.provider.get(ProviderV2.ID.openrouter)).headers).toEqual({ "HTTP-Referer": "https://example.com/", "X-Title": "custom-title", }) diff --git a/packages/core/test/plugin/variant.test.ts b/packages/core/test/plugin/variant.test.ts new file mode 100644 index 0000000000..e20fd3a11d --- /dev/null +++ b/packages/core/test/plugin/variant.test.ts @@ -0,0 +1,84 @@ +import { describe, expect } from "bun:test" +import { Catalog } from "@opencode-ai/core/catalog" +import { Credential } from "@opencode-ai/core/credential" +import { EventV2 } from "@opencode-ai/core/event" +import { Integration } from "@opencode-ai/core/integration" +import { Location } from "@opencode-ai/core/location" +import { ModelV2 } from "@opencode-ai/core/model" +import { Policy } from "@opencode-ai/core/policy" +import { VariantPlugin } from "@opencode-ai/core/plugin/variant" +import { ProviderV2 } from "@opencode-ai/core/provider" +import { AbsolutePath } from "@opencode-ai/core/schema" +import { Effect, Layer } from "effect" +import { location } from "../fixture/location" +import { testEffect } from "../lib/effect" +import { catalogHost, host } from "./host" + +const events = EventV2.defaultLayer +const locationLayer = Layer.succeed( + Location.Service, + Location.Service.of(location({ directory: AbsolutePath.make(import.meta.dir) })), +) +const connections = Credential.defaultLayer.pipe(Layer.fresh) +const integrations = Integration.locationLayer.pipe(Layer.provide(events), Layer.provide(connections)) +const catalog = Catalog.layer.pipe( + Layer.provide( + Layer.mergeAll( + events, + locationLayer, + Policy.layer.pipe(Layer.provide(locationLayer)), + connections, + integrations, + ), + ), +) +const it = testEffect( + Layer.mergeAll(catalog.pipe(Layer.provide(connections)), integrations, connections, events, locationLayer), +) + +describe("VariantPlugin", () => { + it.effect("adds GLM 5.2 variants after catalog sources", () => + Effect.gen(function* () { + const service = yield* Catalog.Service + yield* service.transform((catalog) => { + catalog.provider.update(ProviderV2.ID.opencode, (provider) => { + provider.package = "@ai-sdk/openai-compatible" + provider.aisdk = true + }) + catalog.model.update(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2"), (model) => { + model.modelID = ModelV2.ID.make("glm-5.2") + model.package = "@ai-sdk/openai-compatible" + model.aisdk = true + }) + }) + yield* VariantPlugin.Plugin.effect(host({ catalog: catalogHost(service) })) + + expect((yield* service.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2")))?.variants).toEqual([ + expect.objectContaining({ id: "high", settings: { reasoningEffort: "high" } }), + expect.objectContaining({ id: "max", settings: { reasoningEffort: "max" } }), + ]) + }), + ) + + it.effect("keeps explicit variants over generated defaults", () => + Effect.gen(function* () { + const service = yield* Catalog.Service + yield* service.transform((catalog) => { + catalog.model.update(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2"), (model) => { + model.modelID = ModelV2.ID.make("glm-5.2") + model.package = "@ai-sdk/openai-compatible" + model.aisdk = true + model.variants = [ + { id: ModelV2.VariantID.make("high"), headers: { custom: "true" } }, + ] + }) + }) + yield* VariantPlugin.Plugin.effect(host({ catalog: catalogHost(service) })) + + expect((yield* service.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2")))?.variants).toEqual([ + expect.objectContaining({ id: "high", headers: { custom: "true" } }), + expect.objectContaining({ id: "max", settings: { reasoningEffort: "max" } }), + ]) + }), + ) +}) diff --git a/packages/core/test/session-runner-model.test.ts b/packages/core/test/session-runner-model.test.ts index efebe60d04..d2283a1086 100644 --- a/packages/core/test/session-runner-model.test.ts +++ b/packages/core/test/session-runner-model.test.ts @@ -12,28 +12,23 @@ import { SessionV2 } from "@opencode-ai/core/session" import { AbsolutePath } from "@opencode-ai/core/schema" import { it } from "./lib/effect" -type Api = - | { - readonly type: "aisdk" - readonly package: string - readonly url?: string - readonly settings?: Record - } - | { readonly type: "native"; readonly url?: string; readonly settings: Record } +type Transport = { + readonly package: string + readonly aisdk?: true + readonly settings?: Record +} -const model = (api: Api, variants: ModelV2.Info["variants"] = []) => +const model = (transport: Transport, variants?: NonNullable) => ModelV2.Info.make({ id: ModelV2.ID.make("test-model"), + modelID: ModelV2.ID.make("api-test-model"), providerID: ProviderV2.ID.make("test-provider"), name: "Test model", - api: { id: ModelV2.ID.make("api-test-model"), ...api }, + ...transport, + settings: { apiKey: "secret", ...transport.settings }, capabilities: { tools: true, input: ["text"], output: ["text"] }, - request: { - headers: { "x-test": "header" }, - body: { apiKey: "secret", custom_extension: { enabled: true } }, - generation: { temperature: 0.7 }, - options: { store: false, serviceTier: "priority" }, - }, + headers: { "x-test": "header" }, + body: { custom_extension: { enabled: true } }, variants, time: { released: 0 }, cost: [], @@ -46,7 +41,7 @@ describe("SessionRunnerModel", () => { it.effect("maps catalog OpenAI AI SDK models into native Responses routes", () => Effect.gen(function* () { const resolved = yield* SessionRunnerModel.fromCatalogModel( - model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }), + model({ package: "@ai-sdk/openai", aisdk: true, settings: { baseURL: "https://openai.example/v1" } }), ) expect(resolved).toMatchObject({ id: "api-test-model", provider: "test-provider" }) @@ -56,8 +51,6 @@ describe("SessionRunnerModel", () => { defaults: { headers: { "x-test": "header" }, limits: { context: 100, output: 20 }, - generation: { temperature: 0.7 }, - providerOptions: { openai: { store: false, serviceTier: "priority" } }, http: { body: { custom_extension: { enabled: true } } }, }, }) @@ -67,7 +60,7 @@ describe("SessionRunnerModel", () => { it.effect("keeps catalog apiKey credentials out of provider JSON", () => Effect.gen(function* () { const resolved = yield* SessionRunnerModel.fromCatalogModel( - model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }), + model({ package: "@ai-sdk/openai", aisdk: true, settings: { baseURL: "https://openai.example/v1" } }), ) const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" })) @@ -81,12 +74,16 @@ describe("SessionRunnerModel", () => { const resolved = yield* SessionRunnerModel.fromCatalogModel( ModelV2.Info.make({ ...model({ - type: "aisdk", package: "@ai-sdk/openai-compatible", - url: "https://compatible.example/v1", - settings: { apiKey: "settings-secret", compatibility: "strict" }, + aisdk: true, + settings: { + apiKey: "settings-secret", + baseURL: "https://compatible.example/v1", + compatibility: "strict", + }, }), - request: { headers: {}, body: {}, generation: {}, options: {} }, + headers: {}, + body: {}, }), ) const request = LLM.request({ model: resolved, prompt: "Hello" }) @@ -103,21 +100,20 @@ describe("SessionRunnerModel", () => { }), ) - it.effect("lowers selected OpenAI Session variants into Responses options", () => + it.effect("overlays selected OpenAI Session variant bodies", () => Effect.gen(function* () { - const base = model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }, [ + const catalog = model({ package: "@ai-sdk/openai", aisdk: true, settings: { baseURL: "https://openai.example/v1" } }, [ { id: ModelV2.VariantID.make("high"), headers: { "x-variant": "high" }, - body: {}, - generation: { temperature: 0.2 }, - options: { reasoningEffort: "high" }, + body: { + store: false, + service_tier: "priority", + temperature: 0.2, + reasoning: { effort: "high" }, + }, }, ]) - const catalog = ModelV2.Info.make({ - ...base, - request: { ...base.request, options: { ...base.request.options, reasoningEffort: "medium" } }, - }) const session = SessionV2.Info.make({ id: SessionV2.ID.make("ses_model_variant"), projectID: ProjectV2.ID.global, @@ -134,31 +130,31 @@ describe("SessionRunnerModel", () => { }) const resolved = yield* SessionRunnerModel.resolve(session, catalog) - const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" })) expect(resolved.route.defaults.headers).toMatchObject({ "x-test": "header", "x-variant": "high" }) - expect(resolved.route.defaults.http?.body).toEqual({ custom_extension: { enabled: true } }) - expect(prepared.body).toMatchObject({ + expect(resolved.route.defaults.http?.body).toEqual({ + custom_extension: { enabled: true }, store: false, service_tier: "priority", temperature: 0.2, reasoning: { effort: "high" }, }) - expect(prepared.body).not.toHaveProperty("reasoningEffort") }), ) - it.effect("lowers selected OpenAI-compatible Session variants into Chat options", () => + it.effect("overlays selected OpenAI-compatible Session variant bodies", () => Effect.gen(function* () { const catalog = model( - { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://compatible.example/v1" }, + { + package: "@ai-sdk/openai-compatible", + aisdk: true, + settings: { baseURL: "https://compatible.example/v1" }, + }, [ { id: ModelV2.VariantID.make("high"), headers: {}, - body: {}, - generation: {}, - options: { reasoningEffort: "high" }, + body: { store: false, reasoning_effort: "high" }, }, ], ) @@ -174,20 +170,18 @@ describe("SessionRunnerModel", () => { }) const resolved = yield* SessionRunnerModel.resolve(session, catalog) - const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" })) - expect(resolved.route.defaults.http?.body).toEqual({ custom_extension: { enabled: true } }) - expect(prepared.body).toMatchObject({ + expect(resolved.route.defaults.http?.body).toEqual({ + custom_extension: { enabled: true }, store: false, reasoning_effort: "high", }) - expect(prepared.body).not.toHaveProperty("reasoningEffort") }), ) it.effect("rejects an explicit unavailable Session variant during model resolution", () => Effect.gen(function* () { - const catalog = model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }) + const catalog = model({ package: "@ai-sdk/openai", aisdk: true, settings: { baseURL: "https://openai.example/v1" } }) const session = SessionV2.Info.make({ id: SessionV2.ID.make("ses_model_variant_unavailable"), projectID: ProjectV2.ID.global, @@ -215,15 +209,13 @@ describe("SessionRunnerModel", () => { }), ) - it.effect("lowers selected Anthropic Session variants into Messages options", () => + it.effect("overlays selected Anthropic Session variant bodies", () => Effect.gen(function* () { - const catalog = model({ type: "aisdk", package: "@ai-sdk/anthropic", url: "https://anthropic.example/v1" }, [ + const catalog = model({ package: "@ai-sdk/anthropic", aisdk: true, settings: { baseURL: "https://anthropic.example/v1" } }, [ { id: ModelV2.VariantID.make("high"), headers: {}, - body: {}, - generation: {}, - options: { thinking: { type: "enabled", budgetTokens: 12000 } }, + body: { thinking: { type: "enabled", budget_tokens: 12000 } }, }, ]) const session = SessionV2.Info.make({ @@ -238,20 +230,18 @@ describe("SessionRunnerModel", () => { }) const resolved = yield* SessionRunnerModel.resolve(session, catalog) - const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" })) - expect(resolved.route.defaults.http?.body).toEqual({ custom_extension: { enabled: true } }) - expect(prepared.body).toMatchObject({ + expect(resolved.route.defaults.http?.body).toEqual({ + custom_extension: { enabled: true }, thinking: { type: "enabled", budget_tokens: 12000 }, }) - expect(JSON.stringify(prepared.body)).not.toContain("budgetTokens") }), ) it.effect("maps catalog Anthropic AI SDK models into native routes", () => Effect.gen(function* () { const resolved = yield* SessionRunnerModel.fromCatalogModel( - model({ type: "aisdk", package: "@ai-sdk/anthropic", url: "https://anthropic.example/v1" }), + model({ package: "@ai-sdk/anthropic", aisdk: true, settings: { baseURL: "https://anthropic.example/v1" } }), ) expect(resolved.route).toMatchObject({ @@ -265,8 +255,9 @@ describe("SessionRunnerModel", () => { Effect.gen(function* () { const resolved = yield* SessionRunnerModel.fromCatalogModel( ModelV2.Info.make({ - ...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }), - request: { headers: {}, body: {}, generation: {}, options: {} }, + ...model({ package: "@ai-sdk/openai", aisdk: true, settings: { baseURL: "https://openai.example/v1" } }), + headers: {}, + body: {}, }), Credential.Key.make({ type: "key", key: "secret" }), ) @@ -288,8 +279,10 @@ describe("SessionRunnerModel", () => { const credential = Credential.Key.make({ type: "key", key: "stored-secret", metadata: { tenant: "work" } }) const resolved = yield* SessionRunnerModel.fromCatalogModel( ModelV2.Info.make({ - ...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }), - request: { headers: {}, body: { apiKey: "configured-secret" }, generation: {}, options: {} }, + ...model({ package: "@ai-sdk/openai", aisdk: true, settings: { baseURL: "https://openai.example/v1" } }), + settings: { apiKey: "configured-secret", baseURL: "https://openai.example/v1" }, + headers: {}, + body: {}, }), credential, ) @@ -302,23 +295,23 @@ describe("SessionRunnerModel", () => { }) expect(headers.authorization).toBe("Bearer stored-secret") - expect(resolved.route.defaults.http?.body).toEqual({ tenant: "work" }) + expect(resolved.route.defaults.http?.body).toEqual({}) }), ) it.effect("rejects catalog APIs without a native route", () => Effect.gen(function* () { const failure = yield* SessionRunnerModel.fromCatalogModel( - model({ type: "aisdk", package: "@ai-sdk/google", url: "https://google.example/v1" }), + model({ package: "@ai-sdk/google", aisdk: true, settings: { baseURL: "https://google.example/v1" } }), ).pipe(Effect.flip) expect(failure).toMatchObject({ - _tag: "SessionRunnerModel.UnsupportedApiError", + _tag: "SessionRunnerModel.UnsupportedPackageError", providerID: "test-provider", modelID: "test-model", - api: "aisdk:@ai-sdk/google", + package: "aisdk:@ai-sdk/google", }) - expect(failure.message).toBe("Unsupported API for test-provider/test-model: aisdk:@ai-sdk/google") + expect(failure.message).toBe("Unsupported package for test-provider/test-model: aisdk:@ai-sdk/google") }), ) @@ -326,15 +319,15 @@ describe("SessionRunnerModel", () => { Effect.sync(() => { expect( SessionRunnerModel.supported( - model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }), + model({ package: "@ai-sdk/openai", aisdk: true, settings: { baseURL: "https://openai.example/v1" } }), ), ).toBe(true) expect( SessionRunnerModel.supported( - model({ type: "aisdk", package: "@ai-sdk/google", url: "https://google.example/v1" }), + model({ package: "@ai-sdk/google", aisdk: true, settings: { baseURL: "https://google.example/v1" } }), ), ).toBe(false) - expect(SessionRunnerModel.supported(model({ type: "native", settings: {} }))).toBe(false) + expect(SessionRunnerModel.supported(model({ package: "native" }))).toBe(false) }), ) }) diff --git a/packages/core/test/shared-schema.test.ts b/packages/core/test/shared-schema.test.ts index a1794b9263..7bd8da8c58 100644 --- a/packages/core/test/shared-schema.test.ts +++ b/packages/core/test/shared-schema.test.ts @@ -19,7 +19,6 @@ import { Credential } from "@opencode-ai/schema/credential" import { FileSystem } from "@opencode-ai/schema/filesystem" import { Integration } from "@opencode-ai/schema/integration" import { LLM } from "@opencode-ai/schema/llm" -import { ModelRequest } from "@opencode-ai/schema/model-request" import { Permission } from "@opencode-ai/schema/permission" import { Reference } from "@opencode-ai/schema/reference" import { Skill } from "@opencode-ai/schema/skill" @@ -35,7 +34,6 @@ test("Core reuses the canonical shared schemas", async () => { coreIntegration, coreLocation, coreLLM, - coreModelRequest, corePermission, coreProject, coreReference, @@ -53,7 +51,6 @@ test("Core reuses the canonical shared schemas", async () => { import("@opencode-ai/core/integration"), import("@opencode-ai/core/location"), import("@opencode-ai/llm"), - import("@opencode-ai/core/model-request"), import("@opencode-ai/core/permission"), import("@opencode-ai/core/project/schema"), import("@opencode-ai/core/reference"), @@ -103,16 +100,10 @@ test("Core reuses the canonical shared schemas", async () => { [ModelV2.Family, Model.Family], [ModelV2.Capabilities, Model.Capabilities], [ModelV2.Cost, Model.Cost], - [ModelV2.Api, Model.Api], [ModelV2.Info, Model.Info], [ProviderV2.ID, Provider.ID], - [ProviderV2.AISDK, Provider.AISDK], - [ProviderV2.Native, Provider.Native], - [ProviderV2.Api, Provider.Api], [ProviderV2.Request, Provider.Request], [ProviderV2.Info, Provider.Info], - [coreModelRequest.Generation, ModelRequest.Generation], - [coreModelRequest.Request, ModelRequest.Request], [corePermission.Effect, Permission.Effect], [corePermission.Rule, Permission.Rule], [corePermission.Ruleset, Permission.Ruleset], diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 9b30f76f72..e68140d9ee 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -7,7 +7,6 @@ export { Integration } from "./integration" export { LLM } from "./llm" export { Location } from "./location" export { Model } from "./model" -export { ModelRequest } from "./model-request" export { Permission } from "./permission" export { Project } from "./project" export { Provider } from "./provider" diff --git a/packages/schema/src/model-request.ts b/packages/schema/src/model-request.ts deleted file mode 100644 index d829acca66..0000000000 --- a/packages/schema/src/model-request.ts +++ /dev/null @@ -1,31 +0,0 @@ -export * as ModelRequest from "./model-request" - -import { Effect, Schema } from "effect" -import { Provider } from "./provider" - -export interface Generation extends Schema.Schema.Type {} -export const Generation = Schema.Struct({ - maxTokens: Schema.Number.pipe(Schema.optional), - temperature: Schema.Number.pipe(Schema.optional), - topP: Schema.Number.pipe(Schema.optional), - topK: Schema.Number.pipe(Schema.optional), - frequencyPenalty: Schema.Number.pipe(Schema.optional), - presencePenalty: Schema.Number.pipe(Schema.optional), - seed: Schema.Number.pipe(Schema.optional), - stop: Schema.String.pipe(Schema.Array, Schema.mutable, Schema.optional), -}) - -export interface Request extends Schema.Schema.Type {} -export const Request = Schema.Struct({ - ...Provider.Request.fields, - generation: Generation.pipe( - Schema.optionalKey, - Schema.withConstructorDefault(Effect.succeed({})), - Schema.withDecodingDefaultKey(Effect.succeed({})), - ), - options: Schema.Record(Schema.String, Schema.Any).pipe( - Schema.optionalKey, - Schema.withConstructorDefault(Effect.succeed({})), - Schema.withDecodingDefaultKey(Effect.succeed({})), - ), -}) diff --git a/packages/schema/src/model.ts b/packages/schema/src/model.ts index 62b76df0fc..1706021358 100644 --- a/packages/schema/src/model.ts +++ b/packages/schema/src/model.ts @@ -1,7 +1,6 @@ export * as Model from "./model" import { Schema } from "effect" -import { ModelRequest } from "./model-request" import { Provider } from "./provider" import { withStatics } from "./schema" @@ -42,34 +41,24 @@ export const Cost = Schema.Struct({ }), }) -export const Api = Schema.Union([ - Schema.Struct({ - id: ID, - ...Provider.AISDK.fields, - }), - Schema.Struct({ - id: ID, - ...Provider.Native.fields, - }), -]).pipe(Schema.toTaggedUnion("type")) -export type Api = typeof Api.Type +export interface Variant extends Schema.Schema.Type {} +export const Variant = Schema.Struct({ + id: VariantID, + ...Provider.Overlays, +}) export interface Info extends Schema.Schema.Type {} export const Info = Schema.Struct({ id: ID, + modelID: ID.pipe(Schema.optional), providerID: Provider.ID, family: Family.pipe(Schema.optional), name: Schema.String, - api: Api, + package: Schema.String.pipe(Schema.optional), + aisdk: Schema.Literal(true).pipe(Schema.optional), + ...Provider.Overlays, capabilities: Capabilities, - request: Schema.Struct({ - ...ModelRequest.Request.fields, - variant: Schema.String.pipe(Schema.optional), - }), - variants: Schema.Struct({ - id: VariantID, - ...ModelRequest.Request.fields, - }).pipe(Schema.Array, Schema.mutable), + variants: Variant.pipe(Schema.Array, Schema.mutable, Schema.optional), time: Schema.Struct({ released: Schema.Finite, }), @@ -90,10 +79,7 @@ export const Info = Schema.Struct({ id: modelID, providerID, name: modelID, - api: { id: modelID, type: "native", settings: {} }, capabilities: { tools: false, input: [], output: [] }, - request: { headers: {}, body: {}, generation: {}, options: {} }, - variants: [], time: { released: 0 }, cost: [], status: "active", diff --git a/packages/schema/src/provider.ts b/packages/schema/src/provider.ts index d3d5109608..6493635364 100644 --- a/packages/schema/src/provider.ts +++ b/packages/schema/src/provider.ts @@ -22,23 +22,11 @@ export const ID = Schema.String.pipe( ) export type ID = typeof ID.Type -export interface AISDK extends Schema.Schema.Type {} -export const AISDK = Schema.Struct({ - type: Schema.Literal("aisdk"), - package: Schema.String, - url: Schema.String.pipe(Schema.optional), +export const Overlays = { settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), -}) - -export interface Native extends Schema.Schema.Type {} -export const Native = Schema.Struct({ - type: Schema.Literal("native"), - url: Schema.String.pipe(Schema.optional), - settings: Schema.Record(Schema.String, Schema.Unknown), -}) - -export const Api = Schema.Union([AISDK, Native]).pipe(Schema.toTaggedUnion("type")) -export type Api = typeof Api.Type + headers: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional), + body: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), +} export interface Request extends Schema.Schema.Type {} export const Request = Schema.Struct({ @@ -52,8 +40,9 @@ export const Info = Schema.Struct({ integrationID: Integration.ID.pipe(Schema.optional), name: Schema.String, disabled: Schema.Boolean.pipe(Schema.optional), - api: Api, - request: Request, + package: Schema.String, + aisdk: Schema.Literal(true).pipe(Schema.optional), + ...Overlays, }) .annotate({ identifier: "ProviderV2.Info" }) .pipe( @@ -62,8 +51,7 @@ export const Info = Schema.Struct({ schema.make({ id, name: id, - api: { type: "native", settings: {} }, - request: { headers: {}, body: {} }, + package: "", }), })), ) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 9f9ddb14c2..694e00560c 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -3986,73 +3986,35 @@ export type SessionMessage = export type ModelV2Info = { id: string + modelID?: string providerID: string family?: string name: string - api: - | { - id: string - type: "aisdk" - package: string - url?: string - settings?: { - [key: string]: unknown - } - } - | { - id: string - type: "native" - url?: string - settings: { - [key: string]: unknown - } - } + package?: string + aisdk?: true + settings?: { + [key: string]: unknown + } + headers?: { + [key: string]: string + } + body?: { + [key: string]: unknown + } capabilities: { tools: boolean input: Array output: Array } - request: { - headers: { - [key: string]: string - } - body: { - [key: string]: unknown - } - generation?: { - maxTokens?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - temperature?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - topP?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - topK?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - frequencyPenalty?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - presencePenalty?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - seed?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - stop?: Array - } - options?: { - [key: string]: unknown - } - variant?: string - } - variants: Array<{ + variants?: Array<{ id: string - headers: { - [key: string]: string - } - body: { + settings?: { [key: string]: unknown } - generation?: { - maxTokens?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - temperature?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - topP?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - topK?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - frequencyPenalty?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - presencePenalty?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - seed?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" - stop?: Array + headers?: { + [key: string]: string } - options?: { + body?: { [key: string]: unknown } }> @@ -4085,29 +4047,16 @@ export type ProviderV2Info = { integrationID?: string name: string disabled?: boolean - api: - | { - type: "aisdk" - package: string - url?: string - settings?: { - [key: string]: unknown - } - } - | { - type: "native" - url?: string - settings: { - [key: string]: unknown - } - } - request: { - headers: { - [key: string]: string - } - body: { - [key: string]: unknown - } + package: string + aisdk?: true + settings?: { + [key: string]: unknown + } + headers?: { + [key: string]: string + } + body?: { + [key: string]: unknown } } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 90f872b2f0..59a01f8a18 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -26833,182 +26833,6 @@ "body": { "type": "object" }, - "generation": { - "type": "object", - "properties": { - "maxTokens": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "temperature": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "topP": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "topK": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "frequencyPenalty": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "presencePenalty": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "seed": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "stop": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "options": { - "type": "object" - }, "variant": { "type": "string" } @@ -27032,182 +26856,6 @@ }, "body": { "type": "object" - }, - "generation": { - "type": "object", - "properties": { - "maxTokens": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "temperature": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "topP": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "topK": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "frequencyPenalty": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "presencePenalty": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "seed": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": ["NaN"] - }, - { - "type": "string", - "enum": ["Infinity"] - }, - { - "type": "string", - "enum": ["-Infinity"] - }, - { - "type": "string", - "enum": ["Infinity", "-Infinity", "NaN"] - } - ] - }, - "stop": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false - }, - "options": { - "type": "object" } }, "required": ["id", "headers", "body"],