tpmjs/docs/debug-reports/conversation-route-timeout-issue.md
Ajax Davis 4c5972adc4 fix: reduce maxDuration to 60s for Vercel Hobby plan compatibility
Routes with maxDuration=300 were silently failing on Vercel's edge
routing layer - requests never reached the serverless function (no
logs appeared). Vercel Hobby plan has a 60s max function duration.

Affected routes:
- /api/agents/[id]/conversation/[conversationId]
- /api/agents/[username]/[uid]/conversation/[conversationId]
- /api/mcp/[username]/[slug]/[transport]

The issue caused these routes to hang indefinitely until client timeout.
2026-01-09 07:06:54 +10:00

25 KiB

Next.js 16 API Route Timeout Issue - Comprehensive Debug Report

Date: 2026-01-09 Project: TPMJS (tpmjs.com) Issue: API routes at /api/agents/[id]/conversation/[conversationId] timeout without any Vercel function logs


Table of Contents

  1. Problem Summary
  2. Environment Details
  3. Symptoms
  4. Working vs Non-Working Endpoints
  5. Directory Structure
  6. Configuration Files
  7. Route File Contents
  8. Investigation Timeline
  9. Hypotheses Tested
  10. Reproduction Steps
  11. Key Observations
  12. Potential Causes

Problem Summary

Requests to /api/agents/[id]/conversation/[conversationId] (both GET and POST) hang indefinitely and timeout after 30-60 seconds. Critically, no logs appear in Vercel for these requests - the requests never seem to reach the serverless function at all.

Other API routes work perfectly fine, including routes with similar structure like /api/agents/[id] and /api/agents/[id]/conversations.


Environment Details

Framework & Runtime

  • Next.js Version: 16.0.8
  • Build System: Turbopack (default in Next.js 16)
  • React Version: 19.0.0
  • Node.js Runtime: nodejs (specified in route)
  • TypeScript Version: 5.9.3

Deployment

  • Platform: Vercel
  • Region: Auto (Vercel default)
  • Monorepo: Turborepo with pnpm

Database

  • Database: Neon PostgreSQL
  • ORM: Prisma 6.19.0
  • Connection: Working (verified via other endpoints)

Key Dependencies (from apps/web/package.json)

{
  "dependencies": {
    "@ai-sdk/anthropic": "^3.0.2",
    "@ai-sdk/google": "^3.0.2",
    "@ai-sdk/groq": "^3.0.2",
    "@ai-sdk/mistral": "^3.0.2",
    "@ai-sdk/openai": "3.0.1",
    "@modelcontextprotocol/sdk": "^1.25.1",
    "@prisma/client": "^6.19.0",
    "@vercel/analytics": "^1.6.1",
    "@vercel/blob": "^2.0.0",
    "@vercel/kv": "^3.0.0",
    "ai": "6.0.3",
    "better-auth": "^1.4.10",
    "next": "^16.0.8",
    "prisma": "^6.19.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "zod": "^4.0.0"
  }
}

Symptoms

  1. GET/POST requests timeout - No response after 30-60 seconds
  2. No Vercel logs - Requests don't appear in Vercel function logs at all
  3. No errors - No build errors, no runtime errors, nothing
  4. Route appears in build output - Listed as dynamic function (ƒ)
  5. Similar routes work - /api/agents/[id] and /api/agents/[id]/conversations work perfectly

Working vs Non-Working Endpoints

Working Endpoints (respond in < 1 second)

Endpoint Response Time Logs in Vercel
GET /api/health ~0.74s Yes
GET /api/agents/cmjx48ojx0001la04lbhn98pz ~0.40s Yes
GET /api/agents/cmjx48ojx0001la04lbhn98pz/conversations ~0.50s Yes
GET /api/tools ~0.80s Yes

Non-Working Endpoints (timeout, no logs)

Endpoint Result Logs in Vercel
GET /api/agents/[id]/conversation/[conversationId] Timeout No logs
POST /api/agents/[id]/conversation/[conversationId] Timeout No logs
GET /api/agents/[username]/[uid]/conversation/[conversationId] Timeout No logs
POST /api/agents/[username]/[uid]/conversation/[conversationId] Timeout No logs

Pattern Identified

