diff --git a/BACKEND/Codex.Api/Endpoints/ManualEndpointRegistration.cs b/BACKEND/Codex.Api/Endpoints/ManualEndpointRegistration.cs index 2ea7c8a..483954b 100644 --- a/BACKEND/Codex.Api/Endpoints/ManualEndpointRegistration.cs +++ b/BACKEND/Codex.Api/Endpoints/ManualEndpointRegistration.cs @@ -12,245 +12,39 @@ using PoweredSoft.DynamicQuery.Core; namespace Codex.Api.Endpoints; /// -/// Manual endpoint registration to ensure proper OpenAPI documentation for all CQRS endpoints. -/// Required because OpenHarbor.CQRS doesn't auto-generate Swagger docs for commands with return values and dynamic queries. +/// 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. /// public static class ManualEndpointRegistration { public static WebApplication MapCodexEndpoints(this WebApplication app) { // ============================================================ - // COMMANDS + // COMMANDS - AUTO-REGISTERED BY OPENHARBOR.CQRS // ============================================================ - - // CreateAgent - No return value (already auto-documented by OpenHarbor) - // UpdateAgent - No return value (already auto-documented by OpenHarbor) - // DeleteAgent - No return value (already auto-documented by OpenHarbor) - - // CreateConversation - Returns Guid - app.MapPost("/api/command/createConversation", - async ([FromBody] CreateConversationCommand command, - ICommandHandler handler) => - { - var result = await handler.HandleAsync(command); - return Results.Ok(new { id = result }); - }) - .WithName("CreateConversation") - .WithTags("Commands") - .WithOpenApi(operation => new(operation) - { - Summary = "Creates a new conversation for grouping related messages", - Description = "Returns the newly created conversation ID", - RequestBody = new OpenApiRequestBody - { - Required = true, - Content = new Dictionary - { - ["application/json"] = new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = nameof(CreateConversationCommand) - } - } - } - } - }, - Responses = new OpenApiResponses - { - ["200"] = new OpenApiResponse - { - Description = "Conversation created successfully", - Content = new Dictionary - { - ["application/json"] = new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Type = "object", - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "string", - Format = "uuid", - Description = "The unique identifier of the created conversation" - } - } - } - } - } - }, - ["400"] = new OpenApiResponse { Description = "Validation failed" }, - ["500"] = new OpenApiResponse { Description = "Internal server error" } - } - }) - .Produces(200) - .ProducesValidationProblem(); - - // StartAgentExecution - Returns Guid - app.MapPost("/api/command/startAgentExecution", - async ([FromBody] StartAgentExecutionCommand command, - ICommandHandler handler) => - { - var result = await handler.HandleAsync(command); - return Results.Ok(new { id = result }); - }) - .WithName("StartAgentExecution") - .WithTags("Commands") - .WithOpenApi(operation => new(operation) - { - Summary = "Starts a new agent execution with a user prompt", - Description = "Creates an execution record and returns its ID. Use this to track agent runs.", - Responses = new OpenApiResponses - { - ["200"] = new OpenApiResponse - { - Description = "Execution started successfully", - Content = new Dictionary - { - ["application/json"] = new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Type = "object", - Properties = new Dictionary - { - ["id"] = new OpenApiSchema - { - Type = "string", - Format = "uuid", - Description = "The unique identifier of the execution" - } - } - } - } - } - }, - ["400"] = new OpenApiResponse { Description = "Validation failed" }, - ["404"] = new OpenApiResponse { Description = "Agent not found" }, - ["500"] = new OpenApiResponse { Description = "Internal server error" } - } - }) - .Produces(200) - .ProducesValidationProblem(); - - // CompleteAgentExecution - No return value - app.MapPost("/api/command/completeAgentExecution", - async ([FromBody] CompleteAgentExecutionCommand command, - ICommandHandler handler) => - { - await handler.HandleAsync(command); - return Results.Ok(); - }) - .WithName("CompleteAgentExecution") - .WithTags("Commands") - .WithOpenApi(operation => new(operation) - { - Summary = "Marks an agent execution as completed with results", - Description = "Updates execution status, tokens used, and stores the response", - Responses = new OpenApiResponses - { - ["200"] = new OpenApiResponse { Description = "Execution completed successfully" }, - ["400"] = new OpenApiResponse { Description = "Validation failed" }, - ["404"] = new OpenApiResponse { Description = "Execution not found" }, - ["500"] = new OpenApiResponse { Description = "Internal server error" } - } - }) - .Produces(200) - .ProducesValidationProblem(); + // 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 + // QUERIES - AUTO-REGISTERED BY OPENHARBOR.CQRS // ============================================================ - - // Health - Already auto-documented - // GetAgent - Already auto-documented - - // GetAgentExecution - app.MapPost("/api/query/getAgentExecution", - async ([FromBody] GetAgentExecutionQuery query, - IQueryHandler handler) => - { - var result = await handler.HandleAsync(query); - return result != null ? Results.Ok(result) : Results.NotFound(); - }) - .WithName("GetAgentExecution") - .WithTags("Queries") - .WithOpenApi(operation => new(operation) - { - Summary = "Get details of a specific agent execution by ID", - Description = "Returns execution details including tokens, cost, messages, and status", - Responses = new OpenApiResponses - { - ["200"] = new OpenApiResponse - { - Description = "Execution details retrieved successfully", - Content = new Dictionary - { - ["application/json"] = new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = nameof(AgentExecutionDetails) - } - } - } - } - }, - ["404"] = new OpenApiResponse { Description = "Execution not found" }, - ["500"] = new OpenApiResponse { Description = "Internal server error" } - } - }) - .Produces(200) - .Produces(404); - - // GetConversation - app.MapPost("/api/query/getConversation", - async ([FromBody] GetConversationQuery query, - IQueryHandler handler) => - { - var result = await handler.HandleAsync(query); - return result != null ? Results.Ok(result) : Results.NotFound(); - }) - .WithName("GetConversation") - .WithTags("Queries") - .WithOpenApi(operation => new(operation) - { - Summary = "Get details of a specific conversation by ID", - Description = "Returns conversation details including messages and execution history", - Responses = new OpenApiResponses - { - ["200"] = new OpenApiResponse - { - Description = "Conversation details retrieved successfully", - Content = new Dictionary - { - ["application/json"] = new OpenApiMediaType - { - Schema = new OpenApiSchema - { - Reference = new OpenApiReference - { - Type = ReferenceType.Schema, - Id = nameof(ConversationDetails) - } - } - } - } - }, - ["404"] = new OpenApiResponse { Description = "Conversation not found" }, - ["500"] = new OpenApiResponse { Description = "Internal server error" } - } - }) - .Produces(200) - .Produces(404); + // 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) diff --git a/BACKEND/docs/openapi.json b/BACKEND/docs/openapi.json index 7e9168d..5fe7b10 100644 --- a/BACKEND/docs/openapi.json +++ b/BACKEND/docs/openapi.json @@ -6,6 +6,139 @@ "version": "v1" }, "paths": { + "/api/agents": { + "get": { + "tags": [ + "Agents" + ], + "summary": "Get all agents", + "description": "Returns a list of all active agents with metadata. Limit: 100 most recent.", + "operationId": "GetAllAgents", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { } + } + } + } + } + } + }, + "/api/agents/{id}/conversations": { + "get": { + "tags": [ + "Agents" + ], + "summary": "Get conversations for an agent", + "description": "Returns all conversations associated with a specific agent.", + "operationId": "GetAgentConversations", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { } + } + } + } + } + } + }, + "/api/agents/{id}/executions": { + "get": { + "tags": [ + "Agents" + ], + "summary": "Get execution history for an agent", + "description": "Returns the 100 most recent executions for a specific agent.", + "operationId": "GetAgentExecutions", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { } + } + } + } + } + } + }, + "/api/command/completeAgentExecution": { + "post": { + "tags": [ + "completeAgentExecution" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CompleteAgentExecutionCommand" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CompleteAgentExecutionCommand" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CompleteAgentExecutionCommand" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/conversations": { + "get": { + "tags": [ + "Conversations" + ], + "summary": "Get all conversations", + "description": "Returns the 100 most recent conversations.", + "operationId": "GetAllConversations", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { } + } + } + } + } + } + }, "/api/command/createAgent": { "post": { "tags": [ @@ -37,6 +170,45 @@ } } }, + "/api/command/createConversation": { + "post": { + "tags": [ + "createConversation" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateConversationCommand" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CreateConversationCommand" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CreateConversationCommand" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string", + "format": "uuid" + } + } + } + } + } + } + }, "/api/command/deleteAgent": { "post": { "tags": [ @@ -68,6 +240,59 @@ } } }, + "/api/executions": { + "get": { + "tags": [ + "Executions" + ], + "summary": "Get all executions", + "description": "Returns the 100 most recent executions across all agents.", + "operationId": "GetAllExecutions", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { } + } + } + } + } + } + }, + "/api/executions/status/{status}": { + "get": { + "tags": [ + "Executions" + ], + "summary": "Get executions by status", + "description": "Returns executions filtered by status (Pending, Running, Completed, Failed, Cancelled).", + "operationId": "GetExecutionsByStatus", + "parameters": [ + { + "name": "status", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { } + } + } + }, + "400": { + "description": "Bad Request" + } + } + } + }, "/api/query/getAgent": { "post": { "tags": [ @@ -134,6 +359,138 @@ } } }, + "/api/query/getAgentExecution": { + "post": { + "tags": [ + "getAgentExecution" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAgentExecutionQuery" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/GetAgentExecutionQuery" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/GetAgentExecutionQuery" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentExecutionDetails" + } + } + } + } + } + }, + "get": { + "tags": [ + "getAgentExecution" + ], + "parameters": [ + { + "name": "Id", + "in": "query", + "description": "Execution ID", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AgentExecutionDetails" + } + } + } + } + } + } + }, + "/api/query/getConversation": { + "post": { + "tags": [ + "getConversation" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetConversationQuery" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/GetConversationQuery" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/GetConversationQuery" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConversationDetails" + } + } + } + } + } + }, + "get": { + "tags": [ + "getConversation" + ], + "parameters": [ + { + "name": "Id", + "in": "query", + "description": "Conversation ID", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConversationDetails" + } + } + } + } + } + } + }, "/api/query/health": { "post": { "tags": [ @@ -198,6 +555,45 @@ } } }, + "/api/command/startAgentExecution": { + "post": { + "tags": [ + "startAgentExecution" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StartAgentExecutionCommand" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/StartAgentExecutionCommand" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/StartAgentExecutionCommand" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "string", + "format": "uuid" + } + } + } + } + } + } + }, "/api/command/updateAgent": { "post": { "tags": [ @@ -232,6 +628,108 @@ }, "components": { "schemas": { + "AgentExecutionDetails": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique execution identifier", + "format": "uuid" + }, + "agentId": { + "type": "string", + "description": "Agent identifier", + "format": "uuid" + }, + "agentName": { + "type": "string", + "description": "Agent name", + "nullable": true + }, + "conversationId": { + "type": "string", + "description": "Conversation identifier if part of a conversation", + "format": "uuid", + "nullable": true + }, + "userPrompt": { + "type": "string", + "description": "Full user prompt", + "nullable": true + }, + "input": { + "type": "string", + "description": "Additional input context or parameters", + "nullable": true + }, + "output": { + "type": "string", + "description": "Agent's complete output/response", + "nullable": true + }, + "status": { + "$ref": "#/components/schemas/ExecutionStatus" + }, + "startedAt": { + "type": "string", + "description": "Execution start timestamp", + "format": "date-time" + }, + "completedAt": { + "type": "string", + "description": "Execution completion timestamp", + "format": "date-time", + "nullable": true + }, + "executionTimeMs": { + "type": "integer", + "description": "Execution time in milliseconds", + "format": "int64", + "nullable": true + }, + "inputTokens": { + "type": "integer", + "description": "Input tokens consumed", + "format": "int32", + "nullable": true + }, + "outputTokens": { + "type": "integer", + "description": "Output tokens generated", + "format": "int32", + "nullable": true + }, + "totalTokens": { + "type": "integer", + "description": "Total tokens used", + "format": "int32", + "nullable": true + }, + "estimatedCost": { + "type": "number", + "description": "Estimated cost in USD", + "format": "double", + "nullable": true + }, + "toolCalls": { + "type": "string", + "description": "Tool calls made during execution (JSON array)", + "nullable": true + }, + "toolCallResults": { + "type": "string", + "description": "Tool execution results (JSON array)", + "nullable": true + }, + "errorMessage": { + "type": "string", + "description": "Error message if execution failed", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Detailed agent execution information" + }, "AgentStatus": { "enum": [ "Active", @@ -252,6 +750,153 @@ "type": "string", "description": "Specifies the type/purpose of the agent." }, + "CompleteAgentExecutionCommand": { + "type": "object", + "properties": { + "executionId": { + "type": "string", + "description": "Execution ID to complete", + "format": "uuid" + }, + "output": { + "type": "string", + "description": "Agent's output/response", + "nullable": true + }, + "status": { + "$ref": "#/components/schemas/ExecutionStatus" + }, + "inputTokens": { + "type": "integer", + "description": "Input tokens consumed", + "format": "int32", + "nullable": true + }, + "outputTokens": { + "type": "integer", + "description": "Output tokens generated", + "format": "int32", + "nullable": true + }, + "estimatedCost": { + "type": "number", + "description": "Estimated cost in USD", + "format": "double", + "nullable": true + }, + "toolCalls": { + "type": "string", + "description": "Tool calls made (JSON array)", + "nullable": true + }, + "toolCallResults": { + "type": "string", + "description": "Tool call results (JSON array)", + "nullable": true + }, + "errorMessage": { + "type": "string", + "description": "Error message if failed", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Completes an agent execution with results and metrics" + }, + "ConversationDetails": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Unique conversation identifier", + "format": "uuid" + }, + "title": { + "type": "string", + "description": "Conversation title", + "nullable": true + }, + "summary": { + "type": "string", + "description": "Conversation summary", + "nullable": true + }, + "isActive": { + "type": "boolean", + "description": "Whether conversation is active" + }, + "startedAt": { + "type": "string", + "description": "Conversation start timestamp", + "format": "date-time" + }, + "lastMessageAt": { + "type": "string", + "description": "Last message timestamp", + "format": "date-time" + }, + "messageCount": { + "type": "integer", + "description": "Total message count", + "format": "int32" + }, + "messages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConversationMessageItem" + }, + "description": "All messages in conversation", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Detailed conversation information with messages" + }, + "ConversationMessageItem": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Message identifier", + "format": "uuid" + }, + "conversationId": { + "type": "string", + "description": "Conversation identifier", + "format": "uuid" + }, + "executionId": { + "type": "string", + "description": "Execution identifier if from agent execution", + "format": "uuid", + "nullable": true + }, + "role": { + "$ref": "#/components/schemas/MessageRole" + }, + "content": { + "type": "string", + "description": "Message content", + "nullable": true + }, + "messageIndex": { + "type": "integer", + "description": "Message index/order in conversation", + "format": "int32" + }, + "isInActiveWindow": { + "type": "boolean", + "description": "Whether message is in active context window" + }, + "createdAt": { + "type": "string", + "description": "Message creation timestamp", + "format": "date-time" + } + }, + "additionalProperties": false, + "description": "Individual message within a conversation" + }, "CreateAgentCommand": { "type": "object", "properties": { @@ -319,6 +964,23 @@ "additionalProperties": false, "description": "Command to create a new AI agent with configuration" }, + "CreateConversationCommand": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Conversation title", + "nullable": true + }, + "summary": { + "type": "string", + "description": "Optional summary or description", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Creates a new conversation for grouping related messages" + }, "DeleteAgentCommand": { "type": "object", "properties": { @@ -331,6 +993,28 @@ "additionalProperties": false, "description": "Command to soft-delete an agent" }, + "ExecutionStatus": { + "enum": [ + "Running", + "Completed", + "Failed", + "Cancelled" + ], + "type": "string", + "description": "Represents the status of an agent execution." + }, + "GetAgentExecutionQuery": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Execution ID", + "format": "uuid" + } + }, + "additionalProperties": false, + "description": "Get detailed agent execution by ID" + }, "GetAgentQuery": { "type": "object", "properties": { @@ -410,11 +1094,33 @@ "additionalProperties": false, "description": "Response containing agent details" }, + "GetConversationQuery": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Conversation ID", + "format": "uuid" + } + }, + "additionalProperties": false, + "description": "Get conversation with all messages by ID" + }, "HealthQuery": { "type": "object", "additionalProperties": false, "description": "Health check query to verify API availability" }, + "MessageRole": { + "enum": [ + "User", + "Assistant", + "System", + "Tool" + ], + "type": "string", + "description": "Represents the role of a message in a conversation." + }, "ModelProviderType": { "enum": [ "CloudApi", @@ -424,6 +1130,34 @@ "type": "string", "description": "Specifies the type of model provider (cloud API or local endpoint)." }, + "StartAgentExecutionCommand": { + "type": "object", + "properties": { + "agentId": { + "type": "string", + "description": "Agent ID to execute", + "format": "uuid" + }, + "userPrompt": { + "type": "string", + "description": "User's input prompt", + "nullable": true + }, + "conversationId": { + "type": "string", + "description": "Optional conversation ID to link execution to", + "format": "uuid", + "nullable": true + }, + "input": { + "type": "string", + "description": "Optional additional input context (JSON)", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Starts a new agent execution" + }, "UpdateAgentCommand": { "type": "object", "properties": {