Merge branch 'master' of https://github.com/SciSharp/BotSharp into features/refine-agent-filter

This commit is contained in:
Jicheng Lu 2025-01-30 11:15:00 -06:00
commit d34284eddf
18 changed files with 129 additions and 77 deletions

View file

@ -5,6 +5,7 @@ public class AgentTaskFilter
public Pagination Pager { get; set; } = new Pagination();
public string? AgentId { get; set; }
public bool? Enabled { get; set; }
public string? Status { get; set; }
public static AgentTaskFilter Empty()
{

View file

@ -4,7 +4,7 @@ public interface IRuleTrigger
{
string Channel => throw new NotImplementedException("Please set the channel of trigger");
string Name { get; set; }
string Name => throw new NotImplementedException("Please set the name of trigger");
string EntityType { get; set; }

View file

@ -1,5 +1,9 @@
public class TaskExecutionStatus
/// <summary>
/// Agent task status
/// </summary>
public class TaskStatus
{
public const string Scheduled = "scheduled";
public const string New = "new";
public const string Running = "running";
public const string Success = "success";

View file

@ -0,0 +1,8 @@
using BotSharp.Abstraction.Tasks.Models;
namespace BotSharp.Abstraction.Tasks;
public interface ITaskFeeder
{
Task<List<AgentTask>> GetTasks();
}

View file

@ -1,35 +1,27 @@
namespace BotSharp.Abstraction.Tasks.Models;
public class AgentTask : AgentTaskMetaData
public class AgentTask
{
public string Id { get; set; }
public string Content { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
public string AgentId { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
public Agent Agent { get; set; }
public AgentTask()
{
}
public AgentTask(string id, string name, string? description = null)
{
Id = id;
Name = name;
Description = description;
}
}
public class AgentTaskMetaData
{
public string Name { get; set; }
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public bool Enabled { get; set; }
public string? DirectAgentId { get; set; }
public string Content { get; set; } = string.Empty;
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
public string AgentId { get; set; } = string.Empty;
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
public Agent Agent { get; set; } = new();
/// <summary>
/// Agent task status
/// </summary>
public string Status { get; set; } = TaskStatus.New;
public DateTime? LastExecutedDateTime { get; set; }
public DateTime? NextExecutionDateTime { get; set; }
public DateTime CreatedDateTime { get; set; }
public DateTime UpdatedDateTime { get; set; }
}

View file

@ -14,6 +14,7 @@
limitations under the License.
******************************************************************************/
using BotSharp.Abstraction.Tasks;
using BotSharp.Core.Crontab.Hooks;
namespace BotSharp.Core.Crontab;
@ -33,6 +34,8 @@ public class CrontabPlugin : IBotSharpPlugin
{
services.AddScoped<IAgentUtilityHook, CrontabUtilityHook>();
services.AddScoped<ICrontabService, CrontabService>();
services.AddScoped<ITaskFeeder, CrontabService>();
services.AddHostedService<CrontabWatcher>();
services.AddHostedService<CrontabEventSubscription>();
}

View file

@ -15,8 +15,14 @@
******************************************************************************/
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Tasks;
using BotSharp.Abstraction.Tasks.Models;
using BotSharp.Abstraction.Utilities;
using BotSharp.Core.Infrastructures;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.Extensions.Logging;
using System.Text.RegularExpressions;
namespace BotSharp.Core.Crontab.Services;
@ -24,7 +30,7 @@ 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.
/// </summary>
public class CrontabService : ICrontabService
public class CrontabService : ICrontabService, ITaskFeeder
{
private readonly IServiceProvider _services;
private ILogger _logger;
@ -52,6 +58,57 @@ public class CrontabService : ICrontabService
return fixedCrantabItems;
}
public async Task<List<AgentTask>> GetTasks()
{
var agentService = _services.GetRequiredService<IAgentService>();
var tasks = new List<AgentTask>();
var cronsources = _services.GetServices<ICrontabSource>();
foreach (var source in cronsources)
{
var cron = source.GetCrontabItem();
// Get all agent subscribed to this cron
var agents = await agentService.GetAgents(new AgentFilter
{
Pager = new Pagination
{
Size = 1000
}
});
var preFilteredAgents = agents.Items.Where(x =>
x.Rules.Exists(r => r.TriggerName == cron.Title)).ToList();
tasks.AddRange(preFilteredAgents.Select(x => new AgentTask
{
Id = Guid.Empty.ToString(),
AgentId = x.Id,
Agent = new BotSharp.Abstraction.Agents.Models.Agent
{
Name = x.Name,
Description = x.Description
},
Name = FormatCrontabName(cron.Title, x.Name),
Content = $"Trigger: {cron.Title}\r\nAgent: {x.Name}\r\nCron expression: {cron.Cron}",
Status = TaskStatus.Scheduled,
Enabled = !x.Disabled,
Description = cron.Description,
LastExecutedDateTime = cron.LastExecutionTime
}));
}
return tasks;
}
private string FormatCrontabName(string trigger, string agent)
{
trigger = trigger.Replace("RuleTrigger", string.Empty);
trigger = Regex.Replace(trigger, "(?<!^)([A-Z])", " $1");
agent = agent.Replace("Operator", string.Empty);
return $"{trigger}";
}
public async Task ScheduledTimeArrived(CrontabItem item)
{
_logger.LogDebug($"ScheduledTimeArrived {item}");

View file

@ -183,7 +183,6 @@ public partial class AgentService
Name = parsedTask.Name,
Description = parsedTask.Description,
Enabled = parsedTask.Enabled,
DirectAgentId = parsedTask.DirectAgentId,
Content = parsedTask.Content,
AgentId = agentId,
CreatedDateTime = parsedTask.CreatedDateTime,

View file

@ -119,12 +119,11 @@ public partial class FileRepository
var fileName = $"{Guid.NewGuid()}.liquid";
var taskFile = Path.Combine(taskDir, fileName);
var metaData = new AgentTaskMetaData
var metaData = new AgentTask
{
Name = task.Name,
Description = task.Description,
Enabled = task.Enabled,
DirectAgentId = task.DirectAgentId,
CreatedDateTime = DateTime.UtcNow,
UpdatedDateTime = DateTime.UtcNow
};
@ -154,12 +153,11 @@ public partial class FileRepository
var parsedTask = ParseAgentTask(taskFile);
if (parsedTask == null) return;
var metaData = new AgentTaskMetaData
var metaData = new AgentTask
{
Name = parsedTask.Name,
Description = parsedTask.Description,
Enabled = parsedTask.Enabled,
DirectAgentId = parsedTask.DirectAgentId,
CreatedDateTime = parsedTask.CreatedDateTime,
UpdatedDateTime = DateTime.UtcNow
};
@ -176,9 +174,6 @@ public partial class FileRepository
case AgentTaskField.Enabled:
metaData.Enabled = task.Enabled;
break;
case AgentTaskField.DirectAgentId:
metaData.DirectAgentId = task.DirectAgentId;
break;
case AgentTaskField.Content:
content = task.Content;
break;
@ -186,7 +181,6 @@ public partial class FileRepository
metaData.Name = task.Name;
metaData.Description = task.Description;
metaData.Enabled = task.Enabled;
metaData.DirectAgentId = task.DirectAgentId;
content = task.Content;
break;
}
@ -235,7 +229,7 @@ public partial class FileRepository
return taskFile;
}
private string BuildAgentTaskFileContent(AgentTaskMetaData metaData, string taskContent)
private string BuildAgentTaskFileContent(AgentTask metaData, string taskContent)
{
return $"{AGENT_TASK_PREFIX}\n{JsonSerializer.Serialize(metaData, _options)}\n{AGENT_TASK_SUFFIX}\n\n{taskContent}";
}

View file

@ -1,5 +1,3 @@
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Tasks;
using BotSharp.Abstraction.Tasks.Models;
@ -20,9 +18,23 @@ public class AgentTaskService : IAgentTaskService
/// <returns></returns>
public async Task<PagedItems<AgentTask>> GetTasks(AgentTaskFilter filter)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var pagedTasks = db.GetAgentTasks(filter);
return await Task.FromResult(pagedTasks);
if (filter.Status == TaskStatus.Scheduled)
{
var taskFeeders = _services.GetServices<ITaskFeeder>();
var items = taskFeeders.SelectMany(x => x.GetTasks().Result);
return new PagedItems<AgentTask>
{
Items = items,
Count = items.Count()
};
}
else
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var pagedTasks = db.GetAgentTasks(filter);
return await Task.FromResult(pagedTasks);
}
}
/// <summary>

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(TargetFramework)</TargetFramework>
@ -47,7 +47,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BotSharp.Core.Rules\BotSharp.Core.Rules.csproj" />
<ProjectReference Include="..\BotSharp.Core\BotSharp.Core.csproj" />
</ItemGroup>

View file

@ -38,10 +38,11 @@ public class AgentTaskController : ControllerBase
[HttpGet("/agent/tasks")]
public async Task<PagedItems<AgentTaskViewModel>> GetAgentTasks([FromQuery] AgentTaskFilter filter)
{
filter.Status = TaskStatus.Scheduled;
var tasks = await _agentTaskService.GetTasks(filter);
return new PagedItems<AgentTaskViewModel>
{
Items = tasks.Items.Select(x => AgentTaskViewModel.From(x)),
Items = tasks.Items.Select(AgentTaskViewModel.From),
Count = tasks.Count
};
}

View file

@ -1,10 +1,4 @@
using BotSharp.Abstraction.Options;
using BotSharp.Abstraction.Users.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BotSharp.OpenAPI.Controllers;

View file

@ -1,6 +1,5 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Rules;
using BotSharp.Core.Rules.Triggers;
namespace BotSharp.OpenAPI.Controllers;

View file

@ -8,7 +8,6 @@ public class AgentTaskCreateModel
public string? Description { get; set; }
public string Content { get; set; }
public bool Enabled { get; set; }
public string? DirectAgentId { get; set; }
public AgentTask ToAgentTask()
{
@ -17,8 +16,7 @@ public class AgentTaskCreateModel
Name = Name,
Description = Description,
Content = Content,
Enabled = Enabled,
DirectAgentId = DirectAgentId
Enabled = Enabled
};
}
}

