SDK Utilities for External Apps — Agentuity Documentation

SDK Utilities for External Apps

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

Use Agentuity services from external apps, scripts, or backends. Standalone packages are the shortest path, while @agentuity/server gives you lower-level control over adapters and service URLs.

Standalone Packages

The fastest way to access Agentuity services from external apps. Each package reads AGENTUITY_SDK_KEY, AGENTUITY_REGION, and service URL overrides from the environment.

typescriptlib/agentuity.ts
import { KeyValueClient } from '@agentuity/keyvalue';
import { VectorClient } from '@agentuity/vector';
import { SandboxClient } from '@agentuity/sandbox';
 
export const kv = new KeyValueClient();
export const vector = new VectorClient();
export const sandbox = new SandboxClient();
typescriptapp/api/sessions/route.ts
import { kv } from '../../lib/agentuity';
 
export async function GET(request: Request) {
  const userId = new URL(request.url).searchParams.get('userId');
  if (!userId) {
    return Response.json({ error: 'Missing userId' }, { status: 400 });
  }
 
  const result = await kv.get<{ messages: string[] }>('sessions', userId);
 
  if (result.exists) {
    return Response.json(result.data);
  }
  return Response.json({ messages: [] });
}

See Standalone Packages for the full list of available packages and their APIs.

Manual Configuration

For custom adapter configuration, direct service URL overrides, or when you need lower-level control, use @agentuity/server directly.

Add these environment variables to your .env:

bash.env.local
AGENTUITY_SDK_KEY=your_sdk_key    # From Agentuity console or project .env
AGENTUITY_REGION=use              # From agentuity.json (region field)
typescriptlib/agentuity-storage.ts
import {
  KeyValueStorageService,
  VectorStorageService,
  StreamStorageService,
  buildClientHeaders,
  createLogger,
  createServerFetchAdapter,
  getServiceUrls,
} from '@agentuity/server';
 
const logger = createLogger('info');
const sdkKey = process.env.AGENTUITY_SDK_KEY;
const region = process.env.AGENTUITY_REGION;
 
if (!sdkKey) {
  throw new Error('AGENTUITY_SDK_KEY environment variable is required');
}
 
if (!region) {
  throw new Error('AGENTUITY_REGION environment variable is required');
}
 
const adapter = createServerFetchAdapter({
  headers: buildClientHeaders({ apiKey: sdkKey }),
}, logger);
 
const urls = getServiceUrls(region);
 
export const kv = new KeyValueStorageService(urls.keyvalue, adapter);
export const vector = new VectorStorageService(urls.vector, adapter);
export const stream = new StreamStorageService(urls.stream, adapter);

Use the client in your Next.js backend:

typescriptapp/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

typescriptapp/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:

typescriptapp/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' },
  });
}

Queue Management

Manage queues programmatically from external apps or scripts using APIClient:

typescriptlib/agentuity-queues.ts
import { APIClient, createLogger, getServiceUrls } from '@agentuity/server';
 
const region = process.env.AGENTUITY_REGION;
const sdkKey = process.env.AGENTUITY_SDK_KEY;
 
if (!region) {
  throw new Error('AGENTUITY_REGION environment variable is required');
}
 
if (!sdkKey) {
  throw new Error('AGENTUITY_SDK_KEY environment variable is required');
}
 
export const logger = createLogger('info');
const urls = getServiceUrls(region);
 
export const client = new APIClient(
  urls.catalyst,
  logger,
  sdkKey
);

Creating and Managing Queues

import {
  createQueue,
  listQueues,
  deleteQueue,
  pauseQueue,
  resumeQueue,
} from '@agentuity/server';
import { client } from '@/lib/agentuity-queues';
 
// Create a worker queue
const queue = await createQueue(client, {
  name: 'order-processing',
  queue_type: 'worker',
  settings: {
    default_max_retries: 5,
    default_visibility_timeout_seconds: 60,
  },
});
 
// List all queues
const { queues } = await listQueues(client);
 
// Pause and resume
await pauseQueue(client, 'order-processing');
await resumeQueue(client, 'order-processing');
 
// Delete a queue
await deleteQueue(client, 'old-queue');

Dead Letter Queue Operations

import {
  listDeadLetterMessages,
  replayDeadLetterMessage,
  purgeDeadLetter,
} from '@agentuity/server';
import { client, logger } from '@/lib/agentuity-queues';
 
// List failed messages
const { messages } = await listDeadLetterMessages(client, 'order-processing');
 
