From 8c3e06798c5b145d29edd1b52c3dc9dc572bde8d Mon Sep 17 00:00:00 2001 From: Shoubhit Dash Date: Wed, 29 Jul 2026 18:18:00 +0530 Subject: [PATCH] refactor(ai): limit provider option inference (#39510) --- packages/ai/src/route/client.ts | 25 ++++++---------------- packages/ai/src/schema/options.ts | 21 +++++++----------- packages/ai/test/llm-option-types.types.ts | 17 +-------------- 3 files changed, 15 insertions(+), 48 deletions(-) diff --git a/packages/ai/src/route/client.ts b/packages/ai/src/route/client.ts index 98d0cab83c..b935699f90 100644 --- a/packages/ai/src/route/client.ts +++ b/packages/ai/src/route/client.ts @@ -45,9 +45,7 @@ export interface Route { readonly defaults: RouteDefaults readonly body: RouteBody readonly with: (patch: RoutePatch) => Route - readonly model: ( - input: RouteMappedModelInput, - ) => Model + readonly model: (input: RouteMappedModelInput) => Model readonly prepareTransport: (body: Body, request: LLMRequest) => Effect.Effect readonly streamPrepared: ( prepared: Prepared, @@ -64,15 +62,9 @@ export type AnyRoute = Route export type HttpOptionsInput = HttpOptions.Input -export type RouteModelInput = Omit< - Model.Input, - "provider" | "route" -> +export type RouteModelInput = Omit -export type RouteRoutedModelInput = Omit< - Model.Input, - "route" -> +export type RouteRoutedModelInput = Omit export interface RouteDefaults { readonly headers?: Record @@ -98,14 +90,9 @@ export interface RoutePatch extends RouteDefaultsInput { readonly endpoint?: EndpointPatch } -type RouteMappedModelInput = - | RouteModelInput - | RouteRoutedModelInput +type RouteMappedModelInput = RouteModelInput | RouteRoutedModelInput -const makeRouteModel = ( - route: AnyRoute, - mapped: RouteMappedModelInput, -) => { +const makeRouteModel = (route: AnyRoute, mapped: RouteMappedModelInput) => { const provider = route.provider ?? ("provider" in mapped ? mapped.provider : undefined) if (!provider) throw new Error(`Route.model(${route.id}) requires a provider`) if (!endpointBaseURL(route.endpoint)) @@ -297,7 +284,7 @@ function makeFromTransport( defaults: mergeRouteDefaults(route.defaults, defaults), }) }, - model: (input: RouteMappedModelInput) => + model: (input: RouteMappedModelInput) => makeRouteModel(route, input), prepareTransport: (body, request) => routeInput.transport.prepare({ diff --git a/packages/ai/src/schema/options.ts b/packages/ai/src/schema/options.ts index 668954d1cf..df7c6544aa 100644 --- a/packages/ai/src/schema/options.ts +++ b/packages/ai/src/schema/options.ts @@ -139,17 +139,15 @@ export class ModelDefaults extends Schema.Class("LLM.ModelDefault generation: Schema.optional(GenerationOptions), providerOptions: Schema.optional(ProviderOptions), http: Schema.optional(HttpOptions), -}) { - declare protected readonly _ModelDefaults: void -} +}) {} export namespace ModelDefaults { - export type Input = + export type Input = | ModelDefaults | { readonly limits?: ModelLimits.Input readonly generation?: GenerationOptions.Input - readonly providerOptions?: Options + readonly providerOptions?: ProviderOptions readonly http?: HttpOptions.Input } @@ -196,7 +194,7 @@ export class Model { this.compatibility = input.compatibility } - static make(input: Model.Input) { + static make(input: Model.Input) { return new Model({ id: ModelID.make(input.id), provider: ProviderID.make(input.provider), @@ -216,7 +214,7 @@ export class Model { } } - static update(model: Model, patch: Partial>) { + static update(model: Model, patch: Partial) { if (Object.keys(patch).length === 0) return model return Model.make({ ...Model.input(model), @@ -234,18 +232,15 @@ export namespace Model { readonly compatibility?: ModelCompatibility } - export type Input = Omit< - ConstructorInput, - "id" | "provider" | "defaults" | "compatibility" - > & { + export type Input = Omit & { readonly id: string | ModelID readonly provider: string | ProviderID - readonly defaults?: ModelDefaults.Input + readonly defaults?: ModelDefaults.Input readonly compatibility?: ModelCompatibility.Input } } -export type ModelInput = Model.Input +export type ModelInput = Model.Input export type ModelProviderOptions = SelectedModel extends Model ? Options : never diff --git a/packages/ai/test/llm-option-types.types.ts b/packages/ai/test/llm-option-types.types.ts index d13ebdcc43..8171a7b2e9 100644 --- a/packages/ai/test/llm-option-types.types.ts +++ b/packages/ai/test/llm-option-types.types.ts @@ -1,5 +1,5 @@ import { Schema } from "effect" -import { LLM, Model, type ModelProviderOptions, type ProviderOptions } from "../src" +import { LLM, type Model, type ModelProviderOptions, type ProviderOptions } from "../src" import { OpenAIChat } from "../src/protocols" interface ExampleOptions { @@ -45,18 +45,3 @@ LLM.request({ model: generic, prompt: "Hello", providerOptions: { arbitrary: { o const options: ModelProviderOptions = { example: { mode: "fast" } } void options - -model.route.model({ - id: "example-with-defaults", - defaults: { - // @ts-expect-error Low-level model defaults preserve known provider option types. - providerOptions: { example: { mode: 1 } }, - }, -}) - -Model.update(model, { - defaults: { - // @ts-expect-error Updating a model cannot contradict its provider option type. - providerOptions: { example: { mode: "slow" } }, - }, -})