Ver Fonte

refactor(core): remove ai sdk option fields (#30581)

Dax há 3 meses atrás
pai
commit
1520b0de20
57 ficheiros alterados com 825 adições e 686 exclusões
  1. 2 6
      packages/core/src/agent.ts
  2. 12 8
      packages/core/src/aisdk.ts
  3. 22 27
      packages/core/src/catalog.ts
  4. 1 1
      packages/core/src/config/agent.ts
  5. 3 5
      packages/core/src/config/plugin/agent.ts
  6. 9 19
      packages/core/src/config/plugin/provider.ts
  7. 7 11
      packages/core/src/config/provider.ts
  8. 10 13
      packages/core/src/model.ts
  9. 3 3
      packages/core/src/plugin/account.ts
  10. 8 8
      packages/core/src/plugin/models-dev.ts
  11. 6 6
      packages/core/src/plugin/provider/amazon-bedrock.ts
  12. 3 3
      packages/core/src/plugin/provider/anthropic.ts
  13. 8 8
      packages/core/src/plugin/provider/azure.ts
  14. 3 3
      packages/core/src/plugin/provider/cerebras.ts
  15. 7 7
      packages/core/src/plugin/provider/cloudflare-workers-ai.ts
  16. 4 4
      packages/core/src/plugin/provider/gitlab.ts
  17. 17 17
      packages/core/src/plugin/provider/google-vertex.ts
  18. 5 5
      packages/core/src/plugin/provider/kilo.ts
  19. 6 6
      packages/core/src/plugin/provider/llmgateway.ts
  20. 6 6
      packages/core/src/plugin/provider/nvidia.ts
  21. 2 2
      packages/core/src/plugin/provider/openai.ts
  22. 2 2
      packages/core/src/plugin/provider/opencode.ts
  23. 4 4
      packages/core/src/plugin/provider/openrouter.ts
  24. 4 4
      packages/core/src/plugin/provider/vercel.ts
  25. 5 5
      packages/core/src/plugin/provider/zenmux.ts
  26. 17 52
      packages/core/src/provider.ts
  27. 37 14
      packages/core/src/v1/config/migrate.ts
  28. 210 0
      packages/core/src/v1/config/provider-options.ts
  29. 1 4
      packages/core/test/account.test.ts
  30. 28 31
      packages/core/test/catalog.test.ts
  31. 6 9
      packages/core/test/config/agent.test.ts
  32. 94 25
      packages/core/test/config/config.test.ts
  33. 11 11
      packages/core/test/config/provider.test.ts
  34. 8 9
      packages/core/test/plugin/provider-amazon-bedrock.test.ts
  35. 7 7
      packages/core/test/plugin/provider-anthropic.test.ts
  36. 11 11
      packages/core/test/plugin/provider-azure-cognitive-services.test.ts
  37. 21 21
      packages/core/test/plugin/provider-azure.test.ts
  38. 4 4
      packages/core/test/plugin/provider-cerebras.test.ts
  39. 14 14
      packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts
  40. 1 1
      packages/core/test/plugin/provider-deepinfra.test.ts
  41. 4 4
      packages/core/test/plugin/provider-dynamic.test.ts
  42. 7 9
      packages/core/test/plugin/provider-gitlab.test.ts
  43. 8 8
      packages/core/test/plugin/provider-google-vertex-anthropic.test.ts
  44. 24 24
      packages/core/test/plugin/provider-google-vertex.test.ts
  45. 3 7
      packages/core/test/plugin/provider-google.test.ts
  46. 3 7
      packages/core/test/plugin/provider-groq.test.ts
  47. 18 16
      packages/core/test/plugin/provider-helper.ts
  48. 18 18
      packages/core/test/plugin/provider-kilo.test.ts
  49. 9 9
      packages/core/test/plugin/provider-llmgateway.test.ts
  50. 17 18
      packages/core/test/plugin/provider-nvidia.test.ts
  51. 2 2
      packages/core/test/plugin/provider-openai.test.ts
  52. 11 15
      packages/core/test/plugin/provider-opencode.test.ts
  53. 8 8
      packages/core/test/plugin/provider-openrouter.test.ts
  54. 10 10
      packages/core/test/plugin/provider-vercel.test.ts
  55. 1 1
      packages/core/test/plugin/provider-xai.test.ts
  56. 17 19
      packages/core/test/plugin/provider-zenmux.test.ts
  57. 36 115
      packages/sdk/js/src/v2/gen/types.gen.ts

+ 2 - 6
packages/core/src/agent.ts

@@ -19,7 +19,7 @@ export const Color = Schema.Union([
 export class Info extends Schema.Class<Info>("AgentV2.Info")({
   id: ID,
   model: ModelV2.Ref.pipe(Schema.optional),
-  options: ProviderV2.Options,
+  request: ProviderV2.Request,
   system: Schema.String.pipe(Schema.optional),
   description: Schema.String.pipe(Schema.optional),
   mode: Schema.Literals(["subagent", "primary", "all"]),
@@ -31,13 +31,9 @@ export class Info extends Schema.Class<Info>("AgentV2.Info")({
   static empty(id: ID) {
     return new Info({
       id,
-      options: {
+      request: {
         headers: {},
         body: {},
-        aisdk: {
-          provider: {},
-          request: {},
-        },
       },
       mode: "all",
       hidden: false,

+ 12 - 8
packages/core/src/aisdk.ts

@@ -58,8 +58,12 @@ function wrapSSE(res: Response, ms: number, ctl: AbortController) {
 }
 
 function prepareOptions(model: ModelV2.Info, pkg: string) {
-  const options: Record<string, any> = { name: model.providerID, ...model.options.aisdk.provider }
-  if (model.endpoint.type === "aisdk" && model.endpoint.url) options.baseURL = model.endpoint.url
+  const options: Record<string, any> = {
+    name: model.providerID,
+    ...(model.api.type === "aisdk" ? (model.api.settings ?? {}) : {}),
+    ...model.request.body,
+  }
+  if (model.api.type === "aisdk" && model.api.url) options.baseURL = model.api.url
 
   const customFetch = options.fetch
   const chunkTimeout = options.chunkTimeout
@@ -123,25 +127,25 @@ export const layer = Layer.effect(
 
     return Service.of({
       language: Effect.fn("AISDK.language")(function* (model) {
-        const key = `${model.providerID}/${model.id}/${model.options.variant ?? "default"}`
+        const key = `${model.providerID}/${model.id}/${model.request.variant ?? "default"}`
         const existing = languages.get(key)
         if (existing) return existing
-        if (model.endpoint.type !== "aisdk")
+        if (model.api.type !== "aisdk")
           return yield* new InitError({
             providerID: model.providerID,
-            cause: new Error(`Unsupported endpoint ${model.endpoint.type}`),
+            cause: new Error(`Unsupported api ${model.api.type}`),
           })
 
-        const options = prepareOptions(model, model.endpoint.package)
+        const options = prepareOptions(model, model.api.package)
         const sdkKey = JSON.stringify({
           providerID: model.providerID,
-          endpoint: model.endpoint,
+          api: model.api,
           options,
         })
         const sdk =
           sdks.get(sdkKey) ??
           (yield* plugin
-            .trigger("aisdk.sdk", { model, package: model.endpoint.package, options }, {})
+            .trigger("aisdk.sdk", { model, package: model.api.package, options }, {})
             .pipe(initError(model.providerID))).sdk
         if (!sdk)
           return yield* new InitError({

+ 22 - 27
packages/core/src/catalog.ts

@@ -97,34 +97,29 @@ export const layer = Layer.effect(
 
     const resolve = (model: ModelV2.Info) => {
       const provider = state.get().providers.get(model.providerID)!.provider
-      const endpoint =
-        model.endpoint.type === "unknown"
-          ? provider.endpoint
-          : model.endpoint.type === "aisdk" && provider.endpoint.type === "aisdk" && !model.endpoint.url
-            ? { ...model.endpoint, url: provider.endpoint.url }
-            : model.endpoint
-      const options = {
+      const api =
+        model.api.type === "native" && !model.api.url && Object.keys(model.api.settings).length === 0
+          ? provider.api
+          : 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 = {
         headers: {
-          ...provider.options.headers,
-          ...model.options.headers,
+          ...provider.request.headers,
+          ...model.request.headers,
         },
         body: {
-          ...provider.options.body,
-          ...model.options.body,
+          ...provider.request.body,
+          ...model.request.body,
         },
-        aisdk: {
-          provider: {
-            ...provider.options.aisdk.provider,
-            ...model.options.aisdk.provider,
-          },
-          request: model.options.aisdk.request,
-        },
-        variant: model.options.variant,
+        variant: model.request.variant,
       }
       return new ModelV2.Info({
         ...model,
-        endpoint,
-        options,
+        api,
+        request,
       })
     }
 
@@ -134,10 +129,10 @@ export const layer = Layer.effect(
       return match
     }
 
-    const normalizeEndpoint = (item: Draft<ProviderV2.Info> | Draft<ModelV2.Info>) => {
-      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
+    const normalizeApi = (item: Draft<ProviderV2.Info> | Draft<ModelV2.Info>) => {
+      if (typeof item.request.body.baseURL !== "string") return
+      item.api.url = item.request.body.baseURL
+      delete item.request.body.baseURL
     }
 
     const state = State.create<Data, Editor>({
@@ -157,7 +152,7 @@ export const layer = Layer.effect(
                 draft.providers.set(providerID, current)
               }
               fn(current.provider)
-              normalizeEndpoint(current.provider)
+              normalizeApi(current.provider)
             },
             remove: (providerID) => {
               draft.providers.delete(providerID)
@@ -179,7 +174,7 @@ export const layer = Layer.effect(
               fn(model)
               model.id = modelID
               model.providerID = providerID
-              normalizeEndpoint(model)
+              normalizeApi(model)
             },
             remove: (providerID, modelID) => {
               draft.providers.get(providerID)?.models.delete(modelID)

+ 1 - 1
packages/core/src/config/agent.ts

@@ -13,7 +13,7 @@ export const Color = Schema.Union([
 export class Info extends Schema.Class<Info>("ConfigV2.Agent")({
   model: Schema.String.pipe(Schema.optional),
   variant: Schema.String.pipe(Schema.optional),
-  options: ConfigProvider.Options.pipe(Schema.optional),
+  request: ConfigProvider.Request.pipe(Schema.optional),
   system: Schema.String.pipe(Schema.optional),
   description: Schema.String.pipe(Schema.optional),
   mode: Schema.Literals(["subagent", "primary", "all"]).pipe(Schema.optional),

+ 3 - 5
packages/core/src/config/plugin/agent.ts

@@ -34,11 +34,9 @@ export const Plugin = PluginV2.define({
             if (item.variant !== undefined && agent.model !== undefined) {
               agent.model.variant = ModelV2.VariantID.make(item.variant)
             }
-            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.request !== undefined) {
+              Object.assign(agent.request.headers, item.request.headers ?? {})
+              Object.assign(agent.request.body, item.request.body ?? {})
             }
             if (item.system !== undefined) agent.system = item.system
             if (item.description !== undefined) agent.description = item.description

+ 9 - 19
packages/core/src/config/plugin/provider.ts

@@ -23,12 +23,10 @@ export const Plugin = PluginV2.define({
             if (item.name !== undefined) provider.name = item.name
             if (item.env !== undefined) provider.env = [...item.env]
             provider.enabled = { via: "custom", data: {} }
-            if (item.endpoint !== undefined) provider.endpoint = { ...item.endpoint }
-            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 ?? {})
+            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 ?? {})
             }
           })
 
@@ -37,7 +35,7 @@ export const Plugin = PluginV2.define({
               if (config.api_id !== undefined) model.apiID = config.api_id
               if (config.family !== undefined) model.family = config.family
               if (config.name !== undefined) model.name = config.name
-              if (config.endpoint !== undefined) model.endpoint = { ...config.endpoint }
+              if (config.api !== undefined) model.api = { ...config.api }
               if (config.capabilities !== undefined) {
                 model.capabilities = {
                   tools: config.capabilities.tools,
@@ -45,12 +43,10 @@ export const Plugin = PluginV2.define({
                   output: [...config.capabilities.output],
                 }
               }
-              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.request !== undefined) {
+                Object.assign(model.request.headers, config.request.headers ?? {})
+                Object.assign(model.request.body, config.request.body ?? {})
+                if (config.request.variant !== undefined) model.request.variant = config.request.variant
               }
               if (config.variants !== undefined) {
                 for (const variant of config.variants) {
@@ -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) {

+ 7 - 11
packages/core/src/config/provider.ts

@@ -4,13 +4,9 @@ import { Schema } from "effect"
 import { ProviderV2 } from "../provider"
 import { ModelV2 } from "../model"
 
-export class Options extends Schema.Class<Options>("ConfigV2.Provider.Options")({
+export class Request extends Schema.Class<Request>("ConfigV2.Provider.Request")({
   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<Cache>("ConfigV2.Model.Cost.Cache")({
@@ -38,15 +34,15 @@ class Model extends Schema.Class<Model>("ConfigV2.Model")({
   api_id: ModelV2.ID.pipe(Schema.optional),
   family: ModelV2.Family.pipe(Schema.optional),
   name: Schema.String.pipe(Schema.optional),
-  endpoint: ProviderV2.Endpoint.pipe(Schema.optional),
+  api: ProviderV2.Api.pipe(Schema.optional),
   capabilities: ModelV2.Capabilities.pipe(Schema.optional),
-  options: Schema.Struct({
-    ...Options.fields,
+  request: Schema.Struct({
+    ...Request.fields,
     variant: Schema.String.pipe(Schema.optional),
   }).pipe(Schema.optional),
   variants: Schema.Struct({
     id: ModelV2.VariantID,
-    ...Options.fields,
+    ...Request.fields,
   }).pipe(Schema.Array, Schema.optional),
   cost: Schema.Union([Cost, Cost.pipe(Schema.Array)]).pipe(Schema.optional),
   disabled: Schema.Boolean.pipe(Schema.optional),
@@ -56,7 +52,7 @@ class Model extends Schema.Class<Model>("ConfigV2.Model")({
 export class Info extends Schema.Class<Info>("ConfigV2.Provider")({
   name: Schema.String.pipe(Schema.optional),
   env: Schema.String.pipe(Schema.Array, Schema.optional),
-  endpoint: ProviderV2.Endpoint.pipe(Schema.optional),
-  options: Options.pipe(Schema.optional),
+  api: ProviderV2.Api.pipe(Schema.optional),
+  request: Request.pipe(Schema.optional),
   models: Schema.Record(Schema.String, Model).pipe(Schema.optional),
 }) {}

+ 10 - 13
packages/core/src/model.ts

@@ -46,15 +46,15 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
   providerID: ProviderV2.ID,
   family: Family.pipe(Schema.optional),
   name: Schema.String,
-  endpoint: ProviderV2.Endpoint,
+  api: ProviderV2.Api,
   capabilities: Capabilities,
-  options: Schema.Struct({
-    ...ProviderV2.Options.fields,
+  request: Schema.Struct({
+    ...ProviderV2.Request.fields,
     variant: Schema.String.pipe(Schema.optional),
   }),
   variants: Schema.Struct({
     id: VariantID,
-    ...ProviderV2.Options.fields,
+    ...ProviderV2.Request.fields,
   }).pipe(Schema.Array),
   time: Schema.Struct({
     released: DateTimeUtcFromMillis,
@@ -69,26 +69,23 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
   }),
 }) {
   static empty(providerID: ProviderV2.ID, modelID: ID): Info {
-    return {
+    return new Info({
       id: modelID,
       apiID: modelID,
       providerID,
       name: modelID,
-      endpoint: {
-        type: "unknown",
+      api: {
+        type: "native",
+        settings: {},
       },
       capabilities: {
         tools: false,
         input: [],
         output: [],
       },
-      options: {
+      request: {
         headers: {},
         body: {},
-        aisdk: {
-          provider: {},
-          request: {},
-        },
       },
       variants: [],
       time: {
@@ -101,7 +98,7 @@ export class Info extends Schema.Class<Info>("ModelV2.Info")({
         context: 0,
         output: 0,
       },
-    }
+    })
   }
 }
 

+ 3 - 3
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.request.body.apiKey = account.credential.key
+              Object.assign(provider.request.body, account.credential.metadata ?? {})
             }
-            if (account.credential.type === "oauth") provider.options.aisdk.provider.apiKey = account.credential.access
+            if (account.credential.type === "oauth") provider.request.body.apiKey = account.credential.access
           })
         }
       }),

+ 8 - 8
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: {},
-    },
   }))
 }
 
@@ -66,14 +62,16 @@ export const ModelsDevPlugin = PluginV2.define({
           catalog.provider.update(providerID, (provider) => {
             provider.name = item.name
             provider.env = [...item.env]
-            provider.endpoint = item.npm
+            provider.api = item.npm
               ? {
                   type: "aisdk",
                   package: item.npm,
                   url: item.api,
                 }
               : {
-                  type: "unknown",
+                  type: "native",
+                  url: item.api,
+                  settings: {},
                 }
           })
 
@@ -82,14 +80,16 @@ export const ModelsDevPlugin = PluginV2.define({
             catalog.model.update(providerID, modelID, (draft) => {
               draft.name = model.name
               draft.family = model.family ? ModelV2.Family.make(model.family) : undefined
-              draft.endpoint = model.provider?.npm
+              draft.api = model.provider?.npm
                 ? {
                     type: "aisdk",
                     package: model.provider?.npm,
                     url: model.provider.api,
                   }
                 : {
-                    type: "unknown",
+                    type: "native",
+                    url: model.provider?.api,
+                    settings: {},
                   }
               draft.capabilities = {
                 tools: model.tool_call,

+ 6 - 6
packages/core/src/plugin/provider/amazon-bedrock.ts

@@ -52,15 +52,15 @@ export const AmazonBedrockPlugin = PluginV2.define({
     return {
       "catalog.transform": Effect.fn(function* (evt) {
         for (const item of evt.provider.list()) {
-          if (item.provider.endpoint.type !== "aisdk") continue
-          if (item.provider.endpoint.package !== "@ai-sdk/amazon-bedrock") continue
+          if (item.provider.api.type !== "aisdk") continue
+          if (item.provider.api.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 (provider.api.type !== "aisdk") return
+            if (typeof provider.request.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.api.url = provider.request.body.endpoint
+            delete provider.request.body.endpoint
           })
         }
       }),

+ 3 - 3
packages/core/src/plugin/provider/anthropic.ts

@@ -7,10 +7,10 @@ export const AnthropicPlugin = PluginV2.define({
     return {
       "catalog.transform": Effect.fn(function* (evt) {
         for (const item of evt.provider.list()) {
-          if (item.provider.endpoint.type !== "aisdk") continue
-          if (item.provider.endpoint.package !== "@ai-sdk/anthropic") continue
+          if (item.provider.api.type !== "aisdk") continue
+          if (item.provider.api.package !== "@ai-sdk/anthropic") continue
           evt.provider.update(item.provider.id, (provider) => {
-            provider.options.headers["anthropic-beta"] =
+            provider.request.headers["anthropic-beta"] =
               "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14"
           })
         }

+ 8 - 8
packages/core/src/plugin/provider/azure.ts

@@ -16,14 +16,14 @@ export const AzurePlugin = PluginV2.define({
     return {
       "catalog.transform": Effect.fn(function* (evt) {
         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
+          if (item.provider.api.type !== "aisdk") continue
+          if (item.provider.api.package !== "@ai-sdk/azure") continue
+          const configured = item.provider.request.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.request.body.resourceName = resourceName
           })
         }
       }),
@@ -33,7 +33,7 @@ export const AzurePlugin = PluginV2.define({
           if (
             !evt.options.resourceName &&
             !evt.options.baseURL &&
-            (evt.model.endpoint.type !== "aisdk" || !evt.model.endpoint.url)
+            (evt.model.api.type !== "aisdk" || !evt.model.api.url)
           ) {
             throw new Error(
               "AZURE_RESOURCE_NAME is missing, set it using env var or reconnecting the azure provider and setting it",
@@ -59,11 +59,11 @@ export const AzureCognitiveServicesPlugin = PluginV2.define({
         const resourceName = process.env.AZURE_COGNITIVE_SERVICES_RESOURCE_NAME
         if (!resourceName) return
         for (const item of evt.provider.list()) {
-          if (item.provider.endpoint.type !== "aisdk") continue
-          if (item.provider.endpoint.package !== "@ai-sdk/openai-compatible") continue
+          if (item.provider.api.type !== "aisdk") continue
+          if (item.provider.api.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.request.body.baseURL = `https://${resourceName}.cognitiveservices.azure.com/openai`
           })
         }
       }),

+ 3 - 3
packages/core/src/plugin/provider/cerebras.ts

@@ -7,10 +7,10 @@ export const CerebrasPlugin = PluginV2.define({
     return {
       "catalog.transform": Effect.fn(function* (ctx) {
         for (const item of ctx.provider.list()) {
-          if (item.provider.endpoint.type !== "aisdk") continue
-          if (item.provider.endpoint.package !== "@ai-sdk/cerebras") continue
+          if (item.provider.api.type !== "aisdk") continue
+          if (item.provider.api.package !== "@ai-sdk/cerebras") continue
           ctx.provider.update(item.provider.id, (provider) => {
-            provider.options.headers["X-Cerebras-3rd-Party-Integration"] = "opencode"
+            provider.request.headers["X-Cerebras-3rd-Party-Integration"] = "opencode"
           })
         }
       }),

+ 7 - 7
packages/core/src/plugin/provider/cloudflare-workers-ai.ts

@@ -14,17 +14,17 @@ export const CloudflareWorkersAIPlugin = PluginV2.define({
         const item = evt.provider.get(providerID)
         if (!item) return
         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)
-          if (accountId) provider.endpoint.url = workersEndpoint(accountId)
+          if (provider.api.type !== "aisdk") return
+          if (provider.api.url) return
+          const accountId = resolveAccountId(provider.request.body)
+          if (accountId) provider.api.url = workersEndpoint(accountId)
         })
       }),
       "aisdk.sdk": Effect.fn(function* (evt) {
         if (evt.model.providerID !== providerID) return
         if (evt.package !== "@ai-sdk/openai-compatible") return
 
-        if (!hasWorkersEndpoint(evt.model.endpoint)) return
+        if (!hasWorkersEndpoint(evt.model.api)) return
         const mod = yield* Effect.promise(() => import("@ai-sdk/openai-compatible"))
         evt.sdk = mod.createOpenAICompatible(sdkOptions(evt.options) as any)
       }),
@@ -44,8 +44,8 @@ function workersEndpoint(accountId: string) {
   return `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`
 }
 
-function hasWorkersEndpoint(endpoint: ProviderV2.Endpoint) {
-  return endpoint.type === "aisdk" && Boolean(endpoint.url)
+function hasWorkersEndpoint(api: ProviderV2.Api) {
+  return api.type === "aisdk" && Boolean(api.url)
 }
 
 function sdkOptions(options: Record<string, any>) {

+ 4 - 4
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.request.body.workflowRef === "string"
+              ? evt.model.request.body.workflowRef
               : undefined
           const workflowDefinition =
-            typeof evt.model.options.aisdk.request.workflowDefinition === "string"
-              ? evt.model.options.aisdk.request.workflowDefinition
+            typeof evt.model.request.body.workflowDefinition === "string"
+              ? evt.model.request.body.workflowDefinition
               : undefined
           const language = evt.sdk.workflowChat(
             gitlab.isWorkflowModel(evt.model.apiID) ? evt.model.apiID : "duo-workflow",

+ 17 - 17
packages/core/src/plugin/provider/google-vertex.ts

@@ -60,22 +60,22 @@ export const GoogleVertexPlugin = PluginV2.define({
     return {
       "catalog.transform": Effect.fn(function* (evt) {
         for (const item of evt.provider.list()) {
-          if (item.provider.endpoint.type !== "aisdk") continue
+          if (item.provider.api.type !== "aisdk") continue
           if (
-            item.provider.endpoint.package !== "@ai-sdk/google-vertex" &&
-            !item.provider.endpoint.package.includes("@ai-sdk/openai-compatible")
+            item.provider.api.package !== "@ai-sdk/google-vertex" &&
+            !item.provider.api.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.request.body)
+          const location = String(resolveLocation(item.provider.request.body))
           evt.provider.update(item.provider.id, (provider) => {
-            if (project) provider.options.aisdk.provider.project = project
-            provider.options.aisdk.provider.location = location
-            if (provider.endpoint.type === "aisdk" && provider.endpoint.url) {
-              provider.endpoint.url = replaceVertexVars(provider.endpoint.url, project, location)
+            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.endpoint.type === "aisdk" && provider.endpoint.package.includes("@ai-sdk/openai-compatible")) {
-              provider.options.aisdk.provider.fetch = authFetch(provider.options.aisdk.provider.fetch)
+            if (provider.api.type === "aisdk" && provider.api.package.includes("@ai-sdk/openai-compatible")) {
+              provider.request.body.fetch = authFetch(provider.request.body.fetch)
             }
           })
         }
@@ -111,21 +111,21 @@ export const GoogleVertexAnthropicPlugin = PluginV2.define({
     return {
       "catalog.transform": Effect.fn(function* (evt) {
         for (const item of evt.provider.list()) {
-          if (item.provider.endpoint.type !== "aisdk") continue
-          if (item.provider.endpoint.package !== "@ai-sdk/google-vertex/anthropic") continue
+          if (item.provider.api.type !== "aisdk") continue
+          if (item.provider.api.package !== "@ai-sdk/google-vertex/anthropic") continue
           const project =
-            item.provider.options.aisdk.provider.project ??
+            item.provider.request.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.request.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.request.body.project = project
+            provider.request.body.location = location
           })
         }
       }),

+ 5 - 5
packages/core/src/plugin/provider/kilo.ts

@@ -7,12 +7,12 @@ export const KiloPlugin = PluginV2.define({
     return {
       "catalog.transform": Effect.fn(function* (evt) {
         for (const item of evt.provider.list()) {
-          if (item.provider.endpoint.type !== "aisdk") continue
-          if (item.provider.endpoint.package !== "@ai-sdk/openai-compatible") continue
-          if (item.provider.endpoint.url !== "https://api.kilo.ai/api/gateway") 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.kilo.ai/api/gateway") continue
           evt.provider.update(item.provider.id, (provider) => {
-            provider.options.headers["HTTP-Referer"] = "https://opencode.ai/"
-            provider.options.headers["X-Title"] = "opencode"
+            provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
+            provider.request.headers["X-Title"] = "opencode"
           })
         }
       }),

+ 6 - 6
packages/core/src/plugin/provider/llmgateway.ts

@@ -8,13 +8,13 @@ export const LLMGatewayPlugin = PluginV2.define({
       "catalog.transform": Effect.fn(function* (evt) {
         for (const item of evt.provider.list()) {
           if (item.provider.enabled === false) continue
-          if (item.provider.endpoint.type !== "aisdk") continue
-          if (item.provider.endpoint.package !== "@ai-sdk/openai-compatible") continue
-          if (item.provider.endpoint.url !== "https://api.llmgateway.io/v1") 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
           evt.provider.update(item.provider.id, (provider) => {
-            provider.options.headers["HTTP-Referer"] = "https://opencode.ai/"
-            provider.options.headers["X-Title"] = "opencode"
-            provider.options.headers["X-Source"] = "opencode"
+            provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
+            provider.request.headers["X-Title"] = "opencode"
+            provider.request.headers["X-Source"] = "opencode"
           })
         }
       }),

+ 6 - 6
packages/core/src/plugin/provider/nvidia.ts

@@ -7,13 +7,13 @@ export const NvidiaPlugin = PluginV2.define({
     return {
       "catalog.transform": Effect.fn(function* (evt) {
         for (const item of evt.provider.list()) {
-          if (item.provider.endpoint.type !== "aisdk") continue
-          if (item.provider.endpoint.package !== "@ai-sdk/openai-compatible") continue
-          if (item.provider.endpoint.url !== "https://integrate.api.nvidia.com/v1") continue
+          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
           evt.provider.update(item.provider.id, (provider) => {
-            provider.options.headers["HTTP-Referer"] = "https://opencode.ai/"
-            provider.options.headers["X-Title"] = "opencode"
-            provider.options.headers["X-BILLING-INVOKE-ORIGIN"] ??= "OpenCode"
+            provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
+            provider.request.headers["X-Title"] = "opencode"
+            provider.request.headers["X-BILLING-INVOKE-ORIGIN"] ??= "OpenCode"
           })
         }
       }),

+ 2 - 2
packages/core/src/plugin/provider/openai.ts

@@ -18,8 +18,8 @@ export const OpenAIPlugin = PluginV2.define({
       }),
       "catalog.transform": Effect.fn(function* (evt) {
         for (const item of evt.provider.list()) {
-          if (item.provider.endpoint.type !== "aisdk") continue
-          if (item.provider.endpoint.package !== "@ai-sdk/openai") continue
+          if (item.provider.api.type !== "aisdk") continue
+          if (item.provider.api.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

+ 2 - 2
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.request.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.request.body.apiKey = "public"
         })
         if (hasKey) return
         for (const model of item.models.values()) {

+ 4 - 4
packages/core/src/plugin/provider/openrouter.ts

@@ -8,11 +8,11 @@ export const OpenRouterPlugin = PluginV2.define({
     return {
       "catalog.transform": Effect.fn(function* (evt) {
         for (const item of evt.provider.list()) {
-          if (item.provider.endpoint.type !== "aisdk") continue
-          if (item.provider.endpoint.package !== "@openrouter/ai-sdk-provider") continue
+          if (item.provider.api.type !== "aisdk") continue
+          if (item.provider.api.package !== "@openrouter/ai-sdk-provider") continue
           evt.provider.update(item.provider.id, (provider) => {
-            provider.options.headers["HTTP-Referer"] = "https://opencode.ai/"
-            provider.options.headers["X-Title"] = "opencode"
+            provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
+            provider.request.headers["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

+ 4 - 4
packages/core/src/plugin/provider/vercel.ts

@@ -7,11 +7,11 @@ export const VercelPlugin = PluginV2.define({
     return {
       "catalog.transform": Effect.fn(function* (evt) {
         for (const item of evt.provider.list()) {
-          if (item.provider.endpoint.type !== "aisdk") continue
-          if (item.provider.endpoint.package !== "@ai-sdk/vercel") continue
+          if (item.provider.api.type !== "aisdk") continue
+          if (item.provider.api.package !== "@ai-sdk/vercel") continue
           evt.provider.update(item.provider.id, (provider) => {
-            provider.options.headers["http-referer"] = "https://opencode.ai/"
-            provider.options.headers["x-title"] = "opencode"
+            provider.request.headers["http-referer"] = "https://opencode.ai/"
+            provider.request.headers["x-title"] = "opencode"
           })
         }
       }),

+ 5 - 5
packages/core/src/plugin/provider/zenmux.ts

@@ -7,12 +7,12 @@ export const ZenmuxPlugin = PluginV2.define({
     return {
       "catalog.transform": Effect.fn(function* (evt) {
         for (const item of evt.provider.list()) {
-          if (item.provider.endpoint.type !== "aisdk") continue
-          if (item.provider.endpoint.package !== "@ai-sdk/openai-compatible") continue
-          if (item.provider.endpoint.url !== "https://zenmux.ai/api/v1") continue
+          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
           evt.provider.update(item.provider.id, (provider) => {
-            provider.options.headers["HTTP-Referer"] ??= "https://opencode.ai/"
-            provider.options.headers["X-Title"] ??= "opencode"
+            provider.request.headers["HTTP-Referer"] ??= "https://opencode.ai/"
+            provider.request.headers["X-Title"] ??= "opencode"
           })
         }
       }),

+ 17 - 52
packages/core/src/provider.ts

@@ -25,59 +25,27 @@ export type ID = typeof ID.Type
 export const ModelID = Schema.String.pipe(Schema.brand("ModelID"))
 export type ModelID = typeof ModelID.Type
 
-const OpenAIResponses = Schema.Struct({
-  type: Schema.Literal("openai/responses"),
-  url: Schema.String,
-  websocket: Schema.optional(Schema.Boolean),
-})
-
-const OpenAICompletions = Schema.Struct({
-  type: Schema.Literal("openai/completions"),
-  url: Schema.String,
-  reasoning: Schema.Union([
-    Schema.Struct({
-      type: Schema.Literal("reasoning_content"),
-    }),
-    Schema.Struct({
-      type: Schema.Literal("reasoning_details"),
-    }),
-  ]).pipe(Schema.optional),
-})
-export type OpenAICompletions = typeof OpenAICompletions.Type
-
 const AISDK = Schema.Struct({
   type: Schema.Literal("aisdk"),
   package: Schema.String,
   url: Schema.String.pipe(Schema.optional),
+  settings: Schema.Record(Schema.String, Schema.Unknown).pipe(Schema.optional),
 })
 
-const AnthropicMessages = Schema.Struct({
-  type: Schema.Literal("anthropic/messages"),
-  url: Schema.String,
-})
-
-const UnknownEndpoint = Schema.Struct({
-  type: Schema.Literal("unknown"),
+const Native = Schema.Struct({
+  type: Schema.Literal("native"),
+  url: Schema.String.pipe(Schema.optional),
+  settings: Schema.Record(Schema.String, Schema.Unknown),
 })
 
-export const Endpoint = Schema.Union([
-  UnknownEndpoint,
-  OpenAIResponses,
-  OpenAICompletions,
-  AnthropicMessages,
-  AISDK,
-]).pipe(Schema.toTaggedUnion("type"))
-export type Endpoint = typeof Endpoint.Type
+export const Api = Schema.Union([AISDK, Native]).pipe(Schema.toTaggedUnion("type"))
+export type Api = typeof Api.Type
 
-export const Options = Schema.Struct({
+export const Request = 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
+export type Request = typeof Request.Type
 
 export class Info extends Schema.Class<Info>("ProviderV2.Info")({
   id: ID,
@@ -98,26 +66,23 @@ export class Info extends Schema.Class<Info>("ProviderV2.Info")({
     }),
   ]),
   env: Schema.String.pipe(Schema.Array),
-  endpoint: Endpoint,
-  options: Options,
+  api: Api,
+  request: Request,
 }) {
   static empty(providerID: ID): Info {
-    return {
+    return new Info({
       id: providerID,
       name: providerID,
       enabled: false,
       env: [],
-      endpoint: {
-        type: "unknown",
+      api: {
+        type: "native",
+        settings: {},
       },
-      options: {
+      request: {
         headers: {},
         body: {},
-        aisdk: {
-          provider: {},
-          request: {},
-        },
       },
-    }
+    })
   }
 }

+ 37 - 14
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) {
@@ -111,7 +112,7 @@ function migrateAgent(info: ConfigAgentV1.Info) {
   return {
     model: info.model,
     variant: info.variant,
-    options: Object.keys(body).length ? { body } : undefined,
+    request: Object.keys(body).length ? { body } : undefined,
     system: info.prompt,
     description: info.description,
     mode: info.mode,
@@ -160,22 +161,27 @@ function providers(info?: Readonly<Record<string, ConfigProviderV1.Info>>) {
 }
 
 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 },
+    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 },
     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 +203,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 },
+    api: info.provider?.npm
+      ? { type: "aisdk" as const, package: info.provider.npm, url: info.provider.api, settings: {} }
+      : 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 })),
+    request: (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)))
+}

+ 210 - 0
packages/core/src/v1/config/provider-options.ts

@@ -0,0 +1,210 @@
+export * as ConfigProviderOptionsV1 from "./provider-options"
+
+type Options = Readonly<Record<string, unknown>>
+
+export interface ProviderResult {
+  readonly headers?: Record<string, string>
+  readonly body?: Record<string, unknown>
+  readonly url?: string
+  readonly settings?: Record<string, unknown>
+}
+
+export interface Lowerer {
+  readonly provider: (options: Options) => ProviderResult
+  readonly request: (options: Options) => Record<string, unknown>
+}
+
+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 = {
+  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"]),
+    }
+  },
+  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: 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 = { ...options.metadata, user_id: options.metadata.userId }
+      delete (result.metadata as Record<string, unknown>).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: 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<Record<string, Lowerer>> = {
+  "@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<string> = []): 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<string>) {
+  return Object.fromEntries(Object.entries(options).filter(([key]) => !keys.includes(key)))
+}
+
+function pick(options: Options, keys: ReadonlyArray<string>) {
+  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<string, string | undefined>) {
+  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<string, unknown>) {
+  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<string, unknown> {
+  return typeof input === "object" && input !== null && !Array.isArray(input)
+}

+ 1 - 4
packages/core/test/account.test.ts

@@ -32,10 +32,7 @@ function context(
         updates.push({
           id: providerID,
           enabled: provider.enabled,
-          apiKey:
-            typeof provider.options.aisdk.provider.apiKey === "string"
-              ? provider.options.aisdk.provider.apiKey
-              : undefined,
+          apiKey: typeof provider.request.body.apiKey === "string" ? provider.request.body.apiKey : undefined,
         })
       },
       remove: (providerID) => {

+ 28 - 31
packages/core/test/catalog.test.ts

@@ -20,7 +20,7 @@ const it = testEffect(
 )
 
 describe("CatalogV2", () => {
-  it.effect("normalizes provider baseURL into endpoint url", () =>
+  it.effect("normalizes provider baseURL into api url", () =>
     Effect.gen(function* () {
       const catalog = yield* Catalog.Service
       const providerID = ProviderV2.ID.make("test")
@@ -28,16 +28,16 @@ describe("CatalogV2", () => {
 
       yield* transform((catalog) =>
         catalog.provider.update(providerID, (provider) => {
-          provider.endpoint = {
+          provider.api = {
             type: "aisdk",
             package: "@ai-sdk/openai-compatible",
             url: "https://default.example.com",
           }
-          provider.options.aisdk.provider.baseURL = "https://override.example.com"
+          provider.request.body.baseURL = "https://override.example.com"
         }),
       )
 
-      expect((yield* catalog.provider.get(providerID)).endpoint).toEqual({
+      expect((yield* catalog.provider.get(providerID)).api).toEqual({
         type: "aisdk",
         package: "@ai-sdk/openai-compatible",
         url: "https://override.example.com",
@@ -45,7 +45,7 @@ describe("CatalogV2", () => {
     }),
   )
 
-  it.effect("normalizes model baseURL into endpoint url", () =>
+  it.effect("normalizes model baseURL into api url", () =>
     Effect.gen(function* () {
       const catalog = yield* Catalog.Service
       const providerID = ProviderV2.ID.make("test")
@@ -54,27 +54,28 @@ describe("CatalogV2", () => {
 
       yield* transform((catalog) => {
         catalog.provider.update(providerID, (provider) => {
-          provider.endpoint = {
+          provider.api = {
             type: "aisdk",
             package: "@ai-sdk/openai-compatible",
             url: "https://provider.example.com",
           }
         })
         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.api = { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://model.example.com" }
+          model.request.body.baseURL = "https://override.example.com"
         })
       })
 
-      expect((yield* catalog.model.get(providerID, modelID)).endpoint).toEqual({
+      expect((yield* catalog.model.get(providerID, modelID)).api).toEqual({
         type: "aisdk",
         package: "@ai-sdk/openai-compatible",
         url: "https://override.example.com",
+        settings: {},
       })
     }),
   )
 
-  it.effect("resolves unknown model endpoint from provider endpoint", () =>
+  it.effect("resolves default model api from provider api", () =>
     Effect.gen(function* () {
       const catalog = yield* Catalog.Service
       const providerID = ProviderV2.ID.make("test")
@@ -83,7 +84,7 @@ describe("CatalogV2", () => {
 
       yield* transform((catalog) => {
         catalog.provider.update(providerID, (provider) => {
-          provider.endpoint = {
+          provider.api = {
             type: "aisdk",
             package: "@ai-sdk/openai-compatible",
             url: "https://provider.example.com",
@@ -92,7 +93,7 @@ describe("CatalogV2", () => {
         catalog.model.update(providerID, modelID, () => {})
       })
 
-      expect((yield* catalog.model.get(providerID, modelID)).endpoint).toEqual({
+      expect((yield* catalog.model.get(providerID, modelID)).api).toEqual({
         type: "aisdk",
         package: "@ai-sdk/openai-compatible",
         url: "https://provider.example.com",
@@ -115,16 +116,16 @@ describe("CatalogV2", () => {
             Effect.sync(() => {
               const item = evt.provider.get(providerID)
               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.api.type)
+              if (item?.provider.api.type === "aisdk") seen.push(item.provider.api.url)
+              seen.push(item?.provider.request.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.api = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
+          provider.request.body.baseURL = "https://provider.example.com"
         }),
       )
 
@@ -187,7 +188,7 @@ describe("CatalogV2", () => {
     }),
   )
 
-  it.effect("resolves provider and model option merges", () =>
+  it.effect("resolves provider and model request merges", () =>
     Effect.gen(function* () {
       const catalog = yield* Catalog.Service
       const providerID = ProviderV2.ID.make("test")
@@ -196,25 +197,21 @@ describe("CatalogV2", () => {
 
       yield* transform((catalog) => {
         catalog.provider.update(providerID, (provider) => {
-          provider.options.headers.provider = "provider"
-          provider.options.headers.shared = "provider"
-          provider.options.body.provider = true
-          provider.options.aisdk.provider.provider = true
+          provider.request.headers.provider = "provider"
+          provider.request.headers.shared = "provider"
+          provider.request.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.request.headers.model = "model"
+          model.request.headers.shared = "model"
+          model.request.body.model = true
+          model.request.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.request.headers).toEqual({ provider: "provider", shared: "model", model: "model" })
+      expect(model.request.body).toEqual({ provider: true, model: true, request: true })
     }),
   )
 

+ 6 - 9
packages/core/test/config/agent.test.ts

@@ -107,10 +107,9 @@ describe("ConfigAgentPlugin.Plugin", () => {
                     hidden: true,
                     color: "warning",
                     steps: 12,
-                    options: {
+                    request: {
                       headers: { first: "one", shared: "first" },
-                      body: { enabled: true },
-                      aisdk: { provider: { profile: "review" }, request: { effort: "medium" } },
+                      body: { enabled: true, profile: "review", effort: "medium" },
                     },
                   },
                 },
@@ -121,10 +120,9 @@ describe("ConfigAgentPlugin.Plugin", () => {
               info: decode({
                 agents: {
                   reviewer: {
-                    options: {
+                    request: {
                       headers: { shared: "last", second: "two" },
-                      body: { retries: 2 },
-                      aisdk: { request: { effort: "high" } },
+                      body: { retries: 2, effort: "high" },
                     },
                   },
                 },
@@ -149,10 +147,9 @@ describe("ConfigAgentPlugin.Plugin", () => {
         steps: 12,
         model: { providerID: "anthropic", id: "claude-sonnet", variant: undefined },
       })
-      expect(reviewer.options).toEqual({
+      expect(reviewer.request).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" },
       })
     }),
   )

+ 94 - 25
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"
@@ -41,14 +43,10 @@ function testLayer(
 }
 
 const provider = {
-  endpoint: { type: "unknown" },
-  options: {
+  api: { type: "native", settings: {} },
+  request: {
     headers: {},
     body: {},
-    aisdk: {
-      provider: {},
-      request: {},
-    },
   },
   models: {},
 }
@@ -62,6 +60,46 @@ 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.effect("migrates v1 provider setup options into AISDK settings", () =>
+    Effect.sync(() => {
+      const migrated = ConfigMigrateV1.migrate({
+        provider: {
+          bedrock: {
+            npm: "@ai-sdk/amazon-bedrock",
+            options: {
+              headers: { "x-test": "1" },
+              body: { trace: true },
+              region: "us-east-1",
+              profile: "dev",
+            },
+          },
+        },
+      })
+
+      expect(migrated.providers?.bedrock?.api).toEqual({
+        type: "aisdk",
+        package: "@ai-sdk/amazon-bedrock",
+        url: undefined,
+        settings: { region: "us-east-1", profile: "dev" },
+      })
+      expect(migrated.providers?.bedrock?.request).toEqual({
+        headers: { "x-test": "1" },
+        body: { trace: true },
+      })
+    }),
+  )
+
   it.live("returns an empty configuration when directory files do not exist", () =>
     Effect.acquireRelease(
       Effect.promise(() => tmpdir()),
@@ -186,9 +224,9 @@ describe("Config", () => {
                   reviewer: {
                     model: "openrouter/openai/gpt-5",
                     variant: "high",
-                    options: {
+                    request: {
                       headers: { "x-agent": "reviewer" },
-                      aisdk: { request: { reasoningEffort: "high" } },
+                      body: { reasoningEffort: "high" },
                     },
                     description: "Review changes for correctness",
                     system: "Find regressions.",
@@ -266,22 +304,21 @@ describe("Config", () => {
               { action: "bash", resource: "*", effect: "ask" },
               { action: "bash", resource: "git status", effect: "allow" },
             ])
-            expect(documents[0]?.info.agents?.reviewer).toEqual({
-              model: "openrouter/openai/gpt-5",
-              variant: "high",
-              options: {
-                headers: { "x-agent": "reviewer" },
-                aisdk: { request: { reasoningEffort: "high" } },
-              },
-              description: "Review changes for correctness",
-              system: "Find regressions.",
-              mode: "subagent",
-              hidden: false,
-              color: "warning",
-              steps: 12,
-              disabled: false,
-              permissions: [{ action: "edit", resource: "*", effect: "deny" }],
+            const reviewer = documents[0]?.info.agents?.reviewer
+            expect(reviewer?.model).toBe("openrouter/openai/gpt-5")
+            expect(reviewer?.variant).toBe("high")
+            expect(reviewer?.request).toEqual({
+              headers: { "x-agent": "reviewer" },
+              body: { reasoningEffort: "high" },
             })
+            expect(reviewer?.description).toBe("Review changes for correctness")
+            expect(reviewer?.system).toBe("Find regressions.")
+            expect(reviewer?.mode).toBe("subagent")
+            expect(reviewer?.hidden).toBe(false)
+            expect(reviewer?.color).toBe("warning")
+            expect(reviewer?.steps).toBe(12)
+            expect(reviewer?.disabled).toBe(false)
+            expect(reviewer?.permissions).toEqual([{ action: "edit", resource: "*", effect: "deny" }])
             expect(documents[0]?.info.snapshots).toBe(false)
             expect(documents[0]?.info.watcher).toEqual({ ignore: ["node_modules/**", "dist/**", ".git"] })
             expect(documents[0]?.info.formatter).toEqual({
@@ -379,6 +416,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: {
@@ -410,7 +465,7 @@ describe("Config", () => {
             expect(documents[0]?.info.agents?.reviewer).toMatchObject({
               system: "Review changes.",
               disabled: true,
-              options: { body: { temperature: 0.2 } },
+              request: { body: { temperature: 0.2 } },
               permissions: [{ action: "read", resource: "*", effect: "allow" }],
             })
             expect(documents[0]?.info.plugins).toEqual([
@@ -420,6 +475,20 @@ 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({
+              request: { body: { apiKey: "secret" } },
+              models: {
+                model: {
+                  request: { body: { reasoningEffort: "high" } },
+                  variants: [{ id: "fast", body: { temperature: 0.2 } }],
+                },
+              },
+            })
+            expect(documents[0]?.info.providers?.openai).toMatchObject({
+              api: { settings: {} },
+              request: { headers: { Authorization: "Bearer secret", "OpenAI-Organization": "org" } },
+              models: { model: { request: { body: { reasoning_effort: "high", service_tier: "priority" } } } },
+            })
             expect(documents[0]?.info.compaction).toEqual({
               auto: true,
               prune: undefined,

+ 11 - 11
packages/core/test/config/provider.test.ts

@@ -8,7 +8,7 @@ import { PluginV2 } from "@opencode-ai/core/plugin"
 import { ProviderV2 } from "@opencode-ai/core/provider"
 import { it } from "../plugin/provider-helper"
 
-function options(headers: Record<string, string>, variant?: string) {
+function request(headers: Record<string, string>, variant?: string) {
   return {
     headers,
     variant,
@@ -35,8 +35,8 @@ describe("ConfigProviderPlugin.Plugin", () => {
                   custom: {
                     name: "Configured",
                     env: ["CUSTOM_API_KEY"],
-                    endpoint: { type: "unknown" },
-                    options: options({ first: "first", shared: "first" }),
+                    api: { type: "native", settings: {} },
+                    request: request({ first: "first", shared: "first" }),
                     models: {
                       chat: {
                         name: "First",
@@ -44,7 +44,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
                         disabled: true,
                         limit: { context: 100, output: 50 },
                         cost: { input: 1, output: 2 },
-                        options: options({ first: "first", shared: "first" }, "retained"),
+                        request: request({ first: "first", shared: "first" }, "retained"),
                         variants: [
                           {
                             id: "fast",
@@ -62,14 +62,14 @@ describe("ConfigProviderPlugin.Plugin", () => {
               info: decode({
                 providers: {
                   custom: {
-                    endpoint: { type: "aisdk", package: "custom-sdk", url: "https://example.test" },
-                    options: options({ last: "last", shared: "last" }),
+                    api: { type: "aisdk", package: "custom-sdk", url: "https://example.test" },
+                    request: request({ last: "last", shared: "last" }),
                     models: {
                       chat: {
                         api_id: "api-chat",
                         name: "Last",
                         limit: { output: 75 },
-                        options: options({ last: "last", shared: "last" }),
+                        request: request({ last: "last", shared: "last" }),
                         variants: [
                           {
                             id: "fast",
@@ -110,16 +110,16 @@ describe("ConfigProviderPlugin.Plugin", () => {
       expect(provider.name).toBe("Renamed")
       expect(provider.env).toEqual(["CUSTOM_API_KEY"])
       expect(provider.enabled).toEqual({ via: "custom", data: {} })
-      expect(provider.endpoint).toEqual({ type: "aisdk", package: "custom-sdk", url: "https://example.test" })
-      expect(provider.options.headers).toEqual({ first: "first", shared: "last", last: "last" })
+      expect(provider.api).toEqual({ type: "aisdk", package: "custom-sdk", url: "https://example.test" })
+      expect(provider.request.headers).toEqual({ first: "first", shared: "last", last: "last" })
       expect(model.apiID).toBe(ModelV2.ID.make("api-chat"))
       expect(model.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.options.headers).toEqual({ first: "first", shared: "last", last: "last" })
-      expect(model.options.variant).toBe("retained")
+      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([
         ModelV2.VariantID.make("fast"),
         ModelV2.VariantID.make("slow"),

+ 8 - 9
packages/core/test/plugin/provider-amazon-bedrock.test.ts

@@ -19,7 +19,7 @@ function bedrockFetch(sdk: unknown, modelID = "anthropic.claude-sonnet-4-5") {
 }
 
 describe("AmazonBedrockPlugin", () => {
-  it.effect("moves endpoint option to endpoint URL", () =>
+  it.effect("moves endpoint option to api URL", () =>
     Effect.gen(function* () {
       const plugin = yield* PluginV2.Service
       const catalog = yield* Catalog.Service
@@ -27,25 +27,24 @@ describe("AmazonBedrockPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
         const bedrock = provider("amazon-bedrock", {
-          endpoint: { type: "aisdk", package: "@ai-sdk/amazon-bedrock" },
-          options: {
+          api: { type: "aisdk", package: "@ai-sdk/amazon-bedrock" },
+          request: {
             headers: {},
-            body: {},
-            aisdk: { provider: { endpoint: "https://bedrock.example" }, request: {} },
+            body: { endpoint: "https://bedrock.example" },
           },
         })
         catalog.provider.update(bedrock.id, (item) => {
-          item.endpoint = bedrock.endpoint
-          item.options = bedrock.options
+          item.api = bedrock.api
+          item.request = bedrock.request
         })
       })
       const result = yield* catalog.provider.get(ProviderV2.ID.amazonBedrock)
-      expect(result.endpoint).toEqual({
+      expect(result.api).toEqual({
         type: "aisdk",
         package: "@ai-sdk/amazon-bedrock",
         url: "https://bedrock.example",
       })
-      expect(result.options.aisdk.provider.endpoint).toBeUndefined()
+      expect(result.request.body.endpoint).toBeUndefined()
     }),
   )
 

+ 7 - 7
packages/core/test/plugin/provider-anthropic.test.ts

@@ -15,18 +15,18 @@ describe("AnthropicPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
         const item = provider("anthropic", {
-          endpoint: { type: "aisdk", package: "@ai-sdk/anthropic" },
-          options: { headers: { Existing: "1" }, body: {}, aisdk: { provider: {}, request: {} } },
+          api: { type: "aisdk", package: "@ai-sdk/anthropic" },
+          request: { headers: { Existing: "1" }, body: {} },
         })
         catalog.provider.update(item.id, (draft) => {
-          draft.endpoint = item.endpoint
-          draft.options = item.options
+          draft.api = item.api
+          draft.request = item.request
         })
       })
-      expect((yield* catalog.provider.get(ProviderV2.ID.anthropic)).options.headers["anthropic-beta"]).toBe(
+      expect((yield* catalog.provider.get(ProviderV2.ID.anthropic)).request.headers["anthropic-beta"]).toBe(
         "interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
       )
-      expect((yield* catalog.provider.get(ProviderV2.ID.anthropic)).options.headers.Existing).toBe("1")
+      expect((yield* catalog.provider.get(ProviderV2.ID.anthropic)).request.headers.Existing).toBe("1")
     }),
   )
 
@@ -37,7 +37,7 @@ describe("AnthropicPlugin", () => {
       yield* plugin.add(AnthropicPlugin)
       const transform = yield* catalog.transform()
       yield* transform((catalog) => catalog.provider.update(provider("openai").id, () => {}))
-      expect((yield* catalog.provider.get(ProviderV2.ID.openai)).options.headers["anthropic-beta"]).toBeUndefined()
+      expect((yield* catalog.provider.get(ProviderV2.ID.openai)).request.headers["anthropic-beta"]).toBeUndefined()
     }),
   )
 

+ 11 - 11
packages/core/test/plugin/provider-azure-cognitive-services.test.ts

@@ -16,17 +16,17 @@ describe("AzureCognitiveServicesPlugin", () => {
         const transform = yield* catalog.transform()
         yield* transform((catalog) => {
           catalog.provider.update(ProviderV2.ID.make("azure-cognitive-services"), (item) => {
-            item.endpoint = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
+            item.api = { type: "aisdk", package: "@ai-sdk/openai-compatible" }
           })
         })
         const result = yield* catalog.provider.get(ProviderV2.ID.make("azure-cognitive-services"))
-        expect(result.endpoint).toEqual({
+        expect(result.api).toEqual({
           type: "aisdk",
           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.request.body.baseURL).toBeUndefined()
+        expect(result.request.body.resourceName).toBeUndefined()
       }),
     ),
   )
@@ -40,22 +40,22 @@ describe("AzureCognitiveServicesPlugin", () => {
         const transform = yield* catalog.transform()
         yield* transform((catalog) => {
           const azure = provider("azure-cognitive-services", {
-            endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible" },
+            api: { type: "aisdk", package: "@ai-sdk/openai-compatible" },
           })
           const openai = provider("openai")
           catalog.provider.update(azure.id, (item) => {
-            item.endpoint = azure.endpoint
+            item.api = azure.api
           })
           catalog.provider.update(openai.id, (item) => {
-            item.endpoint = openai.endpoint
+            item.api = openai.api
           })
         })
         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.endpoint).toEqual({ type: "aisdk", package: "@ai-sdk/openai-compatible" })
-        expect(openai.options.aisdk.provider.baseURL).toBeUndefined()
-        expect(openai.endpoint).toEqual({ type: "aisdk", package: "test-provider" })
+        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" })
       }),
     ),
   )

+ 21 - 21
packages/core/test/plugin/provider-azure.test.ts

@@ -34,10 +34,10 @@ describe("AzurePlugin", () => {
         const transform = yield* catalog.transform()
         yield* transform((catalog) => {
           catalog.provider.update(ProviderV2.ID.azure, (item) => {
-            item.endpoint = { type: "aisdk", package: "@ai-sdk/azure" }
+            item.api = { 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)).request.body.resourceName).toBe("from-env")
       }),
     ),
   )
@@ -51,19 +51,19 @@ describe("AzurePlugin", () => {
         const transform = yield* catalog.transform()
         yield* transform((catalog) => {
           const azure = provider("azure", {
-            endpoint: { type: "aisdk", package: "@ai-sdk/azure" },
-            options: { headers: {}, body: {}, aisdk: { provider: { resourceName: "from-config" }, request: {} } },
+            api: { type: "aisdk", package: "@ai-sdk/azure" },
+            request: { headers: {}, body: { resourceName: "from-config" } },
           })
           catalog.provider.update(azure.id, (item) => {
-            item.endpoint = azure.endpoint
-            item.options = azure.options
+            item.api = azure.api
+            item.request = azure.request
           })
           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)).request.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)).request.body.resourceName).toBeUndefined()
       }),
     ),
   )
@@ -100,10 +100,10 @@ describe("AzurePlugin", () => {
           const transform = yield* catalog.transform()
           yield* transform((catalog) => {
             catalog.provider.update(ProviderV2.ID.azure, (item) => {
-              item.endpoint = { type: "aisdk", package: "@ai-sdk/azure" }
+              item.api = { 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)).request.body.resourceName).toBe(
             "from-account",
           )
         }),
@@ -119,15 +119,15 @@ describe("AzurePlugin", () => {
         const transform = yield* catalog.transform()
         yield* transform((catalog) => {
           const azure = provider("azure", {
-            endpoint: { type: "aisdk", package: "@ai-sdk/azure" },
-            options: { headers: {}, body: {}, aisdk: { provider: { resourceName: "" }, request: {} } },
+            api: { type: "aisdk", package: "@ai-sdk/azure" },
+            request: { headers: {}, body: { resourceName: "" } },
           })
           catalog.provider.update(azure.id, (item) => {
-            item.endpoint = azure.endpoint
-            item.options = azure.options
+            item.api = azure.api
+            item.request = azure.request
           })
         })
-        expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.aisdk.provider.resourceName).toBe("from-env")
+        expect((yield* catalog.provider.get(ProviderV2.ID.azure)).request.body.resourceName).toBe("from-env")
       }),
     ),
   )
@@ -141,15 +141,15 @@ describe("AzurePlugin", () => {
         const transform = yield* catalog.transform()
         yield* transform((catalog) => {
           const azure = provider("azure", {
-            endpoint: { type: "aisdk", package: "@ai-sdk/azure" },
-            options: { headers: {}, body: {}, aisdk: { provider: { resourceName: "   " }, request: {} } },
+            api: { type: "aisdk", package: "@ai-sdk/azure" },
+            request: { headers: {}, body: { resourceName: "   " } },
           })
           catalog.provider.update(azure.id, (item) => {
-            item.endpoint = azure.endpoint
-            item.options = azure.options
+            item.api = azure.api
+            item.request = azure.request
           })
         })
-        expect((yield* catalog.provider.get(ProviderV2.ID.azure)).options.aisdk.provider.resourceName).toBe("from-env")
+        expect((yield* catalog.provider.get(ProviderV2.ID.azure)).request.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 } } },
+            request: { headers: {}, body: { useCompletionUrls: true } },
           }),
           sdk: fakeSelectorSdk(calls),
           options: {},

+ 4 - 4
packages/core/test/plugin/provider-cerebras.test.ts

@@ -27,11 +27,11 @@ describe("CerebrasPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
         catalog.provider.update(ProviderV2.ID.make("cerebras"), (item) => {
-          item.endpoint = { type: "aisdk", package: "@ai-sdk/cerebras" }
-          item.options.headers.Existing = "1"
+          item.api = { type: "aisdk", package: "@ai-sdk/cerebras" }
+          item.request.headers.Existing = "1"
         })
       })
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("cerebras"))).options.headers).toEqual({
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("cerebras"))).request.headers).toEqual({
         Existing: "1",
         "X-Cerebras-3rd-Party-Integration": "opencode",
       })
@@ -45,7 +45,7 @@ describe("CerebrasPlugin", () => {
       yield* plugin.add(CerebrasPlugin)
       const transform = yield* catalog.transform()
       yield* transform((catalog) => catalog.provider.update(ProviderV2.ID.make("groq"), () => {}))
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("groq"))).options.headers).toEqual({})
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("groq"))).request.headers).toEqual({})
     }),
   )
 

+ 14 - 14
packages/core/test/plugin/provider-cloudflare-workers-ai.test.ts

@@ -54,20 +54,20 @@ describe("CloudflareWorkersAIPlugin", () => {
         const transform = yield* catalog.transform()
         yield* transform((catalog) =>
           catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
-            provider.endpoint = { type: "aisdk", package: "test-provider" }
+            provider.api = { type: "aisdk", package: "test-provider" }
           }),
         )
         const provider = yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))
         const sdk = yield* plugin.trigger(
           "aisdk.sdk",
           {
-            model: model("cloudflare-workers-ai", "@cf/model", { endpoint: provider.endpoint }),
+            model: model("cloudflare-workers-ai", "@cf/model", { api: provider.api }),
             package: "@ai-sdk/openai-compatible",
             options: { name: "cloudflare-workers-ai", headers: { custom: "header" } },
           },
           {},
         )
-        expect(provider.endpoint).toEqual({
+        expect(provider.api).toEqual({
           type: "aisdk",
           package: "test-provider",
           url: "https://api.cloudflare.com/client/v4/accounts/acct/ai/v1",
@@ -86,10 +86,10 @@ describe("CloudflareWorkersAIPlugin", () => {
         const transform = yield* catalog.transform()
         yield* transform((catalog) =>
           catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
-            provider.endpoint = { type: "aisdk", package: "test-provider", url: "https://proxy.example/v1" }
+            provider.api = { type: "aisdk", package: "test-provider", url: "https://proxy.example/v1" }
           }),
         )
-        expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).endpoint).toEqual({
+        expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).api).toEqual({
           type: "aisdk",
           package: "test-provider",
           url: "https://proxy.example/v1",
@@ -107,7 +107,7 @@ describe("CloudflareWorkersAIPlugin", () => {
           "aisdk.sdk",
           {
             model: model("cloudflare-workers-ai", "@cf/model", {
-              endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://proxy.example/v1" },
+              api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://proxy.example/v1" },
             }),
             package: "@ai-sdk/openai-compatible",
             options: { name: "cloudflare-workers-ai", baseURL: "https://proxy.example/v1" },
@@ -152,10 +152,10 @@ describe("CloudflareWorkersAIPlugin", () => {
           const transform = yield* catalog.transform()
           yield* transform((catalog) =>
             catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
-              provider.endpoint = { type: "aisdk", package: "test-provider" }
+              provider.api = { type: "aisdk", package: "test-provider" }
             }),
           )
-          expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).endpoint).toEqual({
+          expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).api).toEqual({
             type: "aisdk",
             package: "test-provider",
             url: "https://api.cloudflare.com/client/v4/accounts/account-acct/ai/v1",
@@ -173,11 +173,11 @@ describe("CloudflareWorkersAIPlugin", () => {
         const transform = yield* catalog.transform()
         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.api = { type: "aisdk", package: "test-provider" }
+            provider.request.body.accountId = "configured-acct"
           }),
         )
-        expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).endpoint).toEqual({
+        expect((yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai"))).api).toEqual({
           type: "aisdk",
           package: "test-provider",
           url: "https://api.cloudflare.com/client/v4/accounts/env-acct/ai/v1",
@@ -195,7 +195,7 @@ describe("CloudflareWorkersAIPlugin", () => {
           "aisdk.sdk",
           {
             model: model("cloudflare-workers-ai", "@cf/model", {
-              endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://proxy.example/v1" },
+              api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://proxy.example/v1" },
             }),
             package: "@ai-sdk/openai-compatible",
             options: {
@@ -224,7 +224,7 @@ describe("CloudflareWorkersAIPlugin", () => {
           "aisdk.sdk",
           {
             model: model("cloudflare-workers-ai", "@cf/model", {
-              endpoint: {
+              api: {
                 type: "aisdk",
                 package: "@ai-sdk/openai-compatible",
                 url: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1",
@@ -273,7 +273,7 @@ describe("CloudflareWorkersAIPlugin", () => {
           "aisdk.sdk",
           {
             model: model("cloudflare-workers-ai", "@cf/model", {
-              endpoint: { type: "aisdk", package: "@ai-sdk/anthropic", url: "https://proxy.example/v1" },
+              api: { type: "aisdk", package: "@ai-sdk/anthropic", url: "https://proxy.example/v1" },
             }),
             package: "@ai-sdk/anthropic",
             options: { name: "cloudflare-workers-ai" },

+ 1 - 1
packages/core/test/plugin/provider-deepinfra.test.ts

@@ -122,7 +122,7 @@ describe("DeepInfraPlugin", () => {
       yield* plugin.add(DeepInfraPlugin)
       const language = yield* aisdk.language(
         model("deepinfra", "meta-llama/Llama-3.3-70B-Instruct", {
-          endpoint: { type: "aisdk", package: "@ai-sdk/deepinfra" },
+          api: { type: "aisdk", package: "@ai-sdk/deepinfra" },
         }),
       )
       expect(language.provider).toBe("deepinfra.chat")

+ 4 - 4
packages/core/test/plugin/provider-dynamic.test.ts

@@ -122,7 +122,7 @@ describe("DynamicProviderPlugin", () => {
       const aisdk = yield* AISDK.Service
       yield* plugin.add(dynamicPlugin(npmEntrypointLayer(Option.none<string>())))
       const exit = yield* aisdk
-        .language(model("missing-entrypoint", "alias", { endpoint: { type: "aisdk", package: "fixture-provider" } }))
+        .language(model("missing-entrypoint", "alias", { api: { type: "aisdk", package: "fixture-provider" } }))
         .pipe(Effect.exit)
       expect(exit._tag).toBe("Failure")
       if (exit._tag === "Failure") expect(Cause.prettyErrors(exit.cause).join("\n")).toContain("AISDK.InitError")
@@ -136,7 +136,7 @@ describe("DynamicProviderPlugin", () => {
       yield* plugin.add(dynamicPlugin())
       const exit = yield* aisdk
         .language(
-          model("bad-import", "alias", { endpoint: { type: "aisdk", package: "file:///missing/provider-factory.js" } }),
+          model("bad-import", "alias", { api: { type: "aisdk", package: "file:///missing/provider-factory.js" } }),
         )
         .pipe(Effect.exit)
       expect(exit._tag).toBe("Failure")
@@ -151,7 +151,7 @@ describe("DynamicProviderPlugin", () => {
       const tmp = yield* tempEntrypoint("export const notAProviderFactory = true\n")
       yield* plugin.add(dynamicPlugin(npmEntrypointLayer(Option.some(tmp.entrypoint))))
       const exit = yield* aisdk
-        .language(model("missing-factory", "alias", { endpoint: { type: "aisdk", package: "fixture-provider" } }))
+        .language(model("missing-factory", "alias", { api: { type: "aisdk", package: "fixture-provider" } }))
         .pipe(Effect.exit)
       expect(exit._tag).toBe("Failure")
       if (exit._tag === "Failure") expect(Cause.prettyErrors(exit.cause).join("\n")).toContain("AISDK.InitError")
@@ -166,7 +166,7 @@ describe("DynamicProviderPlugin", () => {
       const language = yield* aisdk.language(
         model("custom", "alias", {
           apiID: ModelV2.ID.make("test-model-api"),
-          endpoint: { type: "aisdk", package: fixtureProvider },
+          api: { type: "aisdk", package: fixtureProvider },
         }),
       )
       expect(language).toMatchObject({ modelID: "test-model-api", options: { name: "custom" } })

+ 7 - 9
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.request.body,
             },
             {},
           )
@@ -238,7 +238,7 @@ describe("GitLabPlugin", () => {
             {
               model: model("gitlab", "claude"),
               package: "gitlab-ai-provider",
-              options: provider.options.aisdk.provider,
+              options: provider.request.body,
             },
             {},
           )
@@ -256,10 +256,9 @@ describe("GitLabPlugin", () => {
         "aisdk.language",
         {
           model: model("gitlab", "duo-workflow-custom", {
-            options: {
+            request: {
               headers: {},
-              body: {},
-              aisdk: { provider: {}, request: { workflowRef: "ref", workflowDefinition: "definition" } },
+              body: { workflowRef: "ref", workflowDefinition: "definition" },
             },
           }),
           sdk: {
@@ -320,10 +319,9 @@ describe("GitLabPlugin", () => {
         "aisdk.language",
         {
           model: model("gitlab", "duo-workflow-custom", {
-            options: {
+            request: {
               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: {} } },
+            request: { headers: { h: "v" }, body: {} },
           }),
           sdk: {
             workflowChat: () => undefined,

+ 8 - 8
packages/core/test/plugin/provider-google-vertex-anthropic.test.ts

@@ -25,12 +25,12 @@ describe("GoogleVertexAnthropicPlugin", () => {
           const transform = yield* catalog.transform()
           yield* transform((catalog) =>
             catalog.provider.update(ProviderV2.ID.make("google-vertex-anthropic"), (provider) => {
-              provider.endpoint = { type: "aisdk", package: "@ai-sdk/google-vertex/anthropic" }
+              provider.api = { type: "aisdk", package: "@ai-sdk/google-vertex/anthropic" }
             }),
           )
           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.request.body.project).toBe("cloud-project")
+          expect(provider.request.body.location).toBe("cloud-location")
         }),
     ),
   )
@@ -44,14 +44,14 @@ describe("GoogleVertexAnthropicPlugin", () => {
         const transform = yield* catalog.transform()
         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.api = { type: "aisdk", package: "@ai-sdk/google-vertex/anthropic" }
+            provider.request.body.project = "configured-project"
+            provider.request.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.request.body.project).toBe("configured-project")
+        expect(provider.request.body.location).toBe("configured-location")
       }),
     ),
   )

+ 24 - 24
packages/core/test/plugin/provider-google-vertex.test.ts

@@ -53,7 +53,7 @@ describe("GoogleVertexPlugin", () => {
           const transform = yield* catalog.transform()
           yield* transform((catalog) =>
             catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
-              provider.endpoint = {
+              provider.api = {
                 type: "aisdk",
                 package: "@ai-sdk/openai-compatible",
                 url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}",
@@ -61,9 +61,9 @@ 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.endpoint).toEqual({
+          expect(provider.request.body.project).toBe("google-cloud-project")
+          expect(provider.request.body.location).toBe("google-vertex-location")
+          expect(provider.api).toEqual({
             type: "aisdk",
             package: "@ai-sdk/openai-compatible",
             url: "https://google-vertex-location-aiplatform.googleapis.com/v1/projects/google-cloud-project/locations/google-vertex-location",
@@ -92,7 +92,7 @@ describe("GoogleVertexPlugin", () => {
           const transform = yield* catalog.transform()
           yield* transform((catalog) =>
             catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
-              provider.endpoint = {
+              provider.api = {
                 type: "aisdk",
                 package: "@ai-sdk/openai-compatible",
                 url: "https://${GOOGLE_VERTEX_ENDPOINT}/v1/projects/${GOOGLE_VERTEX_PROJECT}/locations/${GOOGLE_VERTEX_LOCATION}",
@@ -104,7 +104,7 @@ describe("GoogleVertexPlugin", () => {
             "aisdk.sdk",
             {
               model: model("google-vertex", "gemini", {
-                endpoint: { type: "aisdk", package: "@ai-sdk/google-vertex" },
+                api: { type: "aisdk", package: "@ai-sdk/google-vertex" },
               }),
               package: "@ai-sdk/google-vertex",
               options: { name: "google-vertex" },
@@ -112,8 +112,8 @@ describe("GoogleVertexPlugin", () => {
             {},
           )
 
-          expect(provider.options.aisdk.provider.project).toBe("vertex-project")
-          expect(provider.endpoint).toEqual({
+          expect(provider.request.body.project).toBe("vertex-project")
+          expect(provider.api).toEqual({
             type: "aisdk",
             package: "@ai-sdk/openai-compatible",
             url: "https://europe-west4-aiplatform.googleapis.com/v1/projects/vertex-project/locations/europe-west4",
@@ -142,19 +142,19 @@ describe("GoogleVertexPlugin", () => {
           const transform = yield* catalog.transform()
           yield* transform((catalog) =>
             catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
-              provider.endpoint = {
+              provider.api = {
                 type: "aisdk",
                 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.request.body.project = "config-project"
+              provider.request.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.endpoint).toEqual({
+          expect(provider.request.body.project).toBe("config-project")
+          expect(provider.request.body.location).toBe("global")
+          expect(provider.api).toEqual({
             type: "aisdk",
             package: "@ai-sdk/openai-compatible",
             url: "https://aiplatform.googleapis.com/v1/projects/config-project/locations/global",
@@ -171,17 +171,17 @@ describe("GoogleVertexPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) =>
         catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
-          provider.endpoint = {
+          provider.api = {
             type: "aisdk",
             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.request.body.project = "config-project"
+          provider.request.body.location = "eu"
         }),
       )
       const provider = yield* catalog.provider.get(ProviderV2.ID.make("google-vertex"))
-      expect(provider.endpoint).toEqual({
+      expect(provider.api).toEqual({
         type: "aisdk",
         package: "@ai-sdk/openai-compatible",
         url: "https://eu-aiplatform.googleapis.com/v1/projects/config-project/locations/eu",
@@ -207,13 +207,13 @@ describe("GoogleVertexPlugin", () => {
           const transform = yield* catalog.transform()
           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.api = { type: "aisdk", package: "@ai-sdk/google-vertex" }
+              provider.request.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.request.body.project).toBe("config-project")
+          expect(provider.request.body.location).toBe("us-central1")
         }),
     ),
   )
@@ -233,7 +233,7 @@ describe("GoogleVertexPlugin", () => {
             "aisdk.sdk",
             {
               model: model("google-vertex", "gemini", {
-                endpoint: { type: "aisdk", package: "@ai-sdk/google-vertex" },
+                api: { type: "aisdk", package: "@ai-sdk/google-vertex" },
               }),
               package: "@ai-sdk/google-vertex",
               options: { name: "google-vertex" },
@@ -283,7 +283,7 @@ describe("GoogleVertexPlugin", () => {
             "aisdk.sdk",
             {
               model: model("google-vertex", "gemini", {
-                endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible" },
+                api: { type: "aisdk", package: "@ai-sdk/openai-compatible" },
               }),
               package: "@ai-sdk/openai-compatible",
               options: { name: "google-vertex" },

+ 3 - 7
packages/core/test/plugin/provider-google.test.ts

@@ -52,17 +52,13 @@ describe("GooglePlugin", () => {
       const language = yield* aisdk.language(
         model("custom-google", "alias", {
           apiID: ModelV2.ID.make("gemini-api"),
-          endpoint: {
+          api: {
             type: "aisdk",
             package: "@ai-sdk/google",
           },
-          options: {
+          request: {
             headers: {},
-            body: {},
-            aisdk: {
-              provider: { apiKey: "test" },
-              request: {},
-            },
+            body: { apiKey: "test" },
           },
         }),
       )

+ 3 - 7
packages/core/test/plugin/provider-groq.test.ts

@@ -83,17 +83,13 @@ describe("GroqPlugin", () => {
       const result = yield* aisdk.language(
         model("groq", "alias", {
           apiID: ModelV2.ID.make("llama-api"),
-          endpoint: {
+          api: {
             type: "aisdk",
             package: "@ai-sdk/groq",
           },
-          options: {
+          request: {
             headers: {},
-            body: {},
-            aisdk: {
-              provider: { apiKey: "test" },
-              request: {},
-            },
+            body: { apiKey: "test" },
           },
         }),
       )

+ 18 - 16
packages/core/test/plugin/provider-helper.ts

@@ -54,43 +54,45 @@ export const it = testEffect(
   ),
 )
 
-export function provider(providerID: string, options?: Partial<ProviderV2.Info>) {
+type ProviderInput = Partial<Omit<ProviderV2.Info, "api" | "request">> & {
+  api?: ProviderV2.Api
+  request?: ProviderV2.Request
+}
+
+type ModelInput = Partial<Omit<ModelV2.Info, "api" | "request">> & {
+  api?: ProviderV2.Api
+  request?: ModelV2.Info["request"]
+}
+
+export function provider(providerID: string, options?: ProviderInput) {
   return new ProviderV2.Info({
     ...ProviderV2.Info.empty(ProviderV2.ID.make(providerID)),
-    endpoint: {
+    api: options?.api ?? {
       type: "aisdk",
       package: "test-provider",
     },
     ...options,
-    options: {
+    request: {
       headers: {},
       body: {},
-      aisdk: {
-        provider: {},
-        request: {},
-      },
-      ...options?.options,
+      ...options?.request,
     },
   })
 }
 
-export function model(providerID: string, modelID: string, options?: Partial<ModelV2.Info>) {
+export function model(providerID: string, modelID: string, options?: ModelInput) {
   return new ModelV2.Info({
     ...ModelV2.Info.empty(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
     apiID: ModelV2.ID.make(modelID),
-    endpoint: {
+    api: options?.api ?? {
       type: "aisdk",
       package: "test-provider",
     },
     ...options,
-    options: {
+    request: {
       headers: {},
       body: {},
-      aisdk: {
-        provider: {},
-        request: {},
-      },
-      ...options?.options,
+      ...options?.request,
     },
   })
 }

+ 18 - 18
packages/core/test/plugin/provider-kilo.test.ts

@@ -25,21 +25,21 @@ describe("KiloPlugin", () => {
       const transform = yield* catalog.transform()
       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: {} } },
+          api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
+          request: { headers: { Existing: "value" }, body: {} },
         })
         catalog.provider.update(kilo.id, (draft) => {
-          draft.endpoint = kilo.endpoint
-          draft.options = kilo.options
+          draft.api = kilo.api
+          draft.request = kilo.request
         })
         catalog.provider.update(provider("openrouter").id, () => {})
       })
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo"))).options.headers).toEqual({
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo"))).request.headers).toEqual({
         Existing: "value",
         "HTTP-Referer": "https://opencode.ai/",
         "X-Title": "opencode",
       })
-      expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).options.headers).toEqual({})
+      expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).request.headers).toEqual({})
     }),
   )
 
@@ -51,21 +51,21 @@ describe("KiloPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
         const item = provider("kilo", {
-          endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
+          api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
         })
         catalog.provider.update(item.id, (draft) => {
-          draft.endpoint = item.endpoint
+          draft.api = item.api
         })
       })
 
       const result = yield* catalog.provider.get(ProviderV2.ID.make("kilo"))
-      expect(result.options.headers).toEqual({
+      expect(result.request.headers).toEqual({
         "HTTP-Referer": "https://opencode.ai/",
         "X-Title": "opencode",
       })
-      expect(result.options.headers).not.toHaveProperty("http-referer")
-      expect(result.options.headers).not.toHaveProperty("x-title")
-      expect(result.options.headers).not.toHaveProperty("X-Source")
+      expect(result.request.headers).not.toHaveProperty("http-referer")
+      expect(result.request.headers).not.toHaveProperty("x-title")
+      expect(result.request.headers).not.toHaveProperty("X-Source")
     }),
   )
 
@@ -77,24 +77,24 @@ describe("KiloPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
         const kilo = provider("kilo", {
-          endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
+          api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.kilo.ai/api/gateway" },
         })
         catalog.provider.update(kilo.id, (draft) => {
-          draft.endpoint = kilo.endpoint
+          draft.api = kilo.api
         })
         const custom = provider("custom-kilo", {
-          endpoint: { type: "aisdk", package: "kilo" },
+          api: { type: "aisdk", package: "kilo" },
         })
         catalog.provider.update(custom.id, (draft) => {
-          draft.endpoint = custom.endpoint
+          draft.api = custom.api
         })
       })
 
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo"))).options.headers).toEqual({
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo"))).request.headers).toEqual({
         "HTTP-Referer": "https://opencode.ai/",
         "X-Title": "opencode",
       })
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("custom-kilo"))).options.headers).toEqual({})
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("custom-kilo"))).request.headers).toEqual({})
     }),
   )
 })

+ 9 - 9
packages/core/test/plugin/provider-llmgateway.test.ts

@@ -26,13 +26,13 @@ describe("LLMGatewayPlugin", () => {
       yield* transform((catalog) => {
         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: {} } },
+          api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.llmgateway.io/v1" },
+          request: { headers: { Existing: "value" }, body: {} },
         })
         catalog.provider.update(llmgateway.id, (draft) => {
           draft.enabled = llmgateway.enabled
-          draft.endpoint = llmgateway.endpoint
-          draft.options = llmgateway.options
+          draft.api = llmgateway.api
+          draft.request = llmgateway.request
         })
         const openrouter = provider("openrouter", {
           enabled: { via: "env", name: "OPENROUTER_API_KEY" },
@@ -41,13 +41,13 @@ describe("LLMGatewayPlugin", () => {
           draft.enabled = openrouter.enabled
         })
       })
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway"))).options.headers).toEqual({
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway"))).request.headers).toEqual({
         Existing: "value",
         "HTTP-Referer": "https://opencode.ai/",
         "X-Title": "opencode",
         "X-Source": "opencode",
       })
-      expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).options.headers).toEqual({})
+      expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).request.headers).toEqual({})
     }),
   )
 
@@ -59,15 +59,15 @@ describe("LLMGatewayPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
         const item = provider("llmgateway", {
-          endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.llmgateway.io/v1" },
+          api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.llmgateway.io/v1" },
         })
         catalog.provider.update(item.id, (draft) => {
-          draft.endpoint = item.endpoint
+          draft.api = item.api
         })
       })
 
       expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway"))).enabled).toBe(false)
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway"))).options.headers).toEqual({})
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway"))).request.headers).toEqual({})
     }),
   )
 })

+ 17 - 18
packages/core/test/plugin/provider-nvidia.test.ts

@@ -25,22 +25,22 @@ describe("NvidiaPlugin", () => {
       const transform = yield* catalog.transform()
       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: {} } },
+          api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" },
+          request: { headers: { Existing: "value" }, body: {} },
         })
         catalog.provider.update(nvidia.id, (draft) => {
-          draft.endpoint = nvidia.endpoint
-          draft.options = nvidia.options
+          draft.api = nvidia.api
+          draft.request = nvidia.request
         })
         catalog.provider.update(provider("openrouter").id, () => {})
       })
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).options.headers).toEqual({
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).request.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)).options.headers).toEqual({})
+      expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).request.headers).toEqual({})
     }),
   )
 
@@ -52,16 +52,16 @@ describe("NvidiaPlugin", () => {
       const transform = yield* catalog.transform()
       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: {} } },
+          api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" },
+          request: { headers: {}, body: {} },
         })
         catalog.provider.update(item.id, (draft) => {
-          draft.endpoint = item.endpoint
-          draft.options = item.options
+          draft.api = item.api
+          draft.request = item.request
         })
       })
 
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).options.headers).toEqual({
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).request.headers).toEqual({
         "HTTP-Referer": "https://opencode.ai/",
         "X-Title": "opencode",
         "X-BILLING-INVOKE-ORIGIN": "OpenCode",
@@ -77,20 +77,19 @@ describe("NvidiaPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
         const item = provider("nvidia", {
-          endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" },
-          options: {
+          api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://integrate.api.nvidia.com/v1" },
+          request: {
             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) => {
-          draft.endpoint = item.endpoint
-          draft.options = item.options
+          draft.api = item.api
+          draft.request = item.request
         })
       })
 
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).options.headers).toEqual({
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).request.headers).toEqual({
         "HTTP-Referer": "https://opencode.ai/",
         "X-Title": "opencode",
         "X-BILLING-INVOKE-ORIGIN": "CustomOrigin",

+ 2 - 2
packages/core/test/plugin/provider-openai.test.ts

@@ -79,9 +79,9 @@ describe("OpenAIPlugin", () => {
       yield* plugin.add(OpenAIPlugin)
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
-        const item = provider("openai", { endpoint: { type: "aisdk", package: "@ai-sdk/openai" } })
+        const item = provider("openai", { api: { type: "aisdk", package: "@ai-sdk/openai" } })
         catalog.provider.update(item.id, (draft) => {
-          draft.endpoint = item.endpoint
+          draft.api = item.api
         })
         catalog.model.update(item.id, ModelV2.ID.make("gpt-5"), () => {})
         catalog.model.update(item.id, ModelV2.ID.make("gpt-5-chat-latest"), () => {})

+ 11 - 15
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)).request.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)).request.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)).request.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)).request.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)).request.body.apiKey).toBeUndefined()
         expect((yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true)
       }),
     ),
@@ -134,24 +134,20 @@ describe("OpencodePlugin", () => {
         const transform = yield* catalog.transform()
         yield* transform((catalog) => {
           const item = provider("opencode", {
-            options: {
+            request: {
               headers: {},
-              body: {},
-              aisdk: {
-                provider: { apiKey: "configured" },
-                request: {},
-              },
+              body: { apiKey: "configured" },
             },
           })
           catalog.provider.update(item.id, (draft) => {
-            draft.options = item.options
+            draft.request = item.request
           })
           const paid = model("opencode", "paid", { cost: cost(1) })
           catalog.model.update(item.id, paid.id, (draft) => {
             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)).request.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)).request.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)).request.body.apiKey).toBeUndefined()
         expect((yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("paid"))).enabled).toBe(true)
       }),
     ),

+ 8 - 8
packages/core/test/plugin/provider-openrouter.test.ts

@@ -26,22 +26,22 @@ describe("OpenRouterPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
         const openrouter = provider("openrouter", {
-          endpoint: { type: "aisdk", package: "@openrouter/ai-sdk-provider" },
-          options: { headers: { Existing: "value" }, body: {}, aisdk: { provider: {}, request: {} } },
+          api: { type: "aisdk", package: "@openrouter/ai-sdk-provider" },
+          request: { headers: { Existing: "value" }, body: {} },
         })
         catalog.provider.update(openrouter.id, (item) => {
-          item.endpoint = openrouter.endpoint
-          item.options = openrouter.options
+          item.api = openrouter.api
+          item.request = openrouter.request
         })
         catalog.provider.update(ProviderV2.ID.make("nvidia"), () => {})
       })
 
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("openrouter"))).options.headers).toEqual({
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("openrouter"))).request.headers).toEqual({
         Existing: "value",
         "HTTP-Referer": "https://opencode.ai/",
         "X-Title": "opencode",
       })
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).options.headers).toEqual({})
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia"))).request.headers).toEqual({})
     }),
   )
 
@@ -78,10 +78,10 @@ describe("OpenRouterPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
         const openrouter = provider("openrouter", {
-          endpoint: { type: "aisdk", package: "@openrouter/ai-sdk-provider" },
+          api: { type: "aisdk", package: "@openrouter/ai-sdk-provider" },
         })
         catalog.provider.update(openrouter.id, (item) => {
-          item.endpoint = openrouter.endpoint
+          item.api = openrouter.api
         })
         catalog.provider.update(ProviderV2.ID.openai, () => {})
         for (const item of [

+ 10 - 10
packages/core/test/plugin/provider-vercel.test.ts

@@ -15,15 +15,15 @@ describe("VercelPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
         const item = provider("vercel", {
-          endpoint: { type: "aisdk", package: "@ai-sdk/vercel" },
-          options: { headers: { Existing: "1" }, body: {}, aisdk: { provider: {}, request: {} } },
+          api: { type: "aisdk", package: "@ai-sdk/vercel" },
+          request: { headers: { Existing: "1" }, body: {} },
         })
         catalog.provider.update(item.id, (draft) => {
-          draft.endpoint = item.endpoint
-          draft.options = item.options
+          draft.api = item.api
+          draft.request = item.request
         })
       })
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel"))).options.headers).toEqual({
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel"))).request.headers).toEqual({
         Existing: "1",
         "http-referer": "https://opencode.ai/",
         "x-title": "opencode",
@@ -38,15 +38,15 @@ describe("VercelPlugin", () => {
       yield* plugin.add(VercelPlugin)
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
-        const item = provider("vercel", { endpoint: { type: "aisdk", package: "@ai-sdk/vercel" } })
+        const item = provider("vercel", { api: { type: "aisdk", package: "@ai-sdk/vercel" } })
         catalog.provider.update(item.id, (draft) => {
-          draft.endpoint = item.endpoint
+          draft.api = item.api
         })
       })
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel"))).options.headers).not.toHaveProperty(
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel"))).request.headers).not.toHaveProperty(
         "HTTP-Referer",
       )
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel"))).options.headers).not.toHaveProperty("X-Title")
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel"))).request.headers).not.toHaveProperty("X-Title")
     }),
   )
 
@@ -71,7 +71,7 @@ describe("VercelPlugin", () => {
       yield* plugin.add(VercelPlugin)
       const transform = yield* catalog.transform()
       yield* transform((catalog) => catalog.provider.update(provider("gateway").id, () => {}))
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("gateway"))).options.headers).toEqual({})
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("gateway"))).request.headers).toEqual({})
     }),
   )
 })

+ 1 - 1
packages/core/test/plugin/provider-xai.test.ts

@@ -13,7 +13,7 @@ const it = testEffect(PluginV2.locationLayer.pipe(Layer.provide(EventV2.defaultL
 const model = new ModelV2.Info({
   ...ModelV2.Info.empty(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")),
   apiID: ModelV2.ID.make("grok-4"),
-  endpoint: {
+  api: {
     type: "aisdk",
     package: "@ai-sdk/xai",
   },

+ 17 - 19
packages/core/test/plugin/provider-zenmux.test.ts

@@ -25,15 +25,15 @@ describe("ZenmuxPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
         const item = provider("zenmux", {
-          endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
+          api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
         })
         catalog.provider.update(item.id, (draft) => {
-          draft.endpoint = item.endpoint
+          draft.api = item.api
         })
       })
       const result = yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))
-      expect(result.options.headers).toEqual({ "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" })
-      expect(Object.keys(result.options.headers).sort()).toEqual(["HTTP-Referer", "X-Title"])
+      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"])
     }),
   )
 
@@ -45,16 +45,16 @@ describe("ZenmuxPlugin", () => {
       const transform = yield* catalog.transform()
       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: {} } },
+          api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
+          request: { headers: { Existing: "value" }, body: {} },
         })
         catalog.provider.update(item.id, (draft) => {
-          draft.endpoint = item.endpoint
-          draft.options = item.options
+          draft.api = item.api
+          draft.request = item.request
         })
       })
 
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).options.headers).toEqual({
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).request.headers).toEqual({
         Existing: "value",
         "HTTP-Referer": "https://opencode.ai/",
         "X-Title": "opencode",
@@ -70,20 +70,19 @@ describe("ZenmuxPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
         const item = provider("zenmux", {
-          endpoint: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
-          options: {
+          api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://zenmux.ai/api/v1" },
+          request: {
             headers: { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" },
             body: {},
-            aisdk: { provider: {}, request: {} },
           },
         })
         catalog.provider.update(item.id, (draft) => {
-          draft.endpoint = item.endpoint
-          draft.options = item.options
+          draft.api = item.api
+          draft.request = item.request
         })
       })
 
-      expect((yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).options.headers).toEqual({
+      expect((yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).request.headers).toEqual({
         "HTTP-Referer": "https://example.com/",
         "X-Title": "custom-title",
       })
@@ -98,18 +97,17 @@ describe("ZenmuxPlugin", () => {
       const transform = yield* catalog.transform()
       yield* transform((catalog) => {
         const item = provider("openrouter", {
-          options: {
+          request: {
             headers: { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" },
             body: {},
-            aisdk: { provider: {}, request: {} },
           },
         })
         catalog.provider.update(item.id, (draft) => {
-          draft.options = item.options
+          draft.request = item.request
         })
       })
 
-      expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).options.headers).toEqual({
+      expect((yield* catalog.provider.get(ProviderV2.ID.openrouter)).request.headers).toEqual({
         "HTTP-Referer": "https://example.com/",
         "X-Title": "custom-title",
       })

+ 36 - 115
packages/sdk/js/src/v2/gen/types.gen.ts

@@ -2689,55 +2689,34 @@ export type ModelV2Info = {
   providerID: string
   family?: string
   name: string
-  endpoint:
-    | {
-        type: "unknown"
-      }
-    | {
-        type: "openai/responses"
-        url: string
-        websocket?: boolean
-      }
-    | {
-        type: "openai/completions"
-        url: string
-        reasoning?:
-          | {
-              type: "reasoning_content"
-            }
-          | {
-              type: "reasoning_details"
-            }
-      }
-    | {
-        type: "anthropic/messages"
-        url: string
-      }
+  api:
     | {
         type: "aisdk"
         package: string
         url?: string
+        settings?: {
+          [key: string]: unknown
+        }
+      }
+    | {
+        type: "native"
+        url?: string
+        settings: {
+          [key: string]: unknown
+        }
       }
   capabilities: {
     tools: boolean
     input: Array<string>
     output: Array<string>
   }
-  options: {
+  request: {
     headers: {
       [key: string]: string
     }
     body: {
       [key: string]: unknown
     }
-    aisdk: {
-      provider: {
-        [key: string]: unknown
-      }
-      request: {
-        [key: string]: unknown
-      }
-    }
     variant?: string
   }
   variants: Array<{
@@ -2748,14 +2727,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"
@@ -3640,50 +3611,29 @@ export type ProviderV2Info = {
         }
       }
   env: Array<string>
-  endpoint:
-    | {
-        type: "unknown"
-      }
-    | {
-        type: "openai/responses"
-        url: string
-        websocket?: boolean
-      }
-    | {
-        type: "openai/completions"
-        url: string
-        reasoning?:
-          | {
-              type: "reasoning_content"
-            }
-          | {
-              type: "reasoning_details"
-            }
-      }
-    | {
-        type: "anthropic/messages"
-        url: string
-      }
+  api:
     | {
         type: "aisdk"
         package: string
         url?: string
+        settings?: {
+          [key: string]: unknown
+        }
       }
-  options: {
+    | {
+        type: "native"
+        url?: string
+        settings: {
+          [key: string]: unknown
+        }
+      }
+  request: {
     headers: {
       [key: string]: string
     }
     body: {
       [key: string]: unknown
     }
-    aisdk: {
-      provider: {
-        [key: string]: unknown
-      }
-      request: {
-        [key: string]: unknown
-      }
-    }
   }
 }
 
@@ -3748,55 +3698,34 @@ export type ModelV2Info1 = {
   providerID: string
   family?: string
   name: string
-  endpoint:
-    | {
-        type: "unknown"
-      }
-    | {
-        type: "openai/responses"
-        url: string
-        websocket?: boolean
-      }
-    | {
-        type: "openai/completions"
-        url: string
-        reasoning?:
-          | {
-              type: "reasoning_content"
-            }
-          | {
-              type: "reasoning_details"
-            }
-      }
-    | {
-        type: "anthropic/messages"
-        url: string
-      }
+  api:
     | {
         type: "aisdk"
         package: string
         url?: string
+        settings?: {
+          [key: string]: unknown
+        }
+      }
+    | {
+        type: "native"
+        url?: string
+        settings: {
+          [key: string]: unknown
+        }
       }
   capabilities: {
     tools: boolean
     input: Array<string>
     output: Array<string>
   }
-  options: {
+  request: {
     headers: {
       [key: string]: string
     }
     body: {
       [key: string]: unknown
     }
-    aisdk: {
-      provider: {
-        [key: string]: unknown
-      }
-      request: {
-        [key: string]: unknown
-      }
-    }
     variant?: string
   }
   variants: Array<{
@@ -3807,14 +3736,6 @@ export type ModelV2Info1 = {
     body: {
       [key: string]: unknown
     }
-    aisdk: {
-      provider: {
-        [key: string]: unknown
-      }
-      request: {
-        [key: string]: unknown
-      }
-    }
   }>
   time: {
     released: number | "NaN" | "Infinity" | "-Infinity"