All routes with the path pattern */conversation/[conversationId] fail. Routes with */conversations (plural, no nested param) work.


Directory Structure

apps/web/src/app/api/agents/
├── route.ts                                    ✅ Works
├── [id]/
│   ├── route.ts                                ✅ Works
│   ├── like/route.ts                           ✅ Works
│   ├── tools/route.ts                          ✅ Works
│   ├── tools/[toolId]/route.ts                 ✅ Works (nested dynamic)
│   ├── logs/route.ts                           ✅ Works
│   ├── clone/route.ts                          ✅ Works
│   ├── conversations/route.ts                  ✅ Works
│   ├── collections/route.ts                    ✅ Works
│   ├── collections/[collectionId]/route.ts     ✅ Works (nested dynamic)
│   ├── stats/route.ts                          ✅ Works
│   └── conversation/
│       └── [conversationId]/
│           └── route.ts                        ❌ FAILS - No logs
└── [username]/
    └── [uid]/
        └── conversation/
            └── [conversationId]/
                └── route.ts                    ❌ FAILS - No logs

Key Observation: Other nested dynamic routes work ([id]/tools/[toolId], [id]/collections/[collectionId]), but [id]/conversation/[conversationId] does not.


Configuration Files

Root vercel.json

{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "buildCommand": "pnpm run build:web",
  "outputDirectory": "apps/web/.next",
  "installCommand": "pnpm install",
  "framework": "nextjs",
  "git": {
    "deploymentEnabled": {
      "main": true
    }
  },
  "github": {
    "silent": false,
    "autoJobCancelation": true
  },
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Strict-Transport-Security",
          "value": "max-age=31536000; includeSubDomains; preload"
        },
        {
          "key": "X-Content-Type-Options",
          "value": "nosniff"
        },
        {
          "key": "X-Frame-Options",
          "value": "DENY"
        },
        {
          "key": "X-XSS-Protection",
          "value": "1; mode=block"
        },
        {
          "key": "Referrer-Policy",
          "value": "strict-origin-when-cross-origin"
        }
      ]
    }
  ],
  "crons": [
    {
      "path": "/api/sync/changes",
      "schedule": "0 */4 * * *"
    },
    {
      "path": "/api/sync/keyword",
      "schedule": "0 */6 * * *"
    },
    {
      "path": "/api/sync/metrics",
      "schedule": "0 0 * * *"
    },
    {
      "path": "/api/sync/stats-snapshot",
      "schedule": "0 0 * * *"
    },
    {
      "path": "/api/sync/cleanup-activity",
      "schedule": "0 3 * * *"
    }
  ]
}

apps/web/vercel.json

{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "buildCommand": "cd ../.. && pnpm run build:web",
  "installCommand": "cd ../.. && pnpm install"
}

apps/web/next.config.ts

import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  transpilePackages: ['@tpmjs/ui', '@tpmjs/utils', '@tpmjs/db', '@tpmjs/types', '@tpmjs/env'],
  reactStrictMode: true,
  serverExternalPackages: ['@tpmjs/package-executor'],
  async redirects() {
    return [
      {
        source: '/tools-ideas',
        destination: '/tool-ideas',
        permanent: true,
      },
    ];
  },
};

export default nextConfig;

apps/web/src/middleware.ts

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Protect /dashboard routes
  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    // Check for session token - better-auth uses __Secure- prefix for HTTPS cookies
    const sessionToken =
      request.cookies.get('__Secure-better-auth.session_token') ||
      request.cookies.get('better-auth.session_token');
    if (!sessionToken) {
      return NextResponse.redirect(new URL('/sign-in', request.url));
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*'],
};

Note: Middleware only matches /dashboard/* paths, not /api/* paths. Should not affect API routes.


Route File Contents

FAILING ROUTE: apps/web/src/app/api/agents/[id]/conversation/[conversationId]/route.ts

/**
 * Agent Conversation Endpoint (ID-based version)
 *
 * POST: Send a message and stream the AI response
 * GET: Retrieve conversation history
 * DELETE: Delete a conversation
 *
 * This endpoint uses agent id directly for dashboard usage
 */

