CODEX_ADK/BACKEND/Codex.Api/Endpoints/SimpleEndpoints.cs
jean-philippe 3fae2fcbe1 Initial commit: CODEX_ADK (Svrnty Console) MVP v1.0.0
This is the initial commit for the CODEX_ADK project, a full-stack AI agent
management platform featuring:

BACKEND (ASP.NET Core 8.0):
- CQRS architecture with 6 commands and 7 queries
- 16 API endpoints (all working and tested)
- PostgreSQL database with 5 entities
- AES-256 encryption for API keys
- FluentValidation on all commands
- Rate limiting and CORS configured
- OpenAPI/Swagger documentation
- Docker Compose setup (PostgreSQL + Ollama)

FRONTEND (Flutter 3.x):
- Dark theme with Svrnty branding
- Collapsible sidebar navigation
- CQRS API client with Result<T> error handling
- Type-safe endpoints from OpenAPI schema
- Multi-platform support (Web, iOS, Android, macOS, Linux, Windows)

DOCUMENTATION:
- Comprehensive API reference
- Architecture documentation
- Development guidelines for Claude Code
- API integration guides
- context-claude.md project overview

Status: Backend ready (Grade A-), Frontend integration pending

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-26 18:32:38 -04:00

244 lines
8.5 KiB
C#

