Update to latest M.E.AI version (#401)
This commit is contained in:
parent
26c3622101
commit
61092e0266
8 changed files with 106 additions and 10 deletions
|
|
@ -2,7 +2,7 @@
|
|||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<System10Version>10.0.0-preview.3.25171.5</System10Version>
|
||||
<MicrosoftExtensionsAIVersion>9.4.3-preview.1.25230.7</MicrosoftExtensionsAIVersion>
|
||||
<MicrosoftExtensionsAIVersion>9.4.4-preview.1.25259.16</MicrosoftExtensionsAIVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Product dependencies netstandard -->
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
<!-- Product dependencies shared -->
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="$(MicrosoftExtensionsAIVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="9.4.4-preview.1.25259.16" />
|
||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="$(MicrosoftExtensionsAIVersion)" />
|
||||
<PackageVersion Include="System.Net.ServerSentEvents" Version="$(System10Version)" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@
|
|||
<!-- Dependencies needed by all -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
<PackageReference Include="System.Net.ServerSentEvents" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Utils;
|
||||
using ModelContextProtocol.Utils.Json;
|
||||
|
|
@ -68,7 +69,7 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
|
|||
Description = options?.Description,
|
||||
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
|
||||
SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
|
||||
Services = options?.Services,
|
||||
CreateInstance = AIFunctionMcpServerTool.GetCreateInstanceFunc(),
|
||||
ConfigureParameterBinding = pi =>
|
||||
{
|
||||
if (pi.ParameterType == typeof(RequestContext<GetPromptRequestParams>))
|
||||
|
|
@ -110,6 +111,32 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
|
|||
};
|
||||
}
|
||||
|
||||
if (options?.Services is { } services &&
|
||||
services.GetService<IServiceProviderIsService>() is { } ispis &&
|
||||
ispis.IsService(pi.ParameterType))
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExcludeFromSchema = true,
|
||||
BindParameter = (pi, args) =>
|
||||
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
|
||||
(pi.HasDefaultValue ? null :
|
||||
throw new ArgumentException("No service of the requested type was found.")),
|
||||
};
|
||||
}
|
||||
|
||||
if (pi.GetCustomAttribute<FromKeyedServicesAttribute>() is { } keyedAttr)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExcludeFromSchema = true,
|
||||
BindParameter = (pi, args) =>
|
||||
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
|
||||
(pi.HasDefaultValue ? null :
|
||||
throw new ArgumentException("No service of the requested type was found.")),
|
||||
};
|
||||
}
|
||||
|
||||
return default;
|
||||
|
||||
static RequestContext<GetPromptRequestParams>? GetRequestContext(AIFunctionArguments args)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Utils;
|
||||
using ModelContextProtocol.Utils.Json;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
|
|
@ -76,7 +76,7 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
|
|||
Description = options?.Description,
|
||||
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
|
||||
SerializerOptions = McpJsonUtilities.DefaultOptions,
|
||||
Services = options?.Services,
|
||||
CreateInstance = AIFunctionMcpServerTool.GetCreateInstanceFunc(),
|
||||
ConfigureParameterBinding = pi =>
|
||||
{
|
||||
if (pi.ParameterType == typeof(RequestContext<ReadResourceRequestParams>))
|
||||
|
|
@ -118,6 +118,32 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
|
|||
};
|
||||
}
|
||||
|
||||
if (options?.Services is { } services &&
|
||||
services.GetService<IServiceProviderIsService>() is { } ispis &&
|
||||
ispis.IsService(pi.ParameterType))
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExcludeFromSchema = true,
|
||||
BindParameter = (pi, args) =>
|
||||
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
|
||||
(pi.HasDefaultValue ? null :
|
||||
throw new ArgumentException("No service of the requested type was found.")),
|
||||
};
|
||||
}
|
||||
|
||||
if (pi.GetCustomAttribute<FromKeyedServicesAttribute>() is { } keyedAttr)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExcludeFromSchema = true,
|
||||
BindParameter = (pi, args) =>
|
||||
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
|
||||
(pi.HasDefaultValue ? null :
|
||||
throw new ArgumentException("No service of the requested type was found.")),
|
||||
};
|
||||
}
|
||||
|
||||
// These parameters are the ones and only ones to include in the schema. The schema
|
||||
// won't be consumed by anyone other than this instance, which will use it to determine
|
||||
// which properties should show up in the URI template.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Utils;
|
||||
using ModelContextProtocol.Utils.Json;
|
||||
|
|
@ -60,6 +61,14 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
|
|||
options);
|
||||
}
|
||||
|
||||
// TODO: Fix the need for this suppression.
|
||||
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111:ReflectionToDynamicallyAccessedMembers",
|
||||
Justification = "AIFunctionFactory ensures that the Type passed to AIFunctionFactoryOptions.CreateInstance has public constructors preserved")]
|
||||
internal static Func<Type, AIFunctionArguments, object> GetCreateInstanceFunc() =>
|
||||
static ([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] type, args) => args.Services is { } services ?
|
||||
ActivatorUtilities.CreateInstance(services, type) :
|
||||
Activator.CreateInstance(type)!;
|
||||
|
||||
private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions(
|
||||
MethodInfo method, McpServerToolCreateOptions? options) =>
|
||||
new()
|
||||
|
|
@ -68,7 +77,7 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
|
|||
Description = options?.Description,
|
||||
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
|
||||
SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
|
||||
Services = options?.Services,
|
||||
CreateInstance = GetCreateInstanceFunc(),
|
||||
ConfigureParameterBinding = pi =>
|
||||
{
|
||||
if (pi.ParameterType == typeof(RequestContext<CallToolRequestParams>))
|
||||
|
|
@ -110,6 +119,32 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
|
|||
};
|
||||
}
|
||||
|
||||
if (options?.Services is { } services &&
|
||||
services.GetService<IServiceProviderIsService>() is { } ispis &&
|
||||
ispis.IsService(pi.ParameterType))
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExcludeFromSchema = true,
|
||||
BindParameter = (pi, args) =>
|
||||
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
|
||||
(pi.HasDefaultValue ? null :
|
||||
throw new ArgumentException("No service of the requested type was found.")),
|
||||
};
|
||||
}
|
||||
|
||||
if (pi.GetCustomAttribute<FromKeyedServicesAttribute>() is { } keyedAttr)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
ExcludeFromSchema = true,
|
||||
BindParameter = (pi, args) =>
|
||||
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
|
||||
(pi.HasDefaultValue ? null :
|
||||
throw new ArgumentException("No service of the requested type was found.")),
|
||||
};
|
||||
}
|
||||
|
||||
return default;
|
||||
|
||||
static RequestContext<CallToolRequestParams>? GetRequestContext(AIFunctionArguments args)
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public class McpServerPromptTests
|
|||
Assert.Contains("something", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
|
||||
Assert.DoesNotContain("actualMyService", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await prompt.GetAsync(
|
||||
await Assert.ThrowsAnyAsync<ArgumentException>(async () => await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
TestContext.Current.CancellationToken));
|
||||
|
||||
|
|
|
|||
|
|
@ -375,7 +375,7 @@ public partial class McpServerResourceTests
|
|||
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await resource.ReadAsync(
|
||||
await Assert.ThrowsAnyAsync<ArgumentException>(async () => await resource.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://Test" } },
|
||||
TestContext.Current.CancellationToken));
|
||||
|
||||
|
|
|
|||
|
|
@ -156,6 +156,10 @@ public partial class McpServerToolTests
|
|||
[Fact]
|
||||
public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableAndDisposableTargets()
|
||||
{
|
||||
ServiceCollection sc = new();
|
||||
sc.AddSingleton<MyService>();
|
||||
IServiceProvider services = sc.BuildServiceProvider();
|
||||
|
||||
McpServerToolCreateOptions options = new() { SerializerOptions = JsonContext2.Default.Options };
|
||||
McpServerTool tool1 = McpServerTool.Create(
|
||||
typeof(AsyncDisposableAndDisposableToolType).GetMethod(nameof(AsyncDisposableAndDisposableToolType.InstanceMethod))!,
|
||||
|
|
@ -163,7 +167,7 @@ public partial class McpServerToolTests
|
|||
options);
|
||||
|
||||
var result = await tool1.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object) { Services = services },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal("""{"asyncDisposals":1,"disposals":0}""", result.Content[0].Text);
|
||||
}
|
||||
|
|
@ -428,6 +432,11 @@ public partial class McpServerToolTests
|
|||
|
||||
private class AsyncDisposableAndDisposableToolType : IAsyncDisposable, IDisposable
|
||||
{
|
||||
public AsyncDisposableAndDisposableToolType(MyService service)
|
||||
{
|
||||
Assert.NotNull(service);
|
||||
}
|
||||
|
||||
[JsonPropertyOrder(0)]
|
||||
public int AsyncDisposals { get; private set; }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue