| | | 1 | | using Elsa.Authorization; |
| | | 2 | | using Elsa.Abstractions; |
| | | 3 | | using Elsa.AI.Abstractions.Contracts; |
| | | 4 | | using Elsa.AI.Abstractions.Models; |
| | | 5 | | using Elsa.AI.Host.Options; |
| | | 6 | | using Elsa.AI.Host.Permissions; |
| | | 7 | | using Elsa.Workflows; |
| | | 8 | | using Elsa.Workflows.Management; |
| | | 9 | | using JetBrains.Annotations; |
| | | 10 | | using Microsoft.Extensions.DependencyInjection; |
| | | 11 | | using Microsoft.Extensions.Options; |
| | | 12 | | |
| | | 13 | | namespace Elsa.AI.Host.Endpoints.AI.Capabilities; |
| | | 14 | | |
| | | 15 | | [PublicAPI] |
| | 8 | 16 | | public class Endpoint( |
| | 8 | 17 | | IOptions<AIHostOptions> options, |
| | 8 | 18 | | IEnumerable<IAIProvider> providers, |
| | 8 | 19 | | IEnumerable<IAIConversationStore> conversationStores, |
| | 8 | 20 | | IEnumerable<IAIProposalStore> proposalStores, |
| | 8 | 21 | | IServiceScopeFactory serviceScopeFactory) : ElsaEndpointWithoutRequest<Response> |
| | | 22 | | { |
| | | 23 | | public override void Configure() |
| | | 24 | | { |
| | 0 | 25 | | Get("/ai/capabilities"); |
| | 0 | 26 | | RequirePermission(Elsa.AI.Host.Permissions.AIResourcePermissions.Capabilities, CoreVerbs.View); |
| | 0 | 27 | | } |
| | | 28 | | |
| | | 29 | | public override Task<Response> ExecuteAsync(CancellationToken cancellationToken) |
| | | 30 | | { |
| | 8 | 31 | | var optionsValue = options.Value; |
| | 8 | 32 | | var providerOptions = optionsValue.Providers.ToList(); |
| | 17 | 33 | | var availableProviders = providers.Where(x => providerOptions.IsProviderEnabled(x.Name)).ToList(); |
| | 8 | 34 | | var hasSelectableProvider = HasSelectableProvider(optionsValue.DefaultProviderName, providerOptions, availablePr |
| | 16 | 35 | | var hasDurableConversationStore = conversationStores.Any(x => x is not IAITransientConversationStore); |
| | | 36 | | |
| | 8 | 37 | | return Task.FromResult(new Response( |
| | 8 | 38 | | optionsValue.StreamingEnabled && hasSelectableProvider, |
| | 8 | 39 | | optionsValue.ConversationPersistenceEnabled && hasDurableConversationStore, |
| | 8 | 40 | | optionsValue.ProposalReviewEnabled && proposalStores.Any(), |
| | 8 | 41 | | optionsValue.SupportedAttachmentKinds.ToList(), |
| | 8 | 42 | | optionsValue.Agents.Select(x => new AIAgentCapability(x.Name, x.DisplayName, x.Description)).ToList(), |
| | 8 | 43 | | CreateGroundingCapabilities(optionsValue, proposalStores.Any()))); |
| | | 44 | | } |
| | | 45 | | |
| | | 46 | | private IReadOnlyCollection<AIGroundingCapabilityDescriptor> CreateGroundingCapabilities(AIHostOptions optionsValue, |
| | | 47 | | { |
| | 8 | 48 | | var grounding = optionsValue.Grounding; |
| | 8 | 49 | | using var scope = serviceScopeFactory.CreateScope(); |
| | 8 | 50 | | var services = scope.ServiceProvider; |
| | 8 | 51 | | return |
| | 8 | 52 | | [ |
| | 8 | 53 | | CreateCapability( |
| | 8 | 54 | | "activities", |
| | 8 | 55 | | "Activity discovery", |
| | 8 | 56 | | grounding.ActivityGroundingEnabled && services.GetService<IActivityRegistry>() != null, |
| | 8 | 57 | | [CapabilityToolNames.ActivitiesSearchToolName, CapabilityToolNames.ActivityDescriptorToolName], |
| | 8 | 58 | | [AIContextAttachmentKinds.Activity], |
| | 8 | 59 | | grounding.ActivityGroundingEnabled, |
| | 8 | 60 | | "Activity Registry is not registered."), |
| | 8 | 61 | | CreateCapability( |
| | 8 | 62 | | "workflows", |
| | 8 | 63 | | "Workflow definitions", |
| | 8 | 64 | | grounding.WorkflowGroundingEnabled && services.GetService<IWorkflowDefinitionStore>() != null, |
| | 8 | 65 | | [CapabilityToolNames.WorkflowsSearchToolName, CapabilityToolNames.WorkflowDefinitionToolName, Capability |
| | 8 | 66 | | [AIContextAttachmentKinds.WorkflowDefinition], |
| | 8 | 67 | | grounding.WorkflowGroundingEnabled, |
| | 8 | 68 | | "Workflow definition store is not registered."), |
| | 8 | 69 | | CreateCapability( |
| | 8 | 70 | | "proposals", |
| | 8 | 71 | | "Workflow proposals", |
| | 8 | 72 | | grounding.ProposalGroundingEnabled && hasProposalStore, |
| | 8 | 73 | | [CapabilityToolNames.WorkflowValidateDraftToolName, CapabilityToolNames.WorkflowProposeCreateToolName, C |
| | 8 | 74 | | [AIContextAttachmentKinds.WorkflowDefinition, AIContextAttachmentKinds.Activity], |
| | 8 | 75 | | grounding.ProposalGroundingEnabled, |
| | 8 | 76 | | "AI proposal store is not registered."), |
| | 8 | 77 | | CreateCapability( |
| | 8 | 78 | | "runtime", |
| | 8 | 79 | | "Runtime inspection", |
| | 8 | 80 | | grounding.RuntimeGroundingEnabled && services.GetService<IWorkflowInstanceStore>() != null, |
| | 8 | 81 | | [CapabilityToolNames.InstancesSearchToolName, CapabilityToolNames.WorkflowInstanceToolName, CapabilityTo |
| | 8 | 82 | | [AIContextAttachmentKinds.WorkflowInstance, AIContextAttachmentKinds.DiagnosticsScope, AIContextAttachme |
| | 8 | 83 | | grounding.RuntimeGroundingEnabled, |
| | 8 | 84 | | "Workflow instance store is not registered.") |
| | 8 | 85 | | ]; |
| | 8 | 86 | | } |
| | | 87 | | |
| | | 88 | | private static AIGroundingCapabilityDescriptor CreateCapability( |
| | | 89 | | string family, |
| | | 90 | | string displayName, |
| | | 91 | | bool available, |
| | | 92 | | IReadOnlyCollection<string> toolNames, |
| | | 93 | | IReadOnlyCollection<string> attachmentKinds, |
| | | 94 | | bool enabled, |
| | | 95 | | string unavailableReason) => |
| | 32 | 96 | | new() |
| | 32 | 97 | | { |
| | 32 | 98 | | Family = family, |
| | 32 | 99 | | DisplayName = displayName, |
| | 32 | 100 | | Available = available, |
| | 32 | 101 | | ToolNames = toolNames, |
| | 32 | 102 | | AttachmentKinds = attachmentKinds, |
| | 32 | 103 | | DisabledReasons = available ? [] : [enabled ? unavailableReason : "Grounding family is disabled by configura |
| | 32 | 104 | | }; |
| | | 105 | | |
| | | 106 | | private static bool HasSelectableProvider(string? providerName, IReadOnlyCollection<AIProviderOptions> providerOptio |
| | | 107 | | { |
| | 8 | 108 | | if (!string.IsNullOrWhiteSpace(providerName)) |
| | | 109 | | { |
| | 1 | 110 | | var configuredProviders = providerOptions.Where(x => x.Enabled).ToList(); |
| | 1 | 111 | | var configuredProvider = configuredProviders.FirstOrDefault(x => string.Equals(x.Name, providerName, StringC |
| | 1 | 112 | | return configuredProvider != null |
| | 0 | 113 | | ? availableProviders.Any(x => string.Equals(x.Name, configuredProvider.Name, StringComparison.OrdinalIgn |
| | 0 | 114 | | string.Equals(x.Name, configuredProvider.Provider, StringComparison.Ordina |
| | 3 | 115 | | : availableProviders.Any(x => string.Equals(x.Name, providerName, StringComparison.OrdinalIgnoreCase)); |
| | | 116 | | } |
| | | 117 | | |
| | 7 | 118 | | return availableProviders.Count == 1; |
| | | 119 | | } |
| | | 120 | | } |
| | | 121 | | |
| | | 122 | | public record Response( |
| | | 123 | | bool Streaming, |
| | | 124 | | bool ConversationPersistence, |
| | | 125 | | bool ProposalReview, |
| | | 126 | | IReadOnlyCollection<string> SupportedAttachmentKinds, |
| | | 127 | | IReadOnlyCollection<AIAgentCapability> Agents, |
| | | 128 | | IReadOnlyCollection<AIGroundingCapabilityDescriptor> Grounding); |
| | | 129 | | |
| | | 130 | | public record AIAgentCapability(string Name, string DisplayName, string Description); |
| | | 131 | | |
| | | 132 | | file static class CapabilityToolNames |
| | | 133 | | { |
| | | 134 | | public const string ActivitiesSearchToolName = "activities.search"; |
| | | 135 | | public const string ActivityDescriptorToolName = "activities.getDescriptor"; |
| | | 136 | | public const string WorkflowsSearchToolName = "workflows.search"; |
| | | 137 | | public const string WorkflowDefinitionToolName = "workflows.getDefinition"; |
| | | 138 | | public const string WorkflowDefinitionGraphToolName = "workflows.getDefinitionGraph"; |
| | | 139 | | public const string WorkflowUsageSearchToolName = "workflows.findUsages"; |
| | | 140 | | public const string WorkflowValidateDraftToolName = "workflows.validateDraft"; |
| | | 141 | | public const string WorkflowProposeCreateToolName = "workflows.proposeCreate"; |
| | | 142 | | public const string WorkflowProposeUpdateToolName = "workflows.proposeUpdate"; |
| | | 143 | | public const string InstancesSearchToolName = "instances.search"; |
| | | 144 | | public const string WorkflowInstanceToolName = "instances.get"; |
| | | 145 | | public const string WorkflowInstanceExecutionHistoryToolName = "instances.getExecutionHistory"; |
| | | 146 | | public const string WorkflowInstanceActivityStateToolName = "instances.getActivityState"; |
| | | 147 | | public const string IncidentsSearchToolName = "incidents.search"; |
| | | 148 | | public const string IncidentToolName = "incidents.get"; |
| | | 149 | | } |