Skip to content

Commit 5acc6c8

Browse files
authored
Merge pull request #457 from iceljc/features/add-agent-user-role
Features/add agent user role
2 parents 2f5f920 + effd44e commit 5acc6c8

File tree

15 files changed

+186
-54
lines changed

15 files changed

+186
-54
lines changed

src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,7 @@ public interface IAgentService
4848
string GetDataDir();
4949
string GetAgentDataDir(string agentId);
5050

51+
List<Agent> GetAgentsByUser(string userId);
52+
5153
PluginDef GetPlugin(string agentId);
5254
}

src/Infrastructure/BotSharp.Abstraction/Plugins/Models/PluginMenuDef.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ public class PluginMenuDef
1919
[JsonIgnore]
2020
public int Weight { get; set; }
2121

22+
[JsonIgnore]
23+
public List<string>? Roles { get; set; }
24+
2225
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
2326
public List<PluginMenuDef>? SubMenu { get; set; }
2427

src/Infrastructure/BotSharp.Core/Agents/AgentPlugin.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using BotSharp.Abstraction.MLTasks;
22
using BotSharp.Abstraction.Plugins.Models;
33
using BotSharp.Abstraction.Settings;
4+
using BotSharp.Abstraction.Users.Enums;
45
using Microsoft.Extensions.Configuration;
56

67
namespace BotSharp.Core.Agents;
@@ -43,8 +44,8 @@ public bool AttachMenu(List<PluginMenuDef> menu)
4344
{
4445
SubMenu = new List<PluginMenuDef>
4546
{
46-
new PluginMenuDef("Routing", link: "page/agent/router"), // icon: "bx bx-map-pin"
47-
new PluginMenuDef("Evaluating", link: "page/agent/evaluator"), // icon: "bx bx-task"
47+
new PluginMenuDef("Routing", link: "page/agent/router") { Roles = new List<string> { UserRole.Admin } }, // icon: "bx bx-map-pin"
48+
new PluginMenuDef("Evaluating", link: "page/agent/evaluator") { Roles = new List<string> { UserRole.Admin } }, // icon: "bx bx-task"
4849
new PluginMenuDef("Agents", link: "page/agent"), // icon: "bx bx-bot"
4950
}
5051
});

src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.CreateAgent.cs

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
using BotSharp.Abstraction.Agents.Models;
2-
using BotSharp.Abstraction.Functions.Models;
3-
using BotSharp.Abstraction.Repositories;
41
using BotSharp.Abstraction.Tasks.Models;
5-
using BotSharp.Abstraction.Users.Models;
62
using System.IO;
73
using System.Text.RegularExpressions;
84

@@ -26,32 +22,13 @@ public async Task<Agent> CreateAgent(Agent agent)
2622

2723
var dbSettings = _services.GetRequiredService<BotSharpDatabaseSettings>();
2824
var agentSettings = _services.GetRequiredService<AgentSettings>();
29-
var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir);
30-
var foundAgent = FetchAgentFileByName(agent.Name, filePath);
31-
32-
if (foundAgent != null)
33-
{
34-
agentRecord.SetId(foundAgent.Id)
35-
.SetName(foundAgent.Name)
36-
.SetDescription(foundAgent.Description)
37-
.SetIsPublic(foundAgent.IsPublic)
38-
.SetDisabled(foundAgent.Disabled)
39-
.SetAgentType(foundAgent.Type)
40-
.SetProfiles(foundAgent.Profiles)
41-
.SetRoutingRules(foundAgent.RoutingRules)
42-
.SetInstruction(foundAgent.Instruction)
43-
.SetTemplates(foundAgent.Templates)
44-
.SetFunctions(foundAgent.Functions)
45-
.SetResponses(foundAgent.Responses)
46-
.SetLlmConfig(foundAgent.LlmConfig);
47-
}
4825

4926
var user = _db.GetUserById(_user.Id);
5027
var userAgentRecord = new UserAgent
5128
{
5229
Id = Guid.NewGuid().ToString(),
5330
UserId = user.Id,
54-
AgentId = foundAgent?.Id ?? agentRecord.Id,
31+
AgentId = agentRecord.Id,
5532
Editable = false,
5633
CreatedTime = DateTime.UtcNow,
5734
UpdatedTime = DateTime.UtcNow
@@ -65,7 +42,7 @@ public async Task<Agent> CreateAgent(Agent agent)
6542

6643
Utilities.ClearCache();
6744

68-
return agentRecord;
45+
return await Task.FromResult(agentRecord);
6946
}
7047

