diff --git a/README.md b/README.md
index 4a30ecb..2b12c1b 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
[](https://github.com/goodtocode/agent-framework-quick-start/actions/workflows/gtc-agent-standalone-web-api-sql.yml)
-Microsoft Agent Framework Quick-start is a 100% Microsoft, enterprise-ready starter kit for building modern, agentic applications with C#, Blazor (Fluent UI), and ASP.NET Core Web API. This solution demonstrates how to use the Microsoft Agent Framework to create a Copilot-style chat client, fully integrated with SQL Server for persistent storage of authors, chat sessions, and messages—all orchestrated through a clean architecture pattern.
+Microsoft Agent Framework Quick-start is a enterprise-ready starter kit for building modern, agentic applications with C#, Blazor (Fluent UI), and ASP.NET Core Web API. This solution demonstrates how to use the Microsoft Agent Framework to create a Copilot-style chat client, fully integrated with SQL Server for persistent storage of authors, chat sessions, and messages—all orchestrated through a clean architecture pattern.
With built-in tools (plugins) for querying and managing your own data, automated Azure infrastructure (Bicep), and seamless CI/CD (GitHub Actions), this repo provides everything you need to build, deploy, and extend real-world AI-powered apps on a traditional .NET stack—no JavaScript, no raw HTML, just pure Blazor and Fluent UI. Perfect for teams looking to modernize with AI while leveraging familiar, pragmatic enterprise patterns.
@@ -257,26 +257,15 @@ dotnet user-secrets set "ConnectionStrings:DefaultConnection" "YOUR_SQL_CONNECTI
1. Open Windows Terminal in Powershell or Cmd mode
2. cd to root of repository
-3. (Optional) If you have an existing database, scaffold current entities into your project
-
- ```
- dotnet ef dbcontext scaffold "Data Source=localhost;Initial Catalog=AgentFramework;Min Pool Size=3;MultipleActiveResultSets=True;Trusted_Connection=Yes;TrustServerCertificate=True;" Microsoft.EntityFrameworkCore.SqlServer -t WeatherForecastView -c WeatherChannelContext -f -o WebApi
- ```
-
-4. Create an initial migration
- ```
- dotnet ef migrations add InitialCreate --project .\src\Infrastructure.SqlServer\Infrastructure.SqlServer.csproj --startup-project .\src\Presentation.WebApi\Presentation.WebApi.csproj --context AgentFrameworkContext
- ```
-
-5. Develop new entities and configurations
-6. When ready to deploy new entities and configurations
+3. Deploy new entities and configurations to database
```
dotnet ef database update --project .\src\Infrastructure.SqlServer\Infrastructure.SqlServer.csproj --startup-project .\src\Presentation.WebApi\Presentation.WebApi.csproj --context AgentFrameworkContext --connection "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=AgentFramework;Min Pool Size=3;MultipleActiveResultSets=True;Trusted_Connection=Yes;TrustServerCertificate=True;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30"
```
-7. When an entity changes, is created or deleted, create a new migration. Suggest doing this each new version.
+4. When an entity changes, is created or deleted, create a new migration. Suggest doing this each new version.
```
dotnet ef migrations add v1.1.1 --project .\src\Infrastructure.SqlServer\Infrastructure.SqlServer.csproj --startup-project .\src\Presentation.WebApi\Presentation.WebApi.csproj --context AgentFrameworkContext
+ dotnet ef database update --project .\src\Infrastructure.SqlServer\Infrastructure.SqlServer.csproj --startup-project .\src\Presentation.WebApi\Presentation.WebApi.csproj --context AgentFrameworkContext --connection "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=AgentFramework;Min Pool Size=3;MultipleActiveResultSets=True;Trusted_Connection=Yes;TrustServerCertificate=True;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30"
```
# Running the Application
@@ -286,9 +275,9 @@ Right-click Presentation.WebApi and select Set as Default Project
dotnet run --project src/Presentation.WebApi/Presentation.WebApi.csproj
```
-## Open http://localhost:7777/swagger/index.html
+## Open http://localhost:7175/swagger/index.html
Open Microsoft Edge or modern browser
-Navigate to: http://localhost:7777/swagger/index.html in your browser to the Swagger API Interface
+Navigate to: http://localhost:7175/swagger/index.html in your browser to the Swagger API Interface
# Github Actions for Azure IaC and CI/CD
## GitHub Actions (.github folder)
diff --git a/data/Chat/Actor.sql b/data/Chat/Actor.sql
new file mode 100644
index 0000000..58e32b1
--- /dev/null
+++ b/data/Chat/Actor.sql
@@ -0,0 +1,2 @@
+Select top 1000 *
+From Actors
\ No newline at end of file
diff --git a/data/Chat/ChatSessions.sql b/data/Chat/ChatSessions.sql
index e69de29..74e55ca 100644
--- a/data/Chat/ChatSessions.sql
+++ b/data/Chat/ChatSessions.sql
@@ -0,0 +1,2 @@
+Select Top 1000 *
+From ChatSessions
\ No newline at end of file
diff --git a/src/.github/copilot-instructions.md b/src/.github/copilot-instructions.md
new file mode 100644
index 0000000..35edb6d
--- /dev/null
+++ b/src/.github/copilot-instructions.md
@@ -0,0 +1,4 @@
+# Copilot Instructions
+
+## General Guidelines
+- Use custom .NotEmpty("message") validation convention instead of .NotEmpty().WithMessage("message") in FluentValidation validators. The Goodtocode.Validation library extends FluentValidation with custom syntax where validation methods accept message as a parameter: .NotEmpty("message"), .NotEqual(value, "message"), etc. Do not use .WithMessage() - pass the message directly as a parameter to the validation method.
\ No newline at end of file
diff --git a/src/Core.Application/Abstractions/ICurrentUserContext.cs b/src/Core.Application/Abstractions/ICurrentUserContext.cs
new file mode 100644
index 0000000..e22974e
--- /dev/null
+++ b/src/Core.Application/Abstractions/ICurrentUserContext.cs
@@ -0,0 +1,7 @@
+namespace Goodtocode.AgentFramework.Core.Application.Abstractions;
+
+public interface ICurrentUserContext
+{
+ Guid OwnerId { get; }
+ Guid TenantId { get; }
+}
\ No newline at end of file
diff --git a/src/Core.Application/Abstractions/IRequiresUserContext.cs b/src/Core.Application/Abstractions/IRequiresUserContext.cs
new file mode 100644
index 0000000..f7d031d
--- /dev/null
+++ b/src/Core.Application/Abstractions/IRequiresUserContext.cs
@@ -0,0 +1,20 @@
+using Goodtocode.AgentFramework.Core.Domain.Auth;
+
+namespace Goodtocode.AgentFramework.Core.Application.Abstractions;
+
+///
+/// Marker interface for requests that require user context to be injected via pipeline behavior.
+///
+/// This interface is used to identify requests that need authenticated user information.
+/// When a request implements this interface, the UserInfoBehavior pipeline will automatically
+/// populate the property with the current user's context before
+/// the request handler executes.
+public interface IRequiresUserContext
+{
+ ///
+ /// Gets or sets the authenticated user's context.
+ ///
+ /// This property is automatically populated by the pipeline behavior
+ /// before the request handler is invoked.
+ IUserContext? UserContext { get; set; }
+}
diff --git a/src/Core.Application/Abstractions/IUserInfoRequest.cs b/src/Core.Application/Abstractions/IUserInfoRequest.cs
deleted file mode 100644
index ffb011f..0000000
--- a/src/Core.Application/Abstractions/IUserInfoRequest.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Goodtocode.AgentFramework.Core.Domain.Auth;
-
-namespace Goodtocode.AgentFramework.Core.Application.Abstractions;
-
-///
-/// Represents a request containing user information.
-///
-/// This interface is used to encapsulate user information in a request. The property allows getting or setting the associated user details.
-public interface IUserInfoRequest
-{
- ///
- /// Gets or sets the user information associated with the current context.
- ///
- IUserEntity? UserInfo { get; set; }
-}
diff --git a/src/Core.Application/Actor/CreateActorCommand.cs b/src/Core.Application/Actor/CreateActorCommand.cs
index 947c171..de69dc6 100644
--- a/src/Core.Application/Actor/CreateActorCommand.cs
+++ b/src/Core.Application/Actor/CreateActorCommand.cs
@@ -14,7 +14,7 @@ public class CreateActorCommand : IRequest
public Guid TenantId { get; set; }
}
-public class CreateAuthorCommandHandler(IAgentFrameworkContext context) : IRequestHandler
+public class CreateActorCommandHandler(IAgentFrameworkContext context) : IRequestHandler
{
private readonly IAgentFrameworkContext _context = context;
@@ -23,7 +23,7 @@ public async Task Handle(CreateActorCommand request, CancellationToken
GuardAgainstEmptyOwnerId(request?.OwnerId);
GuardAgainstIdExists(_context.Actors, request!.Id);
- var Actor = ActorEntity.Create(request!.Id == Guid.Empty ? Guid.NewGuid() : request!.Id, request.OwnerId, request.TenantId, request.FirstName, request.LastName, request.Email);
+ var Actor = ActorEntity.Create(request!.Id == Guid.Empty ? Guid.NewGuid() : request!.Id, request.FirstName, request.LastName, request.Email);
_context.Actors.Add(Actor);
try
{
diff --git a/src/Core.Application/Actor/DeleteActorByExternalIdCommand.cs b/src/Core.Application/Actor/DeleteActorByExternalIdCommand.cs
index c42fe6f..024a12a 100644
--- a/src/Core.Application/Actor/DeleteActorByExternalIdCommand.cs
+++ b/src/Core.Application/Actor/DeleteActorByExternalIdCommand.cs
@@ -9,7 +9,7 @@ public class DeleteActorByOwnerIdCommand : IRequest
public Guid OwnerId { get; set; }
}
-public class DeleteAuthorByOwnerIdCommandHandler(IAgentFrameworkContext context) : IRequestHandler
+public class DeleteActorByOwnerIdCommandHandler(IAgentFrameworkContext context) : IRequestHandler
{
private readonly IAgentFrameworkContext _context = context;
diff --git a/src/Core.Application/Actor/DeleteActorCommand.cs b/src/Core.Application/Actor/DeleteActorCommand.cs
index bcfafa8..57c6714 100644
--- a/src/Core.Application/Actor/DeleteActorCommand.cs
+++ b/src/Core.Application/Actor/DeleteActorCommand.cs
@@ -9,7 +9,7 @@ public class DeleteActorCommand : IRequest
public Guid Id { get; set; }
}
-public class DeleteAuthorCommandHandler(IAgentFrameworkContext context) : IRequestHandler
+public class DeleteActorCommandHandler(IAgentFrameworkContext context) : IRequestHandler
{
private readonly IAgentFrameworkContext _context = context;
diff --git a/src/Core.Application/Actor/GetActorChatSessionQuery.cs b/src/Core.Application/Actor/GetActorChatSessionQuery.cs
index ac3ae26..f0885a2 100644
--- a/src/Core.Application/Actor/GetActorChatSessionQuery.cs
+++ b/src/Core.Application/Actor/GetActorChatSessionQuery.cs
@@ -11,7 +11,7 @@ public class GetActorChatSessionQuery : IRequest
public Guid ChatSessionId { get; set; }
}
-public class GetAuthorChatSessionQueryHandler(IAgentFrameworkContext context) : IRequestHandler
+public class GetActorChatSessionQueryHandler(IAgentFrameworkContext context) : IRequestHandler
{
private readonly IAgentFrameworkContext _context = context;
diff --git a/src/Core.Application/Actor/GetActorChatSessionsPaginatedQuery.cs b/src/Core.Application/Actor/GetActorChatSessionsPaginatedQuery.cs
index b39c80d..15ce0db 100644
--- a/src/Core.Application/Actor/GetActorChatSessionsPaginatedQuery.cs
+++ b/src/Core.Application/Actor/GetActorChatSessionsPaginatedQuery.cs
@@ -14,7 +14,7 @@ public class GetActorChatSessionsPaginatedQuery : IRequest>
+public class GetActorChatSessionsPaginatedQueryHandler(IAgentFrameworkContext context) : IRequestHandler>
{
private readonly IAgentFrameworkContext _context = context;
diff --git a/src/Core.Application/Actor/GetActorChatSessionsQuery.cs b/src/Core.Application/Actor/GetActorChatSessionsQuery.cs
index b424343..20cf0ae 100644
--- a/src/Core.Application/Actor/GetActorChatSessionsQuery.cs
+++ b/src/Core.Application/Actor/GetActorChatSessionsQuery.cs
@@ -10,7 +10,7 @@ public class GetActorChatSessionsQuery : IRequest>
public DateTime? EndDate { get; set; }
}
-public class GetAuthorChatSessionsQueryHandler(IAgentFrameworkContext context) : IRequestHandler>
+public class GetActorChatSessionsQueryHandler(IAgentFrameworkContext context) : IRequestHandler>
{
private readonly IAgentFrameworkContext _context = context;
diff --git a/src/Core.Application/Actor/GetActorQuery.cs b/src/Core.Application/Actor/GetActorQuery.cs
index 0ef2316..98596a2 100644
--- a/src/Core.Application/Actor/GetActorQuery.cs
+++ b/src/Core.Application/Actor/GetActorQuery.cs
@@ -9,7 +9,7 @@ public class GetActorQuery : IRequest
public Guid ActorId { get; set; }
}
-public class GetAuthorQueryHandler(IAgentFrameworkContext context) : IRequestHandler
+public class GetActorQueryHandler(IAgentFrameworkContext context) : IRequestHandler
{
private readonly IAgentFrameworkContext _context = context;
diff --git a/src/Core.Application/Actor/GetMyActorQuery.cs b/src/Core.Application/Actor/GetMyActorQuery.cs
index d152ea2..058e17b 100644
--- a/src/Core.Application/Actor/GetMyActorQuery.cs
+++ b/src/Core.Application/Actor/GetMyActorQuery.cs
@@ -5,18 +5,18 @@
namespace Goodtocode.AgentFramework.Core.Application.Actor;
-public class GetMyActorQuery : IRequest, IUserInfoRequest
+public class GetMyActorQuery : IRequest, IRequiresUserContext
{
- public IUserEntity? UserInfo { get; set; }
+ public IUserContext? UserContext { get; set; }
}
-public class GetAuthorByOwnerIdQueryHandler(IAgentFrameworkContext context) : IRequestHandler
+public class GetActorByOwnerIdQueryHandler(IAgentFrameworkContext context) : IRequestHandler
{
private readonly IAgentFrameworkContext _context = context;
public async Task Handle(GetMyActorQuery request, CancellationToken cancellationToken)
{
- var actor = await _context.Actors.Where(x => x.OwnerId == request!.UserInfo!.OwnerId).FirstOrDefaultAsync(cancellationToken: cancellationToken);
+ var actor = await _context.Actors.Where(x => x.OwnerId == request!.UserContext!.OwnerId).FirstOrDefaultAsync(cancellationToken: cancellationToken);
GuardAgainstNotFound(actor);
return ActorDto.CreateFrom(actor);
diff --git a/src/Core.Application/Actor/GetMyActorQueryValidator.cs b/src/Core.Application/Actor/GetMyActorQueryValidator.cs
index 65469c3..53c6117 100644
--- a/src/Core.Application/Actor/GetMyActorQueryValidator.cs
+++ b/src/Core.Application/Actor/GetMyActorQueryValidator.cs
@@ -4,6 +4,6 @@ public class GetMyActorQueryValidator : Validator
{
public GetMyActorQueryValidator()
{
- RuleFor(x => x.UserInfo).NotEmpty();
+ RuleFor(x => x.UserContext).NotEmpty();
}
}
\ No newline at end of file
diff --git a/src/Core.Application/Actor/SaveMyActorCommand.cs b/src/Core.Application/Actor/SaveMyActorCommand.cs
index 3ad8303..0cd0937 100644
--- a/src/Core.Application/Actor/SaveMyActorCommand.cs
+++ b/src/Core.Application/Actor/SaveMyActorCommand.cs
@@ -4,16 +4,16 @@
namespace Goodtocode.AgentFramework.Core.Application.Actor;
-public class SaveMyActorCommand : IRequest, IUserInfoRequest
+public class SaveMyActorCommand : IRequest, IRequiresUserContext
{
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string? Email { get; set; }
public Guid TenantId { get; set; }
- public IUserEntity? UserInfo { get; set; }
+ public IUserContext? UserContext { get; set; }
}
-public class SaveAuthorCommandHandler(IAgentFrameworkContext context) : IRequestHandler
+public class SaveActorCommandHandler(IAgentFrameworkContext context) : IRequestHandler
{
private readonly IAgentFrameworkContext _context = context;
@@ -21,7 +21,7 @@ public async Task Handle(SaveMyActorCommand request, CancellationToken
{
GuardAgainstEmptyTenantId(request?.TenantId);
- var actor = await _context.Actors.Where(x => x.OwnerId == request!.UserInfo!.OwnerId && x.TenantId == request.TenantId).FirstOrDefaultAsync(cancellationToken);
+ var actor = await _context.Actors.Where(x => x.OwnerId == request!.UserContext!.OwnerId && x.TenantId == request.TenantId).FirstOrDefaultAsync(cancellationToken);
if (actor is not null)
{
actor.Update(request?.FirstName, request?.LastName ?? actor.LastName, request?.Email);
@@ -29,7 +29,7 @@ public async Task Handle(SaveMyActorCommand request, CancellationToken
}
else
{
- actor = ActorEntity.Create(Guid.NewGuid(), request!.UserInfo!.OwnerId, request.TenantId, request.FirstName, request.LastName, request.Email);
+ actor = ActorEntity.Create(Guid.NewGuid(), request?.FirstName, request?.LastName, request?.Email);
_context.Actors.Add(actor);
}
diff --git a/src/Core.Application/Actor/SaveMyActorCommandValidator.cs b/src/Core.Application/Actor/SaveMyActorCommandValidator.cs
index 955ad78..2d19fb9 100644
--- a/src/Core.Application/Actor/SaveMyActorCommandValidator.cs
+++ b/src/Core.Application/Actor/SaveMyActorCommandValidator.cs
@@ -4,7 +4,7 @@ public class SaveMyActorCommandValidator : Validator
{
public SaveMyActorCommandValidator()
{
- RuleFor(x => x.UserInfo).NotEmpty();
+ RuleFor(x => x.UserContext).NotEmpty();
RuleFor(x => x.TenantId).NotEmpty();
}
}
\ No newline at end of file
diff --git a/src/Core.Application/Actor/UpdateActorCommand.cs b/src/Core.Application/Actor/UpdateActorCommand.cs
index e3866ee..3f745c5 100644
--- a/src/Core.Application/Actor/UpdateActorCommand.cs
+++ b/src/Core.Application/Actor/UpdateActorCommand.cs
@@ -10,7 +10,7 @@ public class UpdateActorCommand : IRequest
public string Name { get; set; } = string.Empty;
}
-public class UpdateAuthorCommandHandler(IAgentFrameworkContext context) : IRequestHandler
+public class UpdateActorCommandHandler(IAgentFrameworkContext context) : IRequestHandler
{
private readonly IAgentFrameworkContext _context = context;
diff --git a/src/Core.Application/ChatCompletion/CreateChatMessageCommandValidator.cs b/src/Core.Application/ChatCompletion/CreateChatMessageCommandValidator.cs
deleted file mode 100644
index 0e4ec20..0000000
--- a/src/Core.Application/ChatCompletion/CreateChatMessageCommandValidator.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class CreateChatMessageCommandValidator : Validator
-{
- public CreateChatMessageCommandValidator()
- {
- RuleFor(x => x.ChatSessionId).NotEmpty();
- RuleFor(x => x.Message).NotEmpty();
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/CreateChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/CreateChatSessionCommandValidator.cs
deleted file mode 100644
index 5763664..0000000
--- a/src/Core.Application/ChatCompletion/CreateChatSessionCommandValidator.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class CreateChatSessionCommandValidator : Validator
-{
- public CreateChatSessionCommandValidator()
- {
- RuleFor(x => x.Message).NotEmpty();
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/CreateChatMessageCommand.cs b/src/Core.Application/ChatCompletion/CreateMyChatMessageCommand.cs
similarity index 65%
rename from src/Core.Application/ChatCompletion/CreateChatMessageCommand.cs
rename to src/Core.Application/ChatCompletion/CreateMyChatMessageCommand.cs
index d75107a..c2d2730 100644
--- a/src/Core.Application/ChatCompletion/CreateChatMessageCommand.cs
+++ b/src/Core.Application/ChatCompletion/CreateMyChatMessageCommand.cs
@@ -7,28 +7,28 @@
namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-public class CreateChatMessageCommand : IRequest, IUserInfoRequest
+public class CreateMyChatMessageCommand : IRequest, IRequiresUserContext
{
public Guid Id { get; set; }
public Guid ChatSessionId { get; set; }
public string? Message { get; set; }
- public IUserEntity? UserInfo { get; set; }
+ public IUserContext? UserContext { get; set; }
}
-public class CreateChatMessageCommandHandler(AIAgent agent, IAgentFrameworkContext context) : IRequestHandler
+public class CreateChatMessageCommandHandler(AIAgent agent, IAgentFrameworkContext context) : IRequestHandler
{
private readonly AIAgent _agent = agent;
private readonly IAgentFrameworkContext _context = context;
- public async Task Handle(CreateChatMessageCommand request, CancellationToken cancellationToken)
+ public async Task Handle(CreateMyChatMessageCommand request, CancellationToken cancellationToken)
{
- GuardAgainstSessionNotFound(_context.ChatSessions, request!.ChatSessionId);
GuardAgainstEmptyMessage(request?.Message);
GuardAgainstIdExists(_context.ChatMessages, request!.Id);
- GuardAgainstEmptyUser(request?.UserInfo);
- GuardAgainstUnauthorizedUser(_context.ChatSessions, request!.UserInfo!);
+ GuardAgainstEmptyUser(request?.UserContext);
- var chatSession = _context.ChatSessions.Find(request.ChatSessionId);
+ var chatSession = _context.ChatSessions.Find(request!.ChatSessionId);
+ GuardAgainstSessionNotFound(chatSession);
+ GuardAgainstUnauthorizedUser(chatSession!, request.UserContext!);
var chatHistory = new List();
foreach (ChatMessageEntity message in chatSession!.Messages)
@@ -69,13 +69,10 @@ public async Task Handle(CreateChatMessageCommand request, Cance
return ChatMessageDto.CreateFrom(chatMessage);
}
- private static void GuardAgainstSessionNotFound(DbSet dbSet, Guid sessionId)
+ private static void GuardAgainstSessionNotFound(ChatSessionEntity? chatSession)
{
- if (sessionId != Guid.Empty && !dbSet.Any(x => x.Id == sessionId))
- throw new CustomValidationException(
- [
- new("ChatSessionId", "Chat Session does not exist")
- ]);
+ if (chatSession == null)
+ throw new CustomNotFoundException("Chat Session Not Found");
}
private static void GuardAgainstEmptyMessage(string? message)
@@ -93,23 +90,19 @@ private static void GuardAgainstIdExists(DbSet dbSet, Guid id
throw new CustomConflictException("Id already exists");
}
- private static void GuardAgainstEmptyUser(IUserEntity? userInfo)
+ private static void GuardAgainstEmptyUser(IUserContext? userContext)
{
- if (userInfo == null || userInfo.OwnerId == Guid.Empty || userInfo.TenantId == Guid.Empty)
+ if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty)
throw new CustomValidationException(
[
- new("UserInfo", "User information is required to create a chat message")
+ new("UserContext", "User information is required to create a chat message")
]);
}
- private static void GuardAgainstUnauthorizedUser(DbSet dbSet, IUserEntity userInfo)
+ private static void GuardAgainstUnauthorizedUser(ChatSessionEntity chatSession, IUserContext userContext)
{
- bool isAuthorized = dbSet.Any(x => x.Actor != null && x.Actor.OwnerId == userInfo.OwnerId);
- if (!isAuthorized)
- throw new CustomValidationException(
- [
- new("UserInfo", "User is not authorized to create a chat message in this session")
- ]);
+ if (chatSession.OwnerId != userContext.OwnerId)
+ throw new CustomForbiddenAccessException("ChatSession", chatSession.Id);
}
private static void GuardAgainstNullAgentResponse(ChatMessage? response)
diff --git a/src/Core.Application/ChatCompletion/CreateMyChatMessageCommandValidator.cs b/src/Core.Application/ChatCompletion/CreateMyChatMessageCommandValidator.cs
new file mode 100644
index 0000000..e4eba49
--- /dev/null
+++ b/src/Core.Application/ChatCompletion/CreateMyChatMessageCommandValidator.cs
@@ -0,0 +1,24 @@
+namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
+
+public class CreateMyChatMessageCommandValidator : Validator
+{
+ public CreateMyChatMessageCommandValidator()
+ {
+ RuleFor(x => x.ChatSessionId)
+ .NotEmpty("ChatSessionId is required");
+
+ RuleFor(x => x.Message)
+ .NotEmpty("Message is required");
+
+ RuleFor(x => x.UserContext)
+ .NotEmpty("User information is required");
+
+ RuleFor(x => x.UserContext!.OwnerId)
+ .NotEmpty("OwnerId is required")
+ .When(x => x.UserContext != null);
+
+ RuleFor(x => x.UserContext!.TenantId)
+ .NotEmpty("TenantId is required")
+ .When(x => x.UserContext != null);
+ }
+}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/CreateChatSessionCommand.cs b/src/Core.Application/ChatCompletion/CreateMyChatSessionCommand.cs
similarity index 61%
rename from src/Core.Application/ChatCompletion/CreateChatSessionCommand.cs
rename to src/Core.Application/ChatCompletion/CreateMyChatSessionCommand.cs
index 4b7efcb..72e6580 100644
--- a/src/Core.Application/ChatCompletion/CreateChatSessionCommand.cs
+++ b/src/Core.Application/ChatCompletion/CreateMyChatSessionCommand.cs
@@ -1,34 +1,53 @@
using Goodtocode.AgentFramework.Core.Application.Abstractions;
using Goodtocode.AgentFramework.Core.Application.Common.Exceptions;
using Goodtocode.AgentFramework.Core.Domain.Actor;
+using Goodtocode.AgentFramework.Core.Domain.Auth;
using Goodtocode.AgentFramework.Core.Domain.ChatCompletion;
+using Goodtocode.Domain.Entities;
using Microsoft.Agents.AI;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.AI;
namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-public class CreateChatSessionCommand : IRequest
+public class CreateMyChatSessionCommand : IRequest, IRequiresUserContext
{
public Guid Id { get; set; }
- public Guid ActorId { get; set; }
public string? Title { get; set; }
public string? Message { get; set; }
+ public IUserContext? UserContext { get; set; }
}
-public class CreateChatSessionCommandHandler(AIAgent kernel, IAgentFrameworkContext context) : IRequestHandler
+public class CreateChatSessionCommandHandler(AIAgent kernel, IAgentFrameworkContext context) : IRequestHandler
{
private readonly AIAgent _agent = kernel;
private readonly IAgentFrameworkContext _context = context;
- public async Task Handle(CreateChatSessionCommand request, CancellationToken cancellationToken)
+ public async Task Handle(CreateMyChatSessionCommand request, CancellationToken cancellationToken)
{
- GuardAgainstEmtpyActorId(request.ActorId);
GuardAgainstEmptyMessage(request?.Message);
GuardAgainstIdExists(_context.ChatSessions, request!.Id);
+ GuardAgainstEmptyUser(request?.UserContext);
+
+ var actor = await _context.Actors
+ .FirstOrDefaultAsync(a => a.OwnerId == request!.UserContext!.OwnerId
+ && a.TenantId == request.UserContext.TenantId, cancellationToken);
+
+ if (actor == null)
+ {
+ actor = ActorEntity.Create(
+ Guid.NewGuid(),
+ request?.UserContext?.FirstName,
+ request?.UserContext?.LastName,
+ request?.UserContext?.Email
+ );
+ _context.Actors.Add(actor);
+ await _context.SaveChangesAsync(cancellationToken);
+ }
var chatHistory = new List
{
- new(ChatRole.User, request.Message!)
+ new(ChatRole.User, request!.Message!)
};
var agentResponse = await _agent.RunAsync(chatHistory, cancellationToken: cancellationToken);
@@ -36,14 +55,10 @@ public async Task Handle(CreateChatSessionCommand request, Cance
GuardAgainstNullAgentResponse(response);
- var actor = await _context.Actors
- .FirstOrDefaultAsync(x => x.OwnerId == request.ActorId, cancellationToken);
- GuardAgainstActorNotFound(actor);
-
var title = request!.Title ?? $"{request!.Message![..(request.Message!.Length >= 25 ? 25 : request.Message!.Length)]}";
var chatSession = ChatSessionEntity.Create(
request.Id,
- actor!.Id,
+ actor.Id,
title,
Enum.TryParse(response!.Role.ToString().ToLowerInvariant(), out var role) ? role : ChatMessageRole.assistant,
request.Message!,
@@ -58,19 +73,7 @@ public async Task Handle(CreateChatSessionCommand request, Cance
private static void GuardAgainstActorNotFound(ActorEntity? actor)
{
if (actor == null)
- throw new CustomValidationException(
- [
- new("ActorId", "ActorId required for sessions")
- ]);
- }
-
- private static void GuardAgainstEmtpyActorId(Guid actorId)
- {
- if (actorId == Guid.Empty)
- throw new CustomValidationException(
- [
- new("ActorId", "ActorId required for sessions")
- ]);
+ throw new CustomNotFoundException("Actor Not Found");
}
private static void GuardAgainstEmptyMessage(string? message)
@@ -88,6 +91,21 @@ private static void GuardAgainstIdExists(DbSet dbSet, Guid id
throw new CustomConflictException("Id already exists");
}
+ private static void GuardAgainstEmptyUser(IUserContext? userContext)
+ {
+ if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty)
+ throw new CustomValidationException(
+ [
+ new("UserInfo", "User information is required to create a chat session")
+ ]);
+ }
+
+ private static void GuardAgainstUnauthorizedUser(ActorEntity actor, IUserContext userContext)
+ {
+ if (actor.OwnerId != userContext.OwnerId)
+ throw new CustomForbiddenAccessException("Actor", actor.Id);
+ }
+
private static void GuardAgainstNullAgentResponse(object? response)
{
if (response == null)
diff --git a/src/Core.Application/ChatCompletion/CreateMyChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/CreateMyChatSessionCommandValidator.cs
new file mode 100644
index 0000000..047aad8
--- /dev/null
+++ b/src/Core.Application/ChatCompletion/CreateMyChatSessionCommandValidator.cs
@@ -0,0 +1,21 @@
+namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
+
+public class CreateMyChatSessionCommandValidator : Validator
+{
+ public CreateMyChatSessionCommandValidator()
+ {
+ RuleFor(x => x.Message)
+ .NotEmpty("Message is required");
+
+ RuleFor(x => x.UserContext)
+ .NotEmpty("User information is required");
+
+ RuleFor(x => x.UserContext!.OwnerId)
+ .NotEmpty("OwnerId is required")
+ .When(x => x.UserContext != null);
+
+ RuleFor(x => x.UserContext!.TenantId)
+ .NotEmpty("TenantId is required")
+ .When(x => x.UserContext != null);
+ }
+}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/DeleteChatSessionCommand.cs b/src/Core.Application/ChatCompletion/DeleteChatSessionCommand.cs
deleted file mode 100644
index ebdc4fb..0000000
--- a/src/Core.Application/ChatCompletion/DeleteChatSessionCommand.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Goodtocode.AgentFramework.Core.Application.Abstractions;
-using Goodtocode.AgentFramework.Core.Application.Common.Exceptions;
-using Goodtocode.AgentFramework.Core.Domain.ChatCompletion;
-
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class DeleteChatSessionCommand : IRequest
-{
- public Guid Id { get; set; }
-}
-
-public class DeleteChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler
-{
- private readonly IAgentFrameworkContext _context = context;
-
- public async Task Handle(DeleteChatSessionCommand request, CancellationToken cancellationToken)
- {
- var chatSession = _context.ChatSessions.Find(request.Id);
- GuardAgainstNotFound(chatSession);
-
- _context.ChatSessions.Remove(chatSession!);
- await _context.SaveChangesAsync(cancellationToken);
- }
-
- private static void GuardAgainstNotFound(ChatSessionEntity? chatSession)
- {
- if (chatSession == null)
- throw new CustomNotFoundException("Chat Session Not Found");
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/DeleteChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/DeleteChatSessionCommandValidator.cs
deleted file mode 100644
index 844d1c2..0000000
--- a/src/Core.Application/ChatCompletion/DeleteChatSessionCommandValidator.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class DeleteChatSessionCommandValidator : Validator
-{
- public DeleteChatSessionCommandValidator()
- {
- RuleFor(x => x.Id).NotEmpty();
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/DeleteMyChatSessionCommand.cs b/src/Core.Application/ChatCompletion/DeleteMyChatSessionCommand.cs
new file mode 100644
index 0000000..3dba1ca
--- /dev/null
+++ b/src/Core.Application/ChatCompletion/DeleteMyChatSessionCommand.cs
@@ -0,0 +1,50 @@
+using Goodtocode.AgentFramework.Core.Application.Abstractions;
+using Goodtocode.AgentFramework.Core.Application.Common.Exceptions;
+using Goodtocode.AgentFramework.Core.Domain.Auth;
+using Goodtocode.AgentFramework.Core.Domain.ChatCompletion;
+
+namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
+
+public class DeleteMyChatSessionCommand : IRequest, IRequiresUserContext
+{
+ public Guid Id { get; set; }
+ public IUserContext? UserContext { get; set; }
+}
+
+public class DeleteChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler
+{
+ private readonly IAgentFrameworkContext _context = context;
+
+ public async Task Handle(DeleteMyChatSessionCommand request, CancellationToken cancellationToken)
+ {
+ GuardAgainstEmptyUser(request?.UserContext);
+
+ var chatSession = _context.ChatSessions.Find(request!.Id);
+ GuardAgainstNotFound(chatSession);
+ GuardAgainstUnauthorized(chatSession!, request.UserContext!);
+
+ _context.ChatSessions.Remove(chatSession!);
+ await _context.SaveChangesAsync(cancellationToken);
+ }
+
+ private static void GuardAgainstNotFound(ChatSessionEntity? chatSession)
+ {
+ if (chatSession == null)
+ throw new CustomNotFoundException("Chat Session Not Found");
+ }
+
+ private static void GuardAgainstEmptyUser(IUserContext? userContext)
+ {
+ if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty)
+ throw new CustomValidationException(
+ [
+ new("UserContext", "User information is required to delete a chat session")
+ ]);
+ }
+
+ private static void GuardAgainstUnauthorized(ChatSessionEntity chatSession, IUserContext userContext)
+ {
+ if (chatSession.OwnerId != userContext.OwnerId)
+ throw new CustomForbiddenAccessException("ChatSession", chatSession.Id);
+ }
+}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/DeleteMyChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/DeleteMyChatSessionCommandValidator.cs
new file mode 100644
index 0000000..a2d7b9e
--- /dev/null
+++ b/src/Core.Application/ChatCompletion/DeleteMyChatSessionCommandValidator.cs
@@ -0,0 +1,21 @@
+namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
+
+public class DeleteMyChatSessionCommandValidator : Validator
+{
+ public DeleteMyChatSessionCommandValidator()
+ {
+ RuleFor(x => x.Id)
+ .NotEmpty("Id is required");
+
+ RuleFor(x => x.UserContext)
+ .NotEmpty("User information is required");
+
+ RuleFor(x => x.UserContext!.OwnerId)
+ .NotEmpty("OwnerId is required")
+ .When(x => x.UserContext != null);
+
+ RuleFor(x => x.UserContext!.TenantId)
+ .NotEmpty("TenantId is required")
+ .When(x => x.UserContext != null);
+ }
+}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetChatMessageQuery.cs b/src/Core.Application/ChatCompletion/GetChatMessageQuery.cs
deleted file mode 100644
index dd659d0..0000000
--- a/src/Core.Application/ChatCompletion/GetChatMessageQuery.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Goodtocode.AgentFramework.Core.Application.Abstractions;
-using Goodtocode.AgentFramework.Core.Application.Common.Exceptions;
-using Goodtocode.AgentFramework.Core.Domain.ChatCompletion;
-
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class GetChatMessageQuery : IRequest
-{
- public Guid Id { get; set; }
-}
-
-public class GetChatMessageQueryHandler(IAgentFrameworkContext context) : IRequestHandler
-{
- private readonly IAgentFrameworkContext _context = context;
-
- public async Task Handle(GetChatMessageQuery request,
- CancellationToken cancellationToken)
- {
- var chatMessage = await _context.ChatMessages.FindAsync([request.Id, cancellationToken], cancellationToken: cancellationToken);
- GuardAgainstNotFound(chatMessage);
-
- return ChatMessageDto.CreateFrom(chatMessage);
- }
-
- private static void GuardAgainstNotFound(ChatMessageEntity? chatMessage)
- {
- if (chatMessage == null)
- throw new CustomNotFoundException("Chat Message Not Found");
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetChatMessageQueryValidator.cs b/src/Core.Application/ChatCompletion/GetChatMessageQueryValidator.cs
deleted file mode 100644
index 9129e09..0000000
--- a/src/Core.Application/ChatCompletion/GetChatMessageQueryValidator.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class GetChatMessageQueryValidator : Validator
-{
- public GetChatMessageQueryValidator()
- {
- RuleFor(x => x.Id).NotEmpty();
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQuery.cs b/src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQuery.cs
deleted file mode 100644
index c8ea763..0000000
--- a/src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQuery.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Goodtocode.AgentFramework.Core.Application.Abstractions;
-using Goodtocode.AgentFramework.Core.Application.Common.Mappings;
-using Goodtocode.AgentFramework.Core.Application.Common.Models;
-
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class GetChatMessagesPaginatedQuery : IRequest>
-{
- public DateTime? StartDate { get; set; }
- public DateTime? EndDate { get; set; }
- public int PageNumber { get; init; } = 1;
- public int PageSize { get; init; } = 10;
-}
-
-public class GetChatMessagesPaginatedQueryHandler(IAgentFrameworkContext context) : IRequestHandler>
-{
- private readonly IAgentFrameworkContext _context = context;
-
- public async Task> Handle(GetChatMessagesPaginatedQuery request, CancellationToken cancellationToken)
- {
- var returnData = await _context.ChatMessages
- .OrderByDescending(x => x.Timestamp)
- .Where(x => (request.StartDate == null || x.Timestamp > request.StartDate)
- && (request.EndDate == null || x.Timestamp < request.EndDate))
- .Select(x => ChatMessageDto.CreateFrom(x))
- .PaginatedListAsync(request.PageNumber, request.PageSize);
-
- return returnData;
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQueryValidator.cs b/src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQueryValidator.cs
deleted file mode 100644
index 3070658..0000000
--- a/src/Core.Application/ChatCompletion/GetChatMessagesPaginatedQueryValidator.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class GetChatMessagesPaginatedQueryValidator : Validator
-{
- public GetChatMessagesPaginatedQueryValidator()
- {
- RuleFor(v => v.StartDate).NotEmpty()
- .When(v => v.EndDate != null)
- .LessThanOrEqualTo(v => v.EndDate);
-
- RuleFor(v => v.EndDate)
- .NotEmpty()
- .When(v => v.StartDate != null)
- .GreaterThanOrEqualTo(v => v.StartDate);
-
- RuleFor(x => x.PageNumber).NotEqual(0);
-
- RuleFor(x => x.PageSize).NotEqual(0);
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetChatMessagesQuery.cs b/src/Core.Application/ChatCompletion/GetChatMessagesQuery.cs
deleted file mode 100644
index 9579430..0000000
--- a/src/Core.Application/ChatCompletion/GetChatMessagesQuery.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Goodtocode.AgentFramework.Core.Application.Abstractions;
-
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class GetChatMessagesQuery : IRequest>
-{
- public DateTime? StartDate { get; set; }
- public DateTime? EndDate { get; set; }
-}
-
-public class GetChatMessagesQueryHandler(IAgentFrameworkContext context) : IRequestHandler>
-{
- private readonly IAgentFrameworkContext _context = context;
-
- public async Task> Handle(GetChatMessagesQuery request, CancellationToken cancellationToken)
- {
- var returnData = await _context.ChatMessages
- .OrderByDescending(x => x.Timestamp)
- .Where(x => (request.StartDate == null || x.Timestamp > request.StartDate)
- && (request.EndDate == null || x.Timestamp < request.EndDate))
- .Select(x => ChatMessageDto.CreateFrom(x))
- .ToListAsync(cancellationToken);
-
- return returnData;
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetChatMessagesQueryValidator.cs b/src/Core.Application/ChatCompletion/GetChatMessagesQueryValidator.cs
deleted file mode 100644
index 7b54b41..0000000
--- a/src/Core.Application/ChatCompletion/GetChatMessagesQueryValidator.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class GetChatMessagesQueryValidator : Validator
-{
- public GetChatMessagesQueryValidator()
- {
- RuleFor(v => v.StartDate).NotEmpty()
- .When(v => v.EndDate != null)
- .LessThanOrEqualTo(v => v.EndDate);
-
- RuleFor(v => v.EndDate)
- .NotEmpty()
- .When(v => v.StartDate != null)
- .GreaterThanOrEqualTo(v => v.StartDate);
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetChatSessionQuery.cs b/src/Core.Application/ChatCompletion/GetChatSessionQuery.cs
deleted file mode 100644
index ed2e271..0000000
--- a/src/Core.Application/ChatCompletion/GetChatSessionQuery.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Goodtocode.AgentFramework.Core.Application.Abstractions;
-using Goodtocode.AgentFramework.Core.Application.Common.Exceptions;
-using Goodtocode.AgentFramework.Core.Domain.ChatCompletion;
-
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class GetChatSessionQuery : IRequest
-{
- public Guid Id { get; set; }
-}
-
-public class GetChatSessionQueryHandler(IAgentFrameworkContext context) : IRequestHandler
-{
- private readonly IAgentFrameworkContext _context = context;
-
- public async Task Handle(GetChatSessionQuery request,
- CancellationToken cancellationToken)
- {
- var chatSession = await _context.ChatSessions.FindAsync([request.Id, cancellationToken], cancellationToken: cancellationToken);
- GuardAgainstNotFound(chatSession);
-
- return ChatSessionDto.CreateFrom(chatSession);
- }
-
- private static void GuardAgainstNotFound(ChatSessionEntity? chatSession)
- {
- if (chatSession == null)
- throw new CustomNotFoundException("Chat Session Not Found");
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetChatSessionQueryValidator.cs b/src/Core.Application/ChatCompletion/GetChatSessionQueryValidator.cs
deleted file mode 100644
index 964ae49..0000000
--- a/src/Core.Application/ChatCompletion/GetChatSessionQueryValidator.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class GetChatSessionQueryValidator : Validator
-{
- public GetChatSessionQueryValidator()
- {
- RuleFor(x => x.Id).NotEmpty();
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQuery.cs b/src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQuery.cs
deleted file mode 100644
index a98b117..0000000
--- a/src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQuery.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Goodtocode.AgentFramework.Core.Application.Abstractions;
-using Goodtocode.AgentFramework.Core.Application.Common.Mappings;
-using Goodtocode.AgentFramework.Core.Application.Common.Models;
-
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class GetChatSessionsPaginatedQuery : IRequest>
-{
- public DateTime? StartDate { get; set; }
- public DateTime? EndDate { get; set; }
- public int PageNumber { get; init; } = 1;
- public int PageSize { get; init; } = 10;
-}
-
-public class GetChatSessionsPaginatedQueryHandler(IAgentFrameworkContext context) : IRequestHandler>
-{
- private readonly IAgentFrameworkContext _context = context;
-
- public async Task> Handle(GetChatSessionsPaginatedQuery request, CancellationToken cancellationToken)
- {
- var returnData = await _context.ChatSessions
- .OrderByDescending(x => x.Timestamp)
- .Where(x => (request.StartDate == null || x.Timestamp > request.StartDate)
- && (request.EndDate == null || x.Timestamp < request.EndDate))
- .Select(x => ChatSessionDto.CreateFrom(x))
- .PaginatedListAsync(request.PageNumber, request.PageSize);
-
- return returnData;
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQueryValidator.cs b/src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQueryValidator.cs
deleted file mode 100644
index 605b382..0000000
--- a/src/Core.Application/ChatCompletion/GetChatSessionsPaginatedQueryValidator.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class GetChatSessionsPaginatedQueryValidator : Validator
-{
- public GetChatSessionsPaginatedQueryValidator()
- {
- RuleFor(v => v.StartDate).NotEmpty()
- .When(v => v.EndDate != null)
- .LessThanOrEqualTo(v => v.EndDate);
-
- RuleFor(v => v.EndDate)
- .NotEmpty()
- .When(v => v.StartDate != null)
- .GreaterThanOrEqualTo(v => v.StartDate);
-
- RuleFor(x => x.PageNumber).NotEqual(0);
-
- RuleFor(x => x.PageSize).NotEqual(0);
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetChatSessionsQuery.cs b/src/Core.Application/ChatCompletion/GetChatSessionsQuery.cs
deleted file mode 100644
index c8f0636..0000000
--- a/src/Core.Application/ChatCompletion/GetChatSessionsQuery.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Goodtocode.AgentFramework.Core.Application.Abstractions;
-
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class GetChatSessionsQuery : IRequest>
-{
- public DateTime? StartDate { get; set; }
- public DateTime? EndDate { get; set; }
-}
-
-public class GetChatSessionsQueryHandler(IAgentFrameworkContext context) : IRequestHandler>
-{
- private readonly IAgentFrameworkContext _context = context;
-
- public async Task> Handle(GetChatSessionsQuery request, CancellationToken cancellationToken)
- {
- var returnData = await _context.ChatSessions
- .OrderByDescending(x => x.Timestamp)
- .Where(x => (request.StartDate == null || x.Timestamp > request.StartDate)
- && (request.EndDate == null || x.Timestamp < request.EndDate))
- .Select(x => ChatSessionDto.CreateFrom(x))
- .ToListAsync(cancellationToken);
-
- return returnData;
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetChatSessionsQueryValidator.cs b/src/Core.Application/ChatCompletion/GetChatSessionsQueryValidator.cs
deleted file mode 100644
index b0f50ff..0000000
--- a/src/Core.Application/ChatCompletion/GetChatSessionsQueryValidator.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class GetChatSessionsQueryValidator : Validator
-{
- public GetChatSessionsQueryValidator()
- {
- RuleFor(v => v.StartDate).NotEmpty()
- .When(v => v.EndDate != null)
- .LessThanOrEqualTo(v => v.EndDate);
-
- RuleFor(v => v.EndDate)
- .NotEmpty()
- .When(v => v.StartDate != null)
- .GreaterThanOrEqualTo(v => v.StartDate);
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetMyChatMessageQuery.cs b/src/Core.Application/ChatCompletion/GetMyChatMessageQuery.cs
new file mode 100644
index 0000000..7efbcec
--- /dev/null
+++ b/src/Core.Application/ChatCompletion/GetMyChatMessageQuery.cs
@@ -0,0 +1,50 @@
+using Goodtocode.AgentFramework.Core.Application.Abstractions;
+using Goodtocode.AgentFramework.Core.Application.Common.Exceptions;
+using Goodtocode.AgentFramework.Core.Domain.Auth;
+using Goodtocode.AgentFramework.Core.Domain.ChatCompletion;
+
+namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
+
+public class GetMyChatMessageQuery : IRequest, IRequiresUserContext
+{
+ public Guid Id { get; set; }
+ public IUserContext? UserContext { get; set; }
+}
+
+public class GetChatMessageQueryHandler(IAgentFrameworkContext context) : IRequestHandler
+{
+ private readonly IAgentFrameworkContext _context = context;
+
+ public async Task Handle(GetMyChatMessageQuery request,
+ CancellationToken cancellationToken)
+ {
+ GuardAgainstEmptyUser(request?.UserContext);
+
+ var chatMessage = await _context.ChatMessages.FindAsync([request!.Id, cancellationToken], cancellationToken: cancellationToken);
+ GuardAgainstNotFound(chatMessage);
+ GuardAgainstUnauthorized(chatMessage!, request.UserContext!);
+
+ return ChatMessageDto.CreateFrom(chatMessage);
+ }
+
+ private static void GuardAgainstNotFound(ChatMessageEntity? chatMessage)
+ {
+ if (chatMessage == null)
+ throw new CustomNotFoundException("Chat Message Not Found");
+ }
+
+ private static void GuardAgainstEmptyUser(IUserContext? userContext)
+ {
+ if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty)
+ throw new CustomValidationException(
+ [
+ new("UserInfo", "User information is required to retrieve a chat message")
+ ]);
+ }
+
+ private static void GuardAgainstUnauthorized(ChatMessageEntity chatMessage, IUserContext userInfo)
+ {
+ if (chatMessage.ChatSession?.OwnerId != userInfo.OwnerId)
+ throw new CustomForbiddenAccessException("ChatMessage", chatMessage.Id);
+ }
+}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetMyChatMessageQueryValidator.cs b/src/Core.Application/ChatCompletion/GetMyChatMessageQueryValidator.cs
new file mode 100644
index 0000000..b125777
--- /dev/null
+++ b/src/Core.Application/ChatCompletion/GetMyChatMessageQueryValidator.cs
@@ -0,0 +1,21 @@
+namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
+
+public class GetMyChatMessageQueryValidator : Validator
+{
+ public GetMyChatMessageQueryValidator()
+ {
+ RuleFor(x => x.Id)
+ .NotEmpty("Id is required");
+
+ RuleFor(x => x.UserContext)
+ .NotEmpty("User information is required");
+
+ RuleFor(x => x.UserContext!.OwnerId)
+ .NotEmpty("OwnerId is required")
+ .When(x => x.UserContext != null);
+
+ RuleFor(x => x.UserContext!.TenantId)
+ .NotEmpty("TenantId is required")
+ .When(x => x.UserContext != null);
+ }
+}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQuery.cs b/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQuery.cs
new file mode 100644
index 0000000..877e4c8
--- /dev/null
+++ b/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQuery.cs
@@ -0,0 +1,46 @@
+using Goodtocode.AgentFramework.Core.Application.Abstractions;
+using Goodtocode.AgentFramework.Core.Application.Common.Mappings;
+using Goodtocode.AgentFramework.Core.Application.Common.Models;
+using Goodtocode.AgentFramework.Core.Domain.Auth;
+
+namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
+
+public class GetMyChatMessagesPaginatedQuery : IRequest>, IRequiresUserContext
+{
+ public DateTime? StartDate { get; set; }
+ public DateTime? EndDate { get; set; }
+ public int PageNumber { get; init; } = 1;
+ public int PageSize { get; init; } = 10;
+ public IUserContext? UserContext { get; set; }
+}
+
+public class GetChatMessagesPaginatedQueryHandler(IAgentFrameworkContext context) : IRequestHandler>
+{
+ private readonly IAgentFrameworkContext _context = context;
+
+ public async Task> Handle(GetMyChatMessagesPaginatedQuery request, CancellationToken cancellationToken)
+ {
+ GuardAgainstEmptyUser(request?.UserContext);
+
+ var userContext = request!.UserContext!;
+
+ var returnData = await _context.ChatMessages
+ .Where(x => x.ChatSession != null && x.ChatSession.OwnerId == userContext.OwnerId)
+ .OrderByDescending(x => x.Timestamp)
+ .Where(x => (request.StartDate == null || x.Timestamp > request.StartDate.Value)
+ && (request.EndDate == null || x.Timestamp < request.EndDate.Value))
+ .Select(x => ChatMessageDto.CreateFrom(x))
+ .PaginatedListAsync(request.PageNumber, request.PageSize);
+
+ return returnData;
+ }
+
+ private static void GuardAgainstEmptyUser(IUserContext? userContext)
+ {
+ if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty)
+ throw new CustomValidationException(
+ [
+ new("UserInfo", "User information is required to retrieve chat messages")
+ ]);
+ }
+}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQueryValidator.cs b/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQueryValidator.cs
new file mode 100644
index 0000000..e7b7d1c
--- /dev/null
+++ b/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQueryValidator.cs
@@ -0,0 +1,34 @@
+namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
+
+public class GetMyChatMessagesPaginatedQueryValidator : Validator
+{
+ public GetMyChatMessagesPaginatedQueryValidator()
+ {
+ RuleFor(v => v.StartDate)
+ .NotEmpty("StartDate is required when EndDate is provided")
+ .When(v => v.EndDate != null)
+ .LessThanOrEqualTo(v => v.EndDate);
+
+ RuleFor(v => v.EndDate)
+ .NotEmpty("EndDate is required when StartDate is provided")
+ .When(v => v.StartDate != null)
+ .GreaterThanOrEqualTo(v => v.StartDate);
+
+ RuleFor(x => x.PageNumber)
+ .NotEqual(0, "PageNumber must be greater than 0");
+
+ RuleFor(x => x.PageSize)
+ .NotEqual(0, "PageSize must be greater than 0");
+
+ RuleFor(x => x.UserContext)
+ .NotEmpty("User information is required");
+
+ RuleFor(x => x.UserContext!.OwnerId)
+ .NotEmpty("OwnerId is required")
+ .When(x => x.UserContext != null);
+
+ RuleFor(x => x.UserContext!.TenantId)
+ .NotEmpty("TenantId is required")
+ .When(x => x.UserContext != null);
+ }
+}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetMyChatSessionQuery.cs b/src/Core.Application/ChatCompletion/GetMyChatSessionQuery.cs
index e8470bb..7f6a5ed 100644
--- a/src/Core.Application/ChatCompletion/GetMyChatSessionQuery.cs
+++ b/src/Core.Application/ChatCompletion/GetMyChatSessionQuery.cs
@@ -5,31 +5,25 @@
namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-public class GetMyChatSessionQuery : IRequest, IUserInfoRequest
+public class GetMyChatSessionQuery : IRequest, IRequiresUserContext
{
- public Guid ChatSessionId { get; set; }
- public IUserEntity? UserInfo { get; set; }
+ public Guid Id { get; set; }
+ public IUserContext? UserContext { get; set; }
}
-public class GetAuthorChatSessionByOwnerIdQueryHandler(IAgentFrameworkContext context) : IRequestHandler
+public class GetMyChatSessionQueryHandler(IAgentFrameworkContext context) : IRequestHandler
{
private readonly IAgentFrameworkContext _context = context;
public async Task Handle(GetMyChatSessionQuery request, CancellationToken cancellationToken)
{
- var returnData = await _context.ChatSessions
- .Where(cs => cs.Id == request.ChatSessionId)
- .Join(_context.Actors,
- cs => cs.ActorId,
- a => a.Id,
- (cs, a) => new { ChatSession = cs, Actor = a })
- .Where(joined => joined.Actor.OwnerId == request.UserInfo!.OwnerId)
- .Select(joined => joined.ChatSession)
- .FirstOrDefaultAsync(cancellationToken);
-
- GuardAgainstNotFound(returnData);
-
- return ChatSessionDto.CreateFrom(returnData);
+ GuardAgainstEmptyUser(request?.UserContext);
+
+ var chatSession = await _context.ChatSessions.FindAsync([request!.Id], cancellationToken: cancellationToken);
+ GuardAgainstNotFound(chatSession);
+ GuardAgainstUnauthorized(chatSession!, request.UserContext!);
+
+ return ChatSessionDto.CreateFrom(chatSession);
}
private static void GuardAgainstNotFound(ChatSessionEntity? entity)
@@ -37,4 +31,19 @@ private static void GuardAgainstNotFound(ChatSessionEntity? entity)
if (entity is null)
throw new CustomNotFoundException("Chat Session Not Found");
}
+
+ private static void GuardAgainstEmptyUser(IUserContext? userContext)
+ {
+ if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty)
+ throw new CustomValidationException(
+ [
+ new("UserInfo", "User information is required to retrieve a chat session")
+ ]);
+ }
+
+ private static void GuardAgainstUnauthorized(ChatSessionEntity chatSession, IUserContext userInfo)
+ {
+ if (chatSession.OwnerId != userInfo.OwnerId)
+ throw new CustomForbiddenAccessException("ChatSession", chatSession.Id);
+ }
}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetMyChatSessionQueryValidator.cs b/src/Core.Application/ChatCompletion/GetMyChatSessionQueryValidator.cs
index 7352d7d..bce0538 100644
--- a/src/Core.Application/ChatCompletion/GetMyChatSessionQueryValidator.cs
+++ b/src/Core.Application/ChatCompletion/GetMyChatSessionQueryValidator.cs
@@ -4,7 +4,18 @@ public class GetMyChatSessionQueryValidator : Validator
{
public GetMyChatSessionQueryValidator()
{
- RuleFor(x => x.UserInfo).NotEmpty();
- RuleFor(x => x.ChatSessionId).NotEmpty();
+ RuleFor(x => x.Id)
+ .NotEmpty("ChatSessionId is required");
+
+ RuleFor(x => x.UserContext)
+ .NotEmpty("User information is required");
+
+ RuleFor(x => x.UserContext!.OwnerId)
+ .NotEmpty("OwnerId is required")
+ .When(x => x.UserContext != null);
+
+ RuleFor(x => x.UserContext!.TenantId)
+ .NotEmpty("TenantId is required")
+ .When(x => x.UserContext != null);
}
}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQuery.cs b/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQuery.cs
index 10c7754..502ac77 100644
--- a/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQuery.cs
+++ b/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQuery.cs
@@ -1,40 +1,47 @@
using Goodtocode.AgentFramework.Core.Application.Abstractions;
+using Goodtocode.AgentFramework.Core.Application.Common.Exceptions;
using Goodtocode.AgentFramework.Core.Application.Common.Mappings;
using Goodtocode.AgentFramework.Core.Application.Common.Models;
using Goodtocode.AgentFramework.Core.Domain.Auth;
namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-public class GetMyChatSessionsPaginatedQuery : IRequest>, IUserInfoRequest
+public class GetMyChatSessionsPaginatedQuery : IRequest>, IRequiresUserContext
{
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int PageNumber { get; init; } = 1;
public int PageSize { get; init; } = 10;
- public IUserEntity? UserInfo { get; set; }
+ public IUserContext? UserContext { get; set; }
}
-public class GetAuthorChatSessionsByExternalIdPaginatedQueryHandler(IAgentFrameworkContext context) : IRequestHandler>
+public class GetMyChatSessionsPaginatedQueryHandler(IAgentFrameworkContext context) : IRequestHandler>
{
private readonly IAgentFrameworkContext _context = context;
public async Task> Handle(GetMyChatSessionsPaginatedQuery request, CancellationToken cancellationToken)
{
+ GuardAgainstEmptyUser(request?.UserContext);
+ var ownerId = request!.UserContext!.OwnerId;
+
var returnData = await _context.ChatSessions
.Include(x => x.Messages)
+ .Where(x => x.OwnerId == ownerId)
.OrderByDescending(x => x.Timestamp)
.Where(x => (request.StartDate == null || x.Timestamp > request.StartDate)
&& (request.EndDate == null || x.Timestamp < request.EndDate))
- .Join(_context.Actors,
- cs => cs.ActorId,
- a => a.Id,
- (cs, a) => new { ChatSession = cs, Actor = a })
- .Where(joined => joined.Actor.OwnerId == request.UserInfo!.OwnerId)
- .Select(joined => joined.ChatSession)
- .Select(x => ChatSessionDto.CreateFrom(x))
+ .Select(x => ChatSessionDto.CreateFrom(x))
.PaginatedListAsync(request.PageNumber, request.PageSize);
return returnData;
+ }
+ private static void GuardAgainstEmptyUser(IUserContext? userContext)
+ {
+ if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty)
+ throw new CustomValidationException(
+ [
+ new("UserInfo", "User information is required to retrieve chat sessions")
+ ]);
}
}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQueryValidator.cs b/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQueryValidator.cs
index 981ce8a..132a6ff 100644
--- a/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQueryValidator.cs
+++ b/src/Core.Application/ChatCompletion/GetMyChatSessionsPaginatedQueryValidator.cs
@@ -4,19 +4,31 @@ public class GetMyChatSessionsPaginatedQueryValidator : Validator x.UserInfo).NotEmpty();
-
- RuleFor(v => v.StartDate).NotEmpty()
+ RuleFor(v => v.StartDate)
+ .NotEmpty("StartDate is required when EndDate is provided")
.When(v => v.EndDate != null)
.LessThanOrEqualTo(v => v.EndDate);
RuleFor(v => v.EndDate)
- .NotEmpty()
+ .NotEmpty("EndDate is required when StartDate is provided")
.When(v => v.StartDate != null)
.GreaterThanOrEqualTo(v => v.StartDate);
- RuleFor(x => x.PageNumber).NotEqual(0);
+ RuleFor(x => x.PageNumber)
+ .NotEqual(0, "PageNumber must be greater than 0");
+
+ RuleFor(x => x.PageSize)
+ .NotEqual(0, "PageSize must be greater than 0");
+
+ RuleFor(x => x.UserContext)
+ .NotEmpty("User information is required");
+
+ RuleFor(x => x.UserContext!.OwnerId)
+ .NotEmpty("OwnerId is required")
+ .When(x => x.UserContext != null);
- RuleFor(x => x.PageSize).NotEqual(0);
+ RuleFor(x => x.UserContext!.TenantId)
+ .NotEmpty("TenantId is required")
+ .When(x => x.UserContext != null);
}
}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetMyChatSessionsQuery.cs b/src/Core.Application/ChatCompletion/GetMyChatSessionsQuery.cs
index 2f221c1..064fa81 100644
--- a/src/Core.Application/ChatCompletion/GetMyChatSessionsQuery.cs
+++ b/src/Core.Application/ChatCompletion/GetMyChatSessionsQuery.cs
@@ -1,34 +1,45 @@
using Goodtocode.AgentFramework.Core.Application.Abstractions;
+using Goodtocode.AgentFramework.Core.Application.Common.Exceptions;
using Goodtocode.AgentFramework.Core.Domain.Auth;
namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-public class GetMyChatSessionsQuery : IRequest>, IUserInfoRequest
+public class GetMyChatSessionsQuery : IRequest>, IRequiresUserContext
{
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
- public IUserEntity? UserInfo { get; set; }
+ public IUserContext? UserContext { get; set; }
}
-public class GetAuthorChatSessionsByOwnerIdQueryHandler(IAgentFrameworkContext context) : IRequestHandler>
+public class GetMyChatSessionsQueryHandler(IAgentFrameworkContext context) : IRequestHandler>
{
private readonly IAgentFrameworkContext _context = context;
public async Task> Handle(GetMyChatSessionsQuery request, CancellationToken cancellationToken)
{
+ GuardAgainstEmptyUser(request?.UserContext);
+
+ var startDate = request?.StartDate;
+ var endDate = request?.EndDate;
+ var userContext = request?.UserContext;
+
var returnData = await _context.ChatSessions
+ .Where(x => userContext != null && x.OwnerId == userContext.OwnerId)
.OrderByDescending(x => x.Timestamp)
- .Where(x => (request.StartDate == null || x.Timestamp > request.StartDate)
- && (request.EndDate == null || x.Timestamp < request.EndDate))
- .Join(_context.Actors,
- cs => cs.ActorId,
- a => a.Id,
- (cs, a) => new { ChatSession = cs, Actor = a })
- .Where(joined => joined.Actor.OwnerId == request.UserInfo!.OwnerId)
- .Select(joined => joined.ChatSession)
+ .Where(x => (startDate == null || x.Timestamp > startDate)
+ && (endDate == null || x.Timestamp < endDate))
.Select(x => ChatSessionDto.CreateFrom(x))
.ToListAsync(cancellationToken);
return returnData;
}
+
+ private static void GuardAgainstEmptyUser(IUserContext? userContext)
+ {
+ if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty)
+ throw new CustomValidationException(
+ [
+ new("UserInfo", "User information is required to retrieve chat sessions")
+ ]);
+ }
}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/GetMyChatSessionsQueryValidator.cs b/src/Core.Application/ChatCompletion/GetMyChatSessionsQueryValidator.cs
index 9c30ea1..ef768c6 100644
--- a/src/Core.Application/ChatCompletion/GetMyChatSessionsQueryValidator.cs
+++ b/src/Core.Application/ChatCompletion/GetMyChatSessionsQueryValidator.cs
@@ -4,15 +4,25 @@ public class GetMyChatSessionsQueryValidator : Validator
{
public GetMyChatSessionsQueryValidator()
{
- RuleFor(x => x.UserInfo).NotEmpty();
-
- RuleFor(v => v.StartDate).NotEmpty()
+ RuleFor(v => v.StartDate)
+ .NotEmpty("StartDate is required when EndDate is provided")
.When(v => v.EndDate != null)
.LessThanOrEqualTo(v => v.EndDate);
RuleFor(v => v.EndDate)
- .NotEmpty()
+ .NotEmpty("EndDate is required when StartDate is provided")
.When(v => v.StartDate != null)
.GreaterThanOrEqualTo(v => v.StartDate);
+
+ RuleFor(x => x.UserContext)
+ .NotEmpty("User information is required");
+
+ RuleFor(x => x.UserContext!.OwnerId)
+ .NotEmpty("OwnerId is required")
+ .When(x => x.UserContext != null);
+
+ RuleFor(x => x.UserContext!.TenantId)
+ .NotEmpty("TenantId is required")
+ .When(x => x.UserContext != null);
}
}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/PatchChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/PatchChatSessionCommandValidator.cs
deleted file mode 100644
index aedb7bd..0000000
--- a/src/Core.Application/ChatCompletion/PatchChatSessionCommandValidator.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class PatchChatSessionCommandValidator : Validator
-{
- public PatchChatSessionCommandValidator()
- {
- RuleFor(x => x.Id).NotEmpty();
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/PatchChatSessionCommand.cs b/src/Core.Application/ChatCompletion/PatchMyChatSessionCommand.cs
similarity index 51%
rename from src/Core.Application/ChatCompletion/PatchChatSessionCommand.cs
rename to src/Core.Application/ChatCompletion/PatchMyChatSessionCommand.cs
index ebce817..0d39b3b 100644
--- a/src/Core.Application/ChatCompletion/PatchChatSessionCommand.cs
+++ b/src/Core.Application/ChatCompletion/PatchMyChatSessionCommand.cs
@@ -1,25 +1,29 @@
using Goodtocode.AgentFramework.Core.Application.Abstractions;
using Goodtocode.AgentFramework.Core.Application.Common.Exceptions;
+using Goodtocode.AgentFramework.Core.Domain.Auth;
using Goodtocode.AgentFramework.Core.Domain.ChatCompletion;
namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-public class PatchChatSessionCommand : IRequest
+public class PatchMyChatSessionCommand : IRequest, IRequiresUserContext
{
public Guid Id { get; set; }
public string Title { get; set; } = string.Empty;
+ public IUserContext? UserContext { get; set; }
}
-public class PatchChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler
+public class PatchChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler
{
private readonly IAgentFrameworkContext _context = context;
- public async Task Handle(PatchChatSessionCommand request, CancellationToken cancellationToken)
+ public async Task Handle(PatchMyChatSessionCommand request, CancellationToken cancellationToken)
{
+ GuardAgainstEmptyTitle(request.Title);
+ GuardAgainstEmptyUser(request?.UserContext);
- var chatSession = _context.ChatSessions.Find(request.Id);
+ var chatSession = _context.ChatSessions.Find(request!.Id);
GuardAgainstNotFound(chatSession);
- GuardAgainstEmptyTitle(request.Title);
+ GuardAgainstUnauthorized(chatSession!, request.UserContext!);
chatSession!.Update(request.Title);
@@ -41,4 +45,19 @@ private static void GuardAgainstEmptyTitle(string title)
new("Title", "Title cannot be empty")
]);
}
+
+ private static void GuardAgainstEmptyUser(IUserContext? userContext)
+ {
+ if (userContext == null || userContext.OwnerId == Guid.Empty || userContext.TenantId == Guid.Empty)
+ throw new CustomValidationException(
+ [
+ new("UserContext", "User information is required to update a chat session")
+ ]);
+ }
+
+ private static void GuardAgainstUnauthorized(ChatSessionEntity chatSession, IUserContext userContext)
+ {
+ if (chatSession.OwnerId != userContext.OwnerId)
+ throw new CustomForbiddenAccessException("ChatSession", chatSession.Id);
+ }
}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/PatchMyChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/PatchMyChatSessionCommandValidator.cs
new file mode 100644
index 0000000..06cdd8f
--- /dev/null
+++ b/src/Core.Application/ChatCompletion/PatchMyChatSessionCommandValidator.cs
@@ -0,0 +1,24 @@
+namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
+
+public class PatchMyChatSessionCommandValidator : Validator
+{
+ public PatchMyChatSessionCommandValidator()
+ {
+ RuleFor(x => x.Id)
+ .NotEmpty("Id is required");
+
+ RuleFor(x => x.Title)
+ .NotEmpty("Title is required");
+
+ RuleFor(x => x.UserContext)
+ .NotEmpty("User information is required");
+
+ RuleFor(x => x.UserContext!.OwnerId)
+ .NotEmpty("OwnerId is required")
+ .When(x => x.UserContext != null);
+
+ RuleFor(x => x.UserContext!.TenantId)
+ .NotEmpty("TenantId is required")
+ .When(x => x.UserContext != null);
+ }
+}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/UpdateChatSessionCommand.cs b/src/Core.Application/ChatCompletion/UpdateChatSessionCommand.cs
deleted file mode 100644
index ee71e69..0000000
--- a/src/Core.Application/ChatCompletion/UpdateChatSessionCommand.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Goodtocode.AgentFramework.Core.Application.Abstractions;
-using Goodtocode.AgentFramework.Core.Application.Common.Exceptions;
-using Goodtocode.AgentFramework.Core.Domain.ChatCompletion;
-
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class UpdateChatSessionCommand : IRequest
-{
- public Guid Id { get; set; }
- public string Title { get; set; } = string.Empty;
- public ICollection Messages { get; set; } = [];
-}
-
-public class UpdateChatSessionCommandHandler(IAgentFrameworkContext context) : IRequestHandler
-{
- private readonly IAgentFrameworkContext _context = context;
-
- public async Task Handle(UpdateChatSessionCommand request, CancellationToken cancellationToken)
- {
- var chatSession = _context.ChatSessions.Find(request.Id);
- GuardAgainstNotFound(chatSession);
-
-
- _context.ChatSessions.Update(chatSession!);
- await _context.SaveChangesAsync(cancellationToken);
- }
-
- private static void GuardAgainstNotFound(ChatSessionEntity? chatSession)
- {
- if (chatSession == null)
- throw new CustomNotFoundException("Chat Session Not Found");
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/ChatCompletion/UpdateChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/UpdateChatSessionCommandValidator.cs
deleted file mode 100644
index 15ad79b..0000000
--- a/src/Core.Application/ChatCompletion/UpdateChatSessionCommandValidator.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Goodtocode.AgentFramework.Core.Application.ChatCompletion;
-
-public class UpdateChatSessionCommandValidator : Validator
-{
- public UpdateChatSessionCommandValidator()
- {
- RuleFor(x => x.Id).NotEmpty();
- RuleFor(x => x.Title).NotEmpty();
- }
-}
\ No newline at end of file
diff --git a/src/Core.Application/Common/Exceptions/CustomForbiddenAccessException.cs b/src/Core.Application/Common/Exceptions/CustomForbiddenAccessException.cs
index 9994011..3b21140 100644
--- a/src/Core.Application/Common/Exceptions/CustomForbiddenAccessException.cs
+++ b/src/Core.Application/Common/Exceptions/CustomForbiddenAccessException.cs
@@ -17,7 +17,7 @@ public CustomForbiddenAccessException(string message, Exception innerException)
}
public CustomForbiddenAccessException(string name, object id)
- : base($"Entity \"{name}\" ({id}) was not found.")
+ : base($"Access to entity \"{name}\" ({id}) is forbidden.")
{
}
}
\ No newline at end of file
diff --git a/src/Core.Application/Core.Application.csproj b/src/Core.Application/Core.Application.csproj
index 2531e9e..57821d1 100644
--- a/src/Core.Application/Core.Application.csproj
+++ b/src/Core.Application/Core.Application.csproj
@@ -10,8 +10,8 @@
enable
-
-
+
+
diff --git a/src/Core.Domain/Actor/ActorEntity.cs b/src/Core.Domain/Actor/ActorEntity.cs
index a985aa0..8edaffc 100644
--- a/src/Core.Domain/Actor/ActorEntity.cs
+++ b/src/Core.Domain/Actor/ActorEntity.cs
@@ -11,20 +11,18 @@ protected ActorEntity() { }
public string? LastName { get; private set; } = string.Empty;
public string? Email { get; private set; } = string.Empty;
- public static ActorEntity Create(Guid id, Guid ownerId, Guid tenantId, string? firstName, string? lastName, string? email)
+ public static ActorEntity Create(Guid id, string? firstName, string? lastName, string? email)
{
return new ActorEntity
{
Id = id == Guid.Empty ? Guid.NewGuid() : id,
- OwnerId = ownerId,
- TenantId = tenantId,
FirstName = firstName,
LastName = lastName,
Email = email
};
}
- public static ActorEntity Create(IUserEntity userInfo)
+ public static ActorEntity Create(IUserContext userInfo)
{
return new ActorEntity
{
diff --git a/src/Core.Domain/Auth/IUserContext.cs b/src/Core.Domain/Auth/IUserContext.cs
new file mode 100644
index 0000000..8756855
--- /dev/null
+++ b/src/Core.Domain/Auth/IUserContext.cs
@@ -0,0 +1,19 @@
+namespace Goodtocode.AgentFramework.Core.Domain.Auth;
+
+///
+/// Represents the authenticated user's context, including their identity, tenant association, and authorization permissions.
+///
+/// This interface provides access to user identity information and computed authorization permissions
+/// based on assigned roles. It serves as the domain representation of the current user's security context.
+public interface IUserContext
+{
+ Guid OwnerId { get; }
+ Guid TenantId { get; }
+ string FirstName { get; }
+ string LastName { get; }
+ string Email { get; }
+ IEnumerable Roles { get; }
+ bool CanView { get; }
+ bool CanEdit { get; }
+ bool CanDelete { get; }
+}
diff --git a/src/Core.Domain/Auth/IUserEntity.cs b/src/Core.Domain/Auth/IUserEntity.cs
deleted file mode 100644
index 55876e0..0000000
--- a/src/Core.Domain/Auth/IUserEntity.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Goodtocode.AgentFramework.Core.Domain.Auth;
-
-public interface IUserEntity
-{
- Guid OwnerId { get; }
- Guid TenantId { get; }
- string FirstName { get; }
- string LastName { get; }
- string Email { get; }
- IEnumerable Roles { get; }
- bool CanView { get; }
- bool CanEdit { get; }
- bool CanDelete { get; }
-}
diff --git a/src/Core.Domain/Auth/UserEntity.cs b/src/Core.Domain/Auth/UserContext.cs
similarity index 68%
rename from src/Core.Domain/Auth/UserEntity.cs
rename to src/Core.Domain/Auth/UserContext.cs
index 3b807b8..2372034 100644
--- a/src/Core.Domain/Auth/UserEntity.cs
+++ b/src/Core.Domain/Auth/UserContext.cs
@@ -7,7 +7,12 @@ public struct UserRoles
public const string ChatViewer = "AssetViewer";
}
-public class UserEntity() : IUserEntity
+///
+/// Represents the authenticated user's context in the domain, including identity and authorization state.
+///
+/// This class encapsulates user identity information and provides role-based authorization logic
+/// through computed properties (CanView, CanEdit, CanDelete).
+public class UserContext() : IUserContext
{
public Guid OwnerId { get; private set; }
public Guid TenantId { get; private set; }
@@ -19,9 +24,9 @@ public class UserEntity() : IUserEntity
public bool CanEdit => Roles.Contains(UserRoles.ChatOwner) || Roles.Contains(UserRoles.ChatEditor);
public bool CanDelete => Roles.Contains(UserRoles.ChatOwner);
- public static UserEntity Create(Guid ownerId, Guid tenantId, string firstName, string lastName, string email, IEnumerable roles)
+ public static UserContext Create(Guid ownerId, Guid tenantId, string firstName, string lastName, string email, IEnumerable roles)
{
- return new UserEntity
+ return new UserContext
{
OwnerId = ownerId,
TenantId = tenantId,
diff --git a/src/Core.Domain/ChatCompletion/ChatMessageEntity.cs b/src/Core.Domain/ChatCompletion/ChatMessageEntity.cs
index 5bb8616..9c433a3 100644
--- a/src/Core.Domain/ChatCompletion/ChatMessageEntity.cs
+++ b/src/Core.Domain/ChatCompletion/ChatMessageEntity.cs
@@ -2,7 +2,7 @@
namespace Goodtocode.AgentFramework.Core.Domain.ChatCompletion;
-public class ChatMessageEntity : DomainEntity, IDomainEntity
+public class ChatMessageEntity : SecuredEntity, IDomainEntity
{
protected ChatMessageEntity() { }
public Guid ChatSessionId { get; private set; }
diff --git a/src/Core.Domain/ChatCompletion/ChatSessionEntity.cs b/src/Core.Domain/ChatCompletion/ChatSessionEntity.cs
index 34044cd..5d790a9 100644
--- a/src/Core.Domain/ChatCompletion/ChatSessionEntity.cs
+++ b/src/Core.Domain/ChatCompletion/ChatSessionEntity.cs
@@ -3,21 +3,20 @@
namespace Goodtocode.AgentFramework.Core.Domain.ChatCompletion;
-public class ChatSessionEntity : DomainEntity
+public class ChatSessionEntity : SecuredEntity
{
protected ChatSessionEntity() { }
public Guid ActorId { get; private set; }
public string? Title { get; private set; } = string.Empty;
public virtual ICollection Messages { get; private set; } = [];
- public virtual ActorEntity? Actor { get; private set; }
- public static ChatSessionEntity Create(Guid id, Guid authorId, string? title, ChatMessageRole responseRole, string initialMessage, string responseMessage)
+ public static ChatSessionEntity Create(Guid id, Guid actorId, string? title, ChatMessageRole responseRole, string initialMessage, string responseMessage)
{
var session = new ChatSessionEntity
{
Id = id == Guid.Empty ? Guid.NewGuid() : id,
- ActorId = authorId,
+ ActorId = actorId,
Title = title,
Timestamp = DateTime.UtcNow
};
diff --git a/src/Core.Domain/Core.Domain.csproj b/src/Core.Domain/Core.Domain.csproj
index 65c072e..e54147d 100644
--- a/src/Core.Domain/Core.Domain.csproj
+++ b/src/Core.Domain/Core.Domain.csproj
@@ -12,6 +12,6 @@
-
+
diff --git a/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs b/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs
index 6122e40..fc98c91 100644
--- a/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs
+++ b/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs
@@ -19,7 +19,7 @@ public sealed class ActorsTool(IServiceProvider serviceProvider) : AITool, IActo
{
private readonly IServiceProvider _serviceProvider = serviceProvider;
- public static string ToolName => "AuthorsTool";
+ public static string ToolName => "ActorsTool";
public string FunctionName => _currentFunctionName;
public Dictionary Parameters => _currentParameters;
@@ -75,7 +75,7 @@ public async Task> GetActorsByNameAsync(string name,
var nameTokens = name?.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? [];
var normalizedInput = name?.Trim() ?? string.Empty;
- var authors = await context.Actors
+ var actors = await context.Actors
.Where(a =>
nameTokens.Any(token =>
EF.Functions.Like(a.FirstName, $"%{token}%") ||
@@ -96,7 +96,7 @@ public async Task> GetActorsByNameAsync(string name,
)
.ToListAsync(cancellationToken);
- if (authors.Count == 0)
+ if (actors.Count == 0)
{
return [ new ActorResponse
{
@@ -108,7 +108,7 @@ public async Task> GetActorsByNameAsync(string name,
}
else
{
- return [.. authors.Select(a => new ActorResponse
+ return [.. actors.Select(a => new ActorResponse
{
ActorId = a.Id,
Name = $"{a.FirstName} {a.LastName}",
diff --git a/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs b/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs
index e14d677..6660bb1 100644
--- a/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs
+++ b/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs
@@ -69,6 +69,6 @@ public async Task UpdateChatSessionTitleAsync(Guid sessionId, string new
context.ChatSessions.Update(chatSession);
await context.SaveChangesAsync(cancellationToken);
- return $"{chatSession.Id}: {chatSession.Timestamp} - {chatSession.Title}: {chatSession.Actor?.FirstName} {chatSession.Actor?.LastName}";
+ return $"{chatSession.Id}: {chatSession.Timestamp} - {chatSession.Title}";
}
}
\ No newline at end of file
diff --git a/src/Infrastructure.SqlServer/Migrations/20260204064841_InitialCreate.Designer.cs b/src/Infrastructure.SqlServer/Migrations/20260208060746_InitialCreate.Designer.cs
similarity index 92%
rename from src/Infrastructure.SqlServer/Migrations/20260204064841_InitialCreate.Designer.cs
rename to src/Infrastructure.SqlServer/Migrations/20260208060746_InitialCreate.Designer.cs
index c7f9da6..94032b3 100644
--- a/src/Infrastructure.SqlServer/Migrations/20260204064841_InitialCreate.Designer.cs
+++ b/src/Infrastructure.SqlServer/Migrations/20260208060746_InitialCreate.Designer.cs
@@ -12,7 +12,7 @@
namespace Goodtocode.AgentFramework.Infrastructure.SqlServer.Migrations
{
[DbContext(typeof(AgentFrameworkContext))]
- [Migration("20260204064841_InitialCreate")]
+ [Migration("20260208060746_InitialCreate")]
partial class InitialCreate
{
///
@@ -103,9 +103,15 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.Property("ModifiedOn")
.HasColumnType("datetime2");
+ b.Property("OwnerId")
+ .HasColumnType("uniqueidentifier");
+
b.Property("Role")
.HasColumnType("int");
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier");
+
b.Property("Timestamp")
.HasColumnType("datetimeoffset");
@@ -146,6 +152,12 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.Property("ModifiedOn")
.HasColumnType("datetime2");
+ b.Property("OwnerId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier");
+
b.Property("Timestamp")
.HasColumnType("datetimeoffset");
@@ -156,8 +168,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("Id"), false);
- b.HasIndex("ActorId");
-
b.HasIndex("Id")
.IsUnique();
@@ -182,17 +192,6 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder)
b.Navigation("ChatSession");
});
- modelBuilder.Entity("Goodtocode.AgentFramework.Core.Domain.ChatCompletion.ChatSessionEntity", b =>
- {
- b.HasOne("Goodtocode.AgentFramework.Core.Domain.Actor.ActorEntity", "Actor")
- .WithMany()
- .HasForeignKey("ActorId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("Actor");
- });
-
modelBuilder.Entity("Goodtocode.AgentFramework.Core.Domain.ChatCompletion.ChatSessionEntity", b =>
{
b.Navigation("Messages");
diff --git a/src/Infrastructure.SqlServer/Migrations/20260204064841_InitialCreate.cs b/src/Infrastructure.SqlServer/Migrations/20260208060746_InitialCreate.cs
similarity index 91%
rename from src/Infrastructure.SqlServer/Migrations/20260204064841_InitialCreate.cs
rename to src/Infrastructure.SqlServer/Migrations/20260208060746_InitialCreate.cs
index 7c6c9f2..89c8dc5 100644
--- a/src/Infrastructure.SqlServer/Migrations/20260204064841_InitialCreate.cs
+++ b/src/Infrastructure.SqlServer/Migrations/20260208060746_InitialCreate.cs
@@ -42,18 +42,14 @@ protected override void Up(MigrationBuilder migrationBuilder)
CreatedOn = table.Column(type: "datetime2", nullable: false),
ModifiedOn = table.Column(type: "datetime2", nullable: true),
DeletedOn = table.Column(type: "datetime2", nullable: true),
- Timestamp = table.Column(type: "datetimeoffset", nullable: false)
+ Timestamp = table.Column(type: "datetimeoffset", nullable: false),
+ OwnerId = table.Column(type: "uniqueidentifier", nullable: false),
+ TenantId = table.Column(type: "uniqueidentifier", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChatSessions", x => x.Id)
.Annotation("SqlServer:Clustered", false);
- table.ForeignKey(
- name: "FK_ChatSessions_Actors_ActorId",
- column: x => x.ActorId,
- principalTable: "Actors",
- principalColumn: "Id",
- onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
@@ -67,7 +63,9 @@ protected override void Up(MigrationBuilder migrationBuilder)
CreatedOn = table.Column(type: "datetime2", nullable: false),
ModifiedOn = table.Column(type: "datetime2", nullable: true),
DeletedOn = table.Column(type: "datetime2", nullable: true),
- Timestamp = table.Column(type: "datetimeoffset", nullable: false)
+ Timestamp = table.Column(type: "datetimeoffset", nullable: false),
+ OwnerId = table.Column(type: "uniqueidentifier", nullable: false),
+ TenantId = table.Column(type: "uniqueidentifier", nullable: false)
},
constraints: table =>
{
@@ -120,11 +118,6 @@ protected override void Up(MigrationBuilder migrationBuilder)
unique: true)
.Annotation("SqlServer:Clustered", true);
- migrationBuilder.CreateIndex(
- name: "IX_ChatSessions_ActorId",
- table: "ChatSessions",
- column: "ActorId");
-
migrationBuilder.CreateIndex(
name: "IX_ChatSessions_Id",
table: "ChatSessions",
@@ -144,13 +137,13 @@ protected override void Up(MigrationBuilder migrationBuilder)
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
- name: "ChatMessages");
+ name: "Actors");
migrationBuilder.DropTable(
- name: "ChatSessions");
+ name: "ChatMessages");
migrationBuilder.DropTable(
- name: "Actors");
+ name: "ChatSessions");
}
}
}
diff --git a/src/Infrastructure.SqlServer/Migrations/AgentFrameworkContextModelSnapshot.cs b/src/Infrastructure.SqlServer/Migrations/AgentFrameworkContextModelSnapshot.cs
index 1da18c0..4b73088 100644
--- a/src/Infrastructure.SqlServer/Migrations/AgentFrameworkContextModelSnapshot.cs
+++ b/src/Infrastructure.SqlServer/Migrations/AgentFrameworkContextModelSnapshot.cs
@@ -100,9 +100,15 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property("ModifiedOn")
.HasColumnType("datetime2");
+ b.Property("OwnerId")
+ .HasColumnType("uniqueidentifier");
+
b.Property("Role")
.HasColumnType("int");
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier");
+
b.Property("Timestamp")
.HasColumnType("datetimeoffset");
@@ -143,6 +149,12 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property("ModifiedOn")
.HasColumnType("datetime2");
+ b.Property("OwnerId")
+ .HasColumnType("uniqueidentifier");
+
+ b.Property("TenantId")
+ .HasColumnType("uniqueidentifier");
+
b.Property("Timestamp")
.HasColumnType("datetimeoffset");
@@ -153,8 +165,6 @@ protected override void BuildModel(ModelBuilder modelBuilder)
SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("Id"), false);
- b.HasIndex("ActorId");
-
b.HasIndex("Id")
.IsUnique();
@@ -179,17 +189,6 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Navigation("ChatSession");
});
- modelBuilder.Entity("Goodtocode.AgentFramework.Core.Domain.ChatCompletion.ChatSessionEntity", b =>
- {
- b.HasOne("Goodtocode.AgentFramework.Core.Domain.Actor.ActorEntity", "Actor")
- .WithMany()
- .HasForeignKey("ActorId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("Actor");
- });
-
modelBuilder.Entity("Goodtocode.AgentFramework.Core.Domain.ChatCompletion.ChatSessionEntity", b =>
{
b.Navigation("Messages");
diff --git a/src/Infrastructure.SqlServer/Persistence/AgentFrameworkContext.cs b/src/Infrastructure.SqlServer/Persistence/AgentFrameworkContext.cs
index 72b8d7a..c800ce6 100644
--- a/src/Infrastructure.SqlServer/Persistence/AgentFrameworkContext.cs
+++ b/src/Infrastructure.SqlServer/Persistence/AgentFrameworkContext.cs
@@ -2,19 +2,27 @@
using Goodtocode.AgentFramework.Core.Domain.Actor;
using Goodtocode.AgentFramework.Core.Domain.ChatCompletion;
using Goodtocode.Domain.Entities;
+using Microsoft.EntityFrameworkCore.ChangeTracking;
using System.Reflection;
namespace Goodtocode.AgentFramework.Infrastructure.SqlServer.Persistence;
public class AgentFrameworkContext : DbContext, IAgentFrameworkContext
{
+ private readonly ICurrentUserContext? _currentUserContext;
+
public DbSet ChatMessages => Set();
public DbSet ChatSessions => Set();
public DbSet Actors => Set();
protected AgentFrameworkContext() { }
- public AgentFrameworkContext(DbContextOptions options) : base(options) { }
+ public AgentFrameworkContext(
+ DbContextOptions options,
+ ICurrentUserContext currentUserContext) : base(options)
+ {
+ _currentUserContext = currentUserContext;
+ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -26,10 +34,43 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
public override Task SaveChangesAsync(CancellationToken cancellationToken = default)
{
+ SetSecurityFields();
SetAuditFields();
return base.SaveChangesAsync(cancellationToken);
}
+ private void SetSecurityFields()
+ {
+ if (_currentUserContext is null ||
+ _currentUserContext.OwnerId == Guid.Empty ||
+ _currentUserContext.TenantId == Guid.Empty)
+ {
+ return;
+ }
+
+ var entries = ChangeTracker.Entries()
+ .Where(e => e.State == EntityState.Added && HasSecurityFields(e));
+
+ foreach (var entry in entries)
+ {
+ SetIfEmpty(entry, "OwnerId", _currentUserContext.OwnerId);
+ SetIfEmpty(entry, "TenantId", _currentUserContext.TenantId);
+ }
+ }
+
+ private static bool HasSecurityFields(EntityEntry entry) =>
+ entry.Metadata.FindProperty("OwnerId") != null &&
+ entry.Metadata.FindProperty("TenantId") != null;
+
+ private static void SetIfEmpty(EntityEntry entry, string propertyName, Guid value)
+ {
+ var property = entry.Property(propertyName);
+ if (property.CurrentValue is Guid guid && guid == Guid.Empty)
+ {
+ property.CurrentValue = value;
+ }
+ }
+
private void SetAuditFields()
{
var entries = ChangeTracker.Entries()
diff --git a/src/Presentation.Blazor/Clients/BackendApiClient.g.cs b/src/Presentation.Blazor/Clients/BackendApiClient.g.cs
index 687c47b..c9de742 100644
--- a/src/Presentation.Blazor/Clients/BackendApiClient.g.cs
+++ b/src/Presentation.Blazor/Clients/BackendApiClient.g.cs
@@ -73,980 +73,34 @@ public string BaseUrl
partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response);
///
- /// Get Chat Message
- ///
- ///
- /// Sample request:
- ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e
- ///
"api-version": 1.0
- ///
- /// OK
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task GetChatMessageQueryAsync(System.Guid id)
- {
- return GetChatMessageQueryAsync(id, System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Get Chat Message
- ///
- ///
- /// Sample request:
- ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e
- ///
"api-version": 1.0
- ///
- /// OK
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task GetChatMessageQueryAsync(System.Guid id, System.Threading.CancellationToken cancellationToken)
- {
- if (id == null)
- throw new System.ArgumentNullException("id");
-
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- request_.Method = new System.Net.Http.HttpMethod("GET");
- request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
-
- var urlBuilder_ = new System.Text.StringBuilder();
- if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
- // Operation Path: "api/v1/admin/messages/{id}"
- urlBuilder_.Append("api/v1/admin/messages/");
- urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture)));
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 200)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- return objectResponse_.Object;
- }
- else
- if (status_ == 401)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- else
- if (status_ == 404)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Not Found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- else
- if (status_ == 500)
- {
- string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
- throw new ApiException("Internal Server Error", status_, responseText_, headers_, null);
- }
- else
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- ///
- /// Get All Chat Messages for a session Query
- ///
- ///
- /// Sample request:
- ///
- ///
"StartDate": "2024-06-01T00:00:00Z"
- ///
"EndDate": "2024-12-01T00:00:00Z"
- ///
"api-version": 1.0
- ///
- /// OK
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task> GetChatMessagesQueryAsync()
- {
- return GetChatMessagesQueryAsync(System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Get All Chat Messages for a session Query
- ///
- ///
- /// Sample request:
- ///
- ///
"StartDate": "2024-06-01T00:00:00Z"
- ///
"EndDate": "2024-12-01T00:00:00Z"
- ///
"api-version": 1.0
- ///
- /// OK
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task> GetChatMessagesQueryAsync(System.Threading.CancellationToken cancellationToken)
- {
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- request_.Method = new System.Net.Http.HttpMethod("GET");
- request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
-
- var urlBuilder_ = new System.Text.StringBuilder();
- if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
- // Operation Path: "api/v1/admin/messages"
- urlBuilder_.Append("api/v1/admin/messages");
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 200)
- {
- var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- return objectResponse_.Object;
- }
- else
- if (status_ == 401)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- else
- if (status_ == 404)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Not Found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- else
- if (status_ == 500)
- {
- string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
- throw new ApiException("Internal Server Error", status_, responseText_, headers_, null);
- }
- else
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- ///
- /// Creates new Chat Message with initial message prompt/response history
- ///
- ///
- /// Types of Chat Completion are:
- ///
1. Informational Prompt: A prompt requesting information
- ///
- Example Prompt: "What's the capital of France?"
- ///
- Example Response: "The capital of France is Paris."
- ///
2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses.
- ///
- Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games”
- ///
- Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.”
- ///
Sample request:
- ///
- ///
HttpPost Body
- ///
{
- ///
"Id": 00000000-0000-0000-0000-000000000000,
- ///
"Message": "Hi, I am interested in learning about Agent Framework."
- ///
}
- ///
- ///
"version": 1.0
- ///
- /// Created
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task CreateChatMessageCommandAsync(CreateChatMessageCommand body)
- {
- return CreateChatMessageCommandAsync(body, System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Creates new Chat Message with initial message prompt/response history
- ///
- ///
- /// Types of Chat Completion are:
- ///
1. Informational Prompt: A prompt requesting information
- ///
- Example Prompt: "What's the capital of France?"
- ///
- Example Response: "The capital of France is Paris."
- ///
2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses.
- ///
- Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games”
- ///
- Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.”
- ///
Sample request:
- ///
- ///
HttpPost Body
- ///
{
- ///
"Id": 00000000-0000-0000-0000-000000000000,
- ///
"Message": "Hi, I am interested in learning about Agent Framework."
- ///
}
- ///
- ///
"version": 1.0
- ///
- /// Created
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task CreateChatMessageCommandAsync(CreateChatMessageCommand body, System.Threading.CancellationToken cancellationToken)
- {
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, JsonSerializerSettings);
- var content_ = new System.Net.Http.ByteArrayContent(json_);
- content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
- request_.Content = content_;
- request_.Method = new System.Net.Http.HttpMethod("POST");
- request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
-
- var urlBuilder_ = new System.Text.StringBuilder();
- if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
- // Operation Path: "api/v1/admin/messages"
- urlBuilder_.Append("api/v1/admin/messages");
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 201)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- return objectResponse_.Object;
- }
- else
- if (status_ == 400)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Bad Request", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- else
- if (status_ == 401)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- else
- if (status_ == 500)
- {
- string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
- throw new ApiException("Internal Server Error", status_, responseText_, headers_, null);
- }
- else
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- ///
- /// Get Chat Messages Paginated Query
- ///
- ///
- /// Sample request:
- ///
- ///
"StartDate": "2024-06-01T00:00:00Z"
- ///
"EndDate": "2024-12-01T00:00:00Z"
- ///
"PageNumber": 1
- ///
"PageSize" : 10
- ///
"api-version": 1.0
- ///
- /// OK
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task GetChatMessagesPaginatedQueryAsync(System.DateTimeOffset? startDate, System.DateTimeOffset? endDate, int? pageNumber, int? pageSize)
- {
- return GetChatMessagesPaginatedQueryAsync(startDate, endDate, pageNumber, pageSize, System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Get Chat Messages Paginated Query
- ///
- ///
- /// Sample request:
- ///
- ///
"StartDate": "2024-06-01T00:00:00Z"
- ///
"EndDate": "2024-12-01T00:00:00Z"
- ///
"PageNumber": 1
- ///
"PageSize" : 10
- ///
"api-version": 1.0
- ///
- /// OK
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task GetChatMessagesPaginatedQueryAsync(System.DateTimeOffset? startDate, System.DateTimeOffset? endDate, int? pageNumber, int? pageSize, System.Threading.CancellationToken cancellationToken)
- {
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- request_.Method = new System.Net.Http.HttpMethod("GET");
- request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
-
- var urlBuilder_ = new System.Text.StringBuilder();
- if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
- // Operation Path: "api/v1/admin/messages/Paginated"
- urlBuilder_.Append("api/v1/admin/messages/Paginated");
- urlBuilder_.Append('?');
- if (startDate != null)
- {
- urlBuilder_.Append(System.Uri.EscapeDataString("StartDate")).Append('=').Append(System.Uri.EscapeDataString(startDate.Value.ToString("s", System.Globalization.CultureInfo.InvariantCulture))).Append('&');
- }
- if (endDate != null)
- {
- urlBuilder_.Append(System.Uri.EscapeDataString("EndDate")).Append('=').Append(System.Uri.EscapeDataString(endDate.Value.ToString("s", System.Globalization.CultureInfo.InvariantCulture))).Append('&');
- }
- if (pageNumber != null)
- {
- urlBuilder_.Append(System.Uri.EscapeDataString("PageNumber")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(pageNumber, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
- }
- if (pageSize != null)
- {
- urlBuilder_.Append(System.Uri.EscapeDataString("PageSize")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(pageSize, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
- }
- urlBuilder_.Length--;
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 200)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- return objectResponse_.Object;
- }
- else
- if (status_ == 401)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- else
- if (status_ == 500)
- {
- string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
- throw new ApiException("Internal Server Error", status_, responseText_, headers_, null);
- }
- else
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- ///
- /// Get Chat Session with history
- ///
- ///
- /// Sample request:
- ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e
- ///
"api-version": 1.0
- ///
- /// OK
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task GetChatSessionQueryAsync(System.Guid id)
- {
- return GetChatSessionQueryAsync(id, System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Get Chat Session with history
- ///
- ///
- /// Sample request:
- ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e
- ///
"api-version": 1.0
- ///
- /// OK
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task GetChatSessionQueryAsync(System.Guid id, System.Threading.CancellationToken cancellationToken)
- {
- if (id == null)
- throw new System.ArgumentNullException("id");
-
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- request_.Method = new System.Net.Http.HttpMethod("GET");
- request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
-
- var urlBuilder_ = new System.Text.StringBuilder();
- if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
- // Operation Path: "api/v1/admin/sessions/{id}"
- urlBuilder_.Append("api/v1/admin/sessions/");
- urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture)));
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 200)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- return objectResponse_.Object;
- }
- else
- if (status_ == 401)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- else
- if (status_ == 404)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Not Found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- else
- if (status_ == 500)
- {
- string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
- throw new ApiException("Internal Server Error", status_, responseText_, headers_, null);
- }
- else
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- ///
- /// Patch Chat Session Command
- ///
- ///
- /// Sample request:
- ///
- ///
HttpPatch Body
- ///
{
- ///
"Id": "60fb5e99-3a78-43df-a512-7d8ff498499e",
- ///
"Title": "Agent Framework Chat Session"
- ///
}
- ///
- ///
"version": 1.0
- ///
- /// No Content
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task PatchChatSessionCommandAsync(System.Guid id, PatchChatSessionCommand body)
- {
- return PatchChatSessionCommandAsync(id, body, System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Patch Chat Session Command
- ///
- ///
- /// Sample request:
- ///
- ///
HttpPatch Body
- ///
{
- ///
"Id": "60fb5e99-3a78-43df-a512-7d8ff498499e",
- ///
"Title": "Agent Framework Chat Session"
- ///
}
- ///
- ///
"version": 1.0
- ///
- /// No Content
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task PatchChatSessionCommandAsync(System.Guid id, PatchChatSessionCommand body, System.Threading.CancellationToken cancellationToken)
- {
- if (id == null)
- throw new System.ArgumentNullException("id");
-
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, JsonSerializerSettings);
- var content_ = new System.Net.Http.ByteArrayContent(json_);
- content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
- request_.Content = content_;
- request_.Method = new System.Net.Http.HttpMethod("PATCH");
-
- var urlBuilder_ = new System.Text.StringBuilder();
- if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
- // Operation Path: "api/v1/admin/sessions/{id}"
- urlBuilder_.Append("api/v1/admin/sessions/");
- urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture)));
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 204)
- {
- return;
- }
- else
- if (status_ == 401)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- else
- if (status_ == 404)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Not Found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- else
- if (status_ == 500)
- {
- string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
- throw new ApiException("Internal Server Error", status_, responseText_, headers_, null);
- }
- else
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- ///
- /// Remove ChatSession Command
- ///
- ///
- /// Sample request:
- ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e
- ///
"api-version": 1.0
- ///
- /// No Content
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task RemoveChatSessionCommandAsync(System.Guid id)
- {
- return RemoveChatSessionCommandAsync(id, System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Remove ChatSession Command
- ///
- ///
- /// Sample request:
- ///
- ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e
- ///
"api-version": 1.0
- ///
- /// No Content
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task RemoveChatSessionCommandAsync(System.Guid id, System.Threading.CancellationToken cancellationToken)
- {
- if (id == null)
- throw new System.ArgumentNullException("id");
-
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- request_.Method = new System.Net.Http.HttpMethod("DELETE");
-
- var urlBuilder_ = new System.Text.StringBuilder();
- if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
- // Operation Path: "api/v1/admin/sessions/{id}"
- urlBuilder_.Append("api/v1/admin/sessions/");
- urlBuilder_.Append(System.Uri.EscapeDataString(ConvertToString(id, System.Globalization.CultureInfo.InvariantCulture)));
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 204)
- {
- return;
- }
- else
- if (status_ == 401)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Unauthorized", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- else
- if (status_ == 404)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Not Found", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- else
- if (status_ == 500)
- {
- string responseText_ = ( response_.Content == null ) ? string.Empty : await ReadAsStringAsync(response_.Content, cancellationToken).ConfigureAwait(false);
- throw new ApiException("Internal Server Error", status_, responseText_, headers_, null);
- }
- else
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- throw new ApiException("Error", status_, objectResponse_.Text, headers_, objectResponse_.Object, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- ///
- /// Get All Chat Sessions Query
+ /// Retrieves the actor profile by external ID, including session history.
///
///
/// Sample request:
///
- ///
"StartDate": "2024-06-01T00:00:00Z"
- ///
"EndDate": "2024-12-01T00:00:00Z"
- ///
"api-version": 1.0
+ ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e
+ ///
"api-version": 1.0
///
/// OK
/// A server side error occurred.
- public virtual System.Threading.Tasks.Task> GetChatSessionsQueryAsync()
+ public virtual System.Threading.Tasks.Task GetMyActorProfileAsync()
{
- return GetChatSessionsQueryAsync(System.Threading.CancellationToken.None);
+ return GetMyActorProfileAsync(System.Threading.CancellationToken.None);
}
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
///
- /// Get All Chat Sessions Query
+ /// Retrieves the actor profile by external ID, including session history.
///
///
/// Sample request:
///
- ///
"StartDate": "2024-06-01T00:00:00Z"
- ///
"EndDate": "2024-12-01T00:00:00Z"
- ///
"api-version": 1.0
+ ///
"Id": 60fb5e99-3a78-43df-a512-7d8ff498499e
+ ///
"api-version": 1.0
///
/// OK
/// A server side error occurred.
- public virtual async System.Threading.Tasks.Task> GetChatSessionsQueryAsync(System.Threading.CancellationToken cancellationToken)
+ public virtual async System.Threading.Tasks.Task GetMyActorProfileAsync(System.Threading.CancellationToken cancellationToken)
{
var client_ = _httpClient;
var disposeClient_ = false;
@@ -1059,8 +113,8 @@ public virtual async System.Threading.Tasks.Task RemoveChatSessionCommandAsync(S
var urlBuilder_ = new System.Text.StringBuilder();
if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
- // Operation Path: "api/v1/admin/sessions"
- urlBuilder_.Append("api/v1/admin/sessions");
+ // Operation Path: "api/v1/my/actors"
+ urlBuilder_.Append("api/v1/my/actors");
PrepareRequest(client_, request_, urlBuilder_);
@@ -1087,7 +141,7 @@ public virtual async System.Threading.Tasks.Task RemoveChatSessionCommandAsync(S
var status_ = (int)response_.StatusCode;
if (status_ == 200)
{
- var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false);
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
@@ -1145,58 +199,46 @@ public virtual async System.Threading.Tasks.Task RemoveChatSessionCommandAsync(S
}
///
- /// Creates new Chat Session with initial message prompt/response history
+ /// Creates a new actor session with empty history.
///
///
- /// Types of Chat Completion are:
- ///
1. Informational Prompt: A prompt requesting information
- ///
- Example Prompt: "What's the capital of France?"
- ///
- Example Response: "The capital of France is Paris."
- ///
2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses.
- ///
- Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games”
- ///
- Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.”
- ///
Sample request:
+ /// Sample request:
///
///
HttpPost Body
///
{
///
"Id": 00000000-0000-0000-0000-000000000000,
- ///
"Message": "Hi, I am interested in learning about Agent Framework."
+ ///
"Name": "John Doe"
///
}
///
///
"version": 1.0
///
- /// Created
+ /// The command containing actor creation details.
+ /// OK
/// A server side error occurred.
- public virtual System.Threading.Tasks.Task CreateChatSessionCommandAsync(CreateChatSessionCommand body)
+ public virtual System.Threading.Tasks.Task SaveMyActorAsync(SaveMyActorCommand body)
{
- return CreateChatSessionCommandAsync(body, System.Threading.CancellationToken.None);
+ return SaveMyActorAsync(body, System.Threading.CancellationToken.None);
}
/// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
///
- /// Creates new Chat Session with initial message prompt/response history
+ /// Creates a new actor session with empty history.
///
///
- /// Types of Chat Completion are:
- ///
1. Informational Prompt: A prompt requesting information
- ///
- Example Prompt: "What's the capital of France?"
- ///
- Example Response: "The capital of France is Paris."
- ///
2. Multiple Choice Prompt: A prompt with instructions for multiple-choice responses.
- ///
- Example Prompt: “Choose an activity for the weekend: a) Hiking b) Movie night c) Cooking class d) Board games”
- ///
- Example Response: “I'd recommend hiking! It's a great way to enjoy nature and get some exercise.”
- ///
Sample request:
+ /// Sample request:
///
///
HttpPost Body
///
{
///
"Id": 00000000-0000-0000-0000-000000000000,
- ///
"Message": "Hi, I am interested in learning about Agent Framework."
+ ///
"Name": "John Doe"
///
}
///
///
"version": 1.0
///
- /// Created
+ /// The command containing actor creation details.
+ /// OK
/// A server side error occurred.
- public virtual async System.Threading.Tasks.Task CreateChatSessionCommandAsync(CreateChatSessionCommand body, System.Threading.CancellationToken cancellationToken)
+ public virtual async System.Threading.Tasks.Task SaveMyActorAsync(SaveMyActorCommand body, System.Threading.CancellationToken cancellationToken)
{
var client_ = _httpClient;
var disposeClient_ = false;
@@ -1213,8 +255,8 @@ public virtual async System.Threading.Tasks.Task CreateChatSessi
var urlBuilder_ = new System.Text.StringBuilder();
if (!string.IsNullOrEmpty(_baseUrl)) urlBuilder_.Append(_baseUrl);
- // Operation Path: "api/v1/admin/sessions"
- urlBuilder_.Append("api/v1/admin/sessions");
+ // Operation Path: "api/v1/my/actors"
+ urlBuilder_.Append("api/v1/my/actors");
PrepareRequest(client_, request_, urlBuilder_);
@@ -1239,9 +281,19 @@ public virtual async System.Threading.Tasks.Task CreateChatSessi
ProcessResponse(client_, response_);
var status_ = (int)response_.StatusCode;
+ if (status_ == 200)
+ {
+ var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ if (objectResponse_.Object == null)
+ {
+ throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
+ }
+ return objectResponse_.Object;
+ }
+ else
if (status_ == 201)
{
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
+ var objectResponse_ = await ReadObjectResponseAsync