refactor(ai): limit provider option inference (#39510)

This commit is contained in:
Shoubhit Dash 2026-07-29 18:18:00 +05:30 committed by GitHub
commit 8c3e06798c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 15 additions and 48 deletions

View file

@ -45,9 +45,7 @@ export interface Route<Body, Prepared = unknown> {
readonly defaults: RouteDefaults
readonly body: RouteBody<Body>
readonly with: (patch: RoutePatch<Body, Prepared>) => Route<Body, Prepared>
readonly model: <Options extends ProviderOptions = ProviderOptions>(
input: RouteMappedModelInput<Options>,
) => Model<Options>
readonly model: <Options extends ProviderOptions = ProviderOptions>(input: RouteMappedModelInput) => Model<Options>
readonly prepareTransport: (body: Body, request: LLMRequest) => Effect.Effect<Prepared, LLMError>
readonly streamPrepared: (
prepared: Prepared,
@ -64,15 +62,9 @@ export type AnyRoute = Route<any, any>
export type HttpOptionsInput = HttpOptions.Input
export type RouteModelInput<Options extends ProviderOptions = ProviderOptions> = Omit<
Model.Input<Options>,
"provider" | "route"
>
export type RouteModelInput = Omit<Model.Input, "provider" | "route">
export type RouteRoutedModelInput<Options extends ProviderOptions = ProviderOptions> = Omit<
Model.Input<Options>,
"route"
>
export type RouteRoutedModelInput = Omit<Model.Input, "route">
export interface RouteDefaults {
readonly headers?: Record<string, string>
@ -98,14 +90,9 @@ export interface RoutePatch<Body, Prepared> extends RouteDefaultsInput {
readonly endpoint?: EndpointPatch<Body>
}
type RouteMappedModelInput<Options extends ProviderOptions = ProviderOptions> =
| RouteModelInput<Options>
| RouteRoutedModelInput<Options>
type RouteMappedModelInput = RouteModelInput | RouteRoutedModelInput
const makeRouteModel = <Options extends ProviderOptions = ProviderOptions>(
route: AnyRoute,
mapped: RouteMappedModelInput<Options>,
) => {
const makeRouteModel = <Options extends ProviderOptions = ProviderOptions>(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<Body, Prepared, Frame, Event, State>(
defaults: mergeRouteDefaults(route.defaults, defaults),
})
},
model: <Options extends ProviderOptions = ProviderOptions>(input: RouteMappedModelInput<Options>) =>
model: <Options extends ProviderOptions = ProviderOptions>(input: RouteMappedModelInput) =>
makeRouteModel<Options>(route, input),
prepareTransport: (body, request) =>
routeInput.transport.prepare({

View file

@ -139,17 +139,15 @@ export class ModelDefaults extends Schema.Class<ModelDefaults>("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<Options extends ProviderOptions = ProviderOptions> =
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<Options extends ProviderOptions = ProviderOptions> {
this.compatibility = input.compatibility
}
static make<Options extends ProviderOptions = ProviderOptions>(input: Model.Input<Options>) {
static make<Options extends ProviderOptions = ProviderOptions>(input: Model.Input) {
return new Model<Options>({
id: ModelID.make(input.id),
provider: ProviderID.make(input.provider),
@ -216,7 +214,7 @@ export class Model<Options extends ProviderOptions = ProviderOptions> {
}
}
static update<Options extends ProviderOptions>(model: Model<Options>, patch: Partial<Model.Input<Options>>) {
static update<Options extends ProviderOptions>(model: Model<Options>, patch: Partial<Model.Input>) {
if (Object.keys(patch).length === 0) return model
return Model.make<Options>({
...Model.input(model),
@ -234,18 +232,15 @@ export namespace Model {
readonly compatibility?: ModelCompatibility
}
export type Input<Options extends ProviderOptions = ProviderOptions> = Omit<
ConstructorInput,
"id" | "provider" | "defaults" | "compatibility"
> & {
export type Input = Omit<ConstructorInput, "id" | "provider" | "defaults" | "compatibility"> & {
readonly id: string | ModelID
readonly provider: string | ProviderID
readonly defaults?: ModelDefaults.Input<Options>
readonly defaults?: ModelDefaults.Input
readonly compatibility?: ModelCompatibility.Input
}
}
export type ModelInput<Options extends ProviderOptions = ProviderOptions> = Model.Input<Options>
export type ModelInput = Model.Input
export type ModelProviderOptions<SelectedModel> = SelectedModel extends Model<infer Options> ? Options : never

View file

@ -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<typeof model> = { example: { mode: "fast" } }
void options
model.route.model<ExampleProviderOptions>({
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" } },
},
})