Skip to content

Conversation

@iceljc
Copy link
Collaborator

@iceljc iceljc commented Jan 7, 2026

PR Type

Enhancement, Bug fix


Description

  • Centralize code execution config logic into reusable CodingUtil class

  • Change multiple ValueTask return types to Task for consistency

  • Add SSE (Server-Sent Events) support for instruction completion endpoint

  • Remove conversation state migration functionality and related API endpoint

  • Enhance Membase plugin with edge operations and embedding support

  • Refactor instruction controller to extract state-setting logic


Diagram Walkthrough

flowchart LR
  A["Code Execution Config"] -->|Extract to Utility| B["CodingUtil.GetCodeExecutionConfig"]
  B -->|Used by| C["RuleEngine"]
  B -->|Used by| D["InstructService"]
  B -->|Used by| E["PyProgrammerFn"]
  F["ValueTask Methods"] -->|Convert to Task| G["Repository Interface"]
  G -->|Implemented by| H["FileRepository & MongoRepository"]
  I["InstructModeController"] -->|Add SSE Endpoint| J["/instruct/{agentId}/sse"]
  K["Membase Plugin"] -->|Add Edge Operations| L["Edge CRUD APIs"]
  K -->|Add Embedding Support| M["Node Models"]
  N["ConversationService"] -->|Remove| O["MigrateLatestStates Method"]
Loading

File Walkthrough

Relevant files
Enhancement
21 files
CodingUtil.cs
New utility class for code execution configuration             
+21/-0   
InstructFileModel.cs
Increase file data substring limit to 50                                 
+1/-1     
RuleEngine.cs
Use centralized CodingUtil for config                                       
+2/-13   
InstructService.Execute.cs
Use centralized CodingUtil for config                                       
+2/-17   
ConversationController.cs
Refactor conversation retrieval logic                                       
+13/-11 
InstructModeController.cs
Add SSE endpoint and extract state logic                                 
+62/-13 
MembasePlugin.cs
Refactor API client registration with auth handler             
+10/-10 
EdgeCreationModel.cs
New model for edge creation                                                           
+12/-0   
EdgeUpdateModel.cs
New model for edge updates                                                             
+6/-0     
NodeCreationModel.cs
Add embedding support to node creation                                     
+8/-0     
NodeUpdateModel.cs
Add embedding support to node updates                                       
+2/-2     
Edge.cs
New edge response model                                                                   
+15/-0   
EdgeDeleteResponse.cs
New edge delete response model                                                     
+6/-0     
GraphInfo.cs
New graph information response model                                         
+18/-0   
Node.cs
Add embedding support and clean formatting                             
+1/-10   
NodeDeleteResponse.cs
New node delete response model                                                     
+6/-0     
IMembaseApi.cs
Add edge operations and graph info endpoints                         
+24/-7   
MembaseAuthHandler.cs
New authentication handler for Membase API                             
+32/-0   
AgentLlmConfigMongoModel.cs
Rename conversion methods for clarity                                       
+2/-2     
MongoRepository.Agent.cs
Update method calls to renamed conversion methods               
+4/-4     
PyProgrammerFn.cs
Use centralized CodingUtil for config                                       
+2/-15   
Bug fix
18 files
IConversationService.cs
Remove migration method from interface                                     
+1/-2     
IBotSharpRepository.cs
Change ValueTask to Task for consistency                                 
+6/-6     
ConversationService.Migration.cs
Remove entire migration service file                                         
+0/-98   
FileRepository.AgentTask.cs
Change ValueTask to Task return type                                         
+1/-1     
FileRepository.Conversation.cs
Change ValueTask to Task return type                                         
+1/-1     
FileRepository.Crontab.cs
Change ValueTask to Task return type                                         
+1/-1     
FileRepository.KnowledgeBase.cs
Change ValueTask to Task return type                                         
+1/-1     
FileRepository.Log.cs
Change ValueTask to Task return type                                         
+1/-1     
FileRepository.User.cs
Change ValueTask to Task return type                                         
+1/-1     
ConversationController.State.cs
Remove migration endpoint from controller                               
+0/-10   
InstructMessageModel.cs
Make Files property nullable                                                         
+1/-1     
UserViewModel.cs
Accept nullable user parameter                                                     
+1/-1     
MongoRepository.AgentTask.cs
Change ValueTask to Task return type                                         
+1/-1     
MongoRepository.Conversation.cs
Change ValueTask to Task return type                                         
+1/-1     
MongoRepository.Crontab.cs
Change ValueTask to Task return type                                         
+1/-1     
MongoRepository.KnowledgeBase.cs
Change ValueTask to Task return type                                         
+1/-1     
MongoRepository.Log.cs
Change ValueTask to Task return type                                         
+1/-1     
MongoRepository.User.cs
Change ValueTask to Task return type                                         
+1/-1     
Formatting
8 files
Conversation.cs
Remove trailing newline formatting                                             
+1/-1     
CyperGraphModels.cs
Remove blank lines between properties                                       
+0/-3     
MembaseController.cs
Remove unused import statement                                                     
+0/-1     
CypherQueryRequest.cs
Remove blank lines between properties                                       
+0/-3     
CypherQueryResponse.cs
Remove blank line between properties                                         
+0/-1     
MembaseService.cs
Remove unused import statements                                                   
+0/-3     
ConversationDocument.cs
Add blank line for formatting                                                       
+1/-0     
BotSharp.OpenAPI.csproj
Fix BOM encoding in project file                                                 
+1/-1     
Miscellaneous
2 files
ConversationService.cs
Add missing using statements                                                         
+2/-0     
Using.cs
Add global using for Membase models                                           
+1/-0     

@qodo-code-review
Copy link

qodo-code-review bot commented Jan 7, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Sensitive data logging

Description: The handler reads and logs the full raw HTTP response body (rawResponse) in #if DEBUG,
which may expose sensitive data (e.g., user/graph content or secrets returned by the API)
into application logs if debug builds are deployed or logs are collected in shared
environments.
MembaseAuthHandler.cs [20-28]

Referred Code
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken)
    {
        requestMessage.Headers.TryAddWithoutValidation("Authorization", $"Bearer {_settings.ApiKey}");
        var response = await base.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false);

#if DEBUG
        var rawResponse = await response.Content.ReadAsStringAsync();
        _logger.LogDebug(rawResponse);
#endif
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status:
Ambiguous tuple return: GetCodeExecutionConfig returns an unnamed (bool, bool, int) tuple which is not
self-documenting and can easily lead to misuse at call sites.

Referred Code
public static (bool, bool, int) GetCodeExecutionConfig(CodingSettings settings, int defaultTimeoutSeconds = 3)
{
    var codeExecution = settings.CodeExecution;

    var useLock = codeExecution?.UseLock ?? false;
    var useProcess = codeExecution?.UseProcess ?? false;
    var timeoutSeconds = codeExecution?.TimeoutSeconds > 0 ? codeExecution.TimeoutSeconds : defaultTimeoutSeconds;

    return (useLock, useProcess, timeoutSeconds);

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Logs raw HTTP body: The handler logs rawResponse directly (_logger.LogDebug(rawResponse)), which is
unstructured and may expose sensitive data returned by the Membase API.

Referred Code
        var rawResponse = await response.Content.ReadAsStringAsync();
        _logger.LogDebug(rawResponse);
#endif

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit logging: New Membase graph capabilities (node/edge CRUD and graph info retrieval) are introduced
without any visible audit logging that records actor/context/outcome for these critical
actions.

Referred Code
[Get("/graph/{graphId}")]
Task<GraphInfo> GetGraphInfoAsync(string graphId);

[Post("/cypher/execute")]
Task<CypherQueryResponse> CypherQueryAsync([Query] string graphId, CypherQueryRequest request);

#region Node
[Get("/graph/{graphId}/node/{nodeId}")]
Task<Node> GetNodeAsync(string graphId, string nodeId);

[Post("/graph/{graphId}/node")]
Task<Node> CreateNodeAsync(string graphId, [Body] NodeCreationModel node);

[Put("/graph/{graphId}/node/{nodeId}")]
Task<Node> UpdateNodeAsync(string graphId, string nodeId, [Body] NodeUpdateModel node);

[Put("/graph/{graphId}/node/{nodeId}/merge")]
Task<Node> MergeNodeAsync(string graphId, string nodeId, [Body] NodeUpdateModel node);

[Delete("/graph/{graphId}/node/{nodeId}")]
Task<NodeDeleteResponse?> DeleteNodeAsync(string graphId, string nodeId);


 ... (clipped 15 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
No non-success handling: The new HTTP auth handler forwards responses without checking or enriching handling for
non-success HTTP statuses, so failures may propagate without actionable context unless
handled elsewhere.

Referred Code
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken)
    {
        requestMessage.Headers.TryAddWithoutValidation("Authorization", $"Bearer {_settings.ApiKey}");
        var response = await base.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false);

#if DEBUG
        var rawResponse = await response.Content.ReadAsStringAsync();
        _logger.LogDebug(rawResponse);
#endif

        return response;
    }

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
No request validation: New request DTOs (e.g., EdgeCreationModel, NodeCreationModel with EmbeddingInfo) introduce
externally supplied fields without any visible validation/sanitization constraints, so
correctness and security depend on validation elsewhere.

