Advanced
Tool middleware
Intercept completion requests, tool input, and tool output.
Middleware transforms runtime data without changing the core tool contract. Use it for instrumentation, normalization, request shaping, large-result spillover, and safe output filtering.
Use hooks when you need to decide whether a run continues. Use middleware when you need to transform requests or results.
Create Middleware
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { createMiddleware } from "@anvia/core";
const TOOL_OUTPUT_LIMIT = 4_000;
const toolOutputDir = "/var/app/tool-outputs";
const spillLargeToolOutput = createMiddleware({
async onToolOutput({ toolName, internalCallId, result }) {
if (result.length <= TOOL_OUTPUT_LIMIT) {
return;
}
await mkdir(toolOutputDir, { recursive: true });
const filename = `${toolName}-${internalCallId}.txt`;
const path = join(toolOutputDir, filename);
await writeFile(path, result, "utf8");
return {
result: `Tool output was ${result.length} characters and was written to ${path}.`,
};
},
});
Attach middleware to the agent when it should apply to every run:
const agent = new AgentBuilder("support", model)
.middleware(spillLargeToolOutput)
.build();
Attach middleware to one request when it is request-specific:
const session = agent.session(threadId, { userId: user.id });
const request = session.prompt(message).withMiddleware(spillLargeToolOutput);
const response = await request.send();
Middleware Points
AgentMiddleware can implement:
onCompletionRequestto replace the provider-neutral completion request before the model callonCompletionResponseto replace the completion response before the runtime continuesonToolInputto replace tool arguments before the tool handler is calledonToolOutputto replace text or structured tool output before it is sent back to the model
Agent-level middleware runs before request-level middleware because the runtime combines agent middleware first, then request middleware.
Tool Input Middleware
Use tool input middleware sparingly. It runs after approvals and before the handler:
const normalizeOrderIds = createMiddleware({
onToolInput({ toolName, args }) {
if (toolName !== "get_order") {
return;
}
const parsed = JSON.parse(args);
return {
args: {
...parsed,
orderId: String(parsed.orderId).trim(),
},
};
},
});
Do not use middleware to bypass schema validation or permission checks. Tool handlers must still validate the operation.
Tool Output Middleware
onToolOutput can return replacement text:
const hideInternalErrors = createMiddleware({
onToolOutput({ result }) {
if (!result.includes("INTERNAL_")) {
return;
}
return "The tool returned an internal service error.";
},
});
Or it can return structured content:
const addToolFooter = createMiddleware({
onToolOutput({ result }) {
return {
structuredResult: [
{ type: "text", text: result },
{ type: "text", text: "Source: internal support tools" },
],
};
},
});
When structured output is returned, core also derives the text result from that structured content for stream events and observers.
Completion Middleware
Completion middleware sees the model request or response for each turn:
const requireSmallResponses = createMiddleware({
onCompletionRequest({ request }) {
return {
request: {
...request,
maxTokens: Math.min(request.maxTokens ?? 600, 600),
},
};
},
});
Use this for cross-cutting request policy. Avoid large hidden prompt rewrites; stable behavior belongs in instructions, tools, or the runner.
Deprecations
Use createMiddleware, .middleware(...), .middlewares(...), .withMiddleware(...), and onToolOutput.
Older names such as createToolMiddleware, .toolMiddleware(...), .toolMiddlewares(...), .withToolMiddleware(...), and onResult are still present for compatibility, but new docs and application code should use the middleware names.
Middleware Checklist
Before adding middleware, check:
- the behavior is truly cross-cutting
- the transformation is deterministic and testable
- tool handlers still enforce permissions
- private data is redacted before browser streams
- the same logic does not belong more clearly in a tool, hook, or runner