using Codex.Dal;
using Codex.Dal.Enums;
using Microsoft.EntityFrameworkCore;
namespace Codex.Api.Endpoints;
/// <summary>
/// Simple, pragmatic REST endpoints for MVP.
/// No over-engineering. Just JSON lists that work.
/// </summary>
public static class SimpleEndpoints
{
public static WebApplication MapSimpleEndpoints(this WebApplication app)
{
// ============================================================
// AGENTS
// ============================================================
app.MapGet("/api/agents", async (CodexDbContext db) =>
{
var agents = await db.Agents
.Where(a => !a.IsDeleted)
.OrderByDescending(a => a.CreatedAt)
.Select(a => new
{
a.Id,
a.Name,
a.Description,
a.Type,
a.ModelProvider,
a.ModelName,
a.ProviderType,
a.ModelEndpoint,
a.Status,
a.CreatedAt,
a.UpdatedAt,
ToolCount = a.Tools.Count(t => t.IsEnabled),
ExecutionCount = a.Executions.Count
})
.Take(100) // More than enough for MVP
.ToListAsync();
return Results.Ok(agents);
})
.WithName("GetAllAgents")
.WithTags("Agents")
.WithOpenApi(operation =>
{
operation.Summary = "Get all agents";
operation.Description = "Returns a list of all active agents with metadata. Limit: 100 most recent.";
return operation;
})
.Produces<object>(200);
app.MapGet("/api/agents/{id:guid}/conversations", async (Guid id, CodexDbContext db) =>
{
var conversations = await db.AgentExecutions
.Where(e => e.AgentId == id && e.ConversationId != null)
.Select(e => e.ConversationId)
.Distinct()
.Join(db.Conversations,
convId => convId,
c => (Guid?)c.Id,
(convId, c) => new
{
c.Id,
c.Title,
c.Summary,
c.StartedAt,
c.LastMessageAt,
c.MessageCount,
c.IsActive
})
.OrderByDescending(c => c.LastMessageAt)
.ToListAsync();
return Results.Ok(conversations);
})
.WithName("GetAgentConversations")
.WithTags("Agents")
.WithOpenApi(operation =>
{
operation.Summary = "Get conversations for an agent";
operation.Description = "Returns all conversations associated with a specific agent.";
return operation;
})
.Produces<object>(200);
app.MapGet("/api/agents/{id:guid}/executions", async (Guid id, CodexDbContext db) =>
{
var executions = await db.AgentExecutions
.Where(e => e.AgentId == id)
.OrderByDescending(e => e.StartedAt)
.Select(e => new
{
e.Id,
e.AgentId,
e.ConversationId,
UserPrompt = e.UserPrompt.Substring(0, Math.Min(e.UserPrompt.Length, 200)), // Truncate for list view
e.Status,
e.StartedAt,
e.CompletedAt,
e.InputTokens,
e.OutputTokens,
e.EstimatedCost,
MessageCount = e.Messages.Count,
e.ErrorMessage
})
.Take(100)
.ToListAsync();
return Results.Ok(executions);
})
.WithName("GetAgentExecutions")
.WithTags("Agents")
.WithOpenApi(operation =>
{
operation.Summary = "Get execution history for an agent";
operation.Description = "Returns the 100 most recent executions for a specific agent.";
return operation;
})
.Produces<object>(200);
// ============================================================
// CONVERSATIONS
// ============================================================
app.MapGet("/api/conversations", async (CodexDbContext db) =>
{
var conversations = await db.Conversations
.OrderByDescending(c => c.LastMessageAt)
.Select(c => new
{
c.Id,
c.Title,
c.Summary,
c.StartedAt,
c.LastMessageAt,
c.MessageCount,
c.IsActive,
ExecutionCount = db.AgentExecutions.Count(e => e.ConversationId == c.Id)
})
.Take(100)
.ToListAsync();
return Results.Ok(conversations);
})
.WithName("GetAllConversations")
.WithTags("Conversations")
.WithOpenApi(operation =>
{
operation.Summary = "Get all conversations";
operation.Description = "Returns the 100 most recent conversations.";
return operation;
})
.Produces<object>(200);
// ============================================================
// EXECUTIONS
// ============================================================
app.MapGet("/api/executions", async (CodexDbContext db) =>
{
var executions = await db.AgentExecutions
.Include(e => e.Agent)
.OrderByDescending(e => e.StartedAt)
.Select(e => new
{
e.Id,
e.AgentId,
AgentName = e.Agent.Name,
e.ConversationId,
UserPrompt = e.UserPrompt.Substring(0, Math.Min(e.UserPrompt.Length, 200)),
e.Status,
e.StartedAt,
e.CompletedAt,
e.InputTokens,
e.OutputTokens,
e.EstimatedCost,
MessageCount = e.Messages.Count,
e.ErrorMessage
})
.Take(100)
.ToListAsync();
return Results.Ok(executions);
})
.WithName("GetAllExecutions")
.WithTags("Executions")
.WithOpenApi(operation =>
{
operation.Summary = "Get all executions";
operation.Description = "Returns the 100 most recent executions across all agents.";
return operation;
})
.Produces<object>(200);
app.MapGet("/api/executions/status/{status}", async (string status, CodexDbContext db) =>
{
if (!Enum.TryParse<ExecutionStatus>(status, true, out var executionStatus))
{
return Results.BadRequest(new { error = $"Invalid status: {status}. Valid values: Pending, Running, Completed, Failed, Cancelled" });
}
var executions = await db.AgentExecutions
.Include(e => e.Agent)
.Where(e => e.Status == executionStatus)
.OrderByDescending(e => e.StartedAt)
.Select(e => new
{
e.Id,
e.AgentId,
AgentName = e.Agent.Name,
e.ConversationId,
UserPrompt = e.UserPrompt.Substring(0, Math.Min(e.UserPrompt.Length, 200)),
e.Status,
e.StartedAt,
e.CompletedAt,
e.InputTokens,
e.OutputTokens,
e.EstimatedCost,
MessageCount = e.Messages.Count,
e.ErrorMessage
})
.Take(100)
.ToListAsync();
return Results.Ok(executions);
})
.WithName("GetExecutionsByStatus")
.WithTags("Executions")
.WithOpenApi(operation =>
{
operation.Summary = "Get executions by status";
operation.Description = "Returns executions filtered by status (Pending, Running, Completed, Failed, Cancelled).";
return operation;
})
.Produces<object>(200)
.Produces(400);
return app;
}
}