Skip to main content
Every route — HTTP and in-process — runs the same lifecycle: beforeOperation → beforeUpstream → afterUpstream or afterError → afterOperation. Register arrays of hooks per phase; each hook runs in array order.

Phases

The hooks option takes one array per phase. Every key is optional. Each hook receives one argument object and may return a promise; the gateway awaits it before running the next hook. Every phase shares requestId, operation, startedAt, a mutable context bag, and mutable otel attributes. See Hook arguments for every field.

Hook arguments

Every phase receives these fields: Every phase except beforeOperation also receives: The remaining fields depend on the phase.

beforeOperation

beforeUpstream

afterUpstream

afterError

afterOperation

HookUsage

LanguageParams


Mutate the upstream call

beforeUpstream is the last chance to inspect or change a call before it reaches the provider. Mutation is in-place only — reassigning a field is a no-op, because the handler holds the original reference.
messages and params are present on the language routes (chat, messages, responses) and absent on modality routes (embeddings, images, speech, transcriptions, rerank, video, evaluate) — those operations have no messages or language params. On chat and messages, message/part-level providerOptions.unknown remains available during the hook and is forwarded into the provider namespace afterward. headers and request-level providerOptions are honored on every route. system and tools are read-only.

Track usage

afterOperation’s usage is summed across every round of a tool loop, not just the final one — use it for billing without re-implementing aggregation.
HookUsage also reports cachedInputTokens, cacheWriteTokens, and reasoningTokens when the provider surfaces them. For evaluate, reported input/output tokens reach afterUpstream and afterOperation. When only one count is reported, hook usage uses zero for the missing count; when neither is reported, hook usage is absent. The HTTP response omits unreported usage fields instead. Request-bound FrogBot usage records are written asynchronously, so completion of evaluate() does not guarantee that a record has been saved.

Failure isolation

afterError and afterOperation isolate hook failures — a throwing hook doesn’t stop the remaining hooks in that phase from running. A throwing beforeOperation hook stops the call before the lifecycle starts, so afterOperation never runs for that failure. For HTTP evaluation, failures before model resolution also skip afterError and afterOperation; once resolved, a failed evaluation runs both phases. For an in-process evaluation, afterUpstream means doEvaluate returned. If the AI SDK subsequently rejects an invalid answer, afterOperation.error reports the failure even though afterUpstream already ran and reported any consumed tokens. The HTTP route wraps the full SDK call, so the same invalid answer runs afterError before afterOperation instead.

Combine hook sets

mergeHooks concatenates hook arrays from multiple sources — first-party provider middleware, plugins, and your own config — with a defined precedence order.