From 033ddf76132147f2828ab393caa4db82b1359e04 Mon Sep 17 00:00:00 2001 From: "15367279252qq.com" Date: Mon, 30 Mar 2026 01:04:56 +0800 Subject: [PATCH 1/5] fix(types): fix the type definitions for factory models and config, and add InferRestArgs utility type --- packages/ai/src/extend-adapter.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/ai/src/extend-adapter.ts b/packages/ai/src/extend-adapter.ts index e2e904161..3aacf05e1 100644 --- a/packages/ai/src/extend-adapter.ts +++ b/packages/ai/src/extend-adapter.ts @@ -149,7 +149,7 @@ type ExtractCustomModelNames> = * For generic functions like `(model: T)`, this gets `T` which * TypeScript treats as the constraint union when used in parameter position. */ -type InferFactoryModels = TFactory extends ( +export type InferFactoryModels = TFactory extends ( model: infer TModel, ...args: Array ) => any @@ -161,7 +161,7 @@ type InferFactoryModels = TFactory extends ( /** * Infer the config parameter type from an adapter factory function. */ -type InferConfig = TFactory extends ( +export type InferConfig = TFactory extends ( model: any, config?: infer TConfig, ) => any @@ -177,6 +177,12 @@ type InferAdapterReturn = TFactory extends ( ? TReturn : never +/** + * Extracts all parameter types after the first parameter from a function. + */ +type InferRestArgs any> = + Parameters extends [any, ...infer Rest] ? Rest : [] + // =========================== // extendAdapter Function // =========================== @@ -225,19 +231,17 @@ type InferAdapterReturn = TFactory extends ( * ``` */ export function extendAdapter< - TFactory extends (...args: Array) => any, + TFactory extends (model: any, ...args: Array) => any, const TDefs extends ReadonlyArray, >( factory: TFactory, _customModels: TDefs, -): ( - model: InferFactoryModels | ExtractCustomModelNames, - ...args: InferConfig extends undefined - ? [] - : [config?: InferConfig] -) => InferAdapterReturn { +) { // At runtime, we simply pass through to the original factory. // The _customModels parameter is only used for type inference. // No runtime validation - users are trusted to pass valid model names. - return factory as any -} + return factory as unknown as ( + model: InferFactoryModels | ExtractCustomModelNames, + ...args: InferRestArgs + ) => InferAdapterReturn +} \ No newline at end of file From 8633c8a1b163242732e9f7399bd91b1de659310e Mon Sep 17 00:00:00 2001 From: "15367279252qq.com" Date: Mon, 30 Mar 2026 01:12:35 +0800 Subject: [PATCH 2/5] refactor(types): simplify type definitions for factory models by removing unused InferConfig type --- packages/ai/src/extend-adapter.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/ai/src/extend-adapter.ts b/packages/ai/src/extend-adapter.ts index 3aacf05e1..25ae0fe94 100644 --- a/packages/ai/src/extend-adapter.ts +++ b/packages/ai/src/extend-adapter.ts @@ -149,7 +149,7 @@ type ExtractCustomModelNames> = * For generic functions like `(model: T)`, this gets `T` which * TypeScript treats as the constraint union when used in parameter position. */ -export type InferFactoryModels = TFactory extends ( +type InferFactoryModels = TFactory extends ( model: infer TModel, ...args: Array ) => any @@ -158,15 +158,6 @@ export type InferFactoryModels = TFactory extends ( : string : string -/** - * Infer the config parameter type from an adapter factory function. - */ -export type InferConfig = TFactory extends ( - model: any, - config?: infer TConfig, -) => any - ? TConfig - : undefined /** * Infer the adapter return type from a factory function. From 865a2e832ee695ff744bbece3b810324c4941843 Mon Sep 17 00:00:00 2001 From: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Date: Thu, 4 Jun 2026 21:11:13 +1000 Subject: [PATCH 3/5] fix(types): handle optional model param, restore explicit return type, add regression test Review follow-ups for #408: - InferRestArgs now matches [any?, ...] so factories with an optional first parameter don't silently drop their trailing args - Extract the extended signature into a named ExtendedFactory type and restore an explicit return type on extendAdapter (no cast needed) - Add a regression test for 3-parameter factories like createAnthropicChat(model, apiKey, config?) (#407) - Add a patch changeset for @tanstack/ai Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/fix-extend-adapter-rest-args.md | 5 ++++ packages/ai/src/extend-adapter.ts | 35 ++++++++++++++-------- packages/ai/tests/extend-adapter.test.ts | 29 ++++++++++++++++++ 3 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 .changeset/fix-extend-adapter-rest-args.md diff --git a/.changeset/fix-extend-adapter-rest-args.md b/.changeset/fix-extend-adapter-rest-args.md new file mode 100644 index 000000000..83178b0b3 --- /dev/null +++ b/.changeset/fix-extend-adapter-rest-args.md @@ -0,0 +1,5 @@ +--- +'@tanstack/ai': patch +--- + +Fix `extendAdapter` dropping required parameters after the model (e.g. `apiKey` in `createAnthropicChat`). All factory parameters after the model are now preserved, including labels and optionality. diff --git a/packages/ai/src/extend-adapter.ts b/packages/ai/src/extend-adapter.ts index 25ae0fe94..33a7cba1c 100644 --- a/packages/ai/src/extend-adapter.ts +++ b/packages/ai/src/extend-adapter.ts @@ -158,7 +158,6 @@ type InferFactoryModels = TFactory extends ( : string : string - /** * Infer the adapter return type from a factory function. */ @@ -169,10 +168,26 @@ type InferAdapterReturn = TFactory extends ( : never /** - * Extracts all parameter types after the first parameter from a function. + * Extracts all parameter types after the model parameter from a factory, + * preserving labels and optionality (e.g. `[apiKey: string, config?: C]`). + * Note: overloaded factories resolve against their last overload (a + * `Parameters` limitation). */ -type InferRestArgs any> = - Parameters extends [any, ...infer Rest] ? Rest : [] +type InferRestArgs) => any> = + Parameters extends [any?, ...infer TRest] ? TRest : [] + +/** + * The factory signature produced by `extendAdapter`: accepts both original + * and custom model names while preserving all remaining parameters and the + * return type of the original factory. + */ +type ExtendedFactory< + TFactory extends (model: any, ...args: Array) => any, + TDefs extends ReadonlyArray, +> = ( + model: InferFactoryModels | ExtractCustomModelNames, + ...args: InferRestArgs +) => InferAdapterReturn // =========================== // extendAdapter Function @@ -224,15 +239,9 @@ type InferRestArgs any> = export function extendAdapter< TFactory extends (model: any, ...args: Array) => any, const TDefs extends ReadonlyArray, ->( - factory: TFactory, - _customModels: TDefs, -) { +>(factory: TFactory, _customModels: TDefs): ExtendedFactory { // At runtime, we simply pass through to the original factory. // The _customModels parameter is only used for type inference. // No runtime validation - users are trusted to pass valid model names. - return factory as unknown as ( - model: InferFactoryModels | ExtractCustomModelNames, - ...args: InferRestArgs - ) => InferAdapterReturn -} \ No newline at end of file + return factory +} diff --git a/packages/ai/tests/extend-adapter.test.ts b/packages/ai/tests/extend-adapter.test.ts index 0bdbd888b..522727a6b 100644 --- a/packages/ai/tests/extend-adapter.test.ts +++ b/packages/ai/tests/extend-adapter.test.ts @@ -243,6 +243,35 @@ describe('extendAdapter', () => { }) }) + describe('Factories with required args after model (#407)', () => { + // Mimics createAnthropicChat(model, apiKey, config?) + function mockChat( + model: TModel, + apiKey: string, + config?: MockAdapterConfig, + ): MockTextAdapter { + void apiKey + return new MockTextAdapter(model, config) + } + + it('should preserve required apiKey and optional config parameters', () => { + const extendedMock = extendAdapter(mockChat, customModels) + + expectTypeOf(extendedMock).parameter(1).toEqualTypeOf() + + const adapter = extendedMock('my-fine-tuned-model', 'sk-test', { + baseURL: 'https://custom.api.com', + }) + expect(adapter.model).toBe('my-fine-tuned-model') + + // config stays optional + void extendedMock('mock-gpt-4', 'sk-test') + + // @ts-expect-error - apiKey is required + void extendedMock('mock-gpt-4') + }) + }) + describe('Empty custom models', () => { it('should work with empty custom models array', () => { const extendedMock = extendAdapter(mockText, [] as const) From b3916152cbe8c6d38135513311f34082e46a0da7 Mon Sep 17 00:00:00 2001 From: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Date: Thu, 4 Jun 2026 21:23:08 +1000 Subject: [PATCH 4/5] refactor(types): remove all any from extend-adapter type utilities Replace any-typed function shapes with a sound AnyAdapterFactory constraint built on never params / unknown return (parameters are contravariant, so never accepts every factory). The deliberate model-union widening now lives in an overload signature instead of an unchecked assignment, so no cast or any remains anywhere in the file. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/ai/src/extend-adapter.ts | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/ai/src/extend-adapter.ts b/packages/ai/src/extend-adapter.ts index 33a7cba1c..89ca2bc71 100644 --- a/packages/ai/src/extend-adapter.ts +++ b/packages/ai/src/extend-adapter.ts @@ -144,6 +144,14 @@ type ExtractCustomModelNames> = // Factory Type Inference // =========================== +/** + * The widest factory shape `extendAdapter` accepts: any function taking a + * model as its first parameter. Parameters are contravariant, so `never` + * params and an `unknown` return accept every factory without resorting + * to `any`. + */ +type AnyAdapterFactory = (model: never, ...args: Array) => unknown + /** * Infer the model parameter type from an adapter factory function. * For generic functions like `(model: T)`, this gets `T` which @@ -151,8 +159,8 @@ type ExtractCustomModelNames> = */ type InferFactoryModels = TFactory extends ( model: infer TModel, - ...args: Array -) => any + ...args: Array +) => unknown ? TModel extends string ? TModel : string @@ -162,7 +170,7 @@ type InferFactoryModels = TFactory extends ( * Infer the adapter return type from a factory function. */ type InferAdapterReturn = TFactory extends ( - ...args: Array + ...args: Array ) => infer TReturn ? TReturn : never @@ -173,8 +181,8 @@ type InferAdapterReturn = TFactory extends ( * Note: overloaded factories resolve against their last overload (a * `Parameters` limitation). */ -type InferRestArgs) => any> = - Parameters extends [any?, ...infer TRest] ? TRest : [] +type InferRestArgs = + Parameters extends [unknown?, ...infer TRest] ? TRest : [] /** * The factory signature produced by `extendAdapter`: accepts both original @@ -182,7 +190,7 @@ type InferRestArgs) => any> = * return type of the original factory. */ type ExtendedFactory< - TFactory extends (model: any, ...args: Array) => any, + TFactory extends AnyAdapterFactory, TDefs extends ReadonlyArray, > = ( model: InferFactoryModels | ExtractCustomModelNames, @@ -237,9 +245,15 @@ type ExtendedFactory< * ``` */ export function extendAdapter< - TFactory extends (model: any, ...args: Array) => any, + TFactory extends AnyAdapterFactory, const TDefs extends ReadonlyArray, ->(factory: TFactory, _customModels: TDefs): ExtendedFactory { +>(factory: TFactory, _customModels: TDefs): ExtendedFactory +// The implementation signature stays at the honest `AnyAdapterFactory` width; +// the overload above performs the deliberate model-union widening. +export function extendAdapter( + factory: AnyAdapterFactory, + _customModels: ReadonlyArray, +): AnyAdapterFactory { // At runtime, we simply pass through to the original factory. // The _customModels parameter is only used for type inference. // No runtime validation - users are trusted to pass valid model names. From c345a7347b4d7e9f64d269049f117cdd049faef5 Mon Sep 17 00:00:00 2001 From: Tom Beckenham <34339192+tombeckenham@users.noreply.github.com> Date: Fri, 5 Jun 2026 10:37:31 +1000 Subject: [PATCH 5/5] test(types): assert invalid model names are rejected for 3-param factories Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/ai/tests/extend-adapter.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/ai/tests/extend-adapter.test.ts b/packages/ai/tests/extend-adapter.test.ts index 522727a6b..605aaa940 100644 --- a/packages/ai/tests/extend-adapter.test.ts +++ b/packages/ai/tests/extend-adapter.test.ts @@ -269,6 +269,9 @@ describe('extendAdapter', () => { // @ts-expect-error - apiKey is required void extendedMock('mock-gpt-4') + + // @ts-expect-error - invalid model names still rejected + void extendedMock('not-a-model', 'sk-test') }) })