diff --git a/packages/core/src/agent.ts b/packages/core/src/agent.ts index d7c249031e..796ffe48ab 100644 --- a/packages/core/src/agent.ts +++ b/packages/core/src/agent.ts @@ -34,10 +34,6 @@ export class Info extends Schema.Class("AgentV2.Info")({ options: { headers: {}, body: {}, - aisdk: { - provider: {}, - request: {}, - }, }, mode: "all", hidden: false, diff --git a/packages/core/src/aisdk.ts b/packages/core/src/aisdk.ts index f3d39f3ac0..de54ea2744 100644 --- a/packages/core/src/aisdk.ts +++ b/packages/core/src/aisdk.ts @@ -58,7 +58,7 @@ function wrapSSE(res: Response, ms: number, ctl: AbortController) { } function prepareOptions(model: ModelV2.Info, pkg: string) { - const options: Record = { name: model.providerID, ...model.options.aisdk.provider } + const options: Record = { name: model.providerID, ...model.options.body } if (model.endpoint.type === "aisdk" && model.endpoint.url) options.baseURL = model.endpoint.url const customFetch = options.fetch diff --git a/packages/core/src/catalog.ts b/packages/core/src/catalog.ts index 5b53b92bd8..af3afd1d0b 100644 --- a/packages/core/src/catalog.ts +++ b/packages/core/src/catalog.ts @@ -112,13 +112,6 @@ export const layer = Layer.effect( ...provider.options.body, ...model.options.body, }, - aisdk: { - provider: { - ...provider.options.aisdk.provider, - ...model.options.aisdk.provider, - }, - request: model.options.aisdk.request, - }, variant: model.options.variant, } return new ModelV2.Info({ @@ -135,9 +128,9 @@ export const layer = Layer.effect( } const normalizeEndpoint = (item: Draft | Draft) => { - if (item.endpoint.type !== "aisdk" || typeof item.options.aisdk.provider.baseURL !== "string") return - item.endpoint.url = item.options.aisdk.provider.baseURL - delete item.options.aisdk.provider.baseURL + if (item.endpoint.type !== "aisdk" || typeof item.options.body.baseURL !== "string") return + item.endpoint.url = item.options.body.baseURL + delete item.options.body.baseURL } const state = State.create({ diff --git a/packages/core/src/config/plugin/agent.ts b/packages/core/src/config/plugin/agent.ts index c05b0a578f..a67d119125 100644 --- a/packages/core/src/config/plugin/agent.ts +++ b/packages/core/src/config/plugin/agent.ts @@ -37,8 +37,6 @@ export const Plugin = PluginV2.define({ if (item.options !== undefined) { Object.assign(agent.options.headers, item.options.headers ?? {}) Object.assign(agent.options.body, item.options.body ?? {}) - Object.assign(agent.options.aisdk.provider, item.options.aisdk?.provider ?? {}) - Object.assign(agent.options.aisdk.request, item.options.aisdk?.request ?? {}) } if (item.system !== undefined) agent.system = item.system if (item.description !== undefined) agent.description = item.description diff --git a/packages/core/src/config/plugin/provider.ts b/packages/core/src/config/plugin/provider.ts index fca2e53302..574faf5013 100644 --- a/packages/core/src/config/plugin/provider.ts +++ b/packages/core/src/config/plugin/provider.ts @@ -27,8 +27,6 @@ export const Plugin = PluginV2.define({ if (item.options !== undefined) { Object.assign(provider.options.headers, item.options.headers ?? {}) Object.assign(provider.options.body, item.options.body ?? {}) - Object.assign(provider.options.aisdk.provider, item.options.aisdk?.provider ?? {}) - Object.assign(provider.options.aisdk.request, item.options.aisdk?.request ?? {}) } }) @@ -48,8 +46,6 @@ export const Plugin = PluginV2.define({ if (config.options !== undefined) { Object.assign(model.options.headers, config.options.headers ?? {}) Object.assign(model.options.body, config.options.body ?? {}) - Object.assign(model.options.aisdk.provider, config.options.aisdk?.provider ?? {}) - Object.assign(model.options.aisdk.request, config.options.aisdk?.request ?? {}) if (config.options.variant !== undefined) model.options.variant = config.options.variant } if (config.variants !== undefined) { @@ -60,17 +56,11 @@ export const Plugin = PluginV2.define({ id: variant.id, headers: {}, body: {}, - aisdk: { - provider: {}, - request: {}, - }, } model.variants.push(existing) } Object.assign(existing.headers, variant.headers ?? {}) Object.assign(existing.body, variant.body ?? {}) - Object.assign(existing.aisdk.provider, variant.aisdk?.provider ?? {}) - Object.assign(existing.aisdk.request, variant.aisdk?.request ?? {}) } } if (config.cost !== undefined) { diff --git a/packages/core/src/config/provider.ts b/packages/core/src/config/provider.ts index fbb0e1c3ef..ad7f51d0b8 100644 --- a/packages/core/src/config/provider.ts +++ b/packages/core/src/config/provider.ts @@ -7,10 +7,6 @@ import { ModelV2 } from "../model" export class Options extends Schema.Class("ConfigV2.Provider.Options")({ headers: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional), body: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), - aisdk: Schema.Struct({ - provider: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), - request: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional), - }).pipe(Schema.optional), }) {} class Cache extends Schema.Class("ConfigV2.Model.Cost.Cache")({ diff --git a/packages/core/src/model.ts b/packages/core/src/model.ts index b0de0802a3..460e4fbb9b 100644 --- a/packages/core/src/model.ts +++ b/packages/core/src/model.ts @@ -85,10 +85,6 @@ export class Info extends Schema.Class("ModelV2.Info")({ options: { headers: {}, body: {}, - aisdk: { - provider: {}, - request: {}, - }, }, variants: [], time: { diff --git a/packages/core/src/plugin/account.ts b/packages/core/src/plugin/account.ts index 68bb43674d..f4e62c6c9a 100644 --- a/packages/core/src/plugin/account.ts +++ b/packages/core/src/plugin/account.ts @@ -32,10 +32,10 @@ export const AccountPlugin = PluginV2.define({ service: account.serviceID, } if (account.credential.type === "api") { - provider.options.aisdk.provider.apiKey = account.credential.key - Object.assign(provider.options.aisdk.provider, account.credential.metadata ?? {}) + provider.options.body.apiKey = account.credential.key + Object.assign(provider.options.body, account.credential.metadata ?? {}) } - if (account.credential.type === "oauth") provider.options.aisdk.provider.apiKey = account.credential.access + if (account.credential.type === "oauth") provider.options.body.apiKey = account.credential.access }) } }), diff --git a/packages/core/src/plugin/models-dev.ts b/packages/core/src/plugin/models-dev.ts index 7ee38ac6de..f3ad0d91c5 100644 --- a/packages/core/src/plugin/models-dev.ts +++ b/packages/core/src/plugin/models-dev.ts @@ -43,10 +43,6 @@ function variants(model: ModelsDev.Model) { id: ModelV2.VariantID.make(id), headers: { ...(item.provider?.headers ?? {}) }, body: { ...(item.provider?.body ?? {}) }, - aisdk: { - provider: {}, - request: {}, - }, })) } diff --git a/packages/core/src/plugin/provider/amazon-bedrock.ts b/packages/core/src/plugin/provider/amazon-bedrock.ts index e7452ac2e9..fbff332423 100644 --- a/packages/core/src/plugin/provider/amazon-bedrock.ts +++ b/packages/core/src/plugin/provider/amazon-bedrock.ts @@ -56,11 +56,11 @@ export const AmazonBedrockPlugin = PluginV2.define({ if (item.provider.endpoint.package !== "@ai-sdk/amazon-bedrock") continue evt.provider.update(item.provider.id, (provider) => { if (provider.endpoint.type !== "aisdk") return - if (typeof provider.options.aisdk.provider.endpoint !== "string") return + if (typeof provider.options.body.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.endpoint.url = provider.options.aisdk.provider.endpoint - delete provider.options.aisdk.provider.endpoint + provider.endpoint.url = provider.options.body.endpoint + delete provider.options.body.endpoint }) } }), diff --git a/packages/core/src/plugin/provider/azure.ts b/packages/core/src/plugin/provider/azure.ts index bea98cf211..e2e7989559 100644 --- a/packages/core/src/plugin/provider/azure.ts +++ b/packages/core/src/plugin/provider/azure.ts @@ -18,12 +18,12 @@ export const AzurePlugin = PluginV2.define({ for (const item of evt.provider.list()) { if (item.provider.endpoint.type !== "aisdk") continue if (item.provider.endpoint.package !== "@ai-sdk/azure") continue - const configured = item.provider.options.aisdk.provider.resourceName + const configured = item.provider.options.body.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.options.aisdk.provider.resourceName = resourceName + provider.options.body.resourceName = resourceName }) } }), @@ -63,7 +63,7 @@ export const AzureCognitiveServicesPlugin = PluginV2.define({ if (item.provider.endpoint.package !== "@ai-sdk/openai-compatible") continue if (!item.provider.id.includes("azure-cognitive-services")) continue evt.provider.update(item.provider.id, (provider) => { - provider.options.aisdk.provider.baseURL = `https://${resourceName}.cognitiveservices.azure.com/openai` + provider.options.body.baseURL = `https://${resourceName}.cognitiveservices.azure.com/openai` }) } }), diff --git a/packages/core/src/plugin/provider/cloudflare-workers-ai.ts b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts index 32cfb059f4..ed2d3a6918 100644 --- a/packages/core/src/plugin/provider/cloudflare-workers-ai.ts +++ b/packages/core/src/plugin/provider/cloudflare-workers-ai.ts @@ -16,7 +16,7 @@ export const CloudflareWorkersAIPlugin = PluginV2.define({ evt.provider.update(item.provider.id, (provider) => { if (provider.endpoint.type !== "aisdk") return if (provider.endpoint.url) return - const accountId = resolveAccountId(provider.options.aisdk.provider) + const accountId = resolveAccountId(provider.options.body) if (accountId) provider.endpoint.url = workersEndpoint(accountId) }) }), diff --git a/packages/core/src/plugin/provider/gitlab.ts b/packages/core/src/plugin/provider/gitlab.ts index 226f5a45eb..ae992043d0 100644 --- a/packages/core/src/plugin/provider/gitlab.ts +++ b/packages/core/src/plugin/provider/gitlab.ts @@ -37,12 +37,12 @@ export const GitLabPlugin = PluginV2.define({ if (evt.model.apiID.startsWith("duo-workflow-")) { const gitlab = yield* Effect.promise(() => import("gitlab-ai-provider")).pipe(Effect.orDie) const workflowRef = - typeof evt.model.options.aisdk.request.workflowRef === "string" - ? evt.model.options.aisdk.request.workflowRef + typeof evt.model.options.body.workflowRef === "string" + ? evt.model.options.body.workflowRef : undefined const workflowDefinition = - typeof evt.model.options.aisdk.request.workflowDefinition === "string" - ? evt.model.options.aisdk.request.workflowDefinition + typeof evt.model.options.body.workflowDefinition === "string" + ? evt.model.options.body.workflowDefinition : undefined const language = evt.sdk.workflowChat( gitlab.isWorkflowModel(evt.model.apiID) ? evt.model.apiID : "duo-workflow", diff --git a/packages/core/src/plugin/provider/google-vertex.ts b/packages/core/src/plugin/provider/google-vertex.ts index ae1692b933..3628918ee5 100644 --- a/packages/core/src/plugin/provider/google-vertex.ts +++ b/packages/core/src/plugin/provider/google-vertex.ts @@ -66,16 +66,16 @@ export const GoogleVertexPlugin = PluginV2.define({ !item.provider.endpoint.package.includes("@ai-sdk/openai-compatible") ) continue - const project = resolveProject(item.provider.options.aisdk.provider) - const location = String(resolveLocation(item.provider.options.aisdk.provider)) + const project = resolveProject(item.provider.options.body) + const location = String(resolveLocation(item.provider.options.body)) evt.provider.update(item.provider.id, (provider) => { - if (project) provider.options.aisdk.provider.project = project - provider.options.aisdk.provider.location = location + if (project) provider.options.body.project = project + provider.options.body.location = location if (provider.endpoint.type === "aisdk" && provider.endpoint.url) { provider.endpoint.url = replaceVertexVars(provider.endpoint.url, project, location) } if (provider.endpoint.type === "aisdk" && provider.endpoint.package.includes("@ai-sdk/openai-compatible")) { - provider.options.aisdk.provider.fetch = authFetch(provider.options.aisdk.provider.fetch) + provider.options.body.fetch = authFetch(provider.options.body.fetch) } }) } @@ -114,18 +114,18 @@ export const GoogleVertexAnthropicPlugin = PluginV2.define({ if (item.provider.endpoint.type !== "aisdk") continue if (item.provider.endpoint.package !== "@ai-sdk/google-vertex/anthropic") continue const project = - item.provider.options.aisdk.provider.project ?? + item.provider.options.body.project ?? process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCP_PROJECT ?? process.env.GCLOUD_PROJECT const location = - item.provider.options.aisdk.provider.location ?? + item.provider.options.body.location ?? process.env.GOOGLE_CLOUD_LOCATION ?? process.env.VERTEX_LOCATION ?? "global" evt.provider.update(item.provider.id, (provider) => { - if (project) provider.options.aisdk.provider.project = project - provider.options.aisdk.provider.location = location + if (project) provider.options.body.project = project + provider.options.body.location = location }) } }), diff --git a/packages/core/src/plugin/provider/opencode.ts b/packages/core/src/plugin/provider/opencode.ts index 64d20f8bd4..ba2be5c330 100644 --- a/packages/core/src/plugin/provider/opencode.ts +++ b/packages/core/src/plugin/provider/opencode.ts @@ -13,11 +13,11 @@ export const OpencodePlugin = PluginV2.define({ hasKey = Boolean( process.env.OPENCODE_API_KEY || item.provider.env.some((env) => process.env[env]) || - item.provider.options.aisdk.provider.apiKey || + item.provider.options.body.apiKey || (item.provider.enabled && item.provider.enabled.via === "account"), ) evt.provider.update(item.provider.id, (provider) => { - if (!hasKey) provider.options.aisdk.provider.apiKey = "public" + if (!hasKey) provider.options.body.apiKey = "public" }) if (hasKey) return for (const model of item.models.values()) { diff --git a/packages/core/src/provider.ts b/packages/core/src/provider.ts index 31127f2f3c..4cc60dc9b6 100644 --- a/packages/core/src/provider.ts +++ b/packages/core/src/provider.ts @@ -72,10 +72,6 @@ export type Endpoint = typeof Endpoint.Type export const Options = Schema.Struct({ headers: Schema.Record(Schema.String, Schema.String), body: Schema.Record(Schema.String, Schema.Any), - aisdk: Schema.Struct({ - provider: Schema.Record(Schema.String, Schema.Any), - request: Schema.Record(Schema.String, Schema.Any), - }), }) export type Options = typeof Options.Type @@ -113,10 +109,6 @@ export class Info extends Schema.Class("ProviderV2.Info")({ options: { headers: {}, body: {}, - aisdk: { - provider: {}, - request: {}, - }, }, } } diff --git a/packages/core/src/v1/config/migrate.ts b/packages/core/src/v1/config/migrate.ts index 4db2c2d887..0a34cbd9de 100644 --- a/packages/core/src/v1/config/migrate.ts +++ b/packages/core/src/v1/config/migrate.ts @@ -5,6 +5,7 @@ import { ConfigAgentV1 } from "./agent" import { ConfigMCPV1 } from "./mcp" import { ConfigPermissionV1 } from "./permission" import { ConfigProviderV1 } from "./provider" +import { ConfigProviderOptionsV1 } from "./provider-options" const keys = new Set([ "logLevel", @@ -99,7 +100,7 @@ function agents(info: typeof ConfigV1.Info.Type) { ...Object.entries(info.mode ?? {}).map(([name, agent]) => [name, { ...agent, mode: "primary" as const }] as const), ] if (!entries.length) return undefined - return Object.fromEntries(entries.map(([name, agent]) => [name, migrateAgent(agent)])) + return Object.fromEntries(entries.flatMap(([name, agent]) => (agent ? [[name, migrateAgent(agent)]] : []))) } function migrateAgent(info: ConfigAgentV1.Info) { @@ -160,22 +161,26 @@ function providers(info?: Readonly>) { } function migrateProvider(info: ConfigProviderV1.Info) { + const lowerer = ConfigProviderOptionsV1.get(info.npm) + const options = lowerer.provider(info.options ?? {}) return { name: info.name, env: info.env, - endpoint: info.npm && { - type: "aisdk" as const, - package: info.npm, - url: info.api ?? (typeof info.options?.baseURL === "string" ? info.options.baseURL : undefined), - }, - options: info.options && { body: info.options }, + endpoint: info.npm + ? { + type: "aisdk" as const, + package: info.npm, + url: info.api ?? options.url, + } + : undefined, + options: info.options && { headers: options.headers, body: options.body }, models: info.models && - Object.fromEntries(Object.entries(info.models).map(([name, model]) => [name, migrateModel(model)])), + Object.fromEntries(Object.entries(info.models).map(([name, model]) => [name, migrateModel(model, info.npm)])), } } -function migrateModel(info: typeof ConfigProviderV1.Model.Type) { +function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: string) { const costs = info.cost && [ { input: info.cost.input, @@ -197,16 +202,33 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type) { info.tool_call !== undefined || info.modalities?.input !== undefined || info.modalities?.output !== undefined ? { tools: info.tool_call ?? false, input: info.modalities?.input ?? [], output: info.modalities?.output ?? [] } : undefined + const lowerer = ConfigProviderOptionsV1.get(info.provider?.npm ?? packageName) return { api_id: info.id, family: info.family, name: info.name, - endpoint: info.provider?.npm && { type: "aisdk" as const, package: info.provider.npm, url: info.provider.api }, + endpoint: info.provider?.npm + ? { type: "aisdk" as const, package: info.provider.npm, url: info.provider.api } + : undefined, capabilities, - options: (info.headers || info.options) && { headers: info.headers, body: info.options }, - variants: info.variants && Object.entries(info.variants).map(([id, options]) => ({ id, body: options })), + options: (info.headers || info.options) && { + headers: info.headers, + body: info.options && lowerer.request(info.options), + }, + variants: + info.variants && + Object.entries(info.variants).map(([id, options]) => ({ id, body: lowerer.request(options) })), cost: costs, disabled: info.status === "deprecated" ? true : undefined, - limit: info.limit, + limit: + info.limit && { + context: int(info.limit.context), + input: info.limit.input === undefined ? undefined : int(info.limit.input), + output: int(info.limit.output), + }, } } + +function int(value: number) { + return Math.max(Number.MIN_SAFE_INTEGER, Math.min(Number.MAX_SAFE_INTEGER, Math.trunc(value))) +} diff --git a/packages/core/src/v1/config/provider-options.ts b/packages/core/src/v1/config/provider-options.ts new file mode 100644 index 0000000000..09482123b8 --- /dev/null +++ b/packages/core/src/v1/config/provider-options.ts @@ -0,0 +1,193 @@ +export * as ConfigProviderOptionsV1 from "./provider-options" + +type Options = Readonly> + +export interface ProviderResult { + readonly headers?: Record + readonly body?: Record + readonly url?: string +} + +export interface Lowerer { + readonly provider: (options: Options) => ProviderResult + readonly request: (options: Options) => Record +} + +export function get(packageName?: string): Lowerer { + return lowerers[packageName ?? ""] ?? raw +} + +const raw: Lowerer = { + provider: body, + request: clone, +} + +const openai: 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: omit(options, ["apiKey", "baseURL", "organization", "project", "headers", "name", "fetch"]), + } + }, + request: snake, +} + +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: omit(options, ["apiKey", "authToken", "baseURL", "headers", "name", "fetch", "generateId"]), + } + }, + 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 = { ...options.metadata, user_id: options.metadata.userId } + delete (result.metadata as Record).userId + } + return result + }, +} + +const google: Lowerer = { + provider(options) { + return { + url: string(options.baseURL), + headers: compact({ "x-goog-api-key": string(options.apiKey), ...headers(options.headers) }), + body: omit(options, ["apiKey", "baseURL", "headers", "name", "fetch", "generateId"]), + } + }, + 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: omit(options, ["apiKey", "baseURL", "headers", "name", "fetch", "tokenProvider"]), + } + }, + request: openai.request, +} + +const bedrock: Lowerer = { + provider(options) { + return { headers: headers(options.headers), body: omit(options, ["headers", "fetch"]) } + }, + request(options) { + return { additionalModelRequestFields: clone(options) } + }, +} + +const openaiCompatible: Lowerer = { + provider(options) { + return { url: string(options.baseURL), headers: headers(options.headers), body: omit(options, ["baseURL", "headers", "name", "fetch"]) } + }, + 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 body(options: Options): ProviderResult { + return { body: clone(options) } +} + +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) +} diff --git a/packages/core/test/account.test.ts b/packages/core/test/account.test.ts index c0d1479ef5..b81fe440d8 100644 --- a/packages/core/test/account.test.ts +++ b/packages/core/test/account.test.ts @@ -33,8 +33,8 @@ function context( id: providerID, enabled: provider.enabled, apiKey: - typeof provider.options.aisdk.provider.apiKey === "string" - ? provider.options.aisdk.provider.apiKey + typeof provider.options.body.apiKey === "string" + ? provider.options.body.apiKey : undefined, }) }, diff --git a/packages/core/test/catalog.test.ts b/packages/core/test/catalog.test.ts index 121b1fe012..7a7d19b34f 100644 --- a/packages/core/test/catalog.test.ts +++ b/packages/core/test/catalog.test.ts @@ -33,7 +33,7 @@ describe("CatalogV2", () => { package: "@ai-sdk/openai-compatible", url: "https://default.example.com", } - provider.options.aisdk.provider.baseURL = "https://override.example.com" + provider.options.body.baseURL = "https://override.example.com" }), ) @@ -62,7 +62,7 @@ describe("CatalogV2", () => { }) catalog.model.update(providerID, modelID, (model) => { model.endpoint = { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://model.example.com" } - model.options.aisdk.provider.baseURL = "https://override.example.com" + model.options.body.baseURL = "https://override.example.com" }) }) @@ -117,14 +117,14 @@ describe("CatalogV2", () => { if (!item) return seen.push(item.provider.endpoint.type) if (item?.provider.endpoint.type === "aisdk") seen.push(item.provider.endpoint.url) - seen.push(item?.provider.options.aisdk.provider.baseURL) + seen.push(item?.provider.options.body.baseURL) }), }), }) yield* transform((catalog) => catalog.provider.update(providerID, (provider) => { provider.endpoint = { type: "aisdk", package: "@ai-sdk/openai-compatible" } - provider.options.aisdk.provider.baseURL = "https://provider.example.com" + provider.options.body.baseURL = "https://provider.example.com" }), ) @@ -199,22 +199,22 @@ describe("CatalogV2", () => { provider.options.headers.provider = "provider" provider.options.headers.shared = "provider" provider.options.body.provider = true - provider.options.aisdk.provider.provider = true + provider.options.body.provider = true }) catalog.model.update(providerID, modelID, (model) => { model.options.headers.model = "model" model.options.headers.shared = "model" model.options.body.model = true - model.options.aisdk.provider.model = true - model.options.aisdk.request.request = true + model.options.body.model = true + model.options.body.request = true }) }) const model = yield* catalog.model.get(providerID, modelID) expect(model.options.headers).toEqual({ provider: "provider", shared: "model", model: "model" }) expect(model.options.body).toEqual({ provider: true, model: true }) - expect(model.options.aisdk.provider).toEqual({ provider: true, model: true }) - expect(model.options.aisdk.request).toEqual({ request: true }) + expect(model.options.body).toEqual({ provider: true, model: true }) + expect(model.options.body).toEqual({ request: true }) }), ) diff --git a/packages/core/test/config/agent.test.ts b/packages/core/test/config/agent.test.ts index dac2933cc1..2924abc7ad 100644 --- a/packages/core/test/config/agent.test.ts +++ b/packages/core/test/config/agent.test.ts @@ -109,8 +109,7 @@ describe("ConfigAgentPlugin.Plugin", () => { steps: 12, options: { headers: { first: "one", shared: "first" }, - body: { enabled: true }, - aisdk: { provider: { profile: "review" }, request: { effort: "medium" } }, + body: { enabled: true, profile: "review", effort: "medium" }, }, }, }, @@ -123,8 +122,7 @@ describe("ConfigAgentPlugin.Plugin", () => { reviewer: { options: { headers: { shared: "last", second: "two" }, - body: { retries: 2 }, - aisdk: { request: { effort: "high" } }, + body: { retries: 2, effort: "high" }, }, }, }, @@ -151,8 +149,7 @@ describe("ConfigAgentPlugin.Plugin", () => { }) expect(reviewer.options).toEqual({ headers: { first: "one", shared: "last", second: "two" }, - body: { enabled: true, retries: 2 }, - aisdk: { provider: { profile: "review" }, request: { effort: "high" } }, + body: { enabled: true, profile: "review", retries: 2, effort: "high" }, }) }), ) diff --git a/packages/core/test/config/config.test.ts b/packages/core/test/config/config.test.ts index 070183c6d2..634344c9af 100644 --- a/packages/core/test/config/config.test.ts +++ b/packages/core/test/config/config.test.ts @@ -1,10 +1,12 @@ import path from "path" import fs from "fs/promises" import { describe, expect } from "bun:test" -import { Effect, Layer } from "effect" +import { Effect, Layer, Schema } from "effect" +import { FastCheck } from "effect/testing" import { Config } from "@opencode-ai/core/config" import { ConfigProvider } from "@opencode-ai/core/config/provider" import { ConfigMigrateV1 } from "@opencode-ai/core/v1/config/migrate" +import { ConfigV1 } from "@opencode-ai/core/v1/config/config" import { FSUtil } from "@opencode-ai/core/fs-util" import { Global } from "@opencode-ai/core/global" import { Location } from "@opencode-ai/core/location" @@ -45,10 +47,6 @@ const provider = { options: { headers: {}, body: {}, - aisdk: { - provider: {}, - request: {}, - }, }, models: {}, } @@ -62,6 +60,17 @@ describe("Config", () => { }), ) + it.effect("migrates arbitrary v1 configuration into valid v2 configuration", () => + Effect.sync(() => { + FastCheck.assert( + FastCheck.property(Schema.toArbitrary(ConfigV1.Info), (info) => { + Schema.decodeUnknownSync(Config.Info)(ConfigMigrateV1.migrate(info), { errors: "all" }) + }), + { numRuns: 100 }, + ) + }), + ) + it.live("returns an empty configuration when directory files do not exist", () => Effect.acquireRelease( Effect.promise(() => tmpdir()), @@ -188,7 +197,7 @@ describe("Config", () => { variant: "high", options: { headers: { "x-agent": "reviewer" }, - aisdk: { request: { reasoningEffort: "high" } }, + body: { reasoningEffort: "high" }, }, description: "Review changes for correctness", system: "Find regressions.", @@ -271,7 +280,7 @@ describe("Config", () => { variant: "high", options: { headers: { "x-agent": "reviewer" }, - aisdk: { request: { reasoningEffort: "high" } }, + body: { reasoningEffort: "high" }, }, description: "Review changes for correctness", system: "Find regressions.", @@ -379,6 +388,24 @@ describe("Config", () => { skills: { paths: ["./skills"], urls: ["https://example.com/.well-known/skills/"] }, reference: { docs: { path: "../docs" } }, attachment: { image: { auto_resize: false, max_width: 1200 } }, + provider: { + custom: { + options: { apiKey: "secret" }, + models: { + model: { + options: { reasoningEffort: "high" }, + variants: { fast: { temperature: 0.2 } }, + }, + }, + }, + openai: { + npm: "@ai-sdk/openai", + options: { apiKey: "secret", organization: "org" }, + models: { + model: { options: { reasoningEffort: "high", serviceTier: "priority" } }, + }, + }, + }, compaction: { auto: true, tail_turns: 3, preserve_recent_tokens: 2000, reserved: 10000 }, experimental: { mcp_timeout: 5000 }, mcp: { @@ -420,6 +447,19 @@ describe("Config", () => { expect(documents[0]?.info.skills).toEqual(["./skills", "https://example.com/.well-known/skills/"]) expect(documents[0]?.info.references).toEqual({ docs: { path: "../docs" } }) expect(documents[0]?.info.attachments).toEqual({ image: { auto_resize: false, max_width: 1200 } }) + expect(documents[0]?.info.providers?.custom).toMatchObject({ + options: { body: { apiKey: "secret" } }, + models: { + model: { + options: { body: { reasoningEffort: "high" } }, + variants: [{ id: "fast", body: { temperature: 0.2 } }], + }, + }, + }) + expect(documents[0]?.info.providers?.openai).toMatchObject({ + options: { headers: { Authorization: "Bearer secret", "OpenAI-Organization": "org" } }, + models: { model: { options: { body: { reasoning_effort: "high", service_tier: "priority" } } } }, + }) expect(documents[0]?.info.compaction).toEqual({ auto: true, prune: undefined, diff --git a/packages/core/test/plugin/provider-amazon-bedrock.test.ts b/packages/core/test/plugin/provider-amazon-bedrock.test.ts index 602e85624b..4e75065411 100644 --- a/packages/core/test/plugin/provider-amazon-bedrock.test.ts +++ b/packages/core/test/plugin/provider-amazon-bedrock.test.ts @@ -30,8 +30,7 @@ describe("AmazonBedrockPlugin", () => { endpoint: { type: "aisdk", package: "@ai-sdk/amazon-bedrock" }, options: { headers: {}, - body: {}, - aisdk: { provider: { endpoint: "https://bedrock.example" }, request: {} }, + body: { endpoint: "https://bedrock.example" }, }, }) catalog.provider.update(bedrock.id, (item) => { @@ -45,7 +44,7 @@ describe("AmazonBedrockPlugin", () => { package: "@ai-sdk/amazon-bedrock", url: "https://bedrock.example", }) - expect(result.options.aisdk.provider.endpoint).toBeUndefined() + expect(result.options.body.endpoint).toBeUndefined() }), ) diff --git a/packages/core/test/plugin/provider-anthropic.test.ts b/packages/core/test/plugin/provider-anthropic.test.ts index 6cae612fd2..a5964da72b 100644 --- a/packages/core/test/plugin/provider-anthropic.test.ts +++ b/packages/core/test/plugin/provider-anthropic.test.ts @@ -16,7 +16,7 @@ describe("AnthropicPlugin", () => { yield* transform((catalog) => { const item = provider("anthropic", { endpoint: { type: "aisdk", package: "@ai-sdk/anthropic" }, - options: { headers: { Existing: "1" }, body: {}, aisdk: { provider: {}, request: {} } }, + options: { headers: { Existing: "1" }, body: {}, }, }) catalog.provider.update(item.id, (draft) => { draft.endpoint = item.endpoint 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 a3837a66ad..04eb0951f0 100644 --- a/packages/core/test/plugin/provider-azure-cognitive-services.test.ts +++ b/packages/core/test/plugin/provider-azure-cognitive-services.test.ts @@ -25,8 +25,8 @@ describe("AzureCognitiveServicesPlugin", () => { package: "@ai-sdk/openai-compatible", url: "https://cognitive.cognitiveservices.azure.com/openai", }) - expect(result.options.aisdk.provider.baseURL).toBeUndefined() - expect(result.options.aisdk.provider.resourceName).toBeUndefined() + expect(result.options.body.baseURL).toBeUndefined() + expect(result.options.body.resourceName).toBeUndefined() }), ), ) @@ -52,9 +52,9 @@ describe("AzureCognitiveServicesPlugin", () => { }) const azure = yield* catalog.provider.get(ProviderV2.ID.make("azure-cognitive-services")) const openai = yield* catalog.provider.get(ProviderV2.ID.openai) - expect(azure.options.aisdk.provider.baseURL).toBeUndefined() + expect(azure.options.body.baseURL).toBeUndefined() expect(azure.endpoint).toEqual({ type: "aisdk", package: "@ai-sdk/openai-compatible" }) - expect(openai.options.aisdk.provider.baseURL).toBeUndefined() + expect(openai.options.body.baseURL).toBeUndefined() expect(openai.endpoint).toEqual({ type: "aisdk", package: "test-provider" }) }), ), diff --git a/packages/core/test/plugin/provider-azure.test.ts b/packages/core/test/plugin/provider-azure.test.ts index 1b917c5af8..d8c7880329 100644 --- a/packages/core/test/plugin/provider-azure.test.ts +++ b/packages/core/test/plugin/provider-azure.test.ts @@ -37,7 +37,7 @@ describe("AzurePlugin", () => { item.endpoint = { type: "aisdk", package: "@ai-sdk/azure" } }) }) - expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.aisdk.provider.resourceName).toBe("from-env") + expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.body.resourceName).toBe("from-env") }), ), ) @@ -52,7 +52,7 @@ describe("AzurePlugin", () => { yield* transform((catalog) => { const azure = provider("azure", { endpoint: { type: "aisdk", package: "@ai-sdk/azure" }, - options: { headers: {}, body: {}, aisdk: { provider: { resourceName: "from-config" }, request: {} } }, + options: { headers: {}, body: { resourceName: "from-config" }, }, }) catalog.provider.update(azure.id, (item) => { item.endpoint = azure.endpoint @@ -60,10 +60,10 @@ describe("AzurePlugin", () => { }) catalog.provider.update(ProviderV2.ID.openai, () => {}) }) - expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.aisdk.provider.resourceName).toBe( + expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.body.resourceName).toBe( "from-config", ) - expect((yield* catalog.provider.get(ProviderV2.ID.openai)).options.aisdk.provider.resourceName).toBeUndefined() + expect((yield* catalog.provider.get(ProviderV2.ID.openai)).options.body.resourceName).toBeUndefined() }), ), ) @@ -103,7 +103,7 @@ describe("AzurePlugin", () => { item.endpoint = { type: "aisdk", package: "@ai-sdk/azure" } }) }) - expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.aisdk.provider.resourceName).toBe( + expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.body.resourceName).toBe( "from-account", ) }), @@ -120,14 +120,14 @@ describe("AzurePlugin", () => { yield* transform((catalog) => { const azure = provider("azure", { endpoint: { type: "aisdk", package: "@ai-sdk/azure" }, - options: { headers: {}, body: {}, aisdk: { provider: { resourceName: "" }, request: {} } }, + options: { headers: {}, body: { resourceName: "" }, }, }) catalog.provider.update(azure.id, (item) => { item.endpoint = azure.endpoint item.options = azure.options }) }) - expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.aisdk.provider.resourceName).toBe("from-env") + expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.body.resourceName).toBe("from-env") }), ), ) @@ -142,14 +142,14 @@ describe("AzurePlugin", () => { yield* transform((catalog) => { const azure = provider("azure", { endpoint: { type: "aisdk", package: "@ai-sdk/azure" }, - options: { headers: {}, body: {}, aisdk: { provider: { resourceName: " " }, request: {} } }, + options: { headers: {}, body: { resourceName: " " }, }, }) catalog.provider.update(azure.id, (item) => { item.endpoint = azure.endpoint item.options = azure.options }) }) - expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.aisdk.provider.resourceName).toBe("from-env") + expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.body.resourceName).toBe("from-env") }), ), ) @@ -227,7 +227,7 @@ describe("AzurePlugin", () => { "aisdk.language", { model: model("azure", "deployment", { - options: { headers: {}, body: {}, aisdk: { provider: {}, request: { useCompletionUrls: true } } }, + options: { headers: {}, body: { useCompletionUrls: true }, }, }), sdk: fakeSelectorSdk(calls), options: {}, 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 980269b37e..61a8a77272 100644 --- a/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts +++ b/packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts @@ -174,7 +174,7 @@ describe("CloudflareWorkersAIPlugin", () => { yield* transform((catalog) => catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => { provider.endpoint = { type: "aisdk", package: "test-provider" } - provider.options.aisdk.provider.accountId = "configured-acct" + provider.options.body.accountId = "configured-acct" }), ) expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).endpoint).toEqual({ diff --git a/packages/core/test/plugin/provider-gitlab.test.ts b/packages/core/test/plugin/provider-gitlab.test.ts index b2cba8e465..5d6c4d27f0 100644 --- a/packages/core/test/plugin/provider-gitlab.test.ts +++ b/packages/core/test/plugin/provider-gitlab.test.ts @@ -190,7 +190,7 @@ describe("GitLabPlugin", () => { { model: model("gitlab", "claude"), package: "gitlab-ai-provider", - options: provider.options.aisdk.provider, + options: provider.options.body, }, {}, ) @@ -238,7 +238,7 @@ describe("GitLabPlugin", () => { { model: model("gitlab", "claude"), package: "gitlab-ai-provider", - options: provider.options.aisdk.provider, + options: provider.options.body, }, {}, ) @@ -258,8 +258,7 @@ describe("GitLabPlugin", () => { model: model("gitlab", "duo-workflow-custom", { options: { headers: {}, - body: {}, - aisdk: { provider: {}, request: { workflowRef: "ref", workflowDefinition: "definition" } }, + body: { workflowRef: "ref", workflowDefinition: "definition" }, }, }), sdk: { @@ -322,8 +321,7 @@ describe("GitLabPlugin", () => { model: model("gitlab", "duo-workflow-custom", { options: { headers: {}, - body: {}, - aisdk: { provider: {}, request: { featureFlags: { request_flag: true } } }, + body: { featureFlags: { request_flag: true } }, }, }), sdk: { @@ -350,7 +348,7 @@ describe("GitLabPlugin", () => { "aisdk.language", { model: model("gitlab", "claude", { - options: { headers: { h: "v" }, body: {}, aisdk: { provider: {}, request: {} } }, + options: { headers: { h: "v" }, body: {}, }, }), 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 9d23dfefb3..be97599d98 100644 --- a/packages/core/test/plugin/provider-google-vertex-anthropic.test.ts +++ b/packages/core/test/plugin/provider-google-vertex-anthropic.test.ts @@ -29,8 +29,8 @@ describe("GoogleVertexAnthropicPlugin", () => { }), ) const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")) - expect(provider.options.aisdk.provider.project).toBe("cloud-project") - expect(provider.options.aisdk.provider.location).toBe("cloud-location") + expect(provider.options.body.project).toBe("cloud-project") + expect(provider.options.body.location).toBe("cloud-location") }), ), ) @@ -45,13 +45,13 @@ describe("GoogleVertexAnthropicPlugin", () => { yield* transform((catalog) => catalog.provider.update(ProviderV2.ID.make("google-vertex-anthropic"), (provider) => { provider.endpoint = { type: "aisdk", package: "@ai-sdk/google-vertex/anthropic" } - provider.options.aisdk.provider.project = "configured-project" - provider.options.aisdk.provider.location = "configured-location" + provider.options.body.project = "configured-project" + provider.options.body.location = "configured-location" }), ) const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")) - expect(provider.options.aisdk.provider.project).toBe("configured-project") - expect(provider.options.aisdk.provider.location).toBe("configured-location") + expect(provider.options.body.project).toBe("configured-project") + expect(provider.options.body.location).toBe("configured-location") }), ), ) diff --git a/packages/core/test/plugin/provider-google-vertex.test.ts b/packages/core/test/plugin/provider-google-vertex.test.ts index 2abb342cc3..bf9a3a31df 100644 --- a/packages/core/test/plugin/provider-google-vertex.test.ts +++ b/packages/core/test/plugin/provider-google-vertex.test.ts @@ -61,8 +61,8 @@ describe("GoogleVertexPlugin", () => { }), ) const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex")) - expect(provider.options.aisdk.provider.project).toBe("google-cloud-project") - expect(provider.options.aisdk.provider.location).toBe("google-vertex-location") + expect(provider.options.body.project).toBe("google-cloud-project") + expect(provider.options.body.location).toBe("google-vertex-location") expect(provider.endpoint).toEqual({ type: "aisdk", package: "@ai-sdk/openai-compatible", @@ -112,7 +112,7 @@ describe("GoogleVertexPlugin", () => { {}, ) - expect(provider.options.aisdk.provider.project).toBe("vertex-project") + expect(provider.options.body.project).toBe("vertex-project") expect(provider.endpoint).toEqual({ type: "aisdk", package: "@ai-sdk/openai-compatible", @@ -147,13 +147,13 @@ describe("GoogleVertexPlugin", () => { package: "@ai-sdk/openai-compatible", url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}", } - provider.options.aisdk.provider.project = "config-project" - provider.options.aisdk.provider.location = "global" + provider.options.body.project = "config-project" + provider.options.body.location = "global" }), ) const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex")) - expect(provider.options.aisdk.provider.project).toBe("config-project") - expect(provider.options.aisdk.provider.location).toBe("global") + expect(provider.options.body.project).toBe("config-project") + expect(provider.options.body.location).toBe("global") expect(provider.endpoint).toEqual({ type: "aisdk", package: "@ai-sdk/openai-compatible", @@ -176,8 +176,8 @@ describe("GoogleVertexPlugin", () => { package: "@ai-sdk/openai-compatible", url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}", } - provider.options.aisdk.provider.project = "config-project" - provider.options.aisdk.provider.location = "eu" + provider.options.body.project = "config-project" + provider.options.body.location = "eu" }), ) const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex")) @@ -208,12 +208,12 @@ describe("GoogleVertexPlugin", () => { yield* transform((catalog) => catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => { provider.endpoint = { type: "aisdk", package: "@ai-sdk/google-vertex" } - provider.options.aisdk.provider.project = "config-project" + provider.options.body.project = "config-project" }), ) const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex")) - expect(provider.options.aisdk.provider.project).toBe("config-project") - expect(provider.options.aisdk.provider.location).toBe("us-central1") + expect(provider.options.body.project).toBe("config-project") + expect(provider.options.body.location).toBe("us-central1") }), ), ) diff --git a/packages/core/test/plugin/provider-google.test.ts b/packages/core/test/plugin/provider-google.test.ts index 8844208e4b..0086b83902 100644 --- a/packages/core/test/plugin/provider-google.test.ts +++ b/packages/core/test/plugin/provider-google.test.ts @@ -58,11 +58,7 @@ describe("GooglePlugin", () => { }, options: { headers: {}, - body: {}, - aisdk: { - provider: { apiKey: "test" }, - request: {}, - }, + body: { apiKey: "test" }, }, }), ) diff --git a/packages/core/test/plugin/provider-groq.test.ts b/packages/core/test/plugin/provider-groq.test.ts index 0eb3f538b8..13bf632d68 100644 --- a/packages/core/test/plugin/provider-groq.test.ts +++ b/packages/core/test/plugin/provider-groq.test.ts @@ -89,11 +89,7 @@ describe("GroqPlugin", () => { }, options: { headers: {}, - body: {}, - aisdk: { - provider: { apiKey: "test" }, - request: {}, - }, + body: { apiKey: "test" }, }, }), ) diff --git a/packages/core/test/plugin/provider-helper.ts b/packages/core/test/plugin/provider-helper.ts index f99510ac70..0d04951222 100644 --- a/packages/core/test/plugin/provider-helper.ts +++ b/packages/core/test/plugin/provider-helper.ts @@ -65,10 +65,6 @@ export function provider(providerID: string, options?: Partial) options: { headers: {}, body: {}, - aisdk: { - provider: {}, - request: {}, - }, ...options?.options, }, }) @@ -86,10 +82,6 @@ export function model(providerID: string, modelID: string, options?: Partial { yield* transform((catalog) => { const kilo = provider("kilo", { endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" }, - options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } }, + options: { headers: { Existing: "value" }, body: {}, }, }) catalog.provider.update(kilo.id, (draft) => { draft.endpoint = kilo.endpoint diff --git a/packages/core/test/plugin/provider-llmgateway.test.ts b/packages/core/test/plugin/provider-llmgateway.test.ts index 980abe2258..896f3e5320 100644 --- a/packages/core/test/plugin/provider-llmgateway.test.ts +++ b/packages/core/test/plugin/provider-llmgateway.test.ts @@ -27,7 +27,7 @@ describe("LLMGatewayPlugin", () => { const llmgateway = provider("llmgateway", { enabled: { via: "env", name: "LLMGATEWAY_API_KEY" }, endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.llmgateway.io/v1" }, - options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } }, + options: { headers: { Existing: "value" }, body: {}, }, }) catalog.provider.update(llmgateway.id, (draft) => { draft.enabled = llmgateway.enabled diff --git a/packages/core/test/plugin/provider-nvidia.test.ts b/packages/core/test/plugin/provider-nvidia.test.ts index dea0000717..4237d6012e 100644 --- a/packages/core/test/plugin/provider-nvidia.test.ts +++ b/packages/core/test/plugin/provider-nvidia.test.ts @@ -26,7 +26,7 @@ describe("NvidiaPlugin", () => { yield* transform((catalog) => { const nvidia = provider("nvidia", { endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" }, - options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } }, + options: { headers: { Existing: "value" }, body: {}, }, }) catalog.provider.update(nvidia.id, (draft) => { draft.endpoint = nvidia.endpoint @@ -53,7 +53,7 @@ describe("NvidiaPlugin", () => { yield* transform((catalog) => { const item = provider("nvidia", { endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" }, - options: { headers: {}, body: {}, aisdk: { provider: {}, request: {} } }, + options: { headers: {}, body: {}, }, }) catalog.provider.update(item.id, (draft) => { draft.endpoint = item.endpoint @@ -80,8 +80,7 @@ describe("NvidiaPlugin", () => { endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" }, options: { headers: { "X-BILLING-INVOKE-ORIGIN": "CustomOrigin" }, - body: {}, - aisdk: { provider: { baseURL: "https://integrate.api.nvidia.com/v1" }, request: {} }, + body: { baseURL: "https://integrate.api.nvidia.com/v1" }, }, }) catalog.provider.update(item.id, (draft) => { diff --git a/packages/core/test/plugin/provider-opencode.test.ts b/packages/core/test/plugin/provider-opencode.test.ts index 4054880711..e9d2dd6d35 100644 --- a/packages/core/test/plugin/provider-opencode.test.ts +++ b/packages/core/test/plugin/provider-opencode.test.ts @@ -33,7 +33,7 @@ describe("OpencodePlugin", () => { draft.cost = [...paid.cost] }) }) - expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBe("public") + expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.body.apiKey).toBe("public") expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(false) }), ), @@ -54,7 +54,7 @@ describe("OpencodePlugin", () => { draft.cost = [...free.cost] }) }) - expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBe("public") + expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.body.apiKey).toBe("public") expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("free"))).enabled).toBe(true) }), ), @@ -75,7 +75,7 @@ describe("OpencodePlugin", () => { draft.cost = [...outputOnly.cost] }) }) - expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBe("public") + expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.body.apiKey).toBe("public") expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("output-only"))).enabled).toBe(true) }), ), @@ -96,7 +96,7 @@ describe("OpencodePlugin", () => { draft.cost = [...paid.cost] }) }) - expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBeUndefined() + expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.body.apiKey).toBeUndefined() expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true) }), ), @@ -119,7 +119,7 @@ describe("OpencodePlugin", () => { draft.cost = [...paid.cost] }) }) - expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBeUndefined() + expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.body.apiKey).toBeUndefined() expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true) }), ), @@ -136,11 +136,7 @@ describe("OpencodePlugin", () => { const item = provider("opencode", { options: { headers: {}, - body: {}, - aisdk: { - provider: { apiKey: "configured" }, - request: {}, - }, + body: { apiKey: "configured" }, }, }) catalog.provider.update(item.id, (draft) => { @@ -151,7 +147,7 @@ describe("OpencodePlugin", () => { draft.cost = [...paid.cost] }) }) - expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBe("configured") + expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.body.apiKey).toBe("configured") expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true) }), ), @@ -174,7 +170,7 @@ describe("OpencodePlugin", () => { draft.cost = [...paid.cost] }) }) - expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.aisdk.provider.apiKey).toBeUndefined() + expect((yield* catalog.provider.get(ProviderV2.ID.opencode)).options.body.apiKey).toBeUndefined() expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true) }), ), @@ -195,7 +191,7 @@ describe("OpencodePlugin", () => { draft.cost = [...paid.cost] }) }) - expect((yield* catalog.provider.get(ProviderV2.ID.openai)).options.aisdk.provider.apiKey).toBeUndefined() + expect((yield* catalog.provider.get(ProviderV2.ID.openai)).options.body.apiKey).toBeUndefined() expect((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 d19575a9bb..249955b69c 100644 --- a/packages/core/test/plugin/provider-openrouter.test.ts +++ b/packages/core/test/plugin/provider-openrouter.test.ts @@ -27,7 +27,7 @@ describe("OpenRouterPlugin", () => { yield* transform((catalog) => { const openrouter = provider("openrouter", { endpoint: { type: "aisdk", package: "@openrouter/ai-sdk-provider" }, - options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } }, + options: { headers: { Existing: "value" }, body: {}, }, }) catalog.provider.update(openrouter.id, (item) => { item.endpoint = openrouter.endpoint diff --git a/packages/core/test/plugin/provider-vercel.test.ts b/packages/core/test/plugin/provider-vercel.test.ts index ba34281196..875e488bba 100644 --- a/packages/core/test/plugin/provider-vercel.test.ts +++ b/packages/core/test/plugin/provider-vercel.test.ts @@ -16,7 +16,7 @@ describe("VercelPlugin", () => { yield* transform((catalog) => { const item = provider("vercel", { endpoint: { type: "aisdk", package: "@ai-sdk/vercel" }, - options: { headers: { Existing: "1" }, body: {}, aisdk: { provider: {}, request: {} } }, + options: { headers: { Existing: "1" }, body: {}, }, }) catalog.provider.update(item.id, (draft) => { draft.endpoint = item.endpoint diff --git a/packages/core/test/plugin/provider-zenmux.test.ts b/packages/core/test/plugin/provider-zenmux.test.ts index c5899627cf..2974ce66a3 100644 --- a/packages/core/test/plugin/provider-zenmux.test.ts +++ b/packages/core/test/plugin/provider-zenmux.test.ts @@ -46,7 +46,7 @@ describe("ZenmuxPlugin", () => { yield* transform((catalog) => { const item = provider("zenmux", { endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" }, - options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } }, + options: { headers: { Existing: "value" }, body: {}, }, }) catalog.provider.update(item.id, (draft) => { draft.endpoint = item.endpoint @@ -74,7 +74,6 @@ describe("ZenmuxPlugin", () => { options: { headers: { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" }, body: {}, - aisdk: { provider: {}, request: {} }, }, }) catalog.provider.update(item.id, (draft) => { @@ -101,7 +100,6 @@ describe("ZenmuxPlugin", () => { options: { headers: { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" }, body: {}, - aisdk: { provider: {}, request: {} }, }, }) catalog.provider.update(item.id, (draft) => { diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 2f279b663c..7a3fbcac39 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -2730,14 +2730,6 @@ export type ModelV2Info = { body: { [key: string]: unknown } - aisdk: { - provider: { - [key: string]: unknown - } - request: { - [key: string]: unknown - } - } variant?: string } variants: Array<{ @@ -2748,14 +2740,6 @@ export type ModelV2Info = { body: { [key: string]: unknown } - aisdk: { - provider: { - [key: string]: unknown - } - request: { - [key: string]: unknown - } - } }> time: { released: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" @@ -3676,14 +3660,6 @@ export type ProviderV2Info = { body: { [key: string]: unknown } - aisdk: { - provider: { - [key: string]: unknown - } - request: { - [key: string]: unknown - } - } } } @@ -3789,14 +3765,6 @@ export type ModelV2Info1 = { body: { [key: string]: unknown } - aisdk: { - provider: { - [key: string]: unknown - } - request: { - [key: string]: unknown - } - } variant?: string } variants: Array<{ @@ -3807,14 +3775,6 @@ export type ModelV2Info1 = { body: { [key: string]: unknown } - aisdk: { - provider: { - [key: string]: unknown - } - request: { - [key: string]: unknown - } - } }> time: { released: number | "NaN" | "Infinity" | "-Infinity"