ソースを参照

refactor(core): simplify model requests

Dax Raad 2 ヶ月 前
コミット
17f312d537

+ 0 - 9
CONTEXT.md

@@ -57,13 +57,6 @@ The bounded projection of a Core-executed tool result persisted in Session histo
 **Managed Tool Output File**:
 A temporary file created under OpenCode's shared tool-output directory to retain complete output that was too large for Session history.
 
-**Model Request Options**:
-Provider-semantic model settings selected from the Catalog and active Session variant before the LLM protocol adapter encodes them for a provider request.
-_Avoid_: Request body, wire options
-
-**Generation Controls**:
-Provider-neutral sampling and output controls, partitioned from provider semantics and compatibility wire fields when model metadata enters the Catalog.
-
 **PTY Environment**:
 The host-supplied environment overlay applied by the server when creating a PTY, observed for the request Location and resolved PTY working directory.
 
@@ -114,8 +107,6 @@ The host-supplied environment overlay applied by the server when creating a PTY,
 - A **Baseline System Context** durably preserves the exact joined text used for the active provider-cache prefix.
 - Completed compaction starts a new **Context Epoch** on the next provider attempt, folding the current complete **System Context** into a fresh baseline and removing earlier **Mid-Conversation System Messages** from active model history.
 - A model/provider switch preserves the current **Context Epoch** and chronological conversation history; the new selection applies to the next provider turn.
-- **Model Request Options** remain provider-semantic through Catalog resolution. The Session runner maps them into the LLM package's provider-option namespace; the selected protocol adapter alone owns provider wire encoding.
-- **Generation Controls**, protocol-semantic **Model Request Options**, and compatibility request body fields are separate Catalog domains. A shared ingestion adapter partitions legacy and models.dev AI-SDK-shaped options before routing.
 - The **PTY Environment** is a server concern rather than a Core PTY concern. PTY creation merges caller values, then the host overlay, then Core-forced terminal invariants such as `TERM` and `OPENCODE_TERMINAL`.
 - A **PTY Environment** adapter observes plugins in the request Location while passing the resolved PTY working directory to the hook; standalone servers use an empty adapter.
 - A **Mid-Conversation System Message** lowers to the provider's native chronological instruction role when supported and to a wrapped chronological fallback otherwise.

+ 2 - 2
packages/core/src/catalog.ts

@@ -2,7 +2,6 @@ export * as Catalog from "./catalog"
 
 import { Array, Context, Effect, Layer, Option, Order, pipe, Schema } from "effect"
 import { ModelV2 } from "./model"
-import { ModelRequest } from "./model-request"
 import { ProviderV2 } from "./provider"
 import { EventV2 } from "./event"
 import { Policy } from "./policy"