Referred Code
public class EdgeCreationModel
{
    public string? Id { get; set; }
    public string SourceNodeId { get; set; } = null!;
    public string TargetNodeId { get; set; } = null!;
    public string Type { get; set; } = null!;
    public bool Directed { get; set; } = true;
    public float? Weight { get; set; } = 1.0f;
    public object? Properties { get; set; }
}

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

qodo-code-review bot commented Jan 7, 2026

PR Code Suggestions ✨

Latest suggestions up to dfdec70

CategorySuggestion                                                                                                                                    Impact
Incremental [*]
Return proper not-found response

Modify the GetConversation method to return an ActionResult instead of
ConversationViewModel?. When a conversation is not found, return an HTTP 404
NotFound() result instead of an empty object to provide clearer API responses.

src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs [145-188]

-public async Task<ConversationViewModel?> GetConversation([FromRoute] string conversationId, [FromQuery] bool isLoadStates = false)
+public async Task<ActionResult<ConversationViewModel>> GetConversation([FromRoute] string conversationId, [FromQuery] bool isLoadStates = false)
 {
     var convService = _services.GetRequiredService<IConversationService>();
     var userService = _services.GetRequiredService<IUserService>();
     var settings = _services.GetRequiredService<PluginSettings>();
 
     var (isAdmin, user) = await userService.IsAdminUser(_user.Id);
 
     var filter = new ConversationFilter
     {
         Id = conversationId,
         UserId = !isAdmin ? user?.Id : null,
         IsLoadLatestStates = isLoadStates
     };
 
     var conversations = await convService.GetConversations(filter);
     var conversation = conversations.Items?.FirstOrDefault();
     if (conversation == null)
     {
-        return new();
+        return NotFound();
     }
 
     user = !string.IsNullOrEmpty(conversation.UserId)
             ? await userService.GetUser(conversation.UserId)
             : null;
 
     if (user == null)
     {
         user = new User
         {
             Id = _user.Id,
             UserName = _user.UserName,
             FirstName = _user.FirstName,
             LastName = _user.LastName,
             Email = _user.Email,
             Source = "Unknown"
         };
     }
 
     var conversationView = ConversationViewModel.FromSession(conversation);
     conversationView.User = UserViewModel.FromUser(user);
     conversationView.IsRealtimeEnabled = settings?.Assemblies?.Contains("BotSharp.Core.Realtime") ?? false;
     return conversationView;
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly points out that returning an empty object on failure is poor API design. Returning a proper HTTP status code like 404 is a significant improvement for client-side error handling and API semantics, and also has security benefits.

Medium
Avoid logging raw file data

In the ToString() method, instead of returning a substring of FileData, return a
summary string like "". This prevents
accidentally logging potentially large or sensitive file content.

src/Infrastructure/BotSharp.Abstraction/Files/Models/InstructFileModel.cs [32-35]

 else if (!string.IsNullOrEmpty(FileData))
 {
-    return FileData.SubstringMax(50);
+    return $"<inline-file-data len={FileData.Length}>";
 }
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This suggestion correctly identifies a potential security and logging issue where raw file data could be leaked into logs via ToString(). Replacing the raw data with a summary is a good practice for security and log management, especially since the PR increased the amount of data being exposed.

Medium
Prevent null list crashes

Revert the Files property from a nullable list back to a non-nullable list
initialized as empty. This change prevents potential NullReferenceException and
adheres to best practices for handling collections.

src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/Request/InstructMessageModel.cs [13]

-public List<InstructFileModel>? Files { get; set; }
+public List<InstructFileModel> Files { get; set; } = [];
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies that changing the Files property to be nullable is a regression in code quality, as it can lead to NullReferenceException. Reverting to a non-nullable, initialized list is a standard best practice that improves robustness.

Low
Possible issue
Make deletes resilient to empty bodies

Wrap the return types of DeleteNodeAsync and DeleteEdgeAsync with ApiResponse to
prevent potential Refit deserialization failures when the API returns an empty
response body (e.g., with a 204 No Content status).

src/Plugins/BotSharp.Plugin.Membase/Services/IMembaseApi.cs [30-45]

 [Delete("/graph/{graphId}/node/{nodeId}")]
-Task<NodeDeleteResponse?> DeleteNodeAsync(string graphId, string nodeId);
+Task<ApiResponse<NodeDeleteResponse>> DeleteNodeAsync(string graphId, string nodeId);
 ...
 [Delete("/graph/{graphId}/edge/{edgeId}")]
-Task<EdgeDeleteResponse> DeleteEdgeAsync(string graphId, string edgeId);
+Task<ApiResponse<EdgeDeleteResponse>> DeleteEdgeAsync(string graphId, string edgeId);

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential runtime deserialization error if the DELETE endpoints return a 204 No Content status, which is a common REST practice. Using ApiResponse<T> makes the client more robust by allowing it to handle non-success status codes and empty response bodies gracefully.

Medium
Validate configuration before creating URI

Add validation to ensure settings.Host is a valid absolute URI before
configuring the HttpClient's BaseAddress. This prevents application startup
crashes from invalid configuration and provides a clear error message.

src/Plugins/BotSharp.Plugin.Membase/MembasePlugin.cs [18-24]

 services.AddTransient<MembaseAuthHandler>();
 services.AddRefitClient<IMembaseApi>(new RefitSettings
         {
             CollectionFormat = CollectionFormat.Multi
         })
         .AddHttpMessageHandler<MembaseAuthHandler>()
-        .ConfigureHttpClient(c => c.BaseAddress = new Uri(settings.Host));
+        .ConfigureHttpClient(c =>
+        {
+            if (string.IsNullOrWhiteSpace(settings.Host) ||
+                !Uri.TryCreate(settings.Host, UriKind.Absolute, out var baseUri))
+            {
+                throw new InvalidOperationException("Membase Host must be a valid absolute URI.");
+            }
 
+            c.BaseAddress = baseUri;
+        });
+
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that an invalid settings.Host value can cause the application to crash on startup. Adding validation for the URI makes the application more robust and provides clearer error messages for misconfigurations, improving diagnosability.

Medium
  • Update

Previous suggestions

✅ Suggestions up to commit 6bf71f2
CategorySuggestion                                                                                                                                    Impact
Possible issue
Annotate request body parameter
Suggestion Impact:The CypherQueryAsync method signature was updated to annotate the request parameter with [Body], ensuring it is serialized into the POST request body.

code diff:

     [Post("/cypher/execute")]
-    Task<CypherQueryResponse> CypherQueryAsync([Query] string graphId, CypherQueryRequest request);
+    Task<CypherQueryResponse> CypherQueryAsync([Query] string graphId, [Body] CypherQueryRequest request);

Add the [Body] attribute to the request parameter in the CypherQueryAsync method
to ensure it's sent in the HTTP request body.

src/Plugins/BotSharp.Plugin.Membase/Services/IMembaseApi.cs [15]

-Task<CypherQueryResponse> CypherQueryAsync([Query] string graphId, CypherQueryRequest request);
+Task<CypherQueryResponse> CypherQueryAsync([Query] string graphId, [Body] CypherQueryRequest request);

[Suggestion processed]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly points out the missing [Body] attribute, which is critical for the POST request to function correctly by sending the request object in the body.

High
Avoid consuming the response stream

Avoid consuming the response stream when logging. After reading the response
content for logging, replace it with a new StringContent so it can be read again
by the caller.

src/Plugins/BotSharp.Plugin.Membase/Services/MembaseAuthHandler.cs [20-31]

 protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken)
 {
     requestMessage.Headers.TryAddWithoutValidation("Authorization", $"Bearer {_settings.ApiKey}");
     var response = await base.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false);
 
 #if DEBUG
