diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs index c3c69f08..7f22c8f2 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/BuiltInAgentId.cs @@ -51,4 +51,14 @@ public class BuiltInAgentId /// Evaluate prompt and conversation /// public const string Evaluator = "dfd9b46d-d00c-40af-8a75-3fbdc2b89869"; + + /// + /// Translates user-defined natural language rules into programmatic code + /// + public const string RuleEncoder = "6acfb93c-3412-402e-9ba5-c5d3cd8f0161"; + + /// + /// Schedule job + /// + public const string Crontab = "c2o139da-a62a-4355-8605-fdf0ffaca58e"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs index cf0defbc..d03a4208 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Enums/ConversationChannel.cs @@ -7,4 +7,5 @@ public class ConversationChannel public const string Phone = "phone"; public const string Messenger = "messenger"; public const string Email = "email"; + public const string Cron = "cron"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/IDistributedLocker.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/IDistributedLocker.cs new file mode 100644 index 00000000..2f5a8069 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/IDistributedLocker.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Abstraction.Infrastructures; + +public interface IDistributedLocker +{ + bool Lock(string resource, Action action, int timeout = 30); + Task LockAsync(string resource, Func action, int timeout = 30); +} diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabHook.cs b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabHook.cs new file mode 100644 index 00000000..49ffe54c --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabHook.cs @@ -0,0 +1,8 @@ +using BotSharp.Core.Crontab.Models; + +namespace BotSharp.Core.Crontab.Abstraction; + +public interface ICrontabHook +{ + Task OnCronTriggered(CrontabItem item); +} diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabService.cs b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabService.cs new file mode 100644 index 00000000..6a18018d --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Crontab/Abstraction/ICrontabService.cs @@ -0,0 +1,9 @@ +using BotSharp.Core.Crontab.Models; + +namespace BotSharp.Core.Crontab.Abstraction; + +public interface ICrontabService +{ + Task> GetCrontable(); + Task ScheduledTimeArrived(CrontabItem item); +} diff --git a/src/Infrastructure/BotSharp.Core.Crontab/BotSharp.Core.Crontab.csproj b/src/Infrastructure/BotSharp.Core.Crontab/BotSharp.Core.Crontab.csproj new file mode 100644 index 00000000..f1c70ad7 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Crontab/BotSharp.Core.Crontab.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + + + + + + + + PreserveNewest + + + + + + + + + + + + + diff --git a/src/Infrastructure/BotSharp.Core.Crontab/CrontabPlugin.cs b/src/Infrastructure/BotSharp.Core.Crontab/CrontabPlugin.cs new file mode 100644 index 00000000..a449cbbb --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Crontab/CrontabPlugin.cs @@ -0,0 +1,40 @@ +/***************************************************************************** + Copyright 2024 Written by Haiping Chen. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +******************************************************************************/ + +namespace BotSharp.Core.Crontab; + +/// +/// Crontab plugin is a time-based job scheduler in agent framework. +/// The cron system is used for automating repetitive tasks, such as trigger AI Agent to do specific task periodically. +/// +public class CrontabPlugin : IBotSharpPlugin +{ + public string Id => "3155c15e-28d3-43f7-8ead-fc43324ec21a"; + public string Name => "BotSharp Crontab"; + public string Description => "Crontab plugin is a time-based job scheduler in agent framework. The cron system is used to trigger AI Agent to do specific task periodically."; + public string IconUrl => "https://icon-library.com/images/stop-watch-icon/stop-watch-icon-10.jpg"; + + public string[] AgentIds = + [ + BuiltInAgentId.Crontab + ]; + + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + services.AddScoped(); + services.AddHostedService(); + } +} diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Models/CrontabItem.cs b/src/Infrastructure/BotSharp.Core.Crontab/Models/CrontabItem.cs new file mode 100644 index 00000000..50cafd02 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Crontab/Models/CrontabItem.cs @@ -0,0 +1,14 @@ +namespace BotSharp.Core.Crontab.Models; + +public class CrontabItem +{ + public string UserId { get; set; } = null!; + public string AgentId { get; set; } = null!; + public string Topic { get; set; } = null!; + public string Cron { get; set; } = null!; + + public override string ToString() + { + return $"AgentId: {AgentId}, UserId: {UserId}, Topic: {Topic}"; + } +} diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs new file mode 100644 index 00000000..a8b5f6dd --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs @@ -0,0 +1,77 @@ +/***************************************************************************** + Copyright 2024 Written by Haiping Chen. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +******************************************************************************/ + +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Core.Crontab.Models; +using BotSharp.Core.Infrastructures; + + +/***************************************************************************** + Copyright 2024 Written by Haiping Chen. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +******************************************************************************/ + +using Microsoft.Extensions.Logging; + +namespace BotSharp.Core.Crontab.Services; + +/// +/// The Crontab service schedules distributed events based on the execution times provided by users. +/// In a scalable environment, distributed locks are used to ensure that each event is triggered only once. +/// +public class CrontabService : ICrontabService +{ + private readonly IServiceProvider _services; + private ILogger _logger; + + public CrontabService(IServiceProvider services, ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task> GetCrontable() + { + return + [ + new CrontabItem + { + Cron = "*/30 * * * * *", + AgentId = BuiltInAgentId.AIAssistant, + } + ]; + } + + public async Task ScheduledTimeArrived(CrontabItem item) + { + _logger.LogInformation("ScheduledTimeArrived"); + await HookEmitter.Emit(_services, async hook => + await hook.OnCronTriggered(item) + ); + await Task.Delay(1000 * 10); + } +} diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs new file mode 100644 index 00000000..0abd050f --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabWatcher.cs @@ -0,0 +1,70 @@ +using BotSharp.Abstraction.Infrastructures; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using NCrontab; + +namespace BotSharp.Core.Crontab.Services; + +public class CrontabWatcher : BackgroundService +{ + private readonly ILogger _logger; + private readonly IServiceProvider _services; + + public CrontabWatcher(IServiceProvider services, ILogger logger) + { + _logger = logger; + _services = services; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.LogInformation("Crontab Watcher background service is running."); + + using (var scope = _services.CreateScope()) + { + var locker = scope.ServiceProvider.GetRequiredService(); + + while (!stoppingToken.IsCancellationRequested) + { + var delay = Task.Delay(1000, stoppingToken); + + await locker.LockAsync("CrontabWatcher", async () => + { + await RunCronChecker(scope.ServiceProvider); + }); + + await delay; + } + + _logger.LogWarning("Crontab Watcher background service is stopped."); + } + } + + private async Task RunCronChecker(IServiceProvider services) + { + var cron = services.GetRequiredService(); + var crons = await cron.GetCrontable(); + foreach (var item in crons) + { + var schedule = CrontabSchedule.Parse(item.Cron, new CrontabSchedule.ParseOptions + { + IncludingSeconds = true // Ensure you account for seconds + }); + + // Get the current time + var currentTime = DateTime.UtcNow; + + // Get the next occurrence from the schedule + var nextOccurrence = schedule.GetNextOccurrence(currentTime.AddSeconds(-1)); + + // Check if the current time matches the schedule + bool matches = currentTime >= nextOccurrence && currentTime < nextOccurrence.AddSeconds(1); + + if (matches) + { + _logger.LogInformation($"The current time matches the cron expression {item}"); + cron.ScheduledTimeArrived(item); + } + } + } +} diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Using.cs b/src/Infrastructure/BotSharp.Core.Crontab/Using.cs new file mode 100644 index 00000000..0da87fb2 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Crontab/Using.cs @@ -0,0 +1,7 @@ +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; + +global using BotSharp.Abstraction.Agents.Enums; +global using BotSharp.Abstraction.Plugins; +global using BotSharp.Core.Crontab.Services; +global using BotSharp.Core.Crontab.Abstraction; diff --git a/src/Infrastructure/BotSharp.Core.Crontab/data/agents/c2o139da-a62a-4355-8605-fdf0ffaca58e/agent.json b/src/Infrastructure/BotSharp.Core.Crontab/data/agents/c2o139da-a62a-4355-8605-fdf0ffaca58e/agent.json new file mode 100644 index 00000000..b17e1e6d --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Crontab/data/agents/c2o139da-a62a-4355-8605-fdf0ffaca58e/agent.json @@ -0,0 +1,24 @@ +{ + "id": "c2o139da-a62a-4355-8605-fdf0ffaca58e", + "name": "Crontab", + "description": "Convert the user-specified schedule into a Cron expression, accurate to the second level.", + "iconUrl": "https://icon-library.com/images/stop-watch-icon/stop-watch-icon-10.jpg", + "type": "task", + "createdDateTime": "2024-12-03T00:00:00Z", + "updatedDateTime": "2024-12-03T00:00:00Z", + "disabled": false, + "isPublic": true, + "profiles": [ "cron" ], + "llmConfig": { + "provider": "openai", + "model": "gpt-4o-mini" + }, + "routingRules": [ + { + "field": "cron_expression", + "required": true, + "field_type": "string", + "description": "A Cron expression, accurate to the second level." + } + ] +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core.Rules/BotSharp.Core.Rules.csproj b/src/Infrastructure/BotSharp.Core.Rules/BotSharp.Core.Rules.csproj new file mode 100644 index 00000000..caef78b8 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Rules/BotSharp.Core.Rules.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/src/Infrastructure/BotSharp.Core.Rules/RulesPlugin.cs b/src/Infrastructure/BotSharp.Core.Rules/RulesPlugin.cs new file mode 100644 index 00000000..14177437 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Rules/RulesPlugin.cs @@ -0,0 +1,19 @@ +namespace BotSharp.Core.Rules; + +public class RulesPlugin : IBotSharpPlugin +{ + public string Id => "0197c1bc-9ae6-4c56-a305-8a1b4095bebc"; + public string Name => "BotSharp Rules"; + public string Description => "Translates user-defined natural language rules into programmatic code and is responsible for executing these rules under user-specified conditions."; + public string IconUrl => "https://w7.pngwing.com/pngs/442/614/png-transparent-regulation-computer-icons-regulatory-compliance-medical-device-manufacturing-others-miscellaneous-blue-text-thumbnail.png"; + + public string[] AgentIds = + [ + BuiltInAgentId.RuleEncoder + ]; + + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + + } +} diff --git a/src/Infrastructure/BotSharp.Core.Rules/Using.cs b/src/Infrastructure/BotSharp.Core.Rules/Using.cs new file mode 100644 index 00000000..d982daae --- /dev/null +++ b/src/Infrastructure/BotSharp.Core.Rules/Using.cs @@ -0,0 +1,4 @@ +global using BotSharp.Abstraction.Agents.Enums; +global using BotSharp.Abstraction.Plugins; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs b/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs index efef5201..d3ace389 100644 --- a/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/BotSharpSideCarPlugin.cs @@ -8,8 +8,10 @@ namespace BotSharp.Core.SideCar; public class BotSharpSideCarPlugin : IBotSharpPlugin { public string Id => "06e5a276-bba0-45af-9625-889267c341c9"; - public string Name => "Side Car"; - public string Description => "Provides side car for calling agent cluster in conversation"; + public string Name => "BotSharp SideCar"; + public string Description => "Provide side car pattern to to better handle Agent Cluster calls in the same conversation. Agent cluster is composed of multiple Routing Agents."; + public string? IconUrl => "https://icons.veryicon.com/png/128/internet--web/2022-alibaba-cloud-product-icon-cloud/aliyuncvc-cloud-video-conference.png"; + public SettingsMeta Settings => new SettingsMeta("SideCar"); public object GetNewSettingsInstance() => new SideCarSettings(); diff --git a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs index c5e33a4b..af8a9324 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpCoreExtensions.cs @@ -25,7 +25,7 @@ public static class BotSharpCoreExtensions config.Bind("Interpreter", interpreterSettings); services.AddSingleton(x => interpreterSettings); - services.AddSingleton(); + services.AddSingleton(); // Register template render services.AddSingleton(); diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs index 32c7af52..48ff193f 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/DistributedLocker.cs @@ -1,9 +1,10 @@ +using BotSharp.Abstraction.Infrastructures; using Medallion.Threading.Redis; using StackExchange.Redis; namespace BotSharp.Core.Infrastructures; -public class DistributedLocker +public class DistributedLocker : IDistributedLocker { private readonly IConnectionMultiplexer _redis; private readonly ILogger _logger; @@ -14,7 +15,7 @@ public class DistributedLocker _logger = logger; } - public async Task Lock(string resource, Func> action, int timeoutInSeconds = 30) + public async Task LockAsync(string resource, Func action, int timeoutInSeconds = 30) { var timeout = TimeSpan.FromSeconds(timeoutInSeconds); @@ -24,9 +25,11 @@ public class DistributedLocker if (handle == null) { _logger.LogWarning($"Acquire lock for {resource} failed due to after {timeout}s timeout."); + return false; } - return await action(); + await action(); + return true; } } diff --git a/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj b/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj index c5bd88d0..d053f4d6 100644 --- a/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj +++ b/src/Plugins/BotSharp.Plugin.AnthropicAI/BotSharp.Plugin.AnthropicAI.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Plugins/BotSharp.Plugin.ExcelHandler/BotSharp.Plugin.ExcelHandler.csproj b/src/Plugins/BotSharp.Plugin.ExcelHandler/BotSharp.Plugin.ExcelHandler.csproj index a57e28b0..e23bd5e7 100644 --- a/src/Plugins/BotSharp.Plugin.ExcelHandler/BotSharp.Plugin.ExcelHandler.csproj +++ b/src/Plugins/BotSharp.Plugin.ExcelHandler/BotSharp.Plugin.ExcelHandler.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -28,7 +28,6 @@ - diff --git a/src/Plugins/BotSharp.Plugin.JavaScriptInterpreter/BotSharp.Plugin.JavaScriptInterpreter.csproj b/src/Plugins/BotSharp.Plugin.JavaScriptInterpreter/BotSharp.Plugin.JavaScriptInterpreter.csproj new file mode 100644 index 00000000..4596ce1b --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.JavaScriptInterpreter/BotSharp.Plugin.JavaScriptInterpreter.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/src/Plugins/BotSharp.Plugin.JavaScriptInterpreter/JsInterpreterPlugin.cs b/src/Plugins/BotSharp.Plugin.JavaScriptInterpreter/JsInterpreterPlugin.cs new file mode 100644 index 00000000..e3811809 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.JavaScriptInterpreter/JsInterpreterPlugin.cs @@ -0,0 +1,24 @@ +using BotSharp.Abstraction.Plugins; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace BotSharp.Plugin.JavaScriptInterpreter; + +public class JsInterpreterPlugin : IBotSharpAppPlugin +{ + public string Id => "7a5a8cd7-26d9-4ac3-9d79-d02084bea372"; + public string Name => "JavaScript Interpreter"; + public string Description => ""; + public string? IconUrl => ""; + + public void Configure(IApplicationBuilder app) + { + + } + + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + + } +} diff --git a/src/Plugins/BotSharp.Plugin.LLamaSharp/BotSharp.Plugin.LLamaSharp.csproj b/src/Plugins/BotSharp.Plugin.LLamaSharp/BotSharp.Plugin.LLamaSharp.csproj index bd7cf7b7..68ce651c 100644 --- a/src/Plugins/BotSharp.Plugin.LLamaSharp/BotSharp.Plugin.LLamaSharp.csproj +++ b/src/Plugins/BotSharp.Plugin.LLamaSharp/BotSharp.Plugin.LLamaSharp.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Plugins/BotSharp.Plugin.LLamaSharp/LlamaAiModel.cs b/src/Plugins/BotSharp.Plugin.LLamaSharp/LlamaAiModel.cs index 9d55dead..a7d83def 100644 --- a/src/Plugins/BotSharp.Plugin.LLamaSharp/LlamaAiModel.cs +++ b/src/Plugins/BotSharp.Plugin.LLamaSharp/LlamaAiModel.cs @@ -36,7 +36,6 @@ public class LlamaAiModel _params = new ModelParams(Path.Combine(_settings.ModelDir, model)) { ContextSize = (uint)_settings.MaxContextLength, - Seed = 1337, GpuLayerCount = _settings.NumberOfGpuLayer }; diff --git a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextEmbeddingProvider.cs b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextEmbeddingProvider.cs index a9288064..1ec461ae 100644 --- a/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextEmbeddingProvider.cs +++ b/src/Plugins/BotSharp.Plugin.LLamaSharp/Providers/TextEmbeddingProvider.cs @@ -1,4 +1,6 @@ using System.IO; +using System.Xml.Linq; +using static System.Net.Mime.MediaTypeNames; namespace BotSharp.Plugin.LLamaSharp.Providers; @@ -19,7 +21,7 @@ public class TextEmbeddingProvider : ITextEmbedding _settings = settings; } - public Task GetVectorAsync(string text) + public async Task GetVectorAsync(string text) { if (_embedder == null) { @@ -29,10 +31,10 @@ public class TextEmbeddingProvider : ITextEmbedding _embedder = new LLamaEmbedder(weights, @params); } - return _embedder.GetEmbeddings(text); + return (await _embedder.GetEmbeddings(text)).First(); } - public Task> GetVectorsAsync(List texts) + public async Task> GetVectorsAsync(List texts) { throw new NotImplementedException(); } diff --git a/src/Plugins/BotSharp.Plugin.PythonInterpreter/BotSharp.Plugin.PythonInterpreter.csproj b/src/Plugins/BotSharp.Plugin.PythonInterpreter/BotSharp.Plugin.PythonInterpreter.csproj index b3293a0f..c5e3d4ff 100644 --- a/src/Plugins/BotSharp.Plugin.PythonInterpreter/BotSharp.Plugin.PythonInterpreter.csproj +++ b/src/Plugins/BotSharp.Plugin.PythonInterpreter/BotSharp.Plugin.PythonInterpreter.csproj @@ -25,7 +25,7 @@ - +