showcase/shell-docs/src/content/ag-ui/concepts/middleware.mdx
Middleware in AG-UI provides a powerful way to transform, filter, and augment the event streams that flow through agents. It enables you to add cross-cutting concerns like logging, authentication, rate limiting, and event filtering without modifying the core agent logic.
Examples below assume the relevant RxJS operators/utilities (map, tap, catchError, switchMap, timer, etc.) are imported.
Middleware sits between the agent execution and the event consumer, allowing you to:
Middleware forms a chain where each middleware wraps the next, creating layers of functionality. When an agent runs, the event stream flows through each middleware in sequence.
const agent = new MyAgent();
// Middleware chain: logging -> auth -> filter -> agent
agent.use(loggingMiddleware, authMiddleware, filterMiddleware);
// When agent runs, events flow through all middleware
await agent.runAgent();
Middleware added with agent.use(...) is applied in runAgent(). connectAgent() currently calls connect() directly and does not run middleware.
For simple transformations, you can use function-based middleware. This is the most concise way to add middleware:
const prefixMiddleware: MiddlewareFunction = (input, next) => {
return next.run(input).pipe(
map((event) => {
if (
event.type === EventType.TEXT_MESSAGE_CHUNK ||
event.type === EventType.TEXT_MESSAGE_CONTENT
) {
return {
...event,
delta: `[AI]: ${event.delta}`,
};
}
return event;
}),
);
};
agent.use(prefixMiddleware);
For more complex scenarios requiring state or configuration, use class-based middleware:
class MetricsMiddleware extends Middleware {
private eventCount = 0;
constructor(private metricsService: MetricsService) {
super();
}
run(input: RunAgentInput, next: AbstractAgent): Observable<BaseEvent> {
const startTime = Date.now();
return this.runNext(input, next).pipe(
tap((event) => {
this.eventCount++;
this.metricsService.recordEvent(event.type);
}),
finalize(() => {
const duration = Date.now() - startTime;
this.metricsService.recordDuration(duration);
this.metricsService.recordEventCount(this.eventCount);
}),
);
}
}
agent.use(new MetricsMiddleware(metricsService));
If you are writing class middleware, prefer the helper methods:
runNext(input, next) normalizes chunk events into full TEXT_MESSAGE_*/TOOL_CALL_* sequences.runNextWithState(input, next) also provides accumulated messages and state after each event.AG-UI provides several built-in middleware components for common use cases:
Filter tool calls based on allowed or disallowed lists:
// Only allow specific tools
const allowedFilter = new FilterToolCallsMiddleware({
allowedToolCalls: ["search", "calculate"],
});
// Or block specific tools
const blockedFilter = new FilterToolCallsMiddleware({
disallowedToolCalls: ["delete", "modify"],
});
agent.use(allowedFilter);
FilterToolCallsMiddleware filters emitted TOOL_CALL_* events. It does not block tool execution in the upstream model/runtime.
Common patterns include logging, auth via forwardedProps, and rate limiting. See the JS middleware reference for concrete implementations.
You can combine multiple middleware to create sophisticated processing pipelines:
const logMiddleware: MiddlewareFunction = (input, next) => next.run(input);
const metricsMiddleware = new MetricsMiddleware(metricsService);
const filterMiddleware = new FilterToolCallsMiddleware({
allowedToolCalls: ["search"],
});
agent.use(logMiddleware, metricsMiddleware, filterMiddleware);
Middleware executes in the order it's added, with each middleware wrapping the next:
agent.use(middleware1, middleware2, middleware3);
// Execution flow:
// → middleware1
// → middleware2
// → middleware3
// → agent.run()
// ← events flow back through middleware3
// ← events flow back through middleware2
// ← events flow back through middleware1
Apply middleware based on runtime conditions:
const conditionalMiddleware: MiddlewareFunction = (input, next) => {
if (input.forwardedProps?.debug === true) {
// Apply debug logging
return next.run(input).pipe(tap((event) => console.debug(event)));
}
return next.run(input);
};
For event transformation and stream-control variants, see the JS middleware reference.
Middleware provides a flexible and powerful way to extend AG-UI agents without modifying their core logic. Whether you need simple event transformation or complex stateful processing, the middleware system offers the tools to build robust, maintainable agent applications.