diff --git a/aspire-orchestrator/Aspire.AppHost/Aspire.AppHost.csproj b/aspire-orchestrator/Aspire.AppHost/Aspire.AppHost.csproj
index 009dc84e..2f3d610b 100644
--- a/aspire-orchestrator/Aspire.AppHost/Aspire.AppHost.csproj
+++ b/aspire-orchestrator/Aspire.AppHost/Aspire.AppHost.csproj
@@ -20,8 +20,9 @@
-
+
+
+
diff --git a/aspire-orchestrator/Aspire.AppHost/Program.cs b/aspire-orchestrator/Aspire.AppHost/Program.cs
index 155a8916..f7a8cae3 100644
--- a/aspire-orchestrator/Aspire.AppHost/Program.cs
+++ b/aspire-orchestrator/Aspire.AppHost/Program.cs
@@ -1,39 +1,117 @@
// Copyright (c) Microsoft. All rights reserved.
-var builder = DistributedApplication.CreateBuilder(args);
-
-var authority = builder.AddParameterFromConfiguration("authority", "EntraId:Authority");
-var clientId = builder.AddParameterFromConfiguration("clientId", "EntraId:ClientId");
-
-// Workbench backend
-var workbenchService = builder.AddWorkbenchService("workbenchservice", projectDirectory: Path.Combine("..", "..", "workbench-service"), clientId: clientId);
-var workbenchServiceEndpoint = workbenchService.GetSemanticWorkbenchEndpoint(builder.ExecutionContext.IsPublishMode);
-
-// Workbench frontend
-var workbenchApp = builder.AddViteApp("workbenchapp", workingDirectory: Path.Combine("..", "..", "workbench-app"), packageManager: "pnpm")
- .WithPnpmPackageInstallation()
- .WithEnvironment(name: "VITE_SEMANTIC_WORKBENCH_SERVICE_URL", workbenchServiceEndpoint)
- .WaitFor(workbenchService)
- .PublishAsDockerFile([
- new DockerBuildArg("VITE_SEMANTIC_WORKBENCH_CLIENT_ID", clientId.Resource.Value),
- new DockerBuildArg("VITE_SEMANTIC_WORKBENCH_AUTHORITY", authority.Resource.Value),
- ]);
-
-// Sample Python agent
-builder.AddAssistantApp("skill-assistant", projectDirectory: Path.Combine("..", "..", "assistants", "skill-assistant"), assistantModuleName: "skill-assistant")
- .WithEnvironment(name: "assistant__workbench_service_url", workbenchServiceEndpoint);
-
-// Sample .NET agent
-var simpleChatBot = builder.AddProject("simple-chatbot-dotnet")
- .WithHttpEndpoint()
- .WaitFor(workbenchService)
- .WithEnvironment(name: "Workbench__WorkbenchEndpoint", workbenchServiceEndpoint);
-
-simpleChatBot.WithEnvironment("Workbench__ConnectorHost", $"{simpleChatBot.GetEndpoint("http")}");
-
-if (!builder.ExecutionContext.IsPublishMode)
+using Aspire.Hosting.Extensions;
+using Projects;
+
+internal static class Program
{
- workbenchApp.WithHttpsEndpoint(env: "PORT");
-}
+ public static void Main(string[] args)
+ {
+ var builder = DistributedApplication.CreateBuilder(args);
+
+ builder
+ .AddSemanticWorkbench(out IResourceBuilder workbenchBackend, out EndpointReference workbenchEndpoint)
+ .AddPythonAssistant("skill-assistant", workbenchEndpoint)
+ .AddDotnetExample("simple-chatbot-dotnet", workbenchBackend, workbenchEndpoint);
+
+ // When running locally
+ if (!builder.ExecutionContext.IsPublishMode)
+ {
+ builder
+ .AddPythonExample("python-01-echo-bot", workbenchEndpoint)
+ .AddPythonExample("python-02-simple-chatbot", workbenchEndpoint)
+ .AddPythonExample("python-03-multimodel-chatbot", workbenchEndpoint)
+ .AddPythonAssistant("explorer-assistant", workbenchEndpoint)
+ .AddPythonAssistant("guided-conversation-assistant", workbenchEndpoint)
+ .AddPythonAssistant("prospector-assistant", workbenchEndpoint)
+ .AddDotnetExample("echo-bot-dotnet", workbenchBackend, workbenchEndpoint)
+ .AddDotnetExample("sw-tutorial-bot-dotnet", workbenchBackend, workbenchEndpoint);
+ }
+
+ builder
+ .ShowDashboardUrl(true)
+ .LaunchDashboard(delay: 5000)
+ .Build()
+ .Run();
+ }
+
+ ///
+ /// Add the workbench frontend and backend components
+ ///
+ private static IDistributedApplicationBuilder AddSemanticWorkbench(this IDistributedApplicationBuilder builder,
+ out IResourceBuilder workbenchBackend, out EndpointReference workbenchServiceEndpoint)
+ {
+ var authority = builder.AddParameterFromConfiguration("authority", "EntraId:Authority");
+ var clientId = builder.AddParameterFromConfiguration("clientId", "EntraId:ClientId");
+
+ // Workbench backend
+ workbenchBackend = builder.AddWorkbenchService(
+ name: "workbenchservice",
+ projectDirectory: Path.Combine("..", "..", "workbench-service"),
+ clientId: clientId);
+
+ workbenchServiceEndpoint = workbenchBackend.GetSemanticWorkbenchEndpoint(builder.ExecutionContext.IsPublishMode);
+
+ // Workbench frontend
+ var workbenchApp = builder.AddViteApp(
+ name: "workbenchapp",
+ workingDirectory: Path.Combine("..", "..", "workbench-app"),
+ packageManager: "pnpm")
+ .WithPnpmPackageInstallation()
+ .WithEnvironment(name: "VITE_SEMANTIC_WORKBENCH_SERVICE_URL", workbenchServiceEndpoint)
+ .WaitFor(workbenchBackend)
+ .PublishAsDockerFile([
+ new DockerBuildArg("VITE_SEMANTIC_WORKBENCH_CLIENT_ID", clientId.Resource.Value),
+ new DockerBuildArg("VITE_SEMANTIC_WORKBENCH_AUTHORITY", authority.Resource.Value),
+ ]);
-builder.Build().Run();
+ // When running locally
+ if (!builder.ExecutionContext.IsPublishMode)
+ {
+ workbenchApp.WithHttpsEndpoint(env: "PORT");
+ }
+
+ return builder;
+ }
+
+ private static IDistributedApplicationBuilder AddPythonAssistant(this IDistributedApplicationBuilder builder,
+ string name, EndpointReference workbenchServiceEndpoint)
+ {
+ builder
+ .AddAssistantUvPythonApp(
+ name: name,
+ projectDirectory: Path.Combine("..", "..", "assistants", name),
+ assistantModuleName: name)
+ .WithEnvironment(name: "assistant__workbench_service_url", workbenchServiceEndpoint);
+
+ return builder;
+ }
+
+ private static IDistributedApplicationBuilder AddPythonExample(this IDistributedApplicationBuilder builder,
+ string name, EndpointReference workbenchServiceEndpoint)
+ {
+ builder
+ .AddAssistantUvPythonApp(
+ name: name,
+ projectDirectory: Path.Combine("..", "..", "examples", "python", name),
+ assistantModuleName: "assistant")
+ .WithEnvironment(name: "assistant__workbench_service_url", workbenchServiceEndpoint);
+
+ return builder;
+ }
+
+ // .NET Agent Example 1
+ private static IDistributedApplicationBuilder AddDotnetExample(this IDistributedApplicationBuilder builder,
+ string name, IResourceBuilder workbenchBackend, EndpointReference workbenchServiceEndpoint) where T : IProjectMetadata, new()
+ {
+ var agent = builder
+ .AddProject(name: name)
+ .WithHttpEndpoint()
+ .WaitFor(workbenchBackend)
+ .WithEnvironment(name: "Workbench__WorkbenchEndpoint", workbenchServiceEndpoint);
+
+ agent.WithEnvironment("Workbench__ConnectorHost", $"{agent.GetEndpoint("http")}");
+
+ return builder;
+ }
+}
diff --git a/aspire-orchestrator/Aspire.Extensions/Dashboard.cs b/aspire-orchestrator/Aspire.Extensions/Dashboard.cs
new file mode 100644
index 00000000..6c63012a
--- /dev/null
+++ b/aspire-orchestrator/Aspire.Extensions/Dashboard.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics;
+
+namespace Aspire.Hosting.Extensions;
+
+public static class Dashboard
+{
+ ///
+ /// Show Aspire dashboard URL before the logging start.
+ ///
+ public static IDistributedApplicationBuilder ShowDashboardUrl(this IDistributedApplicationBuilder builder, bool withStyle = false)
+ {
+ Console.WriteLine(withStyle
+ ? $"\u001b[1mAspire dashboard URL: {GetUrl(builder)}\u001b[0m\n\n"
+ : $"Aspire dashboard URL: {GetUrl(builder)}\n\n");
+ return builder;
+ }
+
+ ///
+ /// Wait 5 seconds and automatically open the browser (when using 'dotnet run' the browser doesn't open)
+ ///
+ public static IDistributedApplicationBuilder LaunchDashboard(this IDistributedApplicationBuilder builder, int delay = 5000)
+ {
+ Task.Run(async () =>
+ {
+ await Task.Delay(delay).ConfigureAwait(false);
+ Process.Start(new ProcessStartInfo { FileName = GetUrl(builder), UseShellExecute = true });
+ });
+
+ return builder;
+ }
+
+ private static string GetUrl(IDistributedApplicationBuilder builder)
+ {
+ string token = builder.Configuration["AppHost:BrowserToken"] ?? string.Empty;
+ string url = builder.Configuration["ASPNETCORE_URLS"]?.Split(";")[0] ?? throw new ArgumentException("ASPNETCORE_URLS is empty");
+ return $"{url}/login?t={token}";
+ }
+}
diff --git a/aspire-orchestrator/Aspire.Extensions/UvAppHostingExtensions.cs b/aspire-orchestrator/Aspire.Extensions/UvAppHostingExtensions.cs
index 2dcbdd32..994b3e12 100644
--- a/aspire-orchestrator/Aspire.Extensions/UvAppHostingExtensions.cs
+++ b/aspire-orchestrator/Aspire.Extensions/UvAppHostingExtensions.cs
@@ -22,7 +22,7 @@ public static IResourceBuilder AddUvApp(
private static IResourceBuilder AddUvApp(this IDistributedApplicationBuilder builder,
string name,
string scriptPath,
- string projectDirectory,
+ string? projectDirectory,
string virtualEnvironmentPath,
params string[] args)
{
@@ -32,7 +32,7 @@ private static IResourceBuilder AddUvApp(this IDistributedApplica
string wd = projectDirectory ?? Path.Combine("..", name);
- projectDirectory = PathNormalizer.NormalizePathForCurrentPlatform(Path.Combine(builder.AppHostDirectory, wd));
+ projectDirectory = Path.Combine(builder.AppHostDirectory, wd).NormalizePathForCurrentPlatform();
var virtualEnvironment = new VirtualEnvironment(Path.IsPathRooted(virtualEnvironmentPath)
? virtualEnvironmentPath
@@ -43,8 +43,8 @@ private static IResourceBuilder AddUvApp(this IDistributedApplica
// var projectExecutable = instrumentationExecutable ?? pythonExecutable;
string[] allArgs = args is { Length: > 0 }
- ? ["run", scriptPath, .. args]
- : ["run", scriptPath];
+ ? ["run", "--frozen", scriptPath, .. args]
+ : ["run", "--frozen", scriptPath];
var projectResource = new UvAppResource(name, projectDirectory);
diff --git a/aspire-orchestrator/Aspire.Extensions/WorkbenchServiceHostingExtensions.cs b/aspire-orchestrator/Aspire.Extensions/WorkbenchServiceHostingExtensions.cs
index b34dca97..ff2e69be 100644
--- a/aspire-orchestrator/Aspire.Extensions/WorkbenchServiceHostingExtensions.cs
+++ b/aspire-orchestrator/Aspire.Extensions/WorkbenchServiceHostingExtensions.cs
@@ -13,18 +13,22 @@ public static IResourceBuilder AddWorkbenchService(
{
ArgumentNullException.ThrowIfNull(builder);
- var workbenchService = builder.AddUvApp(name, projectDirectory, "start-semantic-workbench-service", scriptArgs)
+ var workbenchService = builder
+ .AddUvApp(name, projectDirectory, "start-semantic-workbench-service", scriptArgs)
.PublishAsDockerImage(dockerContext: Path.Combine("..", ".."),
dockerFilePath: Path.Combine("workbench-service", "Dockerfile"),
configure: new(configure => configure
.WithBuildArg("SSHD_ENABLED", "false")))
.WithEnvironment(name: "WORKBENCH__AUTH__ALLOWED_APP_ID", clientId.Resource.Value);
+
if (builder.ExecutionContext.IsPublishMode)
{
+ // When running on Azure
workbenchService.WithHttpsEndpoint(port: 3000);
}
else
{
+ // When running locally
workbenchService.WithHttpEndpoint(env: "PORT");
}
@@ -40,7 +44,7 @@ public static EndpointReference GetSemanticWorkbenchEndpoint(this IResourceBuild
return workbenchService.GetEndpoint(isPublishMode ? "https" : "http");
}
- public static IResourceBuilder AddAssistantApp(
+ public static IResourceBuilder AddAssistantUvPythonApp(
this IDistributedApplicationBuilder builder,
string name,
string projectDirectory,
@@ -48,19 +52,23 @@ public static IResourceBuilder AddAssistantApp(
{
ArgumentNullException.ThrowIfNull(builder);
- var assistant = builder.AddUvApp(name, projectDirectory, "start-assistant")
+ var assistant = builder
+ .AddUvApp(name, projectDirectory, "start-assistant")
.PublishAsDockerImage(dockerContext: Path.Combine("..", ".."),
dockerFilePath: Path.Combine("tools", "docker", "Dockerfile.assistant"),
configure: new(configure => configure
.WithBuildArg("package", assistantModuleName)
.WithBuildArg("app", $"assistant.{assistantModuleName.Replace('-', '_')}:app")
));
+
if (builder.ExecutionContext.IsPublishMode)
{
+ // When running on Azure
assistant.WithHttpEndpoint(port: 3001, env: "ASSISTANT__PORT");
}
else
{
+ // When running locally
assistant.WithHttpEndpoint(env: "ASSISTANT__PORT");
}
diff --git a/aspire-orchestrator/SemanticWorkbench.Aspire.sln b/aspire-orchestrator/SemanticWorkbench.Aspire.sln
index e7a8f5be..1c7a4c2c 100644
--- a/aspire-orchestrator/SemanticWorkbench.Aspire.sln
+++ b/aspire-orchestrator/SemanticWorkbench.Aspire.sln
@@ -10,6 +10,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-03-simple-chatbot",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Extensions", "Aspire.Extensions\Aspire.Extensions.csproj", "{773468A0-6628-4D14-8EE8-7C3F790B6192}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-01-echo-bot", "..\examples\dotnet\dotnet-01-echo-bot\dotnet-01-echo-bot.csproj", "{9AB1A55C-A7D7-45E4-9C4C-D3F5BBD78D64}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "dotnet-02-message-types-demo", "..\examples\dotnet\dotnet-02-message-types-demo\dotnet-02-message-types-demo.csproj", "{B604ADC1-7FF3-449A-9BE7-BC553A810ADE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -32,6 +36,14 @@ Global
{773468A0-6628-4D14-8EE8-7C3F790B6192}.Debug|Any CPU.Build.0 = Debug|Any CPU
{773468A0-6628-4D14-8EE8-7C3F790B6192}.Release|Any CPU.ActiveCfg = Release|Any CPU
{773468A0-6628-4D14-8EE8-7C3F790B6192}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9AB1A55C-A7D7-45E4-9C4C-D3F5BBD78D64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9AB1A55C-A7D7-45E4-9C4C-D3F5BBD78D64}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9AB1A55C-A7D7-45E4-9C4C-D3F5BBD78D64}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9AB1A55C-A7D7-45E4-9C4C-D3F5BBD78D64}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B604ADC1-7FF3-449A-9BE7-BC553A810ADE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B604ADC1-7FF3-449A-9BE7-BC553A810ADE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B604ADC1-7FF3-449A-9BE7-BC553A810ADE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B604ADC1-7FF3-449A-9BE7-BC553A810ADE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/aspire-orchestrator/SemanticWorkbench.Aspire.sln.DotSettings b/aspire-orchestrator/SemanticWorkbench.Aspire.sln.DotSettings
index e0cd1b82..622fbe8f 100644
--- a/aspire-orchestrator/SemanticWorkbench.Aspire.sln.DotSettings
+++ b/aspire-orchestrator/SemanticWorkbench.Aspire.sln.DotSettings
@@ -88,6 +88,7 @@
512
True
Copyright (c) Microsoft. All rights reserved.
+ ABC
ACS
AI
AIGPT
@@ -100,6 +101,7 @@
GPT
GRPC
HMAC
+ HTML
HTTP
IM
IO
@@ -267,6 +269,7 @@ public void It$SOMENAME$()
True
True
True
+ True
True
True
True
diff --git a/aspire-orchestrator/run.sh b/aspire-orchestrator/run.sh
index 3a178add..4c62d8e7 100755
--- a/aspire-orchestrator/run.sh
+++ b/aspire-orchestrator/run.sh
@@ -35,4 +35,4 @@ fi
cd Aspire.AppHost
-dotnet run
\ No newline at end of file
+dotnet run --launch-profile https
diff --git a/examples/dotnet/dotnet-02-message-types-demo/Program.cs b/examples/dotnet/dotnet-02-message-types-demo/Program.cs
index e8cd8ff7..5b15a696 100644
--- a/examples/dotnet/dotnet-02-message-types-demo/Program.cs
+++ b/examples/dotnet/dotnet-02-message-types-demo/Program.cs
@@ -54,7 +54,7 @@ private static IServiceCollection AddAzureAIContentSafety(
IConfiguration config)
{
var authType = config.GetValue("AuthType");
- var endpoint = new Uri(config.GetValue("Endpoint")!);
+ var endpoint = new Uri(config.GetValue("Endpoint")!) ?? throw new ArgumentException("Failed to set Azure AI Content Safety Endpoint");
var apiKey = config.GetValue("ApiKey");
return services.AddSingleton(_ => authType == "AzureIdentity"
diff --git a/examples/dotnet/dotnet-02-message-types-demo/dotnet-02-message-types-demo.csproj b/examples/dotnet/dotnet-02-message-types-demo/dotnet-02-message-types-demo.csproj
index b59c5d36..3bbf2cce 100644
--- a/examples/dotnet/dotnet-02-message-types-demo/dotnet-02-message-types-demo.csproj
+++ b/examples/dotnet/dotnet-02-message-types-demo/dotnet-02-message-types-demo.csproj
@@ -6,7 +6,7 @@
enable
AgentExample
AgentExample
- $(NoWarn);CA1308;CA1861;CA1515;IDE0290;
+ $(NoWarn);CA1308;CA1861;CA1515;IDE0290;CA1508;
diff --git a/examples/dotnet/dotnet-03-simple-chatbot/Program.cs b/examples/dotnet/dotnet-03-simple-chatbot/Program.cs
index 3cbc9a53..c6eb9acb 100644
--- a/examples/dotnet/dotnet-03-simple-chatbot/Program.cs
+++ b/examples/dotnet/dotnet-03-simple-chatbot/Program.cs
@@ -48,7 +48,7 @@ private static IServiceCollection AddAzureAIContentSafety(
IConfiguration config)
{
var authType = config.GetValue("AuthType");
- var endpoint = new Uri(config.GetValue("Endpoint")!);
+ var endpoint = new Uri(config.GetValue("Endpoint")!) ?? throw new ArgumentException("Failed to set Azure AI Content Safety Endpoint");
var apiKey = config.GetValue("ApiKey");
return services.AddSingleton(_ => authType == "AzureIdentity"
diff --git a/examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj b/examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj
index 3d195e7a..f30ae90c 100644
--- a/examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj
+++ b/examples/dotnet/dotnet-03-simple-chatbot/dotnet-03-simple-chatbot.csproj
@@ -6,7 +6,7 @@
enable
AgentExample
AgentExample
- $(NoWarn);SKEXP0010;CA1861;CA1515;IDE0290;CA1031;CA1812;
+ $(NoWarn);SKEXP0010;CA1861;CA1515;IDE0290;CA1031;CA1812;CA1508;
diff --git a/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.cs b/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.cs
index 6340ec98..c51c6027 100644
--- a/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.cs
+++ b/libraries/dotnet/WorkbenchConnector/WorkbenchConnector.cs
@@ -439,8 +439,12 @@ public virtual void Init()
return;
}
- // Example: http://[::]:64351
- string first = feat.Addresses.First().Replace("[::]", "host", StringComparison.OrdinalIgnoreCase);
+ // Example: http://[::]:64351 - Prefer non-HTTPS to avoid cert validation errors
+ string first = feat.Addresses.Any(x => !x.Contains("https:", StringComparison.OrdinalIgnoreCase))
+ ? feat.Addresses.First(x => !x.Contains("https:", StringComparison.OrdinalIgnoreCase)).Replace("[::]", "host", StringComparison.OrdinalIgnoreCase)
+ : feat.Addresses.First().Replace("[::]", "host", StringComparison.OrdinalIgnoreCase);
+
+ this.Log.LogTrace("Address: {Address}", first);
Uri uri = new(first);
this.ConnectorEndpoint = uri.Port > 0
? $"{uri.Scheme}://127.0.0.1:{uri.Port}/{this.WorkbenchConfig.ConnectorApiPrefix.TrimStart('/')}"
@@ -473,7 +477,7 @@ public virtual async Task PingSemanticWorkbenchBackendAsync(CancellationToken ca
name = $"{this.WorkbenchConfig.ConnectorName} [{this.WorkbenchConfig.ConnectorId}]",
description = this.WorkbenchConfig.ConnectorDescription,
url = this.ConnectorEndpoint,
- online_expires_in_seconds = 2 + (int)(PingFrequencyMsecs / 1000)
+ online_expires_in_seconds = 1 + (2 * (int)(PingFrequencyMsecs / 1000))
};
await this.SendAsync(HttpMethod.Put, path, data, null, "PingSWBackend", cancellationToken).ConfigureAwait(false);
@@ -486,7 +490,7 @@ public virtual async Task PingSemanticWorkbenchBackendAsync(CancellationToken ca
#region internals ===========================================================================
- private const int PingFrequencyMsecs = 20000;
+ private const int PingFrequencyMsecs = 15000;
public void Dispose()
{
diff --git a/tools/run-dotnet-example1.sh b/tools/run-dotnet-example1.sh
deleted file mode 100755
index c5e45caa..00000000
--- a/tools/run-dotnet-example1.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-ROOT="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && cd .. && pwd)"
-cd $ROOT
-# ================================================================
-
-cd examples/dotnet/dotnet-01-echo-bot
-
-dotnet build
-dotnet run
diff --git a/tools/run-dotnet-example2.sh b/tools/run-dotnet-example2.sh
deleted file mode 100755
index bda2be45..00000000
--- a/tools/run-dotnet-example2.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-ROOT="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && cd .. && pwd)"
-cd $ROOT
-# ================================================================
-
-cd examples/dotnet/dotnet-02-message-types-demo
-
-dotnet build
-dotnet run
diff --git a/tools/run-dotnet-example3.sh b/tools/run-dotnet-example3.sh
deleted file mode 100755
index 0d677560..00000000
--- a/tools/run-dotnet-example3.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-ROOT="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && cd .. && pwd)"
-cd $ROOT
-# ================================================================
-
-cd examples/dotnet/dotnet-03-simple-chatbot
-
-dotnet build
-dotnet run
diff --git a/tools/run-dotnet-examples-with-aspire.sh b/tools/run-dotnet-examples-with-aspire.sh
new file mode 100755
index 00000000..1a85be6f
--- /dev/null
+++ b/tools/run-dotnet-examples-with-aspire.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+set -e
+ROOT="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && cd .. && pwd)"
+cd $ROOT
+# ================================================================
+
+cd aspire-orchestrator
+
+echo '===================================================================='
+echo ''
+echo 'If the Aspire dashboard is not opened in your browser automatically '
+echo 'look in the log for the following link, including the auth token: '
+echo ''
+echo ' https://localhost:17149/login?t=........ '
+echo ''
+echo '===================================================================='
+
+. run.sh