elsa-core/test/unit/Elsa.AI.Host.UnitTests/Context/AIContextResolverTests.cs

185 lines
6.9 KiB
C#
Raw Normal View History

Implement Weaver AI Copilot core (#7523) * Implement Weaver AI Copilot core * Address Greptile review feedback * Address Greptile persistence feedback * Address Greptile orchestration feedback * Address Greptile tool isolation feedback * Wire chat audit events * Stream chat events over SSE * Use server identity for AI endpoints * Validate AI proposal persistence * Isolate AI audit failures * Enforce AI tool lookup scope * Support AI tool result continuations * Handle AI chat reconnects safely * Tighten AI context and reconnect behavior * Guard AI conversation and persistence setup * Persist AI tool-loop progress * Tighten AI tool registry and reconnect cleanup * Handle AI preparation failures cleanly * Order AI tool messages after assistant turns * Initialize AI provider sessions * Align AI context capabilities * Prevent completed AI reconnect replay * Enforce AI conversation ownership * Default AI proposal creation time * Persist AI session and retention defaults * Allow AI context provider overrides * Scope AI tool results per turn * Apply AI provider configuration * Scope AI proposal reads * Avoid duplicate AI tool continuations * Resolve AI tool registry scopes * Tighten AI reconnect cleanup * Honor default AI proposal tools * Pass AI provider session to turns * Close AI observability gaps * Fix AI capabilities options alias * Harden AI orchestration lifetimes * Track actual AI reconnect conversation * Address AI audit and context review findings * Fix AI reconnect and persistence capabilities * Handle AI session startup failures * Tighten AI orchestration review gaps * Warn on placeholder AI context * Filter disabled AI provider tools * Add durable AI conversation persistence * Fix AI orchestrator persistence lifetime * Handle failed AI reconnect edge cases * Harden AI reconnect failure handling * Address AI reconnect and cleanup review gaps * Tighten AI audit and cleanup persistence * Keep expired AI cleanup best effort * Tighten AI tool lookup and cleanup fallback * Handle AI provider and tenant edge cases * Tighten AI proposal and agent authorization * Address AI tool scope cleanup review * Close remaining AI greptile findings * Harden AI stores and tool defaults * Harden AI conversation persistence edge cases * Cover AI proposal and tool visibility guards * Fix AI capabilities and audit batch resilience * Fix AI conversation truncation for unicode * Resolve remaining AI persistence review nits * Wire AI conversation persistence option * Address AI audit and proposal style review * Fix AI stream truncation surrogate handling * Address AI context and cleanup review * Preserve AI titles and tenant tool defaults * Guard AI conversation user ownership * Align in-memory AI conversation ownership * Fix expired AI conversation cleanup tracking * Harden AI proposal persistence retry * Tighten AI proposal reads and cleanup SQL * Harden AI reconnect and provider defaults * Optimize AI tool listing and message trimming * Preserve AI conversation timestamps * Address final AI persistence review nits * Normalize AI acronym casing * Address Copilot AI review comments * Normalize default tenant handling for AI stores * Harden AI registry and message truncation * Make AI tool filtering explicit * Align AI contracts with implementation * Align remaining AI review contracts * address greptile ai persistence feedback * Address Copilot AI persistence feedback * Address Copilot AI host feedback * Order persisted AI conversation messages * Address Copilot chat and cleanup feedback * Release unused AI reconnect reservations * Address Copilot AI review feedback * Address Copilot tool and conversation feedback * Address Copilot governance feedback * Address Copilot tool test feedback * Address AI review follow-ups * Address Copilot AI follow-ups * Clean up AI persistence tests * Address IAITool disposal review * Address AI integration review follow-ups * Address AI chat persistence review * Address AI registry and truncation review * Enable read-only AI tools by default * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-04 09:35:44 +00:00
using Elsa.AI.Abstractions.Contracts;
using Elsa.AI.Abstractions.Models;
using Elsa.AI.Host.Context;
using Elsa.AI.Host.Options;
using Elsa.AI.Host.Services;
Implement Weaver AI Copilot core (#7523) * Implement Weaver AI Copilot core * Address Greptile review feedback * Address Greptile persistence feedback * Address Greptile orchestration feedback * Address Greptile tool isolation feedback * Wire chat audit events * Stream chat events over SSE * Use server identity for AI endpoints * Validate AI proposal persistence * Isolate AI audit failures * Enforce AI tool lookup scope * Support AI tool result continuations * Handle AI chat reconnects safely * Tighten AI context and reconnect behavior * Guard AI conversation and persistence setup * Persist AI tool-loop progress * Tighten AI tool registry and reconnect cleanup * Handle AI preparation failures cleanly * Order AI tool messages after assistant turns * Initialize AI provider sessions * Align AI context capabilities * Prevent completed AI reconnect replay * Enforce AI conversation ownership * Default AI proposal creation time * Persist AI session and retention defaults * Allow AI context provider overrides * Scope AI tool results per turn * Apply AI provider configuration * Scope AI proposal reads * Avoid duplicate AI tool continuations * Resolve AI tool registry scopes * Tighten AI reconnect cleanup * Honor default AI proposal tools * Pass AI provider session to turns * Close AI observability gaps * Fix AI capabilities options alias * Harden AI orchestration lifetimes * Track actual AI reconnect conversation * Address AI audit and context review findings * Fix AI reconnect and persistence capabilities * Handle AI session startup failures * Tighten AI orchestration review gaps * Warn on placeholder AI context * Filter disabled AI provider tools * Add durable AI conversation persistence * Fix AI orchestrator persistence lifetime * Handle failed AI reconnect edge cases * Harden AI reconnect failure handling * Address AI reconnect and cleanup review gaps * Tighten AI audit and cleanup persistence * Keep expired AI cleanup best effort * Tighten AI tool lookup and cleanup fallback * Handle AI provider and tenant edge cases * Tighten AI proposal and agent authorization * Address AI tool scope cleanup review * Close remaining AI greptile findings * Harden AI stores and tool defaults * Harden AI conversation persistence edge cases * Cover AI proposal and tool visibility guards * Fix AI capabilities and audit batch resilience * Fix AI conversation truncation for unicode * Resolve remaining AI persistence review nits * Wire AI conversation persistence option * Address AI audit and proposal style review * Fix AI stream truncation surrogate handling * Address AI context and cleanup review * Preserve AI titles and tenant tool defaults * Guard AI conversation user ownership * Align in-memory AI conversation ownership * Fix expired AI conversation cleanup tracking * Harden AI proposal persistence retry * Tighten AI proposal reads and cleanup SQL * Harden AI reconnect and provider defaults * Optimize AI tool listing and message trimming * Preserve AI conversation timestamps * Address final AI persistence review nits * Normalize AI acronym casing * Address Copilot AI review comments * Normalize default tenant handling for AI stores * Harden AI registry and message truncation * Make AI tool filtering explicit * Align AI contracts with implementation * Align remaining AI review contracts * address greptile ai persistence feedback * Address Copilot AI persistence feedback * Address Copilot AI host feedback * Order persisted AI conversation messages * Address Copilot chat and cleanup feedback * Release unused AI reconnect reservations * Address Copilot AI review feedback * Address Copilot tool and conversation feedback * Address Copilot governance feedback * Address Copilot tool test feedback * Address AI review follow-ups * Address Copilot AI follow-ups * Clean up AI persistence tests * Address IAITool disposal review * Address AI integration review follow-ups * Address AI chat persistence review * Address AI registry and truncation review * Enable read-only AI tools by default * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-04 09:35:44 +00:00
using Microsoft.Extensions.DependencyInjection;
using MicrosoftOptions = Microsoft.Extensions.Options.Options;
Implement Weaver AI Copilot core (#7523) * Implement Weaver AI Copilot core * Address Greptile review feedback * Address Greptile persistence feedback * Address Greptile orchestration feedback * Address Greptile tool isolation feedback * Wire chat audit events * Stream chat events over SSE * Use server identity for AI endpoints * Validate AI proposal persistence * Isolate AI audit failures * Enforce AI tool lookup scope * Support AI tool result continuations * Handle AI chat reconnects safely * Tighten AI context and reconnect behavior * Guard AI conversation and persistence setup * Persist AI tool-loop progress * Tighten AI tool registry and reconnect cleanup * Handle AI preparation failures cleanly * Order AI tool messages after assistant turns * Initialize AI provider sessions * Align AI context capabilities * Prevent completed AI reconnect replay * Enforce AI conversation ownership * Default AI proposal creation time * Persist AI session and retention defaults * Allow AI context provider overrides * Scope AI tool results per turn * Apply AI provider configuration * Scope AI proposal reads * Avoid duplicate AI tool continuations * Resolve AI tool registry scopes * Tighten AI reconnect cleanup * Honor default AI proposal tools * Pass AI provider session to turns * Close AI observability gaps * Fix AI capabilities options alias * Harden AI orchestration lifetimes * Track actual AI reconnect conversation * Address AI audit and context review findings * Fix AI reconnect and persistence capabilities * Handle AI session startup failures * Tighten AI orchestration review gaps * Warn on placeholder AI context * Filter disabled AI provider tools * Add durable AI conversation persistence * Fix AI orchestrator persistence lifetime * Handle failed AI reconnect edge cases * Harden AI reconnect failure handling * Address AI reconnect and cleanup review gaps * Tighten AI audit and cleanup persistence * Keep expired AI cleanup best effort * Tighten AI tool lookup and cleanup fallback * Handle AI provider and tenant edge cases * Tighten AI proposal and agent authorization * Address AI tool scope cleanup review * Close remaining AI greptile findings * Harden AI stores and tool defaults * Harden AI conversation persistence edge cases * Cover AI proposal and tool visibility guards * Fix AI capabilities and audit batch resilience * Fix AI conversation truncation for unicode * Resolve remaining AI persistence review nits * Wire AI conversation persistence option * Address AI audit and proposal style review * Fix AI stream truncation surrogate handling * Address AI context and cleanup review * Preserve AI titles and tenant tool defaults * Guard AI conversation user ownership * Align in-memory AI conversation ownership * Fix expired AI conversation cleanup tracking * Harden AI proposal persistence retry * Tighten AI proposal reads and cleanup SQL * Harden AI reconnect and provider defaults * Optimize AI tool listing and message trimming * Preserve AI conversation timestamps * Address final AI persistence review nits * Normalize AI acronym casing * Address Copilot AI review comments * Normalize default tenant handling for AI stores * Harden AI registry and message truncation * Make AI tool filtering explicit * Align AI contracts with implementation * Align remaining AI review contracts * address greptile ai persistence feedback * Address Copilot AI persistence feedback * Address Copilot AI host feedback * Order persisted AI conversation messages * Address Copilot chat and cleanup feedback * Release unused AI reconnect reservations * Address Copilot AI review feedback * Address Copilot tool and conversation feedback * Address Copilot governance feedback * Address Copilot tool test feedback * Address AI review follow-ups * Address Copilot AI follow-ups * Clean up AI persistence tests * Address IAITool disposal review * Address AI integration review follow-ups * Address AI chat persistence review * Address AI registry and truncation review * Enable read-only AI tools by default * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-04 09:35:44 +00:00
using System.Text.Json.Nodes;
namespace Elsa.AI.Host.UnitTests.Context;
public class AIContextResolverTests
{
[Fact(DisplayName = "Context resolver resolves workflow definition references server-side")]
public async Task ContextResolverResolvesWorkflowDefinitionReferences()
{
using var provider = CreateProvider(services => services.AddSingleton<IAIContextProvider, WorkflowDefinitionContextProvider>());
var resolver = provider.GetRequiredService<AIContextResolver>();
var result = await resolver.ResolveAsync(new AIChatRequest
{
UserId = "user-1",
Attachments =
[
new AIContextAttachment
{
Kind = "WorkflowDefinition",
ReferenceId = "workflow-1"
}
]
});
var context = Assert.Single(result);
Assert.Equal("WorkflowDefinition", context.Kind);
Assert.Equal("workflow-1", context.ReferenceId);
Assert.Empty(context.Data);
}
[Fact(DisplayName = "Context resolver redacts sensitive data and metadata keys")]
public async Task ContextResolverRedactsSensitiveDataAndMetadataKeys()
{
using var provider = CreateProvider(services => services.AddSingleton<IAIContextProvider, SensitiveContextProvider>());
var resolver = provider.GetRequiredService<AIContextResolver>();
var result = await resolver.ResolveAsync(new AIChatRequest
{
UserId = "user-1",
Attachments =
[
new AIContextAttachment
{
Kind = "Sensitive"
}
]
});
var context = Assert.Single(result);
Assert.Equal("Handles OAuth token refresh", context.Summary);
Assert.Equal("[redacted]", context.Data["accessToken"]!.GetValue<string>());
Assert.Equal("[redacted]", context.Data["description"]!.GetValue<string>());
Assert.Equal("[redacted]", context.Metadata["apiKey"]!.GetValue<string>());
Assert.Equal("visible", context.Data["displayName"]!.GetValue<string>());
}
[Fact(DisplayName = "Context resolver uses the last provider for duplicate provider kinds")]
public async Task ContextResolverUsesTheLastProviderForDuplicateProviderKinds()
{
using var provider = CreateProvider(services =>
{
services.AddSingleton<IAIContextProvider>(new DuplicateContextProvider("first"));
services.AddSingleton<IAIContextProvider>(new DuplicateContextProvider("second"));
});
var resolver = provider.GetRequiredService<AIContextResolver>();
var result = await resolver.ResolveAsync(new AIChatRequest
{
UserId = "user-1",
Attachments = [new AIContextAttachment { Kind = "Duplicate" }]
});
var context = Assert.Single(result);
Assert.Equal("second", context.Summary);
}
[Fact(DisplayName = "Context resolver prefers real providers over placeholders")]
public async Task ContextResolverPrefersRealProvidersOverPlaceholders()
{
using var provider = CreateProvider(services =>
{
services.AddSingleton<IAIContextProvider>(new DuplicateContextProvider("real", "WorkflowDefinition"));
services.AddSingleton<IAIContextProvider, WorkflowDefinitionContextProvider>();
});
var resolver = provider.GetRequiredService<AIContextResolver>();
var result = await resolver.ResolveAsync(new AIChatRequest
{
UserId = "user-1",
Attachments = [new AIContextAttachment { Kind = "WorkflowDefinition", ReferenceId = "workflow-1" }]
});
var context = Assert.Single(result);
Assert.Equal("real", context.Summary);
}
[Fact(DisplayName = "Context resolver resolves scoped providers from request scopes")]
public async Task ContextResolverResolvesScopedProvidersFromRequestScopes()
{
using var provider = CreateProvider(services => services.AddScoped<IAIContextProvider, ScopedContextProvider>());
var resolver = provider.GetRequiredService<AIContextResolver>();
var result = await resolver.ResolveAsync(new AIChatRequest
{
UserId = "user-1",
Attachments = [new AIContextAttachment { Kind = "Scoped" }]
});
var context = Assert.Single(result);
Assert.Equal("scoped", context.Summary);
}
private static ServiceProvider CreateProvider(Action<IServiceCollection> configure)
{
var services = new ServiceCollection();
services.AddLogging();
services.AddSingleton<AIContextResolver>();
services.AddSingleton<WorkflowGroundingMapper>();
services.AddSingleton(sp => new AIGroundingResultFormatter(MicrosoftOptions.Create(new AIHostOptions())));
services.AddSingleton<RuntimeGroundingMapper>();
Implement Weaver AI Copilot core (#7523) * Implement Weaver AI Copilot core * Address Greptile review feedback * Address Greptile persistence feedback * Address Greptile orchestration feedback * Address Greptile tool isolation feedback * Wire chat audit events * Stream chat events over SSE * Use server identity for AI endpoints * Validate AI proposal persistence * Isolate AI audit failures * Enforce AI tool lookup scope * Support AI tool result continuations * Handle AI chat reconnects safely * Tighten AI context and reconnect behavior * Guard AI conversation and persistence setup * Persist AI tool-loop progress * Tighten AI tool registry and reconnect cleanup * Handle AI preparation failures cleanly * Order AI tool messages after assistant turns * Initialize AI provider sessions * Align AI context capabilities * Prevent completed AI reconnect replay * Enforce AI conversation ownership * Default AI proposal creation time * Persist AI session and retention defaults * Allow AI context provider overrides * Scope AI tool results per turn * Apply AI provider configuration * Scope AI proposal reads * Avoid duplicate AI tool continuations * Resolve AI tool registry scopes * Tighten AI reconnect cleanup * Honor default AI proposal tools * Pass AI provider session to turns * Close AI observability gaps * Fix AI capabilities options alias * Harden AI orchestration lifetimes * Track actual AI reconnect conversation * Address AI audit and context review findings * Fix AI reconnect and persistence capabilities * Handle AI session startup failures * Tighten AI orchestration review gaps * Warn on placeholder AI context * Filter disabled AI provider tools * Add durable AI conversation persistence * Fix AI orchestrator persistence lifetime * Handle failed AI reconnect edge cases * Harden AI reconnect failure handling * Address AI reconnect and cleanup review gaps * Tighten AI audit and cleanup persistence * Keep expired AI cleanup best effort * Tighten AI tool lookup and cleanup fallback * Handle AI provider and tenant edge cases * Tighten AI proposal and agent authorization * Address AI tool scope cleanup review * Close remaining AI greptile findings * Harden AI stores and tool defaults * Harden AI conversation persistence edge cases * Cover AI proposal and tool visibility guards * Fix AI capabilities and audit batch resilience * Fix AI conversation truncation for unicode * Resolve remaining AI persistence review nits * Wire AI conversation persistence option * Address AI audit and proposal style review * Fix AI stream truncation surrogate handling * Address AI context and cleanup review * Preserve AI titles and tenant tool defaults * Guard AI conversation user ownership * Align in-memory AI conversation ownership * Fix expired AI conversation cleanup tracking * Harden AI proposal persistence retry * Tighten AI proposal reads and cleanup SQL * Harden AI reconnect and provider defaults * Optimize AI tool listing and message trimming * Preserve AI conversation timestamps * Address final AI persistence review nits * Normalize AI acronym casing * Address Copilot AI review comments * Normalize default tenant handling for AI stores * Harden AI registry and message truncation * Make AI tool filtering explicit * Align AI contracts with implementation * Align remaining AI review contracts * address greptile ai persistence feedback * Address Copilot AI persistence feedback * Address Copilot AI host feedback * Order persisted AI conversation messages * Address Copilot chat and cleanup feedback * Release unused AI reconnect reservations * Address Copilot AI review feedback * Address Copilot tool and conversation feedback * Address Copilot governance feedback * Address Copilot tool test feedback * Address AI review follow-ups * Address Copilot AI follow-ups * Clean up AI persistence tests * Address IAITool disposal review * Address AI integration review follow-ups * Address AI chat persistence review * Address AI registry and truncation review * Enable read-only AI tools by default * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-04 09:35:44 +00:00
configure(services);
return services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true });
}
private class SensitiveContextProvider : IAIContextProvider
{
public string Kind => "Sensitive";
public ValueTask<AIResolvedContext> ResolveAsync(AIContextResolutionRequest request, CancellationToken cancellationToken = default)
{
return ValueTask.FromResult(new AIResolvedContext
{
Kind = Kind,
Summary = "Handles OAuth token refresh",
Data = new JsonObject
{
["accessToken"] = "token-value",
["description"] = "Bearer eyJhbGciOi",
["displayName"] = "visible"
},
Metadata = new JsonObject
{
["apiKey"] = "key-value"
}
});
}
}
private class DuplicateContextProvider(string summary, string kind = "Duplicate") : IAIContextProvider
{
public string Kind => kind;
public ValueTask<AIResolvedContext> ResolveAsync(AIContextResolutionRequest request, CancellationToken cancellationToken = default)
{
return ValueTask.FromResult(new AIResolvedContext
{
Kind = Kind,
Summary = summary
});
}
}
private class ScopedContextProvider : IAIContextProvider
{
public string Kind => "Scoped";
public ValueTask<AIResolvedContext> ResolveAsync(AIContextResolutionRequest request, CancellationToken cancellationToken = default)
{
return ValueTask.FromResult(new AIResolvedContext
{
Kind = Kind,
Summary = "scoped"
});
}
}
}