import { decryptApiKey } from '@/lib/crypto/api-keys';
import { Prisma, prisma } from '@tpmjs/db';
import type { AIProvider } from '@tpmjs/types/agent';
import { SendMessageSchema } from '@tpmjs/types/agent';
import type { LanguageModel, ModelMessage } from 'ai';
import { type NextRequest, NextResponse } from 'next/server';
import { type RateLimitConfig, checkRateLimit } from '~/lib/rate-limit';

/**
 * Rate limit for chat messages: 30 requests per minute
 * This is stricter than default because chat involves expensive LLM calls
 */
const CHAT_RATE_LIMIT: RateLimitConfig = {
  limit: 30,
  windowSeconds: 60,
};

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export const maxDuration = 300; // 5 minutes for long agentic runs

type RouteContext = {
  params: Promise<{ id: string; conversationId: string }>;
};

/**
 * Get AI provider SDK based on provider type
 */
async function getProviderModel(
  provider: AIProvider,
  modelId: string,
  apiKey: string
): Promise<LanguageModel> {
  switch (provider) {
    case 'OPENAI': {
      const { createOpenAI } = await import('@ai-sdk/openai');
      return createOpenAI({ apiKey })(modelId);
    }
    case 'ANTHROPIC': {
      const { createAnthropic } = await import('@ai-sdk/anthropic');
      return createAnthropic({ apiKey })(modelId);
    }
    case 'GOOGLE': {
      const { createGoogleGenerativeAI } = await import('@ai-sdk/google');
      return createGoogleGenerativeAI({ apiKey })(modelId);
    }
    case 'GROQ': {
      const { createGroq } = await import('@ai-sdk/groq');
      return createGroq({ apiKey })(modelId);
    }
    case 'MISTRAL': {
      const { createMistral } = await import('@ai-sdk/mistral');
      return createMistral({ apiKey })(modelId);
    }
    default:
      throw new Error(`Unsupported provider: ${provider}`);
  }
}

/**
 * POST /api/agents/[id]/conversation/[conversationId]
 * Send a message and stream the AI response via SSE
 */
export async function POST(request: NextRequest, context: RouteContext): Promise<Response> {
  // Check rate limit first to prevent expensive LLM calls
  const rateLimitResponse = checkRateLimit(request, CHAT_RATE_LIMIT);
  if (rateLimitResponse) {
    return rateLimitResponse;
  }

  const { id: agentId, conversationId } = await context.params;

  try {
    const body = await request.json();
    const parsed = SendMessageSchema.safeParse(body);
    if (!parsed.success) {
      return NextResponse.json(
        { success: false, error: 'Invalid request', details: parsed.error.flatten() },
        { status: 400 }
      );
    }

    // Fetch agent with all tool relations using agent ID
    const { fetchAgentWithTools, buildAgentTools } = await import('@/lib/agents/build-tools');
    const agent = await fetchAgentWithTools(agentId);

    if (!agent) {
      return NextResponse.json({ success: false, error: 'Agent not found' }, { status: 404 });
    }

    // ... rest of implementation (streaming SSE response)
  } catch (error) {
    console.error('Failed to process message:', error);
    return NextResponse.json(
      {
        success: false,
        error: error instanceof Error ? error.message : 'Failed to process message',
      },
      { status: 500 }
    );
  }
}

/**
 * GET /api/agents/[id]/conversation/[conversationId]
 * Retrieve conversation history with pagination
 */
export async function GET(request: NextRequest, context: RouteContext): Promise<NextResponse> {
  const { id: agentId, conversationId } = await context.params;
  const { searchParams } = new URL(request.url);

  const limit = Math.min(Number.parseInt(searchParams.get('limit') || '50', 10), 100);
  const before = searchParams.get('before');
  const after = searchParams.get('after');

  try {
    // Fetch agent by ID
    const agent = await prisma.agent.findUnique({
      where: { id: agentId },
      select: { id: true },
    });

    if (!agent) {
      return NextResponse.json({ success: false, error: 'Agent not found' }, { status: 404 });
    }

    // Fetch conversation
    const conversation = await prisma.conversation.findUnique({
      where: {
        agentId_slug: {
          agentId: agent.id,
          slug: conversationId,
        },
      },
    });

    if (!conversation) {
      return NextResponse.json(
        { success: false, error: 'Conversation not found' },
        { status: 404 }
      );
    }

    // ... rest of implementation (simple Prisma queries)
  } catch (error) {
    console.error('Failed to fetch conversation:', error);
    return NextResponse.json(
      { success: false, error: 'Failed to fetch conversation' },
      { status: 500 }
    );
  }
}

