Adapters
Framework adapters for integrating the agent server. All of them translate an HTTP request into server.handleRequest / handleRoute and the RouteResult back into a response; what each one adds around that differs:
New in 0.2.0 and 0.3.0
The Node handler and the Fastify adapter arrived in 0.2.0. 0.3.0 adds raw-byte uploads (isRawUploadRequest, uploadBodyLimit), RouteResult.close so a stream ends as soon as the client leaves, NodeHandlerOverrides.error, and body limits that follow the largest file an agent accepts (server.maxUploadBytes()).
| Express | Next.js | Node handler | Fastify | |
|---|---|---|---|---|
| Import | @cognipeer/agent-server | @cognipeer/agent-server | @cognipeer/agent-server or /node | @cognipeer/agent-server/fastify |
Token auth, excludeRoutes | yes | yes | yes | yes |
resolveContext | yes (core) | yes (core) | yes (core) | yes (core), mountParams from the prefix |
Applies cors | no (createCorsMiddleware) | the preflight only | yes | yes |
| SSE, client goes away | result.close() | result.close() | result.close(), back-pressure | through the Node handler |
| A file as its own bytes | yes | yes | yes | yes (buffer parsers, uploadBodyLimit) |
| Body limit | limits.maxBodySize (raw uploads) | limits.maxBodySize | maxBodyBytes / limits.maxBodySize | bodyLimit, uploadBodyLimit, maxBodyBytes |
Express Adapter
createExpressMiddleware
Creates Express middleware from an agent server.
import { createExpressMiddleware } from '@cognipeer/agent-server';
const middleware = createExpressMiddleware(agentServer);
app.use(middleware);Parameters
| Parameter | Type | Description |
|---|---|---|
| agentServer | AgentServer | The agent server instance |
Options
createExpressMiddleware(agentServer, options?):
| Option | |
|---|---|
errorHandler(error, req, res) | Takes over the answer to an error. Without it the middleware answers { error: { code, message } } with the error's statusCode (500 otherwise) |
createExpressRouter(server, options?) is the same middleware; mountOnExpress(app, server, options?) mounts it at the server's basePath; createCorsMiddleware(origins?) answers the preflight and sets the CORS headers (the server's cors setting is not read by the Express adapter).
Returns
RequestHandler - Express middleware function. It calls next() for a request whose URL does not start with basePath.
Example
import express from 'express';
import { createAgentServer, createExpressMiddleware } from '@cognipeer/agent-server';
const agentServer = createAgentServer({
basePath: '/api/agents',
storage,
});
const app = express();
app.use(express.json());
app.use(createExpressMiddleware(agentServer));
app.listen(3000);How It Works
The Express adapter:
- Extracts path, method, query, body from Express request
- Extracts auth token from headers
- Validates token with auth provider
- Calls
agentServer.handleRequest() - Translates
RouteResultto Express response - Handles SSE streaming for stream responses
Streaming Support
For streaming responses, the adapter sets up SSE:
// The adapter handles this automatically
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
for await (const chunk of result.stream) {
res.write(chunk);
}
res.end();Next.js Adapter
createNextRouteHandlers
Creates Next.js App Router handlers.
import { createNextRouteHandlers } from '@cognipeer/agent-server';
export const { GET, POST, PATCH, DELETE, OPTIONS } = createNextRouteHandlers(agentServer);Parameters
| Parameter | Type | Description |
|---|---|---|
| agentServer | AgentServer | The agent server instance |
Options
createNextRouteHandlers(server, options?) and createNextHandler(server, options?) take errorHandler(error, request), which returns the Response for an error. createNextMiddleware(server) is a helper for a Next.js middleware file: it answers the CORS preflight and refuses a request without a token when token auth is on (it checks that the header is present, the route handler validates it).
The OPTIONS handler answers a preflight from the server's cors setting; the other methods add no CORS headers.
Returns
interface NextRouteHandlers {
GET: (request: Request, context: { params: any }) => Promise<Response>;
POST: (request: Request, context: { params: any }) => Promise<Response>;
PATCH: (request: Request, context: { params: any }) => Promise<Response>;
DELETE: (request: Request, context: { params: any }) => Promise<Response>;
OPTIONS: (request: Request, context: { params: any }) => Promise<Response>;
}Example
// app/api/agents/[...path]/route.ts
import { createAgentServer, createNextRouteHandlers } from '@cognipeer/agent-server';
const agentServer = createAgentServer({
basePath: '/api/agents',
storage,
});
await storage.connect();
export const { GET, POST, PATCH, DELETE, OPTIONS } = createNextRouteHandlers(agentServer);Route Pattern
Use a catch-all route pattern:
app/api/agents/[...path]/route.tsThis catches all routes under /api/agents/*.
Streaming Support
The Next.js adapter uses ReadableStream for SSE:
return new Response(
new ReadableStream({
async start(controller) {
for await (const chunk of result.stream) {
controller.enqueue(new TextEncoder().encode(chunk));
}
controller.close();
},
}),
{
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
},
}
);Custom Adapter
Create your own adapter for other frameworks:
import type { AgentServer } from '@cognipeer/agent-server';
function createCustomAdapter(agentServer: AgentServer) {
return async function handleRequest(req: CustomRequest, res: CustomResponse) {
// 1. Extract request info
const method = req.method;
const path = req.path; // handleRequest strips the base path itself
// 2. Authenticate (a no-op returning null when token auth is off)
let user;
try {
user = (await agentServer.authenticate(extractToken(req.headers))) ?? undefined;
} catch (error) {
const { statusCode = 401, code = 'AUTHENTICATION_ERROR', message } = error as { statusCode?: number; code?: string; message?: string };
return res.status(statusCode).json({ error: { code, message } });
}
// 3. Handle the request
const result = await agentServer.handleRequest(method, path, {
query: req.query,
body: req.body, // a file uploaded as itself: pass its bytes as a Buffer
headers: req.headers,
user,
});
// 4. Send the response
if (result.stream) {
// Server-Sent Events: stop reading when the client goes away (the run it was watching goes on)
res.setHeader('Content-Type', 'text/event-stream');
res.on('close', () => result.close?.());
for await (const chunk of result.stream) {
res.write(chunk);
}
res.end();
} else if (result.raw) {
// Binary response
res.setHeader('Content-Type', result.headers?.['Content-Type'] || 'application/octet-stream');
res.send(result.raw);
} else {
// JSON response
res.status(result.status).json(result.body);
}
};
}
function extractToken(headers: Record<string, string>): string | undefined {
const auth = headers.authorization || headers.Authorization;
if (auth?.startsWith('Bearer ')) {
return auth.slice(7);
}
return undefined;
}File uploads. POST /files takes the file either as JSON (a parsed object body) or as itself: its bytes as the body, its own Content-Type and ?name=&conversationId= in the query. For the second form pass the bytes - a Buffer - as body; isRawUploadRequest(method, routePath, contentType) (exported) tells which form a request is (true: a POST to /files whose Content-Type is not JSON). Limit the body yourself: server.maxUploadBytes() is the largest file any agent accepts. See Files.
Node HTTP Adapter
import { createNodeHandler } from '@cognipeer/agent-server'; // or '@cognipeer/agent-server/node'
const handle = createNodeHandler(server, { basePath: '/api', maxBodyBytes: 15_000_000 });
// (req: IncomingMessage, res: ServerResponse, overrides?: { path, body, mountParams, raw, error }) => Promise<boolean>| Option | Default | |
|---|---|---|
basePath | server.getBasePath() | prefix to strip; "" if a router already stripped it |
maxBodyBytes | limits.maxBodySize, else about 1.4 x the largest file an agent accepts (server.maxUploadBytes()), plus 64 KiB | larger bodies answer 413 |
The handler resolves true when it handled the request and false when the path is outside basePath (nothing is written). It applies cors (an exact Origin match or *; the preflight is answered with 204), token auth, resolveContext, JSON parsing (400 on invalid JSON), raw uploads, and SSE with back-pressure.
See the Fastify & Node HTTP guide.
Fastify Adapter
import { mountOnFastify, createFastifyPlugin } from '@cognipeer/agent-server/fastify';
mountOnFastify(app, server, { prefix: '/api/portal/:portalKey', bodyLimit });
app.register(createFastifyPlugin(server, { maxBodyBytes }), { prefix: '/api' });Hono Example
import { Hono } from 'hono';
import { createAgentServer } from '@cognipeer/agent-server';
const app = new Hono();
const agentServer = createAgentServer({ basePath: '/api', storage });
app.all('/api/*', async (c) => {
const path = c.req.path.replace('/api', '');
const context = {
params: {},
query: Object.fromEntries(new URL(c.req.url).searchParams),
body: await c.req.json().catch(() => undefined),
};
const result = await agentServer.handleRequest(
c.req.method,
path,
context
);
if (result.stream) {
return new Response(
new ReadableStream({
async start(controller) {
for await (const chunk of result.stream) {
controller.enqueue(new TextEncoder().encode(chunk));
}
controller.close();
},
}),
{ headers: { 'Content-Type': 'text/event-stream' } }
);
}
return c.json(result.body, result.status);
});
export default app;
