Multi-agent AI laboratory with ASP.NET Core 8.0 backend and Flutter frontend. Implements CQRS architecture, OpenAPI contract-first API design. BACKEND: Agent management, conversations, executions with PostgreSQL + Ollama FRONTEND: Cross-platform UI with strict typing and Result-based error handling Co-Authored-By: Jean-Philippe Brule <jp@svrnty.io>
242 lines
11 KiB
C#
242 lines
11 KiB
C#
using Codex.CQRS.Commands;
|
|
using Codex.CQRS.Queries;
|
|
using Codex.Dal.QueryProviders;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.OpenApi.Models;
|
|
using OpenHarbor.CQRS.Abstractions;
|
|
using OpenHarbor.CQRS.DynamicQuery.Abstractions;
|
|
using PoweredSoft.Data.Core;
|
|
using PoweredSoft.DynamicQuery;
|
|
using PoweredSoft.DynamicQuery.Core;
|
|
|
|
namespace Codex.Api.Endpoints;
|
|
|
|
/// <summary>
|
|
/// Manual endpoint registration for endpoints requiring custom OpenAPI documentation.
|
|
/// OpenHarbor.CQRS v8.1.0-rc1 auto-registers and auto-documents all ICommandHandler implementations.
|
|
/// Manual registration should only be used for advanced customization needs.
|
|
/// </summary>
|
|
public static class ManualEndpointRegistration
|
|
{
|
|
public static WebApplication MapCodexEndpoints(this WebApplication app)
|
|
{
|
|
// ============================================================
|
|
// COMMANDS - AUTO-REGISTERED BY OPENHARBOR.CQRS
|
|
// ============================================================
|
|
// All commands are automatically registered and documented by the framework:
|
|
// - CreateAgent (no return value)
|
|
// - UpdateAgent (no return value)
|
|
// - DeleteAgent (no return value)
|
|
// - CreateConversation (returns Guid)
|
|
// - StartAgentExecution (returns Guid)
|
|
// - CompleteAgentExecution (no return value)
|
|
//
|
|
// Routes: POST /api/command/{commandName}
|
|
// Documentation: Automatically generated from XML comments in command classes
|
|
|
|
// ============================================================
|
|
// QUERIES - AUTO-REGISTERED BY OPENHARBOR.CQRS
|
|
// ============================================================
|
|
// All queries are automatically registered and documented by the framework:
|
|
// - Health (simple check)
|
|
// - GetAgent (returns AgentDetails)
|
|
// - GetAgentExecution (returns AgentExecutionDetails)
|
|
// - GetConversation (returns ConversationDetails)
|
|
//
|
|
// Routes: POST /api/query/{queryName}
|
|
// Documentation: Automatically generated from XML comments in query classes
|
|
|
|
// ============================================================
|
|
// DYNAMIC QUERIES (Paginated Lists)
|
|
// ============================================================
|
|
// NOTE: Dynamic queries are auto-registered by OpenHarbor but not auto-documented.
|
|
// They work via /api/dynamicquery/{ItemType} but aren't in Swagger without manual registration.
|
|
// The endpoints exist and function - frontend can use them directly from openapi.json examples below.
|
|
|
|
// Manual registration disabled for now - OpenHarbor handles these automatically
|
|
// TODO: Add proper schema documentation for dynamic query request/response
|
|
|
|
/*
|
|
// ListAgents
|
|
app.MapPost("/api/dynamicquery/ListAgentsQueryItem",
|
|
async (HttpContext context,
|
|
IQueryableProvider<ListAgentsQueryItem> provider,
|
|
IAsyncQueryableService queryService) =>
|
|
{
|
|
var query = await context.Request.ReadFromJsonAsync<object>();
|
|
var queryable = await provider.GetQueryableAsync(query!);
|
|
var result = await queryService.ExecuteAsync(queryable, query!);
|
|
return Results.Ok(result);
|
|
})
|
|
.WithName("ListAgents")
|
|
.WithTags("DynamicQuery")
|
|
.WithOpenApi(operation => new(operation)
|
|
{
|
|
Summary = "List agents with filtering, sorting, and pagination",
|
|
Description = @"Dynamic query endpoint supporting:
|
|
- **Filtering**: Filter by any property (Name, Type, Status, etc.)
|
|
- **Sorting**: Sort by one or multiple properties
|
|
- **Pagination**: Page and PageSize parameters
|
|
- **Aggregates**: Count, Sum, Average on numeric fields
|
|
|
|
### Example Request
|
|
```json
|
|
{
|
|
""filters"": [
|
|
{ ""path"": ""Name"", ""operator"": ""Contains"", ""value"": ""search"" },
|
|
{ ""path"": ""Status"", ""operator"": ""Equal"", ""value"": ""Active"" }
|
|
],
|
|
""sorts"": [{ ""path"": ""CreatedAt"", ""descending"": true }],
|
|
""page"": 1,
|
|
""pageSize"": 20
|
|
}
|
|
```",
|
|
Responses = new OpenApiResponses
|
|
{
|
|
["200"] = new OpenApiResponse
|
|
{
|
|
Description = "Paginated list of agents",
|
|
Content = new Dictionary<string, OpenApiMediaType>
|
|
{
|
|
["application/json"] = new OpenApiMediaType
|
|
{
|
|
Schema = new OpenApiSchema
|
|
{
|
|
Type = "object",
|
|
Properties = new Dictionary<string, OpenApiSchema>
|
|
{
|
|
["data"] = new OpenApiSchema
|
|
{
|
|
Type = "array",
|
|
Items = new OpenApiSchema
|
|
{
|
|
Reference = new OpenApiReference
|
|
{
|
|
Type = ReferenceType.Schema,
|
|
Id = nameof(ListAgentsQueryItem)
|
|
}
|
|
}
|
|
},
|
|
["page"] = new OpenApiSchema { Type = "integer" },
|
|
["pageSize"] = new OpenApiSchema { Type = "integer" },
|
|
["totalCount"] = new OpenApiSchema { Type = "integer" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.Produces<object>(200);
|
|
|
|
// ListConversations
|
|
app.MapPost("/api/dynamicquery/ListConversationsQueryItem",
|
|
async (HttpContext context,
|
|
IQueryableProvider<ListConversationsQueryItem> provider,
|
|
IAsyncQueryableService queryService) =>
|
|
{
|
|
var query = await context.Request.ReadFromJsonAsync<object>();
|
|
var queryable = await provider.GetQueryableAsync(query!);
|
|
var result = await queryService.ExecuteAsync(queryable, query!);
|
|
return Results.Ok(result);
|
|
})
|
|
.WithName("ListConversations")
|
|
.WithTags("DynamicQuery")
|
|
.WithOpenApi(operation => new(operation)
|
|
{
|
|
Summary = "List conversations with filtering, sorting, and pagination",
|
|
Description = "Returns paginated conversations with message counts and metadata",
|
|
Responses = new OpenApiResponses
|
|
{
|
|
["200"] = new OpenApiResponse
|
|
{
|
|
Description = "Paginated list of conversations",
|
|
Content = new Dictionary<string, OpenApiMediaType>
|
|
{
|
|
["application/json"] = new OpenApiMediaType
|
|
{
|
|
Schema = new OpenApiSchema
|
|
{
|
|
Type = "object",
|
|
Properties = new Dictionary<string, OpenApiSchema>
|
|
{
|
|
["data"] = new OpenApiSchema
|
|
{
|
|
Type = "array",
|
|
Items = new OpenApiSchema
|
|
{
|
|
Reference = new OpenApiReference
|
|
{
|
|
Type = ReferenceType.Schema,
|
|
Id = nameof(ListConversationsQueryItem)
|
|
}
|
|
}
|
|
},
|
|
["totalCount"] = new OpenApiSchema { Type = "integer" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.Produces<object>(200);
|
|
|
|
// ListAgentExecutions
|
|
app.MapPost("/api/dynamicquery/ListAgentExecutionsQueryItem",
|
|
async (HttpContext context,
|
|
IQueryableProvider<ListAgentExecutionsQueryItem> provider,
|
|
IAsyncQueryableService queryService) =>
|
|
{
|
|
var query = await context.Request.ReadFromJsonAsync<object>();
|
|
var queryable = await provider.GetQueryableAsync(query!);
|
|
var result = await queryService.ExecuteAsync(queryable, query!);
|
|
return Results.Ok(result);
|
|
})
|
|
.WithName("ListAgentExecutions")
|
|
.WithTags("DynamicQuery")
|
|
.WithOpenApi(operation => new(operation)
|
|
{
|
|
Summary = "List agent executions with filtering, sorting, and pagination",
|
|
Description = "Returns paginated execution history with tokens, costs, and status",
|
|
Responses = new OpenApiResponses
|
|
{
|
|
["200"] = new OpenApiResponse
|
|
{
|
|
Description = "Paginated list of executions",
|
|
Content = new Dictionary<string, OpenApiMediaType>
|
|
{
|
|
["application/json"] = new OpenApiMediaType
|
|
{
|
|
Schema = new OpenApiSchema
|
|
{
|
|
Type = "object",
|
|
Properties = new Dictionary<string, OpenApiSchema>
|
|
{
|
|
["data"] = new OpenApiSchema
|
|
{
|
|
Type = "array",
|
|
Items = new OpenApiSchema
|
|
{
|
|
Reference = new OpenApiReference
|
|
{
|
|
Type = ReferenceType.Schema,
|
|
Id = nameof(ListAgentExecutionsQueryItem)
|
|
}
|
|
}
|
|
},
|
|
["totalCount"] = new OpenApiSchema { Type = "integer" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.Produces<object>(200);
|
|
*/
|
|
|
|
return app;
|
|
}
|
|
}
|