diff --git a/AI.slnx b/AI.slnx
index f6dcf9d..6f1f494 100644
--- a/AI.slnx
+++ b/AI.slnx
@@ -4,9 +4,13 @@
+
+
+
+
+
-
diff --git a/sample/Aspire/AppHost.cs b/sample/Aspire/AppHost.cs
new file mode 100644
index 0000000..475252b
--- /dev/null
+++ b/sample/Aspire/AppHost.cs
@@ -0,0 +1,15 @@
+using Projects;
+
+var builder = DistributedApplication.CreateBuilder(args);
+
+var server = builder.AddProject("server");
+
+// For now, we can't really launch a console project and have its terminal shown.
+// See https://github.com/dotnet/aspire/issues/8440
+//builder.AddProject("client")
+// .WithReference(server)
+// // Flow the resolved Server HTTP endpoint to the client config
+// .WithEnvironment("ai__clients__chat__endpoint", server.GetEndpoint("http"))
+// .WithExternalConsole();
+
+builder.Build().Run();
diff --git a/sample/Aspire/Aspire.csproj b/sample/Aspire/Aspire.csproj
new file mode 100644
index 0000000..bb2ca40
--- /dev/null
+++ b/sample/Aspire/Aspire.csproj
@@ -0,0 +1,19 @@
+
+
+
+
+
+ Exe
+ net10.0
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/Aspire/Properties/launchSettings.json b/sample/Aspire/Properties/launchSettings.json
new file mode 100644
index 0000000..a433d15
--- /dev/null
+++ b/sample/Aspire/Properties/launchSettings.json
@@ -0,0 +1,29 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "https": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:17198;http://localhost:15055",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21263",
+ "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22169"
+ }
+ },
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:15055",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development",
+ "DOTNET_ENVIRONMENT": "Development",
+ "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19208",
+ "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20046"
+ }
+ }
+ }
+}
diff --git a/sample/Aspire/appsettings.Development.json b/sample/Aspire/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/sample/Aspire/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/sample/Aspire/appsettings.json b/sample/Aspire/appsettings.json
new file mode 100644
index 0000000..31c092a
--- /dev/null
+++ b/sample/Aspire/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Aspire.Hosting.Dcp": "Warning"
+ }
+ }
+}
diff --git a/sample/Client/Client.csproj b/sample/Client/Client.csproj
new file mode 100644
index 0000000..53ca217
--- /dev/null
+++ b/sample/Client/Client.csproj
@@ -0,0 +1,29 @@
+
+
+
+ Exe
+ net10.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/Client/Program.cs b/sample/Client/Program.cs
new file mode 100644
index 0000000..ea3c50a
--- /dev/null
+++ b/sample/Client/Program.cs
@@ -0,0 +1,79 @@
+using System.Net.Http.Json;
+using System.Text.Json.Serialization;
+using Devlooped.Extensions.AI.OpenAI;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Trace;
+using Spectre.Console;
+
+var builder = App.CreateBuilder(args);
+#if DEBUG
+builder.Environment.EnvironmentName = Environments.Development;
+#endif
+
+builder.AddServiceDefaults();
+builder.Services.AddHttpClient();
+
+var app = builder.Build(async (IServiceProvider services, CancellationToken cancellation) =>
+{
+ var baseUrl = Environment.GetEnvironmentVariable("applicationUrl") ?? "http://localhost:5117";
+ var http = services.GetRequiredService().CreateClient();
+ var agents = await http.GetFromJsonAsync($"{baseUrl}/agents", cancellation) ?? [];
+
+ if (agents.Length == 0)
+ {
+ AnsiConsole.MarkupLine(":warning: No agents available");
+ return;
+ }
+
+ var selectedAgent = AnsiConsole.Prompt(new SelectionPrompt()
+ .Title("Select agent:")
+ .UseConverter(a => $"{a.Name}: {a.Description ?? ""}")
+ .AddChoices(agents));
+
+ var chat = new OpenAIChatClient("none", "default", new OpenAI.OpenAIClientOptions
+ {
+ Endpoint = new Uri($"{baseUrl}/{selectedAgent.Name}/v1")
+ }).AsBuilder().UseOpenTelemetry().UseJsonConsoleLogging().Build(services);
+
+ var history = new List();
+
+ AnsiConsole.MarkupLine($":robot: Ready");
+ AnsiConsole.Markup($":person_beard: ");
+ while (!cancellation.IsCancellationRequested)
+ {
+ var input = Console.ReadLine()?.Trim();
+ if (string.IsNullOrEmpty(input))
+ continue;
+
+ history.Add(new ChatMessage(ChatRole.User, input));
+ try
+ {
+ var response = await AnsiConsole.Status().StartAsync(":robot: Thinking...", ctx => chat.GetResponseAsync(input));
+ history.AddRange(response.Messages);
+ try
+ {
+ // Try rendering as formatted markup
+ if (response.Text is { Length: > 0 })
+ AnsiConsole.MarkupLine($":robot: {response.Text}");
+ }
+ catch (Exception)
+ {
+ // Fallback to escaped markup text if rendering fails
+ AnsiConsole.MarkupLineInterpolated($":robot: {response.Text}");
+ }
+ AnsiConsole.Markup($":person_beard: ");
+ }
+ catch (Exception e)
+ {
+ AnsiConsole.WriteException(e);
+ }
+ }
+
+ AnsiConsole.MarkupLine($":robot: Shutting down...");
+});
+
+Console.WriteLine("Powered by Smith");
+
+await app.RunAsync();
+
+record AgentCard(string Name, string? Description);
\ No newline at end of file
diff --git a/sample/Client/Properties/launchSettings.json b/sample/Client/Properties/launchSettings.json
new file mode 100644
index 0000000..bc06646
--- /dev/null
+++ b/sample/Client/Properties/launchSettings.json
@@ -0,0 +1,10 @@
+{
+ "profiles": {
+ "Client": {
+ "commandName": "Project",
+ "environmentVariables": {
+ "applicationUrl": "http://localhost:5117"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/sample/Client/appsettings.json b/sample/Client/appsettings.json
new file mode 100644
index 0000000..1be98d3
--- /dev/null
+++ b/sample/Client/appsettings.json
@@ -0,0 +1,11 @@
+{
+ "AI": {
+ "Clients": {
+ "Chat": {
+ "ApiKey": "dev",
+ "ModelId": "default",
+ "Endpoint": "http://localhost:5117/notes/v1"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/sample/Directory.Build.props b/sample/Directory.Build.props
new file mode 100644
index 0000000..1ee4654
--- /dev/null
+++ b/sample/Directory.Build.props
@@ -0,0 +1,7 @@
+
+
+ enable
+ enable
+ b420aaad-e6e3-43d7-8d91-5a07b19f20ab
+
+
\ No newline at end of file
diff --git a/sample/Directory.Build.targets b/sample/Directory.Build.targets
new file mode 100644
index 0000000..bee8e0b
--- /dev/null
+++ b/sample/Directory.Build.targets
@@ -0,0 +1,22 @@
+
+
+
+ $(DefineConstants);WEB
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/Server/AgentDiscoveryExtensions.cs b/sample/Server/AgentDiscoveryExtensions.cs
new file mode 100644
index 0000000..9e2e30b
--- /dev/null
+++ b/sample/Server/AgentDiscoveryExtensions.cs
@@ -0,0 +1,20 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json.Serialization;
+using Microsoft.Agents.AI.Hosting;
+
+static class AgentDiscoveryExtensions
+{
+ public static void MapAgentDiscovery(this IEndpointRouteBuilder endpoints, [StringSyntax("Route")] string path)
+ {
+ var routeGroup = endpoints.MapGroup(path);
+ routeGroup.MapGet("/", async (AgentCatalog catalog, CancellationToken cancellation)
+ => Results.Ok(await catalog
+ .GetAgentsAsync(cancellation)
+ .Select(agent => new AgentDiscoveryCard(agent.Name!, agent.Description))
+ .ToArrayAsync()))
+ .WithName("GetAgents");
+ }
+
+ record AgentDiscoveryCard(string Name,
+ [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string? Description);
+}
diff --git a/sample/Server/ConsoleExtensions.cs b/sample/Server/ConsoleExtensions.cs
new file mode 100644
index 0000000..c2890da
--- /dev/null
+++ b/sample/Server/ConsoleExtensions.cs
@@ -0,0 +1,47 @@
+using Devlooped.Agents.AI;
+using Devlooped.Extensions.AI;
+using Microsoft.Agents.AI.Hosting;
+using Microsoft.Extensions.AI;
+using Newtonsoft.Json;
+using Spectre.Console;
+using Spectre.Console.Json;
+
+public static class ConsoleExtensions
+{
+ public static async ValueTask RenderAgentsAsync(this IServiceProvider services, IServiceCollection collection)
+ {
+ var catalog = services.GetRequiredService();
+ var settings = new JsonSerializerSettings
+ {
+ NullValueHandling = NullValueHandling.Include,
+ DefaultValueHandling = DefaultValueHandling.Ignore
+ };
+
+ // List configured clients
+ foreach (var description in collection.AsEnumerable().Where(x => x.ServiceType == typeof(IChatClient) && x.IsKeyedService && x.ServiceKey is string))
+ {
+ var client = services.GetKeyedService(description.ServiceKey);
+ if (client is null)
+ continue;
+
+ var metadata = client.GetService();
+ var chatopt = (client as ConfigurableChatClient)?.Options;
+
+ AnsiConsole.Write(new Panel(new JsonText(JsonConvert.SerializeObject(new { Metadata = metadata, Options = chatopt }, settings)))
+ {
+ Header = new PanelHeader($"| 💬 {metadata?.Id} from {metadata?.ConfigurationSection} |"),
+ });
+ }
+
+ // List configured agents
+ await foreach (var agent in catalog.GetAgentsAsync())
+ {
+ var metadata = agent.GetService();
+
+ AnsiConsole.Write(new Panel(new JsonText(JsonConvert.SerializeObject(new { Agent = agent, Metadata = metadata }, settings)))
+ {
+ Header = new PanelHeader($"| 🤖 {agent.DisplayName} from {metadata?.ConfigurationSection} |"),
+ });
+ }
+ }
+}
diff --git a/sample/Server/Program.cs b/sample/Server/Program.cs
new file mode 100644
index 0000000..dc07688
--- /dev/null
+++ b/sample/Server/Program.cs
@@ -0,0 +1,74 @@
+using System.Runtime.InteropServices;
+using System.Text;
+using Devlooped.Extensions.AI;
+using DotNetEnv.Configuration;
+using Microsoft.Agents.AI.Hosting;
+using Microsoft.Agents.AI.Hosting.OpenAI;
+using Microsoft.Extensions.AI;
+using Spectre.Console;
+using Tomlyn.Extensions.Configuration;
+
+var builder = WebApplication.CreateBuilder(args);
+
+#if DEBUG
+builder.Environment.EnvironmentName = Environments.Development;
+// Fixes console rendering when running from Visual Studio
+if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ Console.InputEncoding = Console.OutputEncoding = Encoding.UTF8;
+#endif
+
+builder.AddServiceDefaults();
+builder.ConfigureReload();
+
+// 👇 implicitly calls AddChatClients
+builder.AddAIAgents();
+
+var app = builder.Build();
+
+// From ServiceDefaults.cs
+app.MapDefaultEndpoints();
+
+#if DEBUG
+// 👇 render all configured agents
+await app.Services.RenderAgentsAsync(builder.Services);
+#endif
+
+// Map each agent's endpoints via response API
+var catalog = app.Services.GetRequiredService();
+// List configured agents
+await foreach (var agent in catalog.GetAgentsAsync())
+{
+ if (agent.Name != null)
+ app.MapOpenAIResponses(agent.Name);
+}
+
+// Map the agents HTTP endpoints
+app.MapAgentDiscovery("/agents");
+
+if (!app.Environment.IsProduction())
+{
+ app.Lifetime.ApplicationStarted.Register(() =>
+ {
+ var baseUrl = Environment.GetEnvironmentVariable("ASPNETCORE_URLS");
+ AnsiConsole.MarkupLine("[orange1]Registered Routes:[/]");
+
+ var endpoints = ((IEndpointRouteBuilder)app).DataSources
+ .SelectMany(es => es.Endpoints)
+ .OfType()
+ .Where(e => e.RoutePattern.RawText != null)
+ .OrderBy(e => e.RoutePattern.RawText);
+
+ foreach (var endpoint in endpoints)
+ {
+ var httpMethods = endpoint.Metadata
+ .OfType()
+ .SelectMany(m => m.HttpMethods) ?? [];
+
+ var methods = httpMethods.Any() ? $"{string.Join(", ", httpMethods)}" : "ANY";
+
+ AnsiConsole.MarkupLineInterpolated($"[blue][[{methods}]][/] [lime][link={baseUrl}{endpoint.RoutePattern.RawText}]{endpoint.RoutePattern.RawText}[/][/]");
+ }
+ });
+}
+
+app.Run();
diff --git a/sample/Server/Properties/launchSettings.json b/sample/Server/Properties/launchSettings.json
new file mode 100644
index 0000000..228344d
--- /dev/null
+++ b/sample/Server/Properties/launchSettings.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "applicationUrl": "http://server.dev.localhost:5117",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/sample/Server/Server.csproj b/sample/Server/Server.csproj
new file mode 100644
index 0000000..978e7d6
--- /dev/null
+++ b/sample/Server/Server.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net10.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/SampleChat/ai.toml b/sample/Server/ai.toml
similarity index 84%
rename from src/SampleChat/ai.toml
rename to sample/Server/ai.toml
index 24ef42f..15b0804 100644
--- a/src/SampleChat/ai.toml
+++ b/sample/Server/ai.toml
@@ -1,9 +1,11 @@
[ai.clients.openai]
modelid = "gpt-4.1"
-apikey = "sk-asdf"
-[ai.agents.reminder]
-name = "Orders"
+[ai.clients.grok]
+endpoint = "https://api.x.ai/v1"
+modelid = "grok-4-fast-non-reasoning"
+
+[ai.agents.orders]
description = "Manage orders using catalogs for food or any other item."
instructions = """
You are an AI agent responsible for processing orders for food or other items.
diff --git a/sample/Server/appsettings.Development.json b/sample/Server/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/sample/Server/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/src/SampleChat/appsettings.json b/sample/Server/appsettings.json
similarity index 64%
rename from src/SampleChat/appsettings.json
rename to sample/Server/appsettings.json
index 8c76f9d..78b8db9 100644
--- a/src/SampleChat/appsettings.json
+++ b/sample/Server/appsettings.json
@@ -1,7 +1,15 @@
{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*",
"AI": {
"Agents": {
"Notes": {
+ "Name": "notes",
"Description": "Provides free-form memory",
"Instructions": "You organize and keep notes for the user, using JSON-LD",
"Client": "Grok",
@@ -13,9 +21,8 @@
"Clients": {
"Grok": {
"Endpoint": "https://api.grok.ai/v1",
- "ModelId": "grok-4-fast-non-reasoning",
- "ApiKey": "xai-asdf"
+ "ModelId": "grok-4-fast-non-reasoning"
}
}
}
-}
\ No newline at end of file
+}
diff --git a/sample/ServiceDefaults.cs b/sample/ServiceDefaults.cs
new file mode 100644
index 0000000..b0fa98b
--- /dev/null
+++ b/sample/ServiceDefaults.cs
@@ -0,0 +1,134 @@
+using DotNetEnv.Configuration;
+using OpenTelemetry;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Resources;
+using OpenTelemetry.Trace;
+using Tomlyn.Extensions.Configuration;
+
+
+#if WEB
+using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Microsoft.AspNetCore.Diagnostics.HealthChecks;
+#endif
+
+static class ConfigureOpenTelemetryExtensions
+{
+ const string HealthEndpointPath = "/health";
+ const string AlivenessEndpointPath = "/alive";
+
+ public static TBuilder AddServiceDefaults(this TBuilder builder)
+ where TBuilder : IHostApplicationBuilder
+ {
+ builder.ConfigureOpenTelemetry();
+
+ // .env/secrets override other config, which may contain dummy API keys, for example
+ builder.Configuration
+ .AddDotNetEnv()
+ .AddEnvironmentVariables()
+ .AddUserSecrets();
+
+ builder.ConfigureReload();
+
+#if WEB
+ builder.AddDefaultHealthChecks();
+#endif
+
+ return builder;
+ }
+
+ public static TBuilder ConfigureOpenTelemetry(this TBuilder builder)
+ where TBuilder : IHostApplicationBuilder
+ {
+ var serviceName = builder.Environment.ApplicationName
+ ?? throw new InvalidOperationException("Application name is not set in the hosting environment.");
+
+ builder.Services.AddOpenTelemetry()
+ .ConfigureResource(rb => rb.AddService(serviceName))
+ .WithTracing(tracing =>
+ {
+#if WEB
+ tracing.AddAspNetCoreInstrumentation(tracing =>
+ // Don't trace requests to the health endpoint to avoid filling the dashboard with noise
+ tracing.Filter = httpContext =>
+ !(httpContext.Request.Path.StartsWithSegments(HealthEndpointPath)
+ || httpContext.Request.Path.StartsWithSegments(AlivenessEndpointPath)));
+#endif
+ tracing.AddHttpClientInstrumentation();
+ tracing.AddConsoleExporter();
+ })
+ .WithMetrics(metrics =>
+ {
+#if WEB
+ metrics.AddAspNetCoreInstrumentation();
+#endif
+ metrics.AddRuntimeInstrumentation();
+ metrics.AddHttpClientInstrumentation();
+ });
+
+
+ if (string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]))
+ builder.Services.AddOpenTelemetry().UseOtlpExporter();
+
+ return builder;
+ }
+
+ public static TBuilder ConfigureReload(this TBuilder builder)
+ where TBuilder : IHostApplicationBuilder
+ {
+ if (builder.Environment.IsProduction())
+ {
+ foreach (var toml in Directory.EnumerateFiles(AppContext.BaseDirectory, "*.toml", SearchOption.AllDirectories))
+ builder.Configuration.AddTomlFile(toml, optional: false, reloadOnChange: true);
+ foreach (var json in Directory.EnumerateFiles(AppContext.BaseDirectory, "*.json", SearchOption.AllDirectories))
+ builder.Configuration.AddJsonFile(json, optional: false, reloadOnChange: true);
+ }
+ else
+ {
+ var baseDir = ThisAssembly.Project.MSBuildProjectDirectory;
+ var outDir = Path.Combine(baseDir, ThisAssembly.Project.BaseOutputPath);
+ var objDir = Path.Combine(baseDir, ThisAssembly.Project.BaseIntermediateOutputPath);
+
+ // Only use configs outside of bin/ and obj/ directories since we want reload to happen from source files not output files
+ bool IsSource(string path) => !path.StartsWith(outDir) && !path.StartsWith(objDir);
+
+ foreach (var json in Directory.EnumerateFiles(baseDir, "*.json", SearchOption.AllDirectories).Where(IsSource))
+ builder.Configuration.AddJsonFile(json, optional: false, reloadOnChange: true);
+
+ foreach (var toml in Directory.EnumerateFiles(baseDir, "*.toml", SearchOption.AllDirectories).Where(IsSource))
+ builder.Configuration.AddTomlFile(toml, optional: false, reloadOnChange: true);
+ }
+
+ return builder;
+ }
+
+#if WEB
+ public static TBuilder AddDefaultHealthChecks(this TBuilder builder)
+ where TBuilder : IHostApplicationBuilder
+ {
+ builder.Services.AddHealthChecks()
+ // Add a default liveness check to ensure app is responsive
+ .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]);
+
+ return builder;
+ }
+
+ public static WebApplication MapDefaultEndpoints(this WebApplication app)
+ {
+ // Adding health checks endpoints to applications in non-development environments has security implications.
+ // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments.
+ if (app.Environment.IsDevelopment())
+ {
+ // All health checks must pass for app to be considered ready to accept traffic after starting
+ app.MapHealthChecks(HealthEndpointPath);
+
+ // Only health checks tagged with the "live" tag must pass for app to be considered alive
+ app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions
+ {
+ Predicate = r => r.Tags.Contains("live")
+ });
+ }
+
+ return app;
+ }
+#endif
+}
diff --git a/src/Agents/Agents.csproj b/src/Agents/Agents.csproj
index 199cbb1..f4b074f 100644
--- a/src/Agents/Agents.csproj
+++ b/src/Agents/Agents.csproj
@@ -14,19 +14,19 @@
true
- $(NoWarn);CS0436;SYSLIB1100;SYSLIB1101
+ $(NoWarn);CS0436;SYSLIB1100;SYSLIB1101;MEAI001
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/src/Directory.props b/src/Directory.props
index 43ac06f..294c754 100644
--- a/src/Directory.props
+++ b/src/Directory.props
@@ -2,7 +2,7 @@
Devlooped AI Extensions
- enable
+ true
6eb457f9-16bc-49c5-81f2-33399b254e04
https://api.nuget.org/v3/index.json;https://pkg.kzu.app/index.json
diff --git a/src/Extensions.CodeAnalysis/Extensions.CodeAnalysis.csproj b/src/Extensions.CodeAnalysis/Extensions.CodeAnalysis.csproj
index 5ac03e9..c4e2782 100644
--- a/src/Extensions.CodeAnalysis/Extensions.CodeAnalysis.csproj
+++ b/src/Extensions.CodeAnalysis/Extensions.CodeAnalysis.csproj
@@ -7,7 +7,7 @@
-
+
diff --git a/src/Extensions/Extensions.csproj b/src/Extensions/Extensions.csproj
index 7a5a3a2..612d6a4 100644
--- a/src/Extensions/Extensions.csproj
+++ b/src/Extensions/Extensions.csproj
@@ -21,15 +21,15 @@
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
@@ -52,14 +52,8 @@
-
-
+
+
\ No newline at end of file
diff --git a/src/SampleChat/AppInitializer.cs b/src/SampleChat/AppInitializer.cs
deleted file mode 100644
index 4324e89..0000000
--- a/src/SampleChat/AppInitializer.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Text;
-
-namespace SampleChat;
-
-class AppInitializer
-{
- [ModuleInitializer]
- public static void Init()
- {
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- Console.InputEncoding = Console.OutputEncoding = Encoding.UTF8;
-
- // Load environment variables from .env files in current dir and above.
- DotNetEnv.Env.TraversePath().Load();
-
- // Load environment variables from user profile directory.
- var userEnv = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".env");
- if (File.Exists(userEnv))
- DotNetEnv.Env.Load(userEnv);
- }
-}
diff --git a/src/SampleChat/Program.cs b/src/SampleChat/Program.cs
deleted file mode 100644
index 453834f..0000000
--- a/src/SampleChat/Program.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using Devlooped.Agents.AI;
-using Devlooped.Extensions.AI;
-using Microsoft.Agents.AI;
-using Microsoft.Agents.AI.Hosting;
-using Microsoft.Extensions.AI;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using Newtonsoft.Json;
-using Spectre.Console;
-using Spectre.Console.Json;
-using Tomlyn.Extensions.Configuration;
-
-var host = new HostApplicationBuilder(args);
-
-#if DEBUG
-host.Environment.EnvironmentName = Environments.Development;
-#endif
-
-// Setup config files from output directory vs project directory
-// depending on environment, so we can reload by editing the source
-if (host.Environment.IsProduction())
-{
- foreach (var json in Directory.EnumerateFiles(AppContext.BaseDirectory, "*.json", SearchOption.AllDirectories))
- host.Configuration.AddJsonFile(json, optional: false, reloadOnChange: true);
-
- foreach (var toml in Directory.EnumerateFiles(AppContext.BaseDirectory, "*.toml", SearchOption.AllDirectories))
- host.Configuration.AddTomlFile(toml, optional: false, reloadOnChange: true);
-}
-else
-{
- var baseDir = ThisAssembly.Project.MSBuildProjectDirectory;
- var outDir = Path.Combine(baseDir, ThisAssembly.Project.BaseOutputPath);
- var objDir = Path.Combine(baseDir, ThisAssembly.Project.BaseIntermediateOutputPath);
-
- bool IsSource(string path) => !path.StartsWith(outDir) && !path.StartsWith(objDir);
-
- foreach (var json in Directory.EnumerateFiles(baseDir, "*.json", SearchOption.AllDirectories).Where(IsSource))
- host.Configuration.AddJsonFile(json, optional: false, reloadOnChange: true);
-
- foreach (var toml in Directory.EnumerateFiles(baseDir, "*.toml", SearchOption.AllDirectories).Where(IsSource))
- host.Configuration.AddTomlFile(toml, optional: false, reloadOnChange: true);
-}
-
-// .env/secrets override other config, which may contain dummy API keys, for example
-host.Configuration
- .AddEnvironmentVariables()
- .AddUserSecrets();
-
-// 👇 implicitly calls AddChatClients
-host.AddAIAgents();
-
-var app = host.Build();
-var catalog = app.Services.GetRequiredService();
-var settings = new JsonSerializerSettings
-{
- NullValueHandling = NullValueHandling.Include,
- DefaultValueHandling = DefaultValueHandling.Ignore
-};
-
-// List configured clients
-foreach (var description in host.Services.AsEnumerable().Where(x => x.ServiceType == typeof(IChatClient) && x.IsKeyedService))
-{
- var client = app.Services.GetKeyedService(description.ServiceKey);
- if (client is null)
- continue;
-
- var metadata = client.GetService();
- var chatopt = (client as ConfigurableChatClient)?.Options;
-
- AnsiConsole.Write(new Panel(new JsonText(JsonConvert.SerializeObject(new { Metadata = metadata, Options = chatopt }, settings)))
- {
- Header = new PanelHeader($"| 💬 {description.ServiceKey} |"),
- });
-}
-
-// List configured agents
-await foreach (var agent in catalog.GetAgentsAsync())
-{
- var metadata = agent.GetService();
-
- AnsiConsole.Write(new Panel(new JsonText(JsonConvert.SerializeObject(new { Agent = agent, Metadata = metadata }, settings)))
- {
- Header = new PanelHeader($"| 🤖 {agent.DisplayName} |"),
- });
-}
-
-Console.ReadLine();
diff --git a/src/SampleChat/SampleChat.csproj b/src/SampleChat/SampleChat.csproj
deleted file mode 100644
index b632474..0000000
--- a/src/SampleChat/SampleChat.csproj
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
- Exe
- net10.0
- enable
- enable
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj
index 0ba67c8..b7e726d 100644
--- a/src/Tests/Tests.csproj
+++ b/src/Tests/Tests.csproj
@@ -14,16 +14,16 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+