Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
<LangVersion>14</LangVersion>
<AnalysisMode>Recommended</AnalysisMode>
<ImplicitUsings>enable</ImplicitUsings>
<PreserveCompilationContext>true</PreserveCompilationContext>
Expand Down
34 changes: 17 additions & 17 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<PackageVersion Include="ContentFeedNuget" Version="$(ToolingPackagesVersion)" />
</ItemGroup>
<ItemGroup>
<PackageVersion Include="AspNet.Security.OAuth.GitHub" Version="9.4.1" />
<PackageVersion Include="AspNet.Security.OAuth.GitHub" Version="10.0.0" />
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.4.0" />
<PackageVersion Include="Azure.Identity" Version="1.17.1" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.4.0" />
Expand All @@ -26,29 +26,29 @@
<PackageVersion Include="HtmlAgilityPack" Version="1.12.4" />
<PackageVersion Include="IntelliTect.Multitool" Version="1.5.3" />
<PackageVersion Include="Mailjet.Api" Version="3.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="9.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.12" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.12" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.10" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.10" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="5.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.60.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.PgVector" Version="1.60.0-preview" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.70.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.PgVector" Version="1.70.0-preview" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.102" />
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageVersion Include="ModelContextProtocol" Version="0.3.0-preview.4" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.3.0-preview.4" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" />
<PackageVersion Include="ModelContextProtocol" Version="0.8.0-preview.1" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.8.0-preview.1" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="System.CommandLine" Version="2.0.2" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Octokit" Version="14.0.0" />
<PackageVersion Include="DotnetSitemapGenerator" Version="1.0.4" />
<PackageVersion Include="DotnetSitemapGenerator" Version="2.0.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<!-- Suppress OPENAI001 for experimental OpenAI Response APIs -->
<NoWarn>$(NoWarn);OPENAI001</NoWarn>
</PropertyGroup>

