Codapult provides a shared AI application layer built on the Vercel AI SDK. Chat, agents, batch jobs, playground experiments, tools, guardrails, metering, model routing, and retrieval use the same gateway instead of implementing provider calls independently.
Guides
- Streaming Chat — chat UI, model adapters, streaming route, and tool calls.
- RAG and Semantic Search — indexing, embeddings, vector store, and retrieval.
- Quotas and Memory — conversation persistence, monthly credits, and rate limits.
- AI Agents and IDEs — tools, agents, and the gateway contract for AI-powered development.
Architecture
src/lib/ai/
├── models.ts # Client-safe model options (id, label, provider)
├── providers.ts # getModel() — resolves modelId → LanguageModel
├── embeddings/ # Embedding adapter (OpenAI / Ollama)
├── vector-store/ # Vector store adapter (SQLite / memory)
├── rag.ts # RAG pipeline (index → chunk → embed → store → retrieve)
├── conversations.ts # Conversation/message CRUD
├── gateway/ # Model routing, retries, shared pipeline, and providers
├── prompts/ # Versioned prompts and A/B testing
├── tools/ # Tool definitions, built-ins, and agents
├── guardrails/ # Input/output policy evaluation
├── metering/ # Token usage, cost, and budget analytics
└── batch/ # Asynchronous batch processing
Chat Endpoint
POST /api/ai/chat accepts a JSON body with a messages array and an optional model selector:
{
"messages": [{ "role": "user", "content": "How do I deploy?" }],
"modelId": "gpt-5-mini"
}
The endpoint follows the standard API route pattern: auth check → rate limiting (30 requests per 60 seconds per user) → org quota check → Zod validation → RAG context injection → streaming response.
Available Models
| Model ID | Label | Provider |
|---|---|---|
gpt-5-mini | GPT-5 Mini | OpenAI |
gpt-5.4-mini | GPT-5.4 Mini | OpenAI |
claude-sonnet-5 | Claude Sonnet 5 | Anthropic |
claude-haiku-4-5 | Claude Haiku 4.5 | Anthropic |
gemini-3.7-flash | Gemini 3.7 Flash | |
gemini-3.6-flash | Gemini 3.6 Flash | |
openai/gpt-oss-120b | GPT-OSS 120B (Groq) | Groq |
Models are defined in src/lib/ai/models.ts. To add a model, add its complete definition there, including provider, SDK model ID, capabilities, and pricing. Unknown model IDs are rejected; provider detection by naming convention is not used.
Custom OpenAI-compatible endpoint
For a self-hosted or third-party OpenAI-compatible endpoint, keep the model definition in src/lib/ai/models.ts with provider: 'custom'. Configure only the endpoint and secret through environment variables:
CUSTOM_AI_BASE_URL="https://llm.example.com/v1"
CUSTOM_AI_API_KEY="..."
AI_DEFAULT_MODEL="your-model-id"
CUSTOM_AI_BASE_URL and CUSTOM_AI_API_KEY do not register arbitrary models. The selected model ID must still exist in models.ts and, when configured, in AI_ALLOWED_MODELS.
Configuration
Runtime AI settings live in environment variables and are exposed server-side through env.ai:
AI_DEFAULT_MODEL = 'gpt-5-mini';
AI_ALLOWED_MODELS = 'gpt-5-mini,gpt-5.4-mini,claude-sonnet-5,gemini-3.7-flash';
AI_MAX_RETRIES = '2';
AI_DEFAULT_TEMPERATURE = '0.7';
AI_DEFAULT_MAX_TOKENS = '4096';
AI_DEFAULT_TOP_P = '1';
ENABLE_AI_RAG = 'false';
AI_RAG_MAX_CHUNKS_PER_QUERY = '5';
AI_RAG_MIN_SCORE = '0.4';
| Setting | Description |
|---|---|
AI_DEFAULT_MODEL | Model used when the user doesn't pick one (must match an ID in models.ts) |
AI_ALLOWED_MODELS | Comma-separated model allow-list; empty enables all catalog models |
AI_DEFAULT_TEMPERATURE | Default sampling temperature for providers that support sampling |
AI_DEFAULT_MAX_TOKENS | Default output token limit |
AI_DEFAULT_TOP_P | Default nucleus sampling value |
ENABLE_AI_RAG | Enables retrieval and indexing; defaults to false |
AI_RAG_MAX_CHUNKS_PER_QUERY | Maximum context chunks per query |
AI_RAG_MIN_SCORE | Minimum cosine similarity score (0–1) |
The system instruction is a source-controlled constant in src/lib/ai/system-prompt.ts. Keep product behavior there; secrets and deployment controls belong in the environment.
Tool Use
Chat supports function calling via the Vercel AI SDK. Tools are defined in /api/ai/chat/route.ts. The example below is illustrative — replace it with your own domain-specific tools:
import { z } from 'zod';
import type { Tool } from 'ai';
const chatTools: Record<string, Tool> = {
lookupOrder: {
description: 'Look up an order by ID',
parameters: z.object({ orderId: z.string() }),
execute: async ({ orderId }) => {
// Replace with your own business logic
const order = await db.select().from(orders).where(eq(orders.id, orderId)).limit(1);
return order[0] ?? { error: 'Not found' };
},
},
};
Multi-step tool invocations are enabled with maxSteps: 3. To add a new tool, define it in chatTools with a Zod parameters schema and an execute function.
Organization Quotas
AI usage is tracked per organization. Each plan defines a monthly credit allowance for the aiChat resource. The quota is checked before every chat request via checkOrgQuota(). Credits reset monthly via a background cron job.
Chat Memory
Conversation history is persisted in the database via src/lib/ai/conversations.ts:
| Endpoint | Method | Description |
|---|---|---|
/api/ai/chat/conversations | GET | List user conversations |
/api/ai/chat/conversations | POST | Create a new conversation |
/api/ai/chat/conversations | DELETE | Delete a conversation |
/api/ai/chat/conversations/[id]/messages | GET | Read messages |
The only connected Chat UI component (src/components/ai/ChatUI) connects to these endpoints and renders the production chat interface with model selection, conversation switching, streaming responses, and tool-call progress/results. Use ChatUI directly or compose its supporting AI components for a specialized surface. Optional agent, debug, and message-cost props are integration points for callers that already have that metadata—they are not populated automatically by the basic chat route.
RAG Pipeline
The RAG (Retrieval-Augmented Generation) pipeline lets the AI chat reference your domain-specific content — blog posts, help docs, feature requests, or any custom text.
How It Works
- Index — content is chunked (800 chars, 150 overlap), embedded, and stored in the vector store
- Retrieve — user queries are embedded and matched against stored vectors by cosine similarity
- Augment — matching chunks are injected into the system prompt with source citations
Indexing Content
Use the indexDocument function or the admin API:
import { indexDocument } from '@/lib/ai/rag';
await indexDocument({
sourceType: 'help',
sourceId: 'getting-started',
title: 'Getting Started Guide',
content: markdownContent,
});
For large content, use the rag-index background job:
import { enqueue } from '@/lib/jobs';
await enqueue('rag-index', {
sourceType: 'blog',
sourceId: 'post-123',
title: 'My Blog Post',
content: markdownContent,
});
Admin Indexing API
POST /api/ai/index is admin-only — requires role: "admin" session. Use it to manage the knowledge base from the admin panel or via scripts. It supports three actions:
| Action | Description |
|---|---|
index | Index a document (sourceType, sourceId, title, content) |
search | Search the vector store (query, optional sourceTypes, limit, minScore) |
delete | Delete indexed content (sourceType, optional sourceId) |
Embedding Providers
Embeddings use the adapter pattern, switched via the EMBEDDING_PROVIDER env var:
| Provider | Env Value | Requirements |
|---|---|---|
| OpenAI | openai (default) | OPENAI_API_KEY |
| Ollama | ollama | OLLAMA_BASE_URL, OLLAMA_EMBEDDING_MODEL |
Ollama enables fully self-hosted embeddings — no external API calls. The default Ollama model is nomic-embed-text.
Vector Store
Vector storage uses the adapter pattern, switched via VECTOR_STORE_PROVIDER (accessed as env.ai.vectorStoreProvider in server code):
| Store | Env Value | Description |
|---|---|---|
| SQLite | sqlite (default) | Persisted in Turso alongside app data |
| Memory | memory | In-memory store for development/testing |
Source Types
Indexed content is categorized by source type:
| Type | Description |
|---|---|
blog | Blog posts |
help | Help center / documentation articles |
feature_request | Feature request descriptions |
custom | Any custom content |
Environment Variables
| Variable | Default | Description |
|---|---|---|
OPENAI_API_KEY | — | Required for OpenAI models and default embeddings |
ANTHROPIC_API_KEY | — | Required for Anthropic models |
EMBEDDING_PROVIDER | openai | Embedding backend (openai or ollama) |
VECTOR_STORE_PROVIDER | sqlite | Vector storage backend (sqlite or memory) |
OLLAMA_BASE_URL | http://localhost:11434 | Ollama server URL |
OLLAMA_EMBEDDING_MODEL | nomic-embed-text | Ollama model name for embeddings |
Removing the Module
The AI core is a removable module that contains the shared gateway, chat, agents, batch processing, playground, tools, guardrails, metering, and RAG. Chat and RAG can be disabled independently with ENABLE_AI_CHAT="false" and ENABLE_AI_RAG="false", but both require ENABLE_AI_CORE="true". RAG is opt-in because indexing incurs embedding cost. Use the setup wizard (npx @codapult/cli setup) to remove the AI bundle, or use the feature flags for reversible runtime changes. See the Modules documentation for manual removal steps.