-    var rawResponse = await response.Content.ReadAsStringAsync();
-    _logger.LogDebug(rawResponse);
+    if (response.Content != null)
+    {
+        var rawResponse = await response.Content.ReadAsStringAsync(cancellationToken);
+        _logger.LogDebug(rawResponse);
+
+        // Re-wrap the content to make it readable again by the caller.
+        response.Content = new StringContent(rawResponse, System.Text.Encoding.UTF8, response.Content.Headers.ContentType?.MediaType ?? "application/json");
+    }
 #endif
 
     return response;
 }
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a critical bug where logging the response body consumes the content stream, which would cause deserialization to fail for the actual caller.

Medium
Ensure handler uses updated settings

To ensure MembaseAuthHandler uses the latest settings if they become reloadable,
register it using a factory in AddHttpMessageHandler to resolve it per request.

src/Plugins/BotSharp.Plugin.Membase/MembasePlugin.cs [18-24]

+services.AddTransient<MembaseAuthHandler>();
+services.AddRefitClient<IMembaseApi>(new RefitSettings
+        {
+            CollectionFormat = CollectionFormat.Multi
+        })
+        .AddHttpMessageHandler<MembaseAuthHandler>()
+        .ConfigureHttpClient(c => c.BaseAddress = new Uri(settings.Host));
 
-
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies a potential DI lifecycle issue with DelegatingHandler, but the problem it describes (stale settings) won't occur with the current singleton registration of MembaseSettings.

Low
General
Initialize embedding properties

Initialize the Model and Vector properties in the EmbeddingInfo class to default
values to prevent potential null reference issues.

src/Plugins/BotSharp.Plugin.Membase/Models/Requests/NodeCreationModel.cs [24-28]

 public class EmbeddingInfo
 {
-    public string Model { get; set; }
-    public float[] Vector { get; set; }
+    public string Model { get; set; } = string.Empty;
+    public float[] Vector { get; set; } = Array.Empty<float>();
 }
Suggestion importance[1-10]: 6

__

Why: The suggestion improves robustness by initializing properties to non-null defaults, preventing potential NullReferenceException issues during object creation or deserialization.

Low
Name tuple return elements

Improve readability by naming the return tuple elements in the
GetCodeExecutionConfig method signature (e.g., (bool useLock, bool useProcess,
int timeoutSeconds)).

src/Infrastructure/BotSharp.Abstraction/Coding/Utils/CodingUtil.cs [11]

-public static (bool, bool, int) GetCodeExecutionConfig(CodingSettings settings, int defaultTimeoutSeconds = 3)
+public static (bool useLock, bool useProcess, int timeoutSeconds) GetCodeExecutionConfig(CodingSettings settings, int defaultTimeoutSeconds = 3)
Suggestion importance[1-10]: 4

__

Why: This is a good suggestion for improving code readability and maintainability by naming the elements of the returned tuple, making the method's signature more self-documenting.

Low
Learned
best practice
Validate auth header and cancellation

Validate the API key before adding the Authorization header and pass the
provided cancellation token when reading the response content.

src/Plugins/BotSharp.Plugin.Membase/Services/MembaseAuthHandler.cs [20-31]

 protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken)
 {
-    requestMessage.Headers.TryAddWithoutValidation("Authorization", $"Bearer {_settings.ApiKey}");
+    if (!string.IsNullOrWhiteSpace(_settings.ApiKey))
+    {
+        requestMessage.Headers.TryAddWithoutValidation("Authorization", $"Bearer {_settings.ApiKey}");
+    }
+
     var response = await base.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false);
 
 #if DEBUG
