diff --git a/BotSharp.sln b/BotSharp.sln index 0cf4325e..c6d0684b 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -75,7 +75,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.ChatHub", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.TelegramBots", "src\Plugins\BotSharp.Plugin.TelegramBots\BotSharp.Plugin.TelegramBots.csproj", "{DCA18996-4D3A-4E98-BCD0-1FB77C59253E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Logger", "src\Infrastructure\BotSharp.Logger\BotSharp.Logger.csproj", "{5CA3335E-E6AD-46FD-B277-29BBC3A16500}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Logger", "src\Infrastructure\BotSharp.Logger\BotSharp.Logger.csproj", "{5CA3335E-E6AD-46FD-B277-29BBC3A16500}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.Selenium.csproj b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.Selenium.csproj new file mode 100644 index 00000000..20ca5175 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.Selenium.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.1 + enable + $(MSBuildProjectName.Replace(" ", "_"))s + + + + + + + diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj index 480ebabc..292bfe36 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj +++ b/src/Plugins/BotSharp.Plugin.WebDriver/BotSharp.Plugin.WebDriver.csproj @@ -10,7 +10,21 @@ - + + + + + + + + + + + + + + + diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightInstance.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightInstance.cs new file mode 100644 index 00000000..41faa4c5 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightInstance.cs @@ -0,0 +1,21 @@ +namespace BotSharp.Plugin.WebDriver.Drivers; + +public class PlaywrightInstance : IDisposable +{ + IPlaywright _playwright; + IBrowser _browser; + IPage _page; + + public IPlaywright Playwright => _playwright; + public IBrowser Browser => _browser; + public IPage Page => _page; + + public void SetPlaywright(IPlaywright playwright) { _playwright = playwright; } + public void SetBrowser(IBrowser browser) { _browser = browser; } + public void SetPage(IPage page) { _page = page; } + + public void Dispose() + { + _playwright.Dispose(); + } +} diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightWebDriver.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightWebDriver.cs new file mode 100644 index 00000000..15a3673f --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Drivers/PlaywrightWebDriver.cs @@ -0,0 +1,76 @@ +using BotSharp.Plugin.WebDriver.Services; + +namespace BotSharp.Plugin.WebDriver.Drivers; + +public class PlaywrightWebDriver +{ + private readonly IServiceProvider _services; + private readonly PlaywrightInstance _instance; + + public PlaywrightWebDriver(IServiceProvider services, PlaywrightInstance instance) + { + _services = services; + _instance = instance; + } + + public async Task LaunchBrowser(string? url) + { + if (_instance.Playwright == null) + { + var playwright = await Playwright.CreateAsync(); + _instance.SetPlaywright(playwright); + } + + if (_instance.Browser == null) + { + var browser = await _instance.Playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions + { + Headless = false, + Channel = "chrome", + }); + _instance.SetBrowser(browser); + } + + if (!string.IsNullOrEmpty(url)) + { + var page = await _instance.Browser.NewPageAsync(); + _instance.SetPage(page); + var response = await page.GotoAsync(url); + } + + return _instance.Browser; + } + + public async Task ClickElement(Agent agent, BrowsingContextIn context, string messageId) + { + // Retrieve the page raw html and infer the element path + var body = await _instance.Page.QuerySelectorAsync("body"); + + var str = new List(); + var anchors = await body.QuerySelectorAllAsync("a"); + foreach (var a in anchors) + { + var text = await a.TextContentAsync(); + if (!string.IsNullOrEmpty(text)) + { + str.Add($"{text}"); + } + } + + var buttons = await body.QuerySelectorAllAsync("button"); + foreach (var btn in buttons) + { + var text = await btn.TextContentAsync(); + if (!string.IsNullOrEmpty(text)) + { + str.Add($""); + } + } + + var driverService = _services.GetRequiredService(); + var htmlElementContextOut = await driverService.FindElement(agent, string.Join("", str), context.ElementName, messageId); + + var element = _instance.Page.Locator(htmlElementContextOut.TagName).Nth(htmlElementContextOut.Index + 1); + await element.ClickAsync(); + } +} diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Functions/ClickHtmlElementFn.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Functions/ClickHtmlElementFn.cs new file mode 100644 index 00000000..4b3d7a07 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Functions/ClickHtmlElementFn.cs @@ -0,0 +1,31 @@ +using BotSharp.Abstraction.Agents; + +namespace BotSharp.Plugin.WebDriver.Functions; + +public class ClickHtmlElementFn : IFunctionCallback +{ + public string Name => "click_html_element"; + + private readonly IServiceProvider _services; + private readonly PlaywrightWebDriver _driver; + + public ClickHtmlElementFn(IServiceProvider services, + PlaywrightWebDriver driver) + { + _services = services; + _driver = driver; + } + + public async Task Execute(RoleDialogModel message) + { + var args = JsonSerializer.Deserialize(message.FunctionArgs); + + var agentService = _services.GetRequiredService(); + var agent = await agentService.LoadAgent(message.CurrentAgentId); + await _driver.ClickElement(agent, args, message.MessageId); + + message.Content = "Executed successfully."; + + return true; + } +} diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Functions/OpenBrowserFn.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Functions/OpenBrowserFn.cs new file mode 100644 index 00000000..0d18e640 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Functions/OpenBrowserFn.cs @@ -0,0 +1,28 @@ +using BotSharp.Plugin.WebDriver.LlmContexts; + +namespace BotSharp.Plugin.WebDriver.Functions; + +public class OpenBrowserFn : IFunctionCallback +{ + public string Name => "open_browser"; + + private readonly IServiceProvider _services; + private readonly PlaywrightWebDriver _driver; + + public OpenBrowserFn(IServiceProvider services, + PlaywrightWebDriver driver) + { + _services = services; + _driver = driver; + } + + public async Task Execute(RoleDialogModel message) + { + var args = JsonSerializer.Deserialize(message.FunctionArgs); + + var browser = await _driver.LaunchBrowser(args.Url); + message.Content = "Executed successfully."; + + return true; + } +} diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/LlmContexts/BrowsingContextIn.cs b/src/Plugins/BotSharp.Plugin.WebDriver/LlmContexts/BrowsingContextIn.cs new file mode 100644 index 00000000..401b8e61 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/LlmContexts/BrowsingContextIn.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.WebDriver.LlmContexts; + +public class BrowsingContextIn +{ + [JsonPropertyName("url")] + public string? Url { get; set; } + + [JsonPropertyName("element_name")] + public string? ElementName { get; set; } + + [JsonPropertyName("input_text")] + public string? InputText { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/LlmContexts/HtmlElementContextOut.cs b/src/Plugins/BotSharp.Plugin.WebDriver/LlmContexts/HtmlElementContextOut.cs new file mode 100644 index 00000000..a325a4f6 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/LlmContexts/HtmlElementContextOut.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.WebDriver.LlmContexts; + +public class HtmlElementContextOut +{ + [JsonPropertyName("tag_name")] + public string TagName { get; set; } + + [JsonPropertyName("index")] + public int Index { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/README.md b/src/Plugins/BotSharp.Plugin.WebDriver/README.md new file mode 100644 index 00000000..6cdb6a90 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/README.md @@ -0,0 +1,10 @@ +# Web Driver + +The WebDriver project will provide a new agent of `Web Browser` plugged into BotSharp's Routing mechanism. +When Router analyzes that the user's intention is to operate the web page, it will be routed to this Agent. +This Agent will contain some predefined action functions. +After analyzing the user's conversation record, it will generate the corresponding execution function and then call the browsers' API to perform corresponding operations. + +## Installation + +1. Copy `agents\f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b` folder to `WebStarter\data\agents` diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Services/WebDriverService.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Services/WebDriverService.cs new file mode 100644 index 00000000..913f9156 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Services/WebDriverService.cs @@ -0,0 +1,56 @@ +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.MLTasks; +using BotSharp.Core.Infrastructures; + +namespace BotSharp.Plugin.WebDriver.Services; + +public class WebDriverService +{ + private readonly IServiceProvider _services; + + public WebDriverService(IServiceProvider services) + { + _services = services; + } + + public async Task FindElement(Agent agent, string html, string elementName, string messageId) + { + var parserInstruction = agent.Templates.First(x => x.Name == "html_parser").Content; + + var render = _services.GetRequiredService(); + var prompt = render.Render(parserInstruction, new Dictionary + { + { "html_content", html }, + { "element_name", elementName } + }); + + var completer = CompletionProvider.GetCompletion(_services, + agentConfig: agent.LlmConfig); + + if (completer is ITextCompletion textCompleter) + { + var result = await textCompleter.GetCompletion(prompt, agent.Id, messageId); + return result.JsonContent(); + } + else if (completer is IChatCompletion chatCompleter) + { + var dialogs = new List + { + new RoleDialogModel(AgentRole.User, prompt) + { + CurrentAgentId = agent.Id, + MessageId = messageId + } + }; + var result = chatCompleter.GetChatCompletions(new Agent + { + Id = agent.Id, + Name = agent.Name, + Instruction = "You're a HTML Parser." + }, dialogs); + return result.Content.JsonContent(); + } + + return null; + } +} diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/Using.cs b/src/Plugins/BotSharp.Plugin.WebDriver/Using.cs new file mode 100644 index 00000000..ca78d12b --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/Using.cs @@ -0,0 +1,17 @@ +global using System; +global using System.Collections.Generic; +global using System.Text; +global using BotSharp.Abstraction.Conversations; +global using BotSharp.Abstraction.Plugins; +global using System.Text.Json; +global using BotSharp.Abstraction.Conversations.Models; +global using Microsoft.Playwright; +global using BotSharp.Plugin.WebDriver.Drivers; +global using System.Threading.Tasks; +global using BotSharp.Abstraction.Functions; +global using BotSharp.Abstraction.Agents.Models; +global using BotSharp.Abstraction.Templating; +global using BotSharp.Plugin.WebDriver.LlmContexts; +global using Microsoft.Extensions.DependencyInjection; +global using System.Linq; +global using BotSharp.Abstraction.Utilities; \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/WebDriverPlugin.cs b/src/Plugins/BotSharp.Plugin.WebDriver/WebDriverPlugin.cs index 5dde8be6..c83533f6 100644 --- a/src/Plugins/BotSharp.Plugin.WebDriver/WebDriverPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.WebDriver/WebDriverPlugin.cs @@ -1,14 +1,17 @@ -using BotSharp.Abstraction.Plugins; +using BotSharp.Plugin.WebDriver.Services; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -namespace BotSharp.Plugin.WebDriver; +namespace BotSharp.Plugin.Playwrights; public class WebDriverPlugin : IBotSharpPlugin { public string Name => "Web Driver"; + public string Description => "Manipulate web browser in automation tools."; + public void RegisterDI(IServiceCollection services, IConfiguration config) { - + services.AddScoped(); + services.AddSingleton(); + services.AddScoped(); } } diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/agents/f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b/agent.json b/src/Plugins/BotSharp.Plugin.WebDriver/agents/f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b/agent.json new file mode 100644 index 00000000..d733511b --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/agents/f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b/agent.json @@ -0,0 +1,9 @@ +{ + "name": "Web Browser", + "description": "Perform a specific action on a web browser", + "createdDateTime": "2024-01-02T00:00:00Z", + "updatedDateTime": "2024-01-02T00:00:00Z", + "id": "f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b", + "allowRouting": true, + "isPublic": true + } \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/agents/f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b/functions.json b/src/Plugins/BotSharp.Plugin.WebDriver/agents/f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b/functions.json new file mode 100644 index 00000000..b406814f --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/agents/f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b/functions.json @@ -0,0 +1,30 @@ +[ + { + "name": "open_browser", + "description": "open a browser", + "parameters": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "website url." + } + }, + "required": [] + } + }, + { + "name": "click_html_element", + "description": "Click the html element in a web page.", + "parameters": { + "type": "object", + "properties": { + "element_name": { + "type": "string", + "description": "the html element name." + } + }, + "required": ["element_name"] + } + } +] diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/agents/f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b/instruction.liquid b/src/Plugins/BotSharp.Plugin.WebDriver/agents/f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b/instruction.liquid new file mode 100644 index 00000000..1f9f0377 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/agents/f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b/instruction.liquid @@ -0,0 +1,2 @@ +You are a web browser that can manipulate web elements through automated tools like Playwright and Selenium. + diff --git a/src/Plugins/BotSharp.Plugin.WebDriver/agents/f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b/templates/html_parser.liquid b/src/Plugins/BotSharp.Plugin.WebDriver/agents/f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b/templates/html_parser.liquid new file mode 100644 index 00000000..0b0d7e44 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.WebDriver/agents/f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b/templates/html_parser.liquid @@ -0,0 +1,6 @@ +{{ html_content }} + +=== According to above HTML === +You're using Playwright selector syntax. +Find the html element CSS selector full path of "{{ element_name }}". +Output in JSON format {"tag_name": "", "index": -1} with appropriate values. \ No newline at end of file diff --git a/src/WebStarter/WebStarter.csproj b/src/WebStarter/WebStarter.csproj index 1f011ef6..d7e6168f 100644 --- a/src/WebStarter/WebStarter.csproj +++ b/src/WebStarter/WebStarter.csproj @@ -61,12 +61,13 @@ + + + - - diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 345aabab..ba36ef8b 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -167,6 +167,7 @@ "BotSharp.Plugin.WeChat", // "BotSharp.Plugin.TelegramBots", // "BotSharp.Plugin.RoutingSpeeder", + "BotSharp.Plugin.WebDriver", "BotSharp.Plugin.PizzaBot" ] } diff --git a/tests/BotSharp.Plugin.PizzaBot/BotSharp.Plugin.PizzaBot.csproj b/tests/BotSharp.Plugin.PizzaBot/BotSharp.Plugin.PizzaBot.csproj index ac6d48a2..b4c62dd8 100644 --- a/tests/BotSharp.Plugin.PizzaBot/BotSharp.Plugin.PizzaBot.csproj +++ b/tests/BotSharp.Plugin.PizzaBot/BotSharp.Plugin.PizzaBot.csproj @@ -13,6 +13,12 @@ + + + + + + diff --git a/src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/agent.json b/tests/BotSharp.Plugin.PizzaBot/agents/b284db86-e9c2-4c25-a59e-4649797dd130/agent.json similarity index 100% rename from src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/agent.json rename to tests/BotSharp.Plugin.PizzaBot/agents/b284db86-e9c2-4c25-a59e-4649797dd130/agent.json diff --git a/src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/functions.json b/tests/BotSharp.Plugin.PizzaBot/agents/b284db86-e9c2-4c25-a59e-4649797dd130/functions.json similarity index 100% rename from src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/functions.json rename to tests/BotSharp.Plugin.PizzaBot/agents/b284db86-e9c2-4c25-a59e-4649797dd130/functions.json diff --git a/src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/instruction.liquid b/tests/BotSharp.Plugin.PizzaBot/agents/b284db86-e9c2-4c25-a59e-4649797dd130/instruction.liquid similarity index 100% rename from src/WebStarter/data/agents/b284db86-e9c2-4c25-a59e-4649797dd130/instruction.liquid rename to tests/BotSharp.Plugin.PizzaBot/agents/b284db86-e9c2-4c25-a59e-4649797dd130/instruction.liquid diff --git a/src/WebStarter/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/agent.json b/tests/BotSharp.Plugin.PizzaBot/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/agent.json similarity index 100% rename from src/WebStarter/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/agent.json rename to tests/BotSharp.Plugin.PizzaBot/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/agent.json diff --git a/src/WebStarter/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/functions.json b/tests/BotSharp.Plugin.PizzaBot/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/functions.json similarity index 100% rename from src/WebStarter/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/functions.json rename to tests/BotSharp.Plugin.PizzaBot/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/functions.json diff --git a/src/WebStarter/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/instruction.liquid b/tests/BotSharp.Plugin.PizzaBot/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/instruction.liquid similarity index 100% rename from src/WebStarter/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/instruction.liquid rename to tests/BotSharp.Plugin.PizzaBot/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/instruction.liquid diff --git a/src/WebStarter/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/responses/func.get_pizza_price.0.liquid b/tests/BotSharp.Plugin.PizzaBot/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/responses/func.get_pizza_price.0.liquid similarity index 100% rename from src/WebStarter/data/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/responses/func.get_pizza_price.0.liquid rename to tests/BotSharp.Plugin.PizzaBot/agents/c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd/responses/func.get_pizza_price.0.liquid diff --git a/src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/agent.json b/tests/BotSharp.Plugin.PizzaBot/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/agent.json similarity index 100% rename from src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/agent.json rename to tests/BotSharp.Plugin.PizzaBot/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/agent.json diff --git a/src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/instruction.liquid b/tests/BotSharp.Plugin.PizzaBot/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/instruction.liquid similarity index 100% rename from src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/instruction.liquid rename to tests/BotSharp.Plugin.PizzaBot/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/instruction.liquid diff --git a/src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/instruction.executor.liquid b/tests/BotSharp.Plugin.PizzaBot/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/instruction.executor.liquid similarity index 100% rename from src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/instruction.executor.liquid rename to tests/BotSharp.Plugin.PizzaBot/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/instruction.executor.liquid diff --git a/src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/instruction.reviewer.liquid b/tests/BotSharp.Plugin.PizzaBot/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/instruction.reviewer.liquid similarity index 100% rename from src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/instruction.reviewer.liquid rename to tests/BotSharp.Plugin.PizzaBot/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/instruction.reviewer.liquid diff --git a/src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/task.place_pizza_order.liquid b/tests/BotSharp.Plugin.PizzaBot/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/task.place_pizza_order.liquid similarity index 100% rename from src/WebStarter/data/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/task.place_pizza_order.liquid rename to tests/BotSharp.Plugin.PizzaBot/agents/dfd9b46d-d00c-40af-8a75-3fbdc2b89869/templates/task.place_pizza_order.liquid diff --git a/src/WebStarter/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json b/tests/BotSharp.Plugin.PizzaBot/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json similarity index 100% rename from src/WebStarter/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json rename to tests/BotSharp.Plugin.PizzaBot/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/agent.json diff --git a/src/WebStarter/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/functions.json b/tests/BotSharp.Plugin.PizzaBot/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/functions.json similarity index 100% rename from src/WebStarter/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/functions.json rename to tests/BotSharp.Plugin.PizzaBot/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/functions.json diff --git a/src/WebStarter/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/instruction.liquid b/tests/BotSharp.Plugin.PizzaBot/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/instruction.liquid similarity index 100% rename from src/WebStarter/data/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/instruction.liquid rename to tests/BotSharp.Plugin.PizzaBot/agents/fe8c60aa-b114-4ef3-93cb-a8efeac80f75/instruction.liquid