Files
veridian/AGENTS.md
T

8.3 KiB

Veridian AGENTS.md

Nuxt 4 AI chat platform with Drizzle + PostgreSQL + better-auth

Architecture Overview

app/                    # Nuxt 4 application (SSR)
├── components/         # Vue components (Sidenav, Message, Settings, etc.)
├── composables/        # Shared state (useChat, useAuth, useModels, etc.)
├── layouts/            # Auth and default layouts
├── middleware/         # Global auth middleware
├── pages/              # File-based routing (/, /auth/*, /agent/*)
├── plugins/            # Auth plugins (client/server), remark markdown
├── types/              # TypeScript interfaces
└── utils/              # Crypto, model-mapping, search utilities

server/                 # Server-side API routes
└── api/
    ├── auth/           # Better-auth handler
    ├── chat/           # Generation, cancel endpoints
    └── provider/       # Model fetching from providers

drizzle/               # Database (migrated from Triplit)
├── schema.ts           # Full schema with auth + app tables
├── relations.ts        # Table relationships
└── migrations/         # SQL migration files

lib/                    # Shared utilities
├── auth.ts             # Better-auth server config
└── auth-client.ts      # Better-auth client

Core Commands

As an agentic agent, never run the development server yourself. Instead, once you have completed your task, end generation and inform me that you are done and request a review of your changes. You are allowed to use database commands.

Database Commands

  • bunx drizzle-kit push - Push schema changes to database
  • bunx drizzle-kit generate - Generate migration from schema changes
  • bunx drizzle-kit migrate - Run pending migrations

Code Style

  • Indentation: 4 spaces (no tabs)
  • Semicolons: Always required
  • Equality: Strict equality only (===, !==)
  • Components: PascalCase, never self-closing tags
  • Functions: camelCase for functions, PascalCase for constructors/classes
  • Constants: SCREAMING_SNAKE_CASE for constants, camelCase for const values
  • Files: kebab-case for files, PascalCase for Vue components
  • State: useState('prefix:name', () => default) for reactive state

Imports Order

Group imports in this order (alphabetical within groups):

  1. Type imports (types/)
  2. Dependency imports (npm packages)
  3. Vue/Nuxt imports (vue, #app, ~~/, ~/, @/)
  4. Local imports (lib/, server/)
// Example import order
import type { Result, Ok, Err } from '~~/types/result';
import type { Foo } from 'vue';
import { ref, computed } from 'vue';
import { useFeature } from '~/composables/useFeature';
import { decrypt } from '~/utils/crypto';
import { authClient } from '~~/lib/auth-client';

Error Handling

Avoid using throw statements in your code.

Use the Result<T, E> type with Ok() and Err() factory functions:

import { type Result, Ok, Err } from '~~/types/result';

const myFunction(): Result<ReturnType, ErrorType> {
    if (error) {
        return Err(ErrorType.SpecificError);
    }
    return Ok(data);
}

// Usage
const result = myFunction();
if (result.ok === false) {
    console.error(result.error);
    return;
}
processData(result.data);

Composables Pattern

All composables return reactive state + actions:

export const useFeature = () => {
    const state = useState('feature:state', () => defaultValue);
    const computedValue = computed(() => /* logic */);
    const actions = {
        async doSomething() {
            // implementation
        },
    };
    return { state, computedValue, ...actions };
};

API Route Pattern

// File: server/api/endpoint.method.ts
import { protectRoute } from '~/server/utils/protect';

export default defineEventHandler(async (event) => {
    await protectRoute(event);
    // implementation
    return { /* response */ };
});

Database Query Pattern (Drizzle)

// Server-side queries use Drizzle ORM
import { db } from '~~/server/db';
import { agents } from '~~/drizzle/schema';

// Query with relations
const result = await db.query.agents.findMany({
    where: eq(agents.userId, userId),
    with: { topics: true, messages: true },
});

// Insert with conflict handling
await db.insert(agents).values(data).onConflictDoNothing();

Authentication Flow

  1. Login: Client uses authClient.signIn.email() → derives encryption key from password
  2. Session: Better-auth creates session token → stored in cookie
  3. API Keys: Encrypted client-side with AES-GCM (key derived from password + userId)

Key files: lib/auth.ts, lib/auth-client.ts, app/plugins/auth.*.ts, app/middleware/auth.global.ts

Styling System

UnoCSS with semantic color variables in app/assets/css/base.css:

  • --color-accent, --color-neutral, --color-text, --color-muted
  • --color-highlight (for borders/hover states)
  • Theme-aware: :root.dark / :root.light with color-mix()

Environment Variables

Variable Purpose
DATABASE_URL PostgreSQL connection string
BETTER_AUTH_SECRET Auth encryption

Key Composables

Composable Purpose
useAuth() Session/user state, signIn/signOut
useChat(id) Send/regenerate messages, create topics
useModels() Providers + models with auto-subscription
useAgents() User's agents with topics
useSidebar() Sidebar state + resize
useTheme() Accent/neutral theme cookies
useSettings() Settings dialog state

Key Routes

Route File
/ app/pages/index.vue
/auth/login app/pages/auth/login.vue
/auth/register app/pages/auth/register.vue
/agent/:id app/pages/agent/[id]/index.vue
/agent/:id/topic/:topicId app/pages/agent/[id]/topic/[topicId].vue
/agent/:id/profile app/pages/agent/[id]/profile.vue

Server API Routes

Endpoint File
POST /api/chat/generate server/api/chat/generate.post.ts
POST /api/chat/cancel/:id server/api/chat/cancel/[generationId].post.ts
POST /api/provider/:id/models server/api/provider/[providerId]/models.post.ts
* /api/auth/* server/api/auth/[...all].ts

Known Issues (from BUGS.md)

  1. Sidebar hover animation occasionally glitches on agent routes
  2. Theme switcher + sidebar interaction bug

Critical Architecture Notes

Chat Endpoint God File

  • server/api/topic/[topicId]/chat/index.post.ts is 1263 lines handling route validation, tool definitions, streaming orchestration, DB transactions, and event emission
  • Files that change together: this file + server/utils/events.ts + server/utils/generations.ts
  • Tool definitions (bash, python, file access) should be extracted to separate module before extending

Security: Code Execution Tools Have No Sandboxing

  • bashTool uses exec() and pythonTool uses python3 -c with user-controlled input — full RCE risk
  • Do NOT extend these tools without adding: timeouts, path restrictions, resource limits
  • Location: server/api/topic/[topicId]/chat/index.post.ts ~lines 261-455

Crash Risk: todo() Function

  • todo() literally throws new Error('TODO') on unhandled token types in chat handler
  • Handle gracefully before production deployment
  • Location: server/api/topic/[topicId]/chat/index.post.ts ~line 256

Module-Scoped Mutable State Limitations

  • useDialog.ts has module-scoped actionCallback — not SSR-safe, overwritten on concurrent dialogs
  • pendingGenerations in server/utils/generations.ts and in-memory event channels in server/utils/events.ts won't work in multi-process deployments
  • Fine for single-instance dev, but architectural ceiling for scaling

Pagination Params Validated But Unused

  • agents.get.ts validates page/limit query params but never applies them to the query
  • All agent queries return everything regardless of pagination params

Development Notes

  • Never run dev server as agent - just complete tasks and signal done
  • Always run bunx drizzle-kit push after schema changes
  • Use protectRoute() in all API routes for auth
  • All UI state that persists → use cookies (useCookie())
  • Real-time data → SSE events via server/utils/events.ts
  • API keys → encrypted client-side before DB storage