7148
private Agent FetchAgentFileByName(string agentName, string filePath)
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
1+
using BotSharp.Abstraction.Users.Enums;
2+
13
namespace BotSharp.Core.Agents.Services;
24

35
public partial class AgentService
46
{
57
public async Task<bool> DeleteAgent(string id)
68
{
7-
throw new NotImplementedException();
9+
var user = _db.GetUserById(_user.Id);
10+
var agent = _db.GetAgentsByUser(_user.Id).FirstOrDefault(x => x.Id.IsEqualTo(id));
11+
12+
if (user?.Role != UserRole.Admin && agent == null)
13+
{
14+
return false;
15+
}
16+
17+
var deleted = _db.DeleteAgent(id);
18+
return await Task.FromResult(deleted);
819
}
920
}

src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using BotSharp.Abstraction.Repositories.Enums;
22
using BotSharp.Abstraction.Routing.Models;
3+
using BotSharp.Abstraction.Users.Enums;
34
using System.IO;
45

56
namespace BotSharp.Core.Agents.Services;
@@ -8,6 +9,10 @@ public partial class AgentService
89
{
910
public async Task UpdateAgent(Agent agent, AgentField updateField)
1011
{
12+
var userService = _services.GetRequiredService<IUserService>();
13+
var user = await userService.GetUser(_user.Id);
14+
if (user?.Role != UserRole.Admin) return;
15+
1116
if (agent == null || string.IsNullOrEmpty(agent.Id)) return;
1217

1318
var record = _db.GetAgent(agent.Id);

src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,10 @@ public string GetAgentDataDir(string agentId)
4747
}
4848
return dir;
4949
}
50+
51+
public List<Agent> GetAgentsByUser(string userId)
52+
{
53+
var agents = _db.GetAgentsByUser(userId);
54+
return agents;
55+
}
5056
}

src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,4 +269,20 @@ public void Configure(IApplicationBuilder app)
269269
}
270270
});
271271
}
272+
273+
public List<PluginMenuDef> GetPluginMenuByRoles(List<PluginMenuDef> plugins, string userRole)
274+
{
275+
if (plugins.IsNullOrEmpty()) return plugins;
276+
277+
var filtered = new List<PluginMenuDef>();
278+
foreach (var plugin in plugins)
279+
{
280+
if (plugin.Roles.IsNullOrEmpty() || plugin.Roles.Contains(userRole))
281+
{
282+
plugin.SubMenu = GetPluginMenuByRoles(plugin.SubMenu, userRole);
283+
filtered.Add(plugin);
284+
}
285+
}
286+
return filtered;
287+
}
272288
}

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,40 @@ public bool DeleteAgents()
436436

437437
public bool DeleteAgent(string agentId)
438438
{
439-
return false;
439+
if (string.IsNullOrEmpty(agentId)) return false;
440+
441+
try
442+
{
443+
var agentDir = GetAgentDataDir(agentId);
444+
if (string.IsNullOrEmpty(agentDir)) return false;
445+
446+
// Delete agent user relationships
447+
var usersDir = Path.Combine(_dbSettings.FileRepository, "users");
448+
if (Directory.Exists(usersDir))
449+
{
450+
foreach (var userDir in Directory.GetDirectories(usersDir))
451+
{
452+
var userAgentFile = Directory.GetFiles(userDir).FirstOrDefault(x => Path.GetFileName(x) == USER_AGENT_FILE);
453+
if (string.IsNullOrEmpty(userAgentFile)) continue;
454+
455+
var text = File.ReadAllText(userAgentFile);
456+
var userAgents = JsonSerializer.Deserialize<List<UserAgent>>(text, _options);
457+
if (userAgents.IsNullOrEmpty()) continue;
458+
459+
userAgents = userAgents.Where(x => x.AgentId != agentId).ToList();
460+
File.WriteAllText(userAgentFile, JsonSerializer.Serialize(userAgents, _options));
461+
}
462+
}
463+
464+
// Delete agent folder
465+
Directory.Delete(agentDir, true);
466+
467+
return true;
468+
}
469+
catch
470+
{
471+
return false;
472+
}
440473
}
441474
}
442475
}

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.User.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using BotSharp.Abstraction.Users.Enums;
12
using BotSharp.Abstraction.Users.Models;
23
using System.IO;
34