-    var rawResponse = await response.Content.ReadAsStringAsync();
+    var rawResponse = await response.Content.ReadAsStringAsync(cancellationToken);
     _logger.LogDebug(rawResponse);
 #endif
 
     return response;
 }
Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Improve defensive coding with precise null and state checks to prevent incorrect behavior.

Low

@iceljc iceljc marked this pull request as draft January 10, 2026 18:13
@iceljc iceljc changed the title Features/refine membase refine repository and coding utility Jan 11, 2026
@iceljc iceljc changed the title refine repository and coding utility refine repository and coding util Jan 11, 2026
@iceljc iceljc marked this pull request as ready for review January 12, 2026 16:47
@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Sensitive data exposure

Description: The new SSE endpoint serializes and streams the full InstructResult including
result.States (populated from externally supplied input.* and input.Files) which can
unintentionally expose sensitive data (e.g., file content snippets or secrets placed into
states) to the client and any intermediaries that can observe the response.
InstructModeController.cs [49-171]

Referred Code
[HttpPost("/instruct/{agentId}/sse")]
public async Task InstructCompletionSse([FromRoute] string agentId, [FromBody] InstructMessageModel input)
{
    var state = _services.GetRequiredService<IConversationStateService>();
    SetStates(state, input);

    Response.StatusCode = 200;
    Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.ContentType, "text/event-stream");
    Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.CacheControl, "no-cache");
    Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.Connection, "keep-alive");

    var instructor = _services.GetRequiredService<IInstructService>();
    var result = await instructor.Execute(agentId,
        new RoleDialogModel(AgentRole.User, input.Text),
        instruction: input.Instruction,
        templateName: input.Template,
        files: input.Files,
        codeOptions: input.CodeOptions,
        fileOptions: input.FileOptions);

    result.States = state.GetStates();


 ... (clipped 102 lines)
Sensitive logging

Description: In DEBUG builds the handler logs the raw HTTP response body from Membase
(_logger.LogDebug(rawResponse)), which can leak sensitive graph data, query results, or
error details into logs if DEBUG is enabled in a non-secure environment.
MembaseAuthHandler.cs [20-31]

Referred Code
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken)
    {
        requestMessage.Headers.TryAddWithoutValidation("Authorization", $"Bearer {_settings.ApiKey}");
        var response = await base.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false);

#if DEBUG
        var rawResponse = await response.Content.ReadAsStringAsync();
        _logger.LogDebug(rawResponse);
#endif

        return response;
    }
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🔴
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing null checks: The new SetStates method calls input.States.ForEach(...) without guarding against
input.States being null, which can cause a runtime exception for requests that omit
states.

Referred Code
private void SetStates(IConversationStateService state, InstructMessageModel input)
{
    input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));

    state.SetState("provider", input.Provider, source: StateSource.External)
        .SetState("model", input.Model, source: StateSource.External)
        .SetState("model_id", input.ModelId, source: StateSource.External)
        .SetState("instruction", input.Instruction, source: StateSource.External)
        .SetState("input_text", input.Text, source: StateSource.External)
        .SetState("template_name", input.Template, source: StateSource.External)
        .SetState("channel", input.Channel, source: StateSource.External)
        .SetState("code_options", input.CodeOptions, source: StateSource.External)
        .SetState("file_options", input.FileOptions, source: StateSource.External)
        .SetState("file_count", input.Files?.Count, source: StateSource.External)
        .SetState("file_urls", input.Files?.Select(p => p.ToString()), source: StateSource.External);
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Logs raw responses: The new handler logs the full raw HTTP response body (_logger.LogDebug(rawResponse)) which
can leak sensitive data and is unstructured for auditing.

Referred Code
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken)
    {
        requestMessage.Headers.TryAddWithoutValidation("Authorization", $"Bearer {_settings.ApiKey}");
        var response = await base.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false);

#if DEBUG
        var rawResponse = await response.Content.ReadAsStringAsync();
        _logger.LogDebug(rawResponse);
#endif

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Unvalidated external input: The new SSE endpoint and SetStates write arbitrary user-provided keys/values from input
into conversation state without validation/sanitization, increasing risk of state
poisoning and unexpected downstream behavior.