for (const msg of messages) {
  logger.warn('Failed message', { id: msg.id, reason: msg.failure_reason });
 
  // Replay back to the queue
  await replayDeadLetterMessage(client, 'order-processing', msg.id);
}
 
// Purge all DLQ messages
await purgeDeadLetter(client, 'order-processing');

Webhook Destinations

import { createDestination } from '@agentuity/server';
import { client } from '@/lib/agentuity-queues';
 
const webhookApiKey = process.env.WEBHOOK_API_KEY;
 
if (!webhookApiKey) {
  throw new Error('WEBHOOK_API_KEY environment variable is required');
}
 
await createDestination(client, 'order-processing', {
  name: 'orders-webhook',
  destination_type: 'http',
  enabled: true,
  config: {
    url: 'https://api.example.com/webhook/orders',
    method: 'POST',
    headers: { 'X-API-Key': webhookApiKey },
    timeout_ms: 30000,
    retry_policy: {
      max_attempts: 5,
      initial_backoff_ms: 1000,
      max_backoff_ms: 60000,
      backoff_multiplier: 2.0,
    },
  },
});

HTTP Ingestion Sources

import { createSource } from '@agentuity/server';
import { client, logger } from '@/lib/agentuity-queues';
 
const source = await createSource(client, 'webhook-queue', {
  name: 'stripe-webhooks',
  description: 'Receives Stripe payment events',
  auth_type: 'header',
  auth_value: `Bearer ${process.env.STRIPE_WEBHOOK_SECRET}`,
});
 
// External services POST to this URL
logger.info('Source created', { url: source.url });

Pull-Based Consumption

For workers that pull and acknowledge messages:

import { receiveMessage, ackMessage, nackMessage } from '@agentuity/server';
import { client } from '@/lib/agentuity-queues';
 
// Receive a message (blocks until available or timeout)
const message = await receiveMessage(client, 'order-processing');
 
if (message) {
  try {
    await processOrder(message.payload);
    await ackMessage(client, 'order-processing', message.id);
  } catch (error) {
    // Message returns to queue for retry
    await nackMessage(client, 'order-processing', message.id);
  }
}

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.

typescriptsrc/api/sessions/route.ts
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
import { s } from '@agentuity/schema';
 
const router = new Hono<Env>();
 
const MessageSchema = s.object({
  role: s.string(),
  content: s.string(),
});
 
const SessionPatchSchema = s.object({
  messages: s.optional(s.array(MessageSchema)),
});
 
type Message = s.infer<typeof MessageSchema>;
type SessionPatch = s.infer<typeof SessionPatchSchema>;
 
interface ChatSession {
  messages: Message[];
  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 payload = await c.req.json();
  const parsed = SessionPatchSchema.safeParse(payload);
  if (!parsed.success) {
    return c.json({ error: 'Invalid session payload' }, 400);
  }
  const body: SessionPatch = parsed.data;
 
  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:

typescriptsrc/api/sessions/route.ts
import { Hono } from 'hono';
import type { Env } from '@agentuity/runtime';
import { createMiddleware } from 'hono/factory';
 
const router = new Hono<Env>();
 
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 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.child({ userId: '123' }).info('Processing request');
logger.child({ error: 'Connection refused' }).error('Failed to connect');
 
// 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';
 
const obj: Record<string, unknown> = { name: 'Alice' };
obj.self = obj;
safeStringify(obj); // '{"name":"Alice","self":"[Circular]"}'
 
safeStringify({ id: 9007199254740991n }); // '{"id":"9007199254740991"}'
 
const data = { user: obj, balance: 9007199254740991n };
safeStringify(data, 2);

Error Handling Utilities

RichError

Enhanced errors with context and pretty printing:

import { RichError } from '@agentuity/core';
 
const originalError = new Error('Upstream request timed out');
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';
import { createLogger } from '@agentuity/server';
 
const logger = createLogger('error');
 
// 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');
 
try {
  throw new ValidationError({
    field: 'email',
    code: 'INVALID_FORMAT',
    message: 'Email format is invalid',
  });
} catch (err) {
  if (isStructuredError(err) && err._tag === 'ValidationError') {
    logger.error('Validation failed', { field: err.field, code: err.code });
  }
}

Schema Conversion

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

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

Key Points

  • Standalone packages are the simplest way to access services from external backends
  • Manual configuration with @agentuity/server supports custom adapters and service URL overrides
  • 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