Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
2557c2b
init message queue
Jan 9, 2026
8f23c64
init mq connection and service
Jan 9, 2026
e7a169d
relocate
Jan 10, 2026
2c5ae64
minor change
Jan 10, 2026
4e46bc5
minor change
Jan 10, 2026
94355cc
Merge branch 'master' of https://github.com/SciSharp/BotSharp into fe…
Jan 12, 2026
d0249eb
refine mq
Jan 12, 2026
a7024e0
minor change
Jan 12, 2026
8621fff
Merge branch 'master' of https://github.com/SciSharp/BotSharp into fe…
Jan 16, 2026
56dae92
refine message queue
iceljc Jan 20, 2026
26c3b42
minor change
iceljc Jan 20, 2026
5a6761f
Merge branch 'master' of https://github.com/SciSharp/BotSharp into fe…
Jan 20, 2026
3380b89
add error handling
Jan 20, 2026
5c50205
minor change
Jan 20, 2026
caba30c
temp save
Jan 21, 2026
4a01a6f
refine queue message handling
Jan 21, 2026
292270c
refine rule criteria and actions
Jan 21, 2026
33c247f
rename to messaging
Jan 21, 2026
a8cdca7
remove delayed
Jan 21, 2026
7c6b477
add custom method action
iceljc Jan 22, 2026
ff57169
refine action and add mq channel pool
Jan 22, 2026
ee2e444
refine config
iceljc Jan 23, 2026
436990d
fix mq config
iceljc Jan 23, 2026
5281479
minor change
iceljc Jan 25, 2026
184035c
Merge branch 'master' of https://github.com/SciSharp/BotSharp into fe…
iceljc Jan 25, 2026
77c2adb
refien rule action
iceljc Jan 26, 2026
9341685
replace url
iceljc Jan 26, 2026
dc5e2e6
avoid negative delay qty
iceljc Jan 26, 2026
f73598b
add agent rule action
iceljc Jan 26, 2026
2ea5483
ignore action when null
iceljc Jan 26, 2026
3d1d0ea
return function calling response
iceljc Jan 26, 2026
4b7f5ac
refine http rule
Jan 27, 2026
0b97b67
add agent filter to rule trigger options
iceljc Jan 28, 2026
cd68f58
refine rule criteria
Jan 28, 2026
a318c7a
refine agent rule structure
Jan 28, 2026
953854a
add json options
Jan 28, 2026
208b775
validate nullable rule action name
Jan 29, 2026
2db1aa3
fix namespace and correct unit conversion
iceljc Jan 30, 2026
30c670a
refine type conversion and mq channel
iceljc Jan 30, 2026
17b93b1
add json options in mq publish options
iceljc Jan 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions BotSharp.sln
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.A2A", "src\In
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.MultiTenancy", "src\Plugins\BotSharp.Plugin.MultiTenancy\BotSharp.Plugin.MultiTenancy.csproj", "{562DD0C6-DAC8-02CC-C1DD-D43DF186CE76}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.RabbitMQ", "src\Plugins\BotSharp.Plugin.RabbitMQ\BotSharp.Plugin.RabbitMQ.csproj", "{8E609A1C-0421-5BB5-DEA9-5FDB68F6D1C5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -669,6 +671,14 @@ Global
{562DD0C6-DAC8-02CC-C1DD-D43DF186CE76}.Release|Any CPU.Build.0 = Release|Any CPU
{562DD0C6-DAC8-02CC-C1DD-D43DF186CE76}.Release|x64.ActiveCfg = Release|Any CPU
{562DD0C6-DAC8-02CC-C1DD-D43DF186CE76}.Release|x64.Build.0 = Release|Any CPU
{8E609A1C-0421-5BB5-DEA9-5FDB68F6D1C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8E609A1C-0421-5BB5-DEA9-5FDB68F6D1C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E609A1C-0421-5BB5-DEA9-5FDB68F6D1C5}.Debug|x64.ActiveCfg = Debug|Any CPU
{8E609A1C-0421-5BB5-DEA9-5FDB68F6D1C5}.Debug|x64.Build.0 = Debug|Any CPU
{8E609A1C-0421-5BB5-DEA9-5FDB68F6D1C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E609A1C-0421-5BB5-DEA9-5FDB68F6D1C5}.Release|Any CPU.Build.0 = Release|Any CPU
{8E609A1C-0421-5BB5-DEA9-5FDB68F6D1C5}.Release|x64.ActiveCfg = Release|Any CPU
{8E609A1C-0421-5BB5-DEA9-5FDB68F6D1C5}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -745,6 +755,7 @@ Global
{13223C71-9EAC-9835-28ED-5A4833E6F915} = {53E7CD86-0D19-40D9-A0FA-AB4613837E89}
{E8D01281-D52A-BFF4-33DB-E35D91754272} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749}
{562DD0C6-DAC8-02CC-C1DD-D43DF186CE76} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
{8E609A1C-0421-5BB5-DEA9-5FDB68F6D1C5} = {64264688-0F5C-4AB0-8F2B-B59B717CCE00}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19}
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<PackageVersion Include="Google_GenerativeAI.Live" Version="3.4.1" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Polly" Version="8.4.2" />
<PackageVersion Include="RabbitMQ.Client" Version="7.2.0" />
<PackageVersion Include="SharpFuzz" Version="2.2.0" />
<PackageVersion Include="SharpHook" Version="5.3.9" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.12" />
Expand Down
58 changes: 56 additions & 2 deletions src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRule.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Text.Json;

