diff --git a/BotSharp.sln b/BotSharp.sln
index d281450e..a1c1ddb9 100644
--- a/BotSharp.sln
+++ b/BotSharp.sln
@@ -65,6 +65,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.GoogleAI",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.SemanticKernel", "src\Plugins\BotSharp.Plugin.SemanticKernel\BotSharp.Plugin.SemanticKernel.csproj", "{73EE2CD0-3B27-4F02-A67B-762CBDD740D0}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.MicrosoftExtensionsAI", "src\Plugins\BotSharp.Plugin.MicrosoftExtensionsAI\BotSharp.Plugin.MicrosoftExtensionsAI.csproj", "{72CA059E-6AAA-406C-A1EB-A2243E652F5F}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.SemanticKernel.UnitTests", "tests\BotSharp.Plugin.SemanticKernel.UnitTests\BotSharp.Plugin.SemanticKernel.UnitTests.csproj", "{BC57D428-A1A4-4D38-A2D0-AC6CA943F247}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.Twilio", "src\Plugins\BotSharp.Plugin.Twilio\BotSharp.Plugin.Twilio.csproj", "{E627F1E3-BE03-443A-83A2-86A855A278EB}"
@@ -283,6 +285,14 @@ Global
{73EE2CD0-3B27-4F02-A67B-762CBDD740D0}.Release|Any CPU.Build.0 = Release|Any CPU
{73EE2CD0-3B27-4F02-A67B-762CBDD740D0}.Release|x64.ActiveCfg = Release|Any CPU
{73EE2CD0-3B27-4F02-A67B-762CBDD740D0}.Release|x64.Build.0 = Release|Any CPU
+ {72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Debug|x64.Build.0 = Debug|Any CPU
+ {72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Release|x64.ActiveCfg = Release|Any CPU
+ {72CA059E-6AAA-406C-A1EB-A2243E652F5F}.Release|x64.Build.0 = Release|Any CPU
{BC57D428-A1A4-4D38-A2D0-AC6CA943F247}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BC57D428-A1A4-4D38-A2D0-AC6CA943F247}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC57D428-A1A4-4D38-A2D0-AC6CA943F247}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -491,6 +501,7 @@ Global
{DB3DE37B-1208-4ED3-9615-A52AD0AAD69C} = {5CD330E1-9E5A-4112-8346-6E31CA98EF78}
{8BC29F8A-78D6-422C-B522-10687ADC38ED} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
{73EE2CD0-3B27-4F02-A67B-762CBDD740D0} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
+ {72CA059E-6AAA-406C-A1EB-A2243E652F5F} = {D5293208-2BEF-42FC-A64C-5954F61720BA}
{BC57D428-A1A4-4D38-A2D0-AC6CA943F247} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC}
{E627F1E3-BE03-443A-83A2-86A855A278EB} = {64264688-0F5C-4AB0-8F2B-B59B717CCE00}
{F06B22CB-B143-4680-8FFF-35B9E50E6C47} = {51AFE054-AE99-497D-A593-69BAEFB5106F}
diff --git a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
index e94cf839..474dcf56 100644
--- a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
+++ b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj
@@ -32,6 +32,7 @@
+
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs
index bd34e936..1867a687 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/BrowserActionResult.cs
@@ -6,8 +6,8 @@ public class BrowserActionResult
public bool IsSuccess { get; set; }
public string? Message { get; set; }
public string? StackTrace { get; set; }
- public string Selector { get; set; }
- public string Body { get; set; }
+ public string? Selector { get; set; }
+ public string? Body { get; set; }
public bool IsHighlighted { get; set; }
public override string ToString()
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs
index a63fffc8..2935496c 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementActionArgs.cs
@@ -1,7 +1,9 @@
using BotSharp.Abstraction.Browsing.Enums;
+using System.Diagnostics;
namespace BotSharp.Abstraction.Browsing.Models;
+[DebuggerStepThrough]
public class ElementActionArgs
{
public BroswerActionEnum Action { get; set; }
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs
index 53d72520..b50f2366 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/ElementLocatingArgs.cs
@@ -1,5 +1,8 @@
+using System.Diagnostics;
+
namespace BotSharp.Abstraction.Browsing.Models;
+[DebuggerStepThrough]
public class ElementLocatingArgs
{
[JsonPropertyName("match_rule")]
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/MessageInfo.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/MessageInfo.cs
index afb42dac..92c9d42d 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/MessageInfo.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/MessageInfo.cs
@@ -1,7 +1,9 @@
using BotSharp.Abstraction.Infrastructures;
+using System.Diagnostics;
namespace BotSharp.Abstraction.Browsing.Models;
+[DebuggerStepThrough]
public class MessageInfo : ICacheKey
{
public string AgentId { get; set; } = null!;
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs
index aa4154ff..698939e5 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/PageActionArgs.cs
@@ -1,13 +1,15 @@
using BotSharp.Abstraction.Browsing.Enums;
+using System.Diagnostics;
namespace BotSharp.Abstraction.Browsing.Models;
+[DebuggerStepThrough]
public class PageActionArgs
{
public BroswerActionEnum Action { get; set; }
public string? Content { get; set; }
- public string? Direction { get; set; }
+ public string Direction { get; set; } = "down";
public string Url { get; set; } = null!;
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseData.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseData.cs
index 1d03b0b7..14932118 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseData.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseData.cs
@@ -7,4 +7,9 @@ public class WebPageResponseData
public string ResponseData { get; set; } = null!;
public bool ResponseInMemory { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+
+ public override string ToString()
+ {
+ return $"{Url} {ResponseData.Length}";
+ }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseFilter.cs b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseFilter.cs
index cd97a01d..00b78cc4 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseFilter.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Browsing/Models/WebPageResponseFilter.cs
@@ -1,7 +1,22 @@
+using System.Diagnostics;
+
namespace BotSharp.Abstraction.Browsing.Models;
+[DebuggerStepThrough]
public class WebPageResponseFilter
{
public string Url { get; set; } = null!;
public string[]? QueryParameters { get; set; }
+
+ public string[]? PostData { get; set; }
+
+ ///
+ /// contains, starts, ends, equals
+ ///
+ public string UrlMatchPattern { get; set; } = "contains";
+
+ ///
+ /// Handle Content-Type: text/x-component
+ ///
+ public Func? PartSearch { get; set; } = null;
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs
index dd30f491..c8b29ecd 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Infrastructures/SharpCacheAttribute.cs
@@ -71,7 +71,7 @@ public class SharpCacheAttribute : MoAttribute
private string GetCacheKey(SharpCacheSettings settings, MethodContext context)
{
- var key = settings.Prefix + "-" + context.Method.Name;
+ var key = settings.Prefix + ":" + context.Method.Name;
foreach (var arg in context.Arguments)
{
if (arg is null)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeHook.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeHook.cs
index 6234611c..1167d2e1 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/IKnowledgeHook.cs
@@ -5,7 +5,7 @@ public interface IKnowledgeHook
Task> CollectChunkedKnowledge()
=> Task.FromResult(new List());
- Task> GetRelevantKnowledges(RoleDialogModel message, string text)
+ Task> GetDomainKnowledges(RoleDialogModel message, string text)
=> Task.FromResult(new List());
Task> GetGlobalKnowledges(RoleDialogModel message)
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
index f21850e3..4f604dc4 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs
@@ -21,7 +21,7 @@ public interface IBotSharpRepository
User? GetAffiliateUserByPhone(string phone) => throw new NotImplementedException();
User? GetUserById(string id) => throw new NotImplementedException();
List GetUserByIds(List ids) => throw new NotImplementedException();
- User? GetUserByAffiliateId(string affiliateId) => throw new NotImplementedException();
+ List GetUsersByAffiliateId(string affiliateId) => throw new NotImplementedException();
User? GetUserByUserName(string userName) => throw new NotImplementedException();
void CreateUser(User user) => throw new NotImplementedException();
void UpdateExistUser(string userId, User user) => throw new NotImplementedException();
@@ -29,7 +29,7 @@ public interface IBotSharpRepository
void UpdateUserVerificationCode(string userId, string verficationCode) => throw new NotImplementedException();
void UpdateUserPassword(string userId, string password) => throw new NotImplementedException();
void UpdateUserEmail(string userId, string email) => throw new NotImplementedException();
- void UpdateUserPhone(string userId, string Iphone) => throw new NotImplementedException();
+ void UpdateUserPhone(string userId, string Iphone, string regionCode) => throw new NotImplementedException();
void UpdateUserIsDisable(string userId, bool isDisable) => throw new NotImplementedException();
void UpdateUsersIsDisable(List userIds, bool isDisable) => throw new NotImplementedException();
PagedItems GetUsers(UserFilter filter) => throw new NotImplementedException();
diff --git a/src/Infrastructure/BotSharp.Abstraction/Repositories/IDatabaseHook.cs b/src/Infrastructure/BotSharp.Abstraction/Repositories/IDatabaseHook.cs
new file mode 100644
index 00000000..7484b754
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Abstraction/Repositories/IDatabaseHook.cs
@@ -0,0 +1,7 @@
+namespace BotSharp.Abstraction.Repositories;
+
+public interface IDatabaseHook
+{
+ // Get database type
+ string GetDatabaseType(RoleDialogModel message);
+}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs
index afb0e40e..36b07f11 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/IAuthenticationHook.cs
@@ -1,15 +1,60 @@
using BotSharp.Abstraction.Users.Models;
+using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
namespace BotSharp.Abstraction.Users;
public interface IAuthenticationHook
{
+ ///
+ /// Interupt the authentication process, and return the user object if the user is authenticated
+ ///
+ ///
+ ///
+ ///
Task Authenticate(string id, string password);
- void AddClaims(List claims);
- void BeforeSending(Token token);
+
+ ///
+ /// Add extra claims to user
+ ///
+ ///
+ ///
+ bool AddClaims(List claims)
+ => true;
+
+ ///
+ /// User authenticated successfully
+ ///
+ ///
+ ///
+ bool UserAuthenticated(JwtSecurityToken token)
+ => true;
+
+ ///
+ /// Bfore user updating
+ ///
+ ///
+ ///
Task UserUpdating(User user);
+
+ ///
+ /// After user created
+ ///
+ ///
+ ///
Task UserCreated(User user);
+
+ ///
+ /// Reset password
+ ///
+ ///
+ ///
Task VerificationCodeResetPassword(User user);
+
+ ///
+ /// Delete users
+ ///
+ ///
+ ///
Task DelUsers(List userIds);
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs
index 2eb1ec9d..f42b7f25 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserIdentity.cs
@@ -14,4 +14,8 @@ public interface IUserIdentity
string UserLanguage { get; }
string? Phone { get; }
string? AffiliateId { get; }
+ string? EmployeeId { get; }
+ string Type { get; }
+ string Role { get; }
+ string? RegionCode { get; }
}
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
index 734884ef..2a869d02 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/IUserService.cs
@@ -11,6 +11,7 @@ public interface IUserService
Task CreateUser(User user);
Task ActiveUser(UserActivationModel model);
Task GetAffiliateToken(string authorization);
+ Task GetAdminToken(string authorization);
Task GetToken(string authorization);
Task GetMyProfile();
Task VerifyUserNameExisting(string userName);
@@ -20,7 +21,7 @@ public interface IUserService
Task SendVerificationCodeResetPasswordLogin();
Task ResetUserPassword(User user);
Task ModifyUserEmail(string email);
- Task ModifyUserPhone(string phone);
+ Task ModifyUserPhone(string phone, string regionCode);
Task UpdatePassword(string newPassword, string verificationCode);
Task GetUserTokenExpires();
Task UpdateUsersIsDisable(List userIds, bool isDisable);
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs
index 4b309733..8d136dc5 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Models/User.cs
@@ -21,7 +21,9 @@ public class User
public string Role { get; set; } = UserRole.User;
public string? VerificationCode { get; set; }
public bool Verified { get; set; }
+ public string RegionCode { get; set; } = "CN";
public string? AffiliateId { get; set; }
+ public string? EmployeeId { get; set; }
public bool IsDisabled { get; set; }
public IEnumerable Permissions { get; set; } = [];
diff --git a/src/Infrastructure/BotSharp.Abstraction/Users/Settings/AccountSetting.cs b/src/Infrastructure/BotSharp.Abstraction/Users/Settings/AccountSetting.cs
index 17151d7e..a7f6b717 100644
--- a/src/Infrastructure/BotSharp.Abstraction/Users/Settings/AccountSetting.cs
+++ b/src/Infrastructure/BotSharp.Abstraction/Users/Settings/AccountSetting.cs
@@ -7,4 +7,5 @@ public class AccountSetting
///
public bool NewUserVerification { get; set; }
public string[] AllowMultipleDeviceLoginUserIds { get; set; } = [];
+ public bool CreateUserAutomatically { get; set; } = true;
}
diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs
index 650cdeeb..80ccacc4 100644
--- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs
+++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs
@@ -33,9 +33,9 @@ public partial class FileRepository
return Users.Where(x => ids.Contains(x.Id) || (x.ExternalId != null && ids.Contains(x.ExternalId)))?.ToList() ?? new List();
}
- public User? GetUserByAffiliateId(string affiliateId)
+ public List GetUsersByAffiliateId(string affiliateId)
{
- return Users.FirstOrDefault(x => x.AffiliateId == affiliateId);
+ return Users.Where(x => x.AffiliateId == affiliateId).ToList();
}
public User? GetUserByUserName(string userName = null)
diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs
index 14f09c0e..965be261 100644
--- a/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs
+++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserIdentity.cs
@@ -63,7 +63,7 @@ public class UserIdentity : IUserIdentity
get
{
_contextAccessor.HttpContext.Request.Headers.TryGetValue("User-Language", out var languages);
- return languages.FirstOrDefault() ?? "en-US";
+ return languages.FirstOrDefault() ?? "en-US";
}
}
@@ -72,4 +72,16 @@ public class UserIdentity : IUserIdentity
[JsonPropertyName("affiliateId")]
public string? AffiliateId => _claims?.FirstOrDefault(x => x.Type == "affiliateId")?.Value;
+
+ [JsonPropertyName("employeeId")]
+ public string? EmployeeId => _claims?.FirstOrDefault(x => x.Type == "employeeId")?.Value;
+
+ [JsonPropertyName("type")]
+ public string? Type => _claims?.FirstOrDefault(x => x.Type == "type")?.Value;
+
+ [JsonPropertyName("role")]
+ public string? Role => _claims?.FirstOrDefault(x => x.Type == ClaimTypes.Role)?.Value;
+
+ [JsonPropertyName("regionCode")]
+ public string? RegionCode => _claims?.FirstOrDefault(x => x.Type == "regionCode")?.Value;
}
diff --git a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
index 2098d71d..5bbd3c32 100644
--- a/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
+++ b/src/Infrastructure/BotSharp.Core/Users/Services/UserService.cs
@@ -91,7 +91,8 @@ public class UserService : IUserService
record.Email = user.Email?.ToLower();
if (!string.IsNullOrWhiteSpace(user.Phone))
{
- record.Phone = "+" + Regex.Match(user.Phone, @"\d+").Value;
+ //record.Phone = "+" + Regex.Match(user.Phone, @"\d+").Value;
+ record.Phone = Regex.Match(user.Phone, @"\d+").Value;
}
record.Salt = Guid.NewGuid().ToString("N");
record.Password = Utilities.HashTextMd5($"{user.Password}{record.Salt}");
@@ -144,19 +145,14 @@ public class UserService : IUserService
return true;
}
- public async Task GetAffiliateToken(string authorization)
+ public async Task GetAffiliateToken(string authorization)
{
var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization));
var (id, password) = base64.SplitAsTuple(":");
var db = _services.GetRequiredService();
var record = db.GetAffiliateUserByPhone(id);
- if (record == null)
- {
- record = db.GetUserByPhone(id);
- }
-
- var isCanLoginAffiliateRoleType = record != null && !record.IsDisabled && record.Type != UserType.Client;
- if (!isCanLoginAffiliateRoleType)
+ var isCanLogin = record != null && !record.IsDisabled && record.Type == UserType.Affiliate;
+ if (!isCanLogin)
{
return default;
}
@@ -166,6 +162,39 @@ public class UserService : IUserService
return default;
}
+ var (token, jwt) = BuildToken(record);
+
+ return await Task.FromResult(token);
+ }
+
+ public async Task GetAdminToken(string authorization)
+ {
+ var base64 = Encoding.UTF8.GetString(Convert.FromBase64String(authorization));
+ var (id, password) = base64.SplitAsTuple(":");
+ var db = _services.GetRequiredService();
+ var record = db.GetUserByPhone(id);
+ var isCanLogin = record != null && !record.IsDisabled
+ && record.Type == UserType.Internal && new List
+ {
+ UserRole.Root,UserRole.Admin
+ }.Contains(record.Role);
+ if (!isCanLogin)
+ {
+ return default;
+ }
+
+ if (Utilities.HashTextMd5($"{password}{record.Salt}") != record.Password)
+ {
+ return default;
+ }
+
+ var (token, jwt) = BuildToken(record);
+
+ return await Task.FromResult(token);
+ }
+
+ private (Token, JwtSecurityToken) BuildToken(User record)
+ {
var accessToken = GenerateJwtToken(record);
var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
var token = new Token
@@ -175,7 +204,7 @@ public class UserService : IUserService
TokenType = "Bearer",
Scope = "api"
};
- return token;
+ return (token, jwt);
}
public async Task GetToken(string authorization)
@@ -187,7 +216,7 @@ public class UserService : IUserService
var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id);
if (record == null)
{
- record = db.GetUserByUserName(id);
+ record = db.GetUserByPhone(id);
}
if (record != null && record.Type == UserType.Affiliate)
@@ -238,7 +267,8 @@ public class UserService : IUserService
ExternalId = user.ExternalId,
Password = user.Password,
Type = user.Type,
- Role = user.Role
+ Role = user.Role,
+ RegionCode = user.RegionCode
};
await CreateUser(record);
}
@@ -263,19 +293,10 @@ public class UserService : IUserService
return default;
}
- var accessToken = GenerateJwtToken(record);
- var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
- var token = new Token
- {
- AccessToken = accessToken,
- ExpireTime = jwt.Payload.Exp.Value,
- TokenType = "Bearer",
- Scope = "api"
- };
-
+ var (token, jwt) = BuildToken(record);
foreach (var hook in hooks)
{
- hook.BeforeSending(token);
+ hook.UserAuthenticated(jwt);
}
return token;
@@ -296,7 +317,9 @@ public class UserService : IUserService
new Claim("role", user.Role ?? UserRole.User),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim("phone", user.Phone ?? string.Empty),
- new Claim("affiliateId", user.AffiliateId ?? string.Empty)
+ new Claim("affiliateId", user.AffiliateId ?? string.Empty),
+ new Claim("employeeId", user.EmployeeId ?? string.Empty),
+ new Claim("regionCode", user.RegionCode ?? "CN")
};
var validators = _services.GetServices();
@@ -339,7 +362,7 @@ public class UserService : IUserService
private string GetUserTokenExpiresCacheKey(string userId)
{
- return $"user_{userId}_token_expires";
+ return $"user:{userId}_token_expires";
}
public async Task GetUserTokenExpires()
@@ -397,7 +420,7 @@ public class UserService : IUserService
var record = id.Contains("@") ? db.GetUserByEmail(id) : db.GetUserByUserName(id);
if (record == null)
{
- record = db.GetUserByUserName(id);
+ record = db.GetUserByPhone(id);
}
if (record == null)
@@ -610,19 +633,26 @@ public class UserService : IUserService
return true;
}
- public async Task ModifyUserPhone(string phone)
+ public async Task ModifyUserPhone(string phone, string regionCode)
{
+ if (string.IsNullOrWhiteSpace(regionCode))
+ {
+ throw new Exception("regionCode is required");
+ }
var curUser = await GetMyProfile();
var db = _services.GetRequiredService();
var record = db.GetUserById(curUser.Id);
var existPhone = db.GetUserByPhone(phone);
- if (record == null || existPhone != null)
+ if (record == null || (existPhone != null && existPhone.RegionCode == regionCode))
{
return false;
}
record.Phone = phone;
+ record.RegionCode = regionCode;
+ record.UserName = phone;
+ record.FirstName = phone;
var hooks = _services.GetServices();
foreach (var hook in hooks)
@@ -630,7 +660,7 @@ public class UserService : IUserService
await hook.UserUpdating(record);
}
- db.UpdateUserPhone(record.Id, record.Phone);
+ db.UpdateUserPhone(record.Id, record.Phone, regionCode);
return true;
}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs
index dbb312b0..2521fff6 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/UserController.cs
@@ -1,3 +1,4 @@
+using BotSharp.Abstraction.Users.Settings;
using BotSharp.Abstraction.Users.Enums;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
@@ -17,10 +18,13 @@ public class UserController : ControllerBase
IUserService userService,
IServiceProvider services,
IUserIdentity user)
+ private readonly AccountSetting _setting;
+ public UserController(IUserService userService, IServiceProvider services, AccountSetting setting)
{
_services = services;
_userService = userService;
_user = user;
+ _setting = setting;
}
[AllowAnonymous]
@@ -84,7 +88,7 @@ public class UserController : ControllerBase
public async Task GetMyUserProfile()
{
var user = await _userService.GetMyProfile();
- if (user == null)
+ if (user == null && _setting.CreateUserAutomatically)
{
var identiy = _services.GetRequiredService();
var accessor = _services.GetRequiredService();
@@ -97,6 +101,8 @@ public class UserController : ControllerBase
LastName = identiy.LastName,
Source = claims.First().Issuer,
ExternalId = identiy.Id,
+ RegionCode = identiy.RegionCode,
+ Phone = identiy.Phone,
});
}
return UserViewModel.FromUser(user);
@@ -160,9 +166,9 @@ public class UserController : ControllerBase
}
[HttpPost("/user/phone/modify")]
- public async Task ModifyUserPhone([FromQuery] string phone)
+ public async Task ModifyUserPhone([FromQuery] string phone, [FromQuery] string regionCode = "CN")
{
- return await _userService.ModifyUserPhone(phone);
+ return await _userService.ModifyUserPhone(phone, regionCode);
}
[HttpPost("/user/update/isdisable")]
diff --git a/src/Infrastructure/BotSharp.OpenAPI/Filters/UserSingleLoginFilter.cs b/src/Infrastructure/BotSharp.OpenAPI/Filters/UserSingleLoginFilter.cs
index e8db0801..dda71f48 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/Filters/UserSingleLoginFilter.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/Filters/UserSingleLoginFilter.cs
@@ -1,80 +1,78 @@
using BotSharp.Abstraction.Users.Settings;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Mvc.Filters;
-using Microsoft.Extensions.Configuration;
using Microsoft.Net.Http.Headers;
using System.IdentityModel.Tokens.Jwt;
-namespace BotSharp.OpenAPI.Filters
-{
- public class UserSingleLoginFilter : IAuthorizationFilter
- {
- private readonly IUserService _userService;
- private readonly IServiceProvider _services;
+namespace BotSharp.OpenAPI.Filters;
- public UserSingleLoginFilter(IUserService userService, IServiceProvider services)
+public class UserSingleLoginFilter : IAuthorizationFilter
+{
+ private readonly IUserService _userService;
+ private readonly IServiceProvider _services;
+
+ public UserSingleLoginFilter(IUserService userService, IServiceProvider services)
+ {
+ _userService = userService;
+ _services = services;
+ }
+
+ public void OnAuthorization(AuthorizationFilterContext context)
+ {
+ var isAllowAnonymous = context.ActionDescriptor.EndpointMetadata
+ .Any(em => em.GetType() == typeof(AllowAnonymousAttribute));
+
+ if (isAllowAnonymous)
{
- _userService = userService;
- _services = services;
+ return;
}
- public void OnAuthorization(AuthorizationFilterContext context)
+ var bearerToken = GetBearerToken(context);
+ if (!string.IsNullOrWhiteSpace(bearerToken))
{
- var isAllowAnonymous = context.ActionDescriptor.EndpointMetadata
- .Any(em => em.GetType() == typeof(AllowAnonymousAttribute));
+ var config = _services.GetRequiredService();
+ var token = GetJwtToken(bearerToken);
- if (isAllowAnonymous)
+ if (config.AllowMultipleDeviceLoginUserIds.Contains(token.Claims.First(x => x.Type == "nameid").Value))
{
return;
}
- var bearerToken = GetBearerToken(context);
- if (!string.IsNullOrWhiteSpace(bearerToken))
+ var validTo = token.ValidTo.ToLongTimeString();
+ var currentExpires = GetUserExpires().ToLongTimeString();
+
+ if (validTo != currentExpires)
{
- var config = _services.GetRequiredService();
- var token = GetJwtToken(bearerToken);
-
- if (config.AllowMultipleDeviceLoginUserIds.Contains(token.Claims.First(x => x.Type == "nameid").Value))
- {
- return;
- }
-
- var validTo = token.ValidTo.ToLongTimeString();
- var currentExpires = GetUserExpires().ToLongTimeString();
-
- if (validTo != currentExpires)
- {
- Serilog.Log.Warning($"Token expired. Token expires at {validTo}, current expires at {currentExpires}");
- // login confict
- context.Result = new ConflictResult();
- }
+ Serilog.Log.Warning($"Token expired. Token expires at {validTo}, current expires at {currentExpires}");
+ // login confict
+ context.Result = new ConflictResult();
}
}
-
- private string GetBearerToken(AuthorizationFilterContext context)
- {
- if (context.HttpContext.Request.Headers.TryGetValue(HeaderNames.Authorization, out var bearerToken)
- && !string.IsNullOrWhiteSpace(bearerToken.ToString()))
- {
- var tokenType = bearerToken.ToString().Split(" ").First();
- if (tokenType == JwtBearerDefaults.AuthenticationScheme)
- {
- return bearerToken.ToString().Split(" ").Last();
- }
- }
- return null;
- }
-
- private JwtSecurityToken GetJwtToken(string jwtToken)
- {
- var handler = new JwtSecurityTokenHandler();
- var token = handler.ReadJwtToken(jwtToken);
- return token;
- }
-
- private DateTime GetUserExpires()
- {
- return _userService.GetUserTokenExpires().GetAwaiter().GetResult();
- }
+ }
+
+ private string GetBearerToken(AuthorizationFilterContext context)
+ {
+ if (context.HttpContext.Request.Headers.TryGetValue(HeaderNames.Authorization, out var bearerToken)
+ && !string.IsNullOrWhiteSpace(bearerToken.ToString()))
+ {
+ var tokenType = bearerToken.ToString().Split(" ").First();
+ if (tokenType == JwtBearerDefaults.AuthenticationScheme)
+ {
+ return bearerToken.ToString().Split(" ").Last();
+ }
+ }
+ return null;
+ }
+
+ private JwtSecurityToken GetJwtToken(string jwtToken)
+ {
+ var handler = new JwtSecurityTokenHandler();
+ var token = handler.ReadJwtToken(jwtToken);
+ return token;
+ }
+
+ private DateTime GetUserExpires()
+ {
+ return _userService.GetUserTokenExpires().GetAwaiter().GetResult();
}
}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserCreationModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserCreationModel.cs
index b157ef59..c55da5fa 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserCreationModel.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserCreationModel.cs
@@ -5,18 +5,18 @@ namespace BotSharp.OpenAPI.ViewModels.Users;
public class UserCreationModel
{
public string? UserName { get; set; }
- public string FirstName { get; set; } = string.Empty;
+ public string FirstName { get; set; } = string.Empty;
public string? LastName { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
public string Password { get; set; } = string.Empty;
public string Type { get; set; } = UserType.Client;
public string Role { get; set; } = UserRole.User;
-
+ public string RegionCode { get; set; } = "CN";
public User ToUser()
{
- return new User
- {
+ return new User
+ {
UserName = UserName,
FirstName = FirstName,
LastName = LastName,
@@ -24,7 +24,8 @@ public class UserCreationModel
Phone = Phone,
Password = Password,
Role = Role,
- Type = Type
+ Type = Type,
+ RegionCode = RegionCode
};
}
}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserResetPasswordModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserResetPasswordModel.cs
index 8a58820a..7837c916 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserResetPasswordModel.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserResetPasswordModel.cs
@@ -1,5 +1,3 @@
-using System.Data;
-
namespace BotSharp.OpenAPI.ViewModels.Users;
public class UserResetPasswordModel
@@ -8,6 +6,7 @@ public class UserResetPasswordModel
public string? Phone { get; set; }
public string Password { get; set; } = string.Empty;
public string VerificationCode { get; set; }
+ public string RegionCode { get; set; } = "CN";
public User ToUser()
{
@@ -16,7 +15,8 @@ public class UserResetPasswordModel
Email = Email,
Phone = Phone,
Password = Password,
- VerificationCode = VerificationCode
+ VerificationCode = VerificationCode,
+ RegionCode = RegionCode
};
}
}
diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs
index 78100b18..4df12f8c 100644
--- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs
+++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Users/UserViewModel.cs
@@ -36,6 +36,8 @@ public class UserViewModel
[JsonPropertyName("update_date")]
public DateTime UpdateDate { get; set; }
+ public string RegionCode { get; set; } = "CN";
+
public static UserViewModel FromUser(User user)
{
if (user == null)
@@ -56,7 +58,7 @@ public class UserViewModel
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
- Phone = user.Phone,
+ Phone = user.Phone?.Substring(0, 3) == "+86" ? user.Phone.Substring(3) : user.Phone,
Type = user.Type,
Role = user.Role,
Source = user.Source,
@@ -65,7 +67,8 @@ public class UserViewModel
AgentActions = user.AgentActions?.Select(x => UserAgentActionViewModel.ToViewModel(x)) ?? [],
CreateDate = user.CreatedTime,
UpdateDate = user.UpdatedTime,
- Avatar = "/user/avatar"
+ Avatar = "/user/avatar",
+ RegionCode = string.IsNullOrWhiteSpace(user.RegionCode) ? "CN" : user.RegionCode
};
}
}
diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/KnowledgeRetrievalFn.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/KnowledgeRetrievalFn.cs
index 39584b84..606caad9 100644
--- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/KnowledgeRetrievalFn.cs
+++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/Functions/KnowledgeRetrievalFn.cs
@@ -24,7 +24,7 @@ public class KnowledgeRetrievalFn : IFunctionCallback
var knowledges = new List();
foreach (var hook in hooks)
{
- var k = await hook.GetRelevantKnowledges(message, args.Question);
+ var k = await hook.GetDomainKnowledges(message, args.Question);
knowledges.AddRange(k);
}
knowledges = knowledges.Distinct().ToList();
diff --git a/src/Plugins/BotSharp.Plugin.MicrosoftExtensionsAI/BotSharp.Plugin.MicrosoftExtensionsAI.csproj b/src/Plugins/BotSharp.Plugin.MicrosoftExtensionsAI/BotSharp.Plugin.MicrosoftExtensionsAI.csproj
new file mode 100644
index 00000000..53518b38
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MicrosoftExtensionsAI/BotSharp.Plugin.MicrosoftExtensionsAI.csproj
@@ -0,0 +1,23 @@
+
+
+
+ $(TargetFramework)
+ enable
+ $(LangVersion)
+ $(BotSharpVersion)
+ $(GeneratePackageOnBuild)
+ $(GeneratePackageOnBuild)
+ $(SolutionDir)packages
+ $(NoWarn);NU5104
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Plugins/BotSharp.Plugin.MicrosoftExtensionsAI/MicrosoftExtensionsAIChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.MicrosoftExtensionsAI/MicrosoftExtensionsAIChatCompletionProvider.cs
new file mode 100644
index 00000000..67f1e513
--- /dev/null
+++ b/src/Plugins/BotSharp.Plugin.MicrosoftExtensionsAI/MicrosoftExtensionsAIChatCompletionProvider.cs
@@ -0,0 +1,185 @@
+using BotSharp.Abstraction.Agents.Enums;
+using BotSharp.Abstraction.Agents.Models;
+using BotSharp.Abstraction.Agents;
+using BotSharp.Abstraction.Conversations.Models;
+using BotSharp.Abstraction.Conversations;
+using BotSharp.Abstraction.Files;
+using BotSharp.Abstraction.Files.Utilities;
+using BotSharp.Abstraction.Loggers;
+using BotSharp.Abstraction.MLTasks;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System;
+using System.Linq;
+using System.Text.Json;
+using System.Threading;
+
+namespace BotSharp.Plugin.MicrosoftExtensionsAI;
+
+///
+/// Provides an implementation of for Microsoft.Extensions.AI.
+///
+public sealed class MicrosoftExtensionsAIChatCompletionProvider : IChatCompletion
+{
+ private readonly IChatClient _client;
+ private readonly ILogger _logger;
+ private readonly IServiceProvider _services;
+ private string? _model;
+
+ ///
+ /// Creates an instance of the class.
+ ///
+ public MicrosoftExtensionsAIChatCompletionProvider(
+ IChatClient client,
+ ILogger logger,
+ IServiceProvider services)
+ {
+ _client = client;
+ _model = _client.Metadata.ModelId;
+ _logger = logger;
+ _services = services;
+ }
+
+ ///
+ public string Provider => "microsoft.extensions.ai";
+
+ ///
+ public void SetModelName(string model) => _model = model;
+
+ ///
+ public async Task GetChatCompletions(Agent agent, List conversations)
+ {
+ // Before chat completion hook
+ var hooks = _services.GetServices().ToArray();
+ await Task.WhenAll(hooks.Select(hook => hook.BeforeGenerating(agent, conversations)));
+
+ // Configure options
+ var state = _services.GetRequiredService();
+ var options = new ChatOptions()
+ {
+ Temperature = float.Parse(state.GetState("temperature", "0.0")),
+ MaxOutputTokens = int.Parse(state.GetState("max_tokens", "1024"))
+ };
+
+ if (_services.GetService() is { } agentService)
+ {
+ foreach (var function in agent.Functions)
+ {
+ if (agentService.RenderFunction(agent, function))
+ {
+ var property = agentService.RenderFunctionProperty(agent, function);
+ (options.Tools ??= []).Add(new NopAIFunction(new(function.Name)
+ {
+ Description = function.Description,
+ Parameters = property?.Properties.RootElement.Deserialize>()?.Select(p => new AIFunctionParameterMetadata(p.Key)
+ {
+ Schema = p.Value,
+ }).ToList() ?? [],
+ }));
+ }
+ }
+ }
+
+ // Configure messages
+ List messages = [];
+
+ if (_services.GetRequiredService().RenderedInstruction(agent) is string instruction &&
+ instruction.Length > 0)
+ {
+ messages.Add(new(ChatRole.System, instruction));
+ }
+
+ if (!string.IsNullOrEmpty(agent.Knowledges))
+ {
+ messages.Add(new(ChatRole.System, agent.Knowledges));
+ }
+
+ foreach (var sample in ProviderHelper.GetChatSamples(agent.Samples))
+ {
+ messages.Add(new(sample.Role == AgentRole.Assistant ? ChatRole.Assistant : ChatRole.User, sample.Content));
+ }
+
+ var fileStorage = _services.GetService();
+ bool allowMultiModal = fileStorage is not null && _services.GetService()?.GetSetting(Provider, _model ?? "default")?.MultiModal is true;
+ foreach (var x in conversations)
+ {
+ if (x.Role == AgentRole.Function && x.FunctionName is not null)
+ {
+ messages.Add(new(ChatRole.Assistant,
+ [
+ new FunctionCallContent(x.FunctionName, x.FunctionName, JsonSerializer.Deserialize>(x.FunctionArgs ?? "{}")),
+ new FunctionResultContent(x.FunctionName, x.FunctionName, x.Content)
+ ]));
+ }
+ else if (x.Role == AgentRole.System || x.Role == AgentRole.Assistant)
+ {
+ messages.Add(new(x.Role == AgentRole.System ? ChatRole.System : ChatRole.Assistant, x.Content));
+ }
+ else if (x.Role == AgentRole.User)
+ {
+ List contents = [new TextContent(!string.IsNullOrWhiteSpace(x.Payload) ? x.Payload : x.Content)];
+ if (allowMultiModal)
+ {
+ foreach (var file in x.Files)
+ {
+ if (!string.IsNullOrEmpty(file.FileData))
+ {
+ contents.Add(new ImageContent(file.FileData));
+ }
+ else if (!string.IsNullOrEmpty(file.FileStorageUrl))
+ {
+ var contentType = FileUtility.GetFileContentType(file.FileStorageUrl);
+ var bytes = fileStorage!.GetFileBytes(file.FileStorageUrl);
+ contents.Add(new ImageContent(bytes, contentType));
+ }
+ else if (!string.IsNullOrEmpty(file.FileUrl))
+ {
+ contents.Add(new ImageContent(file.FileUrl));
+ }
+ }
+ }
+
+ messages.Add(new(ChatRole.User, contents) { AuthorName = x.FunctionName });
+ }
+ }
+
+ var completion = await _client.CompleteAsync(messages);
+
+ RoleDialogModel result = new(AgentRole.Assistant, string.Concat(completion.Message.Contents.OfType()))
+ {
+ CurrentAgentId = agent.Id
+ };
+
+ if (completion.Message.Contents.OfType().FirstOrDefault() is { } fcc)
+ {
+ result.Role = AgentRole.Function;
+ result.MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty;
+ result.FunctionName = fcc.Name;
+ result.FunctionArgs = fcc.Arguments is not null ? JsonSerializer.Serialize(fcc.Arguments) : null;
+ }
+
+ // After chat completion hook
+ await Task.WhenAll(hooks.Select(hook => hook.AfterGenerated(result, new() { Model = _model ?? "default" })));
+
+ return result;
+ }
+
+ ///
+ public Task GetChatCompletionsAsync(Agent agent, List conversations, Func onMessageReceived, Func onFunctionExecuting) =>
+ throw new NotImplementedException();
+
+ ///
+ public Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived) =>
+ throw new NotImplementedException();
+
+ private sealed class NopAIFunction(AIFunctionMetadata metadata) : AIFunction
+ {
+ public override AIFunctionMetadata Metadata { get; } = metadata;
+
+ protected override Task