/**
 * DELETE /api/agents/[id]/conversation/[conversationId]
 * Delete a conversation
 */
export async function DELETE(_request: NextRequest, context: RouteContext): Promise<NextResponse> {
  const { id: agentId, conversationId } = await context.params;

  try {
    // Fetch agent by ID
    const agent = await prisma.agent.findUnique({
      where: { id: agentId },
      select: { id: true },
    });

    if (!agent) {
      return NextResponse.json({ success: false, error: 'Agent not found' }, { status: 404 });
    }

    // Delete conversation (messages cascade)
    await prisma.conversation.deleteMany({
      where: {
        agentId: agent.id,
        slug: conversationId,
      },
    });

    return NextResponse.json({
      success: true,
      data: { deleted: true },
    });
  } catch (error) {
    console.error('Failed to delete conversation:', error);
    return NextResponse.json(
      { success: false, error: 'Failed to delete conversation' },
      { status: 500 }
    );
  }
}

WORKING ROUTE: apps/web/src/app/api/agents/[id]/conversations/route.ts (plural)

/**
 * Agent Conversations List Endpoint
 *
 * GET: List all conversations for an agent
 */

import { prisma } from '@tpmjs/db';
import { type NextRequest, NextResponse } from 'next/server';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export const maxDuration = 30;

type RouteContext = {
  params: Promise<{ id: string }>;
};

/**
 * GET /api/agents/[id]/conversations
 * List all conversations for an agent (accepts id or uid)
 */
export async function GET(request: NextRequest, context: RouteContext): Promise<NextResponse> {
  const { id: idOrUid } = await context.params;
  const { searchParams } = new URL(request.url);

  const limit = Math.min(Number.parseInt(searchParams.get('limit') || '20', 10), 100);
  const offset = Number.parseInt(searchParams.get('offset') || '0', 10);

  try {
    // Fetch agent by id or uid
    const agent = await prisma.agent.findFirst({
      where: {
        OR: [{ id: idOrUid }, { uid: idOrUid }],
      },
      select: { id: true },
    });

    if (!agent) {
      return NextResponse.json({ success: false, error: 'Agent not found' }, { status: 404 });
    }

    // Fetch conversations with message count
    const conversations = await prisma.conversation.findMany({
      where: { agentId: agent.id },
      orderBy: { updatedAt: 'desc' },
      take: limit + 1,
      skip: offset,
      include: {
        _count: {
          select: { messages: true },
        },
      },
    });

    const hasMore = conversations.length > limit;
    const data = hasMore ? conversations.slice(0, limit) : conversations;

    return NextResponse.json({
      success: true,
      data: data.map((c) => ({
        id: c.id,
        slug: c.slug,
        title: c.title,
        messageCount: c._count.messages,
        createdAt: c.createdAt,
        updatedAt: c.updatedAt,
      })),
      pagination: {
        limit,
        offset,
        hasMore,
      },
    });
  } catch (error) {
    console.error('Failed to fetch conversations:', error);
    return NextResponse.json(
      { success: false, error: 'Failed to fetch conversations' },
      { status: 500 }
    );
  }
}

WORKING ROUTE: apps/web/src/app/api/health/route.ts

import { type NextRequest, NextResponse } from 'next/server';
import { checkRateLimit } from '~/lib/rate-limit';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export const maxDuration = 60;

/**
 * GET /api/health
 * Simple health check endpoint that doesn't touch the database
 */
export async function GET(request: NextRequest) {
  // Check rate limit
  const rateLimitResponse = checkRateLimit(request);
  if (rateLimitResponse) {
    return rateLimitResponse;
  }
  return NextResponse.json({
    status: 'ok',
    timestamp: new Date().toISOString(),
    build: {
      // Vercel provides these at runtime
      commitSha: process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7) || 'local',
      commitMessage: process.env.VERCEL_GIT_COMMIT_MESSAGE || 'local',
      deploymentUrl: process.env.VERCEL_URL || 'localhost',
    },
    env: {
      hasDatabase: !!process.env.DATABASE_URL,
      nodeEnv: process.env.NODE_ENV,
    },
  });
}

