Learn/Cookbook/Patterns

SDK Utilities for External Apps

Use storage, logging, error handling, and schema utilities from external backends like Next.js or Express

Use @agentuity/server and @agentuity/core utilities in external apps, scripts, or backends that integrate with Agentuity.

Storage Access

Access Agentuity storage (KV, Vector, Stream) directly from your Next.js or Express backend using your SDK key.

Add these environment variables to your .env:

.env.local
AGENTUITY_SDK_KEY=your_sdk_key    # From Agentuity console or project .env
AGENTUITY_REGION=use              # From agentuity.json (region field)

Finding Your Region

Your region is in your Agentuity project's agentuity.json file. If you forget to set AGENTUITY_REGION, the SDK throws a helpful error telling you what's missing.

lib/agentuity-storage.ts
import {
  KeyValueStorageService,
  VectorStorageService,
  StreamStorageService,
} from '@agentuity/core';
import { createServerFetchAdapter, getServiceUrls, createLogger } from '@agentuity/server';
 
const logger = createLogger('info');
 
const adapter = createServerFetchAdapter({
  headers: {
    Authorization: `Bearer ${process.env.AGENTUITY_SDK_KEY}`,
  },
}, logger);
 
const urls = getServiceUrls(process.env.AGENTUITY_REGION!);
 
export const kv = new KeyValueStorageService(urls.catalyst, adapter);
export const vector = new VectorStorageService(urls.catalyst, adapter);
export const stream = new StreamStorageService(urls.stream, adapter);

Use the client in your Next.js backend:

app/api/sessions/route.ts
import { type NextRequest } from 'next/server';
import { kv } from '@/lib/agentuity-storage';
 
interface ChatSession {
  messages: Array<{ role: string; content: string }>;
  createdAt: string;
}
 
export async function GET(request: NextRequest) {
  const sessionId = request.nextUrl.searchParams.get('id');
 
  if (!sessionId) {
    return Response.json({ error: 'Missing session ID' }, { status: 400 });
  }
 
  const result = await kv.get<ChatSession>('sessions', sessionId);
 
  if (!result.exists) {
    return Response.json({ error: 'Not found' }, { status: 404 });
  }
 
  return Response.json(result.data);
}
 
export async function POST(request: NextRequest) {
  const { sessionId, messages } = await request.json();
 
  await kv.set('sessions', sessionId, {
    messages,
    createdAt: new Date().toISOString(),
  }, { ttl: 86400 });
 
  return Response.json({ success: true });
}

Vector Storage

app/api/search/route.ts
import { type NextRequest } from 'next/server';
import { vector } from '@/lib/agentuity-storage';
 
export async function POST(request: NextRequest) {
  const { query, limit = 10 } = await request.json();
 
  const results = await vector.search('documents', {
    query,
    limit,
  });
 
  return Response.json(results);
}

Stream Storage

Use streams for large data exports or file transfers:

app/api/export/route.ts
import { type NextRequest } from 'next/server';
import { stream } from '@/lib/agentuity-storage';
 
export async function GET(request: NextRequest) {
  const id = request.nextUrl.searchParams.get('id');
 
  if (!id) {
    return Response.json({ error: 'Missing stream ID' }, { status: 400 });
  }
 
  const data = await stream.download(id);
  return new Response(data, {
    headers: { 'Content-Type': 'application/octet-stream' },
  });
}

Alternative: HTTP Routes

If you want to centralize storage logic in your Agentuity project (for middleware, sharing across multiple apps, or avoiding SDK key distribution), use HTTP routes instead.

src/api/sessions/route.ts
import { createRouter } from '@agentuity/runtime';
 
const router = createRouter();
 
interface ChatSession {
  messages: Array<{ role: string; content: string }>;
  createdAt: string;
  updatedAt: string;
}
 
// Get a session by ID
router.get('/:id', async (c) => {
  const sessionId = c.req.param('id');
  const result = await c.var.kv.get<ChatSession>('sessions', sessionId); 
 
  if (!result.exists) {
    return c.json({ error: 'Session not found' }, 404);
  }
 
  return c.json(result.data);
});
 
// Create or update a session
router.post('/:id', async (c) => {
  const sessionId = c.req.param('id');
  const body = await c.req.json<Partial<ChatSession>>();
 
  const existing = await c.var.kv.get<ChatSession>('sessions', sessionId);
  const session: ChatSession = existing.exists
    ? { ...existing.data, ...body, updatedAt: new Date().toISOString() }
    : { messages: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), ...body };
 
  await c.var.kv.set('sessions', sessionId, session, { ttl: 86400 * 7 }); 
 
  c.var.logger.info('Session updated', { sessionId });
  return c.json(session);
});
 
