Problem
The media-generation activities, such as generateImage(), can await a provider request indefinitely. This is especially problematic for durable job and workflow systems: a hung provider connection leaves the job running forever, so its retry policy never activates.
The cancellation path is currently incomplete:
ImageAdapterConfig and equivalent adapter configs declare timeout?: number, but do not consume it.
ImageActivityOptions and ImageGenerationOptions expose neither a timeout nor an AbortSignal.
GenerationMiddleware is deliberately observe-only, so it cannot interrupt an in-flight adapter call.
@fal-ai/client supports abortSignal on fal.subscribe(), but @tanstack/ai-fal does not receive or forward one.
Chat already composes cancellation signals internally; media activities need the equivalent shared contract.
Proposed public API
Add optional execution controls to media activity options:
const controller = new AbortController()
await generateImage({
adapter: falImage("fal-ai/nano-banana-2"),
prompt: "A cinematic landscape",
timeout: 10 * 60 * 1000,
abortSignal: controller.signal,
})
timeout?: number is the maximum duration of one activity invocation.
abortSignal?: AbortSignal supports caller cancellation, request disconnects, and job/runtime cancellation.
- Core composes the caller signal and timeout signal, clears timeout resources when settled, and passes the effective signal to the adapter.
- Adapters forward the signal to provider SDKs where supported, for example
fal.subscribe(model, { input, abortSignal }).
This should cover generateImage, generateAudio, generateVideo, generateSpeech, generateTranscription, and likely summarize for a consistent activity contract. A first PR could intentionally scope to generateImage plus @tanstack/ai-fal.
Middleware
A generic timeout middleware is useful, but it cannot solve this alone today: GenerationMiddleware only observes onStart, onFinish, and onError, and has no signal or abort() capability.
Once core owns an effective activity signal, middleware could optionally receive:
interface GenerationMiddlewareContext {
// Existing fields
signal: AbortSignal
abort(reason?: unknown): void
}
That would enable a composable timeoutMiddleware(ms), but direct activity options should remain the baseline execution-control API.
Expected behavior
- No SDK-wide default timeout; callers choose values suitable for each provider or job.
- The first of caller cancellation or timeout wins and preserves its abort reason.
- A timeout should be treated as an abort in lifecycle middleware (
onAbort), while the activity promise rejects so callers and workflows can retry.
- A successful completion clears the timer.
- Request-specific signals must be passed to the provider call, not stored in global provider client configuration. This matters for Fal because
fal.config() is global and concurrent generations must remain isolated.
Tests
- Core: a timeout aborts and is forwarded to a media adapter.
- Core: a caller-provided signal is forwarded and the first abort reason wins.
- Core: a successful completion clears the timer.
- Core: a timeout triggers
onAbort exactly once rather than onError.
- Fal adapter:
fal.subscribe() receives the request-specific abortSignal.
Context
This was found while investigating a Fal image-generation request that did not resolve or reject. An application-level adapter-fetch timeout is a temporary safeguard, but a core activity-level cancellation contract would make this behavior reusable across providers and media activities.
Problem
The media-generation activities, such as
generateImage(), can await a provider request indefinitely. This is especially problematic for durable job and workflow systems: a hung provider connection leaves the job running forever, so its retry policy never activates.The cancellation path is currently incomplete:
ImageAdapterConfigand equivalent adapter configs declaretimeout?: number, but do not consume it.ImageActivityOptionsandImageGenerationOptionsexpose neither a timeout nor anAbortSignal.GenerationMiddlewareis deliberately observe-only, so it cannot interrupt an in-flight adapter call.@fal-ai/clientsupportsabortSignalonfal.subscribe(), but@tanstack/ai-faldoes not receive or forward one.Chat already composes cancellation signals internally; media activities need the equivalent shared contract.
Proposed public API
Add optional execution controls to media activity options:
timeout?: numberis the maximum duration of one activity invocation.abortSignal?: AbortSignalsupports caller cancellation, request disconnects, and job/runtime cancellation.fal.subscribe(model, { input, abortSignal }).This should cover
generateImage,generateAudio,generateVideo,generateSpeech,generateTranscription, and likelysummarizefor a consistent activity contract. A first PR could intentionally scope togenerateImageplus@tanstack/ai-fal.Middleware
A generic timeout middleware is useful, but it cannot solve this alone today:
GenerationMiddlewareonly observesonStart,onFinish, andonError, and has no signal orabort()capability.Once core owns an effective activity signal, middleware could optionally receive:
That would enable a composable
timeoutMiddleware(ms), but direct activity options should remain the baseline execution-control API.Expected behavior
onAbort), while the activity promise rejects so callers and workflows can retry.fal.config()is global and concurrent generations must remain isolated.Tests
onAbortexactly once rather thanonError.fal.subscribe()receives the request-specificabortSignal.Context
This was found while investigating a Fal image-generation request that did not resolve or reject. An application-level adapter-fetch timeout is a temporary safeguard, but a core activity-level cancellation contract would make this behavior reusable across providers and media activities.