Background
Commit 603d4abe (#564) fixed AudioProviderOptions in packages/typescript/ai/src/activities/generateAudio/index.ts:
// Before — collapsed to `object` for any concrete provider options interface
export type AudioProviderOptions<TAdapter> =
TAdapter extends AudioAdapter<any, any>
? TAdapter['~types']['providerOptions']
: object
// After — brand-based structural infer
export type AudioProviderOptions<TAdapter> =
TAdapter extends { '~types': { providerOptions: infer P extends object } }
? P
: object
The same broken pattern still exists in three sibling activities.
Affected files
| Activity |
File |
Helper |
| TTS |
packages/typescript/ai/src/activities/generateSpeech/index.ts:30 |
TTSProviderOptions |
| Transcription |
packages/typescript/ai/src/activities/generateTranscription/index.ts:30 |
TranscriptionProviderOptions |
| Summarize |
packages/typescript/ai/src/activities/summarize/index.ts:28 |
SummarizeProviderOptions |
All three use the same extends *Adapter<any, any> ? T['~types']['providerOptions'] : object shape that was just fixed in audio.
Why this is silent today
Audio surfaced because FalAudioProviderOptions<'fal-ai/minimax-music/v2'> has a required field (lyrics_prompt). The contravariance check on generateAudio's parameter only fires when the concrete provider options has a required field.
Concrete options for the other three activities (OpenAITTSProviderOptions, OpenAITranscriptionProviderOptions, etc.) are all-optional, so the constraint check coincidentally passes — but modelOptions at user call sites is silently typed as object, meaning users get no autocomplete or validation on modelOptions for these activities.
Add a single required field to any concrete *ProviderOptions and these would break the same way ai-fal did.
Fix
For each of the three helpers, apply the same swap:
export type TTSProviderOptions<TAdapter> =
TAdapter extends { '~types': { providerOptions: infer P extends object } }
? P
: object
Note on Summarize: in addition to the helper, SummarizeActivityOptions and its three function-signature constraints at summarize/index.ts:45, 153, 299 currently use extends SummarizeAdapter<string, object> — the broken contravariant form. These should switch to the F-bounded extends SummarizeAdapter<string, SummarizeProviderOptions<TAdapter>> shape (matching audio's pattern) once the helper is fixed.
Acceptance criteria
Out of scope (separate follow-ups)
- Image / Video constraint cleanup:
generateImage/index.ts and generateVideo/index.ts use 4-generic adapters with <string, any, any, any> constraints throughout. Their *ProviderOptions helpers happen to extract correctly (via infer'd positions), but the surrounding any placeholders deserve a tightening pass.
- Chat (
TextAdapter) AnyTextAdapter removal: chat's surface uses extends AnyTextAdapter = TextAdapter<any, any, any, any, any, any, any> at every call site (chat/index.ts:84, 244, 289, 303, 1633). Replacing with an F-bounded constraint is materially different work — likely half a day to a day — and warrants its own issue.
Background
Commit
603d4abe(#564) fixedAudioProviderOptionsinpackages/typescript/ai/src/activities/generateAudio/index.ts:The same broken pattern still exists in three sibling activities.
Affected files
packages/typescript/ai/src/activities/generateSpeech/index.ts:30TTSProviderOptionspackages/typescript/ai/src/activities/generateTranscription/index.ts:30TranscriptionProviderOptionspackages/typescript/ai/src/activities/summarize/index.ts:28SummarizeProviderOptionsAll three use the same
extends *Adapter<any, any> ? T['~types']['providerOptions'] : objectshape that was just fixed in audio.Why this is silent today
Audio surfaced because
FalAudioProviderOptions<'fal-ai/minimax-music/v2'>has a required field (lyrics_prompt). The contravariance check ongenerateAudio's parameter only fires when the concrete provider options has a required field.Concrete options for the other three activities (
OpenAITTSProviderOptions,OpenAITranscriptionProviderOptions, etc.) are all-optional, so the constraint check coincidentally passes — butmodelOptionsat user call sites is silently typed asobject, meaning users get no autocomplete or validation onmodelOptionsfor these activities.Add a single required field to any concrete
*ProviderOptionsand these would break the same way ai-fal did.Fix
For each of the three helpers, apply the same swap:
Note on Summarize: in addition to the helper,
SummarizeActivityOptionsand its three function-signature constraints atsummarize/index.ts:45, 153, 299currently useextends SummarizeAdapter<string, object>— the broken contravariant form. These should switch to the F-boundedextends SummarizeAdapter<string, SummarizeProviderOptions<TAdapter>>shape (matching audio's pattern) once the helper is fixed.Acceptance criteria
TTSProviderOptions,TranscriptionProviderOptions,SummarizeProviderOptionsrewritten to use brand-basedinfer P extends object.<string, object>to F-bounded form.pnpm test:typesgreen across the workspace (the existing ai-openai/ai-fal/ai-anthropic adapter tests are the regression net).anys introduced..agent/self-learning/coupling.jsontriggers: updatepackages/typescript/ai/skills/ai-core/media-generation/SKILL.mdif any user-visible typing guidance changes; update relevantdocs/media/**pages ifmodelOptionsexamples are shown.Out of scope (separate follow-ups)
generateImage/index.tsandgenerateVideo/index.tsuse 4-generic adapters with<string, any, any, any>constraints throughout. Their*ProviderOptionshelpers happen to extract correctly (viainfer'd positions), but the surroundinganyplaceholders deserve a tightening pass.TextAdapter)AnyTextAdapterremoval: chat's surface usesextends AnyTextAdapter = TextAdapter<any, any, any, any, any, any, any>at every call site (chat/index.ts:84, 244, 289, 303, 1633). Replacing with an F-bounded constraint is materially different work — likely half a day to a day — and warrants its own issue.