AuthInterceptor class
Authenticates every run by composing an interceptor instead of configuring the agent (FR-B2). Takes an async header-builder — a closure that can mint a Bearer token, read a keychain, or refresh on demand — resolves it per run (or per retry attempt, see below), and threads the resolved headers onto the outgoing POST.
HttpAgent(
url: endpoint,
interceptors: [
AuthInterceptor(headers: () async => {'Authorization': 'Bearer $token'}),
],
);
The header-injection seam. An Interceptor can only transform
RunAgentInput → Stream<AgUiEvent>; it has no direct path to the HTTP
request — the POST headers are built deep inside HttpAgent's transport. The
carrier from interceptor to transport is therefore the RunAgentInput
itself: the resolved headers ride on input.forwardedProps under the
koel-reserved transportHeadersKey. HttpAgent's transport extracts that
key, strips it from the body before encoding (so the token never reaches
the wire), and merges the headers into the request with the protocol headers
(Content-Type/Accept) winning. Stacked AuthInterceptors compose — the
inner one's reserved map is read and merged, later keys winning.
Once per run, or once per retry attempt — by ordering, not config. The
surface is exactly one named param; "per-run vs per-attempt" is the
composition knob, not a flag. Plain interceptors: [AuthInterceptor(...)]
resolves once per run. With HttpAgent(retry: ...), the auto-built
RetryInterceptor sits outermost and re-proceeds through this interceptor
on every reconnect, so the callback re-fires per attempt — the token-refresh
hook.
A throwing callback becomes a terminal RunErrorEvent. Any throw from
the callback is wrapped as a typed
BusinessError(code: KoelErrorCode.businessAuth, cause: <original>) and
surfaced through the stream; the InterceptorChain classifier passes the
already-typed error through unchanged to a single terminal RunErrorEvent.
The wrapper message is fixed and secret-free — no token is ever echoed into
an error (architecture §5). When resolution fails, no transport connection
opens (the failure precedes chain.proceed).
Open for subclassing. class … implements Interceptor (deliberately
not final): Epic-5's AgnoAuthInterceptor extends AuthInterceptor.
Constructors
-
AuthInterceptor({required Future<
Map< headers()})String, String> > -
Creates an interceptor that resolves
headersper run/attempt and threads the result onto the outgoing request.headersis invoked each time the chain reaches this interceptor; its returned map is merged into the request headers under transportHeadersKey (protocol headers always win). A throw fromheaderssurfaces asRunErrorEvent(BusinessError(businessAuth)).
Properties
- hashCode → int
-
The hash code for this object.
no setterinherited
- runtimeType → Type
-
A representation of the runtime type of the object.
no setterinherited
Methods
-
intercept(
InterceptorChain chain, RunAgentInput input) → Stream< AgUiEvent> -
Wraps the execution of
input, delegating tochainto reach the next interceptor (or the terminal agent). Returns the — possibly transformed — event stream the run produces. -
noSuchMethod(
Invocation invocation) → dynamic -
Invoked when a nonexistent method or property is accessed.
inherited
-
toString(
) → String -
A string representation of this object.
inherited
Operators
-
operator ==(
Object other) → bool -
The equality operator.
inherited
Constants
- transportHeadersKey → const String
-
The koel-namespaced
forwardedPropskey under which resolved auth headers ride from this interceptor toHttpAgent's transport.