namespace BotSharp.Abstraction.Agents.Models;

public class AgentRule
Expand All @@ -8,6 +10,58 @@ public class AgentRule
[JsonPropertyName("disabled")]
public bool Disabled { get; set; }

[JsonPropertyName("criteria")]
public string Criteria { get; set; } = string.Empty;
[JsonPropertyName("rule_criteria")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public AgentRuleCriteria? RuleCriteria { get; set; }

[JsonPropertyName("rule_action")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public AgentRuleAction? RuleAction { get; set; }
}

public class AgentRuleCriteria : AgentRuleConfigBase
{
/// <summary>
/// Criteria
/// </summary>
[JsonPropertyName("criteria_text")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string CriteriaText { get; set; } = string.Empty;

/// <summary>
/// Adaptive configuration for rule criteria.
/// This flexible JSON document can store any criteria-specific configuration.
/// The structure depends on the criteria executor
/// </summary>
[JsonPropertyName("config")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override JsonDocument? Config { get; set; }
}

public class AgentRuleAction : AgentRuleConfigBase
{
/// <summary>
/// Adaptive configuration for rule actions.
/// This flexible JSON document can store any action-specific configuration.
/// The structure depends on the action type:
/// - For "Http" action: contains http_context with base_url, relative_url, method, etc.
/// - For "MessageQueue" action: contains mq_config with topic_name, routing_key, etc.
/// - For custom actions: can contain any custom configuration structure
/// </summary>
[JsonPropertyName("config")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override JsonDocument? Config { get; set; }
}

public class AgentRuleConfigBase
{
[JsonPropertyName("name")]
public virtual string Name { get; set; }

[JsonPropertyName("disabled")]
public virtual bool Disabled { get; set; }

[JsonPropertyName("config")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public virtual JsonDocument? Config { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace BotSharp.Abstraction.Infrastructures.MessageQueues;

/// <summary>
/// Abstract interface for message queue consumers.
/// Implement this interface to create consumers that are independent of MQ products (e.g., RabbitMQ, Kafka, Azure Service Bus).
/// </summary>
public interface IMQConsumer : IDisposable
{
/// <summary>
/// Gets the consumer config
/// </summary>
object Config { get; }

/// <summary>
/// Handles the received message from the queue.
/// </summary>
/// <param name="channel">The consumer channel identifier</param>
/// <param name="data">The message data as string</param>
/// <returns>True if the message was handled successfully, false otherwise</returns>
Task<bool> HandleMessageAsync(string channel, string data);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using BotSharp.Abstraction.Infrastructures.MessageQueues.Models;

namespace BotSharp.Abstraction.Infrastructures.MessageQueues;

public interface IMQService : IDisposable
{
/// <summary>
/// Subscribe a consumer to the message queue.
/// The consumer will be initialized with the appropriate MQ-specific infrastructure.
/// </summary>
/// <param name="key">Unique identifier for the consumer</param>
/// <param name="consumer">The consumer implementing IMQConsumer interface</param>
/// <returns>Task<bool> representing the async subscription operation</returns>
Task<bool> SubscribeAsync(string key, IMQConsumer consumer);

/// <summary>
/// Unsubscribe a consumer from the message queue.
/// </summary>
/// <param name="key">Unique identifier for the consumer</param>
/// <returns>Task<bool> representing the async unsubscription operation</returns>
Task<bool> UnsubscribeAsync(string key);

/// <summary>
/// Publish payload to message queue
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="payload"></param>
/// <param name="options"></param>
/// <returns></returns>
Task<bool> PublishAsync<T>(T payload, MQPublishOptions options);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Microsoft.Extensions.Logging;

namespace BotSharp.Abstraction.Infrastructures.MessageQueues;

/// <summary>
/// Abstract base class for RabbitMQ consumers.
/// Implements IMQConsumer to allow other projects to define consumers independently of RabbitMQ.
/// The RabbitMQ-specific infrastructure is handled by RabbitMQService.
/// </summary>
public abstract class MQConsumerBase : IMQConsumer
{
protected readonly IServiceProvider _services;
protected readonly ILogger _logger;
private bool _disposed = false;

/// <summary>
/// Gets the consumer config for this consumer.
/// Override this property to customize exchange, queue and routing configuration.
/// </summary>
public abstract object Config { get; }

protected MQConsumerBase(
IServiceProvider services,
ILogger logger)
{
_services = services;
_logger = logger;
}

/// <summary>
/// Handles the received message from the queue.
/// </summary>
/// <param name="channel">The consumer channel identifier</param>
/// <param name="data">The message data as string</param>
/// <returns>True if the message was handled successfully, false otherwise</returns>
public abstract Task<bool> HandleMessageAsync(string channel, string data);

public void Dispose()
{
if (_disposed)
{
return;
}

var consumerName = GetType().Name;
_logger.LogWarning($"Disposing consumer: {consumerName}");
_disposed = true;
GC.SuppressFinalize(this);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace BotSharp.Abstraction.Infrastructures.MessageQueues;

public class MessageQueueSettings
{
public bool Enabled { get; set; }
public string Provider { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace BotSharp.Abstraction.Infrastructures.MessageQueues.Models;

public class MQMessage<T>
{
public MQMessage(T payload, string messageId)
{
Payload = payload;
MessageId = messageId;
}

public T Payload { get; set; }
public string MessageId { get; set; }
public DateTime CreateDate { get; set; } = DateTime.UtcNow;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Text.Json;

namespace BotSharp.Abstraction.Infrastructures.MessageQueues.Models;

/// <summary>
/// Configuration options for publishing messages to a message queue.
/// These options are MQ-product agnostic and can be adapted by different implementations.
/// </summary>
public class MQPublishOptions
{
/// <summary>
/// The topic name (exchange in RabbitMQ, topic in Kafka/Azure Service Bus).
/// </summary>
public string TopicName { get; set; } = string.Empty;

/// <summary>
/// The routing key (partition key in some MQ systems, used for message routing).
/// </summary>
public string RoutingKey { get; set; } = string.Empty;

/// <summary>
/// Delay in milliseconds before the message is delivered.
/// </summary>
public long DelayMilliseconds { get; set; }

/// <summary>
/// Optional unique identifier for the message.
/// </summary>
public string? MessageId { get; set; }

/// <summary>
/// Additional arguments for the publish configuration (MQ-specific).
/// </summary>
public Dictionary<string, object?> Arguments { get; set; } = [];

/// <summary>
/// Json serializer options
/// </summary>
public JsonSerializerOptions? JsonOptions { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using BotSharp.Abstraction.Hooks;
using BotSharp.Abstraction.Rules.Models;

namespace BotSharp.Abstraction.Rules.Hooks;

public interface IRuleTriggerHook : IHookBase
{
Task BeforeRuleCriteriaExecuted(Agent agent, IRuleTrigger trigger, RuleCriteriaContext context) => Task.CompletedTask;
Task AfterRuleCriteriaExecuted(Agent agent, IRuleTrigger trigger, RuleCriteriaResult result) => Task.CompletedTask;

Task BeforeRuleActionExecuted(Agent agent, IRuleTrigger trigger, RuleActionContext context) => Task.CompletedTask;
Task AfterRuleActionExecuted(Agent agent, IRuleTrigger trigger, RuleActionResult result) => Task.CompletedTask;
}
26 changes: 25 additions & 1 deletion src/Infrastructure/BotSharp.Abstraction/Rules/IRuleAction.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Rules.Models;
using BotSharp.Abstraction.Rules.Options;

namespace BotSharp.Abstraction.Rules;

/// <summary>
/// Base interface for rule actions that can be executed by the RuleEngine
/// </summary>
public interface IRuleAction
{
}
/// <summary>
/// The unique name of the rule action provider
/// </summary>
string Name { get; }

/// <summary>
/// Execute the rule action
/// </summary>
/// <param name="agent">The agent that triggered the rule</param>
/// <param name="trigger">The rule trigger</param>
/// <param name="context">The action context</param>
/// <returns>The action execution result</returns>
Task<RuleActionResult> ExecuteAsync(
Agent agent,
IRuleTrigger trigger,
RuleActionContext context);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
using BotSharp.Abstraction.Rules.Models;

namespace BotSharp.Abstraction.Rules;

public interface IRuleCriteria
{
string Provider { get; }

Task<RuleCriteriaResult> ValidateAsync(Agent agent, IRuleTrigger trigger, RuleCriteriaContext context)
=> Task.FromResult(new RuleCriteriaResult());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Text.Json;

namespace BotSharp.Abstraction.Rules.Models;

public class RuleActionContext
{
public string Text { get; set; } = string.Empty;
public Dictionary<string, object?> Parameters { get; set; } = [];
public JsonSerializerOptions? JsonOptions { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
namespace BotSharp.Abstraction.Rules.Models;

/// <summary>
/// Result of a rule action execution
/// </summary>
public class RuleActionResult
{
/// <summary>
/// Whether the action executed successfully
/// </summary>
public bool Success { get; set; }

/// <summary>
/// The conversation ID if a new conversation was created
/// </summary>
public string? ConversationId { get; set; }

/// <summary>
/// Response content from the action
/// </summary>
public string? Response { get; set; }

/// <summary>
/// Error message if the action failed
/// </summary>
public string? ErrorMessage { get; set; }

public static RuleActionResult Succeeded(string? response = null)
{
return new RuleActionResult
{
Success = true,
Response = response
};
}

public static RuleActionResult Failed(string errorMessage)
{
return new RuleActionResult
{
Success = false,
ErrorMessage = errorMessage
};
}
}

Loading
Loading