Found while reviewing the @tanstack/ai-octane port (#1000). Fixed there; filing so the other adapters can catch up.
Problem
useGeneration takes a TInput extends Record<string, any> type parameter, and its internal generate really is (input: TInput) => Promise<void>. But the exported return type throws that away:
export interface UseGenerationReturn<TOutput> {
generate: (input: Record<string, any>) => Promise<void>
// ...
}
and the implementation casts to match:
return {
generate: generate as (input: Record<string, any>) => Promise<void>,
// ...
}
So required and narrow input fields are unchecked at the call site. With useGeneration<{ prompt: string; steps: number }, R>, all of these compile:
generate({ prompt: 'x' }) // missing `steps`
generate({ prompt: 'x', steps: 'four' }) // wrong type
generate({ prompt: 'x', steps: 4, seed: 1 }) // unknown key
Fix
Parameterize the return type with TInput and drop the cast:
-export interface UseGenerationReturn<TOutput> {
- generate: (input: Record<string, any>) => Promise<void>
+export interface UseGenerationReturn<TInput, TOutput> {
+ generate: (input: TInput) => Promise<void>
-): UseGenerationReturn<InferGenerationOutputFromReturn<TResult, TTransformed>> {
+): UseGenerationReturn<
+ TInput,
+ InferGenerationOutputFromReturn<TResult, TTransformed>
+> {
return {
- generate: generate as (input: Record<string, any>) => Promise<void>,
+ generate,
In ai-octane this needed no other changes — the media hooks (useGenerateImage/Audio/Speech/Video) declare their own return types and don't reference UseGenerationReturn.
Caveat
This changes the arity of an exported type, so it's breaking for anyone who writes UseGenerationReturn<T> explicitly. Worth batching into a release that's already taking a version bump. ai-octane could absorb it freely (unpublished at 0.0.0), which is why it diverges today — see packages/ai-octane/status.json.
Test
packages/ai-octane/typetests/use-generation.test-d.ts locks this in with @ts-expect-error cases for each of the three bad calls above; verified to fail if the widening is reintroduced.
Affected
(@tanstack/ai-octane fixed in #1000.)
Found while reviewing the
@tanstack/ai-octaneport (#1000). Fixed there; filing so the other adapters can catch up.Problem
useGenerationtakes aTInput extends Record<string, any>type parameter, and its internalgeneratereally is(input: TInput) => Promise<void>. But the exported return type throws that away:and the implementation casts to match:
So required and narrow input fields are unchecked at the call site. With
useGeneration<{ prompt: string; steps: number }, R>, all of these compile:Fix
Parameterize the return type with
TInputand drop the cast:return { - generate: generate as (input: Record<string, any>) => Promise<void>, + generate,In
ai-octanethis needed no other changes — the media hooks (useGenerateImage/Audio/Speech/Video) declare their own return types and don't referenceUseGenerationReturn.Caveat
This changes the arity of an exported type, so it's breaking for anyone who writes
UseGenerationReturn<T>explicitly. Worth batching into a release that's already taking a version bump.ai-octanecould absorb it freely (unpublished at0.0.0), which is why it diverges today — seepackages/ai-octane/status.json.Test
packages/ai-octane/typetests/use-generation.test-d.tslocks this in with@ts-expect-errorcases for each of the three bad calls above; verified to fail if the widening is reintroduced.Affected
@tanstack/ai-react@tanstack/ai-vue@tanstack/ai-solid(
@tanstack/ai-octanefixed in #1000.)