diff --git a/samples/da-ristorante-api-csharp/.gitignore b/samples/da-ristorante-api-csharp/.gitignore
new file mode 100644
index 000000000..ff49814b0
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/.gitignore
@@ -0,0 +1,15 @@
+# TeamsFx files
+M365Agent/appPackage/build
+M365Agent/env/.env.*.user
+M365Agent/env/.env.local
+
+# User-specific files
+*.user
+
+# Build results
+[Dd]ebug/
+[Rr]elease/
+x64/
+x86/
+[Bb]in/
+[Oo]bj/
\ No newline at end of file
diff --git a/samples/da-ristorante-api-csharp/DaRistoranteApi.slnx b/samples/da-ristorante-api-csharp/DaRistoranteApi.slnx
new file mode 100644
index 000000000..0a986fe2f
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/DaRistoranteApi.slnx
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/samples/da-ristorante-api-csharp/DaRistoranteApi/DaRistoranteApi.csproj b/samples/da-ristorante-api-csharp/DaRistoranteApi/DaRistoranteApi.csproj
new file mode 100644
index 000000000..37142be96
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/DaRistoranteApi/DaRistoranteApi.csproj
@@ -0,0 +1,33 @@
+
+
+
+ net10.0
+ enable
+ v4
+ Exe
+ DaRistoranteApi
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+ Never
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/samples/da-ristorante-api-csharp/DaRistoranteApi/MenuData.cs b/samples/da-ristorante-api-csharp/DaRistoranteApi/MenuData.cs
new file mode 100644
index 000000000..cd5166754
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/DaRistoranteApi/MenuData.cs
@@ -0,0 +1,19 @@
+using DaRistoranteApi.Models;
+using System.Text.Json;
+
+namespace DaRistoranteApi
+{
+ public static class MenuData
+ {
+ private static readonly List _dishes;
+
+ static MenuData()
+ {
+ var dataPath = Path.Combine(AppContext.BaseDirectory, "data.json");
+ var json = File.ReadAllText(dataPath);
+ _dishes = JsonSerializer.Deserialize>(json) ?? new List();
+ }
+
+ public static List GetDishes() => _dishes;
+ }
+}
diff --git a/samples/da-ristorante-api-csharp/DaRistoranteApi/Models/DishModels.cs b/samples/da-ristorante-api-csharp/DaRistoranteApi/Models/DishModels.cs
new file mode 100644
index 000000000..d121bf914
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/DaRistoranteApi/Models/DishModels.cs
@@ -0,0 +1,46 @@
+using System.Text.Json.Serialization;
+
+namespace DaRistoranteApi.Models
+{
+ public class Dish
+ {
+ [JsonPropertyName("id")]
+ public int Id { get; set; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("description")]
+ public string Description { get; set; }
+
+ [JsonPropertyName("image_url")]
+ public string ImageUrl { get; set; }
+
+ [JsonPropertyName("price")]
+ public double Price { get; set; }
+
+ [JsonPropertyName("allergens")]
+ public List Allergens { get; set; } = new();
+
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ [JsonPropertyName("course")]
+ public string Course { get; set; }
+ }
+
+ public class OrderedDish
+ {
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("quantity")]
+ public int Quantity { get; set; }
+ }
+
+ public class Order
+ {
+ [JsonPropertyName("dishes")]
+ public List Dishes { get; set; }
+ }
+}
diff --git a/samples/da-ristorante-api-csharp/DaRistoranteApi/Program.cs b/samples/da-ristorante-api-csharp/DaRistoranteApi/Program.cs
new file mode 100644
index 000000000..cd97ae1f6
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/DaRistoranteApi/Program.cs
@@ -0,0 +1,7 @@
+using Microsoft.Extensions.Hosting;
+
+var host = new HostBuilder()
+ .ConfigureFunctionsWorkerDefaults()
+ .Build();
+
+host.Run();
\ No newline at end of file
diff --git a/samples/da-ristorante-api-csharp/DaRistoranteApi/Properties/launchSettings.json b/samples/da-ristorante-api-csharp/DaRistoranteApi/Properties/launchSettings.json
new file mode 100644
index 000000000..f7847fb4e
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/DaRistoranteApi/Properties/launchSettings.json
@@ -0,0 +1,13 @@
+{
+ "profiles": {
+ "Start Project": {
+ "commandName": "Project",
+ "commandLineArgs": "host start --port 5130 --pause-on-error",
+ "dotnetRunMessages": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "hotReloadProfile": "aspnetcore"
+ }
+ }
+}
diff --git a/samples/da-ristorante-api-csharp/DaRistoranteApi/RistoranteApi.cs b/samples/da-ristorante-api-csharp/DaRistoranteApi/RistoranteApi.cs
new file mode 100644
index 000000000..a92a8fbea
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/DaRistoranteApi/RistoranteApi.cs
@@ -0,0 +1,113 @@
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Http;
+using Microsoft.Extensions.Logging;
+using System.Net;
+using System.Text.Json;
+
+namespace DaRistoranteApi
+{
+ public class RistoranteApi
+ {
+ private readonly ILogger _logger;
+
+ public RistoranteApi(ILoggerFactory loggerFactory)
+ {
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ [Function("dishes")]
+ public async Task GetDishesAsync(
+ [HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req)
+ {
+ _logger.LogInformation("C# HTTP trigger function processed a dishes request.");
+
+ var allDishes = MenuData.GetDishes();
+ var filtered = allDishes.AsEnumerable();
+
+ var name = req.Query["name"];
+ if (!string.IsNullOrEmpty(name))
+ {
+ filtered = filtered.Where(d => d.Name.Contains(name, StringComparison.OrdinalIgnoreCase));
+ }
+
+ var course = req.Query["course"];
+ if (!string.IsNullOrEmpty(course))
+ {
+ filtered = filtered.Where(d => d.Course == course);
+ }
+
+ var type = req.Query["type"];
+ if (!string.IsNullOrEmpty(type))
+ {
+ filtered = filtered.Where(d => d.Type == type);
+ }
+
+ var allergensParam = req.Query["allergens"];
+ if (!string.IsNullOrEmpty(allergensParam))
+ {
+ var allergens = allergensParam.Split(',', StringSplitOptions.RemoveEmptyEntries);
+ filtered = filtered.Where(d => allergens.All(a => !d.Allergens.Contains(a)));
+ }
+
+ var response = req.CreateResponse(HttpStatusCode.OK);
+ await response.WriteAsJsonAsync(new { dishes = filtered.ToList() });
+ return response;
+ }
+
+ [Function("orders")]
+ public async Task PlaceOrderAsync(
+ [HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
+ {
+ _logger.LogInformation("C# HTTP trigger function processed an orders request.");
+
+ Models.Order order;
+ try
+ {
+ order = await JsonSerializer.DeserializeAsync(req.Body);
+ }
+ catch (JsonException)
+ {
+ var badResponse = req.CreateResponse(HttpStatusCode.BadRequest);
+ await badResponse.WriteAsJsonAsync(new { message = "Invalid JSON format" });
+ return badResponse;
+ }
+
+ if (order?.Dishes == null || order.Dishes.Count == 0)
+ {
+ var badResponse = req.CreateResponse(HttpStatusCode.BadRequest);
+ await badResponse.WriteAsJsonAsync(new { message = "Invalid order format" });
+ return badResponse;
+ }
+
+ var allDishes = MenuData.GetDishes();
+ double totalPrice = 0;
+
+ foreach (var orderedDish in order.Dishes)
+ {
+ var match = allDishes.FirstOrDefault(d =>
+ d.Name.Contains(orderedDish.Name, StringComparison.OrdinalIgnoreCase));
+
+ if (match == null)
+ {
+ _logger.LogError("Invalid dish: {DishName}", orderedDish.Name);
+ var badResponse = req.CreateResponse(HttpStatusCode.BadRequest);
+ await badResponse.WriteAsJsonAsync(new { message = "One or more invalid dishes" });
+ return badResponse;
+ }
+
+ totalPrice += match.Price * orderedDish.Quantity;
+ }
+
+ var orderId = Random.Shared.Next(0, 10000);
+
+ var response = req.CreateResponse(HttpStatusCode.Created);
+ await response.WriteAsJsonAsync(new
+ {
+ order_id = orderId,
+ status = "confirmed",
+ total_price = totalPrice
+ });
+ return response;
+ }
+ }
+}
diff --git a/samples/da-ristorante-api-csharp/DaRistoranteApi/data.json b/samples/da-ristorante-api-csharp/DaRistoranteApi/data.json
new file mode 100644
index 000000000..e1a054981
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/DaRistoranteApi/data.json
@@ -0,0 +1,176 @@
+[
+ {
+ "id": 1,
+ "name": "Classic Italian Frittata",
+ "description": "A fluffy omelette filled with sautéed mushrooms, onions, and melted pecorino, served with a side of roasted cherry tomatoes.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/frittata.jpeg",
+ "price": 8.99,
+ "allergens": [
+ "eggs",
+ "dairy"
+ ],
+ "course": "breakfast",
+ "type": "dish"
+ },
+ {
+ "id": 2,
+ "name": "Prosciutto & Melon",
+ "description": "Fresh, sweet cantaloupe wrapped in thin slices of salty prosciutto, drizzled with balsamic glaze and a hint of mint.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/prosciutto_melon.jpeg",
+ "price": 7.50,
+ "allergens": [],
+ "course": "breakfast",
+ "type": "dish"
+ },
+ {
+ "id": 3,
+ "name": "Ricotta Pancakes",
+ "description": "Light and fluffy pancakes made with creamy ricotta cheese, topped with fresh berries and a dusting of powdered sugar.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/ricotta_pancakes.jpeg",
+ "price": 9.99,
+ "allergens": [
+ "gluten",
+ "dairy",
+ "eggs"
+ ],
+ "course": "breakfast",
+ "type": "dish"
+ },
+ {
+ "id": 10,
+ "name": "Cappuccino",
+ "description": "A perfect blend of espresso and steamed milk with a thick, velvety foam topping.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/cappuccino.jpeg",
+ "price": 3.99,
+ "allergens": [
+ "dairy"
+ ],
+ "course": "breakfast",
+ "type": "drink"
+ },
+ {
+ "id": 11,
+ "name": "Freshly Squeezed Orange Juice",
+ "description": "Refreshing and tangy, made from hand-picked oranges, a perfect start to your day.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/orange_juice.jpeg",
+ "price": 4.50,
+ "allergens": [],
+ "course": "breakfast",
+ "type": "drink"
+ },
+ {
+ "id": 4,
+ "name": "Caprese Salad",
+ "description": "Juicy vine-ripened tomatoes, fresh mozzarella, and fragrant basil leaves, drizzled with extra virgin olive oil and a touch of balsamic.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/caprese_salad.jpeg",
+ "price": 10.50,
+ "allergens": [
+ "dairy"
+ ],
+ "course": "lunch",
+ "type": "dish"
+ },
+ {
+ "id": 5,
+ "name": "Spaghetti Carbonara",
+ "description": "Al dente spaghetti tossed in a velvety sauce of eggs, Parmesan, and crispy pancetta, finished with cracked black pepper and a sprinkle of parsley.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/spaghetti_carbonara.jpeg",
+ "price": 14.99,
+ "allergens": [
+ "gluten",
+ "dairy",
+ "eggs"
+ ],
+ "course": "lunch",
+ "type": "dish"
+ },
+ {
+ "id": 6,
+ "name": "Grilled Chicken Panini",
+ "description": "Grilled marinated chicken breast with sun-dried tomatoes, arugula, and melted provolone, served on toasted ciabatta bread with pesto mayo.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/chicken_panini.jpeg",
+ "price": 12.50,
+ "allergens": [
+ "gluten",
+ "dairy"
+ ],
+ "course": "lunch",
+ "type": "dish"
+ },
+ {
+ "id": 12,
+ "name": "Iced Tea",
+ "description": "A cool and refreshing brewed tea with a hint of lemon and mint.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/iced_tea.jpeg",
+ "price": 2.99,
+ "allergens": [],
+ "course": "lunch",
+ "type": "drink"
+ },
+ {
+ "id": 13,
+ "name": "San Pellegrino Sparkling Water",
+ "description": "Crisp, bubbly sparkling water from the Italian Alps, a refreshing palate cleanser.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/sparkling_water.jpeg",
+ "price": 3.50,
+ "allergens": [],
+ "course": "lunch",
+ "type": "drink"
+ },
+ {
+ "id": 7,
+ "name": "Veal Osso Buco",
+ "description": "Tender veal shanks braised in a rich tomato and white wine sauce, served with a creamy saffron risotto and gremolata.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/veal_osso_buco.jpeg",
+ "price": 23.99,
+ "allergens": [],
+ "course": "dinner",
+ "type": "dish"
+ },
+ {
+ "id": 8,
+ "name": "Seafood Linguine",
+ "description": "Fresh linguine pasta tossed with mussels, clams, shrimp, and calamari in a delicate white wine and garlic sauce, with a hint of red pepper flakes.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/seafood_linguine.jpeg",
+ "price": 19.99,
+ "allergens": [
+ "gluten",
+ "shellfish"
+ ],
+ "course": "dinner",
+ "type": "dish"
+ },
+ {
+ "id": 9,
+ "name": "Tiramisu",
+ "description": "A luscious, creamy blend of espresso-soaked ladyfingers, mascarpone, and cocoa, this dessert is a classic Italian indulgence.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/tiramisu.jpeg",
+ "price": 7.50,
+ "allergens": [
+ "dairy",
+ "eggs"
+ ],
+ "course": "dinner",
+ "type": "dish"
+ },
+ {
+ "id": 14,
+ "name": "Chianti Classico",
+ "description": "A robust and velvety red wine with notes of cherries, violet, and subtle spice, perfect for pairing with hearty Italian dishes.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/chianti_classico.jpeg",
+ "price": 9.99,
+ "allergens": [],
+ "course": "dinner",
+ "type": "drink"
+ },
+ {
+ "id": 15,
+ "name": "Espresso",
+ "description": "A bold and intense shot of rich espresso, with deep crema, the perfect end to a delightful meal.",
+ "image_url": "https://raw.githubusercontent.com/pnp/copilot-pro-dev-samples/main/samples/da-ristorante-api-csharp/assets/espresso.jpeg",
+ "price": 2.99,
+ "allergens": [],
+ "course": "dinner",
+ "type": "drink"
+ }
+]
\ No newline at end of file
diff --git a/samples/da-ristorante-api-csharp/DaRistoranteApi/host.json b/samples/da-ristorante-api-csharp/DaRistoranteApi/host.json
new file mode 100644
index 000000000..a8dd88f8b
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/DaRistoranteApi/host.json
@@ -0,0 +1,8 @@
+{
+ "version": "2.0",
+ "logging": {
+ "logLevel": {
+ "Function": "Information"
+ }
+ }
+}
diff --git a/samples/da-ristorante-api-csharp/DaRistoranteApi/local.settings.json b/samples/da-ristorante-api-csharp/DaRistoranteApi/local.settings.json
new file mode 100644
index 000000000..dbd8b9860
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/DaRistoranteApi/local.settings.json
@@ -0,0 +1,6 @@
+{
+ "IsEncrypted": false,
+ "Values": {
+ "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
+ }
+}
\ No newline at end of file
diff --git a/samples/da-ristorante-api-csharp/M365Agent/M365Agent.atkproj b/samples/da-ristorante-api-csharp/M365Agent/M365Agent.atkproj
new file mode 100644
index 000000000..21b3d5a0d
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/M365Agent/M365Agent.atkproj
@@ -0,0 +1,10 @@
+
+
+
+ b069b3bd-f6bc-cc40-82ab-3fcc2ea50fdf
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/da-ristorante-api-csharp/M365Agent/appPackage/ai-plugin.json b/samples/da-ristorante-api-csharp/M365Agent/appPackage/ai-plugin.json
new file mode 100644
index 000000000..fde7c2ecb
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/M365Agent/appPackage/ai-plugin.json
@@ -0,0 +1,140 @@
+{
+ "$schema": "https://aka.ms/json-schemas/copilot/plugin/v2.1/schema.json",
+ "schema_version": "v2.1",
+ "namespace": "ilristorante",
+ "name_for_human": "Il Ristorante",
+ "description_for_human": "See the today's menu and place orders",
+ "description_for_model": "Plugin for getting the today's menu, optionally filtered by course and allergens, and placing orders",
+ "functions": [
+ {
+ "name": "getDishes",
+ "description": "Returns information about the dishes on the menu. Can filter by course (breakfast, lunch or dinner), name, allergens, or type (dish, drink).",
+ "capabilities": {
+ "response_semantics": {
+ "data_path": "$.dishes",
+ "properties": {
+ "title": "$.name",
+ "subtitle": "$.description"
+ },
+ "static_template": {
+ "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
+ "type": "AdaptiveCard",
+ "version": "1.5",
+ "body": [
+ {
+ "type": "Container",
+ "items": [
+ {
+ "type": "Image",
+ "url": "${image_url}",
+ "size": "large"
+ },
+ {
+ "type": "TextBlock",
+ "text": "${name}",
+ "weight": "Bolder"
+ },
+ {
+ "type": "TextBlock",
+ "text": "${description}",
+ "wrap": true
+ },
+ {
+ "type": "TextBlock",
+ "text": "Allergens: ${if(count(allergens) > 0, join(allergens, ', '), 'none')}",
+ "weight": "Lighter"
+ },
+ {
+ "type": "TextBlock",
+ "text": "**Price:** €${formatNumber(price, 2)}",
+ "weight": "Lighter",
+ "spacing": "None"
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ },
+ {
+ "name": "placeOrder",
+ "description": "Places an order and returns the order details",
+ "capabilities": {
+ "response_semantics": {
+ "data_path": "$",
+ "properties": {
+ "title": "$.order_id",
+ "subtitle": "$.total_price"
+ },
+ "static_template": {
+ "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
+ "type": "AdaptiveCard",
+ "version": "1.5",
+ "body": [
+ {
+ "type": "TextBlock",
+ "text": "Order Confirmation 🤌",
+ "size": "Large",
+ "weight": "Bolder",
+ "horizontalAlignment": "Center"
+ },
+ {
+ "type": "Container",
+ "items": [
+ {
+ "type": "TextBlock",
+ "text": "Your order has been successfully placed!",
+ "weight": "Bolder",
+ "spacing": "Small"
+ },
+ {
+ "type": "FactSet",
+ "facts": [
+ {
+ "title": "Order ID:",
+ "value": "${order_id} "
+ },
+ {
+ "title": "Status:",
+ "value": "${status}"
+ },
+ {
+ "title": "Total Price:",
+ "value": "€${formatNumber(total_price, 2)}"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ }
+ ],
+ "runtimes": [
+ {
+ "type": "OpenApi",
+ "auth": {
+ "type": "None"
+ },
+ "spec": {
+ "url": "apiSpecificationFile/ristorante.yml",
+ "progress_style": "ShowUsageWithInputAndOutput"
+ },
+ "run_for_functions": ["getDishes", "placeOrder"]
+ }
+ ],
+ "capabilities": {
+ "localization": {},
+ "conversation_starters": [
+ {
+ "text": "What's for lunch today?"
+ },
+ {
+ "text": "What can I order for dinner that is gluten-free?"
+ }
+ ]
+ }
+}
diff --git a/samples/da-ristorante-api-csharp/M365Agent/appPackage/apiSpecificationFile/ristorante.yml b/samples/da-ristorante-api-csharp/M365Agent/appPackage/apiSpecificationFile/ristorante.yml
new file mode 100644
index 000000000..c32ba10f6
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/M365Agent/appPackage/apiSpecificationFile/ristorante.yml
@@ -0,0 +1,128 @@
+openapi: 3.0.0
+info:
+ title: Il Ristorante menu API
+ version: 1.0.0
+ description: API to retrieve dishes and place orders for Il Ristorante.
+servers:
+ - url: ${{OPENAPI_SERVER_URL}}/api
+ description: Il Ristorante API server
+paths:
+ /dishes:
+ get:
+ operationId: getDishes
+ summary: Get all available dishes
+ description: Retrieve a list of all dishes, optionally filtered by course and allergens.
+ parameters:
+ - in: query
+ name: course
+ schema:
+ type: string
+ description: Filter dishes by course. Can be breakfast, lunch, or dinner.
+ - in: query
+ name: name
+ schema:
+ type: string
+ description: Find dishes by name.
+ - in: query
+ name: type
+ schema:
+ type: string
+ description: Filter dishes by type. Can be dish or drink.
+ - in: query
+ name: allergens
+ schema:
+ type: array
+ items:
+ type: string
+ description: Filter dishes to exclude specific allergens.
+ responses:
+ '200':
+ description: A list of dishes.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Dish'
+
+ /orders:
+ post:
+ operationId: placeOrder
+ summary: Place an order
+ description: Place an order by providing an array of dishes and quantities.
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ dishes:
+ type: array
+ description: List of items to order. Each item consists of a dish and a quantity.
+ items:
+ type: object
+ properties:
+ name:
+ type: string
+ description: The name of the dish to order.
+ quantity:
+ type: integer
+ description: The quantity of the dish to order.
+ required:
+ - name
+ - quantity
+ required:
+ - dishes
+ responses:
+ '201':
+ description: Order placed successfully.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ order_id:
+ type: integer
+ status:
+ type: string
+ example: confirmed
+ total_price:
+ type: number
+ format: float
+ '400':
+ description: Invalid order request.
+
+components:
+ schemas:
+ Dish:
+ type: object
+ properties:
+ id:
+ type: integer
+ description: Unique identifier for the dish.
+ name:
+ type: string
+ description: The name of the dish.
+ description:
+ type: string
+ description: A short description of the dish.
+ image_url:
+ type: string
+ description: URL to an image of the dish.
+ price:
+ type: number
+ format: float
+ description: The price of the dish.
+ allergens:
+ type: array
+ items:
+ type: string
+ description: List of allergens in the dish, if any.
+ type:
+ type: string
+ description: The type of dish. Can be either a dish or a drink.
+ course:
+ type: string
+ description: The course to which the dish belongs. Can be breakfast, lunch, or dinner.
+
diff --git a/samples/da-ristorante-api-csharp/M365Agent/appPackage/color.png b/samples/da-ristorante-api-csharp/M365Agent/appPackage/color.png
new file mode 100644
index 000000000..73c4856c8
Binary files /dev/null and b/samples/da-ristorante-api-csharp/M365Agent/appPackage/color.png differ
diff --git a/samples/da-ristorante-api-csharp/M365Agent/appPackage/declarativeAgent.json b/samples/da-ristorante-api-csharp/M365Agent/appPackage/declarativeAgent.json
new file mode 100644
index 000000000..1e1d6edae
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/M365Agent/appPackage/declarativeAgent.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://aka.ms/json-schemas/copilot/declarative-agent/v1.0/schema.json",
+ "version": "v1.0",
+ "name": "Il Ristorante",
+ "description": "Order the most delicious Italian dishes and drinks from the comfort of your desk.",
+ "instructions": "$[file('instruction.txt')]",
+ "conversation_starters": [
+ {
+ "text": "What's for lunch today?"
+ },
+ {
+ "text": "What can I order for dinner that is gluten-free?"
+ }
+ ],
+ "actions": [
+ {
+ "id": "menuPlugin",
+ "file": "ai-plugin.json"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/samples/da-ristorante-api-csharp/M365Agent/appPackage/instruction.txt b/samples/da-ristorante-api-csharp/M365Agent/appPackage/instruction.txt
new file mode 100644
index 000000000..cedfb62d8
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/M365Agent/appPackage/instruction.txt
@@ -0,0 +1,48 @@
+You are an assistant specialized in helping users explore the menu of an Italian restaurant and place orders. You interact with the restaurant’s menu API and guide users through the ordering process, ensuring a smooth and delightful experience. Follow the steps below to assist users in selecting their desired dishes and completing their orders:
+
+### General Behavior:
+- Always greet the user warmly and offer assistance in exploring the menu or placing an order.
+- Use clear, concise language with a friendly tone that aligns with the atmosphere of a high-quality local Italian restaurant.
+- If the user is browsing the menu, offer suggestions based on the course they are interested in (breakfast, lunch, or dinner).
+- Ensure the conversation remains focused on helping the user find the information they need and completing the order.
+- Be proactive but never pushy. Offer suggestions and be informative, especially if the user seems uncertain.
+
+### Menu Exploration:
+- When a user requests to see the menu, use the `GET /dishes` API to retrieve the list of available dishes, optionally filtered by course (breakfast, lunch, or dinner).
+ - Example: If a user asks for breakfast options, use the `GET /dishes?course=breakfast` to return only breakfast dishes.
+- Present the dishes to the user with the following details:
+ - Name of the dish
+ - A tasty description of the dish
+ - Price in € (Euro) formatted as a decimal number with two decimal places
+ - Allergen information (if relevant)
+ - Don't include the URL.
+
+### Beverage Suggestion:
+- If the order does not already include a beverage, suggest a suitable beverage option based on the course.
+- Use the `GET /dishes?course={course}&type=drink` API to retrieve available drinks for that course.
+- Politely offer the suggestion: *"Would you like to add a beverage to your order? I recommend [beverage] for [course]."*
+
+### Placing the Order:
+- Once the user has finalized their order, use the `POST /order` API to submit the order.
+ - Ensure the request includes the correct dish names and quantities as per the user’s selection.
+ - Example API payload:
+
+ ```json
+ {
+ "dishes": [
+ {
+ "name": "frittata",
+ "quantity": 2
+ },
+ {
+ "name": "cappuccino",
+ "quantity": 1
+ }
+ ]
+ }
+ ```
+
+### Error Handling:
+- If the user selects a dish that is unavailable or provides an invalid dish name, respond gracefully and suggest alternative options.
+ - Example: *"It seems that dish is currently unavailable. How about trying [alternative dish]?"*
+- Ensure that any errors from the API are communicated politely to the user, offering to retry or explore other options.
diff --git a/samples/da-ristorante-api-csharp/M365Agent/appPackage/large.png b/samples/da-ristorante-api-csharp/M365Agent/appPackage/large.png
new file mode 100644
index 000000000..9d440f065
Binary files /dev/null and b/samples/da-ristorante-api-csharp/M365Agent/appPackage/large.png differ
diff --git a/samples/da-ristorante-api-csharp/M365Agent/appPackage/manifest.json b/samples/da-ristorante-api-csharp/M365Agent/appPackage/manifest.json
new file mode 100644
index 000000000..2680c1220
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/M365Agent/appPackage/manifest.json
@@ -0,0 +1,37 @@
+{
+ "$schema": "https://developer.microsoft.com/json-schemas/teams/vDevPreview/MicrosoftTeams.schema.json",
+ "manifestVersion": "devPreview",
+ "id": "${{TEAMS_APP_ID}}",
+ "version": "1.0.0",
+ "developer": {
+ "name": "Teams App, Inc.",
+ "websiteUrl": "https://www.example.com",
+ "privacyUrl": "https://www.example.com/privacy",
+ "termsOfUseUrl": "https://www.example.com/termsofuse"
+ },
+ "icons": {
+ "color": "color.png",
+ "outline": "outline.png"
+ },
+ "name": {
+ "short": "Il Ristorante",
+ "full": "See the today's menu and place orders"
+ },
+ "description": {
+ "short": "Order delicious Italian dishes and drinks right from the comfort of your desk.",
+ "full": "Order the most delicious Italian dishes and drinks from the comfort of your desk."
+ },
+ "accentColor": "#FFFFFF",
+ "copilotAgents": {
+ "declarativeAgents": [
+ {
+ "id": "declarativeAgent",
+ "file": "declarativeAgent.json"
+ }
+ ]
+ },
+ "permissions": [
+ "identity",
+ "messageTeamMembers"
+ ]
+}
diff --git a/samples/da-ristorante-api-csharp/M365Agent/appPackage/outline.png b/samples/da-ristorante-api-csharp/M365Agent/appPackage/outline.png
new file mode 100644
index 000000000..f7a4c8644
Binary files /dev/null and b/samples/da-ristorante-api-csharp/M365Agent/appPackage/outline.png differ
diff --git a/samples/da-ristorante-api-csharp/M365Agent/env/.env.dev b/samples/da-ristorante-api-csharp/M365Agent/env/.env.dev
new file mode 100644
index 000000000..68e8a881c
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/M365Agent/env/.env.dev
@@ -0,0 +1,16 @@
+# This file includes environment variables that will be committed to git by default.
+
+# Built-in environment variables
+TEAMSFX_ENV=dev
+APP_NAME_SUFFIX=dev
+
+# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups.
+AZURE_SUBSCRIPTION_ID=
+AZURE_RESOURCE_GROUP_NAME=
+RESOURCE_SUFFIX=
+API_FUNCTION_RESOURCE_ID=
+
+# Generated during provision, you can also add your own variables.
+TEAMS_APP_ID=
+TEAMS_APP_TENANT_ID=
+API_FUNCTION_ENDPOINT=
\ No newline at end of file
diff --git a/samples/da-ristorante-api-csharp/M365Agent/env/.env.local b/samples/da-ristorante-api-csharp/M365Agent/env/.env.local
new file mode 100644
index 000000000..6a9a1e45e
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/M365Agent/env/.env.local
@@ -0,0 +1,16 @@
+# This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment.
+
+# Built-in environment variables
+TEAMSFX_ENV=local
+APP_NAME_SUFFIX=local
+
+# Generated during provision, you can also add your own variables.
+TEAMS_APP_ID=
+TEAMS_APP_TENANT_ID=
+
+OPENAPI_SERVER_URL=
+BOT_DOMAIN=
+M365_TITLE_ID=
+M365_APP_ID=
+TEAMSFX_M365_USER_NAME=
+DEV_TUNNEL_URL=
\ No newline at end of file
diff --git a/samples/da-ristorante-api-csharp/M365Agent/infra/azure.bicep b/samples/da-ristorante-api-csharp/M365Agent/infra/azure.bicep
new file mode 100644
index 000000000..199b558f6
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/M365Agent/infra/azure.bicep
@@ -0,0 +1,56 @@
+@maxLength(20)
+@minLength(4)
+param resourceBaseName string
+param functionAppSKU string
+
+param location string = resourceGroup().location
+param serverfarmsName string = resourceBaseName
+param functionAppName string = resourceBaseName
+// Compute resources for Azure Functions
+resource serverfarms 'Microsoft.Web/serverfarms@2021-02-01' = {
+ name: serverfarmsName
+ location: location
+ sku: {
+ name: functionAppSKU // You can follow https://aka.ms/teamsfx-bicep-add-param-tutorial to add functionServerfarmsSku property to provisionParameters to override the default value "Y1".
+ }
+ properties: {}
+}
+
+// Azure Functions that hosts your function code
+resource functionApp 'Microsoft.Web/sites@2021-02-01' = {
+ name: functionAppName
+ kind: 'functionapp'
+ location: location
+ properties: {
+ serverFarmId: serverfarms.id
+ httpsOnly: true
+ siteConfig: {
+ appSettings: [
+ {
+ name: 'FUNCTIONS_EXTENSION_VERSION'
+ value: '~4' // Use Azure Functions runtime v4
+ }
+ {
+ name: 'FUNCTIONS_WORKER_RUNTIME'
+ value: 'dotnet-isolated' // Use .NET isolated process
+ }
+ {
+ name: 'WEBSITE_RUN_FROM_PACKAGE'
+ value: '1' // Run Azure Functions from a package file
+ }
+ {
+ name: 'SCM_ZIPDEPLOY_DONOT_PRESERVE_FILETIME'
+ value: '1' // Zipdeploy files will always be updated. Detail: https://aka.ms/teamsfx-zipdeploy-donot-preserve-filetime
+ }
+ ]
+ ftpsState: 'FtpsOnly'
+ }
+ }
+}
+var apiEndpoint = 'https://${functionApp.properties.defaultHostName}'
+
+
+// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details.
+output API_FUNCTION_ENDPOINT string = apiEndpoint
+output API_FUNCTION_RESOURCE_ID string = functionApp.id
+output OPENAPI_SERVER_URL string = apiEndpoint
diff --git a/samples/da-ristorante-api-csharp/M365Agent/infra/azure.parameters.json b/samples/da-ristorante-api-csharp/M365Agent/infra/azure.parameters.json
new file mode 100644
index 000000000..8e031ec56
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/M365Agent/infra/azure.parameters.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "resourceBaseName": {
+ "value": "plugin${{RESOURCE_SUFFIX}}"
+ },
+ "functionAppSKU": {
+ "value": "Y1"
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/da-ristorante-api-csharp/M365Agent/launchSettings.json b/samples/da-ristorante-api-csharp/M365Agent/launchSettings.json
new file mode 100644
index 000000000..f90cb5cc3
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/M365Agent/launchSettings.json
@@ -0,0 +1,13 @@
+{
+ "profiles": {
+ "Copilot (browser)": {
+ "commandName": "Project",
+ "launchUrl": "https://m365.cloud.microsoft/chat/entity1-d870f6cd-4aa5-4d42-9626-ab690c041429/${{AGENT_HINT}}?auth=2"
+ },
+ "Copilot (browser) (skip update app)": {
+ "commandName": "Project",
+ "environmentVariables": { "UPDATE_TEAMS_APP": "false" },
+ "launchUrl": "https://m365.cloud.microsoft/chat/entity1-d870f6cd-4aa5-4d42-9626-ab690c041429/${{AGENT_HINT}}?auth=2"
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/da-ristorante-api-csharp/M365Agent/m365agents.local.yml b/samples/da-ristorante-api-csharp/M365Agent/m365agents.local.yml
new file mode 100644
index 000000000..3fb6fe65f
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/M365Agent/m365agents.local.yml
@@ -0,0 +1,64 @@
+# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/yaml.schema.json
+# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file
+# Visit https://aka.ms/teamsfx-actions for details on actions
+version: v1.9
+
+provision:
+ # Creates a Teams app
+ - uses: teamsApp/create
+ with:
+ # Teams app name
+ name: da-ristorante-api-csharp${{APP_NAME_SUFFIX}}
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ teamsAppId: TEAMS_APP_ID
+
+ # Set OPENAPI_SERVER_URL and BOT_DOMAIN for local launch
+ - uses: script
+ with:
+ run:
+ echo "::set-teamsfx-env OPENAPI_SERVER_URL=https://${{DEV_TUNNEL_URL}}";
+ echo "::set-teamsfx-env BOT_DOMAIN=${{DEV_TUNNEL_URL}}";
+
+ # Generate runtime settings to JSON file
+ - uses: file/createOrUpdateJsonFile
+ with:
+ target: ../DaRistoranteApi/local.settings.json
+ content:
+ IsEncrypted: false
+ Values:
+ FUNCTIONS_WORKER_RUNTIME: "dotnet-isolated"
+
+ # Build Teams app package with latest env value
+ - uses: teamsApp/zipAppPackage
+ with:
+ # Path to manifest template
+ manifestPath: ./appPackage/manifest.json
+ outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ outputFolder: ./appPackage/build
+
+ # Validate app package using validation rules
+ - uses: teamsApp/validateAppPackage
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+
+ # Apply the Teams app manifest to an existing Teams app in
+ # Developer Portal.
+ # Will use the app id in manifest file to determine which Teams app to update.
+ - uses: teamsApp/update
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+
+ # Extend your Teams app to Outlook and the Microsoft 365 app
+ - uses: teamsApp/extendToM365
+ with:
+ # Relative path to the build app package.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ titleId: M365_TITLE_ID
+ appId: M365_APP_ID
diff --git a/samples/da-ristorante-api-csharp/M365Agent/m365agents.yml b/samples/da-ristorante-api-csharp/M365Agent/m365agents.yml
new file mode 100644
index 000000000..d18b3fa43
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/M365Agent/m365agents.yml
@@ -0,0 +1,97 @@
+# yaml-language-server: $schema=https://aka.ms/m365-agents-toolkits/v1.9/yaml.schema.json
+# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file
+# Visit https://aka.ms/teamsfx-actions for details on actions
+version: v1.9
+
+environmentFolderPath: ./env
+
+# Triggered when 'teamsapp provision' is executed
+provision:
+ # Creates a Teams app
+ - uses: teamsApp/create
+ with:
+ # Teams app name
+ name: da-ristorante-api-csharp${{APP_NAME_SUFFIX}}
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ teamsAppId: TEAMS_APP_ID
+
+ - uses: arm/deploy # Deploy given ARM templates parallelly.
+ with:
+ # AZURE_SUBSCRIPTION_ID is a built-in environment variable,
+ # if its value is empty, TeamsFx will prompt you to select a subscription.
+ # Referencing other environment variables with empty values
+ # will skip the subscription selection prompt.
+ subscriptionId: ${{AZURE_SUBSCRIPTION_ID}}
+ # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable,
+ # if its value is empty, TeamsFx will prompt you to select or create one
+ # resource group.
+ # Referencing other environment variables with empty values
+ # will skip the resource group selection prompt.
+ resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}}
+ templates:
+ - path: ./infra/azure.bicep # Relative path to this file
+ # Relative path to this yaml file.
+ # Placeholders will be replaced with corresponding environment
+ # variable before ARM deployment.
+ parameters: ./infra/azure.parameters.json
+ # Required when deploying ARM template
+ deploymentName: Create-resources-for-api-plugin
+ # Microsoft 365 Agents Toolkit will download this bicep CLI version from github for you,
+ # will use bicep CLI in PATH if you remove this config.
+ bicepCliVersion: v0.9.1
+
+ # Build Teams app package with latest env value
+ - uses: teamsApp/zipAppPackage
+ with:
+ # Path to manifest template
+ manifestPath: ./appPackage/manifest.json
+ outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ outputFolder: ./appPackage/build
+
+ # Validate app package using validation rules
+ - uses: teamsApp/validateAppPackage
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+
+ # Apply the Teams app manifest to an existing Teams app in
+ # Developer Portal.
+ # Will use the app id in manifest file to determine which Teams app to update.
+ - uses: teamsApp/update
+ with:
+ # Relative path to this file. This is the path for built zip file.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+
+ # Extend your Teams app to Outlook and the Microsoft 365 app
+ - uses: teamsApp/extendToM365
+ with:
+ # Relative path to the build app package.
+ appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+ # Write the information of created resources into environment file for
+ # the specified environment variable(s).
+ writeToEnvironmentFile:
+ titleId: M365_TITLE_ID
+ appId: M365_APP_ID
+
+# Triggered when 'teamsapp deploy' is executed
+deploy:
+ - uses: cli/runDotnetCommand
+ with:
+ args: publish --configuration Release --runtime win-x86 --self-contained
+ DaRistoranteApi.csproj
+ workingDirectory: ../DaRistoranteApi
+
+ # Deploy your application to Azure Functions using the zip deploy feature.
+ # For additional details, see at https://aka.ms/zip-deploy-to-azure-functions
+ - uses: azureFunctions/zipDeploy
+ with:
+ # deploy base folder
+ artifactFolder: bin/Release/net10.0/win-x86/publish
+ # The resource id of the cloud resource to be deployed to.
+ # This key will be generated by arm/deploy action automatically.
+ # You can replace it with your existing Azure Resource id
+ # or add it to your environment variable file.
+ resourceId: ${{API_FUNCTION_RESOURCE_ID}}
+ workingDirectory: ../DaRistoranteApi
diff --git a/samples/da-ristorante-api-csharp/README.md b/samples/da-ristorante-api-csharp/README.md
new file mode 100644
index 000000000..c3def5b71
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/README.md
@@ -0,0 +1,71 @@
+# Browse the menu and place an order at a local Italian restaurant using Microsoft 365 Copilot (C#)
+
+## Summary
+
+This sample demonstrates how to build a declarative agent for Microsoft 365 Copilot that allows you to browse a menu of a local Italian restaurant and place an order. The agent uses an API plugin to connect to an anonymous API. The project contains a C# Azure Function (.NET 10) that serves as the API.
+
+This is the C# port of the [da-ristorante-api](https://github.com/pnp/copilot-pro-dev-samples/tree/main/samples/da-ristorante-api) TypeScript sample.
+
+
+
+
+## Features
+
+This sample illustrates the following concepts:
+
+* Building a declarative agent for Microsoft 365 Copilot with an API plugin
+* Connecting an API plugin to an anonymous API
+* Using C# Azure Functions (.NET 10 isolated worker) as the API backend
+* Using [dev tunnels](https://learn.microsoft.com/azure/developer/dev-tunnels/overview) to test the API plugin locally
+
+## Contributors
+
+* [Yugal Pradhan](https://github.com/YugalPradhan31)
+
+## Version history
+
+Version|Date|Comments
+-------|----|--------
+1.0|May 5, 2026|Initial release
+
+## Prerequisites
+
+* Microsoft 365 tenant with Microsoft 365 Copilot
+* [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
+* [Azure Functions Core Tools v4](https://learn.microsoft.com/azure/azure-functions/functions-run-local#install-the-azure-functions-core-tools)
+* [Visual Studio 2022](https://aka.ms/vs) 17.11 or higher
+* [Microsoft 365 Agents Toolkit for Visual Studio](https://aka.ms/install-teams-toolkit-vs)
+
+## Minimal Path to Awesome
+
+* Clone this repository (or [download this solution as a .ZIP file](https://pnp.github.io/download-partial/?url=https://github.com/pnp/copilot-pro-dev-samples/tree/main/samples/da-ristorante-api-csharp) then unzip it)
+ ```bash
+ git clone https://github.com/pnp/copilot-pro-dev-samples.git
+ cd copilot-pro-dev-samples/samples/da-ristorante-api-csharp
+ ```
+* Open **DaRistoranteApi.slnx** in Visual Studio 2022
+* In the debug dropdown menu, select **Dev Tunnels > Create a Tunnel** (set authentication type to Public) or select an existing public dev tunnel
+* Right-click the **M365Agent** project in Solution Explorer and select **Microsoft 365 Agents Toolkit > Select Microsoft 365 Account**
+* Sign in to Microsoft 365 Agents Toolkit with a **Microsoft 365 work or school account**
+* Press **F5**, or select **Debug > Start Debugging** in Visual Studio to start your app
+* When the browser launches, open the **Copilot** app, select the agent, and start asking questions like:
+ - "What's for lunch today?"
+ - "What can I order for dinner that is gluten-free?"
+
+> **Note:** Please make sure to switch to New Teams when Teams web client has launched.
+
+## Help
+
+We do not support samples, but this community is always willing to help, and we want to improve these samples. We use GitHub to track issues, which makes it easy for community members to volunteer their time and help resolve issues.
+
+You can try looking at [issues related to this sample](https://github.com/pnp/copilot-pro-dev-samples/issues?q=label%3A%22sample%3A%20da-ristorante-api-csharp%22) to see if anybody else is having the same issues.
+
+If you encounter any issues using this sample, [create a new issue](https://github.com/pnp/copilot-pro-dev-samples/issues/new).
+
+Finally, if you have an idea for improvement, [make a suggestion](https://github.com/pnp/copilot-pro-dev-samples/issues/new).
+
+## Disclaimer
+
+**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
+
+
diff --git a/samples/da-ristorante-api-csharp/assets/cappuccino.jpeg b/samples/da-ristorante-api-csharp/assets/cappuccino.jpeg
new file mode 100644
index 000000000..07252d232
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/cappuccino.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/caprese_salad.jpeg b/samples/da-ristorante-api-csharp/assets/caprese_salad.jpeg
new file mode 100644
index 000000000..e7094ac2a
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/caprese_salad.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/chianti_classico.jpeg b/samples/da-ristorante-api-csharp/assets/chianti_classico.jpeg
new file mode 100644
index 000000000..45eacd2ea
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/chianti_classico.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/chicken_panini.jpeg b/samples/da-ristorante-api-csharp/assets/chicken_panini.jpeg
new file mode 100644
index 000000000..23c1bf674
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/chicken_panini.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/espresso.jpeg b/samples/da-ristorante-api-csharp/assets/espresso.jpeg
new file mode 100644
index 000000000..2f9fa42b1
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/espresso.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/frittata.jpeg b/samples/da-ristorante-api-csharp/assets/frittata.jpeg
new file mode 100644
index 000000000..f74693d61
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/frittata.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/iced_tea.jpeg b/samples/da-ristorante-api-csharp/assets/iced_tea.jpeg
new file mode 100644
index 000000000..7264cf196
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/iced_tea.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/orange_juice.jpeg b/samples/da-ristorante-api-csharp/assets/orange_juice.jpeg
new file mode 100644
index 000000000..acce7b6fe
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/orange_juice.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/prosciutto_melon.jpeg b/samples/da-ristorante-api-csharp/assets/prosciutto_melon.jpeg
new file mode 100644
index 000000000..93059e3d7
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/prosciutto_melon.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/ricotta_pancakes.jpeg b/samples/da-ristorante-api-csharp/assets/ricotta_pancakes.jpeg
new file mode 100644
index 000000000..55b351140
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/ricotta_pancakes.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/sample.json b/samples/da-ristorante-api-csharp/assets/sample.json
new file mode 100644
index 000000000..4abb235f3
--- /dev/null
+++ b/samples/da-ristorante-api-csharp/assets/sample.json
@@ -0,0 +1,74 @@
+[
+ {
+ "name": "pnp-copilot-pro-dev-da-ristorante-api-csharp",
+ "source": "pnp",
+ "title": "Browse the menu and place an order at a local Italian restaurant using Microsoft 365 Copilot (C#)",
+ "shortDescription": "This sample demonstrates how to build a declarative agent for Microsoft 365 Copilot that allows you to browse a menu of a local Italian restaurant and place an order. The agent uses an API plugin to connect to an anonymous API built with C# Azure Functions (.NET 10).",
+ "url": "https://github.com/pnp/copilot-pro-dev-samples/tree/main/samples/da-ristorante-api-csharp",
+ "downloadUrl": "https://pnp.github.io/download-partial/?url=https://github.com/pnp/copilot-pro-dev-samples/tree/main/samples/da-ristorante-api-csharp",
+ "longDescription": [
+ "This sample demonstrates how to build a declarative agent for Microsoft 365 Copilot that allows you to browse a menu of a local Italian restaurant and place an order. The agent uses an API plugin to connect to an anonymous API. The project contains a C# Azure Function (.NET 10) that serves as the API."
+ ],
+ "creationDateTime": "2026-05-05",
+ "updateDateTime": "2026-05-05",
+ "products": [
+ "Microsoft 365 Copilot"
+ ],
+ "metadata": [
+ {
+ "key": "PLATFORM",
+ "value": ".NET"
+ },
+ {
+ "key": "LANGUAGE",
+ "value": "C#"
+ },
+ {
+ "key": "API-PLUGIN",
+ "value": "Yes"
+ },
+ {
+ "key": "GRAPH-CONNECTOR",
+ "value": "No"
+ }
+ ],
+ "thumbnails": [
+ {
+ "type": "image",
+ "order": 100,
+ "url": "https://github.com/pnp/copilot-pro-dev-samples/raw/main/samples/da-ristorante-api-csharp/assets/screenshot-menu.png",
+ "alt": "Declarative agent showing what's on the menu for lunch"
+ },
+ {
+ "type": "image",
+ "order": 100,
+ "url": "https://github.com/pnp/copilot-pro-dev-samples/raw/main/samples/da-ristorante-api-csharp/assets/screenshot-order.png",
+ "alt": "Declarative agent ordering lunch"
+ }
+ ],
+ "authors": [
+ {
+ "gitHubAccount": "YugalPradhan31",
+ "pictureUrl": "https://github.com/YugalPradhan31.png",
+ "name": "Yugal Pradhan"
+ }
+ ],
+ "references": [
+ {
+ "name": "Microsoft 365 Copilot extensibility",
+ "description": "Learn more about what Microsoft 365 Copilot and how you can extend it.",
+ "url": "https://learn.microsoft.com/microsoft-365-copilot/extensibility/"
+ },
+ {
+ "name": "Declarative agents for Microsoft 365 Copilot overview",
+ "description": "Learn more about what declarative agents for Microsoft 365 Copilot are.",
+ "url": "https://learn.microsoft.com/microsoft-365-copilot/extensibility/overview-declarative-agent"
+ },
+ {
+ "name": "Build a declarative agent for Microsoft 365 Copilot",
+ "description": "Learn how to build a declarative agent for Microsoft 365 Copilot.",
+ "url": "https://learn.microsoft.com/microsoft-365-copilot/extensibility/build-declarative-agents"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/samples/da-ristorante-api-csharp/assets/screenshot-menu.png b/samples/da-ristorante-api-csharp/assets/screenshot-menu.png
new file mode 100644
index 000000000..b8d0c1b49
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/screenshot-menu.png differ
diff --git a/samples/da-ristorante-api-csharp/assets/screenshot-order.png b/samples/da-ristorante-api-csharp/assets/screenshot-order.png
new file mode 100644
index 000000000..e06a4afab
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/screenshot-order.png differ
diff --git a/samples/da-ristorante-api-csharp/assets/seafood_linguine.jpeg b/samples/da-ristorante-api-csharp/assets/seafood_linguine.jpeg
new file mode 100644
index 000000000..ae692845f
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/seafood_linguine.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/spaghetti_carbonara.jpeg b/samples/da-ristorante-api-csharp/assets/spaghetti_carbonara.jpeg
new file mode 100644
index 000000000..eb076c1c3
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/spaghetti_carbonara.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/sparkling_water.jpeg b/samples/da-ristorante-api-csharp/assets/sparkling_water.jpeg
new file mode 100644
index 000000000..1bcf30691
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/sparkling_water.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/tiramisu.jpeg b/samples/da-ristorante-api-csharp/assets/tiramisu.jpeg
new file mode 100644
index 000000000..69521ab0c
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/tiramisu.jpeg differ
diff --git a/samples/da-ristorante-api-csharp/assets/veal_osso_buco.jpeg b/samples/da-ristorante-api-csharp/assets/veal_osso_buco.jpeg
new file mode 100644
index 000000000..28ce2faa1
Binary files /dev/null and b/samples/da-ristorante-api-csharp/assets/veal_osso_buco.jpeg differ