View file

@ -8,7 +8,6 @@ public class AgentTaskUpdateModel
public string? Description { get; set; }
public string? Content { get; set; }
public bool Enabled { get; set; }
public string? DirectAgentId { get; set; }
public AgentTask ToAgentTask()
{
@ -17,8 +16,7 @@ public class AgentTaskUpdateModel
Name = Name,
Description = Description,
Content = Content,
Enabled = Enabled,
DirectAgentId = DirectAgentId
Enabled = Enabled
};
}
}

View file

@ -5,21 +5,22 @@ namespace BotSharp.OpenAPI.ViewModels.Agents;
public class AgentTaskViewModel
{
public string Id { get; set; }
public string Name { get; set; }
public string Id { get; set; } = null!;
public string Name { get; set; } = null!;
public string? Description { get; set; }
public string Content { get; set; }
public string Content { get; set; } = null!;
public bool Enabled { get; set; }
public string Status { get; set; } = null!;
[JsonPropertyName("created_datetime")]
public DateTime CreatedDateTime { get; set; }
[JsonPropertyName("updated_datetime")]
public DateTime UpdatedDateTime { get; set; }
[JsonPropertyName("agent_id")]
public string AgentId { get; set; }
public string AgentId { get; set; } = null!;
[JsonPropertyName("agent_name")]
public string AgentName { get; set; }
[JsonPropertyName("direct_agent_id")]
public string? DirectAgentId { get; set; }
public string AgentName { get; set; } = null!;
public static AgentTaskViewModel From(AgentTask task)
{
@ -32,7 +33,7 @@ public class AgentTaskViewModel
Enabled = task.Enabled,
AgentId = task.AgentId,
AgentName = task.Agent?.Name,
DirectAgentId = task?.DirectAgentId,
Status = task.Status,
CreatedDateTime = task.CreatedDateTime,
UpdatedDateTime = task.UpdatedDateTime
};

View file

@ -42,7 +42,6 @@ public partial class MongoRepository
Description = x.Description,
Enabled = x.Enabled,
AgentId = x.AgentId,
DirectAgentId = x.DirectAgentId,
Content = x.Content,
CreatedDateTime = x.CreatedTime,
UpdatedDateTime = x.UpdatedTime,
@ -73,7 +72,6 @@ public partial class MongoRepository
Description = taskDoc.Description,
Enabled = taskDoc.Enabled,
AgentId = taskDoc.AgentId,
DirectAgentId = taskDoc.DirectAgentId,
Content = taskDoc.Content,
CreatedDateTime = taskDoc.CreatedTime,
UpdatedDateTime = taskDoc.UpdatedTime,
@ -92,7 +90,6 @@ public partial class MongoRepository
Description = task.Description,
Enabled = task.Enabled,
AgentId = task.AgentId,
DirectAgentId = task.DirectAgentId,
Content = task.Content,
CreatedTime = DateTime.UtcNow,
UpdatedTime = DateTime.UtcNow
@ -112,7 +109,6 @@ public partial class MongoRepository
Description = x.Description,
Enabled = x.Enabled,
AgentId = x.AgentId,
DirectAgentId = x.DirectAgentId,
Content = x.Content,
CreatedTime = x.CreatedDateTime,
UpdatedTime = x.UpdatedDateTime
@ -143,15 +139,11 @@ public partial class MongoRepository
case AgentTaskField.Content:
taskDoc.Content = task.Content;
break;
case AgentTaskField.DirectAgentId:
taskDoc.DirectAgentId = task.DirectAgentId;
break;
case AgentTaskField.All:
taskDoc.Name = task.Name;
taskDoc.Description = task.Description;
taskDoc.Enabled = task.Enabled;
taskDoc.Content = task.Content;
taskDoc.DirectAgentId = task.DirectAgentId;
break;
}