<ItemGroup>
Expand Down
17 changes: 10 additions & 7 deletions EssentialCSharp.Chat.Shared/Services/AIChatService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public AIChatService(IOptions<AIOptions> options, AISearchService searchService,
string prompt,
string? systemPrompt = null,
string? previousResponseId = null,
IMcpClient? mcpClient = null,
McpClient? mcpClient = null,
IEnumerable<ResponseTool>? tools = null,
ResponseReasoningEffortLevel? reasoningEffortLevel = null,
bool enableContextualSearch = false,
Expand All @@ -68,7 +68,7 @@ public AIChatService(IOptions<AIOptions> options, AISearchService searchService,
string prompt,
string? systemPrompt = null,
string? previousResponseId = null,
IMcpClient? mcpClient = null,
McpClient? mcpClient = null,
IEnumerable<ResponseTool>? tools = null,
ResponseReasoningEffortLevel? reasoningEffortLevel = null,
bool enableContextualSearch = false,
Expand Down Expand Up @@ -134,7 +134,7 @@ private async Task<string> EnrichPromptWithContext(string prompt, bool enableCon
private async IAsyncEnumerable<(string text, string? responseId)> ProcessStreamingUpdatesAsync(
IAsyncEnumerable<StreamingResponseUpdate> streamingUpdates,
ResponseCreationOptions responseOptions,
IMcpClient? mcpClient,
McpClient? mcpClient,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var update in streamingUpdates.WithCancellation(cancellationToken))
Expand Down Expand Up @@ -178,7 +178,7 @@ private async Task<string> EnrichPromptWithContext(string prompt, bool enableCon
private async IAsyncEnumerable<(string text, string? responseId)> ExecuteFunctionCallAsync(
FunctionCallResponseItem functionCallItem,
ResponseCreationOptions responseOptions,
IMcpClient mcpClient,
McpClient mcpClient,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// A dictionary of arguments to pass to the tool. Each key represents a parameter name, and its associated value represents the argument value.
Expand Down Expand Up @@ -242,7 +242,7 @@ private static async Task<ResponseCreationOptions> CreateResponseOptionsAsync(
string? previousResponseId = null,
IEnumerable<ResponseTool>? tools = null,
ResponseReasoningEffortLevel? reasoningEffortLevel = null,
IMcpClient? mcpClient = null,
McpClient? mcpClient = null,
CancellationToken cancellationToken = default
)
{
Expand All @@ -265,9 +265,12 @@ private static async Task<ResponseCreationOptions> CreateResponseOptionsAsync(

if (mcpClient is not null)
{
await foreach (McpClientTool tool in mcpClient.EnumerateToolsAsync(cancellationToken: cancellationToken))
var mcpTools = await mcpClient.ListToolsAsync(cancellationToken: cancellationToken);
foreach (McpClientTool tool in mcpTools)
{
options.Tools.Add(ResponseTool.CreateFunctionTool(tool.Name, tool.Description, BinaryData.FromString(tool.JsonSchema.GetRawText())));
// Convert McpClientTool to ResponseTool
// Note: The tool schema is managed internally by the MCP client
options.Tools.Add(ResponseTool.CreateFunctionTool(tool.Name, BinaryData.FromString(tool.Description), strictModeEnabled: false));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

Expand Down
2 changes: 1 addition & 1 deletion EssentialCSharp.Chat/EssentialCSharp.Chat.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Version>0.0.1</Version>
</PropertyGroup>

Expand Down
2 changes: 1 addition & 1 deletion EssentialCSharp.Web.Tests/EssentialCSharp.Web.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>

<IsPackable>false</IsPackable>
<IsPublishable>false</IsPublishable>
Expand Down
42 changes: 32 additions & 10 deletions EssentialCSharp.Web.Tests/WebApplicationFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,55 @@ public sealed class WebApplicationFactory : WebApplicationFactory<Program>
{
private static string SqlConnectionString => $"DataSource=file:{Guid.NewGuid()}?mode=memory&cache=shared";
private SqliteConnection? _Connection;
private bool _databaseInitialized;

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureServices(services =>
{
ServiceDescriptor? descriptor = services.SingleOrDefault(
d => d.ServiceType ==
typeof(DbContextOptions<EssentialCSharpWebContext>));
// Remove the DatabaseMigrationService as it conflicts with test database setup
var migrationServiceDescriptor = services.FirstOrDefault(
d => d.ImplementationType == typeof(DatabaseMigrationService));
if (migrationServiceDescriptor != null)
{
services.Remove(migrationServiceDescriptor);
}

// Remove all existing DbContext-related registrations to avoid provider conflicts in EF Core 10
var descriptorsToRemove = services
.Where(d => d.ServiceType == typeof(EssentialCSharpWebContext) ||
d.ServiceType == typeof(DbContextOptions<EssentialCSharpWebContext>) ||
d.ServiceType == typeof(DbContextOptions))
.ToList();

if (descriptor != null)
foreach (var descriptor in descriptorsToRemove)
{
services.Remove(descriptor);
}

_Connection = new SqliteConnection(SqlConnectionString);
_Connection.Open();

// Add the test DbContext with SQLite
services.AddDbContext<EssentialCSharpWebContext>(options =>
{
options.UseSqlite(_Connection);
});
});
}

using ServiceProvider serviceProvider = services.BuildServiceProvider();
using IServiceScope scope = serviceProvider.CreateScope();
IServiceProvider scopedServices = scope.ServiceProvider;
EssentialCSharpWebContext db = scopedServices.GetRequiredService<EssentialCSharpWebContext>();
/// <summary>
/// Ensures the database is created. Called lazily on first access.
/// </summary>
private void EnsureDatabaseCreated()
{
if (_databaseInitialized) return;

db.Database.EnsureCreated();
});
var factory = Services.GetRequiredService<IServiceScopeFactory>();
using var scope = factory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<EssentialCSharpWebContext>();
db.Database.EnsureCreated();
_databaseInitialized = true;
}

/// <summary>
Expand All @@ -50,6 +70,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder)
/// <returns>The result of the action</returns>
public T InServiceScope<T>(Func<IServiceProvider, T> action)
{
EnsureDatabaseCreated();
var factory = Services.GetRequiredService<IServiceScopeFactory>();
using var scope = factory.CreateScope();
return action(scope.ServiceProvider);
Expand All @@ -61,6 +82,7 @@ public T InServiceScope<T>(Func<IServiceProvider, T> action)
/// <param name="action">The action to execute with the scoped service provider</param>
public void InServiceScope(Action<IServiceProvider> action)
{
EnsureDatabaseCreated();
var factory = Services.GetRequiredService<IServiceScopeFactory>();
using var scope = factory.CreateScope();
action(scope.ServiceProvider);
Expand Down
4 changes: 3 additions & 1 deletion EssentialCSharp.Web/EssentialCSharp.Web.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<!-- Suppress CA1873 for logging performance - would require significant refactoring -->
<NoWarn>$(NoWarn);CA1873</NoWarn>
</PropertyGroup>
<ItemGroup>
<PlaceholderChapterOneHtmlFile Include="$(ProjectDir)/Placeholders/Chapters/01/Pages/*.html" />
Expand Down
2 changes: 1 addition & 1 deletion EssentialCSharp.Web/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ private static void Main(string[] args)
// Only loopback proxies are allowed by default.
// Clear that restriction because forwarders are enabled by explicit
// configuration.
options.KnownNetworks.Clear();
options.KnownIPNetworks.Clear();
options.KnownProxies.Clear();
});

Expand Down