Skip to content

Refactor IMessageContext to use single-layer dependency injection architecture#564

Draft
Copilot wants to merge 4 commits into
mainfrom
copilot/fix-6f12de5b-440e-47cb-8081-cb9f08c3dfba
Draft

Refactor IMessageContext to use single-layer dependency injection architecture#564
Copilot wants to merge 4 commits into
mainfrom
copilot/fix-6f12de5b-440e-47cb-8081-cb9f08c3dfba

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Aug 19, 2025

Problem

Previously, IMessageContext was created manually using the ToMessageContext() extension method within handlers, making it difficult to use dependency injection patterns. Additionally, the ParallelNonBlockingPublisher ran handlers using Task.Run without proper scope management, preventing handlers from accessing scoped services and potentially causing memory leaks.

The GenAi project also used a dual-layer scoping approach where IMessageContext was both provided through dependency injection and stored in Kernel.Data, creating unnecessary complexity.

Solution

This PR refactors the architecture to provide IMessageContext as a scoped dependency with a single, clean dependency injection layer.

Key Changes

1. MessageContextProvider

  • Added a new scoped service that holds the current Discord message context
  • Enables proper dependency injection of message context

2. Enhanced ParallelNonBlockingPublisher

  • Now extracts message context from notifications (MessageReceived, ReactionAdded)
  • Creates child scopes for each handler with proper disposal
  • Sets the message context in each child scope before handler execution
  • Eliminates memory leaks through proper scope cleanup

3. Single-Layer Dependency Injection

  • MessageContextProvider registered as scoped
  • IMessageContext registered as scoped, resolving from the provider
  • Eliminated redundant Kernel.Data storage pattern from GenAi project
  • All components now use pure dependency injection

4. Simplified GenAi Architecture

  • Removed KernelExtensions.cs and SetMessageContext/GetMessageContext methods
  • Updated IPersonality interface to remove IMessageContext parameters
  • Updated PersonalityBase to inject IMessageContext in constructor
  • Updated ImageGenerationPlugin to inject IMessageContext directly
  • Updated all 10 personality implementations to use dependency injection

Updated Handler Patterns

// Before: Manual context creation + dual scoping
public class MentionHandler(ICompletionService completionService)
{
    public async Task Handle(MessageReceived notification, CancellationToken cancellationToken)
    {
        var response = await completionService.Lorekeeper.InvokeAsync(chatHistory, notification.Message.ToMessageContext(), cancellationToken);
    }
}

// After: Pure dependency injection
public class MentionHandler(ICompletionService completionService, IMessageContext messageContext)
{
    public async Task Handle(MessageReceived notification, CancellationToken cancellationToken)
    {
        var response = await completionService.Lorekeeper.InvokeAsync(chatHistory, cancellationToken);
    }
}

Architecture Benefits

  • ✅ Single Scope Layer: Eliminated dual DI + Kernel.Data approach
  • ✅ Memory Safety: Proper scope disposal prevents memory leaks
  • ✅ Clean Dependency Injection: All components use consistent DI patterns
  • ✅ Parallel Processing: Maintains non-blocking parallel execution
  • ✅ Simplified Codebase: 18 files changed, 49 insertions(+), 58 deletions(-)
  • ✅ Type Safety: Handlers for non-message notifications won't accidentally inject IMessageContext

Testing

The PR includes temporary test handlers that validate the dependency injection:

  • Send a message containing !test-context to test MessageReceived notifications
  • React with ✅ or ✨ to messages containing !test-reaction to test ReactionAdded notifications

These test handlers confirm that IMessageContext is properly injected with the correct user context and can successfully reply to messages.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits August 19, 2025 18:36
…support

Co-authored-by: UncleDave <3244206+UncleDave@users.noreply.github.com>
…jection

Co-authored-by: UncleDave <3244206+UncleDave@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor IMessageContext and DiscordMessageContext to be provided as a scoped dependency. The scope should be based on a discord interaction, such as a message. ParallelNonBlockingPublisher may need to be refactored and take on additional respons... Refactor IMessageContext to be provided as scoped dependency Aug 19, 2025
Copilot AI requested a review from UncleDave August 19, 2025 18:41
@UncleDave
Copy link
Copy Markdown
Owner

@copilot include the uses of IMessageContext in the GenAi project. Also consider whether two layers of scopes is required.

…data storage, eliminating dual-layer scoping

Co-authored-by: UncleDave <3244206+UncleDave@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Aug 19, 2025

@copilot include the uses of IMessageContext in the GenAi project. Also consider whether two layers of scopes is required.

I've addressed both points in commit ea03fc3:

GenAi IMessageContext Usage: Updated all GenAi components to use dependency injection instead of the kernel data storage pattern:

  • Removed KernelExtensions.cs and KernelDataMessageContextKey constant
  • Updated PersonalityBase to inject IMessageContext in constructor
  • Updated IPersonality interface to remove IMessageContext parameters
  • Updated ImageGenerationPlugin to inject IMessageContext directly
  • Updated all 10 personality implementations and follower response strategies

Eliminated Dual-Layer Scoping: The architecture now uses a single DI scope layer instead of DI + Kernel.Data:

  • GenAi components access IMessageContext purely through dependency injection
  • Removed redundant kernel data storage mechanism
  • Net result: 18 files changed, 49 insertions(+), 58 deletions(-) - simplified the codebase

The GenAi project now has a cleaner architecture with consistent dependency injection patterns throughout.

Copilot AI changed the title Refactor IMessageContext to be provided as scoped dependency Refactor IMessageContext to use single-layer dependency injection architecture Aug 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants