Skip to content

Conversation

@yileicn
Copy link
Member

@yileicn yileicn commented Dec 24, 2025

PR Type

Enhancement


Description

  • Convert CreateNewConversation to async Task method

  • Replace synchronous File.WriteAllText with async variants

  • Add async database operations in MongoDB repository

  • Handle duplicate key errors gracefully in MongoDB


Diagram Walkthrough

flowchart LR
  IBotSharp["IBotSharpRepository interface"]
  ConvService["ConversationService.NewConversation"]
  FileRepo["FileRepository.CreateNewConversation"]
  MongoRepo["MongoRepository.CreateNewConversation"]
  
  IBotSharp -- "async Task" --> ConvService
  ConvService -- "await async call" --> FileRepo
  ConvService -- "await async call" --> MongoRepo
  FileRepo -- "async File I/O" --> FileOps["File operations"]
  MongoRepo -- "async DB operations" --> MongoOps["MongoDB operations"]
Loading

File Walkthrough

Relevant files
Enhancement
IBotSharpRepository.cs
Convert CreateNewConversation to async Task                           

src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

  • Changed CreateNewConversation method signature from void to Task
  • Enables async/await pattern for conversation creation
+1/-1     
ConversationService.cs
Await async CreateNewConversation call                                     

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs

  • Added await keyword when calling CreateNewConversation
  • Aligns with new async method signature
+1/-1     
FileRepository.Conversation.cs
Implement async file operations                                                   

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs

  • Changed method signature from void to async Task
  • Replaced all File.WriteAllText calls with File.WriteAllTextAsync
  • Enables non-blocking file I/O operations
+6/-6     
MongoRepository.Conversation.cs
Implement async MongoDB operations with error handling     

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs

  • Changed method signature from void to async Task
  • Replaced synchronous InsertOne calls with InsertOneAsync
  • Added try-catch block to handle duplicate key errors (code 11000)
  • Gracefully returns on duplicate key conflicts
+12/-4   

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
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: Meaningful Naming and Self-Documenting Code

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

Status: Passed

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

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: Robust Error Handling and Edge Case Management

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

Status:
Silent exception handling: The duplicate-key MongoDB write error is caught and silently ignored (returns without
logging/telemetry or a clear outcome), and file/database write failure paths are otherwise
unhandled, reducing debuggability and robustness.

Referred Code
try
{
    await _dc.Conversations.InsertOneAsync(convDoc);
    await _dc.ConversationDialogs.InsertOneAsync(dialogDoc);
    await _dc.ConversationStates.InsertOneAsync(stateDoc);
}
catch (MongoWriteException ex) when (ex.WriteError?.Code == 11000)
{
    //11000  duplicate key error
    return;
}

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:
Missing null validation: CreateNewConversation in FileRepository does not validate conversation for null before
dereferencing it, which can cause unexpected failures when called with external/unchecked
inputs.

Referred Code
public async Task CreateNewConversation(Conversation conversation)
{
    var utcNow = DateTime.UtcNow;
    conversation.CreatedTime = utcNow;
    conversation.UpdatedTime = utcNow;

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: The PR adds/updates asynchronous conversation creation and persistence but does not add
any audit log entries that record who performed the action, when it occurred, and the
outcome.

Referred Code
public async Task CreateNewConversation(Conversation conversation)
{
    var utcNow = DateTime.UtcNow;
    conversation.CreatedTime = utcNow;
    conversation.UpdatedTime = utcNow;
    conversation.Tags ??= new();

    var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir, conversation.Id);
    if (!Directory.Exists(dir))
    {
        Directory.CreateDirectory(dir);
    }

    var convFile = Path.Combine(dir, CONVERSATION_FILE);
    if (!File.Exists(convFile))
    {
        await File.WriteAllTextAsync(convFile, JsonSerializer.Serialize(conversation, _options));
    }

    var dialogFile = Path.Combine(dir, DIALOG_FILE);
    if (!File.Exists(dialogFile))


 ... (clipped 22 lines)

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:
No structured logging: The PR introduces new async persistence behavior and exception handling without adding
structured logs, so it is unclear whether operational/audit logs exist elsewhere and
whether they avoid sensitive fields (e.g., UserId).

Referred Code
public async Task CreateNewConversation(Conversation conversation)
{
    if (conversation == null) return;

    var utcNow = DateTime.UtcNow;
    var userId = !string.IsNullOrEmpty(conversation.UserId) ? conversation.UserId : string.Empty;
    var convDoc = new ConversationDocument
    {
        Id = !string.IsNullOrEmpty(conversation.Id) ? conversation.Id : Guid.NewGuid().ToString(),
        AgentId = conversation.AgentId,
        UserId = userId,
        Title = conversation.Title,
        Channel = conversation.Channel,
        ChannelId = conversation.ChannelId,
        TaskId = conversation.TaskId,
        Status = conversation.Status,
        Tags = conversation.Tags ?? new(),
        CreatedTime = utcNow,
        UpdatedTime = utcNow,
        LatestStates = []
    };


 ... (clipped 33 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
Possible issue
Ensure atomic multi-insert with transaction

Ensure atomic insertion of conversation documents by wrapping the InsertOneAsync
calls in a MongoDB transaction to prevent data inconsistency in case of partial
failure.

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs [52-62]

+using var session = await _client.StartSessionAsync();
+session.StartTransaction();
 try
 {
-    await _dc.Conversations.InsertOneAsync(convDoc);
-    await _dc.ConversationDialogs.InsertOneAsync(dialogDoc);
-    await _dc.ConversationStates.InsertOneAsync(stateDoc);
+    await _dc.Conversations.InsertOneAsync(session, convDoc);
+    await _dc.ConversationDialogs.InsertOneAsync(session, dialogDoc);
+    await _dc.ConversationStates.InsertOneAsync(session, stateDoc);
+    await session.CommitTransactionAsync();
 }
 catch (MongoWriteException ex) when (ex.WriteError?.Code == 11000)
 {
-    //11000  duplicate key error
+    await session.AbortTransactionAsync();
+    // duplicate key conflict, skip
     return;
 }
+catch
+{
+    await session.AbortTransactionAsync();
+    throw;
+}
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical data integrity issue where partial data could be created and proposes a robust solution using transactions to ensure atomicity.

High
General
Run independent file writes concurrently

Improve performance by executing the independent File.WriteAllTextAsync
operations concurrently using Task.WhenAll instead of awaiting them
sequentially.

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs [24-52]

+var tasks = new List<Task>();
 var convFile = Path.Combine(dir, CONVERSATION_FILE);
 if (!File.Exists(convFile))
 {
-    await File.WriteAllTextAsync(convFile, JsonSerializer.Serialize(conversation, _options));
+    tasks.Add(File.WriteAllTextAsync(convFile, JsonSerializer.Serialize(conversation, _options)));
 }
 
 var dialogFile = Path.Combine(dir, DIALOG_FILE);
 if (!File.Exists(dialogFile))
 {
-    await File.WriteAllTextAsync(dialogFile, "[]");
+    tasks.Add(File.WriteAllTextAsync(dialogFile, "[]"));
 }
 
 var stateFile = Path.Combine(dir, STATE_FILE);
 if (!File.Exists(stateFile))
 {
-    await File.WriteAllTextAsync(stateFile, "[]");
+    tasks.Add(File.WriteAllTextAsync(stateFile, "[]"));
 }
 
 var latestStateFile = Path.Combine(dir, CONV_LATEST_STATE_FILE);
 if (!File.Exists(latestStateFile))
 {
-    await File.WriteAllTextAsync(latestStateFile, "{}");
+    tasks.Add(File.WriteAllTextAsync(latestStateFile, "{}"));
 }
 
 var breakpointFile = Path.Combine(dir, BREAKPOINT_FILE);
 if (!File.Exists(breakpointFile))
 {
-    await File.WriteAllTextAsync(breakpointFile, "[]");
+    tasks.Add(File.WriteAllTextAsync(breakpointFile, "[]"));
 }
 
+if (tasks.Any())
+{
+    await Task.WhenAll(tasks);
+}
+
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies an opportunity to improve performance by running independent I/O operations concurrently, which is a good practice for async code.

Low
Learned
best practice
Log swallowed duplicate-key errors

Don’t silently ignore duplicate key errors; at minimum log the exception (with
conversation id/user id) or return a result/throw so callers can handle the
failure with full context.

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs [52-62]

 try
 {
     await _dc.Conversations.InsertOneAsync(convDoc);
     await _dc.ConversationDialogs.InsertOneAsync(dialogDoc);
     await _dc.ConversationStates.InsertOneAsync(stateDoc);
 }
 catch (MongoWriteException ex) when (ex.WriteError?.Code == 11000)
 {
-    //11000  duplicate key error
+    _logger.LogWarning(ex,
+        "Duplicate conversation insert ignored. ConversationId={ConversationId}, UserId={UserId}, AgentId={AgentId}",
+        convDoc.Id, userId, conversation.AgentId);
     return;
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Preserve error details instead of swallowing exceptions; include exception messages (or at least log them) rather than returning silently on failures.

Low
Add cancellation token to async I/O

Add an optional CancellationToken parameter to the async repository method and
propagate it to WriteAllTextAsync/InsertOneAsync so callers can cancel
long-running I/O safely.

src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs [130-131]

-Task CreateNewConversation(Conversation conversation)
+Task CreateNewConversation(Conversation conversation, CancellationToken cancellationToken = default)
     => throw new NotImplementedException();
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why:
Relevant best practice - Use correct async patterns by accepting and propagating CancellationToken for async I/O to avoid non-cancellable operations.

Low
  • More

@JackJiang1234
Copy link
Contributor

reviewed

@yileicn yileicn merged commit cee7956 into SciSharp:master Dec 25, 2025
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.

2 participants