src/Infrastructure/BotSharp.Core/Tasks/TaskPlugin.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using BotSharp.Abstraction.Plugins.Models;
22
using BotSharp.Abstraction.Tasks;
3+
using BotSharp.Abstraction.Users.Enums;
34
using BotSharp.Core.Tasks.Services;
45
using Microsoft.Extensions.Configuration;
56

@@ -19,7 +20,10 @@ public void RegisterDI(IServiceCollection services, IConfiguration config)
1920
public bool AttachMenu(List<PluginMenuDef> menu)
2021
{
2122
var section = menu.First(x => x.Label == "Apps");
22-
menu.Add(new PluginMenuDef("Task", link: "page/task", icon: "bx bx-task", weight: section.Weight + 8));
23+
menu.Add(new PluginMenuDef("Task", link: "page/task", icon: "bx bx-task", weight: section.Weight + 8)
24+
{
25+
Roles = new List<string> { UserRole.Admin }
26+
});
2327

2428
return true;
2529
}

src/Infrastructure/BotSharp.OpenAPI/Controllers/AgentController.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using BotSharp.Abstraction.Agents.Models;
2+
using BotSharp.Abstraction.Users.Enums;
23

34
namespace BotSharp.OpenAPI.Controllers;
45

@@ -7,11 +8,13 @@ namespace BotSharp.OpenAPI.Controllers;
78
public class AgentController : ControllerBase
89
{
910
private readonly IAgentService _agentService;
11+
private readonly IUserIdentity _user;
1012
private readonly IServiceProvider _services;
1113

12-
public AgentController(IAgentService agentService, IServiceProvider services)
14+
public AgentController(IAgentService agentService, IUserIdentity user, IServiceProvider services)
1315
{
1416
_agentService = agentService;
17+
_user = user;
1518
_services = services;
1619
}
1720

@@ -23,14 +26,16 @@ public AgentSettings GetSettings()
2326
}
2427

2528
[HttpGet("/agent/{id}")]
26-
public async Task<AgentViewModel> GetAgent([FromRoute] string id)
29+
public async Task<AgentViewModel?> GetAgent([FromRoute] string id)
2730
{
2831
var agents = await GetAgents(new AgentFilter
2932
{
3033
AgentIds = new List<string> { id }
3134
});
3235

3336
var targetAgent = agents.Items.FirstOrDefault();
37+
if (targetAgent == null) return null;
38+
3439
var redirectAgentIds = targetAgent.RoutingRules
3540
.Where(x => !string.IsNullOrEmpty(x.RedirectTo))
3641
.Select(x => x.RedirectTo).ToList();
@@ -45,6 +50,17 @@ public async Task<AgentViewModel> GetAgent([FromRoute] string id)
4550

4651
rule.RedirectToAgentName = found.Name;
4752
}
53+
54+
var editable = true;
55+
var userService = _services.GetRequiredService<IUserService>();
56+
var user = await userService.GetUser(_user.Id);
57+
if (user?.Role != UserRole.Admin)
58+
{
59+
var userAgents = _agentService.GetAgentsByUser(user?.Id);
60+
editable = userAgents?.Select(x => x.Id)?.Contains(targetAgent.Id) ?? false;
61+
}
62+
63+
targetAgent.Editable = editable;
4864
return targetAgent;
4965
}
5066

@@ -118,4 +134,10 @@ public async Task<string> PatchAgentTemplates([FromRoute] string agentId, [FromB
118134
model.Id = agentId;
119135
return await _agentService.PatchAgentTemplate(model);
120136
}
137+
138+
[HttpDelete("/agent/{agentId}")]
139+
public async Task<bool> DeleteAgent([FromRoute] string agentId)
140+
{
141+
return await _agentService.DeleteAgent(agentId);
142+
}
121143
}

src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using BotSharp.Abstraction.Options;
22
using BotSharp.Abstraction.Routing;
3+
using BotSharp.Abstraction.Users.Enums;
34

45
namespace BotSharp.OpenAPI.Controllers;
56

@@ -41,20 +42,23 @@ public async Task<ConversationViewModel> NewConversation([FromRoute] string agen
4142
[HttpPost("/conversations")]
4243
public async Task<PagedItems<ConversationViewModel>> GetConversations([FromBody] ConversationFilter filter)
4344
{
44-
var service = _services.GetRequiredService<IConversationService>();
45-
var conversations = await service.GetConversations(filter);
46-
45+
var convService = _services.GetRequiredService<IConversationService>();
4746
var userService = _services.GetRequiredService<IUserService>();
47+
var user = await userService.GetUser(_user.Id);
48+
if (user == null)
49+
{
50+
return new PagedItems<ConversationViewModel>();
51+
}
52+
53+
filter.UserId = user.Role != UserRole.Admin ? user.Id : null;
54+
var conversations = await convService.GetConversations(filter);
4855
var agentService = _services.GetRequiredService<IAgentService>();
49-
var list = conversations.Items
50-
.Select(x => ConversationViewModel.FromSession(x))
51-
.ToList();
56+
var list = conversations.Items.Select(x => ConversationViewModel.FromSession(x)).ToList();
5257

5358
foreach (var item in list)
5459
{
55-
var user = await userService.GetUser(item.User.Id);
60+
user = await userService.GetUser(item.User.Id);
5661
item.User = UserViewModel.FromUser(user);
57-
5862
var agent = await agentService.GetAgent(item.AgentId);
5963
item.AgentName = agent?.Name;
6064
}
@@ -119,21 +123,30 @@ public async Task<IEnumerable<ChatResponseModel>> GetDialogs([FromRoute] string
119123
}
120124

121125
[HttpGet("/conversation/{conversationId}")]
122-
public async Task<ConversationViewModel> GetConversation([FromRoute] string conversationId)
126+
public async Task<ConversationViewModel?> GetConversation([FromRoute] string conversationId)
123127
{
124128
var service = _services.GetRequiredService<IConversationService>();
125-
var conversations = await service.GetConversations(new ConversationFilter
129+
var userService = _services.GetRequiredService<IUserService>();
130+
var user = await userService.GetUser(_user.Id);
131+
if (user == null)
126132
{
127-
Id = conversationId
128-
});
133+
return null;
134+
}
129135

130-
var userService = _services.GetRequiredService<IUserService>();
136+
var filter = new ConversationFilter
137+
{
138+
Id = conversationId,
139+
UserId = user.Role != UserRole.Admin ? user.Id : null
140+
};
141+
var conversations = await service.GetConversations(filter);
142+
if (conversations.Items.IsNullOrEmpty())
143+
{
144+
return null;
145+
}
146+
131147
var result = ConversationViewModel.FromSession(conversations.Items.First());
132-
133148
var state = _services.GetRequiredService<IConversationStateService>();
134149
result.States = state.Load(conversationId, isReadOnly: true);
135-
136-
var user = await userService.GetUser(result.User.Id);
137150
result.User = UserViewModel.FromUser(user);
138151

139152
return result;
@@ -178,7 +191,22 @@ public async Task<UserViewModel> GetConversationUser([FromRoute] string conversa
178191
[HttpDelete("/conversation/{conversationId}")]
179192
public async Task<bool> DeleteConversation([FromRoute] string conversationId)
180193
{
194+
var userService = _services.GetRequiredService<IUserService>();
181195
var conversationService = _services.GetRequiredService<IConversationService>();
196+
197+
var user = await userService.GetUser(_user.Id);
198+
var filter = new ConversationFilter
199+
{
200+
Id = conversationId,
201+
UserId = user.Role != UserRole.Admin ? user.Id : null
202+
};
203+
var conversations = await conversationService.GetConversations(filter);
204+
205+
if (conversations.Items.IsNullOrEmpty())
206+
{
207+
return false;
208+
}
209+
182210
var response = await conversationService.DeleteConversations(new List<string> { conversationId });
183211
return response;
184212
}

0 commit comments

Comments
 (0)