WORKING ROUTE: apps/web/src/app/api/agents/[id]/route.ts

import { Prisma, prisma } from '@tpmjs/db';
import { UpdateAgentSchema } from '@tpmjs/types/agent';
import { headers } from 'next/headers';
import type { NextRequest } from 'next/server';

import { logActivity } from '~/lib/activity';
import {
  apiConflict,
  apiForbidden,
  apiInternalError,
  apiNotFound,
  apiSuccess,
  apiUnauthorized,
  apiValidationError,
} from '~/lib/api-response';
import { auth } from '~/lib/auth';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

type RouteContext = {
  params: Promise<{ id: string }>;
};

/**
 * GET /api/agents/[id]
 * Get a single agent's details
 */
export async function GET(_request: NextRequest, context: RouteContext) {
  const requestId = crypto.randomUUID();

  try {
    const session = await auth.api.getSession({ headers: await headers() });
    const { id } = await context.params;

    const agent = await prisma.agent.findUnique({
      where: { id },
      include: {
        collections: {
          include: {
            collection: {
              select: {
                id: true,
                name: true,
                _count: { select: { tools: true } },
              },
            },
          },
          orderBy: { position: 'asc' },
          take: 50,
        },
        tools: {
          include: {
            tool: {
              select: {
                id: true,
                name: true,
                description: true,
                package: {
                  select: {
                    npmPackageName: true,
                    category: true,
                  },
                },
              },
            },
          },
          orderBy: { position: 'asc' },
          take: 100,
        },
        _count: {
          select: {
            tools: true,
            collections: true,
            conversations: true,
          },
        },
      },
    });

    if (!agent) {
      return apiNotFound('Agent', requestId);
    }

    // Check access - owner or public
    const isOwner = session?.user?.id === agent.userId;
    if (!isOwner && !agent.isPublic) {
      return apiForbidden('Access denied', requestId);
    }

    return apiSuccess(
      {
        ...agent,
        isOwner,
        toolCount: agent._count.tools,
        collectionCount: agent._count.collections,
        conversationCount: agent._count.conversations,
        // ... mapping
      },
      { requestId }
    );
  } catch (error) {
    console.error('Failed to get agent:', error);
    return apiInternalError('Failed to get agent', requestId);
  }
}

// PATCH and DELETE handlers also work fine

Investigation Timeline

Initial State

  • Routes were working previously (before refactoring)
  • User refactored code, issue started

Fix Attempt 1: Rate Limiter Timeout

  • Hypothesis: checkRateLimitDistributed using @vercel/kv was hanging
  • Action: Added 2-second timeout to KV calls
  • Result: Still timing out

Fix Attempt 2: Switch to Sync Rate Limiter

  • Hypothesis: Async KV rate limiter had connection issues
  • Action: Changed from checkRateLimitDistributed to checkRateLimit (sync, in-memory)
  • Result: Still timing out

Current State

  • Route builds successfully
  • Route appears in Vercel build output as dynamic function
  • Requests timeout with no logs
  • Other similar routes work fine

Hypotheses Tested