Referred Code
[HttpPost("/instruct/{agentId}/sse")]
public async Task InstructCompletionSse([FromRoute] string agentId, [FromBody] InstructMessageModel input)
{
    var state = _services.GetRequiredService<IConversationStateService>();
    SetStates(state, input);

    Response.StatusCode = 200;
    Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.ContentType, "text/event-stream");
    Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.CacheControl, "no-cache");
    Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.Connection, "keep-alive");

    var instructor = _services.GetRequiredService<IInstructService>();
    var result = await instructor.Execute(agentId,
        new RoleDialogModel(AgentRole.User, input.Text),
        instruction: input.Instruction,
        templateName: input.Template,
        files: input.Files,
        codeOptions: input.CodeOptions,
        fileOptions: input.FileOptions);

    result.States = state.GetStates();


 ... (clipped 94 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Re-evaluate the Server-Sent Events implementation

The SSE endpoint should be refactored to stream partial results as they are
generated, rather than computing the entire response and sending it as a single
event. This requires modifying the underlying service to support streaming.

Examples:

src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.cs [50-72]
    public async Task InstructCompletionSse([FromRoute] string agentId, [FromBody] InstructMessageModel input)
    {
        var state = _services.GetRequiredService<IConversationStateService>();
        SetStates(state, input);

        Response.StatusCode = 200;
        Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.ContentType, "text/event-stream");
        Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.CacheControl, "no-cache");
        Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.Connection, "keep-alive");


 ... (clipped 13 lines)

Solution Walkthrough:

Before:

public async Task InstructCompletionSse(...)
{
    // ... set up response headers for SSE

    // Await the full result from the service
    var result = await instructor.Execute(...);

    // Send the complete result as a single SSE event
    await OnChunkReceived(Response, result);
}

private async Task OnChunkReceived(HttpResponse response, InstructResult result)
{
    var json = JsonSerializer.Serialize(result, ...);
    var buffer = Encoding.UTF8.GetBytes($"data:{json}\n\n");
    await response.Body.WriteAsync(buffer);
    await response.Body.FlushAsync();
}

After:

public async Task InstructCompletionSse(...)
{
    // ... set up response headers for SSE

    // The service now returns a stream of partial results
    // (e.g., using IAsyncEnumerable or a callback)
    await foreach (var partialResult in instructor.ExecuteStream(...))
    {
        // Send each partial result as a separate SSE event
        await SendSseChunk(Response, partialResult);
    }
}

private async Task SendSseChunk(HttpResponse response, PartialResult partialResult)
{
    var json = JsonSerializer.Serialize(partialResult, ...);
    var buffer = Encoding.UTF8.GetBytes($"data:{json}\n\n");
    await response.Body.WriteAsync(buffer);
    await response.Body.FlushAsync();
}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a fundamental design flaw in the new SSE endpoint, where the entire result is computed before being sent as a single event, defeating the purpose of streaming.

High
Possible issue
Prevent null in embedding info

Initialize the Model and Vector properties in the EmbeddingInfo class to
non-null default values to avoid potential null reference errors.

src/Plugins/BotSharp.Plugin.Membase/Models/Requests/NodeCreationModel.cs [24-28]

 public class EmbeddingInfo
 {
-    public string Model { get; set; }
-    public float[] Vector { get; set; }
+    public string Model { get; set; } = string.Empty;
+    public float[] Vector { get; set; } = Array.Empty<float>();
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: Initializing properties to non-null default values is a good practice that prevents potential NullReferenceExceptions and makes the class more robust.

Low
Learned
best practice
Add null-safe state setting

Guard against input/input.States being null (and optionally null keys) before
iterating to avoid NullReferenceException when clients omit fields.

src/Infrastructure/BotSharp.OpenAPI/Controllers/Instruct/InstructModeController.cs [148-163]

 private void SetStates(IConversationStateService state, InstructMessageModel input)
 {
-    input.States.ForEach(x => state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External));
+    if (input?.States != null)
+    {
+        foreach (var x in input.States.Where(s => !string.IsNullOrWhiteSpace(s.Key)))
+        {
+            state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External);
+        }
+    }
 
-    state.SetState("provider", input.Provider, source: StateSource.External)
-        .SetState("model", input.Model, source: StateSource.External)
-        .SetState("model_id", input.ModelId, source: StateSource.External)
-        .SetState("instruction", input.Instruction, source: StateSource.External)
-        .SetState("input_text", input.Text, source: StateSource.External)
-        .SetState("template_name", input.Template, source: StateSource.External)
-        .SetState("channel", input.Channel, source: StateSource.External)
-        .SetState("code_options", input.CodeOptions, source: StateSource.External)
-        .SetState("file_options", input.FileOptions, source: StateSource.External)
-        .SetState("file_count", input.Files?.Count, source: StateSource.External)
-        .SetState("file_urls", input.Files?.Select(p => p.ToString()), source: StateSource.External);
+    state.SetState("provider", input?.Provider, source: StateSource.External)
+        .SetState("model", input?.Model, source: StateSource.External)
+        .SetState("model_id", input?.ModelId, source: StateSource.External)
+        .SetState("instruction", input?.Instruction, source: StateSource.External)
+        .SetState("input_text", input?.Text, source: StateSource.External)
+        .SetState("template_name", input?.Template, source: StateSource.External)
+        .SetState("channel", input?.Channel, source: StateSource.External)
+        .SetState("code_options", input?.CodeOptions, source: StateSource.External)
+        .SetState("file_options", input?.FileOptions, source: StateSource.External)
+        .SetState("file_count", input?.Files?.Count, source: StateSource.External)
+        .SetState("file_urls", input?.Files?.Select(p => p.ToString()), source: StateSource.External);
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Add defensive null checks for request collections/properties before iterating or dereferencing to prevent runtime exceptions.

Low
Don’t consume response content

Buffer the content before reading/logging (or skip body logging) so you don’t
consume the response stream and break Refit’s deserialization.

src/Plugins/BotSharp.Plugin.Membase/Services/MembaseAuthHandler.cs [20-31]

 protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage requestMessage, CancellationToken cancellationToken)
 {
     requestMessage.Headers.TryAddWithoutValidation("Authorization", $"Bearer {_settings.ApiKey}");
     var response = await base.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false);
 
 #if DEBUG
-    var rawResponse = await response.Content.ReadAsStringAsync();
-    _logger.LogDebug(rawResponse);
+    if (response.Content != null)
+    {
+        await response.Content.LoadIntoBufferAsync(cancellationToken).ConfigureAwait(false);
+        var rawResponse = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
+        _logger.LogDebug(rawResponse);
+    }
 #endif
 
     return response;
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Avoid consuming response streams in handlers; preserve error/details without breaking downstream processing.

Low
General
Fix method name typo

Fix the typo in the method name from GetConversationStateSearhKeys to
GetConversationStateSearchKeys and update all its usages.

src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs [64]

-Task<List<string>> GetConversationStateSearhKeys(ConversationStateKeysFilter filter);
+Task<List<string>> GetConversationStateSearchKeys(ConversationStateKeysFilter filter);

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies a typo in a method name. Fixing it improves code clarity and maintainability, and the suggestion also correctly notes that all references must be updated.

Low
Name tuple return values

Add names to the tuple elements in the return type of GetCodeExecutionConfig to
improve readability.

src/Infrastructure/BotSharp.Abstraction/Coding/Utils/CodingUtil.cs [11-20]

-public static (bool, bool, int) GetCodeExecutionConfig(CodingSettings settings, int defaultTimeoutSeconds = 3)
+public static (bool useLock, bool useProcess, int timeoutSeconds) GetCodeExecutionConfig(CodingSettings settings, int defaultTimeoutSeconds = 3)
 {
     var codeExecution = settings.CodeExecution;
 
     var useLock = codeExecution?.UseLock ?? false;
     var useProcess = codeExecution?.UseProcess ?? false;
     var timeoutSeconds = codeExecution?.TimeoutSeconds > 0 ? codeExecution.TimeoutSeconds : defaultTimeoutSeconds;
 
     return (useLock, useProcess, timeoutSeconds);
 }
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out that naming tuple return values improves code readability and maintainability for consumers of the method.

Low
  • More

@iceljc iceljc merged commit ee73e6e into SciSharp:master Jan 12, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant