Skip to content

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()).

ExpressNext.jsNode handlerFastify
Import@cognipeer/agent-server@cognipeer/agent-server@cognipeer/agent-server or /node@cognipeer/agent-server/fastify
Token auth, excludeRoutesyesyesyesyes
resolveContextyes (core)yes (core)yes (core)yes (core), mountParams from the prefix
Applies corsno (createCorsMiddleware)the preflight onlyyesyes
SSE, client goes awayresult.close()result.close()result.close(), back-pressurethrough the Node handler
A file as its own bytesyesyesyesyes (buffer parsers, uploadBodyLimit)
Body limitlimits.maxBodySize (raw uploads)limits.maxBodySizemaxBodyBytes / limits.maxBodySizebodyLimit, uploadBodyLimit, maxBodyBytes

Express Adapter ​

createExpressMiddleware ​

Creates Express middleware from an agent server.

typescript
import { createExpressMiddleware } from '@cognipeer/agent-server';

const middleware = createExpressMiddleware(agentServer);
app.use(middleware);

Parameters ​

ParameterTypeDescription
agentServerAgentServerThe 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 ​

typescript
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:

  1. Extracts path, method, query, body from Express request
  2. Extracts auth token from headers
  3. Validates token with auth provider
  4. Calls agentServer.handleRequest()
  5. Translates RouteResult to Express response
  6. Handles SSE streaming for stream responses

Streaming Support ​

For streaming responses, the adapter sets up SSE:

typescript
// 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.

typescript
import { createNextRouteHandlers } from '@cognipeer/agent-server';

export const { GET, POST, PATCH, DELETE, OPTIONS } = createNextRouteHandlers(agentServer);

Parameters ​

ParameterTypeDescription
agentServerAgentServerThe 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 ​

typescript
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 ​

typescript
// 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.ts

This catches all routes under /api/agents/*.

Streaming Support ​

The Next.js adapter uses ReadableStream for SSE:

typescript
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:

typescript
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 ​

typescript
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>
OptionDefault
basePathserver.getBasePath()prefix to strip; "" if a router already stripped it
maxBodyByteslimits.maxBodySize, else about 1.4 x the largest file an agent accepts (server.maxUploadBytes()), plus 64 KiBlarger 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 ​

typescript
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 ​

typescript
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;

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub