* 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>
146 lines
5.1 KiB
C#
146 lines
5.1 KiB
C#
using System.Reflection;
|
|
using System.Security.Claims;
|
|
using Elsa.AI.Abstractions.Contracts;
|
|
using Elsa.AI.Abstractions.Models;
|
|
using Elsa.AI.Host.Endpoints.AI.Tools;
|
|
using Elsa.AI.Host.Options;
|
|
using Elsa.Extensions;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using MicrosoftOptions = Microsoft.Extensions.Options.Options;
|
|
using Request = Elsa.AI.Host.Endpoints.AI.Tools.Request;
|
|
using ToolsEndpoint = Elsa.AI.Host.Endpoints.AI.Tools.Endpoint;
|
|
|
|
namespace Elsa.AI.IntegrationTests;
|
|
|
|
public class AIToolsEndpointTests
|
|
{
|
|
[Fact(DisplayName = "Tools endpoint returns enabled registry results")]
|
|
public async Task ToolsEndpointReturnsEnabledRegistryResults()
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddAIHostServices();
|
|
using var provider = services.BuildServiceProvider();
|
|
var endpoint = new ToolsEndpoint(provider.GetRequiredService<IAIToolRegistry>(), MicrosoftOptions.Create(new AIHostOptions()));
|
|
|
|
var tools = await endpoint.ExecuteAsync(new Request(), CancellationToken.None);
|
|
|
|
Assert.Empty(tools);
|
|
}
|
|
|
|
[Fact(DisplayName = "Tools endpoint forwards agent scope to registry")]
|
|
public async Task ToolsEndpointForwardsAgentScopeToRegistry()
|
|
{
|
|
var services = new ServiceCollection();
|
|
services.AddAIHostServices();
|
|
services.AddSingleton<IAITool, WorkflowAuthorTool>();
|
|
services.AddSingleton<IAITool, WorkflowEditorTool>();
|
|
using var provider = services.BuildServiceProvider();
|
|
var endpoint = new ToolsEndpoint(provider.GetRequiredService<IAIToolRegistry>(), MicrosoftOptions.Create(new AIHostOptions()));
|
|
SetHttpContext(endpoint, "workflows:author");
|
|
|
|
var tools = await endpoint.ExecuteAsync(new Request { Agent = "workflow-author" }, CancellationToken.None);
|
|
|
|
var tool = Assert.Single(tools);
|
|
Assert.Equal("workflow.author", tool.Name);
|
|
}
|
|
|
|
[Fact(DisplayName = "Tool registry caches definitions across list calls")]
|
|
public async Task ToolRegistryCachesDefinitionsAcrossListCalls()
|
|
{
|
|
CountingTool.Reset();
|
|
var services = new ServiceCollection();
|
|
services.AddAIHostServices();
|
|
services.AddTransient<IAITool>(_ => CountingTool.Create());
|
|
using var provider = services.BuildServiceProvider();
|
|
var registry = provider.GetRequiredService<IAIToolRegistry>();
|
|
|
|
await registry.ListAsync(new AIToolQuery(), CancellationToken.None);
|
|
await registry.ListAsync(new AIToolQuery(), CancellationToken.None);
|
|
|
|
Assert.Equal(1, CountingTool.ConstructorCount);
|
|
}
|
|
|
|
private class WorkflowAuthorTool : IAITool
|
|
{
|
|
public AIToolDefinition Definition { get; } = new()
|
|
{
|
|
Name = "workflow.author",
|
|
DisplayName = "Workflow author",
|
|
AgentScopes = ["workflow-author"],
|
|
Permissions = ["workflows:author"]
|
|
};
|
|
|
|
public ValueTask<AIToolResult> ExecuteAsync(AIToolExecutionContext context, CancellationToken cancellationToken = default) =>
|
|
ValueTask.FromResult(new AIToolResult());
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
}
|
|
|
|
private class WorkflowEditorTool : IAITool
|
|
{
|
|
public AIToolDefinition Definition { get; } = new()
|
|
{
|
|
Name = "workflow.editor",
|
|
DisplayName = "Workflow editor",
|
|
AgentScopes = ["workflow-editor"],
|
|
Permissions = ["workflows:editor"]
|
|
};
|
|
|
|
public ValueTask<AIToolResult> ExecuteAsync(AIToolExecutionContext context, CancellationToken cancellationToken = default) =>
|
|
ValueTask.FromResult(new AIToolResult());
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
}
|
|
|
|
private class CountingTool : IAITool
|
|
{
|
|
private static int _constructorCount;
|
|
|
|
public static int ConstructorCount => _constructorCount;
|
|
|
|
private CountingTool()
|
|
{
|
|
}
|
|
|
|
public static CountingTool Create()
|
|
{
|
|
Interlocked.Increment(ref _constructorCount);
|
|
return new CountingTool();
|
|
}
|
|
|
|
public AIToolDefinition Definition { get; } = new()
|
|
{
|
|
Name = "counting.tool",
|
|
DisplayName = "Counting tool"
|
|
};
|
|
|
|
public ValueTask<AIToolResult> ExecuteAsync(AIToolExecutionContext context, CancellationToken cancellationToken = default) =>
|
|
ValueTask.FromResult(new AIToolResult());
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
|
|
public static void Reset()
|
|
{
|
|
Interlocked.Exchange(ref _constructorCount, 0);
|
|
}
|
|
}
|
|
|
|
private static void SetHttpContext(ToolsEndpoint endpoint, params string[] permissions)
|
|
{
|
|
var identity = new ClaimsIdentity(permissions.Select(x => new Claim(PermissionNames.ClaimType, x)), "test");
|
|
var property = typeof(ToolsEndpoint)
|
|
.GetProperty(nameof(ToolsEndpoint.HttpContext), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!;
|
|
property.SetValue(endpoint, new DefaultHttpContext
|
|
{
|
|
User = new ClaimsPrincipal(identity)
|
|
});
|
|
}
|
|
}
|