diff --git a/Directory.Build.props b/Directory.Build.props
index a5553de1..06f2ec7d 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -27,7 +27,7 @@
true
- 3.3.3
- 3.3.2
+ 3.4.0-preview.2684
+ 3.4.0-preview.857
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 12a59b8c..bfe6051f 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -1,77 +1,101 @@
-
- true
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Elsa.Integrations.sln b/Elsa.Integrations.sln
index c17ea98b..5f176d11 100644
--- a/Elsa.Integrations.sln
+++ b/Elsa.Integrations.sln
@@ -57,6 +57,26 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.AzureServ
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.TestServer.Web", "test\component\Elsa.TestServer.Web\Elsa.TestServer.Web.csproj", "{91D7599F-B4BC-4C2E-A346-56DDCD6F9FDC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Agents.Activities", "src\Elsa.Integrations.Agents.Activities\Elsa.Integrations.Agents.Activities.csproj", "{328F9C5A-60BD-4996-9422-32637BB1A55B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Agents.Api", "src\Elsa.Integrations.Agents.Api\Elsa.Integrations.Agents.Api.csproj", "{2FB8910D-9310-4DA3-9151-80AA401134A8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Agents.Core", "src\Elsa.Integrations.Agents.Core\Elsa.Integrations.Agents.Core.csproj", "{293490F1-E0A1-4067-BDEB-E6CE09649749}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Agents.Models", "src\Elsa.Integrations.Agents.Models\Elsa.Integrations.Agents.Models.csproj", "{4E6009CF-5525-4AD3-B898-205EC493FB15}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Agents.Persistence", "src\Elsa.Integrations.Agents.Persistence\Elsa.Integrations.Agents.Persistence.csproj", "{863E1AD1-7C87-48B0-8274-A7FE0FC31C60}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Agents.Persistence.EntityFrameworkCore", "src\Elsa.Integrations.Agents.Persistence.EntityFrameworkCore\Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.csproj", "{E441ED7C-A9AF-4CC6-AA4D-02ECD38075CC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql", "src\Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql\Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql.csproj", "{0366AA72-4E2C-4416-A3C9-BB1964EAAB89}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql", "src\Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql\Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql.csproj", "{59AE52F2-8F9F-44A2-92E8-ED4EEB895F31}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.Sqlite", "src\Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.Sqlite\Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.Sqlite.csproj", "{4455BAC7-FD3C-4429-A2A2-44A83A331056}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer", "src\Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer\Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer.csproj", "{4955F8F7-B4D2-4695-9C29-A2DC9DDCD8E2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -95,6 +115,46 @@ Global
{91D7599F-B4BC-4C2E-A346-56DDCD6F9FDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{91D7599F-B4BC-4C2E-A346-56DDCD6F9FDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{91D7599F-B4BC-4C2E-A346-56DDCD6F9FDC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {328F9C5A-60BD-4996-9422-32637BB1A55B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {328F9C5A-60BD-4996-9422-32637BB1A55B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {328F9C5A-60BD-4996-9422-32637BB1A55B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {328F9C5A-60BD-4996-9422-32637BB1A55B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2FB8910D-9310-4DA3-9151-80AA401134A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2FB8910D-9310-4DA3-9151-80AA401134A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2FB8910D-9310-4DA3-9151-80AA401134A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2FB8910D-9310-4DA3-9151-80AA401134A8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {293490F1-E0A1-4067-BDEB-E6CE09649749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {293490F1-E0A1-4067-BDEB-E6CE09649749}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {293490F1-E0A1-4067-BDEB-E6CE09649749}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {293490F1-E0A1-4067-BDEB-E6CE09649749}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4E6009CF-5525-4AD3-B898-205EC493FB15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4E6009CF-5525-4AD3-B898-205EC493FB15}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4E6009CF-5525-4AD3-B898-205EC493FB15}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4E6009CF-5525-4AD3-B898-205EC493FB15}.Release|Any CPU.Build.0 = Release|Any CPU
+ {863E1AD1-7C87-48B0-8274-A7FE0FC31C60}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {863E1AD1-7C87-48B0-8274-A7FE0FC31C60}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {863E1AD1-7C87-48B0-8274-A7FE0FC31C60}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {863E1AD1-7C87-48B0-8274-A7FE0FC31C60}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E441ED7C-A9AF-4CC6-AA4D-02ECD38075CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E441ED7C-A9AF-4CC6-AA4D-02ECD38075CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E441ED7C-A9AF-4CC6-AA4D-02ECD38075CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E441ED7C-A9AF-4CC6-AA4D-02ECD38075CC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {0366AA72-4E2C-4416-A3C9-BB1964EAAB89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {0366AA72-4E2C-4416-A3C9-BB1964EAAB89}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {0366AA72-4E2C-4416-A3C9-BB1964EAAB89}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0366AA72-4E2C-4416-A3C9-BB1964EAAB89}.Release|Any CPU.Build.0 = Release|Any CPU
+ {59AE52F2-8F9F-44A2-92E8-ED4EEB895F31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {59AE52F2-8F9F-44A2-92E8-ED4EEB895F31}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {59AE52F2-8F9F-44A2-92E8-ED4EEB895F31}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {59AE52F2-8F9F-44A2-92E8-ED4EEB895F31}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4455BAC7-FD3C-4429-A2A2-44A83A331056}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4455BAC7-FD3C-4429-A2A2-44A83A331056}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4455BAC7-FD3C-4429-A2A2-44A83A331056}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4455BAC7-FD3C-4429-A2A2-44A83A331056}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4955F8F7-B4D2-4695-9C29-A2DC9DDCD8E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4955F8F7-B4D2-4695-9C29-A2DC9DDCD8E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4955F8F7-B4D2-4695-9C29-A2DC9DDCD8E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4955F8F7-B4D2-4695-9C29-A2DC9DDCD8E2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -111,5 +171,15 @@ Global
{A1ADCCC2-01DD-41BA-8A81-FAAE96EC729D} = {AF041BAE-B45A-428B-B7F5-921CCB895558}
{2175629F-FA99-4840-9F12-3FE7417706B9} = {60461F37-79E1-4EBB-8742-9F6578C6A745}
{91D7599F-B4BC-4C2E-A346-56DDCD6F9FDC} = {60461F37-79E1-4EBB-8742-9F6578C6A745}
+ {328F9C5A-60BD-4996-9422-32637BB1A55B} = {527248D6-B851-4C8D-8667-E2FB0A91DABF}
+ {2FB8910D-9310-4DA3-9151-80AA401134A8} = {527248D6-B851-4C8D-8667-E2FB0A91DABF}
+ {293490F1-E0A1-4067-BDEB-E6CE09649749} = {527248D6-B851-4C8D-8667-E2FB0A91DABF}
+ {4E6009CF-5525-4AD3-B898-205EC493FB15} = {527248D6-B851-4C8D-8667-E2FB0A91DABF}
+ {863E1AD1-7C87-48B0-8274-A7FE0FC31C60} = {527248D6-B851-4C8D-8667-E2FB0A91DABF}
+ {E441ED7C-A9AF-4CC6-AA4D-02ECD38075CC} = {527248D6-B851-4C8D-8667-E2FB0A91DABF}
+ {0366AA72-4E2C-4416-A3C9-BB1964EAAB89} = {527248D6-B851-4C8D-8667-E2FB0A91DABF}
+ {59AE52F2-8F9F-44A2-92E8-ED4EEB895F31} = {527248D6-B851-4C8D-8667-E2FB0A91DABF}
+ {4455BAC7-FD3C-4429-A2A2-44A83A331056} = {527248D6-B851-4C8D-8667-E2FB0A91DABF}
+ {4955F8F7-B4D2-4695-9C29-A2DC9DDCD8E2} = {527248D6-B851-4C8D-8667-E2FB0A91DABF}
EndGlobalSection
EndGlobal
diff --git a/NuGet.Config b/NuGet.Config
index e5fb919d..e3f020ab 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -11,6 +11,7 @@
+
diff --git a/src/Elsa.Integrations.Agents.Activities/Activities/AgentActivity.cs b/src/Elsa.Integrations.Agents.Activities/Activities/AgentActivity.cs
new file mode 100644
index 00000000..35acc9bd
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Activities/Activities/AgentActivity.cs
@@ -0,0 +1,63 @@
+using System.ComponentModel;
+using System.Dynamic;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.Unicode;
+using Elsa.Agents;
+using Elsa.Expressions.Helpers;
+using Elsa.Extensions;
+using Elsa.Integrations.Agents.Activities.ActivityProviders;
+using Elsa.Workflows;
+using Elsa.Workflows.Models;
+using Elsa.Workflows.Serialization.Converters;
+
+namespace Elsa.Integrations.Agents.Activities;
+
+///
+/// An activity that executes a function of a skilled agent. This is an internal activity that is used by .
+///
+[Browsable(false)]
+public class AgentActivity : CodeActivity
+{
+ private static JsonSerializerOptions? _serializerOptions;
+
+ private static JsonSerializerOptions SerializerOptions =>
+ _serializerOptions ??= new JsonSerializerOptions
+ {
+ Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
+ PropertyNameCaseInsensitive = true
+ }.WithConverters(new ExpandoObjectConverterFactory());
+
+ [JsonIgnore] internal string AgentName { get; set; } = null!;
+
+ ///
+ protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
+ {
+ var activityDescriptor = context.ActivityDescriptor;
+ var inputDescriptors = activityDescriptor.Inputs;
+ var functionInput = new Dictionary();
+
+ foreach (var inputDescriptor in inputDescriptors)
+ {
+ var input = (Input?)inputDescriptor.ValueGetter(this);
+ var inputValue = input != null ? context.Get(input.MemoryBlockReference()) : null;
+ functionInput[inputDescriptor.Name] = inputValue;
+ }
+
+ var agentInvoker = context.GetRequiredService();
+ var result = await agentInvoker.InvokeAgentAsync(AgentName, functionInput, context.CancellationToken);
+ var json = result.FunctionResult.GetValue();
+ var outputType = context.ActivityDescriptor.Outputs.Single().Type;
+
+ // If the target type is object, we want the JSON to be deserialized into an ExpandoObject for dynamic field access.
+ if (outputType == typeof(object))
+ outputType = typeof(ExpandoObject);
+
+ var converterOptions = new ObjectConverterOptions(SerializerOptions);
+ var outputValue = json.ConvertTo(outputType, converterOptions);
+ var outputDescriptor = activityDescriptor.Outputs.Single();
+ var output = (Output)outputDescriptor.ValueGetter(this);
+ context.Set(output, outputValue);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Activities/ActivityProviders/AgentActivityProvider.cs b/src/Elsa.Integrations.Agents.Activities/ActivityProviders/AgentActivityProvider.cs
new file mode 100644
index 00000000..ed7cf17c
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Activities/ActivityProviders/AgentActivityProvider.cs
@@ -0,0 +1,94 @@
+using Elsa.Agents;
+using Elsa.Expressions.Contracts;
+using Elsa.Expressions.Extensions;
+using Elsa.Extensions;
+using Elsa.Workflows;
+using Elsa.Workflows.Models;
+using Humanizer;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Activities.ActivityProviders;
+
+///
+/// Provides activities for each function of registered agents.
+///
+[UsedImplicitly]
+public class AgentActivityProvider(
+ IKernelConfigProvider kernelConfigProvider,
+ IActivityDescriber activityDescriber,
+ IActivityFactory activityFactory,
+ IWellKnownTypeRegistry wellKnownTypeRegistry
+) : IActivityProvider
+{
+ ///
+ public async ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default)
+ {
+ var kernelConfig = await kernelConfigProvider.GetKernelConfigAsync(cancellationToken);
+ var agents = kernelConfig.Agents;
+ var activityDescriptors = new List();
+
+ foreach (var kvp in agents)
+ {
+ var agentConfig = kvp.Value;
+ var activityDescriptor = await activityDescriber.DescribeActivityAsync(typeof(AgentActivity), cancellationToken);
+ var activityTypeName = $"Elsa.Agents.{agentConfig.Name.Pascalize()}";
+ activityDescriptor.Name = agentConfig.Name.Pascalize();
+ activityDescriptor.TypeName = activityTypeName;
+ activityDescriptor.Description = agentConfig.Description;
+ activityDescriptor.DisplayName = agentConfig.Name.Humanize().Transform(To.TitleCase);
+ activityDescriptor.IsBrowsable = true;
+ activityDescriptor.Category = "Agents";
+ activityDescriptor.Kind = ActivityKind.Job;
+ activityDescriptor.CustomProperties["RootType"] = nameof(AgentActivity);
+
+ activityDescriptor.Constructor = context =>
+ {
+ var activity = (AgentActivity)activityFactory.Create(typeof(AgentActivity), context);
+ activity.Type = activityTypeName;
+ activity.AgentName = agentConfig.Name;
+ return activity;
+ };
+
+ activityDescriptors.Add(activityDescriptor);
+ activityDescriptor.Inputs.Clear();
+
+ foreach (var inputVariable in agentConfig.InputVariables)
+ {
+ var inputName = inputVariable.Name;
+ var inputType = inputVariable.Type == null! ? "object" : inputVariable.Type;
+ var nakedInputType = wellKnownTypeRegistry.GetTypeOrDefault(inputType);
+ var inputDescriptor = new InputDescriptor
+ {
+ Name = inputVariable.Name,
+ DisplayName = inputVariable.Name.Humanize(),
+ Description = inputVariable.Description,
+ Type = nakedInputType,
+ ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(inputName),
+ ValueSetter = (activity, value) => activity.SyntheticProperties[inputName] = value!,
+ IsSynthetic = true,
+ IsWrapped = true,
+ UIHint = ActivityDescriber.GetUIHint(nakedInputType)
+ };
+ activityDescriptor.Inputs.Add(inputDescriptor);
+ }
+
+ activityDescriptor.Outputs.Clear();
+ var outputVariable = agentConfig.OutputVariable;
+ var outputType = outputVariable.Type == null! ? "object" : outputVariable.Type;
+ var nakedOutputType = wellKnownTypeRegistry.GetTypeOrDefault(outputType);
+ var outputName = "Output";
+ var outputDescriptor = new OutputDescriptor
+ {
+ Name = outputName,
+ Description = agentConfig.OutputVariable.Description,
+ Type = nakedOutputType,
+ IsSynthetic = true,
+ ValueGetter = activity => activity.SyntheticProperties.GetValueOrDefault(outputName),
+ ValueSetter = (activity, value) => activity.SyntheticProperties[outputName] = value!,
+ };
+ activityDescriptor.Outputs.Add(outputDescriptor);
+ }
+
+ return activityDescriptors;
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Activities/Elsa.Integrations.Agents.Activities.csproj b/src/Elsa.Integrations.Agents.Activities/Elsa.Integrations.Agents.Activities.csproj
new file mode 100644
index 00000000..6a0aaa7d
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Activities/Elsa.Integrations.Agents.Activities.csproj
@@ -0,0 +1,17 @@
+
+
+
+ Provides Agent activities
+ elsa module agents semantic kernel llm ai
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Elsa.Integrations.Agents.Activities/Elsa.Integrations.Agents.Activities.csproj.DotSettings b/src/Elsa.Integrations.Agents.Activities/Elsa.Integrations.Agents.Activities.csproj.DotSettings
new file mode 100644
index 00000000..7230eb35
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Activities/Elsa.Integrations.Agents.Activities.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ True
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Activities/Extensions/AgentActivitiesFeatureModuleExtensions.cs b/src/Elsa.Integrations.Agents.Activities/Extensions/AgentActivitiesFeatureModuleExtensions.cs
new file mode 100644
index 00000000..1040b311
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Activities/Extensions/AgentActivitiesFeatureModuleExtensions.cs
@@ -0,0 +1,22 @@
+using Elsa.Extensions;
+using Elsa.Features.Services;
+using Elsa.Integrations.Agents.Activities.Features;
+using JetBrains.Annotations;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.Agents;
+
+///
+/// An extension class that installs the Agents feature.
+///
+[PublicAPI]
+public static class AgentActivitiesFeatureModuleExtensions
+{
+ ///
+ /// Installs the Agents feature.
+ ///
+ public static IModule UseAgentActivities(this IModule module, Action? configure = null)
+ {
+ return module.Use(configure);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Activities/Features/AgentActivitiesFeature.cs b/src/Elsa.Integrations.Agents.Activities/Features/AgentActivitiesFeature.cs
new file mode 100644
index 00000000..680429db
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Activities/Features/AgentActivitiesFeature.cs
@@ -0,0 +1,29 @@
+using Elsa.Agents.Features;
+using Elsa.Features.Abstractions;
+using Elsa.Features.Attributes;
+using Elsa.Features.Services;
+using Elsa.Integrations.Agents.Activities.ActivityProviders;
+using Elsa.Integrations.Agents.Activities.Handlers;
+using Elsa.Workflows.Management.Features;
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Elsa.Integrations.Agents.Activities.Features;
+
+///
+/// A feature that installs Semantic Kernel functionality.
+///
+[DependsOn(typeof(WorkflowManagementFeature))]
+[DependsOn(typeof(AgentsFeature))]
+[UsedImplicitly]
+public class AgentActivitiesFeature(IModule module) : FeatureBase(module)
+{
+ ///
+ public override void Apply()
+ {
+ Services
+ .AddActivityProvider()
+ .AddNotificationHandler()
+ ;
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Activities/FodyWeavers.xml b/src/Elsa.Integrations.Agents.Activities/FodyWeavers.xml
new file mode 100644
index 00000000..00e1d9a1
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Activities/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Activities/Handlers/RefreshActivityRegistry.cs b/src/Elsa.Integrations.Agents.Activities/Handlers/RefreshActivityRegistry.cs
new file mode 100644
index 00000000..137705d3
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Activities/Handlers/RefreshActivityRegistry.cs
@@ -0,0 +1,21 @@
+using Elsa.Integrations.Agents.Activities.ActivityProviders;
+using Elsa.Integrations.Agents.Persistence.Notifications;
+using Elsa.Mediator.Contracts;
+using Elsa.Workflows;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Activities.Handlers;
+
+[UsedImplicitly]
+public class RefreshActivityRegistry(IActivityRegistry activityRegistry, AgentActivityProvider agentActivityProvider) :
+ INotificationHandler,
+ INotificationHandler,
+ INotificationHandler,
+ INotificationHandler
+{
+ public Task HandleAsync(AgentDefinitionCreated notification, CancellationToken cancellationToken) => RefreshRegistryAsync(cancellationToken);
+ public Task HandleAsync(AgentDefinitionUpdated notification, CancellationToken cancellationToken) => RefreshRegistryAsync(cancellationToken);
+ public Task HandleAsync(AgentDefinitionDeleted notification, CancellationToken cancellationToken) => RefreshRegistryAsync(cancellationToken);
+ public Task HandleAsync(AgentDefinitionsDeletedInBulk notification, CancellationToken cancellationToken) => RefreshRegistryAsync(cancellationToken);
+ private Task RefreshRegistryAsync(CancellationToken cancellationToken) => activityRegistry.RefreshDescriptorsAsync(agentActivityProvider, cancellationToken);
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Elsa.Integrations.Agents.Api.csproj b/src/Elsa.Integrations.Agents.Api/Elsa.Integrations.Agents.Api.csproj
new file mode 100644
index 00000000..96133af1
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Elsa.Integrations.Agents.Api.csproj
@@ -0,0 +1,17 @@
+
+
+
+ Provides Agents API endpoints
+ elsa module agents semantic kernel api llm ai
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Elsa.Integrations.Agents.Api/Elsa.Integrations.Agents.Api.csproj.DotSettings b/src/Elsa.Integrations.Agents.Api/Elsa.Integrations.Agents.Api.csproj.DotSettings
new file mode 100644
index 00000000..6fc31774
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Elsa.Integrations.Agents.Api.csproj.DotSettings
@@ -0,0 +1,2 @@
+
+ True
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/BulkDelete/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/BulkDelete/Endpoint.cs
new file mode 100644
index 00000000..723ddb05
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/BulkDelete/Endpoint.cs
@@ -0,0 +1,33 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using Elsa.Integrations.Agents.Persistence.Filters;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Agents.BulkDelete;
+
+///
+/// Deletes an agent.
+///
+[UsedImplicitly]
+public class Endpoint(IAgentManager agentManager) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Post("/ai/bulk-actions/agents/delete");
+ ConfigurePermissions("ai/agents:delete");
+ }
+
+ ///
+ public override async Task ExecuteAsync(BulkDeleteRequest req, CancellationToken ct)
+ {
+ var ids = req.Ids;
+ var filter = new AgentDefinitionFilter
+ {
+ Ids = ids
+ };
+ var count = await agentManager.DeleteManyAsync(filter, ct);
+ return new BulkDeleteResponse(count);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Create/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Create/Endpoint.cs
new file mode 100644
index 00000000..742ffbfe
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Create/Endpoint.cs
@@ -0,0 +1,69 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Extensions;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using Elsa.Integrations.Agents.Persistence.Entities;
+using Elsa.Integrations.Agents.Persistence.Filters;
+using Elsa.Workflows;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Agents.Create;
+
+///
+/// Lists all registered agents.
+///
+[UsedImplicitly]
+public class Endpoint(IAgentManager agentManager, IIdentityGenerator identityGenerator) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Post("/ai/agents");
+ ConfigurePermissions("ai/agents:write");
+ }
+
+ ///
+ public override async Task ExecuteAsync(AgentInputModel req, CancellationToken ct)
+ {
+ var isNameUnique = await IsNameUniqueAsync(req.Name, ct);
+
+ if (!isNameUnique)
+ {
+ AddError("An Agent already exists with the specified name");
+ await SendErrorsAsync(cancellation: ct);
+ return null!;
+ }
+
+ var newEntity = new AgentDefinition
+ {
+ Id = identityGenerator.GenerateId(),
+ Name = req.Name.Trim(),
+ Description = req.Description.Trim(),
+ AgentConfig = new AgentConfig
+ {
+ Description = req.Description.Trim(),
+ Name = req.Name.Trim(),
+ Agents = req.Agents,
+ ExecutionSettings = req.ExecutionSettings,
+ InputVariables = req.InputVariables,
+ OutputVariable = req.OutputVariable,
+ Services = req.Services,
+ Plugins = req.Plugins,
+ FunctionName = req.FunctionName,
+ PromptTemplate = req.PromptTemplate
+ }
+ };
+
+ await agentManager.AddAsync(newEntity, ct);
+ return newEntity.ToModel();
+ }
+
+ private async Task IsNameUniqueAsync(string name, CancellationToken ct)
+ {
+ var filter = new AgentDefinitionFilter
+ {
+ Name = name
+ };
+ return await agentManager.FindAsync(filter, ct) == null;
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Delete/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Delete/Endpoint.cs
new file mode 100644
index 00000000..94d0b635
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Delete/Endpoint.cs
@@ -0,0 +1,33 @@
+using Elsa.Abstractions;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Agents.Delete;
+
+///
+/// Deletes an Agent.
+///
+[UsedImplicitly]
+public class Endpoint(IAgentManager agentManager) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Delete("/ai/agents/{id}");
+ ConfigurePermissions("ai/agents:delete");
+ }
+
+ ///
+ public override async Task HandleAsync(Request req, CancellationToken ct)
+ {
+ var entity = await agentManager.GetAsync(req.Id, ct);
+
+ if(entity == null)
+ {
+ await SendNotFoundAsync(ct);
+ return;
+ }
+
+ await agentManager.DeleteAsync(entity, ct);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Delete/Request.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Delete/Request.cs
new file mode 100644
index 00000000..ede9f143
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Delete/Request.cs
@@ -0,0 +1,8 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Agents.Delete;
+
+public class Request
+{
+ [Required] public string Id { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/GenerateUniqueName/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/GenerateUniqueName/Endpoint.cs
new file mode 100644
index 00000000..02d10165
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/GenerateUniqueName/Endpoint.cs
@@ -0,0 +1,27 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Agents.GenerateUniqueName;
+
+///
+/// Generates a unique name for an agent.
+///
+[UsedImplicitly]
+public class Endpoint(IAgentManager agentManager) : ElsaEndpointWithoutRequest
+{
+ ///
+ public override void Configure()
+ {
+ Post("/ai/actions/agents/generate-unique-name");
+ ConfigurePermissions("ai/agents:write");
+ }
+
+ ///
+ public override async Task ExecuteAsync(CancellationToken ct)
+ {
+ var newName = await agentManager.GenerateUniqueNameAsync(ct);
+ return new(newName);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Get/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Get/Endpoint.cs
new file mode 100644
index 00000000..f37b988e
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Get/Endpoint.cs
@@ -0,0 +1,35 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Extensions;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Agents.Get;
+
+///
+/// Gets an agent.
+///
+[UsedImplicitly]
+public class Endpoint(IAgentManager agentManager) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Get("/ai/agents/{id}");
+ ConfigurePermissions("ai/agents:read");
+ }
+
+ ///
+ public override async Task ExecuteAsync(Request req, CancellationToken ct)
+ {
+ var entity = await agentManager.GetAsync(req.Id, ct);
+
+ if(entity == null)
+ {
+ await SendNotFoundAsync(ct);
+ return null!;
+ }
+
+ return entity.ToModel();
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Get/Request.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Get/Request.cs
new file mode 100644
index 00000000..7de29c92
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Get/Request.cs
@@ -0,0 +1,8 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Agents.Get;
+
+public class Request
+{
+ [Required] public string Id { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Invoke/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Invoke/Endpoint.cs
new file mode 100644
index 00000000..14c6df1a
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Invoke/Endpoint.cs
@@ -0,0 +1,27 @@
+using System.Text.Json;
+using Elsa.Abstractions;
+using Elsa.Agents;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Agents.Invoke;
+
+///
+/// Invokes an agent.
+///
+[UsedImplicitly]
+public class Execute(AgentInvoker agentInvoker) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Post("/ai/agents/{agent}/invoke");
+ ConfigurePermissions("ai/agents:invoke");
+ }
+
+ ///
+ public override async Task ExecuteAsync(Request req, CancellationToken ct)
+ {
+ var result = await agentInvoker.InvokeAgentAsync(req.Agent, req.Inputs, ct).AsJsonElementAsync();
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Invoke/Request.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Invoke/Request.cs
new file mode 100644
index 00000000..e4b8816c
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Invoke/Request.cs
@@ -0,0 +1,7 @@
+namespace Elsa.Integrations.Agents.Api.Endpoints.Agents.Invoke;
+
+public class Request
+{
+ public string Agent { get; set; }
+ public IDictionary Inputs { get; set; }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/IsUniqueName/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/IsUniqueName/Endpoint.cs
new file mode 100644
index 00000000..5a47e288
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/IsUniqueName/Endpoint.cs
@@ -0,0 +1,27 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Agents.IsUniqueName;
+
+///
+/// Checks if a name is unique.
+///
+[UsedImplicitly]
+public class Endpoint(IAgentManager agentManager) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Post("/ai/queries/agents/is-unique-name");
+ ConfigurePermissions("ai/agents:read");
+ }
+
+ ///
+ public override async Task ExecuteAsync(IsUniqueNameRequest req, CancellationToken ct)
+ {
+ var isUnique = await agentManager.IsNameUniqueAsync(req.Name, req.Id, ct);
+ return new(isUnique);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/List/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/List/Endpoint.cs
new file mode 100644
index 00000000..9d29cf60
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/List/Endpoint.cs
@@ -0,0 +1,30 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Extensions;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using Elsa.Models;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Agents.List;
+
+///
+/// Lists all agents.
+///
+[UsedImplicitly]
+public class Endpoint(IAgentManager agentManager) : ElsaEndpointWithoutRequest>
+{
+ ///
+ public override void Configure()
+ {
+ Get("/ai/agents");
+ ConfigurePermissions("ai/agents:read");
+ }
+
+ ///
+ public override async Task> ExecuteAsync(CancellationToken ct)
+ {
+ var entities = await agentManager.ListAsync(ct);
+ var models = entities.Select(x => x.ToModel()).ToList();
+ return new ListResponse(models);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Update/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Update/Endpoint.cs
new file mode 100644
index 00000000..5a0a315b
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Agents/Update/Endpoint.cs
@@ -0,0 +1,67 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Extensions;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Agents.Update;
+
+///
+/// Updates an agent.
+///
+[UsedImplicitly]
+public class Endpoint(IAgentManager agentManager) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Post("/ai/agents/{id}");
+ ConfigurePermissions("ai/agents:write");
+ }
+
+ ///
+ public override async Task ExecuteAsync(AgentInputModel req, CancellationToken ct)
+ {
+ var id = Route("id")!;
+ var entity = await agentManager.GetAsync(id, ct);
+
+ if (entity == null)
+ {
+ await SendNotFoundAsync(ct);
+ return null!;
+ }
+
+ var isNameDuplicate = await IsNameDuplicateAsync(req.Name, id, ct);
+
+ if (isNameDuplicate)
+ {
+ AddError("Another agent already exists with the specified name");
+ await SendErrorsAsync(cancellation: ct);
+ return entity.ToModel();
+ }
+
+ entity.Name = req.Name.Trim();
+ entity.Description = req.Description.Trim();
+ entity.AgentConfig = new AgentConfig
+ {
+ Name = req.Name.Trim(),
+ Description = req.Description.Trim(),
+ FunctionName = req.FunctionName.Trim(),
+ Services = req.Services,
+ PromptTemplate = req.PromptTemplate.Trim(),
+ InputVariables = req.InputVariables,
+ OutputVariable = req.OutputVariable,
+ ExecutionSettings = req.ExecutionSettings,
+ Plugins = req.Plugins,
+ Agents = req.Agents
+ };
+
+ await agentManager.UpdateAsync(entity, ct);
+ return entity.ToModel();
+ }
+
+ private async Task IsNameDuplicateAsync(string name, string id, CancellationToken cancellationToken)
+ {
+ return !await agentManager.IsNameUniqueAsync(name, id, cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/BulkDelete/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/BulkDelete/Endpoint.cs
new file mode 100644
index 00000000..f2ee8606
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/BulkDelete/Endpoint.cs
@@ -0,0 +1,33 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using Elsa.Integrations.Agents.Persistence.Filters;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.ApiKeys.BulkDelete;
+
+///
+/// Deletes an API key.
+///
+[UsedImplicitly]
+public class Endpoint(IApiKeyStore store) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Post("/ai/bulk-actions/api-keys/delete");
+ ConfigurePermissions("ai/api-keys:delete");
+ }
+
+ ///
+ public override async Task ExecuteAsync(BulkDeleteRequest req, CancellationToken ct)
+ {
+ var ids = req.Ids;
+ var filter = new ApiKeyDefinitionFilter
+ {
+ Ids = ids
+ };
+ var count = await store.DeleteManyAsync(filter, ct);
+ return new BulkDeleteResponse(count);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Create/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Create/Endpoint.cs
new file mode 100644
index 00000000..5ce03549
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Create/Endpoint.cs
@@ -0,0 +1,50 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using Elsa.Integrations.Agents.Persistence.Entities;
+using Elsa.Integrations.Agents.Persistence.Filters;
+using Elsa.Workflows;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.ApiKeys.Create;
+
+///
+/// Lists all registered API keys.
+///
+[UsedImplicitly]
+public class Endpoint(IApiKeyStore store, IIdentityGenerator identityGenerator) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Post("/ai/api-keys");
+ ConfigurePermissions("ai/api-keys:write");
+ }
+
+ ///
+ public override async Task ExecuteAsync(ApiKeyInputModel req, CancellationToken ct)
+ {
+ var existingEntityFilter = new ApiKeyDefinitionFilter
+ {
+ Name = req.Name
+ };
+ var existingEntity = await store.FindAsync(existingEntityFilter, ct);
+
+ if (existingEntity != null)
+ {
+ AddError("An API key already exists with the specified name");
+ await SendErrorsAsync(cancellation: ct);
+ return existingEntity.ToModel();
+ }
+
+ var newEntity = new ApiKeyDefinition
+ {
+ Id = identityGenerator.GenerateId(),
+ Name = req.Name.Trim(),
+ Value = req.Value.Trim()
+ };
+
+ await store.AddAsync(newEntity, ct);
+ return newEntity.ToModel();
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Delete/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Delete/Endpoint.cs
new file mode 100644
index 00000000..1393cdbf
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Delete/Endpoint.cs
@@ -0,0 +1,33 @@
+using Elsa.Abstractions;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.ApiKeys.Delete;
+
+///
+/// Delete an API key.
+///
+[UsedImplicitly]
+public class Endpoint(IApiKeyStore store) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Delete("/ai/api-keys/{id}");
+ ConfigurePermissions("ai/api-keys:delete");
+ }
+
+ ///
+ public override async Task HandleAsync(Request req, CancellationToken ct)
+ {
+ var entity = await store.GetAsync(req.Id, ct);
+
+ if(entity == null)
+ {
+ await SendNotFoundAsync(ct);
+ return;
+ }
+
+ await store.DeleteAsync(entity, ct);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Delete/Request.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Delete/Request.cs
new file mode 100644
index 00000000..96a3692f
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Delete/Request.cs
@@ -0,0 +1,8 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.ApiKeys.Delete;
+
+public class Request
+{
+ [Required] public string Id { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Get/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Get/Endpoint.cs
new file mode 100644
index 00000000..e0f6d527
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Get/Endpoint.cs
@@ -0,0 +1,34 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.ApiKeys.Get;
+
+///
+/// Lists all registered API keys.
+///
+[UsedImplicitly]
+public class Endpoint(IApiKeyStore store) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Get("/ai/api-keys/{id}");
+ ConfigurePermissions("ai/api-keys:read");
+ }
+
+ ///
+ public override async Task ExecuteAsync(Request req, CancellationToken ct)
+ {
+ var entity = await store.GetAsync(req.Id, ct);
+
+ if(entity == null)
+ {
+ await SendNotFoundAsync(ct);
+ return null!;
+ }
+
+ return entity.ToModel();
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Get/Request.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Get/Request.cs
new file mode 100644
index 00000000..f73de159
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Get/Request.cs
@@ -0,0 +1,8 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.ApiKeys.Get;
+
+public class Request
+{
+ [Required] public string Id { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/List/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/List/Endpoint.cs
new file mode 100644
index 00000000..57df8674
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/List/Endpoint.cs
@@ -0,0 +1,29 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using Elsa.Models;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.ApiKeys.List;
+
+///
+/// Lists all registered API keys.
+///
+[UsedImplicitly]
+public class Endpoint(IApiKeyStore store) : ElsaEndpointWithoutRequest>
+{
+ ///
+ public override void Configure()
+ {
+ Get("/ai/api-keys");
+ ConfigurePermissions("ai/api-keys:read");
+ }
+
+ ///
+ public override async Task> ExecuteAsync(CancellationToken ct)
+ {
+ var entities = await store.ListAsync(ct);
+ var models = entities.Select(x => x.ToModel()).ToList();
+ return new ListResponse(models);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Update/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Update/Endpoint.cs
new file mode 100644
index 00000000..344b82b8
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/ApiKeys/Update/Endpoint.cs
@@ -0,0 +1,61 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using Elsa.Integrations.Agents.Persistence.Entities;
+using Elsa.Integrations.Agents.Persistence.Filters;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.ApiKeys.Update;
+
+///
+/// Lists all registered API keys.
+///
+[UsedImplicitly]
+public class Endpoint(IApiKeyStore store) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Post("/ai/api-keys/{id}");
+ ConfigurePermissions("ai/api-keys:write");
+ }
+
+ ///
+ public override async Task ExecuteAsync(ApiKeyModel req, CancellationToken ct)
+ {
+ var entity = await store.GetAsync(req.Id, ct);
+
+ if(entity == null)
+ {
+ await SendNotFoundAsync(ct);
+ return null!;
+ }
+
+ var isNameDuplicate = await IsNameDuplicateAsync(req.Name, req.Id, ct);
+
+ if (isNameDuplicate)
+ {
+ AddError("Another API key already exists with the specified name");
+ await SendErrorsAsync(cancellation: ct);
+ return entity;
+ }
+
+ entity.Name = req.Name.Trim();
+ entity.Value = req.Value.Trim();
+
+ await store.UpdateAsync(entity, ct);
+ return entity;
+ }
+
+ private async Task IsNameDuplicateAsync(string name, string id, CancellationToken cancellationToken)
+ {
+ var filter = new ApiKeyDefinitionFilter
+ {
+ Name = name,
+ NotId = id
+ };
+
+ var entity = await store.FindAsync(filter, cancellationToken);
+ return entity != null;
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Plugins/List/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Plugins/List/Endpoint.cs
new file mode 100644
index 00000000..12fbb1aa
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Plugins/List/Endpoint.cs
@@ -0,0 +1,28 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Models;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Plugins.List;
+
+///
+/// Lists all registered plugins.
+///
+[UsedImplicitly]
+public class Endpoint(IPluginDiscoverer pluginDiscoverer) : ElsaEndpointWithoutRequest>
+{
+ ///
+ public override void Configure()
+ {
+ Get("/ai/plugins");
+ ConfigurePermissions("ai/plugins:read");
+ }
+
+ ///
+ public override Task> ExecuteAsync(CancellationToken ct)
+ {
+ var descriptors = pluginDiscoverer.GetPluginDescriptors();
+ var models = descriptors.Select(x => x.ToModel()).ToList();
+ return Task.FromResult(new ListResponse(models));
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Services/BulkDelete/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/BulkDelete/Endpoint.cs
new file mode 100644
index 00000000..6f80a597
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/BulkDelete/Endpoint.cs
@@ -0,0 +1,33 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using Elsa.Integrations.Agents.Persistence.Filters;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Services.BulkDelete;
+
+///
+/// Deletes an API key.
+///
+[UsedImplicitly]
+public class Endpoint(IServiceStore store) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Post("/ai/bulk-actions/services/delete");
+ ConfigurePermissions("ai/api-keys:delete");
+ }
+
+ ///
+ public override async Task ExecuteAsync(BulkDeleteRequest req, CancellationToken ct)
+ {
+ var ids = req.Ids;
+ var filter = new ServiceDefinitionFilter
+ {
+ Ids = ids
+ };
+ var count = await store.DeleteManyAsync(filter, ct);
+ return new BulkDeleteResponse(count);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Create/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Create/Endpoint.cs
new file mode 100644
index 00000000..45335f14
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Create/Endpoint.cs
@@ -0,0 +1,51 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using Elsa.Integrations.Agents.Persistence.Entities;
+using Elsa.Integrations.Agents.Persistence.Filters;
+using Elsa.Workflows;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Services.Create;
+
+///
+/// Lists all registered API keys.
+///
+[UsedImplicitly]
+public class Endpoint(IServiceStore store, IIdentityGenerator identityGenerator) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Post("/ai/services");
+ ConfigurePermissions("ai/services:write");
+ }
+
+ ///
+ public override async Task ExecuteAsync(ServiceInputModel req, CancellationToken ct)
+ {
+ var existingEntityFilter = new ServiceDefinitionFilter
+ {
+ Name = req.Name
+ };
+ var existingEntity = await store.FindAsync(existingEntityFilter, ct);
+
+ if (existingEntity != null)
+ {
+ AddError("A Service already exists with the specified name");
+ await SendErrorsAsync(cancellation: ct);
+ return existingEntity.ToModel();
+ }
+
+ var newEntity = new ServiceDefinition
+ {
+ Id = identityGenerator.GenerateId(),
+ Name = req.Name.Trim(),
+ Type = req.Type,
+ Settings = req.Settings
+ };
+
+ await store.AddAsync(newEntity, ct);
+ return newEntity.ToModel();
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Delete/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Delete/Endpoint.cs
new file mode 100644
index 00000000..a94505ea
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Delete/Endpoint.cs
@@ -0,0 +1,33 @@
+using Elsa.Abstractions;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Services.Delete;
+
+///
+/// Delete an API key.
+///
+[UsedImplicitly]
+public class Endpoint(IServiceStore store) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Delete("/ai/services/{id}");
+ ConfigurePermissions("ai/services:delete");
+ }
+
+ ///
+ public override async Task HandleAsync(Request req, CancellationToken ct)
+ {
+ var entity = await store.GetAsync(req.Id, ct);
+
+ if(entity == null)
+ {
+ await SendNotFoundAsync(ct);
+ return;
+ }
+
+ await store.DeleteAsync(entity, ct);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Delete/Request.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Delete/Request.cs
new file mode 100644
index 00000000..d3453e2a
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Delete/Request.cs
@@ -0,0 +1,8 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Services.Delete;
+
+public class Request
+{
+ [Required] public string Id { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Get/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Get/Endpoint.cs
new file mode 100644
index 00000000..0625e2f0
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Get/Endpoint.cs
@@ -0,0 +1,34 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Services.Get;
+
+///
+/// Lists all registered API keys.
+///
+[UsedImplicitly]
+public class Endpoint(IServiceStore store) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Get("/ai/services/{id}");
+ ConfigurePermissions("ai/services:read");
+ }
+
+ ///
+ public override async Task ExecuteAsync(Request req, CancellationToken ct)
+ {
+ var entity = await store.GetAsync(req.Id, ct);
+
+ if(entity == null)
+ {
+ await SendNotFoundAsync(ct);
+ return null!;
+ }
+
+ return entity.ToModel();
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Get/Request.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Get/Request.cs
new file mode 100644
index 00000000..e5a3577a
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Get/Request.cs
@@ -0,0 +1,8 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Services.Get;
+
+public class Request
+{
+ [Required] public string Id { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Services/List/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/List/Endpoint.cs
new file mode 100644
index 00000000..b9509035
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/List/Endpoint.cs
@@ -0,0 +1,29 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using Elsa.Models;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Services.List;
+
+///
+/// Lists all registered API keys.
+///
+[UsedImplicitly]
+public class Endpoint(IServiceStore store) : ElsaEndpointWithoutRequest>
+{
+ ///
+ public override void Configure()
+ {
+ Get("/ai/services");
+ ConfigurePermissions("ai/services:read");
+ }
+
+ ///
+ public override async Task> ExecuteAsync(CancellationToken ct)
+ {
+ var entities = await store.ListAsync(ct);
+ var models = entities.Select(x => x.ToModel()).ToList();
+ return new ListResponse(models);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Update/Endpoint.cs b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Update/Endpoint.cs
new file mode 100644
index 00000000..4d4fa20e
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Endpoints/Services/Update/Endpoint.cs
@@ -0,0 +1,61 @@
+using Elsa.Abstractions;
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Contracts;
+using Elsa.Integrations.Agents.Persistence.Filters;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Endpoints.Services.Update;
+
+///
+/// Lists all registered API keys.
+///
+[UsedImplicitly]
+public class Endpoint(IServiceStore store) : ElsaEndpoint
+{
+ ///
+ public override void Configure()
+ {
+ Post("/ai/services/{id}");
+ ConfigurePermissions("ai/services:write");
+ }
+
+ ///
+ public override async Task ExecuteAsync(ServiceModel req, CancellationToken ct)
+ {
+ var entity = await store.GetAsync(req.Id, ct);
+
+ if(entity == null)
+ {
+ await SendNotFoundAsync(ct);
+ return null!;
+ }
+
+ var isNameDuplicate = await IsNameDuplicateAsync(req.Name, req.Id, ct);
+
+ if (isNameDuplicate)
+ {
+ AddError("Another service already exists with the specified name");
+ await SendErrorsAsync(cancellation: ct);
+ return entity.ToModel();
+ }
+
+ entity.Name = req.Name.Trim();
+ entity.Type = req.Type.Trim();
+ entity.Settings = req.Settings;
+
+ await store.UpdateAsync(entity, ct);
+ return entity.ToModel();
+ }
+
+ private async Task IsNameDuplicateAsync(string name, string id, CancellationToken cancellationToken)
+ {
+ var filter = new ServiceDefinitionFilter
+ {
+ Name = name,
+ NotId = id
+ };
+
+ var entity = await store.FindAsync(filter, cancellationToken);
+ return entity != null;
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Extensions/AgentDefinitionExtensions.cs b/src/Elsa.Integrations.Agents.Api/Extensions/AgentDefinitionExtensions.cs
new file mode 100644
index 00000000..745e9bcc
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Extensions/AgentDefinitionExtensions.cs
@@ -0,0 +1,26 @@
+using Elsa.Agents;
+using Elsa.Integrations.Agents.Persistence.Entities;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.Extensions;
+
+public static class AgentDefinitionExtensions
+{
+ public static AgentModel ToModel(this AgentDefinition agentDefinition)
+ {
+ return new AgentModel
+ {
+ Id = agentDefinition.Id,
+ Name = agentDefinition.Name,
+ Description = agentDefinition.Description,
+ Agents = agentDefinition.AgentConfig.Agents,
+ ExecutionSettings = agentDefinition.AgentConfig.ExecutionSettings,
+ InputVariables = agentDefinition.AgentConfig.InputVariables,
+ OutputVariable = agentDefinition.AgentConfig.OutputVariable,
+ Services = agentDefinition.AgentConfig.Services,
+ Plugins = agentDefinition.AgentConfig.Plugins,
+ FunctionName = agentDefinition.AgentConfig.FunctionName,
+ PromptTemplate = agentDefinition.AgentConfig.PromptTemplate
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Extensions/ModuleExtensions.cs b/src/Elsa.Integrations.Agents.Api/Extensions/ModuleExtensions.cs
new file mode 100644
index 00000000..9ed4aed0
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Extensions/ModuleExtensions.cs
@@ -0,0 +1,19 @@
+using Elsa.Features.Services;
+using Elsa.Integrations.Agents.Api.Features;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.Extensions;
+
+///
+/// Extends with methods to install Semantic Kernel API endpoints.
+///
+public static class ModuleExtensions
+{
+ ///
+ /// Installs the Semantic Kernel API feature.
+ ///
+ public static IModule UseAgentsApi(this IModule module, Action? configure = null)
+ {
+ return module.Use(configure);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/Features/AgentsApiFeature.cs b/src/Elsa.Integrations.Agents.Api/Features/AgentsApiFeature.cs
new file mode 100644
index 00000000..88a48166
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/Features/AgentsApiFeature.cs
@@ -0,0 +1,22 @@
+using Elsa.Extensions;
+using Elsa.Features.Abstractions;
+using Elsa.Features.Attributes;
+using Elsa.Features.Services;
+using Elsa.Integrations.Agents.Persistence.Features;
+using JetBrains.Annotations;
+
+namespace Elsa.Integrations.Agents.Api.Features;
+
+///
+/// A feature that installs API endpoints to interact with skilled agents.
+///
+[DependsOn(typeof(AgentPersistenceFeature))]
+[UsedImplicitly]
+public class AgentsApiFeature(IModule module) : FeatureBase(module)
+{
+ ///
+ public override void Configure()
+ {
+ Module.AddFastEndpointsAssembly();
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Api/FodyWeavers.xml b/src/Elsa.Integrations.Agents.Api/FodyWeavers.xml
new file mode 100644
index 00000000..00e1d9a1
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Api/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Abstractions/PluginProvider.cs b/src/Elsa.Integrations.Agents.Core/Abstractions/PluginProvider.cs
new file mode 100644
index 00000000..c64a1f34
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Abstractions/PluginProvider.cs
@@ -0,0 +1,6 @@
+namespace Elsa.Agents;
+
+public abstract class PluginProvider : IPluginProvider
+{
+ public virtual IEnumerable GetPlugins() => [];
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Contracts/IAgentServiceProvider.cs b/src/Elsa.Integrations.Agents.Core/Contracts/IAgentServiceProvider.cs
new file mode 100644
index 00000000..1eb96057
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Contracts/IAgentServiceProvider.cs
@@ -0,0 +1,7 @@
+namespace Elsa.Agents;
+
+public interface IAgentServiceProvider
+{
+ string Name { get; }
+ void ConfigureKernel(KernelBuilderContext context);
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Contracts/IKernelConfigProvider.cs b/src/Elsa.Integrations.Agents.Core/Contracts/IKernelConfigProvider.cs
new file mode 100644
index 00000000..a64f7411
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Contracts/IKernelConfigProvider.cs
@@ -0,0 +1,6 @@
+namespace Elsa.Agents;
+
+public interface IKernelConfigProvider
+{
+ Task GetKernelConfigAsync(CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Contracts/IPluginDiscoverer.cs b/src/Elsa.Integrations.Agents.Core/Contracts/IPluginDiscoverer.cs
new file mode 100644
index 00000000..5a9fa460
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Contracts/IPluginDiscoverer.cs
@@ -0,0 +1,6 @@
+namespace Elsa.Agents;
+
+public interface IPluginDiscoverer
+{
+ IEnumerable GetPluginDescriptors();
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Contracts/IPluginProvider.cs b/src/Elsa.Integrations.Agents.Core/Contracts/IPluginProvider.cs
new file mode 100644
index 00000000..0db6dfd7
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Contracts/IPluginProvider.cs
@@ -0,0 +1,9 @@
+namespace Elsa.Agents;
+
+///
+/// Implementations of this interface are responsible for providing plugins.
+///
+public interface IPluginProvider
+{
+ IEnumerable GetPlugins();
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Contracts/IServiceDiscoverer.cs b/src/Elsa.Integrations.Agents.Core/Contracts/IServiceDiscoverer.cs
new file mode 100644
index 00000000..681e6fd4
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Contracts/IServiceDiscoverer.cs
@@ -0,0 +1,6 @@
+namespace Elsa.Agents;
+
+public interface IServiceDiscoverer
+{
+ IEnumerable Discover();
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Elsa.Integrations.Agents.Core.csproj b/src/Elsa.Integrations.Agents.Core/Elsa.Integrations.Agents.Core.csproj
new file mode 100644
index 00000000..bd669771
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Elsa.Integrations.Agents.Core.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Provides an agentic framework using Semantic Kernel
+ elsa module agents semantic kernel llm ai
+ Elsa.Agents
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Elsa.Integrations.Agents.Core/Elsa.Integrations.Agents.Core.csproj.DotSettings b/src/Elsa.Integrations.Agents.Core/Elsa.Integrations.Agents.Core.csproj.DotSettings
new file mode 100644
index 00000000..0eb55f59
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Elsa.Integrations.Agents.Core.csproj.DotSettings
@@ -0,0 +1,9 @@
+
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Extensions/AgentConfigExtensions.cs b/src/Elsa.Integrations.Agents.Core/Extensions/AgentConfigExtensions.cs
new file mode 100644
index 00000000..2a4cdc6f
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Extensions/AgentConfigExtensions.cs
@@ -0,0 +1,49 @@
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Connectors.OpenAI;
+
+#pragma warning disable SKEXP0001
+#pragma warning disable SKEXP0010
+
+namespace Elsa.Agents;
+
+public static class AgentConfigExtensions
+{
+ public static OpenAIPromptExecutionSettings ToOpenAIPromptExecutionSettings(this AgentConfig agentConfig)
+ {
+ return new OpenAIPromptExecutionSettings
+ {
+ Temperature = agentConfig.ExecutionSettings.Temperature,
+ TopP = agentConfig.ExecutionSettings.TopP,
+ MaxTokens = agentConfig.ExecutionSettings.MaxTokens,
+ PresencePenalty = agentConfig.ExecutionSettings.PresencePenalty,
+ FrequencyPenalty = agentConfig.ExecutionSettings.FrequencyPenalty,
+ ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
+ ResponseFormat = agentConfig.ExecutionSettings.ResponseFormat,
+ ChatSystemPrompt = agentConfig.PromptTemplate,
+ };
+ }
+
+ public static PromptTemplateConfig ToPromptTemplateConfig(this AgentConfig agentConfig)
+ {
+ var promptExecutionSettingsDictionary = new Dictionary
+ {
+ [PromptExecutionSettings.DefaultServiceId] = agentConfig.ToOpenAIPromptExecutionSettings(),
+ };
+
+ return new PromptTemplateConfig
+ {
+ Name = agentConfig.FunctionName,
+ Description = agentConfig.Description,
+ Template = agentConfig.PromptTemplate,
+ ExecutionSettings = promptExecutionSettingsDictionary,
+ AllowDangerouslySetContent = true,
+ InputVariables = agentConfig.InputVariables.Select(x => new InputVariable
+ {
+ Name = x.Name,
+ Description = x.Description,
+ IsRequired = true,
+ AllowDangerouslySetContent = true
+ }).ToList()
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Extensions/FunctionResultExtensions.cs b/src/Elsa.Integrations.Agents.Core/Extensions/FunctionResultExtensions.cs
new file mode 100644
index 00000000..ba02314f
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Extensions/FunctionResultExtensions.cs
@@ -0,0 +1,25 @@
+using System.Text.Json;
+using Microsoft.SemanticKernel;
+
+namespace Elsa.Agents;
+
+public static class FunctionResultExtensions
+{
+ public static async Task AsJsonElementAsync(this Task resultTask)
+ {
+ var result = await resultTask;
+ return result.FunctionResult.AsJsonElement();
+ }
+
+ public static async Task AsJsonElementAsync(this Task resultTask)
+ {
+ var result = await resultTask;
+ return result.AsJsonElement();
+ }
+
+ public static JsonElement AsJsonElement(this FunctionResult result)
+ {
+ var response = result.GetValue()!;
+ return JsonSerializer.Deserialize(response);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Extensions/ModuleExtensions.cs b/src/Elsa.Integrations.Agents.Core/Extensions/ModuleExtensions.cs
new file mode 100644
index 00000000..bf521d0f
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Extensions/ModuleExtensions.cs
@@ -0,0 +1,19 @@
+using Elsa.Agents.Features;
+using Elsa.Features.Services;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.Extensions;
+
+///
+/// Extends with methods to install Semantic Kernel API endpoints.
+///
+public static class ModuleExtensions
+{
+ ///
+ /// Installs the Semantic Kernel API feature.
+ ///
+ public static IModule UseAgents(this IModule module, Action? configure = null)
+ {
+ return module.Use(configure);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Extensions/ServiceCollectionExtensions.cs b/src/Elsa.Integrations.Agents.Core/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..b33fed1c
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,16 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Elsa.Agents;
+
+public static class ServiceCollectionExtensions
+{
+ public static IServiceCollection AddPluginProvider(this IServiceCollection services) where T: class, IPluginProvider
+ {
+ return services.AddScoped();
+ }
+
+ public static IServiceCollection AddAgentServiceProvider(this IServiceCollection services) where T: class, IAgentServiceProvider
+ {
+ return services.AddScoped();
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Features/AgentsFeature.cs b/src/Elsa.Integrations.Agents.Core/Features/AgentsFeature.cs
new file mode 100644
index 00000000..518d1ebd
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Features/AgentsFeature.cs
@@ -0,0 +1,40 @@
+using Elsa.Agents.Plugins;
+using Elsa.Features.Abstractions;
+using Elsa.Features.Services;
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Elsa.Agents.Features;
+
+///
+/// A feature that installs API endpoints to interact with skilled agents.
+///
+[UsedImplicitly]
+public class AgentsFeature(IModule module) : FeatureBase(module)
+{
+ private Func _kernelConfigProviderFactory = sp => sp.GetRequiredService();
+
+ public AgentsFeature UseKernelConfigProvider(Func factory)
+ {
+ _kernelConfigProviderFactory = factory;
+ return this;
+ }
+
+ ///
+ public override void Apply()
+ {
+ Services.AddOptions();
+
+ Services
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped()
+ .AddScoped(_kernelConfigProviderFactory)
+ .AddScoped()
+ .AddPluginProvider()
+ .AddAgentServiceProvider()
+ .AddAgentServiceProvider()
+ ;
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/FodyWeavers.xml b/src/Elsa.Integrations.Agents.Core/FodyWeavers.xml
new file mode 100644
index 00000000..00e1d9a1
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Models/InvokeAgentResult.cs b/src/Elsa.Integrations.Agents.Core/Models/InvokeAgentResult.cs
new file mode 100644
index 00000000..ee2732b9
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Models/InvokeAgentResult.cs
@@ -0,0 +1,14 @@
+using System.Text.Json;
+using Microsoft.SemanticKernel;
+
+namespace Elsa.Agents;
+
+public record InvokeAgentResult(AgentConfig Function, FunctionResult FunctionResult)
+{
+ public object? ParseResult()
+ {
+ var targetType = Type.GetType(Function.OutputVariable.Type) ?? typeof(JsonElement);
+ var json = FunctionResult.GetValue();
+ return JsonSerializer.Deserialize(json, targetType);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Models/KernelBuilderContext.cs b/src/Elsa.Integrations.Agents.Core/Models/KernelBuilderContext.cs
new file mode 100644
index 00000000..8fe3dc15
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Models/KernelBuilderContext.cs
@@ -0,0 +1,20 @@
+using JetBrains.Annotations;
+using Microsoft.SemanticKernel;
+
+namespace Elsa.Agents;
+
+[UsedImplicitly]
+public record KernelBuilderContext(IKernelBuilder KernelBuilder, KernelConfig KernelConfig, ServiceConfig ServiceConfig)
+{
+ public string GetApiKey()
+ {
+ var settings = ServiceConfig.Settings;
+ if (settings.TryGetValue("ApiKey", out var apiKey))
+ return (string)apiKey;
+
+ if (settings.TryGetValue("ApiKeyRef", out var apiKeyRef))
+ return KernelConfig.ApiKeys[(string)apiKeyRef].Value;
+
+ throw new KeyNotFoundException($"No api key found for service {ServiceConfig.Type}");
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Models/PluginDescriptor.cs b/src/Elsa.Integrations.Agents.Core/Models/PluginDescriptor.cs
new file mode 100644
index 00000000..31b0c8c7
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Models/PluginDescriptor.cs
@@ -0,0 +1,33 @@
+using System.ComponentModel;
+using System.Reflection;
+
+namespace Elsa.Agents;
+
+///
+/// A descriptor for a plugin.
+///
+public class PluginDescriptor
+{
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public Type PluginType { get; set; }
+
+ public PluginDescriptorModel ToModel() => new()
+ {
+ Name = Name,
+ Description = Description,
+ PluginType = PluginType.AssemblyQualifiedName!
+ };
+
+ public static PluginDescriptor From(string? name = null)
+ {
+ var pluginType = typeof(TPlugin);
+ var description = pluginType.GetCustomAttribute()?.Description ?? string.Empty;
+ return new PluginDescriptor
+ {
+ Name = name ?? pluginType.Name.Replace("Plugin", ""),
+ Description = description,
+ PluginType = pluginType
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Options/AgentsOptions.cs b/src/Elsa.Integrations.Agents.Core/Options/AgentsOptions.cs
new file mode 100644
index 00000000..a92862a6
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Options/AgentsOptions.cs
@@ -0,0 +1,8 @@
+namespace Elsa.Agents;
+
+public class AgentsOptions
+{
+ public ICollection ApiKeys { get; set; }
+ public ICollection Services { get; set; }
+ public ICollection Agents { get; set; }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Plugins/ImageGeneratorPlugin.cs b/src/Elsa.Integrations.Agents.Core/Plugins/ImageGeneratorPlugin.cs
new file mode 100644
index 00000000..a4d62975
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Plugins/ImageGeneratorPlugin.cs
@@ -0,0 +1,39 @@
+using System.ComponentModel;
+using JetBrains.Annotations;
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.TextToImage;
+
+#pragma warning disable SKEXP0001
+
+namespace Elsa.Agents.Plugins;
+
+[Description("Generates an image from text")]
+[UsedImplicitly]
+public class ImageGeneratorPlugin
+{
+ [KernelFunction("generate_image_from_text")]
+ [Description("Generates an image from text")]
+ [return: Description("The URL to the generated image")]
+ public async Task GenerateImage(
+ Kernel kernel,
+ [Description("The text to generate an image from")]
+ string description,
+ [Description("The width of the image to generate. When not specified, a default size will be used.")]
+ int width = 1024,
+ [Description("The height of the image to generate. When not specified, a default size will be used.")]
+ int height = 1024)
+ {
+ var dallE = kernel.GetRequiredService();
+ var imageUrl = await dallE.GenerateImageAsync(description.Trim(), width, height);
+ return imageUrl;
+ }
+}
+
+[UsedImplicitly]
+public class ImageGeneratorPluginProvider : PluginProvider
+{
+ public override IEnumerable GetPlugins()
+ {
+ yield return PluginDescriptor.From();
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/ServiceProviders/OpenAIChatCompletionProvider.cs b/src/Elsa.Integrations.Agents.Core/ServiceProviders/OpenAIChatCompletionProvider.cs
new file mode 100644
index 00000000..6af538fe
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/ServiceProviders/OpenAIChatCompletionProvider.cs
@@ -0,0 +1,14 @@
+using Microsoft.SemanticKernel;
+
+namespace Elsa.Agents;
+
+public class OpenAIChatCompletionProvider : IAgentServiceProvider
+{
+ public string Name => "OpenAIChatCompletion";
+ public void ConfigureKernel(KernelBuilderContext context)
+ {
+ var modelId = (string)context.ServiceConfig.Settings["ModelId"];
+ var apiKey = context.GetApiKey();
+ context.KernelBuilder.AddOpenAIChatCompletion(modelId, apiKey);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/ServiceProviders/OpenAITextToImageProvider.cs b/src/Elsa.Integrations.Agents.Core/ServiceProviders/OpenAITextToImageProvider.cs
new file mode 100644
index 00000000..b20aba85
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/ServiceProviders/OpenAITextToImageProvider.cs
@@ -0,0 +1,16 @@
+using Microsoft.SemanticKernel;
+#pragma warning disable SKEXP0010
+
+namespace Elsa.Agents;
+
+public class OpenAITextToImageProvider : IAgentServiceProvider
+{
+ public string Name => "OpenAITextToImage";
+
+ public void ConfigureKernel(KernelBuilderContext context)
+ {
+ var modelId = (string)context.ServiceConfig.Settings["ModelId"];
+ var apiKey = context.GetApiKey();
+ context.KernelBuilder.AddOpenAITextToImage(apiKey, modelId: modelId);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Services/AgentInvoker.cs b/src/Elsa.Integrations.Agents.Core/Services/AgentInvoker.cs
new file mode 100644
index 00000000..a40458c5
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Services/AgentInvoker.cs
@@ -0,0 +1,55 @@
+using Microsoft.SemanticKernel;
+using Microsoft.SemanticKernel.Connectors.OpenAI;
+
+#pragma warning disable SKEXP0010
+#pragma warning disable SKEXP0001
+
+namespace Elsa.Agents;
+
+public class AgentInvoker(KernelFactory kernelFactory, IKernelConfigProvider kernelConfigProvider)
+{
+ public async Task InvokeAgentAsync(string agentName, IDictionary input, CancellationToken cancellationToken = default)
+ {
+ var kernelConfig = await kernelConfigProvider.GetKernelConfigAsync(cancellationToken);
+ var kernel = kernelFactory.CreateKernel(kernelConfig, agentName);
+ var agentConfig = kernelConfig.Agents[agentName];
+ var executionSettings = agentConfig.ExecutionSettings;
+ var promptExecutionSettings = new OpenAIPromptExecutionSettings
+ {
+ Temperature = executionSettings.Temperature,
+ TopP = executionSettings.TopP,
+ MaxTokens = executionSettings.MaxTokens,
+ PresencePenalty = executionSettings.PresencePenalty,
+ FrequencyPenalty = executionSettings.FrequencyPenalty,
+ ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions,
+ ResponseFormat = executionSettings.ResponseFormat,
+ ChatSystemPrompt = agentConfig.PromptTemplate,
+ };
+
+ var promptExecutionSettingsDictionary = new Dictionary
+ {
+ [PromptExecutionSettings.DefaultServiceId] = promptExecutionSettings,
+ };
+
+ var promptTemplateConfig = new PromptTemplateConfig
+ {
+ Name = agentConfig.FunctionName,
+ Description = agentConfig.Description,
+ Template = agentConfig.PromptTemplate,
+ ExecutionSettings = promptExecutionSettingsDictionary,
+ AllowDangerouslySetContent = true,
+ InputVariables = agentConfig.InputVariables.Select(x => new InputVariable
+ {
+ Name = x.Name,
+ Description = x.Description,
+ IsRequired = true,
+ AllowDangerouslySetContent = true
+ }).ToList()
+ };
+
+ var kernelFunction = kernel.CreateFunctionFromPrompt(promptTemplateConfig);
+ var kernelArguments = new KernelArguments(input);
+ var result = await kernelFunction.InvokeAsync(kernel, kernelArguments, cancellationToken: cancellationToken);
+ return new(agentConfig, result);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Services/ConfigurationKernelConfigProvider.cs b/src/Elsa.Integrations.Agents.Core/Services/ConfigurationKernelConfigProvider.cs
new file mode 100644
index 00000000..e4320df2
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Services/ConfigurationKernelConfigProvider.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+using Microsoft.Extensions.Options;
+
+namespace Elsa.Agents;
+
+[UsedImplicitly]
+public class ConfigurationKernelConfigProvider(IOptions options) : IKernelConfigProvider
+{
+ public Task GetKernelConfigAsync(CancellationToken cancellationToken = default)
+ {
+ var kernelConfig = new KernelConfig();
+ foreach (var apiKey in options.Value.ApiKeys) kernelConfig.ApiKeys[apiKey.Name] = apiKey;
+ foreach (var service in options.Value.Services) kernelConfig.Services[service.Name] = service;
+ foreach (var agent in options.Value.Agents) kernelConfig.Agents[agent.Name] = agent;
+ return Task.FromResult(kernelConfig);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Services/KernelFactory.cs b/src/Elsa.Integrations.Agents.Core/Services/KernelFactory.cs
new file mode 100644
index 00000000..bdc738f4
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Services/KernelFactory.cs
@@ -0,0 +1,113 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.SemanticKernel;
+
+#pragma warning disable SKEXP0001
+#pragma warning disable SKEXP0010
+
+namespace Elsa.Agents;
+
+public class KernelFactory(IPluginDiscoverer pluginDiscoverer, IServiceDiscoverer serviceDiscoverer, ILoggerFactory loggerFactory, IServiceProvider serviceProvider, ILogger logger)
+{
+ public Kernel CreateKernel(KernelConfig kernelConfig, string agentName)
+ {
+ var agent = kernelConfig.Agents[agentName];
+ return CreateKernel(kernelConfig, agent);
+ }
+
+ public Kernel CreateKernel(KernelConfig kernelConfig, AgentConfig agentConfig)
+ {
+ var builder = Kernel.CreateBuilder();
+ builder.Services.AddLogging(services => services.AddConsole().SetMinimumLevel(LogLevel.Trace));
+ builder.Services.AddSingleton(agentConfig);
+
+ ApplyAgentConfig(builder, kernelConfig, agentConfig);
+
+ return builder.Build();
+ }
+
+ private void ApplyAgentConfig(IKernelBuilder builder, KernelConfig kernelConfig, AgentConfig agentConfig)
+ {
+ var services = serviceDiscoverer.Discover().ToDictionary(x => x.Name);
+
+ foreach (string serviceName in agentConfig.Services)
+ {
+ if (!kernelConfig.Services.TryGetValue(serviceName, out var serviceConfig))
+ {
+ logger.LogWarning($"Service {serviceName} not found");
+ continue;
+ }
+
+ AddService(builder, kernelConfig, serviceConfig, services);
+ }
+
+ AddPlugins(builder, agentConfig);
+ AddAgents(builder, kernelConfig, agentConfig);
+ }
+
+ private void AddService(IKernelBuilder builder, KernelConfig kernelConfig, ServiceConfig serviceConfig, Dictionary services)
+ {
+ if (!services.TryGetValue(serviceConfig.Type, out var serviceProvider))
+ {
+ logger.LogWarning($"Service provider {serviceConfig.Type} not found");
+ return;
+ }
+
+ var context = new KernelBuilderContext(builder, kernelConfig, serviceConfig);
+ serviceProvider.ConfigureKernel(context);
+ }
+
+ private void AddPlugins(IKernelBuilder builder, AgentConfig agent)
+ {
+ var plugins = pluginDiscoverer.GetPluginDescriptors().ToDictionary(x => x.Name);
+ foreach (var pluginName in agent.Plugins)
+ {
+ if (!plugins.TryGetValue(pluginName, out var pluginDescriptor))
+ {
+ logger.LogWarning($"Plugin {pluginName} not found");
+ continue;
+ }
+
+ var pluginType = pluginDescriptor.PluginType;
+ var pluginInstance = ActivatorUtilities.GetServiceOrCreateInstance(serviceProvider, pluginType);
+ builder.Plugins.AddFromObject(pluginInstance, pluginName);
+ }
+ }
+
+ private void AddAgents(IKernelBuilder builder, KernelConfig kernelConfig, AgentConfig agent)
+ {
+ foreach (var agentName in agent.Agents)
+ {
+ if (!kernelConfig.Agents.TryGetValue(agentName, out var subAgent))
+ {
+ logger.LogWarning($"Agent {agentName} not found");
+ continue;
+ }
+
+ var promptExecutionSettings = subAgent.ToOpenAIPromptExecutionSettings();
+ var promptExecutionSettingsDictionary = new Dictionary
+ {
+ [PromptExecutionSettings.DefaultServiceId] = promptExecutionSettings,
+ };
+ var promptTemplateConfig = new PromptTemplateConfig
+ {
+ Name = subAgent.FunctionName,
+ Description = subAgent.Description,
+ Template = subAgent.PromptTemplate,
+ ExecutionSettings = promptExecutionSettingsDictionary,
+ AllowDangerouslySetContent = true,
+ InputVariables = subAgent.InputVariables.Select(x => new InputVariable
+ {
+ Name = x.Name,
+ Description = x.Description,
+ IsRequired = true,
+ AllowDangerouslySetContent = true
+ }).ToList()
+ };
+
+ var subAgentFunction = KernelFunctionFactory.CreateFromPrompt(promptTemplateConfig, loggerFactory: loggerFactory);
+ var agentPlugin = KernelPluginFactory.CreateFromFunctions(subAgent.Name, subAgent.Description, [subAgentFunction]);
+ builder.Plugins.Add(agentPlugin);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Services/PluginDiscoverer.cs b/src/Elsa.Integrations.Agents.Core/Services/PluginDiscoverer.cs
new file mode 100644
index 00000000..fa645195
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Services/PluginDiscoverer.cs
@@ -0,0 +1,9 @@
+namespace Elsa.Agents;
+
+public class PluginDiscoverer(IEnumerable providers) : IPluginDiscoverer
+{
+ public IEnumerable GetPluginDescriptors()
+ {
+ return providers.SelectMany(x => x.GetPlugins());
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Core/Services/ServiceDiscoverer.cs b/src/Elsa.Integrations.Agents.Core/Services/ServiceDiscoverer.cs
new file mode 100644
index 00000000..46f18c6e
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Core/Services/ServiceDiscoverer.cs
@@ -0,0 +1,9 @@
+namespace Elsa.Agents;
+
+public class ServiceDiscoverer(IEnumerable providers) : IServiceDiscoverer
+{
+ public IEnumerable Discover()
+ {
+ return providers;
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Agents/AgentInputModel.cs b/src/Elsa.Integrations.Agents.Models/Agents/AgentInputModel.cs
new file mode 100644
index 00000000..b5c003ed
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Agents/AgentInputModel.cs
@@ -0,0 +1,17 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Agents;
+
+public class AgentInputModel
+{
+ [Required] public string Name { get; set; } = "";
+ [Required] public string Description { get; set; } = "";
+ public ICollection Services { get; set; } = [];
+ [Required] public string FunctionName { get; set; } = "";
+ [Required] public string PromptTemplate { get; set; } = "";
+ public ICollection InputVariables { get; set; } = [];
+ [Required] public OutputVariableConfig OutputVariable { get; set; } = new();
+ public ExecutionSettingsConfig ExecutionSettings { get; set; } = new();
+ public ICollection Plugins { get; set; } = [];
+ public ICollection Agents { get; set; } = [];
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Agents/AgentModel.cs b/src/Elsa.Integrations.Agents.Models/Agents/AgentModel.cs
new file mode 100644
index 00000000..6adfd629
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Agents/AgentModel.cs
@@ -0,0 +1,8 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Agents;
+
+public class AgentModel : AgentInputModel
+{
+ [Required] public string Id { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/ApiKeys/ApiKeyInputModel.cs b/src/Elsa.Integrations.Agents.Models/ApiKeys/ApiKeyInputModel.cs
new file mode 100644
index 00000000..6a2ad23d
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/ApiKeys/ApiKeyInputModel.cs
@@ -0,0 +1,9 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Agents;
+
+public class ApiKeyInputModel
+{
+ [Required] public string Name { get; set; } = null!;
+ [Required] public string Value { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/ApiKeys/ApiKeyModel.cs b/src/Elsa.Integrations.Agents.Models/ApiKeys/ApiKeyModel.cs
new file mode 100644
index 00000000..678522eb
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/ApiKeys/ApiKeyModel.cs
@@ -0,0 +1,8 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Agents;
+
+public class ApiKeyModel : ApiKeyInputModel
+{
+ [Required] public string Id { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/BulkActions/BulkDeleteRequest.cs b/src/Elsa.Integrations.Agents.Models/BulkActions/BulkDeleteRequest.cs
new file mode 100644
index 00000000..45a99951
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/BulkActions/BulkDeleteRequest.cs
@@ -0,0 +1,8 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Agents;
+
+public class BulkDeleteRequest
+{
+ [Required] public ICollection Ids { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/BulkActions/BulkDeleteResponse.cs b/src/Elsa.Integrations.Agents.Models/BulkActions/BulkDeleteResponse.cs
new file mode 100644
index 00000000..5ad391ab
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/BulkActions/BulkDeleteResponse.cs
@@ -0,0 +1,3 @@
+namespace Elsa.Agents;
+
+public record BulkDeleteResponse(long DeletedCount);
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Configs/AgentConfig.cs b/src/Elsa.Integrations.Agents.Models/Configs/AgentConfig.cs
new file mode 100644
index 00000000..6257ae12
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Configs/AgentConfig.cs
@@ -0,0 +1,17 @@
+namespace Elsa.Agents;
+
+public class AgentConfig
+{
+ public string Name { get; set; } = "";
+ public string Description { get; set; } = "";
+ public ICollection Services { get; set; } = [];
+ public string FunctionName { get; set; } = null!;
+ public string PromptTemplate { get; set; } = null!;
+ public ICollection InputVariables { get; set; } = [];
+ public OutputVariableConfig OutputVariable { get; set; } = new();
+ public ExecutionSettingsConfig ExecutionSettings { get; set; } = new();
+ public ICollection Plugins { get; set; } = [];
+ public ICollection Agents { get; set; } = [];
+
+
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Configs/ApiKeyConfig.cs b/src/Elsa.Integrations.Agents.Models/Configs/ApiKeyConfig.cs
new file mode 100644
index 00000000..88b4e733
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Configs/ApiKeyConfig.cs
@@ -0,0 +1,10 @@
+using JetBrains.Annotations;
+
+namespace Elsa.Agents;
+
+[UsedImplicitly]
+public class ApiKeyConfig
+{
+ public string Name { get; set; }
+ public string Value { get; set; }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Configs/ExecutionSettingsConfig.cs b/src/Elsa.Integrations.Agents.Models/Configs/ExecutionSettingsConfig.cs
new file mode 100644
index 00000000..bdb512b7
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Configs/ExecutionSettingsConfig.cs
@@ -0,0 +1,13 @@
+#pragma warning disable SKEXP0010
+
+namespace Elsa.Agents;
+
+public class ExecutionSettingsConfig
+{
+ public int? MaxTokens { get; set; }
+ public double Temperature { get; set; }
+ public double TopP { get; set; }
+ public double PresencePenalty { get; set; }
+ public double FrequencyPenalty { get; set; }
+ public string? ResponseFormat { get; set; }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Configs/FunctionConfig.cs b/src/Elsa.Integrations.Agents.Models/Configs/FunctionConfig.cs
new file mode 100644
index 00000000..a4b3d05b
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Configs/FunctionConfig.cs
@@ -0,0 +1,7 @@
+namespace Elsa.Agents;
+
+public class FunctionConfig
+{
+
+
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Configs/InputVariableConfig.cs b/src/Elsa.Integrations.Agents.Models/Configs/InputVariableConfig.cs
new file mode 100644
index 00000000..4e0faaaa
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Configs/InputVariableConfig.cs
@@ -0,0 +1,8 @@
+namespace Elsa.Agents;
+
+public class InputVariableConfig
+{
+ public string Name { get; set; } = "";
+ public string Description { get; set; } = "";
+ public string Type { get; set; } = "";
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Configs/KernelConfig.cs b/src/Elsa.Integrations.Agents.Models/Configs/KernelConfig.cs
new file mode 100644
index 00000000..75c28527
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Configs/KernelConfig.cs
@@ -0,0 +1,8 @@
+namespace Elsa.Agents;
+
+public class KernelConfig
+{
+ public IDictionary ApiKeys { get; set; } = new Dictionary();
+ public IDictionary Services { get; } = new Dictionary();
+ public IDictionary Agents { get; } = new Dictionary();
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Configs/OutputVariableConfig.cs b/src/Elsa.Integrations.Agents.Models/Configs/OutputVariableConfig.cs
new file mode 100644
index 00000000..e9f4fe1c
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Configs/OutputVariableConfig.cs
@@ -0,0 +1,7 @@
+namespace Elsa.Agents;
+
+public class OutputVariableConfig
+{
+ public string Description { get; set; } = "";
+ public string Type { get; set; } = "";
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Configs/ServiceConfig.cs b/src/Elsa.Integrations.Agents.Models/Configs/ServiceConfig.cs
new file mode 100644
index 00000000..4ad83b92
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Configs/ServiceConfig.cs
@@ -0,0 +1,8 @@
+namespace Elsa.Agents;
+
+public class ServiceConfig
+{
+ public string Name { get; set; }
+ public string Type { get; set; }
+ public IDictionary Settings { get; set; } = new Dictionary();
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Elsa.Integrations.Agents.Models.csproj b/src/Elsa.Integrations.Agents.Models/Elsa.Integrations.Agents.Models.csproj
new file mode 100644
index 00000000..08a821df
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Elsa.Integrations.Agents.Models.csproj
@@ -0,0 +1,9 @@
+
+
+
+ Provides models that can be shared between the API and API clients
+ elsa module agents semantic kernel llm ai api
+ Elsa.Agents
+
+
+
diff --git a/src/Elsa.Integrations.Agents.Models/Elsa.Integrations.Agents.Models.csproj.DotSettings b/src/Elsa.Integrations.Agents.Models/Elsa.Integrations.Agents.Models.csproj.DotSettings
new file mode 100644
index 00000000..a9c50b1b
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Elsa.Integrations.Agents.Models.csproj.DotSettings
@@ -0,0 +1,8 @@
+
+ True
+ True
+ True
+ True
+ True
+ True
+ True
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/FodyWeavers.xml b/src/Elsa.Integrations.Agents.Models/FodyWeavers.xml
new file mode 100644
index 00000000..00e1d9a1
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Plugins/PluginDescriptor.cs b/src/Elsa.Integrations.Agents.Models/Plugins/PluginDescriptor.cs
new file mode 100644
index 00000000..c8325fdf
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Plugins/PluginDescriptor.cs
@@ -0,0 +1,11 @@
+namespace Elsa.Agents;
+
+///
+/// A descriptor for a plugin.
+///
+public class PluginDescriptorModel
+{
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public string PluginType { get; set; }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Services/ServiceInputModel.cs b/src/Elsa.Integrations.Agents.Models/Services/ServiceInputModel.cs
new file mode 100644
index 00000000..2ffa016a
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Services/ServiceInputModel.cs
@@ -0,0 +1,10 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Agents;
+
+public class ServiceInputModel
+{
+ [Required] public string Name { get; set; } = null!;
+ [Required] public string Type { get; set; } = null!;
+ public IDictionary Settings { get; set; } = new Dictionary();
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/Services/ServiceModel.cs b/src/Elsa.Integrations.Agents.Models/Services/ServiceModel.cs
new file mode 100644
index 00000000..845c835f
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/Services/ServiceModel.cs
@@ -0,0 +1,8 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Elsa.Agents;
+
+public class ServiceModel : ServiceInputModel
+{
+ [Required] public string Id { get; set; } = null!;
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/UniqueName/GenerateUniqueNameResponse.cs b/src/Elsa.Integrations.Agents.Models/UniqueName/GenerateUniqueNameResponse.cs
new file mode 100644
index 00000000..14140daf
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/UniqueName/GenerateUniqueNameResponse.cs
@@ -0,0 +1,3 @@
+namespace Elsa.Agents;
+
+public record GenerateUniqueNameResponse(string Name);
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/UniqueName/IsUniqueNameRequest.cs b/src/Elsa.Integrations.Agents.Models/UniqueName/IsUniqueNameRequest.cs
new file mode 100644
index 00000000..e7626911
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/UniqueName/IsUniqueNameRequest.cs
@@ -0,0 +1,11 @@
+using System.ComponentModel.DataAnnotations;
+using JetBrains.Annotations;
+
+namespace Elsa.Agents;
+
+[UsedImplicitly]
+public class IsUniqueNameRequest
+{
+ [Required] public string Name { get; set; } = null!;
+ public string? Id { get; set; }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Models/UniqueName/IsUniqueNameResponse.cs b/src/Elsa.Integrations.Agents.Models/UniqueName/IsUniqueNameResponse.cs
new file mode 100644
index 00000000..2a36a364
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Models/UniqueName/IsUniqueNameResponse.cs
@@ -0,0 +1,5 @@
+using JetBrains.Annotations;
+
+namespace Elsa.Agents;
+
+[UsedImplicitly] public record IsUniqueNameResponse(bool IsUnique);
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/AgentsMySqlProvidersExtensions.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/AgentsMySqlProvidersExtensions.cs
new file mode 100644
index 00000000..a247a3b6
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/AgentsMySqlProvidersExtensions.cs
@@ -0,0 +1,22 @@
+using System.Reflection;
+using Elsa.Integrations.Agents.Persistence.EntityFrameworkCore;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.EntityFrameworkCore.Extensions;
+
+///
+/// Provides extensions to configure EF Core to use MySQL.
+///
+public static class AgentsMySqlProvidersExtensions
+{
+ private static Assembly Assembly => typeof(AgentsMySqlProvidersExtensions).Assembly;
+
+ ///
+ /// Configures the feature to use MySQL.
+ ///
+ public static EFCoreAgentPersistenceFeature UseMySql(this EFCoreAgentPersistenceFeature feature, string connectionString, ElsaDbContextOptions? options = null)
+ {
+ feature.UseMySql(Assembly, connectionString, options);
+ return feature;
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/DbContextFactories.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/DbContextFactories.cs
new file mode 100644
index 00000000..76ba7473
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/DbContextFactories.cs
@@ -0,0 +1,15 @@
+using Elsa.EntityFrameworkCore.Abstractions;
+using Elsa.EntityFrameworkCore.Extensions;
+using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore;
+
+namespace Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql;
+
+[UsedImplicitly]
+public class MySqlAgentsDbContextFactory : DesignTimeDbContextFactoryBase
+{
+ protected override void ConfigureBuilder(DbContextOptionsBuilder builder, string connectionString)
+ {
+ builder.UseElsaMySql(GetType().Assembly, connectionString, serverVersion: MySqlServerVersion.LatestSupportedServerVersion);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql.csproj b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql.csproj
new file mode 100644
index 00000000..1b98e8e2
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net8.0;net9.0
+ Provides an EF Core migrations for MySQL for the Agents Persistence module.
+ elsa module agents semantic kernel llm ai persistence efcore entity framework core mysql
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/FodyWeavers.xml b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/FodyWeavers.xml
new file mode 100644
index 00000000..00e1d9a1
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/Migrations/20250302144856_V3_4.Designer.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/Migrations/20250302144856_V3_4.Designer.cs
new file mode 100644
index 00000000..1e75e194
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/Migrations/20250302144856_V3_4.Designer.cs
@@ -0,0 +1,119 @@
+//
+using Elsa.Integrations.Agents.Persistence.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql.Migrations
+{
+ [DbContext(typeof(AgentsDbContext))]
+ [Migration("20250302144856_V3_4")]
+ partial class V3_4
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("Elsa")
+ .HasAnnotation("ProductVersion", "8.0.13")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.AgentDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("AgentConfig")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.Property("TenantId")
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_AgentDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_AgentDefinition_TenantId");
+
+ b.ToTable("AgentDefinitions", "Elsa");
+ });
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.ApiKeyDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.Property("TenantId")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_ApiKeyDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_ApiKeyDefinition_TenantId");
+
+ b.ToTable("ApiKeysDefinitions", "Elsa");
+ });
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.ServiceDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.Property("Settings")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("TenantId")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_ServiceDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_ServiceDefinition_TenantId");
+
+ b.ToTable("ServicesDefinitions", "Elsa");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/Migrations/20250302144856_V3_4.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/Migrations/20250302144856_V3_4.cs
new file mode 100644
index 00000000..828958d4
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/Migrations/20250302144856_V3_4.cs
@@ -0,0 +1,144 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql.Migrations
+{
+ ///
+ public partial class V3_4 : Migration
+ {
+ private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema;
+
+ ///
+ public V3_4(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema)
+ {
+ _schema = schema;
+ }
+
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.EnsureSchema(
+ name: "Elsa");
+
+ migrationBuilder.AlterDatabase()
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.CreateTable(
+ name: "AgentDefinitions",
+ schema: _schema.Schema,
+ columns: table => new
+ {
+ Id = table.Column(type: "varchar(255)", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Name = table.Column(type: "varchar(255)", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Description = table.Column(type: "longtext", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ AgentConfig = table.Column(type: "longtext", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ TenantId = table.Column(type: "varchar(255)", nullable: true)
+ .Annotation("MySql:CharSet", "utf8mb4")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AgentDefinitions", x => x.Id);
+ })
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.CreateTable(
+ name: "ApiKeysDefinitions",
+ schema: _schema.Schema,
+ columns: table => new
+ {
+ Id = table.Column(type: "varchar(255)", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Name = table.Column(type: "varchar(255)", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Value = table.Column(type: "longtext", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ TenantId = table.Column(type: "varchar(255)", nullable: true)
+ .Annotation("MySql:CharSet", "utf8mb4")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ApiKeysDefinitions", x => x.Id);
+ })
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.CreateTable(
+ name: "ServicesDefinitions",
+ schema: _schema.Schema,
+ columns: table => new
+ {
+ Id = table.Column(type: "varchar(255)", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Name = table.Column(type: "varchar(255)", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Type = table.Column(type: "longtext", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ Settings = table.Column(type: "longtext", nullable: false)
+ .Annotation("MySql:CharSet", "utf8mb4"),
+ TenantId = table.Column(type: "varchar(255)", nullable: true)
+ .Annotation("MySql:CharSet", "utf8mb4")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ServicesDefinitions", x => x.Id);
+ })
+ .Annotation("MySql:CharSet", "utf8mb4");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AgentDefinition_Name",
+ schema: _schema.Schema,
+ table: "AgentDefinitions",
+ column: "Name");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AgentDefinition_TenantId",
+ schema: _schema.Schema,
+ table: "AgentDefinitions",
+ column: "TenantId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ApiKeyDefinition_Name",
+ schema: _schema.Schema,
+ table: "ApiKeysDefinitions",
+ column: "Name");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ApiKeyDefinition_TenantId",
+ schema: _schema.Schema,
+ table: "ApiKeysDefinitions",
+ column: "TenantId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ServiceDefinition_Name",
+ schema: _schema.Schema,
+ table: "ServicesDefinitions",
+ column: "Name");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ServiceDefinition_TenantId",
+ schema: _schema.Schema,
+ table: "ServicesDefinitions",
+ column: "TenantId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "AgentDefinitions",
+ schema: _schema.Schema);
+
+ migrationBuilder.DropTable(
+ name: "ApiKeysDefinitions",
+ schema: _schema.Schema);
+
+ migrationBuilder.DropTable(
+ name: "ServicesDefinitions",
+ schema: _schema.Schema);
+ }
+ }
+}
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/Migrations/AgentsDbContextModelSnapshot.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/Migrations/AgentsDbContextModelSnapshot.cs
new file mode 100644
index 00000000..ec2ad3d8
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/Migrations/AgentsDbContextModelSnapshot.cs
@@ -0,0 +1,116 @@
+//
+using Elsa.Integrations.Agents.Persistence.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql.Migrations
+{
+ [DbContext(typeof(AgentsDbContext))]
+ partial class AgentsDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("Elsa")
+ .HasAnnotation("ProductVersion", "8.0.13")
+ .HasAnnotation("Relational:MaxIdentifierLength", 64);
+
+ MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.AgentDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("AgentConfig")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.Property("TenantId")
+ .HasColumnType("varchar(255)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_AgentDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_AgentDefinition_TenantId");
+
+ b.ToTable("AgentDefinitions", "Elsa");
+ });
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.ApiKeyDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.Property("TenantId")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_ApiKeyDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_ApiKeyDefinition_TenantId");
+
+ b.ToTable("ApiKeysDefinitions", "Elsa");
+ });
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.ServiceDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("varchar(255)");
+
+ b.Property("Settings")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.Property("TenantId")
+ .HasColumnType("varchar(255)");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasColumnType("longtext");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_ServiceDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_ServiceDefinition_TenantId");
+
+ b.ToTable("ServicesDefinitions", "Elsa");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/efcore-3.4.sh b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/efcore-3.4.sh
new file mode 100644
index 00000000..e8aa6cee
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.MySql/efcore-3.4.sh
@@ -0,0 +1 @@
+ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.IElsaDbContextSchema --efOptions "migrations add V3_4 -c AgentsDbContext -o Migrations"
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/DbContextFactories.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/DbContextFactories.cs
new file mode 100644
index 00000000..9070a895
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/DbContextFactories.cs
@@ -0,0 +1,15 @@
+using Elsa.EntityFrameworkCore.Abstractions;
+using Elsa.EntityFrameworkCore.Extensions;
+using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore;
+
+namespace Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql;
+
+[UsedImplicitly]
+public class PostgreSqlAgentsDbContextFactory : DesignTimeDbContextFactoryBase
+{
+ protected override void ConfigureBuilder(DbContextOptionsBuilder builder, string connectionString)
+ {
+ builder.UseElsaPostgreSql(GetType().Assembly, connectionString);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql.csproj b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql.csproj
new file mode 100644
index 00000000..1e24f848
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Provides an EF Core migrations for PostgreSQL for the Agents Persistence module.
+ elsa module agents semantic kernel llm ai persistence efcore entity framework core postgresql
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/FodyWeavers.xml b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/FodyWeavers.xml
new file mode 100644
index 00000000..00e1d9a1
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/Migrations/20250302144916_V3_4.Designer.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/Migrations/20250302144916_V3_4.Designer.cs
new file mode 100644
index 00000000..bedf2012
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/Migrations/20250302144916_V3_4.Designer.cs
@@ -0,0 +1,119 @@
+//
+using Elsa.Integrations.Agents.Persistence.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql.Migrations
+{
+ [DbContext(typeof(AgentsDbContext))]
+ [Migration("20250302144916_V3_4")]
+ partial class V3_4
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("Elsa")
+ .HasAnnotation("ProductVersion", "8.0.13")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.AgentDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("AgentConfig")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TenantId")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_AgentDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_AgentDefinition_TenantId");
+
+ b.ToTable("AgentDefinitions", "Elsa");
+ });
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.ApiKeyDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TenantId")
+ .HasColumnType("text");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_ApiKeyDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_ApiKeyDefinition_TenantId");
+
+ b.ToTable("ApiKeysDefinitions", "Elsa");
+ });
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.ServiceDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Settings")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TenantId")
+ .HasColumnType("text");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_ServiceDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_ServiceDefinition_TenantId");
+
+ b.ToTable("ServicesDefinitions", "Elsa");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/Migrations/20250302144916_V3_4.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/Migrations/20250302144916_V3_4.cs
new file mode 100644
index 00000000..b50a08e3
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/Migrations/20250302144916_V3_4.cs
@@ -0,0 +1,124 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql.Migrations
+{
+ ///
+ public partial class V3_4 : Migration
+ {
+ private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema;
+
+ ///
+ public V3_4(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema)
+ {
+ _schema = schema;
+ }
+
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.EnsureSchema(
+ name: "Elsa");
+
+ migrationBuilder.CreateTable(
+ name: "AgentDefinitions",
+ schema: _schema.Schema,
+ columns: table => new
+ {
+ Id = table.Column(type: "text", nullable: false),
+ Name = table.Column(type: "text", nullable: false),
+ Description = table.Column(type: "text", nullable: false),
+ AgentConfig = table.Column(type: "text", nullable: false),
+ TenantId = table.Column(type: "text", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AgentDefinitions", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ApiKeysDefinitions",
+ schema: _schema.Schema,
+ columns: table => new
+ {
+ Id = table.Column(type: "text", nullable: false),
+ Name = table.Column(type: "text", nullable: false),
+ Value = table.Column(type: "text", nullable: false),
+ TenantId = table.Column(type: "text", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ApiKeysDefinitions", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ServicesDefinitions",
+ schema: _schema.Schema,
+ columns: table => new
+ {
+ Id = table.Column(type: "text", nullable: false),
+ Name = table.Column(type: "text", nullable: false),
+ Type = table.Column(type: "text", nullable: false),
+ Settings = table.Column(type: "text", nullable: false),
+ TenantId = table.Column(type: "text", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ServicesDefinitions", x => x.Id);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AgentDefinition_Name",
+ schema: _schema.Schema,
+ table: "AgentDefinitions",
+ column: "Name");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AgentDefinition_TenantId",
+ schema: _schema.Schema,
+ table: "AgentDefinitions",
+ column: "TenantId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ApiKeyDefinition_Name",
+ schema: _schema.Schema,
+ table: "ApiKeysDefinitions",
+ column: "Name");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ApiKeyDefinition_TenantId",
+ schema: _schema.Schema,
+ table: "ApiKeysDefinitions",
+ column: "TenantId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ServiceDefinition_Name",
+ schema: _schema.Schema,
+ table: "ServicesDefinitions",
+ column: "Name");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ServiceDefinition_TenantId",
+ schema: _schema.Schema,
+ table: "ServicesDefinitions",
+ column: "TenantId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "AgentDefinitions",
+ schema: _schema.Schema);
+
+ migrationBuilder.DropTable(
+ name: "ApiKeysDefinitions",
+ schema: _schema.Schema);
+
+ migrationBuilder.DropTable(
+ name: "ServicesDefinitions",
+ schema: _schema.Schema);
+ }
+ }
+}
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/Migrations/AgentsDbContextModelSnapshot.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/Migrations/AgentsDbContextModelSnapshot.cs
new file mode 100644
index 00000000..957b4e2f
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/Migrations/AgentsDbContextModelSnapshot.cs
@@ -0,0 +1,116 @@
+//
+using Elsa.Integrations.Agents.Persistence.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql.Migrations
+{
+ [DbContext(typeof(AgentsDbContext))]
+ partial class AgentsDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("Elsa")
+ .HasAnnotation("ProductVersion", "8.0.13")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.AgentDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("AgentConfig")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TenantId")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_AgentDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_AgentDefinition_TenantId");
+
+ b.ToTable("AgentDefinitions", "Elsa");
+ });
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.ApiKeyDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TenantId")
+ .HasColumnType("text");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_ApiKeyDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_ApiKeyDefinition_TenantId");
+
+ b.ToTable("ApiKeysDefinitions", "Elsa");
+ });
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.ServiceDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Settings")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TenantId")
+ .HasColumnType("text");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_ServiceDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_ServiceDefinition_TenantId");
+
+ b.ToTable("ServicesDefinitions", "Elsa");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/PostgreSqlProvidersExtensions.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/PostgreSqlProvidersExtensions.cs
new file mode 100644
index 00000000..430f3dcb
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/PostgreSqlProvidersExtensions.cs
@@ -0,0 +1,22 @@
+using System.Reflection;
+using Elsa.Integrations.Agents.Persistence.EntityFrameworkCore;
+
+// ReSharper disable once CheckNamespace
+namespace Elsa.EntityFrameworkCore.Extensions;
+
+///
+/// Provides extensions to configure EF Core to use SQL Server.
+///
+public static class AgentsPostgreSqlProvidersExtensions
+{
+ private static Assembly Assembly => typeof(AgentsPostgreSqlProvidersExtensions).Assembly;
+
+ ///
+ /// Configures the feature to use SQL Server.
+ ///
+ public static EFCoreAgentPersistenceFeature UsePostgreSql(this EFCoreAgentPersistenceFeature feature, string connectionString, ElsaDbContextOptions? options = null)
+ {
+ feature.UsePostgreSql(Assembly, connectionString, options);
+ return feature;
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/efcore-3.4.sh b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/efcore-3.4.sh
new file mode 100644
index 00000000..e8aa6cee
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.PostgreSql/efcore-3.4.sh
@@ -0,0 +1 @@
+ef-migration-runtime-schema --interface Elsa.EntityFrameworkCore.IElsaDbContextSchema --efOptions "migrations add V3_4 -c AgentsDbContext -o Migrations"
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/DbContextFactories.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/DbContextFactories.cs
new file mode 100644
index 00000000..34ae323e
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/DbContextFactories.cs
@@ -0,0 +1,15 @@
+using Elsa.EntityFrameworkCore.Abstractions;
+using Elsa.EntityFrameworkCore.Extensions;
+using JetBrains.Annotations;
+using Microsoft.EntityFrameworkCore;
+
+namespace Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer;
+
+[UsedImplicitly]
+public class SqlServerAgentsDbContextFactory : DesignTimeDbContextFactoryBase
+{
+ protected override void ConfigureBuilder(DbContextOptionsBuilder builder, string connectionString)
+ {
+ builder.UseElsaSqlServer(GetType().Assembly, connectionString);
+ }
+}
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer.csproj b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer.csproj
new file mode 100644
index 00000000..79591086
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Provides an EF Core migrations for SQL Server for the Agents Persistence module.
+ elsa module agents semantic kernel llm ai persistence efcore entity framework core sqlserver
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/FodyWeavers.xml b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/FodyWeavers.xml
new file mode 100644
index 00000000..00e1d9a1
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/Migrations/20250302144957_V3_4.Designer.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/Migrations/20250302144957_V3_4.Designer.cs
new file mode 100644
index 00000000..9e6b1e39
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/Migrations/20250302144957_V3_4.Designer.cs
@@ -0,0 +1,119 @@
+//
+using Elsa.Integrations.Agents.Persistence.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer.Migrations
+{
+ [DbContext(typeof(AgentsDbContext))]
+ [Migration("20250302144957_V3_4")]
+ partial class V3_4
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("Elsa")
+ .HasAnnotation("ProductVersion", "8.0.13")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.AgentDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("AgentConfig")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("TenantId")
+ .HasColumnType("nvarchar(450)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_AgentDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_AgentDefinition_TenantId");
+
+ b.ToTable("AgentDefinitions", "Elsa");
+ });
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.ApiKeyDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("TenantId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Value")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_ApiKeyDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_ApiKeyDefinition_TenantId");
+
+ b.ToTable("ApiKeysDefinitions", "Elsa");
+ });
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.ServiceDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Settings")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TenantId")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property("Type")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .HasDatabaseName("IX_ServiceDefinition_Name");
+
+ b.HasIndex("TenantId")
+ .HasDatabaseName("IX_ServiceDefinition_TenantId");
+
+ b.ToTable("ServicesDefinitions", "Elsa");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/Migrations/20250302144957_V3_4.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/Migrations/20250302144957_V3_4.cs
new file mode 100644
index 00000000..6e2e77f2
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/Migrations/20250302144957_V3_4.cs
@@ -0,0 +1,124 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer.Migrations
+{
+ ///
+ public partial class V3_4 : Migration
+ {
+ private readonly Elsa.EntityFrameworkCore.IElsaDbContextSchema _schema;
+
+ ///
+ public V3_4(Elsa.EntityFrameworkCore.IElsaDbContextSchema schema)
+ {
+ _schema = schema;
+ }
+
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.EnsureSchema(
+ name: "Elsa");
+
+ migrationBuilder.CreateTable(
+ name: "AgentDefinitions",
+ schema: _schema.Schema,
+ columns: table => new
+ {
+ Id = table.Column(type: "nvarchar(450)", nullable: false),
+ Name = table.Column(type: "nvarchar(450)", nullable: false),
+ Description = table.Column(type: "nvarchar(max)", nullable: false),
+ AgentConfig = table.Column(type: "nvarchar(max)", nullable: false),
+ TenantId = table.Column(type: "nvarchar(450)", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AgentDefinitions", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ApiKeysDefinitions",
+ schema: _schema.Schema,
+ columns: table => new
+ {
+ Id = table.Column(type: "nvarchar(450)", nullable: false),
+ Name = table.Column(type: "nvarchar(450)", nullable: false),
+ Value = table.Column(type: "nvarchar(max)", nullable: false),
+ TenantId = table.Column(type: "nvarchar(450)", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ApiKeysDefinitions", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ServicesDefinitions",
+ schema: _schema.Schema,
+ columns: table => new
+ {
+ Id = table.Column(type: "nvarchar(450)", nullable: false),
+ Name = table.Column(type: "nvarchar(450)", nullable: false),
+ Type = table.Column(type: "nvarchar(max)", nullable: false),
+ Settings = table.Column(type: "nvarchar(max)", nullable: false),
+ TenantId = table.Column(type: "nvarchar(450)", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ServicesDefinitions", x => x.Id);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AgentDefinition_Name",
+ schema: _schema.Schema,
+ table: "AgentDefinitions",
+ column: "Name");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AgentDefinition_TenantId",
+ schema: _schema.Schema,
+ table: "AgentDefinitions",
+ column: "TenantId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ApiKeyDefinition_Name",
+ schema: _schema.Schema,
+ table: "ApiKeysDefinitions",
+ column: "Name");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ApiKeyDefinition_TenantId",
+ schema: _schema.Schema,
+ table: "ApiKeysDefinitions",
+ column: "TenantId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ServiceDefinition_Name",
+ schema: _schema.Schema,
+ table: "ServicesDefinitions",
+ column: "Name");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_ServiceDefinition_TenantId",
+ schema: _schema.Schema,
+ table: "ServicesDefinitions",
+ column: "TenantId");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "AgentDefinitions",
+ schema: _schema.Schema);
+
+ migrationBuilder.DropTable(
+ name: "ApiKeysDefinitions",
+ schema: _schema.Schema);
+
+ migrationBuilder.DropTable(
+ name: "ServicesDefinitions",
+ schema: _schema.Schema);
+ }
+ }
+}
diff --git a/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/Migrations/AgentsDbContextModelSnapshot.cs b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/Migrations/AgentsDbContextModelSnapshot.cs
new file mode 100644
index 00000000..0a8783c3
--- /dev/null
+++ b/src/Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer/Migrations/AgentsDbContextModelSnapshot.cs
@@ -0,0 +1,116 @@
+//
+using Elsa.Integrations.Agents.Persistence.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Elsa.Integrations.Agents.Persistence.EntityFrameworkCore.SqlServer.Migrations
+{
+ [DbContext(typeof(AgentsDbContext))]
+ partial class AgentsDbContextModelSnapshot : ModelSnapshot
+ {
+ protected override void BuildModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasDefaultSchema("Elsa")
+ .HasAnnotation("ProductVersion", "8.0.13")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("Elsa.Integrations.Agents.Persistence.Entities.AgentDefinition", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("nvarchar(450)");
+
+ b.Property