# Hypothesis Test Result
1 Rate limiter hanging Added timeout Failed
2 KV connection issue Switched to sync limiter Failed
3 Database connection Tested with /api/health DB works
4 Prisma cold start Tested other Prisma routes Other routes work
5 Build issue Checked build output Route is built
6 Middleware blocking Checked middleware config Only matches /dashboard/*

Reproduction Steps

1. Test Working Endpoint

curl -s 'https://tpmjs.com/api/health' -w "\nTime: %{time_total}s"
# Returns: {"status":"ok",...} in ~0.7s

2. Test Working Agent Endpoint

curl -s 'https://tpmjs.com/api/agents/cmjx48ojx0001la04lbhn98pz' -w "\nTime: %{time_total}s"
# Returns: Agent data in ~0.4s

3. Test Working Conversations (plural) Endpoint

curl -s 'https://tpmjs.com/api/agents/cmjx48ojx0001la04lbhn98pz/conversations' -w "\nTime: %{time_total}s"
# Returns: Conversations list in ~0.5s

4. Test Failing Conversation (singular) Endpoint - GET

curl -s 'https://tpmjs.com/api/agents/cmjx48ojx0001la04lbhn98pz/conversation/conv-1767892050799-rk8eaqwhr' \
  -w "\nTime: %{time_total}s" \
  --max-time 60

# Result: Hangs until timeout, no response
# Vercel logs: NO ENTRY for this request

5. Test Failing Conversation Endpoint - POST

curl 'https://tpmjs.com/api/agents/cmjx48ojx0001la04lbhn98pz/conversation/conv-1767892050799-rk8eaqwhr' \
  -H 'Content-Type: application/json' \
  --data-raw '{"message":"hi"}' \
  -w "\nTime: %{time_total}s" \
  --max-time 60

# Result: Hangs until timeout, no response
# Vercel logs: NO ENTRY for this request

Key Observations

1. No Vercel Logs

The most critical observation: requests to the failing route do not appear in Vercel logs at all. This suggests the request is not reaching the serverless function.

2. Route Pattern Difference

  • /api/agents/[id]/conversation/[conversationId] - Fails
  • /api/agents/[id]/conversations - Works
  • /api/agents/[id]/collections/[collectionId] - Works
  • /api/agents/[id]/tools/[toolId] - Works

The pattern conversation/[conversationId] fails while similar patterns like tools/[toolId] work.

3. Build Output Shows Route

Route (app)
├ ƒ /api/agents/[id]/conversation/[conversationId]  ← Listed as dynamic function

4. Singular vs Plural

  • conversation (singular) + nested param = Fails
  • conversations (plural) = Works

5. Multiple Failing Routes

Both route variants fail:

  • /api/agents/[id]/conversation/[conversationId]/route.ts
  • /api/agents/[username]/[uid]/conversation/[conversationId]/route.ts

Potential Causes

1. Next.js 16 / Turbopack Routing Bug

There may be a bug in Next.js 16 or Turbopack with:

  • Specific path patterns
  • Nested dynamic segments with certain names
  • Routes with "conversation" in the path

2. Vercel Edge Routing Issue

Vercel's edge network might have issues routing to:

  • Deeply nested dynamic routes
  • Routes with specific naming patterns
  • Routes with certain configurations

3. Build Artifact Corruption

The route might:

  • Build successfully but not deploy correctly
  • Have a corrupted manifest entry
  • Be missing from the serverless function bundle

4. Route Conflict

There might be an internal conflict between:

  • conversation (singular) and conversations (plural)
  • The nested dynamic segment pattern
  • Some internal Next.js routing logic

5. Framework Bug with Dynamic Imports

The route uses dynamic imports (await import()) which might cause issues with:

  • Turbopack bundling
  • Serverless function generation
  • Module resolution

Recent Commits

5bf4260 fix: use sync rate limiter for conversation routes
05ccdc6 fix: add timeout to Vercel KV rate limiter to prevent hanging
4db1522 fix: add ID-based conversation route for dashboard chat
7fe1027 fix: add root-level health and execute-tool routes for TPMJS compatibility
63d408b fix(executor): use correct Vercel Sandbox SDK API

Questions for Further Investigation

  1. Is this a known Next.js 16 / Turbopack issue?
  2. Does the route name "conversation" conflict with any internal Next.js routing?
  3. Is there a Vercel-specific issue with nested dynamic segments?
  4. Why do other nested dynamic routes (tools/[toolId], collections/[collectionId]) work?
  5. Is there something specific about the combination of:
    • Multiple dynamic segments ([id] + [conversationId])
    • The word "conversation"
    • The route file size/complexity

Files Affected

  • apps/web/src/app/api/agents/[id]/conversation/[conversationId]/route.ts
  • apps/web/src/app/api/agents/[username]/[uid]/conversation/[conversationId]/route.ts
  • apps/web/src/app/api/agents/[id]/conversations/route.ts
  • apps/web/src/app/api/agents/[id]/tools/[toolId]/route.ts
  • apps/web/src/app/api/agents/[id]/collections/[collectionId]/route.ts
  • apps/web/src/app/api/agents/[id]/route.ts
  • apps/web/src/app/api/health/route.ts