// Delete a session
router.delete('/:id', async (c) => {
  const sessionId = c.req.param('id');
  await c.var.kv.delete('sessions', sessionId); 
 
  c.var.logger.info('Session deleted', { sessionId });
  return c.json({ success: true });
});
 
export default router;

Securing Storage Routes

Add authentication middleware to protect storage endpoints:

src/api/sessions/route.ts
import { createRouter, createMiddleware } from '@agentuity/runtime';
 
const router = createRouter();
 
const requireAuth = createMiddleware(async (c, next) => {
  const apiKey = c.req.header('x-api-key');
 
  if (!apiKey || apiKey !== process.env.STORAGE_API_KEY) {
    c.var.logger.warn('Unauthorized storage access attempt');
    return c.json({ error: 'Unauthorized' }, 401);
  }
 
  await next();
});
 
router.use('/*', requireAuth); 
 
// Routes now require x-api-key header
router.get('/:id', async (c) => {
  // ... same as above
});
 
export default router;

Routes also have access to c.var.vector and c.var.stream for Vector and Stream storage.

Logging with createLogger

Create structured loggers for scripts or external services:

import { createLogger } from '@agentuity/server';
 
// Create a logger with info level, no timestamps, dark color scheme
const logger = createLogger('info', false, 'dark');
 
logger.info('Processing request', { userId: '123' });
logger.error('Failed to connect', { error: err.message });
 
// Child logger with persistent context
const requestLogger = logger.child({ requestId: 'req_abc' });
requestLogger.info('Starting'); // Includes requestId in all logs

Parameters:

ParameterTypeDefaultDescription
level'trace' | 'debug' | 'info' | 'warn' | 'error''info'Minimum log level
showTimestampbooleanfalseInclude ISO timestamps
colorScheme'light' | 'dark''dark'Terminal color scheme
contextRecord<string, unknown>{}Default context for all logs

Safe JSON Serialization

safeStringify handles circular references and BigInt values that break JSON.stringify:

import { safeStringify } from '@agentuity/core';
 
// Circular references become "[Circular]"
const obj = { name: 'Alice' };
obj.self = obj;
safeStringify(obj); // '{"name":"Alice","self":"[Circular]"}'
 
// BigInt converts to string
safeStringify({ id: 9007199254740991n }); // '{"id":"9007199254740991"}'
 
// Pretty-print with indentation
safeStringify(data, 2);

Error Handling Utilities

RichError

Enhanced errors with context and pretty printing:

import { RichError } from '@agentuity/core';
 
const error = new RichError({
  message: 'Request failed',
  statusCode: 500,
  endpoint: '/api/users',
  cause: originalError,
});
 
error.plainArgs;     // { statusCode: 500, endpoint: '/api/users' }
error.cause;         // originalError (the cause chain)
error.prettyPrint(); // Formatted multi-line output with stack traces
error.toJSON();      // Serializable object for logging
error.toString();    // Same as prettyPrint()

StructuredError

Type-safe, discriminated errors with a _tag for pattern matching:

import { StructuredError, isStructuredError } from '@agentuity/core';
 
// Create error types
const NotFoundError = StructuredError('NotFound');
const ValidationError = StructuredError('ValidationError')<{
  field: string;
  code: string;
}>();
 
// With a default message (message is preset, cannot be overridden)
const UpgradeRequired = StructuredError('UpgradeRequired', 'Upgrade required to access this feature');
 
// Throw with context
throw new ValidationError({
  field: 'email',
  code: 'INVALID_FORMAT',
  message: 'Email format is invalid',
});
 
// Type-safe handling
if (isStructuredError(err) && err._tag === 'ValidationError') {
  logger.error('Validation failed', { field: err.field, code: err.code });
}

Schema Conversion

Convert Zod or Agentuity schemas to JSON Schema for tooling or OpenAPI generation:

import { toJSONSchema } from '@agentuity/server';
import { z } from 'zod';
 
const schema = z.object({ name: z.string(), age: z.number() });
const jsonSchema = toJSONSchema(schema);
// { type: 'object', properties: { name: { type: 'string' }, age: { type: 'number' } }, ... }

Key Points

  • Direct SDK is the simplest way to access storage from external backends
  • Required env vars: AGENTUITY_SDK_KEY and AGENTUITY_REGION (from agentuity.json)
  • HTTP routes are an alternative for centralized logic, middleware, or sharing across apps
  • Works with any framework: Next.js, Express, Remix, etc.

See Also

Need Help?

Join our DiscordCommunity for assistance or just to hang with other humans building agents.

Send us an email at hi@agentuity.com if you'd like to get in touch.

Please Follow us on

If you haven't already, please Signup for your free account now and start building your first agent!