timeout<T> method
Runs operation, failing with AgenticTimeoutException after limit.
Implemented against Clock.delay rather than Future.timeout so that a
fake clock can drive it. name appears in the error and should identify
the operation, for example openai.chat.completions.
The underlying future is not cancelled — Dart futures cannot be — so
operations that hold resources should also observe a
CancellationToken. What this guarantees is that the caller stops
waiting, not that the work stops running.
Implementation
Future<T> timeout<T>(
Future<T> Function() operation, {
required Duration limit,
required String name,
}) {
final completer = Completer<T>();
// Both completion paths below are wired into `completer`, so the future
// is deliberately not awaited here.
// ignore: discarded_futures
final work = operation();
unawaited(
work.then<void>(
(value) {
if (!completer.isCompleted) completer.complete(value);
},
onError: (Object error, StackTrace stackTrace) {
if (!completer.isCompleted) {
completer.completeError(error, stackTrace);
}
},
),
);
unawaited(
delay(limit).then((_) {
if (completer.isCompleted) return;
completer.completeError(
AgenticTimeoutException(
'`$name` did not complete within '
'${limit.inMilliseconds}ms.',
operation: name,
timeout: limit,
),
StackTrace.current,
);
}),
);
return completer.future;
}