@@ -86,7 +85,8 @@ export const layer = Layer.effect(
               ? { ...model.api, settings: { ...provider.api.settings, ...model.api.settings } }
               : model.api
       const request = {
-        ...ModelRequest.merge({ ...provider.request, generation: {}, options: {} }, model.request),
+        headers: { ...provider.request.headers, ...model.request.headers },
+        body: { ...provider.request.body, ...model.request.body },
         variant: model.request.variant,
       }
       return ModelV2.Info.make({

+ 4 - 15
packages/core/src/config/plugin/provider.ts

@@ -4,7 +4,6 @@ import { define } from "../../plugin/internal"
 import { Effect } from "effect"
 import { Config } from "../../config"
 import { ModelV2 } from "../../model"
-import { ModelRequest } from "../../model-request"
 import { ProviderV2 } from "../../provider"
 
 export const Plugin = define({
@@ -59,15 +58,11 @@ export const Plugin = define({
                 Object.assign(provider.request.body, item.request.body)
               }
             })
-            const providerApi = catalog.provider.get(providerID)?.provider.api
-            const providerPackage = providerApi?.type === "aisdk" ? providerApi.package : undefined
-
             for (const [id, config] of Object.entries(item.models ?? {})) {
               catalog.model.update(providerID, id, (model) => {
                 if (config.family !== undefined) model.family = config.family
                 if (config.name !== undefined) model.name = config.name
                 if (config.api !== undefined) model.api = { ...model.api, ...config.api }
-                const packageName = model.api.type === "aisdk" ? model.api.package : providerPackage
                 if (config.capabilities !== undefined) {
                   model.capabilities = {
                     tools: config.capabilities.tools,
@@ -76,10 +71,8 @@ export const Plugin = define({
                   }
                 }
                 if (config.request !== undefined) {
-                  ModelRequest.assign(model.request, {
-                    headers: config.request.headers,
-                    ...ModelRequest.normalizeAiSdkOptions(packageName, config.request.body ?? {}),
-                  })
+                  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) {
@@ -90,15 +83,11 @@ export const Plugin = define({
                         id: variant.id,
                         headers: {},
                         body: {},
-                        generation: {},
-                        options: {},
                       }
                       model.variants.push(existing)
                     }
-                    ModelRequest.assign(existing, {
-                      headers: variant.headers,
-                      ...ModelRequest.normalizeAiSdkOptions(packageName, variant.body ?? {}),
-                    })
+                    Object.assign(existing.headers, variant.headers)
+                    Object.assign(existing.body, variant.body)
                   }
                 }
                 if (config.cost !== undefined) {

+ 0 - 102
packages/core/src/model-request.ts

@@ -1,102 +0,0 @@
-export * as ModelRequest from "./model-request"
-
-import { ModelRequest } from "@opencode-ai/schema/model-request"
-
-export const Generation = ModelRequest.Generation
-export type Generation = ModelRequest.Generation
-
-export const Request = ModelRequest.Request
-export type Request = ModelRequest.Request
-
-interface MutableRequest {
-  headers: Record<string, string>
-  body: Record<string, unknown>
-  generation?: Record<string, unknown>
-  options?: Record<string, unknown>
-}
-
-const generationKeys = new Map<string, keyof Generation>([
-  ["maxOutputTokens", "maxTokens"],
-  ["maxTokens", "maxTokens"],
-  ["temperature", "temperature"],
-  ["topP", "topP"],
-  ["topK", "topK"],
-  ["frequencyPenalty", "frequencyPenalty"],
-  ["presencePenalty", "presencePenalty"],
-  ["seed", "seed"],
-  ["stopSequences", "stop"],
-  ["stop", "stop"],
-])
-
-interface Profile {
-  readonly namespace: string
-  readonly semantics: ReadonlyMap<string, string>
-}
-
-const profiles = new Map<string, Profile>([
-  [
-    "@ai-sdk/openai",
-    {
-      namespace: "openai",
-      semantics: new Map([
-        ["store", "store"],
-        ["promptCacheKey", "promptCacheKey"],
-        ["reasoningEffort", "reasoningEffort"],
-        ["reasoningSummary", "reasoningSummary"],
-        ["include", "include"],
-        ["textVerbosity", "textVerbosity"],
-        ["serviceTier", "serviceTier"],
-        ["service_tier", "serviceTier"],
-      ]),
-    },
-  ],
-  [
-    "@ai-sdk/openai-compatible",
-    {
-      namespace: "openai",
-      semantics: new Map([
-        ["store", "store"],
-        ["promptCacheKey", "promptCacheKey"],
-        ["reasoningEffort", "reasoningEffort"],
-        ["reasoning_effort", "reasoningEffort"],
-      ]),
-    },
-  ],
-  ["@ai-sdk/anthropic", { namespace: "anthropic", semantics: new Map([["thinking", "thinking"]]) }],
-])
-
-export const namespace = (packageName: string) => profiles.get(packageName)?.namespace
-
-export const merge = (base: Request, override: Partial<Request>) => ({
-  headers: { ...base.headers, ...override.headers },
-  body: { ...base.body, ...override.body },
-  generation: { ...base.generation, ...override.generation },
-  options: { ...base.options, ...override.options },
-})
-
-export const assign = (target: MutableRequest, override: Partial<Request>) => {
-  Object.assign(target.headers, override.headers)
-  Object.assign(target.body, override.body)
-  Object.assign((target.generation ??= {}), override.generation)
-  Object.assign((target.options ??= {}), override.options)
-}
-
-/** Partitions AI-SDK-shaped request options before they enter the Catalog. */
-export function normalizeAiSdkOptions(packageName: string | undefined, input: Readonly<Record<string, unknown>>) {
-  const generation: Record<string, number | ReadonlyArray<string>> = {}
-  const options: Record<string, unknown> = {}
-  const body: Record<string, unknown> = {}
-  const semantics = profiles.get(packageName ?? "")?.semantics
-
-  for (const [key, value] of Object.entries(input)) {
-    const generationKey = generationKeys.get(key)
-    if (generationKey === "stop" && Array.isArray(value) && value.every((item) => typeof item === "string"))
-      generation[generationKey] = value
-    else if (generationKey !== undefined && generationKey !== "stop" && typeof value === "number")
-      generation[generationKey] = value
-    else if (semantics?.has(key)) options[semantics.get(key)!] = value
-    else body[key] = value
-  }
-
-  return { generation, options, body }
-}

+ 7 - 11
packages/core/src/plugin/models-dev.ts

@@ -2,7 +2,6 @@ import { define } from "./internal"
 import { Effect, Stream } from "effect"
 import { EventV2 } from "../event"
 import { ModelV2 } from "../model"
-import { ModelRequest } from "../model-request"
 import { ModelsDev } from "../models-dev"
 import { ProviderV2 } from "../provider"
 
@@ -38,15 +37,12 @@ function cost(input: ModelsDev.Model["cost"]) {
   ]
 }
 
-function variants(model: ModelsDev.Model, packageName?: string) {
-  return Object.entries(model.experimental?.modes ?? {}).map(([id, item]) => {
-    const request = ModelRequest.normalizeAiSdkOptions(packageName, item.provider?.body ?? {})
-    return {
-      id: ModelV2.VariantID.make(id),
-      headers: { ...(item.provider?.headers ?? {}) },
-      ...request,
-    }
-  })
+function variants(model: ModelsDev.Model) {
+  return Object.entries(model.experimental?.modes ?? {}).map(([id, item]) => ({
+    id: ModelV2.VariantID.make(id),
+    headers: { ...(item.provider?.headers ?? {}) },
+    body: { ...(item.provider?.body ?? {}) },
+  }))
 }
 
 export const ModelsDevPlugin = define({
@@ -115,7 +111,7 @@ export const ModelsDevPlugin = define({
                 input: [...(model.modalities?.input ?? [])],
                 output: [...(model.modalities?.output ?? [])],
               }
-              draft.variants = variants(model, model.provider?.npm ?? item.npm)
+              draft.variants = variants(model)
               draft.time.released = released(model.release_date)
               draft.cost = cost(model.cost)
               draft.status = model.status ?? "active"

+ 5 - 6
packages/core/src/plugin/provider/opencode.ts

@@ -8,9 +8,9 @@ import { EventV2 } from "../../event"
 import { Credential } from "../../credential"
 import { Integration } from "../../integration"
 import { ModelV2 } from "../../model"
-import { ModelRequest } from "../../model-request"
 import { ProviderV2 } from "../../provider"
 import { ConfigProviderV1 } from "../../v1/config/provider"
+import { ConfigProviderOptionsV1 } from "../../v1/config/provider-options"
 import { ConfigV1 } from "../../v1/config/config"
 
 const defaultServer = "https://console.opencode.ai"
@@ -142,15 +142,14 @@ export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | S
             if (config.modalities?.input !== undefined) model.capabilities.input = [...config.modalities.input]
             if (config.modalities?.output !== undefined) model.capabilities.output = [...config.modalities.output]
             const packageName = config.provider?.npm ?? item.npm
-            ModelRequest.assign(model.request, {
-              headers: config.headers,
-              ...ModelRequest.normalizeAiSdkOptions(packageName, withoutCredentials(config.options)),
-            })
+            const lowerer = ConfigProviderOptionsV1.get(packageName)
+            Object.assign(model.request.headers, config.headers)
+            Object.assign(model.request.body, lowerer.request(withoutCredentials(config.options)))
             if (config.variants !== undefined) {
               model.variants = Object.entries(config.variants).map(([id, options]) => ({
                 id: ModelV2.VariantID.make(id),
                 headers: { ...(options.headers ?? {}) },
-                ...ModelRequest.normalizeAiSdkOptions(packageName, withoutCredentials(options)),
+                body: lowerer.request(withoutCredentials(options)),
               }))
             }
             if (config.release_date !== undefined) {

+ 2 - 6
packages/core/src/session/runner/model.ts

@@ -11,7 +11,6 @@ import { Catalog } from "../../catalog"
 import { Credential } from "../../credential"
 import { Integration } from "../../integration"
 import { ModelV2 } from "../../model"
-import { ModelRequest } from "../../model-request"
 import { ProviderV2 } from "../../provider"
 import { SessionSchema } from "../schema"
 
@@ -88,8 +87,6 @@ const apiKey = (model: ModelV2.Info, credential?: Credential.Value) => {
 }
 
 const withDefaults = (model: ModelV2.Info, route: AnyRoute) => {
-  const options = model.request.options ?? {}
-  const namespace = model.api.type === "aisdk" ? ModelRequest.namespace(model.api.package) : undefined
   const body = model.request.body
   const httpBody = Object.hasOwn(body, "apiKey")
     ? Object.fromEntries(Object.entries(body).filter(([key]) => key !== "apiKey"))
@@ -98,8 +95,6 @@ const withDefaults = (model: ModelV2.Info, route: AnyRoute) => {
     provider: model.providerID,
     endpoint: model.api.url === undefined ? undefined : { baseURL: model.api.url },
     headers: model.request.headers,
-    generation: model.request.generation,
-    providerOptions: namespace && Object.keys(options).length > 0 ? { [namespace]: options } : undefined,
     http: { body: httpBody },
     limits: { context: model.limit.context, output: model.limit.output },
   })
@@ -122,7 +117,8 @@ const withVariant = (
   return Effect.succeed(
     variant
       ? produce(model, (draft) => {
-          ModelRequest.assign(draft.request, variant)
+          Object.assign(draft.request.headers, variant.headers)
+          Object.assign(draft.request.body, variant.body)
         })
       : model,
   )

+ 2 - 7
packages/core/src/v1/config/migrate.ts

@@ -6,7 +6,6 @@ import { ConfigMCPV1 } from "./mcp"
 import { ConfigPermissionV1 } from "./permission"
 import { ConfigProviderV1 } from "./provider"
 import { ConfigProviderOptionsV1 } from "./provider-options"
-import { ModelRequest } from "../../model-request"
 
 const keys = new Set([
   "logLevel",
@@ -192,11 +191,7 @@ function migrateProvider(info: ConfigProviderV1.Info) {
 function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: string) {
   const packageID = info.provider?.npm ?? packageName
   const lowerer = ConfigProviderOptionsV1.get(packageID)
-  const ingest = (options: Readonly<Record<string, unknown>>) => {
-    const request = ModelRequest.normalizeAiSdkOptions(packageID, options)
-    return { ...lowerer.request(request.body), ...request.generation, ...request.options }
-  }
-  const request = info.options && ingest(info.options)
+  const request = info.options && lowerer.request(info.options)
   const costs = info.cost && [
     {
       input: info.cost.input,
@@ -241,7 +236,7 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st
       info.variants &&
       Object.entries(info.variants).map(([id, options]) => ({
         id,
-        body: ingest(options),
+        body: lowerer.request(options),
       })),
     cost: costs,
     disabled: info.status === "deprecated" ? true : undefined,

+ 2 - 5
packages/core/test/catalog.test.ts

@@ -233,16 +233,13 @@ describe("CatalogV2", () => {
           model.request.headers.shared = "model"
           model.request.body.model = true
           model.request.body.request = true
-          const options = (model.request.options ??= {})
-          options.shared = "model"
-          options.model = true
+          model.request.body.shared = "model"
         })
       })
 
       const model = required(yield* catalog.model.get(providerID, modelID))
       expect(model.request.headers).toEqual({ provider: "provider", shared: "model", model: "model" })
-      expect(model.request.body).toEqual({ provider: true, model: true, request: true })
-      expect(model.request.options).toEqual({ shared: "model", model: true })
+      expect(model.request.body).toEqual({ provider: true, model: true, request: true, shared: "model" })
     }),
   )
 

+ 2 - 2
packages/core/test/config/config.test.ts

@@ -599,9 +599,9 @@ describe("Config", () => {
               models: {
                 model: {
                   request: {
-                    body: { temperature: 0.3, reasoningEffort: "high", serviceTier: "priority" },
+                    body: { temperature: 0.3, reasoning_effort: "high", service_tier: "priority" },
                   },
-                  variants: [{ id: "high", body: { reasoningEffort: "high", reasoningSummary: "auto" } }],
+                  variants: [{ id: "high", body: { reasoning_effort: "high", reasoning_summary: "auto" } }],
                 },
               },
             })

+ 4 - 6
packages/core/test/config/provider.test.ts

@@ -55,7 +55,7 @@ function request(headers: Record<string, string>, variant?: string) {
 const decode = Schema.decodeUnknownSync(Config.Info)
 
 describe("ConfigProviderPlugin.Plugin", () => {
-  it.effect("partitions existing model variant bodies without changing config shape", () =>
+  it.effect("keeps configured model variant bodies unchanged", () =>
     Effect.gen(function* () {
       const catalog = yield* Catalog.Service
       const providerID = ProviderV2.ID.opencode
@@ -96,8 +96,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
       expect(model.variants).toMatchObject([
         {
           id: "high",
-          body: {},
-          options: {
+          body: {
             reasoningEffort: "high",
             reasoningSummary: "auto",
             include: ["reasoning.encrypted_content"],
@@ -107,7 +106,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
     }),
   )
 
-  it.effect("uses the effective provider package across layered config", () =>
+  it.effect("keeps layered model variant bodies unchanged", () =>
     Effect.gen(function* () {
       const catalog = yield* Catalog.Service
       const providerID = ProviderV2.ID.opencode
@@ -147,8 +146,7 @@ describe("ConfigProviderPlugin.Plugin", () => {
       const model = required(yield* catalog.model.get(providerID, modelID))
       expect(model.variants[0]).toMatchObject({
         id: "high",
-        body: {},
-        options: { reasoningEffort: "high" },
+        body: { reasoningEffort: "high" },
       })
     }),
   )

+ 0 - 44
packages/core/test/model-request.test.ts

@@ -1,44 +0,0 @@
-import { describe, expect, test } from "bun:test"
-import { ModelRequest } from "@opencode-ai/core/model-request"
-
-describe("ModelRequest", () => {
-  test("partitions AI SDK model and models.dev mode options", () => {
-    expect(
-      ModelRequest.normalizeAiSdkOptions("@ai-sdk/openai", {
-        maxOutputTokens: 4096,
-        temperature: 0.2,
-        reasoningEffort: "high",
-        serviceTier: "priority",
-        custom_extension: { enabled: true },
-      }),
-    ).toEqual({
-      generation: { maxTokens: 4096, temperature: 0.2 },
-      options: { reasoningEffort: "high", serviceTier: "priority" },
-      body: { custom_extension: { enabled: true } },
-    })
-  })
-
-  test("keeps unknown-provider options as compatibility fields", () => {
-    expect(ModelRequest.normalizeAiSdkOptions(undefined, { temperature: 0.2, reasoningEffort: "high" })).toEqual({
-      generation: { temperature: 0.2 },
-      options: {},
-      body: { reasoningEffort: "high" },
-    })
-  })
-
-  test("does not consult inherited package-name properties", () => {
-    expect(ModelRequest.normalizeAiSdkOptions("__proto__", { reasoningEffort: "high" })).toEqual({
-      generation: {},
-      options: {},
-      body: { reasoningEffort: "high" },
-    })
-  })
-
-  test("normalizes models.dev wire aliases owned by native protocols", () => {
-    expect(ModelRequest.normalizeAiSdkOptions("@ai-sdk/openai", { service_tier: "priority" })).toEqual({
-      generation: {},
-      options: { serviceTier: "priority" },
-      body: {},
-    })
-  })
-})

+ 0 - 10
packages/core/test/plugin/host.ts

@@ -290,21 +290,11 @@ function modelInfo(value: ModelV2.Info | ModelV2.MutableInfo) {
       ...value.request,
       headers: { ...value.request.headers },
       body: { ...value.request.body },
-      generation: value.request.generation && {
-        ...value.request.generation,
-        stop: value.request.generation.stop && [...value.request.generation.stop],
-      },
-      options: value.request.options && { ...value.request.options },
     },
     variants: value.variants.map((variant) => ({
       ...variant,
       headers: { ...variant.headers },
       body: { ...variant.body },
-      generation: variant.generation && {
-        ...variant.generation,
-        stop: variant.generation.stop && [...variant.generation.stop],
-      },
-      options: variant.options && { ...variant.options },
     })),
     time: { ...value.time },
     cost: value.cost.map((cost) => ({ ...cost, tier: cost.tier && { ...cost.tier }, cache: { ...cost.cache } })),

+ 2 - 5
packages/core/test/plugin/provider-opencode.test.ts

@@ -150,15 +150,12 @@ describe("OpencodePlugin", () => {
             cost: [{ input: 1, output: 2, cache: { read: 0.1, write: 0 } }],
             limit: { context: 1000, output: 100 },
           })
-          expect(model.request).toMatchObject({ body: { custom: "value" }, generation: { temperature: 0.5 } })
-          expect(model.request.body).toEqual({ custom: "value" })
+          expect(model.request.body).toEqual({ custom: "value", temperature: 0.5 })
           expect(model.variants).toEqual([
             {
               id: ModelV2.VariantID.make("high"),
               headers: {},
-              body: {},
-              generation: { temperature: 0.2 },
-              options: {},
+              body: { temperature: 0.2 },
             },
           ])
           expect(

+ 21 - 36
packages/core/test/session-runner-model.test.ts

@@ -31,8 +31,6 @@ const model = (api: Api, variants: ModelV2.Info["variants"] = []) =>
     request: {
       headers: { "x-test": "header" },
       body: { apiKey: "secret", custom_extension: { enabled: true } },
-      generation: { temperature: 0.7 },
-      options: { store: false, serviceTier: "priority" },
     },
     variants,
     time: { released: 0 },
@@ -56,8 +54,6 @@ describe("SessionRunnerModel", () => {
         defaults: {
           headers: { "x-test": "header" },
           limits: { context: 100, output: 20 },
-          generation: { temperature: 0.7 },
-          providerOptions: { openai: { store: false, serviceTier: "priority" } },
           http: { body: { custom_extension: { enabled: true } } },
         },
       })
@@ -86,7 +82,7 @@ describe("SessionRunnerModel", () => {
             url: "https://compatible.example/v1",
             settings: { apiKey: "settings-secret", compatibility: "strict" },
           }),
-          request: { headers: {}, body: {}, generation: {}, options: {} },
+          request: { headers: {}, body: {} },
         }),
       )
       const request = LLM.request({ model: resolved, prompt: "Hello" })
@@ -103,21 +99,20 @@ describe("SessionRunnerModel", () => {
     }),
   )
 
-  it.effect("lowers selected OpenAI Session variants into Responses options", () =>
+  it.effect("overlays selected OpenAI Session variant bodies", () =>
     Effect.gen(function* () {
-      const base = model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }, [
+      const catalog = model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }, [
         {
           id: ModelV2.VariantID.make("high"),
           headers: { "x-variant": "high" },
-          body: {},
-          generation: { temperature: 0.2 },
-          options: { reasoningEffort: "high" },
+          body: {
+            store: false,
+            service_tier: "priority",
+            temperature: 0.2,
+            reasoning: { effort: "high" },
+          },
         },
       ])
-      const catalog = ModelV2.Info.make({
-        ...base,
-        request: { ...base.request, options: { ...base.request.options, reasoningEffort: "medium" } },
-      })
       const session = SessionV2.Info.make({
         id: SessionV2.ID.make("ses_model_variant"),
         projectID: ProjectV2.ID.global,
@@ -134,21 +129,19 @@ describe("SessionRunnerModel", () => {
       })
 
       const resolved = yield* SessionRunnerModel.resolve(session, catalog)
-      const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" }))
 
       expect(resolved.route.defaults.headers).toMatchObject({ "x-test": "header", "x-variant": "high" })
-      expect(resolved.route.defaults.http?.body).toEqual({ custom_extension: { enabled: true } })
-      expect(prepared.body).toMatchObject({
+      expect(resolved.route.defaults.http?.body).toEqual({
+        custom_extension: { enabled: true },
         store: false,
         service_tier: "priority",
         temperature: 0.2,
         reasoning: { effort: "high" },
       })
-      expect(prepared.body).not.toHaveProperty("reasoningEffort")
     }),
   )
 
-  it.effect("lowers selected OpenAI-compatible Session variants into Chat options", () =>
+  it.effect("overlays selected OpenAI-compatible Session variant bodies", () =>
     Effect.gen(function* () {
       const catalog = model(
         { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://compatible.example/v1" },
@@ -156,9 +149,7 @@ describe("SessionRunnerModel", () => {
           {
             id: ModelV2.VariantID.make("high"),
             headers: {},
-            body: {},
-            generation: {},
-            options: { reasoningEffort: "high" },
+            body: { store: false, reasoning_effort: "high" },
           },
         ],
       )
@@ -174,14 +165,12 @@ describe("SessionRunnerModel", () => {
       })
 
       const resolved = yield* SessionRunnerModel.resolve(session, catalog)
-      const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" }))
 
-      expect(resolved.route.defaults.http?.body).toEqual({ custom_extension: { enabled: true } })
-      expect(prepared.body).toMatchObject({
+      expect(resolved.route.defaults.http?.body).toEqual({
+        custom_extension: { enabled: true },
         store: false,
         reasoning_effort: "high",
       })
-      expect(prepared.body).not.toHaveProperty("reasoningEffort")
     }),
   )
 
@@ -215,15 +204,13 @@ describe("SessionRunnerModel", () => {
     }),
   )
 
-  it.effect("lowers selected Anthropic Session variants into Messages options", () =>
+  it.effect("overlays selected Anthropic Session variant bodies", () =>
     Effect.gen(function* () {
       const catalog = model({ type: "aisdk", package: "@ai-sdk/anthropic", url: "https://anthropic.example/v1" }, [
         {
           id: ModelV2.VariantID.make("high"),
           headers: {},
-          body: {},
-          generation: {},
-          options: { thinking: { type: "enabled", budgetTokens: 12000 } },
+          body: { thinking: { type: "enabled", budget_tokens: 12000 } },
         },
       ])
       const session = SessionV2.Info.make({
@@ -238,13 +225,11 @@ describe("SessionRunnerModel", () => {
       })
 
       const resolved = yield* SessionRunnerModel.resolve(session, catalog)
-      const prepared = yield* LLMClient.prepare(LLM.request({ model: resolved, prompt: "Hello" }))
 
-      expect(resolved.route.defaults.http?.body).toEqual({ custom_extension: { enabled: true } })
-      expect(prepared.body).toMatchObject({
+      expect(resolved.route.defaults.http?.body).toEqual({
+        custom_extension: { enabled: true },
         thinking: { type: "enabled", budget_tokens: 12000 },
       })
-      expect(JSON.stringify(prepared.body)).not.toContain("budgetTokens")
     }),
   )
 
@@ -266,7 +251,7 @@ describe("SessionRunnerModel", () => {
       const resolved = yield* SessionRunnerModel.fromCatalogModel(
         ModelV2.Info.make({
           ...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
-          request: { headers: {}, body: {}, generation: {}, options: {} },
+          request: { headers: {}, body: {} },
         }),
         Credential.Key.make({ type: "key", key: "secret" }),
       )
@@ -289,7 +274,7 @@ describe("SessionRunnerModel", () => {
       const resolved = yield* SessionRunnerModel.fromCatalogModel(
         ModelV2.Info.make({
           ...model({ type: "aisdk", package: "@ai-sdk/openai", url: "https://openai.example/v1" }),
-          request: { headers: {}, body: { apiKey: "configured-secret" }, generation: {}, options: {} },
+          request: { headers: {}, body: { apiKey: "configured-secret" } },
         }),
         credential,
       )

+ 0 - 5
packages/core/test/shared-schema.test.ts

@@ -19,7 +19,6 @@ import { Credential } from "@opencode-ai/schema/credential"
 import { FileSystem } from "@opencode-ai/schema/filesystem"
 import { Integration } from "@opencode-ai/schema/integration"
 import { LLM } from "@opencode-ai/schema/llm"
-import { ModelRequest } from "@opencode-ai/schema/model-request"
 import { Permission } from "@opencode-ai/schema/permission"
 import { Reference } from "@opencode-ai/schema/reference"
 import { Skill } from "@opencode-ai/schema/skill"
@@ -35,7 +34,6 @@ test("Core reuses the canonical shared schemas", async () => {
     coreIntegration,
     coreLocation,
     coreLLM,
-    coreModelRequest,
     corePermission,
     coreProject,
     coreReference,
@@ -53,7 +51,6 @@ test("Core reuses the canonical shared schemas", async () => {
     import("@opencode-ai/core/integration"),
     import("@opencode-ai/core/location"),
     import("@opencode-ai/llm"),
-    import("@opencode-ai/core/model-request"),
     import("@opencode-ai/core/permission"),
     import("@opencode-ai/core/project/schema"),
     import("@opencode-ai/core/reference"),
@@ -111,8 +108,6 @@ test("Core reuses the canonical shared schemas", async () => {
     [ProviderV2.Api, Provider.Api],
     [ProviderV2.Request, Provider.Request],
     [ProviderV2.Info, Provider.Info],
-    [coreModelRequest.Generation, ModelRequest.Generation],
-    [coreModelRequest.Request, ModelRequest.Request],
     [corePermission.Effect, Permission.Effect],
     [corePermission.Rule, Permission.Rule],
     [corePermission.Ruleset, Permission.Ruleset],

+ 0 - 1
packages/schema/src/index.ts

@@ -7,7 +7,6 @@ export { Integration } from "./integration"
 export { LLM } from "./llm"
 export { Location } from "./location"
 export { Model } from "./model"
-export { ModelRequest } from "./model-request"
 export { Permission } from "./permission"
 export { Project } from "./project"
 export { Provider } from "./provider"

+ 0 - 31
packages/schema/src/model-request.ts

@@ -1,31 +0,0 @@
-export * as ModelRequest from "./model-request"
-
-import { Effect, Schema } from "effect"
-import { Provider } from "./provider"
-
-export interface Generation extends Schema.Schema.Type<typeof Generation> {}
-export const Generation = Schema.Struct({
-  maxTokens: Schema.Number.pipe(Schema.optional),
-  temperature: Schema.Number.pipe(Schema.optional),
-  topP: Schema.Number.pipe(Schema.optional),
-  topK: Schema.Number.pipe(Schema.optional),
-  frequencyPenalty: Schema.Number.pipe(Schema.optional),
-  presencePenalty: Schema.Number.pipe(Schema.optional),
-  seed: Schema.Number.pipe(Schema.optional),
-  stop: Schema.String.pipe(Schema.Array, Schema.mutable, Schema.optional),
-})
-
-export interface Request extends Schema.Schema.Type<typeof Request> {}
-export const Request = Schema.Struct({
-  ...Provider.Request.fields,
-  generation: Generation.pipe(
-    Schema.optionalKey,
-    Schema.withConstructorDefault(Effect.succeed({})),
-    Schema.withDecodingDefaultKey(Effect.succeed({})),
-  ),
-  options: Schema.Record(Schema.String, Schema.Any).pipe(
-    Schema.optionalKey,
-    Schema.withConstructorDefault(Effect.succeed({})),
-    Schema.withDecodingDefaultKey(Effect.succeed({})),
-  ),
-})

+ 3 - 4
packages/schema/src/model.ts

@@ -1,7 +1,6 @@
 export * as Model from "./model"
 
 import { Schema } from "effect"
-import { ModelRequest } from "./model-request"
 import { Provider } from "./provider"
 import { withStatics } from "./schema"
 
@@ -63,12 +62,12 @@ export const Info = Schema.Struct({
   api: Api,
   capabilities: Capabilities,
   request: Schema.Struct({
-    ...ModelRequest.Request.fields,
+    ...Provider.Request.fields,
     variant: Schema.String.pipe(Schema.optional),
   }),
   variants: Schema.Struct({
     id: VariantID,
-    ...ModelRequest.Request.fields,
+    ...Provider.Request.fields,
   }).pipe(Schema.Array, Schema.mutable),
   time: Schema.Struct({
     released: Schema.Finite,
@@ -92,7 +91,7 @@ export const Info = Schema.Struct({
           name: modelID,
           api: { id: modelID, type: "native", settings: {} },
           capabilities: { tools: false, input: [], output: [] },
-          request: { headers: {}, body: {}, generation: {}, options: {} },
+          request: { headers: {}, body: {} },
           variants: [],
           time: { released: 0 },
           cost: [],

+ 0 - 26
packages/sdk/js/src/v2/gen/types.gen.ts

@@ -4019,19 +4019,6 @@ export type ModelV2Info = {
     body: {
       [key: string]: unknown
     }
-    generation?: {
-      maxTokens?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      temperature?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      topP?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      topK?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      frequencyPenalty?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      presencePenalty?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      seed?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      stop?: Array<string>
-    }
-    options?: {
-      [key: string]: unknown
-    }
     variant?: string
   }
   variants: Array<{
@@ -4042,19 +4029,6 @@ export type ModelV2Info = {
     body: {
       [key: string]: unknown
     }
-    generation?: {
-      maxTokens?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      temperature?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      topP?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      topK?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      frequencyPenalty?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      presencePenalty?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      seed?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
-      stop?: Array<string>
-    }
-    options?: {
-      [key: string]: unknown
-    }
   }>
   time: {
     released: number

+ 0 - 352
packages/sdk/openapi.json

@@ -26833,182 +26833,6 @@
               "body": {
                 "type": "object"
               },
-              "generation": {
-                "type": "object",
-                "properties": {
-                  "maxTokens": {
-                    "anyOf": [
-                      {
-                        "type": "number"
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["NaN"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["-Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity", "-Infinity", "NaN"]
-                      }
-                    ]
-                  },
-                  "temperature": {
-                    "anyOf": [
-                      {
-                        "type": "number"
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["NaN"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["-Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity", "-Infinity", "NaN"]
-                      }
-                    ]
-                  },
-                  "topP": {
-                    "anyOf": [
-                      {
-                        "type": "number"
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["NaN"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["-Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity", "-Infinity", "NaN"]
-                      }
-                    ]
-                  },
-                  "topK": {
-                    "anyOf": [
-                      {
-                        "type": "number"
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["NaN"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["-Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity", "-Infinity", "NaN"]
-                      }
-                    ]
-                  },
-                  "frequencyPenalty": {
-                    "anyOf": [
-                      {
-                        "type": "number"
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["NaN"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["-Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity", "-Infinity", "NaN"]
-                      }
-                    ]
-                  },
-                  "presencePenalty": {
-                    "anyOf": [
-                      {
-                        "type": "number"
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["NaN"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["-Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity", "-Infinity", "NaN"]
-                      }
-                    ]
-                  },
-                  "seed": {
-                    "anyOf": [
-                      {
-                        "type": "number"
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["NaN"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["-Infinity"]
-                      },
-                      {
-                        "type": "string",
-                        "enum": ["Infinity", "-Infinity", "NaN"]
-                      }
-                    ]
-                  },
-                  "stop": {
-                    "type": "array",
-                    "items": {
-                      "type": "string"
-                    }
-                  }
-                },
-                "additionalProperties": false
-              },
-              "options": {
-                "type": "object"
-              },
               "variant": {
                 "type": "string"
               }
@@ -27032,182 +26856,6 @@
                 },
                 "body": {
                   "type": "object"
-                },
-                "generation": {
-                  "type": "object",
-                  "properties": {
-                    "maxTokens": {
-                      "anyOf": [
-                        {
-                          "type": "number"
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["NaN"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["-Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity", "-Infinity", "NaN"]
-                        }
-                      ]
-                    },
-                    "temperature": {
-                      "anyOf": [
-                        {
-                          "type": "number"
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["NaN"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["-Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity", "-Infinity", "NaN"]
-                        }
-                      ]
-                    },
-                    "topP": {
-                      "anyOf": [
-                        {
-                          "type": "number"
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["NaN"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["-Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity", "-Infinity", "NaN"]
-                        }
-                      ]
-                    },
-                    "topK": {
-                      "anyOf": [
-                        {
-                          "type": "number"
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["NaN"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["-Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity", "-Infinity", "NaN"]
-                        }
-                      ]
-                    },
-                    "frequencyPenalty": {
-                      "anyOf": [
-                        {
-                          "type": "number"
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["NaN"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["-Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity", "-Infinity", "NaN"]
-                        }
-                      ]
-                    },
-                    "presencePenalty": {
-                      "anyOf": [
-                        {
-                          "type": "number"
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["NaN"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["-Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity", "-Infinity", "NaN"]
-                        }
-                      ]
-                    },
-                    "seed": {
-                      "anyOf": [
-                        {
-                          "type": "number"
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["NaN"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["-Infinity"]
-                        },
-                        {
-                          "type": "string",
-                          "enum": ["Infinity", "-Infinity", "NaN"]
-                        }
-                      ]
-                    },
-                    "stop": {
-                      "type": "array",
-                      "items": {
-                        "type": "string"
-                      }
-                    }
-                  },
-                  "additionalProperties": false
-                },
-                "options": {
-                  "type": "object"
                 }
               },
               "required": ["id", "headers", "body"],