diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebDriverHook.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebDriverHook.cs index ce5d03b6..30a58bb4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebDriverHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/IWebDriverHook.cs @@ -4,5 +4,6 @@ namespace BotSharp.Abstraction.Browsing; public interface IWebDriverHook { - Task> GetUploadFiles(MessageInfo message); + Task> GetUploadFiles(MessageInfo message) => Task.FromResult(new List()); + Task OnLocateElement(MessageInfo message, string content) => Task.CompletedTask; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Models/MessageState.cs b/src/Infrastructure/BotSharp.Abstraction/Models/MessageState.cs index a8709810..074be848 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Models/MessageState.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Models/MessageState.cs @@ -8,20 +8,24 @@ public class MessageState [JsonPropertyName("active_rounds")] public int ActiveRounds { get; set; } = -1; + [JsonPropertyName("global")] + public bool Global { get; set; } + public MessageState() { } - public MessageState(string key, object value, int activeRounds = -1) + public MessageState(string key, object value, int activeRounds = -1, bool isGlobal = false) { Key = key; Value = value; ActiveRounds = activeRounds; + Global = isGlobal; } public override string ToString() { - return $"Key: {Key} => Value: {Value}, ActiveRounds: {ActiveRounds}"; + return $"Key: {Key} => Value: {Value}, ActiveRounds: {ActiveRounds}, Global: {Global}"; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs index 9ba98c5d..b94876c3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs @@ -10,6 +10,7 @@ public class ConversationFilter public string? Title { get; set; } public string? TitleAlias { get; set; } public string? AgentId { get; set; } + public List? AgentIds { get; set; } public string? Status { get; set; } public string? Channel { get; set; } public string? ChannelId { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs index a4858c5c..f221cab2 100644 --- a/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs @@ -7,6 +7,15 @@ public class SideCarOptions public static SideCarOptions Empty() { - return new SideCarOptions(); + return new(); + } + + public static SideCarOptions InheritStates(IEnumerable? targetStates = null) + { + return new() + { + IsInheritStates = true, + InheritStateKeys = targetStates + }; } } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 6eaeb167..676ea362 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -178,7 +178,7 @@ public partial class ConversationService : IConversationService { _conversationId = conversationId; _state.Load(_conversationId, isReadOnly); - states.ForEach(x => _state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, source: StateSource.External)); + states.ForEach(x => _state.SetState(x.Key, x.Value, activeRounds: x.ActiveRounds, isNeedVersion: !x.Global, source: StateSource.External)); } public async Task GetConversationRecordOrCreateNew(string agentId) diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index f7e70ca7..c5947708 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -475,7 +475,7 @@ namespace BotSharp.Core.Repository if (!filter.AgentNames.IsNullOrEmpty()) { - query = query.Where(x => filter.AgentNames.Contains(x.Name)); + query = query.Where(x => filter.AgentNames.Contains(x.Name, StringComparer.OrdinalIgnoreCase)); } if (!string.IsNullOrEmpty(filter.SimilarName)) diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 3d03e816..f772acb2 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -396,6 +396,12 @@ public partial class FileRepository Directory.CreateDirectory(dir); } + if (filter?.AgentId != null) + { + filter.AgentIds ??= []; + filter.AgentIds.Add(filter.AgentId); + } + var totalDirs = Directory.GetDirectories(dir); foreach (var d in totalDirs) { @@ -419,9 +425,9 @@ public partial class FileRepository { matched = matched && record.TitleAlias.Contains(filter.TitleAlias); } - if (filter?.AgentId != null) + if (filter?.AgentIds != null && filter.AgentIds.Any()) { - matched = matched && record.AgentId == filter.AgentId; + matched = matched && filter.AgentIds.Contains(record.AgentId); } if (filter?.Status != null) { diff --git a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs index f7e5ecde..2124da16 100644 --- a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs +++ b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs @@ -4,10 +4,12 @@ using BotSharp.Abstraction.Templating; using BotSharp.Abstraction.Translation.Models; using Fluid; using Fluid.Ast; +using Fluid.Values; using System.Collections; using System.IO; using System.Reflection; using System.Text.Encodings.Web; +using System.Text.RegularExpressions; namespace BotSharp.Core.Templating; @@ -36,9 +38,11 @@ public class TemplateRender : ITemplateRender _options.MemberAccessStrategy.Register(); _options.MemberAccessStrategy.Register(); - _parser.RegisterIdentifierTag("link", (string identifier, TextWriter writer, TextEncoder encoder, TemplateContext context) => + _options.Filters.AddFilter("from_agent", FromAgentFilter); + + _parser.RegisterExpressionTag("link", (Expression expression, TextWriter writer, TextEncoder encoder, TemplateContext context) => { - return RenderIdentifierTag("link", identifier, writer, encoder, context); + return RenderTag("link", expression, writer, encoder, context, services); }); } @@ -82,20 +86,46 @@ public class TemplateRender : ITemplateRender #region Private methods - private static async ValueTask RenderIdentifierTag(string tag, string identifier, TextWriter writer, TextEncoder encoder, TemplateContext context) + private static async ValueTask RenderTag( + string tag, + Expression expression, + TextWriter writer, + TextEncoder encoder, + TemplateContext context, + IServiceProvider services) { try { - var value = await context.Model.GetValueAsync(TemplateRenderConstant.RENDER_AGENT, context); - var agent = value?.ToObjectValue() as Agent; - var found = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo(identifier)); - var key = $"{agent?.Id} | {tag} | {identifier}"; + var value = await expression.EvaluateAsync(context); + var expStr = value?.ToStringValue() ?? string.Empty; - if (found == null || (context.AmbientValues.TryGetValue(key, out var visited) && (bool)visited)) + value = await context.Model.GetValueAsync(TemplateRenderConstant.RENDER_AGENT, context); + var agent = value?.ToObjectValue() as Agent; + + var splited = Regex.Split(expStr, @"\s*from_agent\s*", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Trim()) + .ToArray(); + + var templateName = splited.ElementAtOrDefault(0); + var agentName = splited.ElementAtOrDefault(1); + + if (splited.Length > 1 && !agentName.IsEqualTo(agent?.Name)) + { + using var scope = services.CreateScope(); + var agentService = scope.ServiceProvider.GetRequiredService(); + var result = await agentService.GetAgents(new() { SimilarName = agentName }); + agent = result?.Items?.FirstOrDefault(); + } + + var template = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo(templateName)); + var key = $"{tag} | {agent?.Id} | {templateName}"; + + if (template == null || (context.AmbientValues.TryGetValue(key, out var visited) && (bool)visited)) { writer.Write(string.Empty); } - else if (_parser.TryParse(found.Content, out var t, out _)) + else if (_parser.TryParse(template.Content, out var t, out _)) { context.AmbientValues[key] = true; var rendered = t.Render(context); @@ -115,6 +145,16 @@ public class TemplateRender : ITemplateRender return Completion.Normal; } + private static ValueTask FromAgentFilter( + FluidValue input, + FilterArguments arguments, + TemplateContext context) + { + var inputStr = input?.ToStringValue() ?? string.Empty; + var fromAgent = arguments.At(0).ToStringValue(); + return new StringValue($"{inputStr} from_agent {fromAgent}"); + } + private static bool IsStringType(Type type) { return type == typeof(string); diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid index 775de66c..1ada1850 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid +++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid @@ -5,7 +5,8 @@ Follow these steps to handle user request: 2. Determine which agent is suitable to handle this conversation. Try to minimize the routing of human service. 3. Extract and populate agent required arguments, think carefully, leave it as blank object if user didn't provide the specific arguments. 4. You must include all required args for the selected agent, but you must not make up any parameters when there is no exact value provided, those parameters must set value as null if not declared. -5. If user is greeting, you can call function response_to_user with a greeting message. +5. Call function route_to_agent if user have specific requests and available agent to proceed. Do not ask user to provide any required args by yourself. The requested agent will handle and fill the required args internally. +6. If user is greeting or do not have specific request, then you can call function response_to_user with a greeting message. {% if routing_requirements and routing_requirements != empty %} [REQUIREMENTS] diff --git a/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs b/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs index 4752f6d6..02cafbd0 100644 --- a/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs +++ b/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs @@ -1,9 +1,6 @@ using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Conversations.Enums; using BotSharp.Abstraction.Repositories.Filters; -using BotSharp.Abstraction.Statistics.Enums; -using BotSharp.Abstraction.Statistics.Models; -using BotSharp.Abstraction.Statistics.Services; using BotSharp.Abstraction.Users; namespace BotSharp.Logger.Hooks; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs index e8c87c2c..de67fcab 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs @@ -30,7 +30,8 @@ public class InstructModeController : ControllerBase .SetState("model_id", input.ModelId, source: StateSource.External) .SetState("instruction", input.Instruction, source: StateSource.External) .SetState("input_text", input.Text, source: StateSource.External) - .SetState("template_name", input.Template, source: StateSource.External); + .SetState("template_name", input.Template, source: StateSource.External) + .SetState("channel", input.Channel, source: StateSource.External); var instructor = _services.GetRequiredService(); var result = await instructor.Execute(agentId, diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs index 7079c642..138d5084 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs @@ -82,8 +82,8 @@ public class ChatStreamMiddleware var (eventType, data) = MapEvents(conn, receivedText); if (eventType == "start") { - var states = InitStates(data); - await ConnectToModel(hub, webSocket, states); + var request = InitRequest(data); + await ConnectToModel(hub, webSocket, request?.States); } else if (eventType == "media") { @@ -157,16 +157,15 @@ public class ChatStreamMiddleware }); } - private List InitStates(string data) + private ChatStreamRequest? InitRequest(string data) { try { - var states = JsonSerializer.Deserialize>(data, BotSharpOptions.defaultJsonOptions); - return states ?? []; + return JsonSerializer.Deserialize(data, BotSharpOptions.defaultJsonOptions); } catch { - return []; + return null; } } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Models/Stream/ChatStreamRequest.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Models/Stream/ChatStreamRequest.cs new file mode 100644 index 00000000..1a72d7dc --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Models/Stream/ChatStreamRequest.cs @@ -0,0 +1,10 @@ +using BotSharp.Abstraction.Models; +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.ChatHub.Models.Stream; + +public class ChatStreamRequest +{ + [JsonPropertyName("states")] + public List States { get; set; } = []; +} diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index edcf3a5f..d0da6fbf 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -346,6 +346,12 @@ public partial class MongoRepository var convBuilder = Builders.Filter; var convFilters = new List>() { convBuilder.Empty }; + if (filter?.AgentId != null) + { + filter.AgentIds ??= []; + filter.AgentIds.Add(filter.AgentId); + } + // Filter conversations if (!string.IsNullOrEmpty(filter?.Id)) { @@ -359,9 +365,9 @@ public partial class MongoRepository { convFilters.Add(convBuilder.Regex(x => x.Title, new BsonRegularExpression(filter.TitleAlias, "i"))); } - if (!string.IsNullOrEmpty(filter?.AgentId)) + if (filter?.AgentIds != null && filter.AgentIds.Any()) { - convFilters.Add(convBuilder.Eq(x => x.AgentId, filter.AgentId)); + convFilters.Add(convBuilder.In(x => x.AgentId, filter.AgentIds)); } if (!string.IsNullOrEmpty(filter?.Status)) { diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs index f571fa6c..c2f0682b 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/ExecuteQueryFn.cs @@ -33,7 +33,7 @@ public class ExecuteQueryFn : IFunctionCallback var results = dbType.ToLower() switch { "mysql" => RunQueryInMySql(args.SqlStatements), - "sqlserver" => RunQueryInSqlServer(args.SqlStatements), + "sqlserver" or "mssql" => RunQueryInSqlServer(args.SqlStatements), "redshift" => RunQueryInRedshift(args.SqlStatements), _ => throw new NotImplementedException($"Database type {dbType} is not supported.") }; diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/GetTableDefinitionFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/GetTableDefinitionFn.cs index 283620c9..25ef526a 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/GetTableDefinitionFn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/GetTableDefinitionFn.cs @@ -37,7 +37,7 @@ public class GetTableDefinitionFn : IFunctionCallback var tableDdls = dbType switch { "mysql" => GetDdlFromMySql(tables), - "sqlserver" => GetDdlFromSqlServer(tables), + "sqlserver" or "mssql" => GetDdlFromSqlServer(tables), "redshift" => GetDdlFromRedshift(tables), _ => throw new NotImplementedException($"Database type {dbType} is not supported.") }; diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs index eb9253e4..3e693f80 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs @@ -32,7 +32,7 @@ public class SqlSelect : IFunctionCallback var result = dbType switch { "mysql" => RunQueryInMySql(args), - "sqlserver" => RunQueryInSqlServer(args), + "sqlserver" or "mssql" => RunQueryInSqlServer(args), "redshift" => RunQueryInRedshift(args), _ => throw new NotImplementedException($"Database type {dbType} is not supported.") }; diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs index 89b38f93..1489c4a8 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlValidateFn.cs @@ -34,7 +34,7 @@ public class SqlValidateFn : IFunctionCallback var validateSql = dbType.ToLower() switch { "mysql" => $"EXPLAIN\r\n{sql.Replace("SET ", "-- SET ", StringComparison.InvariantCultureIgnoreCase).Replace(";", "; EXPLAIN ").TrimEnd("EXPLAIN ".ToCharArray())}", - "sqlserver" => $"SET PARSEONLY ON;\r\n{sql}\r\nSET PARSEONLY OFF;", + "sqlserver" or "mssql" => $"SET PARSEONLY ON;\r\n{sql}\r\nSET PARSEONLY OFF;", "redshift" => $"explain\r\n{sql}", _ => throw new NotImplementedException($"Database type {dbType} is not supported.") }; diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/UtilFunctions/GetTableDefinitionFn.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/UtilFunctions/GetTableDefinitionFn.cs index 20f35572..43005c68 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/UtilFunctions/GetTableDefinitionFn.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/UtilFunctions/GetTableDefinitionFn.cs @@ -31,7 +31,7 @@ public class GetTableDefinitionFn : IFunctionCallback var tableDdls = dbType switch { "mysql" => GetDdlFromMySql(tables), - "sqlserver" => GetDdlFromSqlServer(tables), + "sqlserver" or "mssql" => GetDdlFromSqlServer(tables), "redshift" => GetDdlFromRedshift(tables,schema), _ => throw new NotImplementedException($"Database type {dbType} is not supported.") }; diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/UtilFunctions/SqlSelect.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/UtilFunctions/SqlSelect.cs index 1ae27ac7..72bef238 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/UtilFunctions/SqlSelect.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/UtilFunctions/SqlSelect.cs @@ -30,7 +30,7 @@ public class SqlSelect : IFunctionCallback var result = dbType switch { "mysql" => RunQueryInMySql(args), - "sqlserver" => RunQueryInSqlServer(args), + "sqlserver" or "mssql" => RunQueryInSqlServer(args), "redshift" => RunQueryInRedshift(args), _ => throw new NotImplementedException($"Database type {dbType} is not supported.") }; diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs index de58dca6..b5dbfb2e 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs @@ -166,16 +166,16 @@ public class TwilioInboundController : TwilioController var states = new List { - new("channel", ConversationChannel.Phone), - new("calling_phone", request.From), - new("phone_direction", request.Direction), - new("twilio_call_sid", request.CallSid), + new("channel", ConversationChannel.Phone, isGlobal: true), + new("calling_phone", request.From, isGlobal: true), + new("phone_direction", request.Direction, isGlobal: true), + new("twilio_call_sid", request.CallSid, isGlobal: true), }; if (request.Direction == "inbound") { - states.Add(new MessageState("calling_phone_from", request.From)); - states.Add(new MessageState("calling_phone_to", request.To)); + states.Add(new MessageState("calling_phone_from", request.From, isGlobal: true)); + states.Add(new MessageState("calling_phone_to", request.To, isGlobal: true)); } var requestStates = ParseStates(request.States); diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/OutboundPhoneCallFn.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/OutboundPhoneCallFn.cs index 8d34dd22..3c5532bd 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/OutboundPhoneCallFn.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/OutboundPhoneCallFn.cs @@ -9,6 +9,7 @@ using BotSharp.Plugin.Twilio.Interfaces; using BotSharp.Plugin.Twilio.Models; using BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts; using Twilio.Rest.Api.V2010.Account; +using Twilio.TwiML.Messaging; using Twilio.Types; using Conversation = BotSharp.Abstraction.Conversations.Models.Conversation; using Task = System.Threading.Tasks.Task; @@ -176,7 +177,7 @@ public class OutboundPhoneCallFn : IFunctionCallback }); var utcNow = DateTime.UtcNow; - var excludStates = new List + var excludeStates = new List { "provider", "model", @@ -185,22 +186,34 @@ public class OutboundPhoneCallFn : IFunctionCallback "llm_total_cost" }; - var curStates = state.GetStates().Select(x => new MessageState(x.Key, x.Value)).ToList(); + var curConvStates = state.GetStates().Select(x => new MessageState(x.Key, x.Value)).ToList(); var subConvStates = new List { - new(StateConst.ORIGIN_CONVERSATION_ID, originConversationId), - new("channel", "phone"), - new("phone_from", call.From), - new("phone_direction", call.Direction), - new("phone_number", call.To), - new("twilio_call_sid", call.Sid) + new(StateConst.ORIGIN_CONVERSATION_ID, originConversationId, isGlobal: true), + new("channel", "phone", isGlobal: true), + new("phone_from", call.From, isGlobal: true), + new("phone_direction", call.Direction, isGlobal: true), + new("phone_number", call.To, isGlobal: true), + new("twilio_call_sid", call.Sid, isGlobal: true) }; var subStateKeys = subConvStates.Select(x => x.Key).ToList(); - var included = curStates.Where(x => !subStateKeys.Contains(x.Key) && !excludStates.Contains(x.Key)); - var newStates = subConvStates.Concat(included).Select(x => new StateKeyValue + var included = curConvStates.Where(x => !subStateKeys.Contains(x.Key) && !excludeStates.Contains(x.Key)); + + var mappedCurConvStates = MapStates(included, messageId, utcNow); + var mappedSubConvStates = MapStates(subConvStates, messageId, utcNow); + var allStates = mappedCurConvStates.Concat(mappedSubConvStates).ToList(); + + db.UpdateConversationStates(newConversationId, allStates); + } + + private IEnumerable MapStates(IEnumerable states, string messageId, DateTime updateTime) + { + if (states.IsNullOrEmpty()) return []; + + return states.Select(x => new StateKeyValue { Key = x.Key, - Versioning = true, + Versioning = !x.Global, Values = [ new StateValue { @@ -209,11 +222,9 @@ public class OutboundPhoneCallFn : IFunctionCallback Active = true, ActiveRounds = x.ActiveRounds, Source = StateSource.Application, - UpdateTime = utcNow + UpdateTime = updateTime } ] }).ToList(); - - db.UpdateConversationStates(newConversationId, newStates); } } diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs index e70d7f27..b4b2eb2f 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightDriver/PlaywrightWebDriver.LocateElement.cs @@ -114,6 +114,11 @@ public partial class PlaywrightWebDriver // fix if html has & result.Body = HttpUtility.HtmlDecode(html); result.IsSuccess = true; + var hooks = _services.GetServices(); + foreach (var hook in hooks) + { + await hook.OnLocateElement(message, result.Body); + } } else if (count > 1) { diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebCloseBrowserFn.cs b/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebCloseBrowserFn.cs index ff898232..6c7bfefa 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebCloseBrowserFn.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebCloseBrowserFn.cs @@ -28,7 +28,7 @@ public class UtilWebCloseBrowserFn : IFunctionCallback ContextId = webDriverService.GetMessageContext(message) }; - await browser.CloseBrowser(message.CurrentAgentId); + await browser.CloseBrowser(msg.ContextId); message.Content = $"Browser closed."; diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebLocateElementFn.cs b/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebLocateElementFn.cs index d30048c8..e2e3d34f 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebLocateElementFn.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/UtilFunctions/UtilWebLocateElementFn.cs @@ -30,6 +30,7 @@ public class UtilWebLocateElementFn : IFunctionCallback MessageId = message.MessageId, ContextId = webDriverService.GetMessageContext(message) }; + browser.SetServiceProvider(_services); var result = await browser.LocateElement(msg, locatorArgs); message.Content = $"Locating element {(result.IsSuccess ? "success" : "failed")}. ";