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>
120 lines
4.2 KiB
C#
120 lines
4.2 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Codex.Dal;
|
|
using Codex.Dal.Enums;
|
|
using FluentValidation;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using OpenHarbor.CQRS.Abstractions;
|
|
|
|
namespace Codex.CQRS.Commands;
|
|
|
|
/// <summary>
|
|
/// Completes an agent execution with results and metrics
|
|
/// </summary>
|
|
public record CompleteAgentExecutionCommand
|
|
{
|
|
/// <summary>Execution ID to complete</summary>
|
|
public Guid ExecutionId { get; init; }
|
|
|
|
/// <summary>Agent's output/response</summary>
|
|
public string Output { get; init; } = string.Empty;
|
|
|
|
/// <summary>Execution status (Completed, Failed, Cancelled)</summary>
|
|
public ExecutionStatus Status { get; init; }
|
|
|
|
/// <summary>Input tokens consumed</summary>
|
|
public int? InputTokens { get; init; }
|
|
|
|
/// <summary>Output tokens generated</summary>
|
|
public int? OutputTokens { get; init; }
|
|
|
|
/// <summary>Estimated cost in USD</summary>
|
|
public decimal? EstimatedCost { get; init; }
|
|
|
|
/// <summary>Tool calls made (JSON array)</summary>
|
|
public string? ToolCalls { get; init; }
|
|
|
|
/// <summary>Tool call results (JSON array)</summary>
|
|
public string? ToolCallResults { get; init; }
|
|
|
|
/// <summary>Error message if failed</summary>
|
|
public string? ErrorMessage { get; init; }
|
|
}
|
|
|
|
public class CompleteAgentExecutionCommandHandler : ICommandHandler<CompleteAgentExecutionCommand>
|
|
{
|
|
private readonly CodexDbContext _dbContext;
|
|
|
|
public CompleteAgentExecutionCommandHandler(CodexDbContext dbContext)
|
|
{
|
|
_dbContext = dbContext;
|
|
}
|
|
|
|
public async Task HandleAsync(CompleteAgentExecutionCommand command, CancellationToken cancellationToken = default)
|
|
{
|
|
var execution = await _dbContext.AgentExecutions
|
|
.FirstOrDefaultAsync(e => e.Id == command.ExecutionId, cancellationToken);
|
|
|
|
if (execution == null)
|
|
{
|
|
throw new InvalidOperationException($"Execution with ID {command.ExecutionId} not found");
|
|
}
|
|
|
|
if (execution.Status != ExecutionStatus.Running)
|
|
{
|
|
throw new InvalidOperationException($"Execution {command.ExecutionId} is not in Running state (current: {execution.Status})");
|
|
}
|
|
|
|
var completedAt = DateTime.UtcNow;
|
|
var executionTimeMs = (long)(completedAt - execution.StartedAt).TotalMilliseconds;
|
|
|
|
execution.Output = command.Output;
|
|
execution.Status = command.Status;
|
|
execution.CompletedAt = completedAt;
|
|
execution.ExecutionTimeMs = executionTimeMs;
|
|
execution.InputTokens = command.InputTokens;
|
|
execution.OutputTokens = command.OutputTokens;
|
|
execution.TotalTokens = (command.InputTokens ?? 0) + (command.OutputTokens ?? 0);
|
|
execution.EstimatedCost = command.EstimatedCost;
|
|
execution.ToolCalls = command.ToolCalls;
|
|
execution.ToolCallResults = command.ToolCallResults;
|
|
execution.ErrorMessage = command.ErrorMessage;
|
|
|
|
await _dbContext.SaveChangesAsync(cancellationToken);
|
|
}
|
|
}
|
|
|
|
public class CompleteAgentExecutionCommandValidator : AbstractValidator<CompleteAgentExecutionCommand>
|
|
{
|
|
public CompleteAgentExecutionCommandValidator()
|
|
{
|
|
RuleFor(x => x.ExecutionId)
|
|
.NotEmpty().WithMessage("Execution ID is required");
|
|
|
|
RuleFor(x => x.Status)
|
|
.Must(s => s == ExecutionStatus.Completed || s == ExecutionStatus.Failed || s == ExecutionStatus.Cancelled)
|
|
.WithMessage("Status must be Completed, Failed, or Cancelled");
|
|
|
|
RuleFor(x => x.ErrorMessage)
|
|
.NotEmpty()
|
|
.When(x => x.Status == ExecutionStatus.Failed)
|
|
.WithMessage("Error message is required when status is Failed");
|
|
|
|
RuleFor(x => x.InputTokens)
|
|
.GreaterThanOrEqualTo(0)
|
|
.When(x => x.InputTokens.HasValue)
|
|
.WithMessage("Input tokens must be >= 0");
|
|
|
|
RuleFor(x => x.OutputTokens)
|
|
.GreaterThanOrEqualTo(0)
|
|
.When(x => x.OutputTokens.HasValue)
|
|
.WithMessage("Output tokens must be >= 0");
|
|
|
|
RuleFor(x => x.EstimatedCost)
|
|
.GreaterThanOrEqualTo(0)
|
|
.When(x => x.EstimatedCost.HasValue)
|
|
.WithMessage("Estimated cost must be >= 0");
|
|
}
|
|
}
|