From 16b20f26f9e22f9650e6e14e25d3f72bad746ed0 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Tue, 28 Jan 2025 18:23:46 -0500 Subject: [PATCH 01/53] feat: Set up Scaffolding for Core.Grpc * Define skeleton for GrpcAgentRuntime * Implement CloudEvent and RPC Payload serialization/marshaling --- .../Core.Grpc/GrpcAgentRuntime.cs | 597 ++++++++++++++++++ .../GrpcAgentWorkerHostBuilderExtension.cs | 70 ++ .../Core.Grpc/IAgentMessageSerializer.cs | 23 + .../Core.Grpc/IAgentRuntimeExtensions.cs | 102 +++ .../Core.Grpc/IProtoMessageSerializer.cs | 10 + .../Core.Grpc/ISerializationRegistry.cs | 27 + .../Core.Grpc/ITypeNameResolver.cs | 9 + .../Core.Grpc/ProtoSerializationRegistry.cs | 37 ++ .../Core.Grpc/ProtoTypeNameResolver.cs | 21 + .../Core.Grpc/ProtobufConversionExtensions.cs | 61 ++ .../Core.Grpc/ProtobufMessageSerializer.cs | 46 ++ .../src/Microsoft.AutoGen/Core/AgentsApp.cs | 2 + 12 files changed, 1005 insertions(+) create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentWorkerHostBuilderExtension.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/IAgentMessageSerializer.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/IAgentRuntimeExtensions.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/IProtoMessageSerializer.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/ISerializationRegistry.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/ITypeNameResolver.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoSerializationRegistry.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoTypeNameResolver.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufConversionExtensions.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufMessageSerializer.cs diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs new file mode 100644 index 000000000000..5deba58ae62b --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs @@ -0,0 +1,597 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// GrpcAgentRuntime.cs + +using System.Collections.Concurrent; +using System.Threading.Channels; +using Google.Protobuf; +using Grpc.Core; +using Microsoft.AutoGen.Contracts; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.AutoGen.Protobuf; + +namespace Microsoft.AutoGen.Core.Grpc; + +public sealed class GrpcAgentRuntime( + AgentRpc.AgentRpcClient client, + IHostApplicationLifetime hostApplicationLifetime, + IServiceProvider serviceProvider, + ILogger logger + ) : IAgentRuntime, IDisposable +{ + private readonly object _channelLock = new(); + + // Request ID -> + private readonly ConcurrentDictionary> _pendingRequests = new(); + private Dictionary>> agentFactories = new(); + private Dictionary agentInstances = new(); + + private readonly Channel<(Message Message, TaskCompletionSource WriteCompletionSource)> _outboundMessagesChannel = Channel.CreateBounded<(Message, TaskCompletionSource)>(new BoundedChannelOptions(1024) + { + AllowSynchronousContinuations = true, + SingleReader = true, + SingleWriter = false, + FullMode = BoundedChannelFullMode.Wait + }); + + private readonly AgentRpc.AgentRpcClient _client = client; + public readonly IServiceProvider ServiceProvider = serviceProvider; + + private readonly ILogger _logger = logger; + private readonly CancellationTokenSource _shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping); + private AsyncDuplexStreamingCall? _channel; + private Task? _readTask; + private Task? _writeTask; + + private string _clientId = Guid.NewGuid().ToString(); + private CallOptions CallOptions + { + get + { + var metadata = new Metadata + { + { "client-id", this._clientId } + }; + return new CallOptions(headers: metadata); + } + } + + public IProtoSerializationRegistry SerializationRegistry { get; } = new ProtoSerializationRegistry(); + + public void Dispose() + { + _outboundMessagesChannel.Writer.TryComplete(); + _channel?.Dispose(); + } + + private async Task RunReadPump() + { + var channel = GetChannel(); + while (!_shutdownCts.Token.IsCancellationRequested) + { + try + { + await foreach (var message in channel.ResponseStream.ReadAllAsync(_shutdownCts.Token)) + { + // next if message is null + if (message == null) + { + continue; + } + switch (message.MessageCase) + { + case Message.MessageOneofCase.Request: + var request = message.Request ?? throw new InvalidOperationException("Request is null."); + await HandleRequest(request); + break; + case Message.MessageOneofCase.Response: + var response = message.Response ?? throw new InvalidOperationException("Response is null."); + await HandleResponse(response); + break; + case Message.MessageOneofCase.CloudEvent: + var cloudEvent = message.CloudEvent ?? throw new InvalidOperationException("CloudEvent is null."); + await HandlePublish(cloudEvent); + break; + default: + throw new InvalidOperationException($"Unexpected message '{message}'."); + } + } + } + catch (OperationCanceledException) + { + // Time to shut down. + break; + } + catch (Exception ex) when (!_shutdownCts.IsCancellationRequested) + { + _logger.LogError(ex, "Error reading from channel."); + channel = RecreateChannel(channel); + } + catch + { + // Shutdown requested. + break; + } + } + } + + private async ValueTask HandleRequest(RpcRequest request, CancellationToken cancellationToken = default) + { + if (request is null) + { + throw new InvalidOperationException("Request is null."); + } + if (request.Payload is null) + { + throw new InvalidOperationException("Payload is null."); + } + if (request.Target is null) + { + throw new InvalidOperationException("Target is null."); + } + if (request.Source is null) + { + throw new InvalidOperationException("Source is null."); + } + + var agentId = request.Target; + var agent = await EnsureAgentAsync(agentId.FromProtobuf()); + + // Convert payload back to object + var payload = request.Payload; + var message = PayloadToObject(payload); + + var messageContext = new MessageContext(request.RequestId, cancellationToken) + { + Sender = request.Source.FromProtobuf(), + Topic = null, + IsRpc = true + }; + + var result = await agent.OnMessageAsync(message, messageContext); + + if (result is not null) + { + var response = new RpcResponse + { + RequestId = request.RequestId, + Payload = ObjectToPayload(result) + }; + + var responseMessage = new Message + { + Response = response + }; + + await WriteChannelAsync(responseMessage, cancellationToken); + } + } + + private async ValueTask HandleResponse(RpcResponse request, CancellationToken _ = default) + { + if (request is null) + { + throw new InvalidOperationException("Request is null."); + } + if (request.Payload is null) + { + throw new InvalidOperationException("Payload is null."); + } + if (request.RequestId is null) + { + throw new InvalidOperationException("RequestId is null."); + } + + if (_pendingRequests.TryRemove(request.RequestId, out var resultSink)) + { + var payload = request.Payload; + var message = PayloadToObject(payload); + resultSink.SetResult(message); + } + } + + private async ValueTask HandlePublish(CloudEvent evt, CancellationToken cancellationToken = default) + { + if (evt is null) + { + throw new InvalidOperationException("CloudEvent is null."); + } + if (evt.ProtoData is null) + { + throw new InvalidOperationException("ProtoData is null."); + } + if (evt.Attributes is null) + { + throw new InvalidOperationException("Attributes is null."); + } + + var topic = new TopicId(evt.Type, evt.Source); + var sender = new Contracts.AgentId + { + Type = evt.Attributes["agagentsendertype"].CeString, + Key = evt.Attributes["agagentsenderkey"].CeString + }; + + var messageId = evt.Id; + var typeName = evt.Attributes["dataschema"].CeString; + var serializer = SerializationRegistry.GetSerializer(typeName) ?? throw new Exception(); + var message = serializer.Deserialize(evt.ProtoData); + + var messageContext = new MessageContext(messageId, cancellationToken) + { + Sender = sender, + Topic = topic, + IsRpc = false + }; + var agent = await EnsureAgentAsync(sender); + await agent.OnMessageAsync(message, messageContext); + } + + private async Task RunWritePump() + { + var channel = GetChannel(); + var outboundMessages = _outboundMessagesChannel.Reader; + while (!_shutdownCts.IsCancellationRequested) + { + (Message Message, TaskCompletionSource WriteCompletionSource) item = default; + try + { + await outboundMessages.WaitToReadAsync().ConfigureAwait(false); + + // Read the next message if we don't already have an unsent message + // waiting to be sent. + if (!outboundMessages.TryRead(out item)) + { + break; + } + + while (!_shutdownCts.IsCancellationRequested) + { + await channel.RequestStream.WriteAsync(item.Message, _shutdownCts.Token).ConfigureAwait(false); + item.WriteCompletionSource.TrySetResult(); + break; + } + } + catch (OperationCanceledException) + { + // Time to shut down. + item.WriteCompletionSource?.TrySetCanceled(); + break; + } + catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable) + { + // we could not connect to the endpoint - most likely we have the wrong port or failed ssl + // we need to let the user know what port we tried to connect to and then do backoff and retry + _logger.LogError(ex, "Error connecting to GRPC endpoint {Endpoint}.", Environment.GetEnvironmentVariable("AGENT_HOST")); + break; + } + catch (RpcException ex) when (ex.StatusCode == StatusCode.OK) + { + _logger.LogError(ex, "Error writing to channel, continuing (Status OK). {ex}", channel.ToString()); + break; + } + catch (Exception ex) when (!_shutdownCts.IsCancellationRequested) + { + item.WriteCompletionSource?.TrySetException(ex); + _logger.LogError(ex, $"Error writing to channel.{ex}"); + channel = RecreateChannel(channel); + continue; + } + catch + { + // Shutdown requested. + item.WriteCompletionSource?.TrySetCanceled(); + break; + } + } + + while (outboundMessages.TryRead(out var item)) + { + item.WriteCompletionSource.TrySetCanceled(); + } + } + + // private override async ValueTask SendMessageAsync(Payload message, AgentId agentId, AgentId? agent = null, CancellationToken? cancellationToken = default) + // { + // var request = new RpcRequest + // { + // RequestId = Guid.NewGuid().ToString(), + // Source = agent, + // Target = agentId, + // Payload = message, + // }; + + // // Actually send it and wait for the response + // throw new NotImplementedException(); + // } + + // new is intentional + + // public new async ValueTask RuntimeSendRequestAsync(IAgent agent, RpcRequest request, CancellationToken cancellationToken = default) + // { + // var requestId = Guid.NewGuid().ToString(); + // _pendingRequests[requestId] = ((Agent)agent, request.RequestId); + // request.RequestId = requestId; + // await WriteChannelAsync(new Message { Request = request }, cancellationToken).ConfigureAwait(false); + // } + + private async Task WriteChannelAsync(Message message, CancellationToken cancellationToken = default) + { + var tcs = new TaskCompletionSource(); + await _outboundMessagesChannel.Writer.WriteAsync((message, tcs), cancellationToken).ConfigureAwait(false); + } + private AsyncDuplexStreamingCall GetChannel() + { + if (_channel is { } channel) + { + return channel; + } + + lock (_channelLock) + { + if (_channel is not null) + { + return _channel; + } + + return RecreateChannel(null); + } + } + + private AsyncDuplexStreamingCall RecreateChannel(AsyncDuplexStreamingCall? channel) + { + if (_channel is null || _channel == channel) + { + lock (_channelLock) + { + if (_channel is null || _channel == channel) + { + _channel?.Dispose(); + _channel = _client.OpenChannel(cancellationToken: _shutdownCts.Token); + } + } + } + + return _channel; + } + public async Task StartAsync(CancellationToken cancellationToken) + { + _channel = GetChannel(); + _logger.LogInformation("Starting " + GetType().Name + ",connecting to gRPC endpoint " + Environment.GetEnvironmentVariable("AGENT_HOST")); + var didSuppress = false; + if (!ExecutionContext.IsFlowSuppressed()) + { + didSuppress = true; + ExecutionContext.SuppressFlow(); + } + + try + { + _readTask = Task.Run(RunReadPump, cancellationToken); + _writeTask = Task.Run(RunWritePump, cancellationToken); + } + finally + { + if (didSuppress) + { + ExecutionContext.RestoreFlow(); + } + } + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + _shutdownCts.Cancel(); + + _outboundMessagesChannel.Writer.TryComplete(); + + if (_readTask is { } readTask) + { + await readTask.ConfigureAwait(false); + } + + if (_writeTask is { } writeTask) + { + await writeTask.ConfigureAwait(false); + } + lock (_channelLock) + { + _channel?.Dispose(); + } + } + + private async ValueTask EnsureAgentAsync(Contracts.AgentId agentId) + { + if (!this.agentInstances.TryGetValue(agentId, out IHostableAgent? agent)) + { + if (!this.agentFactories.TryGetValue(agentId.Type, out Func>? factoryFunc)) + { + throw new Exception($"Agent with name {agentId.Type} not found."); + } + + agent = await factoryFunc(agentId, this); + this.agentInstances.Add(agentId, agent); + } + + return this.agentInstances[agentId]; + } + + private Payload ObjectToPayload(object message) { + if (!SerializationRegistry.Exists(message.GetType())) + { + SerializationRegistry.RegisterSerializer(message.GetType()); + } + var rpcMessage = (SerializationRegistry.GetSerializer(message.GetType()) ?? throw new Exception()).Serialize(message); + + var typeName = SerializationRegistry.TypeNameResolver.ResolveTypeName(message); + const string PAYLOAD_DATA_CONTENT_TYPE = "application/x-protobuf"; + + // Protobuf any to byte array + Payload payload = new() + { + DataType = typeName, + DataContentType = PAYLOAD_DATA_CONTENT_TYPE, + Data = rpcMessage.ToByteString() + }; + + return payload; + } + + private object PayloadToObject(Payload payload) { + var typeName = payload.DataType; + var data = payload.Data; + var type = SerializationRegistry.TypeNameResolver.ResolveTypeName(typeName); + var serializer = SerializationRegistry.GetSerializer(type) ?? throw new Exception(); + var any = Google.Protobuf.WellKnownTypes.Any.Parser.ParseFrom(data); + return serializer.Deserialize(any); + } + + public async ValueTask SendMessageAsync(object message, Contracts.AgentId recepient, Contracts.AgentId? sender = null, string? messageId = null, CancellationToken cancellationToken = default) + { + if (!SerializationRegistry.Exists(message.GetType())) + { + SerializationRegistry.RegisterSerializer(message.GetType()); + } + + var payload = ObjectToPayload(message); + var request = new RpcRequest + { + RequestId = Guid.NewGuid().ToString(), + Source = (sender ?? new Contracts.AgentId() ).ToProtobuf(), + Target = recepient.ToProtobuf(), + Payload = payload, + }; + + Message msg = new() + { + Request = request + }; + // Create a future that will be completed when the response is received + var resultSink = new ResultSink(); + this._pendingRequests.TryAdd(request.RequestId, resultSink); + await WriteChannelAsync(msg, cancellationToken); + + return await resultSink.Future; + } + + private CloudEvent CreateCloudEvent(Google.Protobuf.WellKnownTypes.Any payload, TopicId topic, string dataType, Contracts.AgentId sender, string messageId) + { + const string PAYLOAD_DATA_CONTENT_TYPE = "application/x-protobuf"; + return new CloudEvent + { + ProtoData = payload, + Type = topic.Type, + Source = topic.Source, + Id = messageId, + Attributes = { + { + "datacontenttype", new CloudEvent.Types.CloudEventAttributeValue { CeString = PAYLOAD_DATA_CONTENT_TYPE } + }, + { + "dataschema", new CloudEvent.Types.CloudEventAttributeValue { CeString = dataType } + }, + { + "agagentsendertype", new CloudEvent.Types.CloudEventAttributeValue { CeString = sender.Type } + }, + { + "agagentsenderkey", new CloudEvent.Types.CloudEventAttributeValue { CeString = sender.Key } + }, + { + "agmsgkind", new CloudEvent.Types.CloudEventAttributeValue { CeString = "publish" } + } + } + }; + } + + public async ValueTask PublishMessageAsync(object message, TopicId topic, Contracts.AgentId? sender = null, string? messageId = null, CancellationToken cancellationToken = default) + { + if (!SerializationRegistry.Exists(message.GetType())) + { + SerializationRegistry.RegisterSerializer(message.GetType()); + } + var protoAny = (SerializationRegistry.GetSerializer(message.GetType()) ?? throw new Exception()).Serialize(message); + var typeName = SerializationRegistry.TypeNameResolver.ResolveTypeName(message); + + var cloudEvent = CreateCloudEvent(protoAny, topic, typeName, sender ?? new Contracts.AgentId(), messageId ?? Guid.NewGuid().ToString()); + + Message msg = new() + { + CloudEvent = cloudEvent + }; + await WriteChannelAsync(msg, cancellationToken); + } + + public ValueTask GetAgentAsync(Contracts.AgentId agentId, bool lazy = true) + { + throw new NotImplementedException(); + } + + public ValueTask GetAgentAsync(AgentType agentType, string key = "default", bool lazy = true) + { + throw new NotImplementedException(); + } + + public ValueTask GetAgentAsync(string agent, string key = "default", bool lazy = true) + { + throw new NotImplementedException(); + } + + public ValueTask> SaveAgentStateAsync(Contracts.AgentId agentId) + { + throw new NotImplementedException(); + } + + public ValueTask LoadAgentStateAsync(Contracts.AgentId agentId, IDictionary state) + { + throw new NotImplementedException(); + } + + public ValueTask GetAgentMetadataAsync(Contracts.AgentId agentId) + { + throw new NotImplementedException(); + } + + public async ValueTask AddSubscriptionAsync(ISubscriptionDefinition subscription) + { + var _ = await this._client.AddSubscriptionAsync(new AddSubscriptionRequest{ + Subscription = subscription.ToProtobuf() + },this.CallOptions); + } + + public ValueTask RemoveSubscriptionAsync(string subscriptionId) + { + throw new NotImplementedException(); + } + + public ValueTask RegisterAgentFactoryAsync(AgentType type, Func> factoryFunc) + { + if (this.agentFactories.ContainsKey(type)) + { + throw new Exception($"Agent with type {type} already exists."); + } + this.agentFactories.Add(type, async (agentId, runtime) => await factoryFunc(agentId, runtime)); + + this._client.RegisterAgentAsync(new RegisterAgentTypeRequest + { + Type = type.Name, + + }, this.CallOptions); + return ValueTask.FromResult(type); + } + + public ValueTask TryGetAgentProxyAsync(Contracts.AgentId agentId) + { + throw new NotImplementedException(); + } + + public ValueTask> SaveStateAsync() + { + throw new NotImplementedException(); + } + + public ValueTask LoadStateAsync(IDictionary state) + { + throw new NotImplementedException(); + } +} + diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentWorkerHostBuilderExtension.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentWorkerHostBuilderExtension.cs new file mode 100644 index 000000000000..7f43b9620f54 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentWorkerHostBuilderExtension.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// GrpcAgentWorkerHostBuilderExtension.cs +using System.Diagnostics; +using Grpc.Core; +using Grpc.Net.Client.Configuration; +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +namespace Microsoft.AutoGen.Core.Grpc; + +public static class GrpcAgentWorkerHostBuilderExtensions +{ + private const string _defaultAgentServiceAddress = "https://localhost:53071"; + + // TODO: How do we ensure AddGrpcAgentWorker and UseInProcessRuntime are mutually exclusive? + public static AgentsAppBuilder AddGrpcAgentWorker(this AgentsAppBuilder builder, string? agentServiceAddress = null) + { + builder.Services.AddGrpcClient(options => + { + options.Address = new Uri(agentServiceAddress ?? builder.Configuration["AGENT_HOST"] ?? _defaultAgentServiceAddress); + options.ChannelOptionsActions.Add(channelOptions => + { + var loggerFactory = new LoggerFactory(); + if (Debugger.IsAttached) + { + channelOptions.HttpHandler = new SocketsHttpHandler + { + EnableMultipleHttp2Connections = false, + KeepAlivePingDelay = TimeSpan.FromSeconds(200), + KeepAlivePingTimeout = TimeSpan.FromSeconds(100), + KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always + }; + } + else + { + channelOptions.HttpHandler = new SocketsHttpHandler + { + EnableMultipleHttp2Connections = true, + KeepAlivePingDelay = TimeSpan.FromSeconds(20), + KeepAlivePingTimeout = TimeSpan.FromSeconds(10), + KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests + }; + } + + var methodConfig = new MethodConfig + { + Names = { MethodName.Default }, + RetryPolicy = new RetryPolicy + { + MaxAttempts = 5, + InitialBackoff = TimeSpan.FromSeconds(1), + MaxBackoff = TimeSpan.FromSeconds(5), + BackoffMultiplier = 1.5, + RetryableStatusCodes = { StatusCode.Unavailable } + } + }; + + channelOptions.ServiceConfig = new() { MethodConfigs = { methodConfig } }; + channelOptions.ThrowOperationCanceledOnCancellation = true; + }); + }); + builder.Services.TryAddSingleton(DistributedContextPropagator.Current); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(sp => (IHostedService)sp.GetRequiredService()); + return builder; + } +} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/IAgentMessageSerializer.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/IAgentMessageSerializer.cs new file mode 100644 index 000000000000..0cc422d54d85 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/IAgentMessageSerializer.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IAgentMessageSerializer.cs + +namespace Microsoft.AutoGen.Core.Grpc; +/// +/// Interface for serializing and deserializing agent messages. +/// +public interface IAgentMessageSerializer +{ + /// + /// Serialize an agent message. + /// + /// The message to serialize. + /// The serialized message. + Google.Protobuf.WellKnownTypes.Any Serialize(object message); + + /// + /// Deserialize an agent message. + /// + /// The message to deserialize. + /// The deserialized message. + object Deserialize(Google.Protobuf.WellKnownTypes.Any message); +} \ No newline at end of file diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/IAgentRuntimeExtensions.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/IAgentRuntimeExtensions.cs new file mode 100644 index 000000000000..8179ff4b494b --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/IAgentRuntimeExtensions.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IAgentRuntimeExtensions.cs + +using System.Diagnostics; +using Google.Protobuf.Collections; +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; +using Microsoft.Extensions.DependencyInjection; +using static Microsoft.AutoGen.Contracts.CloudEvent.Types; + +namespace Microsoft.AutoGen.Core.Grpc; + +public static class GrpcAgentRuntimeExtensions +{ + public static (string?, string?) GetTraceIdAndState(GrpcAgentRuntime runtime, IDictionary metadata) + { + var dcp = runtime.ServiceProvider.GetRequiredService(); + dcp.ExtractTraceIdAndState(metadata, + static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => + { + var metadata = (IDictionary)carrier!; + fieldValues = null; + metadata.TryGetValue(fieldName, out fieldValue); + }, + out var traceParent, + out var traceState); + return (traceParent, traceState); + } + public static (string?, string?) GetTraceIdAndState(GrpcAgentRuntime worker, MapField metadata) + { + var dcp = worker.ServiceProvider.GetRequiredService(); + dcp.ExtractTraceIdAndState(metadata, + static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => + { + var metadata = (MapField)carrier!; + fieldValues = null; + metadata.TryGetValue(fieldName, out var ceValue); + fieldValue = ceValue?.CeString; + }, + out var traceParent, + out var traceState); + return (traceParent, traceState); + } + public static void Update(GrpcAgentRuntime worker, RpcRequest request, Activity? activity = null) + { + var dcp = worker.ServiceProvider.GetRequiredService(); + dcp.Inject(activity, request.Metadata, static (carrier, key, value) => + { + var metadata = (IDictionary)carrier!; + if (metadata.TryGetValue(key, out _)) + { + metadata[key] = value; + } + else + { + metadata.Add(key, value); + } + }); + } + public static void Update(GrpcAgentRuntime worker, CloudEvent cloudEvent, Activity? activity = null) + { + var dcp = worker.ServiceProvider.GetRequiredService(); + dcp.Inject(activity, cloudEvent.Attributes, static (carrier, key, value) => + { + var mapField = (MapField)carrier!; + if (mapField.TryGetValue(key, out var ceValue)) + { + mapField[key] = new CloudEventAttributeValue { CeString = value }; + } + else + { + mapField.Add(key, new CloudEventAttributeValue { CeString = value }); + } + }); + } + + public static IDictionary ExtractMetadata(GrpcAgentRuntime worker, IDictionary metadata) + { + var dcp = worker.ServiceProvider.GetRequiredService(); + var baggage = dcp.ExtractBaggage(metadata, static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => + { + var metadata = (IDictionary)carrier!; + fieldValues = null; + metadata.TryGetValue(fieldName, out fieldValue); + }); + + return baggage as IDictionary ?? new Dictionary(); + } + public static IDictionary ExtractMetadata(GrpcAgentRuntime worker, MapField metadata) + { + var dcp = worker.ServiceProvider.GetRequiredService(); + var baggage = dcp.ExtractBaggage(metadata, static (object? carrier, string fieldName, out string? fieldValue, out IEnumerable? fieldValues) => + { + var metadata = (MapField)carrier!; + fieldValues = null; + metadata.TryGetValue(fieldName, out var ceValue); + fieldValue = ceValue?.CeString; + }); + + return baggage as IDictionary ?? new Dictionary(); + } +} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/IProtoMessageSerializer.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/IProtoMessageSerializer.cs new file mode 100644 index 000000000000..ca690e508d2b --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/IProtoMessageSerializer.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IProtoMessageSerializer.cs + +namespace Microsoft.AutoGen.Core.Grpc; + +public interface IProtoMessageSerializer +{ + Google.Protobuf.WellKnownTypes.Any Serialize(object input); + object Deserialize(Google.Protobuf.WellKnownTypes.Any input); +} \ No newline at end of file diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ISerializationRegistry.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ISerializationRegistry.cs new file mode 100644 index 000000000000..190ed3ec239d --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ISerializationRegistry.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ISerializationRegistry.cs + +namespace Microsoft.AutoGen.Core.Grpc; + +public interface IProtoSerializationRegistry +{ + /// + /// Registers a serializer for the specified type. + /// + /// The type to register. + void RegisterSerializer(System.Type type) => RegisterSerializer(type, new ProtobufMessageSerializer(type)); + + void RegisterSerializer(System.Type type, IProtoMessageSerializer serializer); + + /// + /// Gets the serializer for the specified type. + /// + /// The type to get the serializer for. + /// The serializer for the specified type. + IProtoMessageSerializer? GetSerializer(System.Type type) => GetSerializer(TypeNameResolver.ResolveTypeName(type)); + IProtoMessageSerializer? GetSerializer(string typeName); + + ITypeNameResolver TypeNameResolver { get; } + + bool Exists(System.Type type); +} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ITypeNameResolver.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ITypeNameResolver.cs new file mode 100644 index 000000000000..24de4cb8b449 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ITypeNameResolver.cs @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ITypeNameResolver.cs + +namespace Microsoft.AutoGen.Core.Grpc; + +public interface ITypeNameResolver +{ + string ResolveTypeName(object input); +} \ No newline at end of file diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoSerializationRegistry.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoSerializationRegistry.cs new file mode 100644 index 000000000000..e744bcb0eee9 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoSerializationRegistry.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ProtoSerializationRegistry.cs + +namespace Microsoft.AutoGen.Core.Grpc; + +public class ProtoSerializationRegistry : IProtoSerializationRegistry +{ + private readonly Dictionary _serializers + = new Dictionary(); + + public ITypeNameResolver TypeNameResolver => new ProtoTypeNameResolver(); + + public bool Exists(Type type) + { + return _serializers.ContainsKey(TypeNameResolver.ResolveTypeName(type)); + } + + public IProtoMessageSerializer? GetSerializer(Type type) + { + return GetSerializer(TypeNameResolver.ResolveTypeName(type)); + } + + public IProtoMessageSerializer? GetSerializer(string typeName) + { + _serializers.TryGetValue(typeName, out var serializer); + return serializer; + } + + public void RegisterSerializer(Type type, IProtoMessageSerializer serializer) + { + if (_serializers.ContainsKey(TypeNameResolver.ResolveTypeName(type))) + { + throw new InvalidOperationException($"Serializer already registered for {type.FullName}"); + } + _serializers[TypeNameResolver.ResolveTypeName(type)] = serializer; + } +} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoTypeNameResolver.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoTypeNameResolver.cs new file mode 100644 index 000000000000..a769b0f31c81 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoTypeNameResolver.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ProtoTypeNameResolver.cs + +using Google.Protobuf; + +namespace Microsoft.AutoGen.Core.Grpc; + +public class ProtoTypeNameResolver : ITypeNameResolver +{ + public string ResolveTypeName(object input) + { + if (input is IMessage protoMessage) + { + return protoMessage.Descriptor.FullName; + } + else + { + throw new ArgumentException("Input must be a protobuf message."); + } + } +} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufConversionExtensions.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufConversionExtensions.cs new file mode 100644 index 000000000000..4850b7825afe --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufConversionExtensions.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ProtobufConversionExtensions.cs + +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; + +namespace Microsoft.AutoGen.Core.Grpc; + +public static class ProtobufConversionExtensions +{ + // Convert an ISubscrptionDefinition to a Protobuf Subscription + public static Subscription? ToProtobuf(this ISubscriptionDefinition subscriptionDefinition) + { + // Check if is a TypeSubscription + if (subscriptionDefinition is Contracts.TypeSubscription typeSubscription) + { + return new Subscription + { + Id = typeSubscription.Id, + TypeSubscription = new Protobuf.TypeSubscription + { + TopicType = typeSubscription.TopicType, + AgentType = typeSubscription.AgentType + } + }; + } + + // Check if is a TypePrefixSubscription + if (subscriptionDefinition is Contracts.TypePrefixSubscription typePrefixSubscription) + { + return new Subscription + { + Id = typePrefixSubscription.Id, + TypePrefixSubscription = new Protobuf.TypePrefixSubscription + { + TopicTypePrefix = typePrefixSubscription.TopicTypePrefix, + AgentType = typePrefixSubscription.AgentType + } + }; + } + + return null; + } + + // Convert AgentId from Protobuf to AgentId + public static Contracts.AgentId FromProtobuf(this Protobuf.AgentId agentId) + { + return new Contracts.AgentId(agentId.Type, agentId.Key); + } + + // Convert AgentId from AgentId to Protobuf + public static Protobuf.AgentId ToProtobuf(this Contracts.AgentId agentId) + { + return new Protobuf.AgentId + { + Type = agentId.Type, + Key = agentId.Key + }; + } + +} \ No newline at end of file diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufMessageSerializer.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufMessageSerializer.cs new file mode 100644 index 000000000000..55c1aebfa47d --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufMessageSerializer.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ProtobufMessageSerializer.cs + +using Google.Protobuf; +using Google.Protobuf.WellKnownTypes; + +namespace Microsoft.AutoGen.Core.Grpc; + +/// +/// Interface for serializing and deserializing agent messages. +/// +public class ProtobufMessageSerializer : IProtoMessageSerializer +{ + private System.Type _concreteType; + + public ProtobufMessageSerializer(System.Type concreteType) + { + _concreteType = concreteType; + } + + public object Deserialize(Any message) + { + // Check if the concrete type is a proto IMessage + if (typeof(IMessage).IsAssignableFrom(_concreteType)) + { + var nameOfMethod = nameof(Any.Unpack); + var result = message.GetType().GetMethods().Where(m => m.Name == nameOfMethod && m.IsGenericMethod).First().MakeGenericMethod(_concreteType).Invoke(message, null); + return result as IMessage ?? throw new ArgumentException("Failed to deserialize", nameof(message)); + } + + // Raise an exception if the concrete type is not a proto IMessage + throw new ArgumentException("Concrete type must be a proto IMessage", nameof(_concreteType)); + } + + public Any Serialize(object message) + { + // Check if message is a proto IMessage + if (message is IMessage protoMessage) + { + return Any.Pack(protoMessage); + } + + // Raise an exception if the message is not a proto IMessage + throw new ArgumentException("Message must be a proto IMessage", nameof(message)); + } +} \ No newline at end of file diff --git a/dotnet/src/Microsoft.AutoGen/Core/AgentsApp.cs b/dotnet/src/Microsoft.AutoGen/Core/AgentsApp.cs index bae09a9f1917..cecd8d9ec48d 100644 --- a/dotnet/src/Microsoft.AutoGen/Core/AgentsApp.cs +++ b/dotnet/src/Microsoft.AutoGen/Core/AgentsApp.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Reflection; using Microsoft.AutoGen.Contracts; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -21,6 +22,7 @@ public AgentsAppBuilder(HostApplicationBuilder? baseBuilder = null) } public IServiceCollection Services => this.builder.Services; + public IConfiguration Configuration => this.builder.Configuration; public void AddAgentsFromAssemblies() { From e5da4375002cfdaaea3401ab7453e269895fd56f Mon Sep 17 00:00:00 2001 From: Jacob Alber Date: Wed, 29 Jan 2025 23:03:26 -0500 Subject: [PATCH 02/53] refactor: Extract Message channel logic to MessageRouter --- .../Core.Grpc/GrpcAgentRuntime.cs | 509 +++++++++++------- 1 file changed, 312 insertions(+), 197 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs index 5deba58ae62b..1797182a9704 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs @@ -12,89 +12,130 @@ namespace Microsoft.AutoGen.Core.Grpc; -public sealed class GrpcAgentRuntime( - AgentRpc.AgentRpcClient client, - IHostApplicationLifetime hostApplicationLifetime, - IServiceProvider serviceProvider, - ILogger logger - ) : IAgentRuntime, IDisposable +// TODO: Consider whether we want to just reuse IHandle +internal interface IMessageSink { - private readonly object _channelLock = new(); + public ValueTask OnMessageAsync(TMessage message, CancellationToken cancellation = default); +} - // Request ID -> - private readonly ConcurrentDictionary> _pendingRequests = new(); - private Dictionary>> agentFactories = new(); - private Dictionary agentInstances = new(); +internal sealed class AutoRestartChannel : IDisposable +{ + private readonly object _channelLock = new(); + private readonly AgentRpc.AgentRpcClient _client; + private readonly ILogger _logger; + private readonly CancellationTokenSource _shutdownCts; + private AsyncDuplexStreamingCall? _channel; - private readonly Channel<(Message Message, TaskCompletionSource WriteCompletionSource)> _outboundMessagesChannel = Channel.CreateBounded<(Message, TaskCompletionSource)>(new BoundedChannelOptions(1024) + public AutoRestartChannel(AgentRpc.AgentRpcClient client, + ILogger logger, + CancellationToken shutdownCancellation = default) { - AllowSynchronousContinuations = true, - SingleReader = true, - SingleWriter = false, - FullMode = BoundedChannelFullMode.Wait - }); + _client = client; + _logger = logger; + _shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(shutdownCancellation); + } - private readonly AgentRpc.AgentRpcClient _client = client; - public readonly IServiceProvider ServiceProvider = serviceProvider; + public void EnsureConnected() + { + _logger.LogInformation("Connecting to gRPC endpoint " + Environment.GetEnvironmentVariable("AGENT_HOST")); - private readonly ILogger _logger = logger; - private readonly CancellationTokenSource _shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping); - private AsyncDuplexStreamingCall? _channel; - private Task? _readTask; - private Task? _writeTask; + if (this.RecreateChannel(null) == null) + { + throw new Exception("Failed to connect to gRPC endpoint."); + }; + } - private string _clientId = Guid.NewGuid().ToString(); - private CallOptions CallOptions + public AsyncDuplexStreamingCall StreamingCall { get { - var metadata = new Metadata + if (_channel is { } channel) { - { "client-id", this._clientId } - }; - return new CallOptions(headers: metadata); + return channel; + } + + lock (_channelLock) + { + if (_channel is not null) + { + return _channel; + } + + return RecreateChannel(null); + } } } - public IProtoSerializationRegistry SerializationRegistry { get; } = new ProtoSerializationRegistry(); + public AsyncDuplexStreamingCall RecreateChannel() => RecreateChannel(this._channel); + + private AsyncDuplexStreamingCall RecreateChannel(AsyncDuplexStreamingCall? ownedChannel) + { + // Make sure we are only re-creating the channel if it does not exit or we are the owner. + if (_channel is null || _channel == ownedChannel) + { + lock (_channelLock) + { + if (_channel is null || _channel == ownedChannel) + { + _channel?.Dispose(); + _channel = _client.OpenChannel(cancellationToken: _shutdownCts.Token); + } + } + } + + return _channel; + } public void Dispose() { - _outboundMessagesChannel.Writer.TryComplete(); - _channel?.Dispose(); + IDisposable? channelDisposable = Interlocked.Exchange(ref this._channel, null); + channelDisposable?.Dispose(); } +} + +internal sealed class MessageRouter(AgentRpc.AgentRpcClient client, + IMessageSink incomingMessageSink, + ILogger logger, + CancellationToken shutdownCancellation = default) : IDisposable +{ + private static readonly BoundedChannelOptions DefaultChannelOptions = new BoundedChannelOptions(1024) + { + AllowSynchronousContinuations = true, + SingleReader = true, + SingleWriter = false, + FullMode = BoundedChannelFullMode.Wait + }; + + private readonly ILogger _logger = logger; + + private readonly CancellationTokenSource _shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(shutdownCancellation); + + private readonly IMessageSink _incomingMessageSink = incomingMessageSink; + private readonly Channel<(Message Message, TaskCompletionSource WriteCompletionSource)> _outboundMessagesChannel + // TODO: Enable a way to configure the channel options + = Channel.CreateBounded<(Message, TaskCompletionSource)>(DefaultChannelOptions); + + private readonly AutoRestartChannel _incomingMessageChannel = new AutoRestartChannel(client, logger, shutdownCancellation); + + private Task? _readTask; + private Task? _writeTask; private async Task RunReadPump() { - var channel = GetChannel(); + var cachedChannel = _incomingMessageChannel.StreamingCall; while (!_shutdownCts.Token.IsCancellationRequested) { try { - await foreach (var message in channel.ResponseStream.ReadAllAsync(_shutdownCts.Token)) + await foreach (var message in cachedChannel.ResponseStream.ReadAllAsync(_shutdownCts.Token)) { // next if message is null if (message == null) { continue; } - switch (message.MessageCase) - { - case Message.MessageOneofCase.Request: - var request = message.Request ?? throw new InvalidOperationException("Request is null."); - await HandleRequest(request); - break; - case Message.MessageOneofCase.Response: - var response = message.Response ?? throw new InvalidOperationException("Response is null."); - await HandleResponse(response); - break; - case Message.MessageOneofCase.CloudEvent: - var cloudEvent = message.CloudEvent ?? throw new InvalidOperationException("CloudEvent is null."); - await HandlePublish(cloudEvent); - break; - default: - throw new InvalidOperationException($"Unexpected message '{message}'."); - } + + await _incomingMessageSink.OnMessageAsync(message, _shutdownCts.Token); } } catch (OperationCanceledException) @@ -105,14 +146,199 @@ private async Task RunReadPump() catch (Exception ex) when (!_shutdownCts.IsCancellationRequested) { _logger.LogError(ex, "Error reading from channel."); - channel = RecreateChannel(channel); + cachedChannel = this._incomingMessageChannel.RecreateChannel(); + } + catch + { + // Shutdown requested. + break; + } + } + } + + private async Task RunWritePump() + { + var cachedChannel = this._incomingMessageChannel.StreamingCall; + var outboundMessages = _outboundMessagesChannel.Reader; + while (!_shutdownCts.IsCancellationRequested) + { + (Message Message, TaskCompletionSource WriteCompletionSource) item = default; + try + { + await outboundMessages.WaitToReadAsync().ConfigureAwait(false); + + // Read the next message if we don't already have an unsent message + // waiting to be sent. + if (!outboundMessages.TryRead(out item)) + { + break; + } + + while (!_shutdownCts.IsCancellationRequested) + { + await cachedChannel.RequestStream.WriteAsync(item.Message, _shutdownCts.Token).ConfigureAwait(false); + item.WriteCompletionSource.TrySetResult(); + break; + } + } + catch (OperationCanceledException) + { + // Time to shut down. + item.WriteCompletionSource?.TrySetCanceled(); + break; + } + catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable) + { + // we could not connect to the endpoint - most likely we have the wrong port or failed ssl + // we need to let the user know what port we tried to connect to and then do backoff and retry + _logger.LogError(ex, "Error connecting to GRPC endpoint {Endpoint}.", Environment.GetEnvironmentVariable("AGENT_HOST")); + break; + } + catch (RpcException ex) when (ex.StatusCode == StatusCode.OK) + { + _logger.LogError(ex, "Error writing to channel, continuing (Status OK). {ex}", cachedChannel.ToString()); + break; + } + catch (Exception ex) when (!_shutdownCts.IsCancellationRequested) + { + item.WriteCompletionSource?.TrySetException(ex); + _logger.LogError(ex, $"Error writing to channel.{ex}"); + cachedChannel = this._incomingMessageChannel.RecreateChannel(); + continue; } catch { // Shutdown requested. + item.WriteCompletionSource?.TrySetCanceled(); break; } } + + while (outboundMessages.TryRead(out var item)) + { + item.WriteCompletionSource.TrySetCanceled(); + } + } + + public ValueTask RouteMessageAsync(Message message, CancellationToken cancellation = default) + { + var tcs = new TaskCompletionSource(); + return _outboundMessagesChannel.Writer.WriteAsync((message, tcs), cancellation); + } + + public ValueTask StartAsync(CancellationToken cancellation) + { + // TODO: Should we error out on a noncancellable token? + + this._incomingMessageChannel.EnsureConnected(); + var didSuppress = false; + + // Make sure we do not mistakenly flow the ExecutionContext into the background pumping tasks. + if (!ExecutionContext.IsFlowSuppressed()) + { + didSuppress = true; + ExecutionContext.SuppressFlow(); + } + + try + { + _readTask = Task.Run(RunReadPump, cancellation); + _writeTask = Task.Run(RunWritePump, cancellation); + + return ValueTask.CompletedTask; + } + catch (Exception ex) + { + return ValueTask.FromException(ex); + } + finally + { + if (didSuppress) + { + ExecutionContext.RestoreFlow(); + } + } + } + + // No point in returning a ValueTask here, since we are awaiting the two tasks + public async Task StopAsync() + { + _shutdownCts.Cancel(); + + _outboundMessagesChannel.Writer.TryComplete(); + + List pendingTasks = new(); + if (_readTask is { } readTask) + { + pendingTasks.Add(readTask); + } + + if (_writeTask is { } writeTask) + { + pendingTasks.Add(writeTask); + } + + await Task.WhenAll(pendingTasks).ConfigureAwait(false); + + this._incomingMessageChannel.Dispose(); + } + + public void Dispose() + { + _outboundMessagesChannel.Writer.TryComplete(); + this._incomingMessageChannel.Dispose(); + } +} + +public sealed class GrpcAgentRuntime: IHostedService, IAgentRuntime, IMessageSink, IDisposable +{ + public GrpcAgentRuntime(AgentRpc.AgentRpcClient client, + IHostApplicationLifetime hostApplicationLifetime, + IServiceProvider serviceProvider, + ILogger logger) + { + this._client = client; + this._logger = logger; + this._shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping); + + this._messageRouter = new MessageRouter(client, this, logger, this._shutdownCts.Token); + + this.ServiceProvider = serviceProvider; + } + + // Request ID -> + private readonly ConcurrentDictionary> _pendingRequests = new(); + + private Dictionary>> agentFactories = new(); + private Dictionary agentInstances = new(); + + private readonly AgentRpc.AgentRpcClient _client; + private readonly MessageRouter _messageRouter; + + private readonly ILogger _logger; + private readonly CancellationTokenSource _shutdownCts; + + public IServiceProvider ServiceProvider { get; } + + private string _clientId = Guid.NewGuid().ToString(); + private CallOptions CallOptions + { + get + { + var metadata = new Metadata + { + { "client-id", this._clientId } + }; + return new CallOptions(headers: metadata); + } + } + + public IProtoSerializationRegistry SerializationRegistry { get; } = new ProtoSerializationRegistry(); + + public void Dispose() + { + this._shutdownCts.Cancel(); + this._messageRouter.Dispose(); } private async ValueTask HandleRequest(RpcRequest request, CancellationToken cancellationToken = default) @@ -163,7 +389,7 @@ private async ValueTask HandleRequest(RpcRequest request, CancellationToken canc Response = response }; - await WriteChannelAsync(responseMessage, cancellationToken); + await this._messageRouter.RouteMessageAsync(responseMessage, cancellationToken); } } @@ -227,69 +453,7 @@ private async ValueTask HandlePublish(CloudEvent evt, CancellationToken cancella await agent.OnMessageAsync(message, messageContext); } - private async Task RunWritePump() - { - var channel = GetChannel(); - var outboundMessages = _outboundMessagesChannel.Reader; - while (!_shutdownCts.IsCancellationRequested) - { - (Message Message, TaskCompletionSource WriteCompletionSource) item = default; - try - { - await outboundMessages.WaitToReadAsync().ConfigureAwait(false); - - // Read the next message if we don't already have an unsent message - // waiting to be sent. - if (!outboundMessages.TryRead(out item)) - { - break; - } - - while (!_shutdownCts.IsCancellationRequested) - { - await channel.RequestStream.WriteAsync(item.Message, _shutdownCts.Token).ConfigureAwait(false); - item.WriteCompletionSource.TrySetResult(); - break; - } - } - catch (OperationCanceledException) - { - // Time to shut down. - item.WriteCompletionSource?.TrySetCanceled(); - break; - } - catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable) - { - // we could not connect to the endpoint - most likely we have the wrong port or failed ssl - // we need to let the user know what port we tried to connect to and then do backoff and retry - _logger.LogError(ex, "Error connecting to GRPC endpoint {Endpoint}.", Environment.GetEnvironmentVariable("AGENT_HOST")); - break; - } - catch (RpcException ex) when (ex.StatusCode == StatusCode.OK) - { - _logger.LogError(ex, "Error writing to channel, continuing (Status OK). {ex}", channel.ToString()); - break; - } - catch (Exception ex) when (!_shutdownCts.IsCancellationRequested) - { - item.WriteCompletionSource?.TrySetException(ex); - _logger.LogError(ex, $"Error writing to channel.{ex}"); - channel = RecreateChannel(channel); - continue; - } - catch - { - // Shutdown requested. - item.WriteCompletionSource?.TrySetCanceled(); - break; - } - } - - while (outboundMessages.TryRead(out var item)) - { - item.WriteCompletionSource.TrySetCanceled(); - } - } + // private override async ValueTask SendMessageAsync(Payload message, AgentId agentId, AgentId? agent = null, CancellationToken? cancellationToken = default) // { @@ -315,89 +479,17 @@ private async Task RunWritePump() // await WriteChannelAsync(new Message { Request = request }, cancellationToken).ConfigureAwait(false); // } - private async Task WriteChannelAsync(Message message, CancellationToken cancellationToken = default) + + public ValueTask StartAsync(CancellationToken cancellationToken) { - var tcs = new TaskCompletionSource(); - await _outboundMessagesChannel.Writer.WriteAsync((message, tcs), cancellationToken).ConfigureAwait(false); + return this._messageRouter.StartAsync(cancellationToken); } - private AsyncDuplexStreamingCall GetChannel() - { - if (_channel is { } channel) - { - return channel; - } - lock (_channelLock) - { - if (_channel is not null) - { - return _channel; - } + Task IHostedService.StartAsync(CancellationToken cancellationToken) => this._messageRouter.StartAsync(cancellationToken).AsTask(); - return RecreateChannel(null); - } - } - - private AsyncDuplexStreamingCall RecreateChannel(AsyncDuplexStreamingCall? channel) + public Task StopAsync(CancellationToken cancellationToken) { - if (_channel is null || _channel == channel) - { - lock (_channelLock) - { - if (_channel is null || _channel == channel) - { - _channel?.Dispose(); - _channel = _client.OpenChannel(cancellationToken: _shutdownCts.Token); - } - } - } - - return _channel; - } - public async Task StartAsync(CancellationToken cancellationToken) - { - _channel = GetChannel(); - _logger.LogInformation("Starting " + GetType().Name + ",connecting to gRPC endpoint " + Environment.GetEnvironmentVariable("AGENT_HOST")); - var didSuppress = false; - if (!ExecutionContext.IsFlowSuppressed()) - { - didSuppress = true; - ExecutionContext.SuppressFlow(); - } - - try - { - _readTask = Task.Run(RunReadPump, cancellationToken); - _writeTask = Task.Run(RunWritePump, cancellationToken); - } - finally - { - if (didSuppress) - { - ExecutionContext.RestoreFlow(); - } - } - } - - public async Task StopAsync(CancellationToken cancellationToken) - { - _shutdownCts.Cancel(); - - _outboundMessagesChannel.Writer.TryComplete(); - - if (_readTask is { } readTask) - { - await readTask.ConfigureAwait(false); - } - - if (_writeTask is { } writeTask) - { - await writeTask.ConfigureAwait(false); - } - lock (_channelLock) - { - _channel?.Dispose(); - } + return this._messageRouter.StopAsync(); } private async ValueTask EnsureAgentAsync(Contracts.AgentId agentId) @@ -466,10 +558,11 @@ private object PayloadToObject(Payload payload) { { Request = request }; + // Create a future that will be completed when the response is received var resultSink = new ResultSink(); this._pendingRequests.TryAdd(request.RequestId, resultSink); - await WriteChannelAsync(msg, cancellationToken); + await this._messageRouter.RouteMessageAsync(msg, cancellationToken); return await resultSink.Future; } @@ -518,7 +611,8 @@ public async ValueTask PublishMessageAsync(object message, TopicId topic, Contra { CloudEvent = cloudEvent }; - await WriteChannelAsync(msg, cancellationToken); + + await this._messageRouter.RouteMessageAsync(msg, cancellationToken); } public ValueTask GetAgentAsync(Contracts.AgentId agentId, bool lazy = true) @@ -593,5 +687,26 @@ public ValueTask LoadStateAsync(IDictionary state) { throw new NotImplementedException(); } + + public async ValueTask OnMessageAsync(Message message, CancellationToken cancellation = default) + { + switch (message.MessageCase) + { + case Message.MessageOneofCase.Request: + var request = message.Request ?? throw new InvalidOperationException("Request is null."); + await HandleRequest(request); + break; + case Message.MessageOneofCase.Response: + var response = message.Response ?? throw new InvalidOperationException("Response is null."); + await HandleResponse(response); + break; + case Message.MessageOneofCase.CloudEvent: + var cloudEvent = message.CloudEvent ?? throw new InvalidOperationException("CloudEvent is null."); + await HandlePublish(cloudEvent); + break; + default: + throw new InvalidOperationException($"Unexpected message '{message}'."); + } + } } From df3ca7234318d11b6b1c5279e7abe12e5165fdc4 Mon Sep 17 00:00:00 2001 From: Jacob Alber Date: Wed, 29 Jan 2025 23:05:20 -0500 Subject: [PATCH 03/53] refactor: Move GrpcMessageRouter to own file --- .../Core.Grpc/GrpcAgentRuntime.cs | 281 +---------------- .../Core.Grpc/GrpcMessageRouter.cs | 288 ++++++++++++++++++ 2 files changed, 290 insertions(+), 279 deletions(-) create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcMessageRouter.cs diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs index 1797182a9704..b1f5d43669e6 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs @@ -2,7 +2,6 @@ // GrpcAgentRuntime.cs using System.Collections.Concurrent; -using System.Threading.Channels; using Google.Protobuf; using Grpc.Core; using Microsoft.AutoGen.Contracts; @@ -12,283 +11,7 @@ namespace Microsoft.AutoGen.Core.Grpc; -// TODO: Consider whether we want to just reuse IHandle -internal interface IMessageSink -{ - public ValueTask OnMessageAsync(TMessage message, CancellationToken cancellation = default); -} - -internal sealed class AutoRestartChannel : IDisposable -{ - private readonly object _channelLock = new(); - private readonly AgentRpc.AgentRpcClient _client; - private readonly ILogger _logger; - private readonly CancellationTokenSource _shutdownCts; - private AsyncDuplexStreamingCall? _channel; - - public AutoRestartChannel(AgentRpc.AgentRpcClient client, - ILogger logger, - CancellationToken shutdownCancellation = default) - { - _client = client; - _logger = logger; - _shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(shutdownCancellation); - } - - public void EnsureConnected() - { - _logger.LogInformation("Connecting to gRPC endpoint " + Environment.GetEnvironmentVariable("AGENT_HOST")); - - if (this.RecreateChannel(null) == null) - { - throw new Exception("Failed to connect to gRPC endpoint."); - }; - } - - public AsyncDuplexStreamingCall StreamingCall - { - get - { - if (_channel is { } channel) - { - return channel; - } - - lock (_channelLock) - { - if (_channel is not null) - { - return _channel; - } - - return RecreateChannel(null); - } - } - } - - public AsyncDuplexStreamingCall RecreateChannel() => RecreateChannel(this._channel); - - private AsyncDuplexStreamingCall RecreateChannel(AsyncDuplexStreamingCall? ownedChannel) - { - // Make sure we are only re-creating the channel if it does not exit or we are the owner. - if (_channel is null || _channel == ownedChannel) - { - lock (_channelLock) - { - if (_channel is null || _channel == ownedChannel) - { - _channel?.Dispose(); - _channel = _client.OpenChannel(cancellationToken: _shutdownCts.Token); - } - } - } - - return _channel; - } - - public void Dispose() - { - IDisposable? channelDisposable = Interlocked.Exchange(ref this._channel, null); - channelDisposable?.Dispose(); - } -} - -internal sealed class MessageRouter(AgentRpc.AgentRpcClient client, - IMessageSink incomingMessageSink, - ILogger logger, - CancellationToken shutdownCancellation = default) : IDisposable -{ - private static readonly BoundedChannelOptions DefaultChannelOptions = new BoundedChannelOptions(1024) - { - AllowSynchronousContinuations = true, - SingleReader = true, - SingleWriter = false, - FullMode = BoundedChannelFullMode.Wait - }; - - private readonly ILogger _logger = logger; - - private readonly CancellationTokenSource _shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(shutdownCancellation); - - private readonly IMessageSink _incomingMessageSink = incomingMessageSink; - private readonly Channel<(Message Message, TaskCompletionSource WriteCompletionSource)> _outboundMessagesChannel - // TODO: Enable a way to configure the channel options - = Channel.CreateBounded<(Message, TaskCompletionSource)>(DefaultChannelOptions); - - private readonly AutoRestartChannel _incomingMessageChannel = new AutoRestartChannel(client, logger, shutdownCancellation); - - private Task? _readTask; - private Task? _writeTask; - - private async Task RunReadPump() - { - var cachedChannel = _incomingMessageChannel.StreamingCall; - while (!_shutdownCts.Token.IsCancellationRequested) - { - try - { - await foreach (var message in cachedChannel.ResponseStream.ReadAllAsync(_shutdownCts.Token)) - { - // next if message is null - if (message == null) - { - continue; - } - - await _incomingMessageSink.OnMessageAsync(message, _shutdownCts.Token); - } - } - catch (OperationCanceledException) - { - // Time to shut down. - break; - } - catch (Exception ex) when (!_shutdownCts.IsCancellationRequested) - { - _logger.LogError(ex, "Error reading from channel."); - cachedChannel = this._incomingMessageChannel.RecreateChannel(); - } - catch - { - // Shutdown requested. - break; - } - } - } - - private async Task RunWritePump() - { - var cachedChannel = this._incomingMessageChannel.StreamingCall; - var outboundMessages = _outboundMessagesChannel.Reader; - while (!_shutdownCts.IsCancellationRequested) - { - (Message Message, TaskCompletionSource WriteCompletionSource) item = default; - try - { - await outboundMessages.WaitToReadAsync().ConfigureAwait(false); - - // Read the next message if we don't already have an unsent message - // waiting to be sent. - if (!outboundMessages.TryRead(out item)) - { - break; - } - while (!_shutdownCts.IsCancellationRequested) - { - await cachedChannel.RequestStream.WriteAsync(item.Message, _shutdownCts.Token).ConfigureAwait(false); - item.WriteCompletionSource.TrySetResult(); - break; - } - } - catch (OperationCanceledException) - { - // Time to shut down. - item.WriteCompletionSource?.TrySetCanceled(); - break; - } - catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable) - { - // we could not connect to the endpoint - most likely we have the wrong port or failed ssl - // we need to let the user know what port we tried to connect to and then do backoff and retry - _logger.LogError(ex, "Error connecting to GRPC endpoint {Endpoint}.", Environment.GetEnvironmentVariable("AGENT_HOST")); - break; - } - catch (RpcException ex) when (ex.StatusCode == StatusCode.OK) - { - _logger.LogError(ex, "Error writing to channel, continuing (Status OK). {ex}", cachedChannel.ToString()); - break; - } - catch (Exception ex) when (!_shutdownCts.IsCancellationRequested) - { - item.WriteCompletionSource?.TrySetException(ex); - _logger.LogError(ex, $"Error writing to channel.{ex}"); - cachedChannel = this._incomingMessageChannel.RecreateChannel(); - continue; - } - catch - { - // Shutdown requested. - item.WriteCompletionSource?.TrySetCanceled(); - break; - } - } - - while (outboundMessages.TryRead(out var item)) - { - item.WriteCompletionSource.TrySetCanceled(); - } - } - - public ValueTask RouteMessageAsync(Message message, CancellationToken cancellation = default) - { - var tcs = new TaskCompletionSource(); - return _outboundMessagesChannel.Writer.WriteAsync((message, tcs), cancellation); - } - - public ValueTask StartAsync(CancellationToken cancellation) - { - // TODO: Should we error out on a noncancellable token? - - this._incomingMessageChannel.EnsureConnected(); - var didSuppress = false; - - // Make sure we do not mistakenly flow the ExecutionContext into the background pumping tasks. - if (!ExecutionContext.IsFlowSuppressed()) - { - didSuppress = true; - ExecutionContext.SuppressFlow(); - } - - try - { - _readTask = Task.Run(RunReadPump, cancellation); - _writeTask = Task.Run(RunWritePump, cancellation); - - return ValueTask.CompletedTask; - } - catch (Exception ex) - { - return ValueTask.FromException(ex); - } - finally - { - if (didSuppress) - { - ExecutionContext.RestoreFlow(); - } - } - } - - // No point in returning a ValueTask here, since we are awaiting the two tasks - public async Task StopAsync() - { - _shutdownCts.Cancel(); - - _outboundMessagesChannel.Writer.TryComplete(); - - List pendingTasks = new(); - if (_readTask is { } readTask) - { - pendingTasks.Add(readTask); - } - - if (_writeTask is { } writeTask) - { - pendingTasks.Add(writeTask); - } - - await Task.WhenAll(pendingTasks).ConfigureAwait(false); - - this._incomingMessageChannel.Dispose(); - } - - public void Dispose() - { - _outboundMessagesChannel.Writer.TryComplete(); - this._incomingMessageChannel.Dispose(); - } -} public sealed class GrpcAgentRuntime: IHostedService, IAgentRuntime, IMessageSink, IDisposable { @@ -301,7 +24,7 @@ public GrpcAgentRuntime(AgentRpc.AgentRpcClient client, this._logger = logger; this._shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping); - this._messageRouter = new MessageRouter(client, this, logger, this._shutdownCts.Token); + this._messageRouter = new GrpcMessageRouter(client, this, logger, this._shutdownCts.Token); this.ServiceProvider = serviceProvider; } @@ -313,7 +36,7 @@ public GrpcAgentRuntime(AgentRpc.AgentRpcClient client, private Dictionary agentInstances = new(); private readonly AgentRpc.AgentRpcClient _client; - private readonly MessageRouter _messageRouter; + private readonly GrpcMessageRouter _messageRouter; private readonly ILogger _logger; private readonly CancellationTokenSource _shutdownCts; diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcMessageRouter.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcMessageRouter.cs new file mode 100644 index 000000000000..0ecf8ee1413e --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcMessageRouter.cs @@ -0,0 +1,288 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// GrpcMessageRouter.cs + +using System.Threading.Channels; +using Grpc.Core; +using Microsoft.Extensions.Logging; +using Microsoft.AutoGen.Protobuf; + +namespace Microsoft.AutoGen.Core.Grpc; + +// TODO: Consider whether we want to just reuse IHandle +internal interface IMessageSink +{ + public ValueTask OnMessageAsync(TMessage message, CancellationToken cancellation = default); +} + +internal sealed class AutoRestartChannel : IDisposable +{ + private readonly object _channelLock = new(); + private readonly AgentRpc.AgentRpcClient _client; + private readonly ILogger _logger; + private readonly CancellationTokenSource _shutdownCts; + private AsyncDuplexStreamingCall? _channel; + + public AutoRestartChannel(AgentRpc.AgentRpcClient client, + ILogger logger, + CancellationToken shutdownCancellation = default) + { + _client = client; + _logger = logger; + _shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(shutdownCancellation); + } + + public void EnsureConnected() + { + _logger.LogInformation("Connecting to gRPC endpoint " + Environment.GetEnvironmentVariable("AGENT_HOST")); + + if (this.RecreateChannel(null) == null) + { + throw new Exception("Failed to connect to gRPC endpoint."); + }; + } + + public AsyncDuplexStreamingCall StreamingCall + { + get + { + if (_channel is { } channel) + { + return channel; + } + + lock (_channelLock) + { + if (_channel is not null) + { + return _channel; + } + + return RecreateChannel(null); + } + } + } + + public AsyncDuplexStreamingCall RecreateChannel() => RecreateChannel(this._channel); + + private AsyncDuplexStreamingCall RecreateChannel(AsyncDuplexStreamingCall? ownedChannel) + { + // Make sure we are only re-creating the channel if it does not exit or we are the owner. + if (_channel is null || _channel == ownedChannel) + { + lock (_channelLock) + { + if (_channel is null || _channel == ownedChannel) + { + _channel?.Dispose(); + _channel = _client.OpenChannel(cancellationToken: _shutdownCts.Token); + } + } + } + + return _channel; + } + + public void Dispose() + { + IDisposable? channelDisposable = Interlocked.Exchange(ref this._channel, null); + channelDisposable?.Dispose(); + } +} + +internal sealed class GrpcMessageRouter(AgentRpc.AgentRpcClient client, + IMessageSink incomingMessageSink, + ILogger logger, + CancellationToken shutdownCancellation = default) : IDisposable +{ + private static readonly BoundedChannelOptions DefaultChannelOptions = new BoundedChannelOptions(1024) + { + AllowSynchronousContinuations = true, + SingleReader = true, + SingleWriter = false, + FullMode = BoundedChannelFullMode.Wait + }; + + private readonly ILogger _logger = logger; + + private readonly CancellationTokenSource _shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(shutdownCancellation); + + private readonly IMessageSink _incomingMessageSink = incomingMessageSink; + private readonly Channel<(Message Message, TaskCompletionSource WriteCompletionSource)> _outboundMessagesChannel + // TODO: Enable a way to configure the channel options + = Channel.CreateBounded<(Message, TaskCompletionSource)>(DefaultChannelOptions); + + private readonly AutoRestartChannel _incomingMessageChannel = new AutoRestartChannel(client, logger, shutdownCancellation); + + private Task? _readTask; + private Task? _writeTask; + + private async Task RunReadPump() + { + var cachedChannel = _incomingMessageChannel.StreamingCall; + while (!_shutdownCts.Token.IsCancellationRequested) + { + try + { + await foreach (var message in cachedChannel.ResponseStream.ReadAllAsync(_shutdownCts.Token)) + { + // next if message is null + if (message == null) + { + continue; + } + + await _incomingMessageSink.OnMessageAsync(message, _shutdownCts.Token); + } + } + catch (OperationCanceledException) + { + // Time to shut down. + break; + } + catch (Exception ex) when (!_shutdownCts.IsCancellationRequested) + { + _logger.LogError(ex, "Error reading from channel."); + cachedChannel = this._incomingMessageChannel.RecreateChannel(); + } + catch + { + // Shutdown requested. + break; + } + } + } + + private async Task RunWritePump() + { + var cachedChannel = this._incomingMessageChannel.StreamingCall; + var outboundMessages = _outboundMessagesChannel.Reader; + while (!_shutdownCts.IsCancellationRequested) + { + (Message Message, TaskCompletionSource WriteCompletionSource) item = default; + try + { + await outboundMessages.WaitToReadAsync().ConfigureAwait(false); + + // Read the next message if we don't already have an unsent message + // waiting to be sent. + if (!outboundMessages.TryRead(out item)) + { + break; + } + + while (!_shutdownCts.IsCancellationRequested) + { + await cachedChannel.RequestStream.WriteAsync(item.Message, _shutdownCts.Token).ConfigureAwait(false); + item.WriteCompletionSource.TrySetResult(); + break; + } + } + catch (OperationCanceledException) + { + // Time to shut down. + item.WriteCompletionSource?.TrySetCanceled(); + break; + } + catch (RpcException ex) when (ex.StatusCode == StatusCode.Unavailable) + { + // we could not connect to the endpoint - most likely we have the wrong port or failed ssl + // we need to let the user know what port we tried to connect to and then do backoff and retry + _logger.LogError(ex, "Error connecting to GRPC endpoint {Endpoint}.", Environment.GetEnvironmentVariable("AGENT_HOST")); + break; + } + catch (RpcException ex) when (ex.StatusCode == StatusCode.OK) + { + _logger.LogError(ex, "Error writing to channel, continuing (Status OK). {ex}", cachedChannel.ToString()); + break; + } + catch (Exception ex) when (!_shutdownCts.IsCancellationRequested) + { + item.WriteCompletionSource?.TrySetException(ex); + _logger.LogError(ex, $"Error writing to channel.{ex}"); + cachedChannel = this._incomingMessageChannel.RecreateChannel(); + continue; + } + catch + { + // Shutdown requested. + item.WriteCompletionSource?.TrySetCanceled(); + break; + } + } + + while (outboundMessages.TryRead(out var item)) + { + item.WriteCompletionSource.TrySetCanceled(); + } + } + + public ValueTask RouteMessageAsync(Message message, CancellationToken cancellation = default) + { + var tcs = new TaskCompletionSource(); + return _outboundMessagesChannel.Writer.WriteAsync((message, tcs), cancellation); + } + + public ValueTask StartAsync(CancellationToken cancellation) + { + // TODO: Should we error out on a noncancellable token? + + this._incomingMessageChannel.EnsureConnected(); + var didSuppress = false; + + // Make sure we do not mistakenly flow the ExecutionContext into the background pumping tasks. + if (!ExecutionContext.IsFlowSuppressed()) + { + didSuppress = true; + ExecutionContext.SuppressFlow(); + } + + try + { + _readTask = Task.Run(RunReadPump, cancellation); + _writeTask = Task.Run(RunWritePump, cancellation); + + return ValueTask.CompletedTask; + } + catch (Exception ex) + { + return ValueTask.FromException(ex); + } + finally + { + if (didSuppress) + { + ExecutionContext.RestoreFlow(); + } + } + } + + // No point in returning a ValueTask here, since we are awaiting the two tasks + public async Task StopAsync() + { + _shutdownCts.Cancel(); + + _outboundMessagesChannel.Writer.TryComplete(); + + List pendingTasks = new(); + if (_readTask is { } readTask) + { + pendingTasks.Add(readTask); + } + + if (_writeTask is { } writeTask) + { + pendingTasks.Add(writeTask); + } + + await Task.WhenAll(pendingTasks).ConfigureAwait(false); + + this._incomingMessageChannel.Dispose(); + } + + public void Dispose() + { + _outboundMessagesChannel.Writer.TryComplete(); + this._incomingMessageChannel.Dispose(); + } +} + From 9d981dcca9673dbe3c954ec5e73779623e1e29cc Mon Sep 17 00:00:00 2001 From: Jacob Alber Date: Wed, 29 Jan 2025 23:51:03 -0500 Subject: [PATCH 04/53] refactor: Add default IAgentRuntime.GetAgent implementations --- dotnet/src/Microsoft.AutoGen/Contracts/IAgentRuntime.cs | 6 ++++-- dotnet/src/Microsoft.AutoGen/Core/InProcessRuntime.cs | 6 ------ dotnet/test/Microsoft.AutoGen.Core.Tests/AgentTests.cs | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/Contracts/IAgentRuntime.cs b/dotnet/src/Microsoft.AutoGen/Contracts/IAgentRuntime.cs index 0d84fbe72d37..bb360617dc09 100644 --- a/dotnet/src/Microsoft.AutoGen/Contracts/IAgentRuntime.cs +++ b/dotnet/src/Microsoft.AutoGen/Contracts/IAgentRuntime.cs @@ -53,7 +53,8 @@ public interface IAgentRuntime : ISaveState /// An optional key to specify variations of the agent. Defaults to "default". /// If true, the agent is fetched lazily. /// A task representing the asynchronous operation, returning the agent's ID. - public ValueTask GetAgentAsync(AgentType agentType, string key = "default", bool lazy = true/*, CancellationToken? = default*/); + public ValueTask GetAgentAsync(AgentType agentType, string key = "default", bool lazy = true/*, CancellationToken? = default*/) + => this.GetAgentAsync(new AgentId(agentType, key), lazy); /// /// Retrieves an agent by its string representation. @@ -62,7 +63,8 @@ public interface IAgentRuntime : ISaveState /// An optional key to specify variations of the agent. Defaults to "default". /// If true, the agent is fetched lazily. /// A task representing the asynchronous operation, returning the agent's ID. - public ValueTask GetAgentAsync(string agent, string key = "default", bool lazy = true/*, CancellationToken? = default*/); + public ValueTask GetAgentAsync(string agent, string key = "default", bool lazy = true/*, CancellationToken? = default*/) + => this.GetAgentAsync(new AgentId(agent, key), lazy); /// /// Saves the state of an agent. diff --git a/dotnet/src/Microsoft.AutoGen/Core/InProcessRuntime.cs b/dotnet/src/Microsoft.AutoGen/Core/InProcessRuntime.cs index 69b2d314e550..791376ae56e8 100644 --- a/dotnet/src/Microsoft.AutoGen/Core/InProcessRuntime.cs +++ b/dotnet/src/Microsoft.AutoGen/Core/InProcessRuntime.cs @@ -140,12 +140,6 @@ public async ValueTask GetAgentAsync(AgentId agentId, bool lazy = true) return agentId; } - public ValueTask GetAgentAsync(AgentType agentType, string key = "default", bool lazy = true) - => this.GetAgentAsync(new AgentId(agentType, key), lazy); - - public ValueTask GetAgentAsync(string agent, string key = "default", bool lazy = true) - => this.GetAgentAsync(new AgentId(agent, key), lazy); - public async ValueTask GetAgentMetadataAsync(AgentId agentId) { IHostableAgent agent = await this.EnsureAgentAsync(agentId); diff --git a/dotnet/test/Microsoft.AutoGen.Core.Tests/AgentTests.cs b/dotnet/test/Microsoft.AutoGen.Core.Tests/AgentTests.cs index cc39b3564c66..da81f40a6f3c 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Tests/AgentTests.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Tests/AgentTests.cs @@ -121,7 +121,7 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", (id, runtime) => }); Assert.Null(agent); - await runtime.GetAgentAsync("MyAgent", lazy: false); + await runtime.GetAgentAsync(AgentId.FromStr("MyAgent"), lazy: false); Assert.NotNull(agent); Assert.True(agent.ReceivedItems.Count == 0); From edf64ba72fb25931318e62c44ac3429199c0762f Mon Sep 17 00:00:00 2001 From: Jacob Alber Date: Wed, 29 Jan 2025 23:54:33 -0500 Subject: [PATCH 05/53] feat: Implement remaining methods in GrpcAgentRuntime * Factor out AgentContainer for managing factory registration, instantiation, and subscription management --- .../Core.Grpc/GrpcAgentRuntime.cs | 227 +++++++++++------- 1 file changed, 136 insertions(+), 91 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs index b1f5d43669e6..f0be2376a153 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs @@ -11,7 +11,74 @@ namespace Microsoft.AutoGen.Core.Grpc; +internal sealed class AgentsContainer(IAgentRuntime hostingRuntime) +{ + private readonly IAgentRuntime hostingRuntime = hostingRuntime; + + private Dictionary agentInstances = new(); + private Dictionary subscriptions = new(); + private Dictionary>> agentFactories = new(); + + public async ValueTask EnsureAgentAsync(Contracts.AgentId agentId) + { + if (!this.agentInstances.TryGetValue(agentId, out IHostableAgent? agent)) + { + if (!this.agentFactories.TryGetValue(agentId.Type, out Func>? factoryFunc)) + { + throw new Exception($"Agent with name {agentId.Type} not found."); + } + + agent = await factoryFunc(agentId, this.hostingRuntime); + this.agentInstances.Add(agentId, agent); + } + + return this.agentInstances[agentId]; + } + + public async ValueTask GetAgentAsync(Contracts.AgentId agentId, bool lazy = true) + { + if (!lazy) + { + await this.EnsureAgentAsync(agentId); + } + + return agentId; + } + + public AgentType RegisterAgentFactory(AgentType type, Func> factoryFunc) + { + if (this.agentFactories.ContainsKey(type)) + { + throw new Exception($"Agent factory with type {type} already exists."); + } + this.agentFactories.Add(type, factoryFunc); + return type; + } + + public void AddSubscription(ISubscriptionDefinition subscription) + { + if (this.subscriptions.ContainsKey(subscription.Id)) + { + throw new Exception($"Subscription with id {subscription.Id} already exists."); + } + + this.subscriptions.Add(subscription.Id, subscription); + } + + public bool RemoveSubscriptionAsync(string subscriptionId) + { + if (!this.subscriptions.ContainsKey(subscriptionId)) + { + throw new Exception($"Subscription with id {subscriptionId} does not exist."); + } + + return this.subscriptions.Remove(subscriptionId); + } + + public HashSet RegisteredAgentTypes => this.agentFactories.Keys.ToHashSet(); + public IEnumerable LiveAgents => this.agentInstances.Values; +} public sealed class GrpcAgentRuntime: IHostedService, IAgentRuntime, IMessageSink, IDisposable { @@ -25,21 +92,21 @@ public GrpcAgentRuntime(AgentRpc.AgentRpcClient client, this._shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping); this._messageRouter = new GrpcMessageRouter(client, this, logger, this._shutdownCts.Token); + this._agentsContainer = new AgentsContainer(this); this.ServiceProvider = serviceProvider; } - // Request ID -> + // Request ID -> ResultSink<...> private readonly ConcurrentDictionary> _pendingRequests = new(); - private Dictionary>> agentFactories = new(); - private Dictionary agentInstances = new(); - private readonly AgentRpc.AgentRpcClient _client; private readonly GrpcMessageRouter _messageRouter; private readonly ILogger _logger; private readonly CancellationTokenSource _shutdownCts; + + private readonly AgentsContainer _agentsContainer; public IServiceProvider ServiceProvider { get; } @@ -84,7 +151,7 @@ private async ValueTask HandleRequest(RpcRequest request, CancellationToken canc } var agentId = request.Target; - var agent = await EnsureAgentAsync(agentId.FromProtobuf()); + var agent = await this._agentsContainer.EnsureAgentAsync(agentId.FromProtobuf()); // Convert payload back to object var payload = request.Payload; @@ -172,36 +239,9 @@ private async ValueTask HandlePublish(CloudEvent evt, CancellationToken cancella Topic = topic, IsRpc = false }; - var agent = await EnsureAgentAsync(sender); + var agent = await this._agentsContainer.EnsureAgentAsync(sender); await agent.OnMessageAsync(message, messageContext); } - - - - // private override async ValueTask SendMessageAsync(Payload message, AgentId agentId, AgentId? agent = null, CancellationToken? cancellationToken = default) - // { - // var request = new RpcRequest - // { - // RequestId = Guid.NewGuid().ToString(), - // Source = agent, - // Target = agentId, - // Payload = message, - // }; - - // // Actually send it and wait for the response - // throw new NotImplementedException(); - // } - - // new is intentional - - // public new async ValueTask RuntimeSendRequestAsync(IAgent agent, RpcRequest request, CancellationToken cancellationToken = default) - // { - // var requestId = Guid.NewGuid().ToString(); - // _pendingRequests[requestId] = ((Agent)agent, request.RequestId); - // request.RequestId = requestId; - // await WriteChannelAsync(new Message { Request = request }, cancellationToken).ConfigureAwait(false); - // } - public ValueTask StartAsync(CancellationToken cancellationToken) { @@ -215,22 +255,6 @@ public Task StopAsync(CancellationToken cancellationToken) return this._messageRouter.StopAsync(); } - private async ValueTask EnsureAgentAsync(Contracts.AgentId agentId) - { - if (!this.agentInstances.TryGetValue(agentId, out IHostableAgent? agent)) - { - if (!this.agentFactories.TryGetValue(agentId.Type, out Func>? factoryFunc)) - { - throw new Exception($"Agent with name {agentId.Type} not found."); - } - - agent = await factoryFunc(agentId, this); - this.agentInstances.Add(agentId, agent); - } - - return this.agentInstances[agentId]; - } - private Payload ObjectToPayload(object message) { if (!SerializationRegistry.Exists(message.GetType())) { @@ -338,77 +362,98 @@ public async ValueTask PublishMessageAsync(object message, TopicId topic, Contra await this._messageRouter.RouteMessageAsync(msg, cancellationToken); } - public ValueTask GetAgentAsync(Contracts.AgentId agentId, bool lazy = true) - { - throw new NotImplementedException(); - } + public ValueTask GetAgentAsync(Contracts.AgentId agentId, bool lazy = true) => this._agentsContainer.GetAgentAsync(agentId, lazy); - public ValueTask GetAgentAsync(AgentType agentType, string key = "default", bool lazy = true) + public async ValueTask> SaveAgentStateAsync(Contracts.AgentId agentId) { - throw new NotImplementedException(); + IHostableAgent agent = await this._agentsContainer.EnsureAgentAsync(agentId); + return await agent.SaveStateAsync(); } - public ValueTask GetAgentAsync(string agent, string key = "default", bool lazy = true) + public async ValueTask LoadAgentStateAsync(Contracts.AgentId agentId, IDictionary state) { - throw new NotImplementedException(); + IHostableAgent agent = await this._agentsContainer.EnsureAgentAsync(agentId); + await agent.LoadStateAsync(state); } - public ValueTask> SaveAgentStateAsync(Contracts.AgentId agentId) + public async ValueTask GetAgentMetadataAsync(Contracts.AgentId agentId) { - throw new NotImplementedException(); + IHostableAgent agent = await this._agentsContainer.EnsureAgentAsync(agentId); + return agent.Metadata; } - public ValueTask LoadAgentStateAsync(Contracts.AgentId agentId, IDictionary state) + public ValueTask AddSubscriptionAsync(ISubscriptionDefinition subscription) { - throw new NotImplementedException(); + this._agentsContainer.AddSubscription(subscription); + + // Because we have an extensible definition of ISubscriptionDefinition, we cannot project it to the Gateway. + // What this means is that we will have a much chattier interface between the Gateway and the Runtime. + + //await this._client.AddSubscriptionAsync(new AddSubscriptionRequest + //{ + // Subscription = new Subscription + // { + // Id = subscription.Id, + // TopicType = subscription.TopicType, + // AgentType = subscription.AgentType.Name + // } + //}, this.CallOptions); + + return ValueTask.CompletedTask; } - public ValueTask GetAgentMetadataAsync(Contracts.AgentId agentId) + public ValueTask RemoveSubscriptionAsync(string subscriptionId) { - throw new NotImplementedException(); - } + this._agentsContainer.RemoveSubscriptionAsync(subscriptionId); - public async ValueTask AddSubscriptionAsync(ISubscriptionDefinition subscription) - { - var _ = await this._client.AddSubscriptionAsync(new AddSubscriptionRequest{ - Subscription = subscription.ToProtobuf() - },this.CallOptions); + // See above (AddSubscriptionAsync) for why this is commented out. + + //await this._client.RemoveSubscriptionAsync(new RemoveSubscriptionRequest + //{ + // Id = subscriptionId + //}, this.CallOptions); + + return ValueTask.CompletedTask; } - public ValueTask RemoveSubscriptionAsync(string subscriptionId) + public ValueTask RegisterAgentFactoryAsync(AgentType type, Func> factoryFunc) + => ValueTask.FromResult(this._agentsContainer.RegisterAgentFactory(type, factoryFunc)); + + public ValueTask TryGetAgentProxyAsync(Contracts.AgentId agentId) { - throw new NotImplementedException(); + // TODO: Do we want to support getting remote agent proxies? + return ValueTask.FromResult(new AgentProxy(agentId, this)); } - public ValueTask RegisterAgentFactoryAsync(AgentType type, Func> factoryFunc) + public async ValueTask> SaveStateAsync() { - if (this.agentFactories.ContainsKey(type)) + Dictionary state = new(); + foreach (var agent in this._agentsContainer.LiveAgents) { - throw new Exception($"Agent with type {type} already exists."); + state[agent.Id.ToString()] = await agent.SaveStateAsync(); } - this.agentFactories.Add(type, async (agentId, runtime) => await factoryFunc(agentId, runtime)); - - this._client.RegisterAgentAsync(new RegisterAgentTypeRequest - { - Type = type.Name, - }, this.CallOptions); - return ValueTask.FromResult(type); + return state; } - public ValueTask TryGetAgentProxyAsync(Contracts.AgentId agentId) + public async ValueTask LoadStateAsync(IDictionary state) { - throw new NotImplementedException(); - } + HashSet registeredTypes = this._agentsContainer.RegisteredAgentTypes; - public ValueTask> SaveStateAsync() - { - throw new NotImplementedException(); - } + foreach (var agentIdStr in state.Keys) + { + Contracts.AgentId agentId = Contracts.AgentId.FromStr(agentIdStr); + if (state[agentIdStr] is not IDictionary agentStateDict) + { + throw new Exception($"Agent state for {agentId} is not a {typeof(IDictionary)}: {state[agentIdStr].GetType()}"); + } - public ValueTask LoadStateAsync(IDictionary state) - { - throw new NotImplementedException(); + if (registeredTypes.Contains(agentId.Type)) + { + IHostableAgent agent = await this._agentsContainer.EnsureAgentAsync(agentId); + await agent.LoadStateAsync(agentStateDict); + } + } } public async ValueTask OnMessageAsync(Message message, CancellationToken cancellation = default) From 1369a5d078a7f01090fb46a5473faed1b7084c49 Mon Sep 17 00:00:00 2001 From: Jacob Alber Date: Thu, 30 Jan 2025 00:19:29 -0500 Subject: [PATCH 06/53] fix: Get Core.Grpc test project building --- .../Core.Grpc/AgentsAppBuilderExtensions.cs | 21 ++ .../AgentGrpcTests.cs | 346 ++++++++++-------- .../Microsoft.AutoGen.Core.Grpc.Tests.csproj | 2 +- 3 files changed, 212 insertions(+), 157 deletions(-) create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/AgentsAppBuilderExtensions.cs diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/AgentsAppBuilderExtensions.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/AgentsAppBuilderExtensions.cs new file mode 100644 index 000000000000..e19cc2f343d2 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/AgentsAppBuilderExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// AgentsAppBuilderExtensions.cs + +using Microsoft.AutoGen.Contracts; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AutoGen.Core.Grpc; + +public static class AgentsAppBuilderExtensions +{ + public static AgentsAppBuilder UseGrpcRuntime(this AgentsAppBuilder this_, bool deliverToSelf = false) + { + this_.Services.AddSingleton(); + this_.Services.AddHostedService(services => + { + return (services.GetRequiredService() as GrpcAgentRuntime)!; + }); + + return this_; + } +} diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs index 7514609e145b..bb054ee738a4 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs @@ -1,184 +1,214 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AgentGrpcTests.cs -using System.Collections.Concurrent; -using System.Text.Json; -using FluentAssertions; -using Google.Protobuf.Reflection; +//using System.Collections.Concurrent; +//using System.Text.Json; +//using FluentAssertions; +//using Google.Protobuf.Reflection; using Microsoft.AutoGen.Contracts; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Xunit; -using static Microsoft.AutoGen.Core.Grpc.Tests.AgentGrpcTests; namespace Microsoft.AutoGen.Core.Grpc.Tests; [Trait("Category", "UnitV2")] public class AgentGrpcTests { - /// - /// Verify that if the agent is not initialized via AgentWorker, it should throw the correct exception. - /// - /// void [Fact] - public async Task Agent_ShouldThrowException_WhenNotInitialized() - { - using var runtime = new GrpcRuntime(); - var (_, agent) = runtime.Start(false); // Do not initialize - - // Expect an exception when calling AddSubscriptionAsync because the agent is uninitialized - await Assert.ThrowsAsync( - async () => await agent.AddSubscriptionAsync("TestEvent") - ); - } - - /// - /// validate that the agent is initialized correctly with implicit subs - /// - /// void - [Fact] - public async Task Agent_ShouldInitializeCorrectly() + public void Agent_ShouldInitializeCorrectly() { using var runtime = new GrpcRuntime(); var (worker, agent) = runtime.Start(); Assert.Equal(nameof(GrpcAgentRuntime), worker.GetType().Name); - await Task.Delay(5000); - var subscriptions = await agent.GetSubscriptionsAsync(); - Assert.Equal(2, subscriptions.Count); - } - /// - /// Test AddSubscriptionAsync method - /// - /// void - [Fact] - public async Task SubscribeAsync_UnsubscribeAsync_and_GetSubscriptionsTest() - { - using var runtime = new GrpcRuntime(); - var (_, agent) = runtime.Start(); - await agent.AddSubscriptionAsync("TestEvent"); - await Task.Delay(100); - var subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); - var found = false; - foreach (var subscription in subscriptions) - { - if (subscription.TypeSubscription.TopicType == "TestEvent") - { - found = true; - } - } - Assert.True(found); - await agent.RemoveSubscriptionAsync("TestEvent").ConfigureAwait(true); - await Task.Delay(1000); - subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); - found = false; - foreach (var subscription in subscriptions) - { - if (subscription.TypeSubscription.TopicType == "TestEvent") - { - found = true; - } - } - Assert.False(found); } - /// - /// Test StoreAsync and ReadAsync methods - /// - /// void - [Fact] - public async Task StoreAsync_and_ReadAsyncTest() - { - using var runtime = new GrpcRuntime(); - var (_, agent) = runtime.Start(); - Dictionary state = new() - { - { "testdata", "Active" } - }; - await agent.StoreAsync(new AgentState - { - AgentId = agent.AgentId, - TextData = JsonSerializer.Serialize(state) - }).ConfigureAwait(true); - var readState = await agent.ReadAsync(agent.AgentId).ConfigureAwait(true); - var read = JsonSerializer.Deserialize>(readState.TextData) ?? new Dictionary { { "data", "No state data found" } }; - read.TryGetValue("testdata", out var value); - Assert.Equal("Active", value); - } + ///// + ///// Verify that if the agent is not initialized via AgentWorker, it should throw the correct exception. + ///// + ///// void + //[Fact] + //public async Task Agent_ShouldThrowException_WhenNotInitialized() + //{ + // using var runtime = new GrpcRuntime(); + // var (_, agent) = runtime.Start(false); // Do not initialize - /// - /// Test PublishMessageAsync method and ReceiveMessage method - /// - /// void - [Fact] - public async Task PublishMessageAsync_and_ReceiveMessageTest() + // // Expect an exception when calling AddSubscriptionAsync because the agent is uninitialized + // await Assert.ThrowsAsync( + // async () => await agent.AddSubscriptionAsync("TestEvent") + // ); + //} + + ///// + ///// validate that the agent is initialized correctly with implicit subs + ///// + ///// void + //[Fact] + //public async Task Agent_ShouldInitializeCorrectly() + //{ + // using var runtime = new GrpcRuntime(); + // var (worker, agent) = runtime.Start(); + // Assert.Equal(nameof(GrpcAgentRuntime), worker.GetType().Name); + // await Task.Delay(5000); + // var subscriptions = await agent.GetSubscriptionsAsync(); + // Assert.Equal(2, subscriptions.Count); + //} + ///// + ///// Test AddSubscriptionAsync method + ///// + ///// void + //[Fact] + //public async Task SubscribeAsync_UnsubscribeAsync_and_GetSubscriptionsTest() + //{ + // using var runtime = new GrpcRuntime(); + // var (_, agent) = runtime.Start(); + // await agent.AddSubscriptionAsync("TestEvent"); + // await Task.Delay(100); + // var subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); + // var found = false; + // foreach (var subscription in subscriptions) + // { + // if (subscription.TypeSubscription.TopicType == "TestEvent") + // { + // found = true; + // } + // } + // Assert.True(found); + // await agent.RemoveSubscriptionAsync("TestEvent").ConfigureAwait(true); + // await Task.Delay(1000); + // subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); + // found = false; + // foreach (var subscription in subscriptions) + // { + // if (subscription.TypeSubscription.TopicType == "TestEvent") + // { + // found = true; + // } + // } + // Assert.False(found); + //} + + ///// + ///// Test StoreAsync and ReadAsync methods + ///// + ///// void + //[Fact] + //public async Task StoreAsync_and_ReadAsyncTest() + //{ + // using var runtime = new GrpcRuntime(); + // var (_, agent) = runtime.Start(); + // Dictionary state = new() + // { + // { "testdata", "Active" } + // }; + // await agent.StoreAsync(new AgentState + // { + // AgentId = agent.AgentId, + // TextData = JsonSerializer.Serialize(state) + // }).ConfigureAwait(true); + // var readState = await agent.ReadAsync(agent.AgentId).ConfigureAwait(true); + // var read = JsonSerializer.Deserialize>(readState.TextData) ?? new Dictionary { { "data", "No state data found" } }; + // read.TryGetValue("testdata", out var value); + // Assert.Equal("Active", value); + //} + + ///// + ///// Test PublishMessageAsync method and ReceiveMessage method + ///// + ///// void + //[Fact] + //public async Task PublishMessageAsync_and_ReceiveMessageTest() + //{ + // using var runtime = new GrpcRuntime(); + // var (_, agent) = runtime.Start(); + // var topicType = "TestTopic"; + // await agent.AddSubscriptionAsync(topicType).ConfigureAwait(true); + // var subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); + // var found = false; + // foreach (var subscription in subscriptions) + // { + // if (subscription.TypeSubscription.TopicType == topicType) + // { + // found = true; + // } + // } + // Assert.True(found); + // await agent.PublishMessageAsync(new TextMessage() + // { + // Source = topicType, + // TextMessage_ = "buffer" + // }, topicType).ConfigureAwait(true); + // await Task.Delay(100); + // Assert.True(TestAgent.ReceivedMessages.ContainsKey(topicType)); + // runtime.Stop(); + //} + + //[Fact] + //public async Task InvokeCorrectHandler() + //{ + // var agent = new TestAgent(new AgentsMetadata(TypeRegistry.Empty, new Dictionary(), new Dictionary>(), new Dictionary>()), new Logger(new LoggerFactory())); + + // await agent.HandleObjectAsync("hello world"); + // await agent.HandleObjectAsync(42); + + // agent.ReceivedItems.Should().HaveCount(2); + // agent.ReceivedItems[0].Should().Be("hello world"); + // agent.ReceivedItems[1].Should().Be(42); + //} +} + +/// +/// The test agent is a simple agent that is used for testing purposes. +/// +public class TestAgent(AgentId id, + IAgentRuntime runtime, + Logger? logger = null) : BaseAgent(id, runtime, "Test Agent", logger), + //IHandle, + //IHandle, + IHandle + +{ + //public ValueTask HandleAsync(TextMessage item, MessageContext messageContext) + //{ + // ReceivedMessages[item.Source] = item.Content; + // return ValueTask.CompletedTask; + //} + + public ValueTask HandleAsync(string item, MessageContext messageContext) { - using var runtime = new GrpcRuntime(); - var (_, agent) = runtime.Start(); - var topicType = "TestTopic"; - await agent.AddSubscriptionAsync(topicType).ConfigureAwait(true); - var subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); - var found = false; - foreach (var subscription in subscriptions) - { - if (subscription.TypeSubscription.TopicType == topicType) - { - found = true; - } - } - Assert.True(found); - await agent.PublishMessageAsync(new TextMessage() - { - Source = topicType, - TextMessage_ = "buffer" - }, topicType).ConfigureAwait(true); - await Task.Delay(100); - Assert.True(TestAgent.ReceivedMessages.ContainsKey(topicType)); - runtime.Stop(); + ReceivedItems.Add(item); + return ValueTask.CompletedTask; } - [Fact] - public async Task InvokeCorrectHandler() + public ValueTask HandleAsync(int item, MessageContext messageContext) { - var agent = new TestAgent(new AgentsMetadata(TypeRegistry.Empty, new Dictionary(), new Dictionary>(), new Dictionary>()), new Logger(new LoggerFactory())); + ReceivedItems.Add(item); + return ValueTask.CompletedTask; + } - await agent.HandleObjectAsync("hello world"); - await agent.HandleObjectAsync(42); + //public ValueTask HandleAsync(RpcTextMessage item, MessageContext messageContext) + //{ + // ReceivedMessages[item.Source] = item.Content; + // return ValueTask.FromResult(item.Content); + //} - agent.ReceivedItems.Should().HaveCount(2); - agent.ReceivedItems[0].Should().Be("hello world"); - agent.ReceivedItems[1].Should().Be(42); - } + public List ReceivedItems { get; private set; } = []; /// - /// The test agent is a simple agent that is used for testing purposes. + /// Key: source + /// Value: message /// - public class TestAgent( - [FromKeyedServices("AgentsMetadata")] AgentsMetadata eventTypes, - Logger? logger = null) : Agent(eventTypes, logger), IHandle - { - public Task Handle(TextMessage item, CancellationToken cancellationToken = default) - { - ReceivedMessages[item.Source] = item.TextMessage_; - return Task.CompletedTask; - } - public Task Handle(string item) - { - ReceivedItems.Add(item); - return Task.CompletedTask; - } - public Task Handle(int item) - { - ReceivedItems.Add(item); - return Task.CompletedTask; - } - public List ReceivedItems { get; private set; } = []; + public static Dictionary ReceivedMessages { get; private set; } = new(); +} - /// - /// Key: source - /// Value: message - /// - public static ConcurrentDictionary ReceivedMessages { get; private set; } = new(); +[TypeSubscription("TestTopic")] +public class SubscribedAgent : TestAgent +{ + public SubscribedAgent(AgentId id, + IAgentRuntime runtime, + Logger? logger = null) : base(id, runtime, logger) + { } } @@ -212,14 +242,18 @@ private static int GetAvailablePort() private static async Task StartClientAsync() { - return await AgentsApp.StartAsync().ConfigureAwait(false); - } - private static async Task StartAppHostAsync() - { - return await Microsoft.AutoGen.Runtime.Grpc.Host.StartAsync(local: false, useGrpc: true).ConfigureAwait(false); + AgentsApp agentsApp = await new AgentsAppBuilder().UseGrpcRuntime().AddAgent("TestAgent").BuildAsync(); + await agentsApp.StartAsync(); + + return agentsApp.Host; } + //private static async Task StartAppHostAsync() + //{ + // return await Microsoft.AutoGen.Runtime.Grpc.Host.StartAsync(local: false, useGrpc: true).ConfigureAwait(false); + //} + /// /// Start - gets a new port and starts fresh instances /// @@ -231,14 +265,14 @@ private static async Task StartAppHostAsync() Environment.SetEnvironmentVariable("ASPNETCORE_HTTPS_PORTS", port.ToString()); Environment.SetEnvironmentVariable("AGENT_HOST", $"https://localhost:{port}"); - AppHost = StartAppHostAsync().GetAwaiter().GetResult(); + //AppHost = StartAppHostAsync().GetAwaiter().GetResult(); Client = StartClientAsync().GetAwaiter().GetResult(); var agent = ActivatorUtilities.CreateInstance(Client.Services); var worker = Client.Services.GetRequiredService(); if (initialize) { - Agent.Initialize(worker, agent); + //Agent.Initialize(worker, agent); } return (worker, agent); diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj index f14497e75fbc..a2dad2212a7f 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj @@ -10,7 +10,7 @@ - + From 414c4079e2cd76a5e4c6ccd3b8445d453479ac91 Mon Sep 17 00:00:00 2001 From: Jacob Alber Date: Thu, 30 Jan 2025 08:40:28 -0500 Subject: [PATCH 07/53] fixup: Remove commented out Payload serialization code --- dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs index f0be2376a153..4302d81f6a06 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs @@ -107,7 +107,7 @@ public GrpcAgentRuntime(AgentRpc.AgentRpcClient client, private readonly CancellationTokenSource _shutdownCts; private readonly AgentsContainer _agentsContainer; - + public IServiceProvider ServiceProvider { get; } private string _clientId = Guid.NewGuid().ToString(); @@ -242,7 +242,7 @@ private async ValueTask HandlePublish(CloudEvent evt, CancellationToken cancella var agent = await this._agentsContainer.EnsureAgentAsync(sender); await agent.OnMessageAsync(message, messageContext); } - + public ValueTask StartAsync(CancellationToken cancellationToken) { return this._messageRouter.StartAsync(cancellationToken); From 7c3c9342e63c4322dab7261d3cff9ea30a463ae0 Mon Sep 17 00:00:00 2001 From: Jacob Alber Date: Thu, 30 Jan 2025 08:46:28 -0500 Subject: [PATCH 08/53] fixup: Merge Builder extension method classes * Prefer AgentsAppBuilderExtensions as name, due to matching the incoming `this` type. --- .../Core.Grpc/AgentsAppBuilderExtensions.cs | 65 +++++++++++++++-- .../GrpcAgentWorkerHostBuilderExtension.cs | 70 ------------------- 2 files changed, 60 insertions(+), 75 deletions(-) delete mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentWorkerHostBuilderExtension.cs diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/AgentsAppBuilderExtensions.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/AgentsAppBuilderExtensions.cs index e19cc2f343d2..8b11cc3da354 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/AgentsAppBuilderExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/AgentsAppBuilderExtensions.cs @@ -1,21 +1,76 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AgentsAppBuilderExtensions.cs +using System.Diagnostics; +using Grpc.Core; +using Grpc.Net.Client.Configuration; using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; - +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; namespace Microsoft.AutoGen.Core.Grpc; public static class AgentsAppBuilderExtensions { - public static AgentsAppBuilder UseGrpcRuntime(this AgentsAppBuilder this_, bool deliverToSelf = false) + private const string _defaultAgentServiceAddress = "https://localhost:53071"; + + // TODO: How do we ensure AddGrpcAgentWorker and UseInProcessRuntime are mutually exclusive? + public static AgentsAppBuilder AddGrpcAgentWorker(this AgentsAppBuilder builder, string? agentServiceAddress = null) { - this_.Services.AddSingleton(); - this_.Services.AddHostedService(services => + builder.Services.AddGrpcClient(options => + { + options.Address = new Uri(agentServiceAddress ?? builder.Configuration.GetValue("AGENT_HOST", _defaultAgentServiceAddress)); + options.ChannelOptionsActions.Add(channelOptions => + { + var loggerFactory = new LoggerFactory(); + if (Debugger.IsAttached) + { + channelOptions.HttpHandler = new SocketsHttpHandler + { + EnableMultipleHttp2Connections = false, + KeepAlivePingDelay = TimeSpan.FromSeconds(200), + KeepAlivePingTimeout = TimeSpan.FromSeconds(100), + KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always + }; + } + else + { + channelOptions.HttpHandler = new SocketsHttpHandler + { + EnableMultipleHttp2Connections = true, + KeepAlivePingDelay = TimeSpan.FromSeconds(20), + KeepAlivePingTimeout = TimeSpan.FromSeconds(10), + KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests + }; + } + + var methodConfig = new MethodConfig + { + Names = { MethodName.Default }, + RetryPolicy = new RetryPolicy + { + MaxAttempts = 5, + InitialBackoff = TimeSpan.FromSeconds(1), + MaxBackoff = TimeSpan.FromSeconds(5), + BackoffMultiplier = 1.5, + RetryableStatusCodes = { StatusCode.Unavailable } + } + }; + + channelOptions.ServiceConfig = new() { MethodConfigs = { methodConfig } }; + channelOptions.ThrowOperationCanceledOnCancellation = true; + }); + }); + + builder.Services.TryAddSingleton(DistributedContextPropagator.Current); + builder.Services.AddSingleton(); + builder.Services.AddHostedService(services => { return (services.GetRequiredService() as GrpcAgentRuntime)!; }); - return this_; + return builder; } } diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentWorkerHostBuilderExtension.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentWorkerHostBuilderExtension.cs deleted file mode 100644 index 7f43b9620f54..000000000000 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentWorkerHostBuilderExtension.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// GrpcAgentWorkerHostBuilderExtension.cs -using System.Diagnostics; -using Grpc.Core; -using Grpc.Net.Client.Configuration; -using Microsoft.AutoGen.Contracts; -using Microsoft.AutoGen.Protobuf; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -namespace Microsoft.AutoGen.Core.Grpc; - -public static class GrpcAgentWorkerHostBuilderExtensions -{ - private const string _defaultAgentServiceAddress = "https://localhost:53071"; - - // TODO: How do we ensure AddGrpcAgentWorker and UseInProcessRuntime are mutually exclusive? - public static AgentsAppBuilder AddGrpcAgentWorker(this AgentsAppBuilder builder, string? agentServiceAddress = null) - { - builder.Services.AddGrpcClient(options => - { - options.Address = new Uri(agentServiceAddress ?? builder.Configuration["AGENT_HOST"] ?? _defaultAgentServiceAddress); - options.ChannelOptionsActions.Add(channelOptions => - { - var loggerFactory = new LoggerFactory(); - if (Debugger.IsAttached) - { - channelOptions.HttpHandler = new SocketsHttpHandler - { - EnableMultipleHttp2Connections = false, - KeepAlivePingDelay = TimeSpan.FromSeconds(200), - KeepAlivePingTimeout = TimeSpan.FromSeconds(100), - KeepAlivePingPolicy = HttpKeepAlivePingPolicy.Always - }; - } - else - { - channelOptions.HttpHandler = new SocketsHttpHandler - { - EnableMultipleHttp2Connections = true, - KeepAlivePingDelay = TimeSpan.FromSeconds(20), - KeepAlivePingTimeout = TimeSpan.FromSeconds(10), - KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests - }; - } - - var methodConfig = new MethodConfig - { - Names = { MethodName.Default }, - RetryPolicy = new RetryPolicy - { - MaxAttempts = 5, - InitialBackoff = TimeSpan.FromSeconds(1), - MaxBackoff = TimeSpan.FromSeconds(5), - BackoffMultiplier = 1.5, - RetryableStatusCodes = { StatusCode.Unavailable } - } - }; - - channelOptions.ServiceConfig = new() { MethodConfigs = { methodConfig } }; - channelOptions.ThrowOperationCanceledOnCancellation = true; - }); - }); - builder.Services.TryAddSingleton(DistributedContextPropagator.Current); - builder.Services.AddSingleton(); - builder.Services.AddSingleton(sp => (IHostedService)sp.GetRequiredService()); - return builder; - } -} From 72168b57a7f4695ed800341914a41d6d2bfa9d54 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Mon, 3 Feb 2025 14:57:34 -0500 Subject: [PATCH 09/53] Cleanup constants, put back rpc based impl --- .../Core.Grpc/CloudEventExtensions.cs | 39 ++++++ .../Microsoft.AutoGen/Core.Grpc/Constants.cs | 21 ++++ .../Core.Grpc/GrpcAgentRuntime.cs | 118 ++++-------------- .../Core.Grpc/GrpcMessageRouter.cs | 2 +- .../Core.Grpc/IAgentMessageSerializer.cs | 2 +- ...lizer.cs => IProtobufMessageSerializer.cs} | 6 +- .../Core.Grpc/ISerializationRegistry.cs | 6 +- .../Core.Grpc/ITypeNameResolver.cs | 2 +- .../Core.Grpc/ProtobufConversionExtensions.cs | 3 +- .../Core.Grpc/ProtobufMessageSerializer.cs | 4 +- ...ry.cs => ProtobufSerializationRegistry.cs} | 16 +-- ...esolver.cs => ProtobufTypeNameResolver.cs} | 4 +- .../Core.Grpc/RpcExtensions.cs | 43 +++++++ 13 files changed, 147 insertions(+), 119 deletions(-) create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/CloudEventExtensions.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/Constants.cs rename dotnet/src/Microsoft.AutoGen/Core.Grpc/{IProtoMessageSerializer.cs => IProtobufMessageSerializer.cs} (74%) rename dotnet/src/Microsoft.AutoGen/Core.Grpc/{ProtoSerializationRegistry.cs => ProtobufSerializationRegistry.cs} (56%) rename dotnet/src/Microsoft.AutoGen/Core.Grpc/{ProtoTypeNameResolver.cs => ProtobufTypeNameResolver.cs} (82%) create mode 100644 dotnet/src/Microsoft.AutoGen/Core.Grpc/RpcExtensions.cs diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/CloudEventExtensions.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/CloudEventExtensions.cs new file mode 100644 index 000000000000..cab48d971316 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/CloudEventExtensions.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// CloudEventExtensions.cs + +using Microsoft.AutoGen.Contracts; + +namespace Microsoft.AutoGen.Core.Grpc; + +internal static class CloudEventExtensions +{ + // Convert an ISubscrptionDefinition to a Protobuf Subscription + internal static CloudEvent CreateCloudEvent(Google.Protobuf.WellKnownTypes.Any payload, TopicId topic, string dataType, AgentId sender, string messageId) + { + return new CloudEvent + { + ProtoData = payload, + Type = topic.Type, + Source = topic.Source, + Id = messageId, + Attributes = { + { + Constants.DATA_CONTENT_TYPE_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = Constants.DATA_CONTENT_TYPE_PROTOBUF_VALUE } + }, + { + Constants.DATA_SCHEMA_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = dataType } + }, + { + Constants.AGENT_SENDER_TYPE_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = sender.Type } + }, + { + Constants.AGENT_SENDER_KEY_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = sender.Key } + }, + { + Constants.MESSAGE_KIND_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = Constants.MESSAGE_KIND_VALUE_PUBLISH } + } + } + }; + + } +} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/Constants.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/Constants.cs new file mode 100644 index 000000000000..c3e9592c1dc2 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/Constants.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Constants.cs + +namespace Microsoft.AutoGen.Core.Grpc; + +public static class Constants +{ + public const string DATA_CONTENT_TYPE_PROTOBUF_VALUE = "application/x-protobuf"; + public const string DATA_CONTENT_TYPE_JSON_VALUE = "application/json"; + public const string DATA_CONTENT_TYPE_TEXT_VALUE = "text/plain"; + + public const string DATA_CONTENT_TYPE_ATTR = "datacontenttype"; + public const string DATA_SCHEMA_ATTR = "dataschema"; + public const string AGENT_SENDER_TYPE_ATTR = "agagentsendertype"; + public const string AGENT_SENDER_KEY_ATTR = "agagentsenderkey"; + + public const string MESSAGE_KIND_ATTR = "agmsgkind"; + public const string MESSAGE_KIND_VALUE_PUBLISH = "publish"; + public const string MESSAGE_KIND_VALUE_RPC_REQUEST = "rpc_request"; + public const string MESSAGE_KIND_VALUE_RPC_RESPONSE = "rpc_response"; +} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs index 4302d81f6a06..9126a4f895cd 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs @@ -2,12 +2,11 @@ // GrpcAgentRuntime.cs using System.Collections.Concurrent; -using Google.Protobuf; using Grpc.Core; using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.Core.Grpc; @@ -80,7 +79,7 @@ public bool RemoveSubscriptionAsync(string subscriptionId) public IEnumerable LiveAgents => this.agentInstances.Values; } -public sealed class GrpcAgentRuntime: IHostedService, IAgentRuntime, IMessageSink, IDisposable +public sealed class GrpcAgentRuntime : IHostedService, IAgentRuntime, IMessageSink, IDisposable { public GrpcAgentRuntime(AgentRpc.AgentRpcClient client, IHostApplicationLifetime hostApplicationLifetime, @@ -123,7 +122,7 @@ private CallOptions CallOptions } } - public IProtoSerializationRegistry SerializationRegistry { get; } = new ProtoSerializationRegistry(); + public IProtoSerializationRegistry SerializationRegistry { get; } = new ProtobufSerializationRegistry(); public void Dispose() { @@ -155,7 +154,7 @@ private async ValueTask HandleRequest(RpcRequest request, CancellationToken canc // Convert payload back to object var payload = request.Payload; - var message = PayloadToObject(payload); + var message = payload.ToObject(SerializationRegistry); var messageContext = new MessageContext(request.RequestId, cancellationToken) { @@ -171,7 +170,7 @@ private async ValueTask HandleRequest(RpcRequest request, CancellationToken canc var response = new RpcResponse { RequestId = request.RequestId, - Payload = ObjectToPayload(result) + Payload = result.ToPayload(SerializationRegistry) }; var responseMessage = new Message @@ -201,7 +200,7 @@ private async ValueTask HandleResponse(RpcResponse request, CancellationToken _ if (_pendingRequests.TryRemove(request.RequestId, out var resultSink)) { var payload = request.Payload; - var message = PayloadToObject(payload); + var message = payload.ToObject(SerializationRegistry); resultSink.SetResult(message); } } @@ -224,12 +223,12 @@ private async ValueTask HandlePublish(CloudEvent evt, CancellationToken cancella var topic = new TopicId(evt.Type, evt.Source); var sender = new Contracts.AgentId { - Type = evt.Attributes["agagentsendertype"].CeString, - Key = evt.Attributes["agagentsenderkey"].CeString + Type = evt.Attributes[Constants.AGENT_SENDER_TYPE_ATTR].CeString, + Key = evt.Attributes[Constants.AGENT_SENDER_KEY_ATTR].CeString }; var messageId = evt.Id; - var typeName = evt.Attributes["dataschema"].CeString; + var typeName = evt.Attributes[Constants.DATA_SCHEMA_ATTR].CeString; var serializer = SerializationRegistry.GetSerializer(typeName) ?? throw new Exception(); var message = serializer.Deserialize(evt.ProtoData); @@ -255,36 +254,6 @@ public Task StopAsync(CancellationToken cancellationToken) return this._messageRouter.StopAsync(); } - private Payload ObjectToPayload(object message) { - if (!SerializationRegistry.Exists(message.GetType())) - { - SerializationRegistry.RegisterSerializer(message.GetType()); - } - var rpcMessage = (SerializationRegistry.GetSerializer(message.GetType()) ?? throw new Exception()).Serialize(message); - - var typeName = SerializationRegistry.TypeNameResolver.ResolveTypeName(message); - const string PAYLOAD_DATA_CONTENT_TYPE = "application/x-protobuf"; - - // Protobuf any to byte array - Payload payload = new() - { - DataType = typeName, - DataContentType = PAYLOAD_DATA_CONTENT_TYPE, - Data = rpcMessage.ToByteString() - }; - - return payload; - } - - private object PayloadToObject(Payload payload) { - var typeName = payload.DataType; - var data = payload.Data; - var type = SerializationRegistry.TypeNameResolver.ResolveTypeName(typeName); - var serializer = SerializationRegistry.GetSerializer(type) ?? throw new Exception(); - var any = Google.Protobuf.WellKnownTypes.Any.Parser.ParseFrom(data); - return serializer.Deserialize(any); - } - public async ValueTask SendMessageAsync(object message, Contracts.AgentId recepient, Contracts.AgentId? sender = null, string? messageId = null, CancellationToken cancellationToken = default) { if (!SerializationRegistry.Exists(message.GetType())) @@ -292,11 +261,11 @@ private object PayloadToObject(Payload payload) { SerializationRegistry.RegisterSerializer(message.GetType()); } - var payload = ObjectToPayload(message); + var payload = message.ToPayload(SerializationRegistry); var request = new RpcRequest { RequestId = Guid.NewGuid().ToString(), - Source = (sender ?? new Contracts.AgentId() ).ToProtobuf(), + Source = (sender ?? new Contracts.AgentId()).ToProtobuf(), Target = recepient.ToProtobuf(), Payload = payload, }; @@ -314,35 +283,6 @@ private object PayloadToObject(Payload payload) { return await resultSink.Future; } - private CloudEvent CreateCloudEvent(Google.Protobuf.WellKnownTypes.Any payload, TopicId topic, string dataType, Contracts.AgentId sender, string messageId) - { - const string PAYLOAD_DATA_CONTENT_TYPE = "application/x-protobuf"; - return new CloudEvent - { - ProtoData = payload, - Type = topic.Type, - Source = topic.Source, - Id = messageId, - Attributes = { - { - "datacontenttype", new CloudEvent.Types.CloudEventAttributeValue { CeString = PAYLOAD_DATA_CONTENT_TYPE } - }, - { - "dataschema", new CloudEvent.Types.CloudEventAttributeValue { CeString = dataType } - }, - { - "agagentsendertype", new CloudEvent.Types.CloudEventAttributeValue { CeString = sender.Type } - }, - { - "agagentsenderkey", new CloudEvent.Types.CloudEventAttributeValue { CeString = sender.Key } - }, - { - "agmsgkind", new CloudEvent.Types.CloudEventAttributeValue { CeString = "publish" } - } - } - }; - } - public async ValueTask PublishMessageAsync(object message, TopicId topic, Contracts.AgentId? sender = null, string? messageId = null, CancellationToken cancellationToken = default) { if (!SerializationRegistry.Exists(message.GetType())) @@ -352,7 +292,7 @@ public async ValueTask PublishMessageAsync(object message, TopicId topic, Contra var protoAny = (SerializationRegistry.GetSerializer(message.GetType()) ?? throw new Exception()).Serialize(message); var typeName = SerializationRegistry.TypeNameResolver.ResolveTypeName(message); - var cloudEvent = CreateCloudEvent(protoAny, topic, typeName, sender ?? new Contracts.AgentId(), messageId ?? Guid.NewGuid().ToString()); + var cloudEvent = CloudEventExtensions.CreateCloudEvent(protoAny, topic, typeName, sender ?? new Contracts.AgentId(), messageId ?? Guid.NewGuid().ToString()); Message msg = new() { @@ -382,38 +322,24 @@ public async ValueTask GetAgentMetadataAsync(Contracts.AgentId ag return agent.Metadata; } - public ValueTask AddSubscriptionAsync(ISubscriptionDefinition subscription) + public async ValueTask AddSubscriptionAsync(ISubscriptionDefinition subscription) { this._agentsContainer.AddSubscription(subscription); - // Because we have an extensible definition of ISubscriptionDefinition, we cannot project it to the Gateway. - // What this means is that we will have a much chattier interface between the Gateway and the Runtime. - - //await this._client.AddSubscriptionAsync(new AddSubscriptionRequest - //{ - // Subscription = new Subscription - // { - // Id = subscription.Id, - // TopicType = subscription.TopicType, - // AgentType = subscription.AgentType.Name - // } - //}, this.CallOptions); - - return ValueTask.CompletedTask; + var _ = await this._client.AddSubscriptionAsync(new AddSubscriptionRequest + { + Subscription = subscription.ToProtobuf() + }, this.CallOptions); } - public ValueTask RemoveSubscriptionAsync(string subscriptionId) + public async ValueTask RemoveSubscriptionAsync(string subscriptionId) { this._agentsContainer.RemoveSubscriptionAsync(subscriptionId); - // See above (AddSubscriptionAsync) for why this is commented out. - - //await this._client.RemoveSubscriptionAsync(new RemoveSubscriptionRequest - //{ - // Id = subscriptionId - //}, this.CallOptions); - - return ValueTask.CompletedTask; + await this._client.RemoveSubscriptionAsync(new RemoveSubscriptionRequest + { + Id = subscriptionId + }, this.CallOptions); } public ValueTask RegisterAgentFactoryAsync(AgentType type, Func> factoryFunc) diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcMessageRouter.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcMessageRouter.cs index 0ecf8ee1413e..9fdf0b608785 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcMessageRouter.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcMessageRouter.cs @@ -3,8 +3,8 @@ using System.Threading.Channels; using Grpc.Core; -using Microsoft.Extensions.Logging; using Microsoft.AutoGen.Protobuf; +using Microsoft.Extensions.Logging; namespace Microsoft.AutoGen.Core.Grpc; diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/IAgentMessageSerializer.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/IAgentMessageSerializer.cs index 0cc422d54d85..c2ca53e33710 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/IAgentMessageSerializer.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/IAgentMessageSerializer.cs @@ -20,4 +20,4 @@ public interface IAgentMessageSerializer /// The message to deserialize. /// The deserialized message. object Deserialize(Google.Protobuf.WellKnownTypes.Any message); -} \ No newline at end of file +} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/IProtoMessageSerializer.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/IProtobufMessageSerializer.cs similarity index 74% rename from dotnet/src/Microsoft.AutoGen/Core.Grpc/IProtoMessageSerializer.cs rename to dotnet/src/Microsoft.AutoGen/Core.Grpc/IProtobufMessageSerializer.cs index ca690e508d2b..7d92614b7c3f 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/IProtoMessageSerializer.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/IProtobufMessageSerializer.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// IProtoMessageSerializer.cs +// IProtobufMessageSerializer.cs namespace Microsoft.AutoGen.Core.Grpc; -public interface IProtoMessageSerializer +public interface IProtobufMessageSerializer { Google.Protobuf.WellKnownTypes.Any Serialize(object input); object Deserialize(Google.Protobuf.WellKnownTypes.Any input); -} \ No newline at end of file +} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ISerializationRegistry.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ISerializationRegistry.cs index 190ed3ec239d..c736a1c38cde 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ISerializationRegistry.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ISerializationRegistry.cs @@ -11,15 +11,15 @@ public interface IProtoSerializationRegistry /// The type to register. void RegisterSerializer(System.Type type) => RegisterSerializer(type, new ProtobufMessageSerializer(type)); - void RegisterSerializer(System.Type type, IProtoMessageSerializer serializer); + void RegisterSerializer(System.Type type, IProtobufMessageSerializer serializer); /// /// Gets the serializer for the specified type. /// /// The type to get the serializer for. /// The serializer for the specified type. - IProtoMessageSerializer? GetSerializer(System.Type type) => GetSerializer(TypeNameResolver.ResolveTypeName(type)); - IProtoMessageSerializer? GetSerializer(string typeName); + IProtobufMessageSerializer? GetSerializer(System.Type type) => GetSerializer(TypeNameResolver.ResolveTypeName(type)); + IProtobufMessageSerializer? GetSerializer(string typeName); ITypeNameResolver TypeNameResolver { get; } diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ITypeNameResolver.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ITypeNameResolver.cs index 24de4cb8b449..3b40633c4f0f 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ITypeNameResolver.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ITypeNameResolver.cs @@ -6,4 +6,4 @@ namespace Microsoft.AutoGen.Core.Grpc; public interface ITypeNameResolver { string ResolveTypeName(object input); -} \ No newline at end of file +} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufConversionExtensions.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufConversionExtensions.cs index 4850b7825afe..3175817c0eee 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufConversionExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufConversionExtensions.cs @@ -57,5 +57,4 @@ public static Protobuf.AgentId ToProtobuf(this Contracts.AgentId agentId) Key = agentId.Key }; } - -} \ No newline at end of file +} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufMessageSerializer.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufMessageSerializer.cs index 55c1aebfa47d..09da49640ad0 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufMessageSerializer.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufMessageSerializer.cs @@ -9,7 +9,7 @@ namespace Microsoft.AutoGen.Core.Grpc; /// /// Interface for serializing and deserializing agent messages. /// -public class ProtobufMessageSerializer : IProtoMessageSerializer +public class ProtobufMessageSerializer : IProtobufMessageSerializer { private System.Type _concreteType; @@ -43,4 +43,4 @@ public Any Serialize(object message) // Raise an exception if the message is not a proto IMessage throw new ArgumentException("Message must be a proto IMessage", nameof(message)); } -} \ No newline at end of file +} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoSerializationRegistry.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufSerializationRegistry.cs similarity index 56% rename from dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoSerializationRegistry.cs rename to dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufSerializationRegistry.cs index e744bcb0eee9..1bc0449d5688 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoSerializationRegistry.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufSerializationRegistry.cs @@ -1,32 +1,32 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// ProtoSerializationRegistry.cs +// ProtobufSerializationRegistry.cs namespace Microsoft.AutoGen.Core.Grpc; -public class ProtoSerializationRegistry : IProtoSerializationRegistry +public class ProtobufSerializationRegistry : IProtoSerializationRegistry { - private readonly Dictionary _serializers - = new Dictionary(); + private readonly Dictionary _serializers + = new Dictionary(); - public ITypeNameResolver TypeNameResolver => new ProtoTypeNameResolver(); + public ITypeNameResolver TypeNameResolver => new ProtobufTypeNameResolver(); public bool Exists(Type type) { return _serializers.ContainsKey(TypeNameResolver.ResolveTypeName(type)); } - public IProtoMessageSerializer? GetSerializer(Type type) + public IProtobufMessageSerializer? GetSerializer(Type type) { return GetSerializer(TypeNameResolver.ResolveTypeName(type)); } - public IProtoMessageSerializer? GetSerializer(string typeName) + public IProtobufMessageSerializer? GetSerializer(string typeName) { _serializers.TryGetValue(typeName, out var serializer); return serializer; } - public void RegisterSerializer(Type type, IProtoMessageSerializer serializer) + public void RegisterSerializer(Type type, IProtobufMessageSerializer serializer) { if (_serializers.ContainsKey(TypeNameResolver.ResolveTypeName(type))) { diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoTypeNameResolver.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufTypeNameResolver.cs similarity index 82% rename from dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoTypeNameResolver.cs rename to dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufTypeNameResolver.cs index a769b0f31c81..5d75bb879f56 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtoTypeNameResolver.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufTypeNameResolver.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// ProtoTypeNameResolver.cs +// ProtobufTypeNameResolver.cs using Google.Protobuf; namespace Microsoft.AutoGen.Core.Grpc; -public class ProtoTypeNameResolver : ITypeNameResolver +public class ProtobufTypeNameResolver : ITypeNameResolver { public string ResolveTypeName(object input) { diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/RpcExtensions.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/RpcExtensions.cs new file mode 100644 index 000000000000..a1794f9cdfef --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/RpcExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// RpcExtensions.cs + +using Google.Protobuf; +using Microsoft.AutoGen.Protobuf; + +namespace Microsoft.AutoGen.Core.Grpc; + +internal static class RpcExtensions +{ + + public static Payload ToPayload(this object message, IProtoSerializationRegistry serializationRegistry) + { + if (!serializationRegistry.Exists(message.GetType())) + { + serializationRegistry.RegisterSerializer(message.GetType()); + } + var rpcMessage = (serializationRegistry.GetSerializer(message.GetType()) ?? throw new Exception()).Serialize(message); + + var typeName = serializationRegistry.TypeNameResolver.ResolveTypeName(message); + const string PAYLOAD_DATA_CONTENT_TYPE = "application/x-protobuf"; + + // Protobuf any to byte array + Payload payload = new() + { + DataType = typeName, + DataContentType = PAYLOAD_DATA_CONTENT_TYPE, + Data = rpcMessage.ToByteString() + }; + + return payload; + } + + public static object ToObject(this Payload payload, IProtoSerializationRegistry serializationRegistry) + { + var typeName = payload.DataType; + var data = payload.Data; + var type = serializationRegistry.TypeNameResolver.ResolveTypeName(typeName); + var serializer = serializationRegistry.GetSerializer(type) ?? throw new Exception(); + var any = Google.Protobuf.WellKnownTypes.Any.Parser.ParseFrom(data); + return serializer.Deserialize(any); + } +} From f87497b6121c45391502baa6b504bca590b20e59 Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Mon, 3 Feb 2025 16:37:56 -0500 Subject: [PATCH 10/53] Finish implementation including initial manual testing --- dotnet/AutoGen.sln | 7 +++ dotnet/samples/GettingStartedGrpc/Checker.cs | 34 +++++++++++ .../GettingStartedGrpc.csproj | 26 +++++++++ dotnet/samples/GettingStartedGrpc/Modifier.cs | 29 ++++++++++ dotnet/samples/GettingStartedGrpc/Program.cs | 36 ++++++++++++ .../samples/GettingStartedGrpc/message.proto | 11 ++++ .../Core.Grpc/AgentsAppBuilderExtensions.cs | 2 +- .../Core.Grpc/CloudEventExtensions.cs | 40 +++++++------ .../Core.Grpc/GrpcAgentRuntime.cs | 58 +++++++++++++------ .../Core.Grpc/GrpcMessageRouter.cs | 12 +++- .../Core.Grpc/ITypeNameResolver.cs | 2 +- .../Core.Grpc/ProtobufTypeNameResolver.cs | 6 +- .../Core.Grpc/RpcExtensions.cs | 5 +- 13 files changed, 223 insertions(+), 45 deletions(-) create mode 100644 dotnet/samples/GettingStartedGrpc/Checker.cs create mode 100644 dotnet/samples/GettingStartedGrpc/GettingStartedGrpc.csproj create mode 100644 dotnet/samples/GettingStartedGrpc/Modifier.cs create mode 100644 dotnet/samples/GettingStartedGrpc/Program.cs create mode 100644 dotnet/samples/GettingStartedGrpc/message.proto diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln index ab7a07464c52..21344f506e9c 100644 --- a/dotnet/AutoGen.sln +++ b/dotnet/AutoGen.sln @@ -118,6 +118,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hello", "Hello", "{F42F9C8E EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Core.Grpc", "src\Microsoft.AutoGen\Core.Grpc\Microsoft.AutoGen.Core.Grpc.csproj", "{3D83C6DB-ACEA-48F3-959F-145CCD2EE135}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStartedGrpc", "samples\GettingStartedGrpc\GettingStartedGrpc.csproj", "{C3740DF1-18B1-4607-81E4-302F0308C848}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -306,6 +308,10 @@ Global {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Release|Any CPU.Build.0 = Release|Any CPU + {C3740DF1-18B1-4607-81E4-302F0308C848}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3740DF1-18B1-4607-81E4-302F0308C848}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3740DF1-18B1-4607-81E4-302F0308C848}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3740DF1-18B1-4607-81E4-302F0308C848}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -359,6 +365,7 @@ Global {3D83C6DB-ACEA-48F3-959F-145CCD2EE135} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} {AAD593FE-A49B-425E-A9FE-A0022CD25E3D} = {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} + {C3740DF1-18B1-4607-81E4-302F0308C848} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B} diff --git a/dotnet/samples/GettingStartedGrpc/Checker.cs b/dotnet/samples/GettingStartedGrpc/Checker.cs new file mode 100644 index 000000000000..7f75acbfafd6 --- /dev/null +++ b/dotnet/samples/GettingStartedGrpc/Checker.cs @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Checker.cs + +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Core; +using Microsoft.Extensions.Hosting; +using TerminationF = System.Func; + +namespace GettingStartedGrpcSample; + +[TypeSubscription("default")] +public class Checker( + AgentId id, + IAgentRuntime runtime, + IHostApplicationLifetime hostApplicationLifetime, + TerminationF runUntilFunc + ) : + BaseAgent(id, runtime, "Modifier", null), + IHandle +{ + public async ValueTask HandleAsync(Events.CountUpdate item, MessageContext messageContext) + { + if (!runUntilFunc(item.NewCount)) + { + Console.WriteLine($"\nChecker:\n{item.NewCount} passed the check, continue."); + await this.PublishMessageAsync(new Events.CountMessage { Content = item.NewCount }, new TopicId("default")); + } + else + { + Console.WriteLine($"\nChecker:\n{item.NewCount} failed the check, stopping."); + hostApplicationLifetime.StopApplication(); + } + } +} diff --git a/dotnet/samples/GettingStartedGrpc/GettingStartedGrpc.csproj b/dotnet/samples/GettingStartedGrpc/GettingStartedGrpc.csproj new file mode 100644 index 000000000000..a419cd2fe906 --- /dev/null +++ b/dotnet/samples/GettingStartedGrpc/GettingStartedGrpc.csproj @@ -0,0 +1,26 @@ + + + + Exe + net8.0 + getting_started + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/dotnet/samples/GettingStartedGrpc/Modifier.cs b/dotnet/samples/GettingStartedGrpc/Modifier.cs new file mode 100644 index 000000000000..ad3a9d8d97a6 --- /dev/null +++ b/dotnet/samples/GettingStartedGrpc/Modifier.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Modifier.cs + +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Core; + +using ModifyF = System.Func; + +namespace GettingStartedGrpcSample; + +[TypeSubscription("default")] +public class Modifier( + AgentId id, + IAgentRuntime runtime, + ModifyF modifyFunc + ) : + BaseAgent(id, runtime, "Modifier", null), + IHandle +{ + + public async ValueTask HandleAsync(Events.CountMessage item, MessageContext messageContext) + { + int newValue = modifyFunc(item.Content); + Console.WriteLine($"\nModifier:\nModified {item.Content} to {newValue}"); + + var updateMessage = new Events.CountUpdate { NewCount = newValue }; + await this.PublishMessageAsync(updateMessage, topic: new TopicId("default")); + } +} diff --git a/dotnet/samples/GettingStartedGrpc/Program.cs b/dotnet/samples/GettingStartedGrpc/Program.cs new file mode 100644 index 000000000000..aa9cc5417082 --- /dev/null +++ b/dotnet/samples/GettingStartedGrpc/Program.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Program.cs +using GettingStartedGrpcSample; +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Core; +using Microsoft.AutoGen.Core.Grpc; +using Microsoft.Extensions.DependencyInjection.Extensions; +using ModifyF = System.Func; +using TerminationF = System.Func; + +ModifyF modifyFunc = (int x) => x - 1; +TerminationF runUntilFunc = (int x) => +{ + return x <= 1; +}; + +AgentsAppBuilder appBuilder = new AgentsAppBuilder(); +appBuilder.AddGrpcAgentWorker("http://localhost:50051"); + +appBuilder.Services.TryAddSingleton(modifyFunc); +appBuilder.Services.TryAddSingleton(runUntilFunc); + +appBuilder.AddAgent("Checker"); +appBuilder.AddAgent("Modifier"); + +var app = await appBuilder.BuildAsync(); +await app.StartAsync(); + +// Send the initial count to the agents app, running on the `local` runtime, and pass through the registered services via the application `builder` +await app.PublishMessageAsync(new GettingStartedGrpcSample.Events.CountMessage +{ + Content = 10 +}, new TopicId("default")); + +// Run until application shutdown +await app.WaitForShutdownAsync(); diff --git a/dotnet/samples/GettingStartedGrpc/message.proto b/dotnet/samples/GettingStartedGrpc/message.proto new file mode 100644 index 000000000000..d4acac2e5711 --- /dev/null +++ b/dotnet/samples/GettingStartedGrpc/message.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +option csharp_namespace = "GettingStartedGrpcSample.Events"; + +message CountMessage { + int32 content = 1; +} + +message CountUpdate { + int32 new_count = 1; +} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/AgentsAppBuilderExtensions.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/AgentsAppBuilderExtensions.cs index 8b11cc3da354..7cf66d4cee9d 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/AgentsAppBuilderExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/AgentsAppBuilderExtensions.cs @@ -14,7 +14,7 @@ namespace Microsoft.AutoGen.Core.Grpc; public static class AgentsAppBuilderExtensions { - private const string _defaultAgentServiceAddress = "https://localhost:53071"; + private const string _defaultAgentServiceAddress = "http://localhost:53071"; // TODO: How do we ensure AddGrpcAgentWorker and UseInProcessRuntime are mutually exclusive? public static AgentsAppBuilder AddGrpcAgentWorker(this AgentsAppBuilder builder, string? agentServiceAddress = null) diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/CloudEventExtensions.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/CloudEventExtensions.cs index cab48d971316..1ee46660ce8e 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/CloudEventExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/CloudEventExtensions.cs @@ -8,31 +8,35 @@ namespace Microsoft.AutoGen.Core.Grpc; internal static class CloudEventExtensions { // Convert an ISubscrptionDefinition to a Protobuf Subscription - internal static CloudEvent CreateCloudEvent(Google.Protobuf.WellKnownTypes.Any payload, TopicId topic, string dataType, AgentId sender, string messageId) + internal static CloudEvent CreateCloudEvent(Google.Protobuf.WellKnownTypes.Any payload, TopicId topic, string dataType, AgentId? sender, string messageId) { + var attributes = new Dictionary + { + { + Constants.DATA_CONTENT_TYPE_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = Constants.DATA_CONTENT_TYPE_PROTOBUF_VALUE } + }, + { + Constants.DATA_SCHEMA_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = dataType } + }, + { + Constants.MESSAGE_KIND_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = Constants.MESSAGE_KIND_VALUE_PUBLISH } + } + }; + + if (sender != null) + { + var senderNonNull = (AgentId)sender; + attributes.Add(Constants.AGENT_SENDER_TYPE_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = senderNonNull.Type }); + attributes.Add(Constants.AGENT_SENDER_KEY_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = senderNonNull.Key }); + } + return new CloudEvent { ProtoData = payload, Type = topic.Type, Source = topic.Source, Id = messageId, - Attributes = { - { - Constants.DATA_CONTENT_TYPE_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = Constants.DATA_CONTENT_TYPE_PROTOBUF_VALUE } - }, - { - Constants.DATA_SCHEMA_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = dataType } - }, - { - Constants.AGENT_SENDER_TYPE_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = sender.Type } - }, - { - Constants.AGENT_SENDER_KEY_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = sender.Key } - }, - { - Constants.MESSAGE_KIND_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = Constants.MESSAGE_KIND_VALUE_PUBLISH } - } - } + Attributes = { attributes } }; } diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs index 9126a4f895cd..5f1fc7785345 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs @@ -15,7 +15,7 @@ internal sealed class AgentsContainer(IAgentRuntime hostingRuntime) private readonly IAgentRuntime hostingRuntime = hostingRuntime; private Dictionary agentInstances = new(); - private Dictionary subscriptions = new(); + public Dictionary Subscriptions = new(); private Dictionary>> agentFactories = new(); public async ValueTask EnsureAgentAsync(Contracts.AgentId agentId) @@ -57,22 +57,22 @@ public AgentType RegisterAgentFactory(AgentType type, Func RegisteredAgentTypes => this.agentFactories.Keys.ToHashSet(); @@ -90,7 +90,7 @@ public GrpcAgentRuntime(AgentRpc.AgentRpcClient client, this._logger = logger; this._shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping); - this._messageRouter = new GrpcMessageRouter(client, this, logger, this._shutdownCts.Token); + this._messageRouter = new GrpcMessageRouter(client, this, _clientId, logger, this._shutdownCts.Token); this._agentsContainer = new AgentsContainer(this); this.ServiceProvider = serviceProvider; @@ -109,14 +109,14 @@ public GrpcAgentRuntime(AgentRpc.AgentRpcClient client, public IServiceProvider ServiceProvider { get; } - private string _clientId = Guid.NewGuid().ToString(); + private Guid _clientId = Guid.NewGuid(); private CallOptions CallOptions { get { var metadata = new Metadata { - { "client-id", this._clientId } + { "client-id", this._clientId.ToString() } }; return new CallOptions(headers: metadata); } @@ -221,11 +221,15 @@ private async ValueTask HandlePublish(CloudEvent evt, CancellationToken cancella } var topic = new TopicId(evt.Type, evt.Source); - var sender = new Contracts.AgentId + Contracts.AgentId? sender = null; + if (evt.Attributes.TryGetValue(Constants.AGENT_SENDER_TYPE_ATTR, out var typeValue) && evt.Attributes.TryGetValue(Constants.AGENT_SENDER_KEY_ATTR, out var keyValue)) { - Type = evt.Attributes[Constants.AGENT_SENDER_TYPE_ATTR].CeString, - Key = evt.Attributes[Constants.AGENT_SENDER_KEY_ATTR].CeString - }; + sender = new Contracts.AgentId + { + Type = typeValue.CeString, + Key = keyValue.CeString + }; + } var messageId = evt.Id; var typeName = evt.Attributes[Constants.DATA_SCHEMA_ATTR].CeString; @@ -238,8 +242,17 @@ private async ValueTask HandlePublish(CloudEvent evt, CancellationToken cancella Topic = topic, IsRpc = false }; - var agent = await this._agentsContainer.EnsureAgentAsync(sender); - await agent.OnMessageAsync(message, messageContext); + + // Iterate over subscriptions values to find receiving agents + foreach (var subscription in this._agentsContainer.Subscriptions.Values) + { + if (subscription.Matches(topic)) + { + var recipient = subscription.MapToAgent(topic); + var agent = await this._agentsContainer.EnsureAgentAsync(recipient); + await agent.OnMessageAsync(message, messageContext); + } + } } public ValueTask StartAsync(CancellationToken cancellationToken) @@ -290,9 +303,9 @@ public async ValueTask PublishMessageAsync(object message, TopicId topic, Contra SerializationRegistry.RegisterSerializer(message.GetType()); } var protoAny = (SerializationRegistry.GetSerializer(message.GetType()) ?? throw new Exception()).Serialize(message); - var typeName = SerializationRegistry.TypeNameResolver.ResolveTypeName(message); + var typeName = SerializationRegistry.TypeNameResolver.ResolveTypeName(message.GetType()); - var cloudEvent = CloudEventExtensions.CreateCloudEvent(protoAny, topic, typeName, sender ?? new Contracts.AgentId(), messageId ?? Guid.NewGuid().ToString()); + var cloudEvent = CloudEventExtensions.CreateCloudEvent(protoAny, topic, typeName, sender, messageId ?? Guid.NewGuid().ToString()); Message msg = new() { @@ -342,8 +355,17 @@ await this._client.RemoveSubscriptionAsync(new RemoveSubscriptionRequest }, this.CallOptions); } - public ValueTask RegisterAgentFactoryAsync(AgentType type, Func> factoryFunc) - => ValueTask.FromResult(this._agentsContainer.RegisterAgentFactory(type, factoryFunc)); + public async ValueTask RegisterAgentFactoryAsync(AgentType type, Func> factoryFunc) + { + this._agentsContainer.RegisterAgentFactory(type, factoryFunc); + + await this._client.RegisterAgentAsync(new RegisterAgentTypeRequest + { + Type = type, + }, this.CallOptions); + + return type; + } public ValueTask TryGetAgentProxyAsync(Contracts.AgentId agentId) { diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcMessageRouter.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcMessageRouter.cs index 9fdf0b608785..e46b392c708f 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcMessageRouter.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcMessageRouter.cs @@ -18,15 +18,18 @@ internal sealed class AutoRestartChannel : IDisposable { private readonly object _channelLock = new(); private readonly AgentRpc.AgentRpcClient _client; + private readonly Guid _clientId; private readonly ILogger _logger; private readonly CancellationTokenSource _shutdownCts; private AsyncDuplexStreamingCall? _channel; public AutoRestartChannel(AgentRpc.AgentRpcClient client, + Guid clientId, ILogger logger, CancellationToken shutdownCancellation = default) { _client = client; + _clientId = clientId; _logger = logger; _shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(shutdownCancellation); } @@ -73,8 +76,12 @@ private AsyncDuplexStreamingCall RecreateChannel(AsyncDuplexSt { if (_channel is null || _channel == ownedChannel) { + var metadata = new Metadata + { + { "client-id", _clientId.ToString() } + }; _channel?.Dispose(); - _channel = _client.OpenChannel(cancellationToken: _shutdownCts.Token); + _channel = _client.OpenChannel(cancellationToken: _shutdownCts.Token, headers: metadata); } } } @@ -91,6 +98,7 @@ public void Dispose() internal sealed class GrpcMessageRouter(AgentRpc.AgentRpcClient client, IMessageSink incomingMessageSink, + Guid clientId, ILogger logger, CancellationToken shutdownCancellation = default) : IDisposable { @@ -111,7 +119,7 @@ internal sealed class GrpcMessageRouter(AgentRpc.AgentRpcClient client, // TODO: Enable a way to configure the channel options = Channel.CreateBounded<(Message, TaskCompletionSource)>(DefaultChannelOptions); - private readonly AutoRestartChannel _incomingMessageChannel = new AutoRestartChannel(client, logger, shutdownCancellation); + private readonly AutoRestartChannel _incomingMessageChannel = new AutoRestartChannel(client, clientId, logger, shutdownCancellation); private Task? _readTask; private Task? _writeTask; diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ITypeNameResolver.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ITypeNameResolver.cs index 3b40633c4f0f..67ba1c577f4a 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ITypeNameResolver.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ITypeNameResolver.cs @@ -5,5 +5,5 @@ namespace Microsoft.AutoGen.Core.Grpc; public interface ITypeNameResolver { - string ResolveTypeName(object input); + string ResolveTypeName(Type input); } diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufTypeNameResolver.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufTypeNameResolver.cs index 5d75bb879f56..e376f9a13daa 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufTypeNameResolver.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/ProtobufTypeNameResolver.cs @@ -7,10 +7,12 @@ namespace Microsoft.AutoGen.Core.Grpc; public class ProtobufTypeNameResolver : ITypeNameResolver { - public string ResolveTypeName(object input) + public string ResolveTypeName(Type input) { - if (input is IMessage protoMessage) + if (typeof(IMessage).IsAssignableFrom(input)) { + // TODO: Consider changing this to avoid instantiation... + var protoMessage = (IMessage?)Activator.CreateInstance(input) ?? throw new InvalidOperationException($"Failed to create instance of {input.FullName}"); return protoMessage.Descriptor.FullName; } else diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/RpcExtensions.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/RpcExtensions.cs index a1794f9cdfef..5c264887856c 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/RpcExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/RpcExtensions.cs @@ -17,7 +17,7 @@ public static Payload ToPayload(this object message, IProtoSerializationRegistry } var rpcMessage = (serializationRegistry.GetSerializer(message.GetType()) ?? throw new Exception()).Serialize(message); - var typeName = serializationRegistry.TypeNameResolver.ResolveTypeName(message); + var typeName = serializationRegistry.TypeNameResolver.ResolveTypeName(message.GetType()); const string PAYLOAD_DATA_CONTENT_TYPE = "application/x-protobuf"; // Protobuf any to byte array @@ -35,8 +35,7 @@ public static object ToObject(this Payload payload, IProtoSerializationRegistry { var typeName = payload.DataType; var data = payload.Data; - var type = serializationRegistry.TypeNameResolver.ResolveTypeName(typeName); - var serializer = serializationRegistry.GetSerializer(type) ?? throw new Exception(); + var serializer = serializationRegistry.GetSerializer(typeName) ?? throw new Exception(); var any = Google.Protobuf.WellKnownTypes.Any.Parser.ParseFrom(data); return serializer.Deserialize(any); } From f8f7093cb9dbc36425adf5b09c8c62c00daf382b Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Mon, 3 Feb 2025 16:41:19 -0500 Subject: [PATCH 11/53] remove unrelated files --- .../AgentGrpcTests.cs | 346 ++++++++---------- .../Microsoft.AutoGen.Core.Grpc.Tests.csproj | 2 +- .../AgentTests.cs | 2 +- 3 files changed, 158 insertions(+), 192 deletions(-) diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs index bb054ee738a4..7514609e145b 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs @@ -1,214 +1,184 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AgentGrpcTests.cs -//using System.Collections.Concurrent; -//using System.Text.Json; -//using FluentAssertions; -//using Google.Protobuf.Reflection; +using System.Collections.Concurrent; +using System.Text.Json; +using FluentAssertions; +using Google.Protobuf.Reflection; using Microsoft.AutoGen.Contracts; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Xunit; +using static Microsoft.AutoGen.Core.Grpc.Tests.AgentGrpcTests; namespace Microsoft.AutoGen.Core.Grpc.Tests; [Trait("Category", "UnitV2")] public class AgentGrpcTests { + /// + /// Verify that if the agent is not initialized via AgentWorker, it should throw the correct exception. + /// + /// void [Fact] - public void Agent_ShouldInitializeCorrectly() + public async Task Agent_ShouldThrowException_WhenNotInitialized() + { + using var runtime = new GrpcRuntime(); + var (_, agent) = runtime.Start(false); // Do not initialize + + // Expect an exception when calling AddSubscriptionAsync because the agent is uninitialized + await Assert.ThrowsAsync( + async () => await agent.AddSubscriptionAsync("TestEvent") + ); + } + + /// + /// validate that the agent is initialized correctly with implicit subs + /// + /// void + [Fact] + public async Task Agent_ShouldInitializeCorrectly() { using var runtime = new GrpcRuntime(); var (worker, agent) = runtime.Start(); Assert.Equal(nameof(GrpcAgentRuntime), worker.GetType().Name); + await Task.Delay(5000); + var subscriptions = await agent.GetSubscriptionsAsync(); + Assert.Equal(2, subscriptions.Count); + } + /// + /// Test AddSubscriptionAsync method + /// + /// void + [Fact] + public async Task SubscribeAsync_UnsubscribeAsync_and_GetSubscriptionsTest() + { + using var runtime = new GrpcRuntime(); + var (_, agent) = runtime.Start(); + await agent.AddSubscriptionAsync("TestEvent"); + await Task.Delay(100); + var subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); + var found = false; + foreach (var subscription in subscriptions) + { + if (subscription.TypeSubscription.TopicType == "TestEvent") + { + found = true; + } + } + Assert.True(found); + await agent.RemoveSubscriptionAsync("TestEvent").ConfigureAwait(true); + await Task.Delay(1000); + subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); + found = false; + foreach (var subscription in subscriptions) + { + if (subscription.TypeSubscription.TopicType == "TestEvent") + { + found = true; + } + } + Assert.False(found); } - ///// - ///// Verify that if the agent is not initialized via AgentWorker, it should throw the correct exception. - ///// - ///// void - //[Fact] - //public async Task Agent_ShouldThrowException_WhenNotInitialized() - //{ - // using var runtime = new GrpcRuntime(); - // var (_, agent) = runtime.Start(false); // Do not initialize - - // // Expect an exception when calling AddSubscriptionAsync because the agent is uninitialized - // await Assert.ThrowsAsync( - // async () => await agent.AddSubscriptionAsync("TestEvent") - // ); - //} - - ///// - ///// validate that the agent is initialized correctly with implicit subs - ///// - ///// void - //[Fact] - //public async Task Agent_ShouldInitializeCorrectly() - //{ - // using var runtime = new GrpcRuntime(); - // var (worker, agent) = runtime.Start(); - // Assert.Equal(nameof(GrpcAgentRuntime), worker.GetType().Name); - // await Task.Delay(5000); - // var subscriptions = await agent.GetSubscriptionsAsync(); - // Assert.Equal(2, subscriptions.Count); - //} - ///// - ///// Test AddSubscriptionAsync method - ///// - ///// void - //[Fact] - //public async Task SubscribeAsync_UnsubscribeAsync_and_GetSubscriptionsTest() - //{ - // using var runtime = new GrpcRuntime(); - // var (_, agent) = runtime.Start(); - // await agent.AddSubscriptionAsync("TestEvent"); - // await Task.Delay(100); - // var subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); - // var found = false; - // foreach (var subscription in subscriptions) - // { - // if (subscription.TypeSubscription.TopicType == "TestEvent") - // { - // found = true; - // } - // } - // Assert.True(found); - // await agent.RemoveSubscriptionAsync("TestEvent").ConfigureAwait(true); - // await Task.Delay(1000); - // subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); - // found = false; - // foreach (var subscription in subscriptions) - // { - // if (subscription.TypeSubscription.TopicType == "TestEvent") - // { - // found = true; - // } - // } - // Assert.False(found); - //} - - ///// - ///// Test StoreAsync and ReadAsync methods - ///// - ///// void - //[Fact] - //public async Task StoreAsync_and_ReadAsyncTest() - //{ - // using var runtime = new GrpcRuntime(); - // var (_, agent) = runtime.Start(); - // Dictionary state = new() - // { - // { "testdata", "Active" } - // }; - // await agent.StoreAsync(new AgentState - // { - // AgentId = agent.AgentId, - // TextData = JsonSerializer.Serialize(state) - // }).ConfigureAwait(true); - // var readState = await agent.ReadAsync(agent.AgentId).ConfigureAwait(true); - // var read = JsonSerializer.Deserialize>(readState.TextData) ?? new Dictionary { { "data", "No state data found" } }; - // read.TryGetValue("testdata", out var value); - // Assert.Equal("Active", value); - //} - - ///// - ///// Test PublishMessageAsync method and ReceiveMessage method - ///// - ///// void - //[Fact] - //public async Task PublishMessageAsync_and_ReceiveMessageTest() - //{ - // using var runtime = new GrpcRuntime(); - // var (_, agent) = runtime.Start(); - // var topicType = "TestTopic"; - // await agent.AddSubscriptionAsync(topicType).ConfigureAwait(true); - // var subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); - // var found = false; - // foreach (var subscription in subscriptions) - // { - // if (subscription.TypeSubscription.TopicType == topicType) - // { - // found = true; - // } - // } - // Assert.True(found); - // await agent.PublishMessageAsync(new TextMessage() - // { - // Source = topicType, - // TextMessage_ = "buffer" - // }, topicType).ConfigureAwait(true); - // await Task.Delay(100); - // Assert.True(TestAgent.ReceivedMessages.ContainsKey(topicType)); - // runtime.Stop(); - //} - - //[Fact] - //public async Task InvokeCorrectHandler() - //{ - // var agent = new TestAgent(new AgentsMetadata(TypeRegistry.Empty, new Dictionary(), new Dictionary>(), new Dictionary>()), new Logger(new LoggerFactory())); - - // await agent.HandleObjectAsync("hello world"); - // await agent.HandleObjectAsync(42); - - // agent.ReceivedItems.Should().HaveCount(2); - // agent.ReceivedItems[0].Should().Be("hello world"); - // agent.ReceivedItems[1].Should().Be(42); - //} -} - -/// -/// The test agent is a simple agent that is used for testing purposes. -/// -public class TestAgent(AgentId id, - IAgentRuntime runtime, - Logger? logger = null) : BaseAgent(id, runtime, "Test Agent", logger), - //IHandle, - //IHandle, - IHandle - -{ - //public ValueTask HandleAsync(TextMessage item, MessageContext messageContext) - //{ - // ReceivedMessages[item.Source] = item.Content; - // return ValueTask.CompletedTask; - //} - - public ValueTask HandleAsync(string item, MessageContext messageContext) + /// + /// Test StoreAsync and ReadAsync methods + /// + /// void + [Fact] + public async Task StoreAsync_and_ReadAsyncTest() { - ReceivedItems.Add(item); - return ValueTask.CompletedTask; + using var runtime = new GrpcRuntime(); + var (_, agent) = runtime.Start(); + Dictionary state = new() + { + { "testdata", "Active" } + }; + await agent.StoreAsync(new AgentState + { + AgentId = agent.AgentId, + TextData = JsonSerializer.Serialize(state) + }).ConfigureAwait(true); + var readState = await agent.ReadAsync(agent.AgentId).ConfigureAwait(true); + var read = JsonSerializer.Deserialize>(readState.TextData) ?? new Dictionary { { "data", "No state data found" } }; + read.TryGetValue("testdata", out var value); + Assert.Equal("Active", value); } - public ValueTask HandleAsync(int item, MessageContext messageContext) + /// + /// Test PublishMessageAsync method and ReceiveMessage method + /// + /// void + [Fact] + public async Task PublishMessageAsync_and_ReceiveMessageTest() { - ReceivedItems.Add(item); - return ValueTask.CompletedTask; + using var runtime = new GrpcRuntime(); + var (_, agent) = runtime.Start(); + var topicType = "TestTopic"; + await agent.AddSubscriptionAsync(topicType).ConfigureAwait(true); + var subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); + var found = false; + foreach (var subscription in subscriptions) + { + if (subscription.TypeSubscription.TopicType == topicType) + { + found = true; + } + } + Assert.True(found); + await agent.PublishMessageAsync(new TextMessage() + { + Source = topicType, + TextMessage_ = "buffer" + }, topicType).ConfigureAwait(true); + await Task.Delay(100); + Assert.True(TestAgent.ReceivedMessages.ContainsKey(topicType)); + runtime.Stop(); } - //public ValueTask HandleAsync(RpcTextMessage item, MessageContext messageContext) - //{ - // ReceivedMessages[item.Source] = item.Content; - // return ValueTask.FromResult(item.Content); - //} + [Fact] + public async Task InvokeCorrectHandler() + { + var agent = new TestAgent(new AgentsMetadata(TypeRegistry.Empty, new Dictionary(), new Dictionary>(), new Dictionary>()), new Logger(new LoggerFactory())); + + await agent.HandleObjectAsync("hello world"); + await agent.HandleObjectAsync(42); - public List ReceivedItems { get; private set; } = []; + agent.ReceivedItems.Should().HaveCount(2); + agent.ReceivedItems[0].Should().Be("hello world"); + agent.ReceivedItems[1].Should().Be(42); + } /// - /// Key: source - /// Value: message + /// The test agent is a simple agent that is used for testing purposes. /// - public static Dictionary ReceivedMessages { get; private set; } = new(); -} - -[TypeSubscription("TestTopic")] -public class SubscribedAgent : TestAgent -{ - public SubscribedAgent(AgentId id, - IAgentRuntime runtime, - Logger? logger = null) : base(id, runtime, logger) + public class TestAgent( + [FromKeyedServices("AgentsMetadata")] AgentsMetadata eventTypes, + Logger? logger = null) : Agent(eventTypes, logger), IHandle { + public Task Handle(TextMessage item, CancellationToken cancellationToken = default) + { + ReceivedMessages[item.Source] = item.TextMessage_; + return Task.CompletedTask; + } + public Task Handle(string item) + { + ReceivedItems.Add(item); + return Task.CompletedTask; + } + public Task Handle(int item) + { + ReceivedItems.Add(item); + return Task.CompletedTask; + } + public List ReceivedItems { get; private set; } = []; + + /// + /// Key: source + /// Value: message + /// + public static ConcurrentDictionary ReceivedMessages { get; private set; } = new(); } } @@ -242,17 +212,13 @@ private static int GetAvailablePort() private static async Task StartClientAsync() { - AgentsApp agentsApp = await new AgentsAppBuilder().UseGrpcRuntime().AddAgent("TestAgent").BuildAsync(); - - await agentsApp.StartAsync(); - - return agentsApp.Host; + return await AgentsApp.StartAsync().ConfigureAwait(false); } + private static async Task StartAppHostAsync() + { + return await Microsoft.AutoGen.Runtime.Grpc.Host.StartAsync(local: false, useGrpc: true).ConfigureAwait(false); - //private static async Task StartAppHostAsync() - //{ - // return await Microsoft.AutoGen.Runtime.Grpc.Host.StartAsync(local: false, useGrpc: true).ConfigureAwait(false); - //} + } /// /// Start - gets a new port and starts fresh instances @@ -265,14 +231,14 @@ private static async Task StartClientAsync() Environment.SetEnvironmentVariable("ASPNETCORE_HTTPS_PORTS", port.ToString()); Environment.SetEnvironmentVariable("AGENT_HOST", $"https://localhost:{port}"); - //AppHost = StartAppHostAsync().GetAwaiter().GetResult(); + AppHost = StartAppHostAsync().GetAwaiter().GetResult(); Client = StartClientAsync().GetAwaiter().GetResult(); var agent = ActivatorUtilities.CreateInstance(Client.Services); var worker = Client.Services.GetRequiredService(); if (initialize) { - //Agent.Initialize(worker, agent); + Agent.Initialize(worker, agent); } return (worker, agent); diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj index a2dad2212a7f..f14497e75fbc 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/dotnet/test/Microsoft.AutoGen.Core.Tests/AgentTests.cs b/dotnet/test/Microsoft.AutoGen.Core.Tests/AgentTests.cs index da81f40a6f3c..cc39b3564c66 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Tests/AgentTests.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Tests/AgentTests.cs @@ -121,7 +121,7 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", (id, runtime) => }); Assert.Null(agent); - await runtime.GetAgentAsync(AgentId.FromStr("MyAgent"), lazy: false); + await runtime.GetAgentAsync("MyAgent", lazy: false); Assert.NotNull(agent); Assert.True(agent.ReceivedItems.Count == 0); From 7fb61ca434e1a5025de48849851a0ee15606424d Mon Sep 17 00:00:00 2001 From: Jack Gerrits Date: Mon, 3 Feb 2025 16:57:17 -0500 Subject: [PATCH 12/53] fix build --- dotnet/src/Microsoft.AutoGen/Contracts/IAgentRuntime.cs | 6 ++---- dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs | 6 ++++++ dotnet/src/Microsoft.AutoGen/Core/InProcessRuntime.cs | 6 ++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/Contracts/IAgentRuntime.cs b/dotnet/src/Microsoft.AutoGen/Contracts/IAgentRuntime.cs index bb360617dc09..0d84fbe72d37 100644 --- a/dotnet/src/Microsoft.AutoGen/Contracts/IAgentRuntime.cs +++ b/dotnet/src/Microsoft.AutoGen/Contracts/IAgentRuntime.cs @@ -53,8 +53,7 @@ public interface IAgentRuntime : ISaveState /// An optional key to specify variations of the agent. Defaults to "default". /// If true, the agent is fetched lazily. /// A task representing the asynchronous operation, returning the agent's ID. - public ValueTask GetAgentAsync(AgentType agentType, string key = "default", bool lazy = true/*, CancellationToken? = default*/) - => this.GetAgentAsync(new AgentId(agentType, key), lazy); + public ValueTask GetAgentAsync(AgentType agentType, string key = "default", bool lazy = true/*, CancellationToken? = default*/); /// /// Retrieves an agent by its string representation. @@ -63,8 +62,7 @@ public ValueTask GetAgentAsync(AgentType agentType, string key = "defau /// An optional key to specify variations of the agent. Defaults to "default". /// If true, the agent is fetched lazily. /// A task representing the asynchronous operation, returning the agent's ID. - public ValueTask GetAgentAsync(string agent, string key = "default", bool lazy = true/*, CancellationToken? = default*/) - => this.GetAgentAsync(new AgentId(agent, key), lazy); + public ValueTask GetAgentAsync(string agent, string key = "default", bool lazy = true/*, CancellationToken? = default*/); /// /// Saves the state of an agent. diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs index 5f1fc7785345..2f2057f6aa62 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/GrpcAgentRuntime.cs @@ -317,6 +317,12 @@ public async ValueTask PublishMessageAsync(object message, TopicId topic, Contra public ValueTask GetAgentAsync(Contracts.AgentId agentId, bool lazy = true) => this._agentsContainer.GetAgentAsync(agentId, lazy); + public ValueTask GetAgentAsync(AgentType agentType, string key = "default", bool lazy = true) + => this.GetAgentAsync(new Contracts.AgentId(agentType, key), lazy); + + public ValueTask GetAgentAsync(string agent, string key = "default", bool lazy = true) + => this.GetAgentAsync(new Contracts.AgentId(agent, key), lazy); + public async ValueTask> SaveAgentStateAsync(Contracts.AgentId agentId) { IHostableAgent agent = await this._agentsContainer.EnsureAgentAsync(agentId); diff --git a/dotnet/src/Microsoft.AutoGen/Core/InProcessRuntime.cs b/dotnet/src/Microsoft.AutoGen/Core/InProcessRuntime.cs index 791376ae56e8..69b2d314e550 100644 --- a/dotnet/src/Microsoft.AutoGen/Core/InProcessRuntime.cs +++ b/dotnet/src/Microsoft.AutoGen/Core/InProcessRuntime.cs @@ -140,6 +140,12 @@ public async ValueTask GetAgentAsync(AgentId agentId, bool lazy = true) return agentId; } + public ValueTask GetAgentAsync(AgentType agentType, string key = "default", bool lazy = true) + => this.GetAgentAsync(new AgentId(agentType, key), lazy); + + public ValueTask GetAgentAsync(string agent, string key = "default", bool lazy = true) + => this.GetAgentAsync(new AgentId(agent, key), lazy); + public async ValueTask GetAgentMetadataAsync(AgentId agentId) { IHostableAgent agent = await this.EnsureAgentAsync(agentId); From 734230bc45ff1f8389b2c4b918ebc2ec701cf0de Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Mon, 3 Feb 2025 22:24:26 -0800 Subject: [PATCH 13/53] interim - adding tests for GrpcAgentRuntime --- dotnet/AutoGen.sln | 7 + .../AgentGrpcTests.cs | 327 +++++++----------- .../GrpcAgentRuntimeFixture.cs | 83 +++++ .../GrpcAgentServiceFixture.cs | 60 ++++ .../GrpcWorkerConnection.cs | 120 +++++++ .../Microsoft.AutoGen.Core.Grpc.Tests.csproj | 2 +- 6 files changed, 389 insertions(+), 210 deletions(-) create mode 100644 dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentRuntimeFixture.cs create mode 100644 dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentServiceFixture.cs create mode 100644 dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcWorkerConnection.cs diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln index 21344f506e9c..74b7ac965592 100644 --- a/dotnet/AutoGen.sln +++ b/dotnet/AutoGen.sln @@ -120,6 +120,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Core.Grpc EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStartedGrpc", "samples\GettingStartedGrpc\GettingStartedGrpc.csproj", "{C3740DF1-18B1-4607-81E4-302F0308C848}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Core.Grpc.Tests", "test\Microsoft.AutoGen.Core.Grpc.Tests\Microsoft.AutoGen.Core.Grpc.Tests.csproj", "{23A028D3-5EB1-4FA0-9CD1-A1340B830579}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -312,6 +314,10 @@ Global {C3740DF1-18B1-4607-81E4-302F0308C848}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3740DF1-18B1-4607-81E4-302F0308C848}.Release|Any CPU.ActiveCfg = Release|Any CPU {C3740DF1-18B1-4607-81E4-302F0308C848}.Release|Any CPU.Build.0 = Release|Any CPU + {23A028D3-5EB1-4FA0-9CD1-A1340B830579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23A028D3-5EB1-4FA0-9CD1-A1340B830579}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23A028D3-5EB1-4FA0-9CD1-A1340B830579}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23A028D3-5EB1-4FA0-9CD1-A1340B830579}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -366,6 +372,7 @@ Global {AAD593FE-A49B-425E-A9FE-A0022CD25E3D} = {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} {C3740DF1-18B1-4607-81E4-302F0308C848} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} + {23A028D3-5EB1-4FA0-9CD1-A1340B830579} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B} diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs index 7514609e145b..4a8507822d7d 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs @@ -1,263 +1,172 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AgentGrpcTests.cs - -using System.Collections.Concurrent; -using System.Text.Json; using FluentAssertions; -using Google.Protobuf.Reflection; using Microsoft.AutoGen.Contracts; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.AutoGen.Core.Tests; + using Xunit; -using static Microsoft.AutoGen.Core.Grpc.Tests.AgentGrpcTests; namespace Microsoft.AutoGen.Core.Grpc.Tests; [Trait("Category", "UnitV2")] public class AgentGrpcTests { - /// - /// Verify that if the agent is not initialized via AgentWorker, it should throw the correct exception. - /// - /// void [Fact] - public async Task Agent_ShouldThrowException_WhenNotInitialized() + public async Task AgentShouldNotReceiveMessagesWhenNotSubscribedTest() { - using var runtime = new GrpcRuntime(); - var (_, agent) = runtime.Start(false); // Do not initialize + var fixture = new GrpcAgentRuntimeFixture(); + var runtime = (GrpcAgentRuntime)await fixture.Start(); - // Expect an exception when calling AddSubscriptionAsync because the agent is uninitialized - await Assert.ThrowsAsync( - async () => await agent.AddSubscriptionAsync("TestEvent") - ); - } + Logger logger = new(new LoggerFactory()); + TestAgent agent = null!; - /// - /// validate that the agent is initialized correctly with implicit subs - /// - /// void - [Fact] - public async Task Agent_ShouldInitializeCorrectly() - { - using var runtime = new GrpcRuntime(); - var (worker, agent) = runtime.Start(); - Assert.Equal(nameof(GrpcAgentRuntime), worker.GetType().Name); - await Task.Delay(5000); - var subscriptions = await agent.GetSubscriptionsAsync(); - Assert.Equal(2, subscriptions.Count); - } - /// - /// Test AddSubscriptionAsync method - /// - /// void - [Fact] - public async Task SubscribeAsync_UnsubscribeAsync_and_GetSubscriptionsTest() - { - using var runtime = new GrpcRuntime(); - var (_, agent) = runtime.Start(); - await agent.AddSubscriptionAsync("TestEvent"); - await Task.Delay(100); - var subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); - var found = false; - foreach (var subscription in subscriptions) + await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => { - if (subscription.TypeSubscription.TopicType == "TestEvent") - { - found = true; - } - } - Assert.True(found); - await agent.RemoveSubscriptionAsync("TestEvent").ConfigureAwait(true); - await Task.Delay(1000); - subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); - found = false; - foreach (var subscription in subscriptions) - { - if (subscription.TypeSubscription.TopicType == "TestEvent") - { - found = true; - } - } - Assert.False(found); + agent = new TestAgent(id, runtime, logger); + return await ValueTask.FromResult(agent); + }); + + // Ensure the agent is actually created + AgentId agentId = await runtime.GetAgentAsync("MyAgent", lazy: false); + + // Validate agent ID + agentId.Should().Be(agent.Id, "Agent ID should match the registered agent"); + + var topicType = "TestTopic"; + + await runtime.PublishMessageAsync(new Core.Tests.TextMessage { Source = topicType, Content = "test" }, new TopicId(topicType)).ConfigureAwait(true); + + agent.ReceivedMessages.Any().Should().BeFalse("Agent should not receive messages when not subscribed."); + fixture.Dispose(); } - /// - /// Test StoreAsync and ReadAsync methods - /// - /// void [Fact] - public async Task StoreAsync_and_ReadAsyncTest() + public async Task AgentShouldReceiveMessagesWhenSubscribedTest() { - using var runtime = new GrpcRuntime(); - var (_, agent) = runtime.Start(); - Dictionary state = new() - { - { "testdata", "Active" } - }; - await agent.StoreAsync(new AgentState + var runtime = new InProcessRuntime(); + await runtime.StartAsync(); + + Logger logger = new(new LoggerFactory()); + SubscribedAgent agent = null!; + + await runtime.RegisterAgentFactoryAsync("MyAgent", (id, runtime) => { - AgentId = agent.AgentId, - TextData = JsonSerializer.Serialize(state) - }).ConfigureAwait(true); - var readState = await agent.ReadAsync(agent.AgentId).ConfigureAwait(true); - var read = JsonSerializer.Deserialize>(readState.TextData) ?? new Dictionary { { "data", "No state data found" } }; - read.TryGetValue("testdata", out var value); - Assert.Equal("Active", value); - } + agent = new SubscribedAgent(id, runtime, logger); + return ValueTask.FromResult(agent); + }); + + // Ensure the agent is actually created + AgentId agentId = await runtime.GetAgentAsync("MyAgent", lazy: false); + + // Validate agent ID + agentId.Should().Be(agent.Id, "Agent ID should match the registered agent"); + + await runtime.RegisterImplicitAgentSubscriptionsAsync("MyAgent"); - /// - /// Test PublishMessageAsync method and ReceiveMessage method - /// - /// void - [Fact] - public async Task PublishMessageAsync_and_ReceiveMessageTest() - { - using var runtime = new GrpcRuntime(); - var (_, agent) = runtime.Start(); var topicType = "TestTopic"; - await agent.AddSubscriptionAsync(topicType).ConfigureAwait(true); - var subscriptions = await agent.GetSubscriptionsAsync().ConfigureAwait(true); - var found = false; - foreach (var subscription in subscriptions) - { - if (subscription.TypeSubscription.TopicType == topicType) - { - found = true; - } - } - Assert.True(found); - await agent.PublishMessageAsync(new TextMessage() - { - Source = topicType, - TextMessage_ = "buffer" - }, topicType).ConfigureAwait(true); - await Task.Delay(100); - Assert.True(TestAgent.ReceivedMessages.ContainsKey(topicType)); - runtime.Stop(); + + await runtime.PublishMessageAsync(new Core.Tests.TextMessage { Source = topicType, Content = "test" }, new TopicId(topicType)).ConfigureAwait(true); + + await runtime.RunUntilIdleAsync(); + + agent.ReceivedMessages.Any().Should().BeTrue("Agent should receive messages when subscribed."); } [Fact] - public async Task InvokeCorrectHandler() + public async Task SendMessageAsyncShouldReturnResponseTest() { - var agent = new TestAgent(new AgentsMetadata(TypeRegistry.Empty, new Dictionary(), new Dictionary>(), new Dictionary>()), new Logger(new LoggerFactory())); + // Arrange + var runtime = new InProcessRuntime(); + await runtime.StartAsync(); - await agent.HandleObjectAsync("hello world"); - await agent.HandleObjectAsync(42); + Logger logger = new(new LoggerFactory()); + await runtime.RegisterAgentFactoryAsync("MyAgent", (id, runtime) => ValueTask.FromResult(new TestAgent(id, runtime, logger))); + await runtime.RegisterImplicitAgentSubscriptionsAsync("MyAgent"); - agent.ReceivedItems.Should().HaveCount(2); - agent.ReceivedItems[0].Should().Be("hello world"); - agent.ReceivedItems[1].Should().Be(42); - } + var agentId = new AgentId("MyAgent", "TestAgent"); - /// - /// The test agent is a simple agent that is used for testing purposes. - /// - public class TestAgent( - [FromKeyedServices("AgentsMetadata")] AgentsMetadata eventTypes, - Logger? logger = null) : Agent(eventTypes, logger), IHandle - { - public Task Handle(TextMessage item, CancellationToken cancellationToken = default) + var response = await runtime.SendMessageAsync(new RpcTextMessage { Source = "TestTopic", Content = "Request" }, agentId); + + // Assert + Assert.NotNull(response); + Assert.IsType(response); + if (response is string responseString) { - ReceivedMessages[item.Source] = item.TextMessage_; - return Task.CompletedTask; + Assert.Equal("Request", responseString); } - public Task Handle(string item) - { - ReceivedItems.Add(item); - return Task.CompletedTask; - } - public Task Handle(int item) + } + + public class ReceiverAgent(AgentId id, + IAgentRuntime runtime) : BaseAgent(id, runtime, "Receiver Agent", null), + IHandle + { + public ValueTask HandleAsync(string item, MessageContext messageContext) { ReceivedItems.Add(item); - return Task.CompletedTask; + return ValueTask.CompletedTask; } - public List ReceivedItems { get; private set; } = []; - /// - /// Key: source - /// Value: message - /// - public static ConcurrentDictionary ReceivedMessages { get; private set; } = new(); + public List ReceivedItems { get; private set; } = []; } -} - -/// -/// GrpcRuntimeFixture - provides a fixture for the agent runtime. -/// -/// -/// This fixture is used to provide a runtime for the agent tests. -/// However, it is shared between tests. So operations from one test can affect another. -/// -public sealed class GrpcRuntime : IDisposable -{ - public IHost Client { get; private set; } - public IHost? AppHost { get; private set; } - public GrpcRuntime() + [Fact] + public async Task SubscribeAsyncRemoveSubscriptionAsyncAndGetSubscriptionsTest() { - Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development"); - AppHost = Host.CreateDefaultBuilder().Build(); - Client = Host.CreateDefaultBuilder().Build(); - } + var runtime = new InProcessRuntime(); + await runtime.StartAsync(); + ReceiverAgent? agent = null; + await runtime.RegisterAgentFactoryAsync("MyAgent", (id, runtime) => + { + agent = new ReceiverAgent(id, runtime); + return ValueTask.FromResult(agent); + }); - private static int GetAvailablePort() - { - using var listener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Loopback, 0); - listener.Start(); - int port = ((System.Net.IPEndPoint)listener.LocalEndpoint).Port; - listener.Stop(); - return port; - } + Assert.Null(agent); + await runtime.GetAgentAsync("MyAgent", lazy: false); + Assert.NotNull(agent); + Assert.True(agent.ReceivedItems.Count == 0); - private static async Task StartClientAsync() - { - return await AgentsApp.StartAsync().ConfigureAwait(false); - } - private static async Task StartAppHostAsync() - { - return await Microsoft.AutoGen.Runtime.Grpc.Host.StartAsync(local: false, useGrpc: true).ConfigureAwait(false); + var topicTypeName = "TestTopic"; + await runtime.PublishMessageAsync("info", new TopicId(topicTypeName)); + await Task.Delay(100); - } + Assert.True(agent.ReceivedItems.Count == 0); - /// - /// Start - gets a new port and starts fresh instances - /// - public (IAgentRuntime, TestAgent) Start(bool initialize = true) - { - int port = GetAvailablePort(); // Get a new port per test run + var subscription = new TypeSubscription(topicTypeName, "MyAgent"); + await runtime.AddSubscriptionAsync(subscription); - // Update environment variables so each test runs independently - Environment.SetEnvironmentVariable("ASPNETCORE_HTTPS_PORTS", port.ToString()); - Environment.SetEnvironmentVariable("AGENT_HOST", $"https://localhost:{port}"); + await runtime.PublishMessageAsync("info", new TopicId(topicTypeName)); + await Task.Delay(100); - AppHost = StartAppHostAsync().GetAwaiter().GetResult(); - Client = StartClientAsync().GetAwaiter().GetResult(); + Assert.True(agent.ReceivedItems.Count == 1); + Assert.Equal("info", agent.ReceivedItems[0]); - var agent = ActivatorUtilities.CreateInstance(Client.Services); - var worker = Client.Services.GetRequiredService(); - if (initialize) - { - Agent.Initialize(worker, agent); - } + await runtime.RemoveSubscriptionAsync(subscription.Id); + await runtime.PublishMessageAsync("info", new TopicId(topicTypeName)); + await Task.Delay(100); - return (worker, agent); + Assert.True(agent.ReceivedItems.Count == 1); } - /// - /// Stop - stops the agent and ensures cleanup - /// - public void Stop() + [Fact] + public async Task AgentShouldSaveStateCorrectlyTest() { - Client?.StopAsync().GetAwaiter().GetResult(); - AppHost?.StopAsync().GetAwaiter().GetResult(); - } + var runtime = new InProcessRuntime(); + await runtime.StartAsync(); - /// - /// Dispose - Ensures cleanup after each test - /// - public void Dispose() - { - Stop(); + Logger logger = new(new LoggerFactory()); + TestAgent agent = new TestAgent(new AgentId("TestType", "TestKey"), runtime, logger); + + var state = await agent.SaveStateAsync(); + + // Ensure state is a dictionary + state.Should().NotBeNull(); + state.Should().BeOfType>(); + state.Should().BeEmpty("Default SaveStateAsync should return an empty dictionary."); + + // Add a sample value and verify it updates correctly + state["testKey"] = "testValue"; + state.Should().ContainKey("testKey").WhoseValue.Should().Be("testValue"); } } diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentRuntimeFixture.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentRuntimeFixture.cs new file mode 100644 index 000000000000..f9adaf0659b7 --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentRuntimeFixture.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// GrpcAgentRuntimeFixture.cs +using Microsoft.AspNetCore.Builder; +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Core.Tests; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Microsoft.AutoGen.Core.Grpc.Tests; +/// +/// Fixture for setting up the gRPC agent runtime for testing. +/// +public sealed class GrpcAgentRuntimeFixture : IDisposable +{ + /// the gRPC agent runtime. + public AgentsApp? Client { get; private set; } + /// mock server for testing. + public WebApplication? Server { get; private set; } + + public GrpcAgentRuntimeFixture() + { + } + /// + /// Start - gets a new port and starts fresh instances + /// + public async Task Start(bool initialize = true) + { + int port = GetAvailablePort(); // Get a new port per test run + + // Update environment variables so each test runs independently + Environment.SetEnvironmentVariable("ASPNETCORE_HTTPS_PORTS", port.ToString()); + Environment.SetEnvironmentVariable("AGENT_HOST", $"https://localhost:{port}"); + Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development"); + Server = ServerBuilder().Result; + await Server.StartAsync().ConfigureAwait(true); + Client = ClientBuilder().Result; + await Client.StartAsync().ConfigureAwait(true); + + var worker = Client.Services.GetRequiredService(); + + return (worker); + } + private static async Task ClientBuilder() + { + var appBuilder = new AgentsAppBuilder(); + appBuilder.AddGrpcAgentWorker(); + appBuilder.AddAgent("TestAgent"); + return await appBuilder.BuildAsync(); + } + private static async Task ServerBuilder() + { + var builder = WebApplication.CreateBuilder(); + builder.Services.AddGrpc(); + var app = builder.Build(); + app.MapGrpcService(); + return app; + } + private static int GetAvailablePort() + { + using var listener = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Loopback, 0); + listener.Start(); + int port = ((System.Net.IPEndPoint)listener.LocalEndpoint).Port; + listener.Stop(); + return port; + } + /// + /// Stop - stops the agent and ensures cleanup + /// + public void Stop() + { + (Client as IHost)?.StopAsync(TimeSpan.FromSeconds(30)).GetAwaiter().GetResult(); + Server?.StopAsync().GetAwaiter().GetResult(); + } + + /// + /// Dispose - Ensures cleanup after each test + /// + public void Dispose() + { + Stop(); + } + +} \ No newline at end of file diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentServiceFixture.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentServiceFixture.cs new file mode 100644 index 000000000000..83032d5026a8 --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentServiceFixture.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// GrpcAgentServiceFixture.cs +using Grpc.Core; +using Microsoft.AutoGen.Protobuf; +namespace Microsoft.AutoGen.Core.Grpc.Tests; + +public sealed class GrpcAgentServiceFixture() : AgentRpc.AgentRpcBase +{ + + public override async Task OpenChannel(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) + { + try + { + var workerProcess = new GrpcWorkerConnection(requestStream, responseStream, context); + await workerProcess.Connect().ConfigureAwait(true); + } + catch + { + if (context.CancellationToken.IsCancellationRequested) + { + return; + } + throw; + } + } + public override async Task GetState(AgentId request, ServerCallContext context) + { + return new GetStateResponse { AgentState = new AgentState { AgentId = request } }; + } + public override async Task SaveState(AgentState request, ServerCallContext context) + { + return new SaveStateResponse + { + }; + } + public override async Task AddSubscription(AddSubscriptionRequest request, ServerCallContext context) + { + return new AddSubscriptionResponse + { + }; + } + public override async Task RemoveSubscription(RemoveSubscriptionRequest request, ServerCallContext context) + { + return new RemoveSubscriptionResponse + { + }; + } + public override async Task GetSubscriptions(GetSubscriptionsRequest request, ServerCallContext context) + { + return new GetSubscriptionsResponse + { + }; + } + public override async Task RegisterAgent(RegisterAgentTypeRequest request, ServerCallContext context) + { + return new RegisterAgentTypeResponse + { + }; + } +} \ No newline at end of file diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcWorkerConnection.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcWorkerConnection.cs new file mode 100644 index 000000000000..8debecfe90dd --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcWorkerConnection.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// GrpcWorkerConnection.cs + +using System.Threading.Channels; +using Grpc.Core; +using Microsoft.AutoGen.Protobuf; + +namespace Microsoft.AutoGen.Core.Grpc.Tests; + +internal sealed class GrpcWorkerConnection : IAsyncDisposable +{ + private static long s_nextConnectionId; + private Task _readTask = Task.CompletedTask; + private Task _writeTask = Task.CompletedTask; + private readonly string _connectionId = Interlocked.Increment(ref s_nextConnectionId).ToString(); + private readonly object _lock = new(); + private readonly HashSet _supportedTypes = []; + private readonly CancellationTokenSource _shutdownCancellationToken = new(); + public Task Completion { get; private set; } = Task.CompletedTask; + public IAsyncStreamReader RequestStream { get; } + public IServerStreamWriter ResponseStream { get; } + public ServerCallContext ServerCallContext { get; } + private readonly Channel _outboundMessages; + public GrpcWorkerConnection(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) + { + RequestStream = requestStream; + ResponseStream = responseStream; + ServerCallContext = context; + _outboundMessages = Channel.CreateUnbounded(new UnboundedChannelOptions { AllowSynchronousContinuations = true, SingleReader = true, SingleWriter = false }); + } + public Task Connect() + { + var didSuppress = false; + if (!ExecutionContext.IsFlowSuppressed()) + { + didSuppress = true; + ExecutionContext.SuppressFlow(); + } + + try + { + _readTask = Task.Run(RunReadPump); + _writeTask = Task.Run(RunWritePump); + } + finally + { + if (didSuppress) + { + ExecutionContext.RestoreFlow(); + } + } + + return Completion = Task.WhenAll(_readTask, _writeTask); + } + public void AddSupportedType(string type) + { + lock (_lock) + { + _supportedTypes.Add(type); + } + } + public HashSet GetSupportedTypes() + { + lock (_lock) + { + return new HashSet(_supportedTypes); + } + } + public async Task SendMessage(Message message) + { + await _outboundMessages.Writer.WriteAsync(message).ConfigureAwait(false); + } + public async Task RunReadPump() + { + await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); + try + { + await foreach (var message in RequestStream.ReadAllAsync(_shutdownCancellationToken.Token)) + { + // Fire and forget + //_gateway.OnReceivedMessageAsync(this, message, _shutdownCancellationToken.Token).Ignore(); + } + } + catch (OperationCanceledException) + { + } + finally + { + _shutdownCancellationToken.Cancel(); + //_gateway.OnRemoveWorkerProcess(this); + } + } + + public async Task RunWritePump() + { + await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); + try + { + await foreach (var message in _outboundMessages.Reader.ReadAllAsync(_shutdownCancellationToken.Token)) + { + await ResponseStream.WriteAsync(message); + } + } + catch (OperationCanceledException) + { + } + finally + { + _shutdownCancellationToken.Cancel(); + } + } + + public async ValueTask DisposeAsync() + { + _shutdownCancellationToken.Cancel(); + await Completion.ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); + } + + public override string ToString() => $"Connection-{_connectionId}"; +} diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj index f14497e75fbc..8c47b5326094 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj @@ -10,7 +10,7 @@ - + From ef9230eb6d3e21e92515087e0d3d28ab22fd5cb6 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Mon, 3 Feb 2025 22:26:52 -0800 Subject: [PATCH 14/53] moving all the tests to the right runtime --- .../AgentGrpcTests.cs | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs index 4a8507822d7d..12f40c4dac51 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs @@ -44,16 +44,16 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => [Fact] public async Task AgentShouldReceiveMessagesWhenSubscribedTest() { - var runtime = new InProcessRuntime(); - await runtime.StartAsync(); + var fixture = new GrpcAgentRuntimeFixture(); + var runtime = (GrpcAgentRuntime)await fixture.Start(); Logger logger = new(new LoggerFactory()); SubscribedAgent agent = null!; - await runtime.RegisterAgentFactoryAsync("MyAgent", (id, runtime) => + await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => { agent = new SubscribedAgent(id, runtime, logger); - return ValueTask.FromResult(agent); + return await ValueTask.FromResult(agent); }); // Ensure the agent is actually created @@ -68,8 +68,6 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", (id, runtime) => await runtime.PublishMessageAsync(new Core.Tests.TextMessage { Source = topicType, Content = "test" }, new TopicId(topicType)).ConfigureAwait(true); - await runtime.RunUntilIdleAsync(); - agent.ReceivedMessages.Any().Should().BeTrue("Agent should receive messages when subscribed."); } @@ -77,11 +75,11 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", (id, runtime) => public async Task SendMessageAsyncShouldReturnResponseTest() { // Arrange - var runtime = new InProcessRuntime(); - await runtime.StartAsync(); + var fixture = new GrpcAgentRuntimeFixture(); + var runtime = (GrpcAgentRuntime)await fixture.Start(); Logger logger = new(new LoggerFactory()); - await runtime.RegisterAgentFactoryAsync("MyAgent", (id, runtime) => ValueTask.FromResult(new TestAgent(id, runtime, logger))); + await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => await ValueTask.FromResult(new TestAgent(id, runtime, logger))); await runtime.RegisterImplicitAgentSubscriptionsAsync("MyAgent"); var agentId = new AgentId("MyAgent", "TestAgent"); @@ -113,13 +111,13 @@ public ValueTask HandleAsync(string item, MessageContext messageContext) [Fact] public async Task SubscribeAsyncRemoveSubscriptionAsyncAndGetSubscriptionsTest() { - var runtime = new InProcessRuntime(); - await runtime.StartAsync(); + var fixture = new GrpcAgentRuntimeFixture(); + var runtime = (GrpcAgentRuntime)await fixture.Start(); ReceiverAgent? agent = null; - await runtime.RegisterAgentFactoryAsync("MyAgent", (id, runtime) => + await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => { agent = new ReceiverAgent(id, runtime); - return ValueTask.FromResult(agent); + return await ValueTask.FromResult(agent); }); Assert.Null(agent); @@ -152,8 +150,9 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", (id, runtime) => [Fact] public async Task AgentShouldSaveStateCorrectlyTest() { - var runtime = new InProcessRuntime(); - await runtime.StartAsync(); + + var fixture = new GrpcAgentRuntimeFixture(); + var runtime = (GrpcAgentRuntime)await fixture.Start(); Logger logger = new(new LoggerFactory()); TestAgent agent = new TestAgent(new AgentId("TestType", "TestKey"), runtime, logger); From 05a077bf33c90a1c5f9af160094e7050d3a1c379 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Mon, 3 Feb 2025 22:28:31 -0800 Subject: [PATCH 15/53] cleanup each test --- .../test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs index 12f40c4dac51..b7ed874f66ba 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs @@ -69,6 +69,7 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => await runtime.PublishMessageAsync(new Core.Tests.TextMessage { Source = topicType, Content = "test" }, new TopicId(topicType)).ConfigureAwait(true); agent.ReceivedMessages.Any().Should().BeTrue("Agent should receive messages when subscribed."); + fixture.Dispose(); } [Fact] @@ -93,6 +94,7 @@ public async Task SendMessageAsyncShouldReturnResponseTest() { Assert.Equal("Request", responseString); } + fixture.Dispose(); } public class ReceiverAgent(AgentId id, @@ -145,6 +147,7 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => await Task.Delay(100); Assert.True(agent.ReceivedItems.Count == 1); + fixture.Dispose(); } [Fact] @@ -167,5 +170,6 @@ public async Task AgentShouldSaveStateCorrectlyTest() // Add a sample value and verify it updates correctly state["testKey"] = "testValue"; state.Should().ContainKey("testKey").WhoseValue.Should().Be("testValue"); + fixture.Dispose(); } } From 74329098768ad6d4a7c09597243e9f893c8975d7 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Tue, 4 Feb 2025 12:00:27 -0800 Subject: [PATCH 16/53] Simplify test server side --- .../AgentGrpcTests.cs | 4 +- .../GrpcAgentServiceFixture.cs | 46 ++++--------------- ...nection.cs => TestGrpcWorkerConnection.cs} | 22 +++++++-- .../AgentTests.cs | 2 +- 4 files changed, 31 insertions(+), 43 deletions(-) rename dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/{GrpcWorkerConnection.cs => TestGrpcWorkerConnection.cs} (75%) diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs index b7ed874f66ba..61c604b33eb4 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs @@ -35,7 +35,7 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => var topicType = "TestTopic"; - await runtime.PublishMessageAsync(new Core.Tests.TextMessage { Source = topicType, Content = "test" }, new TopicId(topicType)).ConfigureAwait(true); + await runtime.PublishMessageAsync(new Contracts.TextMessage { Source = topicType, TextMessage_ = "test" }, new TopicId(topicType)).ConfigureAwait(true); agent.ReceivedMessages.Any().Should().BeFalse("Agent should not receive messages when not subscribed."); fixture.Dispose(); @@ -66,7 +66,7 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => var topicType = "TestTopic"; - await runtime.PublishMessageAsync(new Core.Tests.TextMessage { Source = topicType, Content = "test" }, new TopicId(topicType)).ConfigureAwait(true); + await runtime.PublishMessageAsync(new Contracts.TextMessage { Source = topicType, TextMessage_ = "test" }, new TopicId(topicType)).ConfigureAwait(true); agent.ReceivedMessages.Any().Should().BeTrue("Agent should receive messages when subscribed."); fixture.Dispose(); diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentServiceFixture.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentServiceFixture.cs index 83032d5026a8..75eba7af47a7 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentServiceFixture.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentServiceFixture.cs @@ -4,14 +4,16 @@ using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.Core.Grpc.Tests; +/// +/// This fixture is largely just a loopback as we are testing the client side logic of the GrpcAgentRuntime in isolation from the rest of the system. +/// public sealed class GrpcAgentServiceFixture() : AgentRpc.AgentRpcBase { - public override async Task OpenChannel(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) { try { - var workerProcess = new GrpcWorkerConnection(requestStream, responseStream, context); + var workerProcess = new TestGrpcWorkerConnection(requestStream, responseStream, context); await workerProcess.Connect().ConfigureAwait(true); } catch @@ -23,38 +25,10 @@ public override async Task OpenChannel(IAsyncStreamReader requestStream throw; } } - public override async Task GetState(AgentId request, ServerCallContext context) - { - return new GetStateResponse { AgentState = new AgentState { AgentId = request } }; - } - public override async Task SaveState(AgentState request, ServerCallContext context) - { - return new SaveStateResponse - { - }; - } - public override async Task AddSubscription(AddSubscriptionRequest request, ServerCallContext context) - { - return new AddSubscriptionResponse - { - }; - } - public override async Task RemoveSubscription(RemoveSubscriptionRequest request, ServerCallContext context) - { - return new RemoveSubscriptionResponse - { - }; - } - public override async Task GetSubscriptions(GetSubscriptionsRequest request, ServerCallContext context) - { - return new GetSubscriptionsResponse - { - }; - } - public override async Task RegisterAgent(RegisterAgentTypeRequest request, ServerCallContext context) - { - return new RegisterAgentTypeResponse - { - }; - } + public override async Task GetState(Protobuf.AgentId request, ServerCallContext context) => new GetStateResponse { AgentState = new AgentState { AgentId = request } }; + public override async Task SaveState(AgentState request, ServerCallContext context) => new SaveStateResponse { }; + public override async Task AddSubscription(AddSubscriptionRequest request, ServerCallContext context) => new AddSubscriptionResponse { }; + public override async Task RemoveSubscription(RemoveSubscriptionRequest request, ServerCallContext context) => new RemoveSubscriptionResponse { }; + public override async Task GetSubscriptions(GetSubscriptionsRequest request, ServerCallContext context) => new GetSubscriptionsResponse { }; + public override async Task RegisterAgent(RegisterAgentTypeRequest request, ServerCallContext context) => new RegisterAgentTypeResponse { }; } \ No newline at end of file diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcWorkerConnection.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/TestGrpcWorkerConnection.cs similarity index 75% rename from dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcWorkerConnection.cs rename to dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/TestGrpcWorkerConnection.cs index 8debecfe90dd..20b8169db11f 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcWorkerConnection.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/TestGrpcWorkerConnection.cs @@ -1,5 +1,5 @@ // Copyright (c) Microsoft Corporation. All rights reserved. -// GrpcWorkerConnection.cs +// TestGrpcWorkerConnection.cs using System.Threading.Channels; using Grpc.Core; @@ -7,7 +7,7 @@ namespace Microsoft.AutoGen.Core.Grpc.Tests; -internal sealed class GrpcWorkerConnection : IAsyncDisposable +internal sealed class TestGrpcWorkerConnection : IAsyncDisposable { private static long s_nextConnectionId; private Task _readTask = Task.CompletedTask; @@ -21,7 +21,7 @@ internal sealed class GrpcWorkerConnection : IAsyncDisposable public IServerStreamWriter ResponseStream { get; } public ServerCallContext ServerCallContext { get; } private readonly Channel _outboundMessages; - public GrpcWorkerConnection(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) + public TestGrpcWorkerConnection(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) { RequestStream = requestStream; ResponseStream = responseStream; @@ -77,8 +77,22 @@ public async Task RunReadPump() { await foreach (var message in RequestStream.ReadAllAsync(_shutdownCancellationToken.Token)) { - // Fire and forget //_gateway.OnReceivedMessageAsync(this, message, _shutdownCancellationToken.Token).Ignore(); + switch (message.MessageCase) + { + case Message.MessageOneofCase.Request: + await SendMessage(new Message { Request = message.Request }).ConfigureAwait(false); + break; + case Message.MessageOneofCase.Response: + await SendMessage(new Message { Response = message.Response }).ConfigureAwait(false); + break; + case Message.MessageOneofCase.CloudEvent: + await SendMessage(new Message { CloudEvent = message.CloudEvent }).ConfigureAwait(false); + break; + default: + // if it wasn't recognized return bad request + throw new RpcException(new Status(StatusCode.InvalidArgument, $"Unknown message type for message '{message}'")); + }; } } catch (OperationCanceledException) diff --git a/dotnet/test/Microsoft.AutoGen.Core.Tests/AgentTests.cs b/dotnet/test/Microsoft.AutoGen.Core.Tests/AgentTests.cs index cc39b3564c66..c091f9eb7478 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Tests/AgentTests.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Tests/AgentTests.cs @@ -109,7 +109,7 @@ public ValueTask HandleAsync(string item, MessageContext messageContext) } [Fact] - public async Task SubscribeAsyncRemoveSubscriptionAsyncAndGetSubscriptionsTest() + public async Task SubscribeAsyncRemoveSubscriptionAsyncTest() { var runtime = new InProcessRuntime(); await runtime.StartAsync(); From 0dc34f7dad1ea24433cd3303eed3292a652c69f2 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Wed, 5 Feb 2025 08:55:50 -0800 Subject: [PATCH 17/53] interim commit so I can rebase --- dotnet/AutoGen.sln | 19 ++++++++++++------- .../Microsoft.AutoGen.Core.Grpc.csproj | 1 - .../Abstractions/IAgentGrain.cs | 5 +++-- .../Abstractions/IGateway.cs | 7 ++++--- .../Abstractions/IGatewayRegistry.cs | 0 .../Abstractions/IRegistryGrain.cs | 0 ...rosoft.AutoGen.RuntimeGateway.Grpc.csproj} | 9 +++++++++ .../Services/AgentWorkerHostingExtensions.cs | 0 .../Services/Grpc/GrpcGateway.cs | 3 ++- .../Services/Grpc/GrpcGatewayService.cs | 4 ++-- .../Services/Grpc/GrpcWorkerConnection.cs | 4 ++-- .../Services/Orleans/AgentStateGrain.cs | 0 .../Services/Orleans/ISubscriptionsGrain.cs | 0 .../Orleans/OrleansRuntimeHostingExtenions.cs | 0 .../Services/Orleans/RegistryGrain.cs | 0 .../Services/Orleans/SubscriptionsGrain.cs | 0 .../AddSubscriptionRequestSurrogate.cs | 0 .../AddSubscriptionResponseSurrogate.cs | 0 .../Orleans/Surrogates/AgentIdSurrogate.cs | 0 .../Orleans/Surrogates/AgentStateSurrogate.cs | 0 .../Orleans/Surrogates/CloudEventSurrogate.cs | 0 .../Surrogates/GetSubscriptionsRequest.cs | 0 .../RegisterAgentTypeRequestSurrogate.cs | 0 .../RegisterAgentTypeResponseSurrogate.cs | 0 .../Surrogates/RemoveSubscriptionRequest.cs | 0 .../Surrogates/RemoveSubscriptionResponse.cs | 0 .../Orleans/Surrogates/RpcRequestSurrogate.cs | 0 .../Surrogates/RpcResponseSurrogate.cs | 0 .../Surrogates/SubscriptionSurrogate.cs | 0 .../TypePrefixSubscriptionSurrogate.cs | 0 .../Surrogates/TypeSubscriptionSurrogate.cs | 0 31 files changed, 34 insertions(+), 18 deletions(-) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Abstractions/IAgentGrain.cs (56%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Abstractions/IGateway.cs (75%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Abstractions/IGatewayRegistry.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Abstractions/IRegistryGrain.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc/Microsoft.AutoGen.Runtime.Grpc.csproj => RuntimeGateway.Grpc/Microsoft.AutoGen.RuntimeGateway.Grpc.csproj} (77%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/AgentWorkerHostingExtensions.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Grpc/GrpcGateway.cs (99%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Grpc/GrpcGatewayService.cs (94%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Grpc/GrpcWorkerConnection.cs (97%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/AgentStateGrain.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/ISubscriptionsGrain.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/OrleansRuntimeHostingExtenions.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/RegistryGrain.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/SubscriptionsGrain.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/AddSubscriptionRequestSurrogate.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/AddSubscriptionResponseSurrogate.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/AgentIdSurrogate.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/AgentStateSurrogate.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/CloudEventSurrogate.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/GetSubscriptionsRequest.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/RegisterAgentTypeRequestSurrogate.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/RegisterAgentTypeResponseSurrogate.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/RemoveSubscriptionRequest.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/RemoveSubscriptionResponse.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/RpcRequestSurrogate.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/RpcResponseSurrogate.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/SubscriptionSurrogate.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/TypePrefixSubscriptionSurrogate.cs (100%) rename dotnet/src/Microsoft.AutoGen/{Runtime.Grpc => RuntimeGateway.Grpc}/Services/Orleans/Surrogates/TypeSubscriptionSurrogate.cs (100%) diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln index 74b7ac965592..c682e8a36661 100644 --- a/dotnet/AutoGen.sln +++ b/dotnet/AutoGen.sln @@ -122,6 +122,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStartedGrpc", "sampl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Core.Grpc.Tests", "test\Microsoft.AutoGen.Core.Grpc.Tests\Microsoft.AutoGen.Core.Grpc.Tests.csproj", "{23A028D3-5EB1-4FA0-9CD1-A1340B830579}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.RuntimeGateway.Grpc", "src\Microsoft.AutoGen\RuntimeGateway.Grpc\Microsoft.AutoGen.RuntimeGateway.Grpc.csproj", "{09B6171D-3B94-451E-B4AA-A43506699A50}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -300,16 +302,14 @@ Global {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D}.Release|Any CPU.Build.0 = Release|Any CPU - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.CoreOnly|Any CPU.ActiveCfg = Debug|Any CPU - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.CoreOnly|Any CPU.Build.0 = Debug|Any CPU - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Release|Any CPU.Build.0 = Release|Any CPU {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Release|Any CPU.Build.0 = Release|Any CPU + {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Release|Any CPU.Build.0 = Release|Any CPU {C3740DF1-18B1-4607-81E4-302F0308C848}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C3740DF1-18B1-4607-81E4-302F0308C848}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3740DF1-18B1-4607-81E4-302F0308C848}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -318,6 +318,10 @@ Global {23A028D3-5EB1-4FA0-9CD1-A1340B830579}.Debug|Any CPU.Build.0 = Debug|Any CPU {23A028D3-5EB1-4FA0-9CD1-A1340B830579}.Release|Any CPU.ActiveCfg = Release|Any CPU {23A028D3-5EB1-4FA0-9CD1-A1340B830579}.Release|Any CPU.Build.0 = Release|Any CPU + {09B6171D-3B94-451E-B4AA-A43506699A50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09B6171D-3B94-451E-B4AA-A43506699A50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09B6171D-3B94-451E-B4AA-A43506699A50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09B6171D-3B94-451E-B4AA-A43506699A50}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -368,11 +372,12 @@ Global {EAFFE339-26CB-4019-991D-BCCE8E7D33A1} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} {58AD8E1D-83BD-4950-A324-1A20677D78D9} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} {AAD593FE-A49B-425E-A9FE-A0022CD25E3D} = {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} + {3D83C6DB-ACEA-48F3-959F-145CCD2EE135} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} {C3740DF1-18B1-4607-81E4-302F0308C848} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} {23A028D3-5EB1-4FA0-9CD1-A1340B830579} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} + {09B6171D-3B94-451E-B4AA-A43506699A50} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B} diff --git a/dotnet/src/Microsoft.AutoGen/Core.Grpc/Microsoft.AutoGen.Core.Grpc.csproj b/dotnet/src/Microsoft.AutoGen/Core.Grpc/Microsoft.AutoGen.Core.Grpc.csproj index c28a9b1c9087..6a68de1d8903 100644 --- a/dotnet/src/Microsoft.AutoGen/Core.Grpc/Microsoft.AutoGen.Core.Grpc.csproj +++ b/dotnet/src/Microsoft.AutoGen/Core.Grpc/Microsoft.AutoGen.Core.Grpc.csproj @@ -14,7 +14,6 @@ - diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Abstractions/IAgentGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs similarity index 56% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Abstractions/IAgentGrain.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs index 947b6b0cbc0a..874c4d2da1ba 100644 --- a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Abstractions/IAgentGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs @@ -1,10 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // IAgentGrain.cs +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.Runtime.Grpc.Abstractions; internal interface IAgentGrain : IGrainWithStringKey { - ValueTask ReadStateAsync(); - ValueTask WriteStateAsync(Contracts.AgentState state, string eTag); + ValueTask ReadStateAsync(); + ValueTask WriteStateAsync(AgentState state, string eTag); } diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Abstractions/IGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs similarity index 75% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Abstractions/IGateway.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs index 33bb94f7c49b..a63e402e2393 100644 --- a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Abstractions/IGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // IGateway.cs using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.Runtime.Grpc.Abstractions; @@ -8,11 +9,11 @@ public interface IGateway : IGrainObserver { ValueTask InvokeRequestAsync(RpcRequest request); ValueTask BroadcastEventAsync(CloudEvent evt); - ValueTask StoreAsync(Contracts.AgentState value); - ValueTask ReadAsync(AgentId agentId); + ValueTask StoreAsync(AgentState value); + ValueTask ReadAsync(Protobuf.AgentId agentId); ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request); ValueTask SubscribeAsync(AddSubscriptionRequest request); ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request); ValueTask> GetSubscriptionsAsync(GetSubscriptionsRequest request); - Task SendMessageAsync(IConnection connection, CloudEvent cloudEvent); + Task SendMessageAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent); } diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Abstractions/IGatewayRegistry.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Abstractions/IGatewayRegistry.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Abstractions/IRegistryGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryGrain.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Abstractions/IRegistryGrain.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryGrain.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Microsoft.AutoGen.Runtime.Grpc.csproj b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Microsoft.AutoGen.RuntimeGateway.Grpc.csproj similarity index 77% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Microsoft.AutoGen.Runtime.Grpc.csproj rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Microsoft.AutoGen.RuntimeGateway.Grpc.csproj index b874a657d8f2..b29db7a7a36a 100644 --- a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Microsoft.AutoGen.Runtime.Grpc.csproj +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Microsoft.AutoGen.RuntimeGateway.Grpc.csproj @@ -29,4 +29,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/AgentWorkerHostingExtensions.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/AgentWorkerHostingExtensions.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Grpc/GrpcGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs similarity index 99% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Grpc/GrpcGateway.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs index 26c99c894248..a8277571aede 100644 --- a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Grpc/GrpcGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs @@ -4,6 +4,7 @@ using System.Collections.Concurrent; using Grpc.Core; using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; using Microsoft.AutoGen.Runtime.Grpc.Abstractions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -71,7 +72,7 @@ public async ValueTask StoreAsync(AgentState value, CancellationToken cancellati var agentState = _clusterClient.GetGrain($"{value.AgentId.Type}:{value.AgentId.Key}"); await agentState.WriteStateAsync(value, value.ETag); } - public async ValueTask ReadAsync(AgentId agentId, CancellationToken cancellationToken = default) + public async ValueTask ReadAsync(Contracts.AgentId agentId, CancellationToken cancellationToken = default) { var agentState = _clusterClient.GetGrain($"{agentId.Type}:{agentId.Key}"); return await agentState.ReadStateAsync(); diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Grpc/GrpcGatewayService.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs similarity index 94% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Grpc/GrpcGatewayService.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs index 9481922943c9..e22662c66b96 100644 --- a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Grpc/GrpcGatewayService.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // GrpcGatewayService.cs - using Grpc.Core; using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.Runtime.Grpc; @@ -26,7 +26,7 @@ public override async Task OpenChannel(IAsyncStreamReader requestStream throw; } } - public override async Task GetState(AgentId request, ServerCallContext context) + public override async Task GetState(Protobuf.AgentId request, ServerCallContext context) { var state = await Gateway.ReadAsync(request); return new GetStateResponse { AgentState = state }; diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Grpc/GrpcWorkerConnection.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs similarity index 97% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Grpc/GrpcWorkerConnection.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs index cba0f8c4772b..66fd0899d197 100644 --- a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Grpc/GrpcWorkerConnection.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // GrpcWorkerConnection.cs - using System.Threading.Channels; using Grpc.Core; +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.Runtime.Grpc; -internal sealed class GrpcWorkerConnection : IAsyncDisposable, IConnection +public sealed class GrpcWorkerConnection : IAsyncDisposable { private static long s_nextConnectionId; private Task _readTask = Task.CompletedTask; diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/AgentStateGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/AgentStateGrain.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/ISubscriptionsGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/ISubscriptionsGrain.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/ISubscriptionsGrain.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/ISubscriptionsGrain.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/OrleansRuntimeHostingExtenions.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/OrleansRuntimeHostingExtenions.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/OrleansRuntimeHostingExtenions.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/OrleansRuntimeHostingExtenions.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/RegistryGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/RegistryGrain.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/SubscriptionsGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/SubscriptionsGrain.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/SubscriptionsGrain.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/SubscriptionsGrain.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/AddSubscriptionRequestSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionRequestSurrogate.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/AddSubscriptionRequestSurrogate.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionRequestSurrogate.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/AddSubscriptionResponseSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionResponseSurrogate.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/AddSubscriptionResponseSurrogate.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionResponseSurrogate.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/AgentIdSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentIdSurrogate.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/AgentIdSurrogate.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentIdSurrogate.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/CloudEventSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/CloudEventSurrogate.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/CloudEventSurrogate.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/CloudEventSurrogate.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/GetSubscriptionsRequest.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/GetSubscriptionsRequest.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/GetSubscriptionsRequest.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/GetSubscriptionsRequest.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeRequestSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeRequestSurrogate.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeRequestSurrogate.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeRequestSurrogate.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeResponseSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeResponseSurrogate.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeResponseSurrogate.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeResponseSurrogate.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionRequest.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionRequest.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionRequest.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionRequest.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionResponse.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionResponse.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionResponse.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionResponse.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/RpcRequestSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcRequestSurrogate.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/RpcRequestSurrogate.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcRequestSurrogate.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/SubscriptionSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/SubscriptionSurrogate.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/SubscriptionSurrogate.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/SubscriptionSurrogate.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/TypePrefixSubscriptionSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypePrefixSubscriptionSurrogate.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/TypePrefixSubscriptionSurrogate.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypePrefixSubscriptionSurrogate.cs diff --git a/dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/TypeSubscriptionSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypeSubscriptionSurrogate.cs similarity index 100% rename from dotnet/src/Microsoft.AutoGen/Runtime.Grpc/Services/Orleans/Surrogates/TypeSubscriptionSurrogate.cs rename to dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypeSubscriptionSurrogate.cs From 1167274ba6e0318697ee31777428e799b5dc6a77 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Wed, 5 Feb 2025 09:07:52 -0800 Subject: [PATCH 18/53] rebase --- dotnet/AutoGen.sln | 19 ++--- .../AgentGrpcTests.cs | 73 +++++++------------ .../GrpcAgentRuntimeFixture.cs | 6 +- .../GrpcAgentServiceFixture.cs | 6 +- .../Microsoft.AutoGen.Core.Grpc.Tests.csproj | 11 ++- .../TestProtobufAgent.cs | 50 +++++++++++++ .../messages.proto | 13 ++++ 7 files changed, 111 insertions(+), 67 deletions(-) create mode 100644 dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/TestProtobufAgent.cs create mode 100644 dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/messages.proto diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln index c682e8a36661..74b7ac965592 100644 --- a/dotnet/AutoGen.sln +++ b/dotnet/AutoGen.sln @@ -122,8 +122,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStartedGrpc", "sampl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Core.Grpc.Tests", "test\Microsoft.AutoGen.Core.Grpc.Tests\Microsoft.AutoGen.Core.Grpc.Tests.csproj", "{23A028D3-5EB1-4FA0-9CD1-A1340B830579}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.RuntimeGateway.Grpc", "src\Microsoft.AutoGen\RuntimeGateway.Grpc\Microsoft.AutoGen.RuntimeGateway.Grpc.csproj", "{09B6171D-3B94-451E-B4AA-A43506699A50}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -302,14 +300,16 @@ Global {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D}.Release|Any CPU.Build.0 = Release|Any CPU - {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Release|Any CPU.Build.0 = Release|Any CPU + {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.CoreOnly|Any CPU.ActiveCfg = Debug|Any CPU + {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.CoreOnly|Any CPU.Build.0 = Debug|Any CPU {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Debug|Any CPU.Build.0 = Debug|Any CPU {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Release|Any CPU.ActiveCfg = Release|Any CPU {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Release|Any CPU.Build.0 = Release|Any CPU + {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Release|Any CPU.Build.0 = Release|Any CPU {C3740DF1-18B1-4607-81E4-302F0308C848}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C3740DF1-18B1-4607-81E4-302F0308C848}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3740DF1-18B1-4607-81E4-302F0308C848}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -318,10 +318,6 @@ Global {23A028D3-5EB1-4FA0-9CD1-A1340B830579}.Debug|Any CPU.Build.0 = Debug|Any CPU {23A028D3-5EB1-4FA0-9CD1-A1340B830579}.Release|Any CPU.ActiveCfg = Release|Any CPU {23A028D3-5EB1-4FA0-9CD1-A1340B830579}.Release|Any CPU.Build.0 = Release|Any CPU - {09B6171D-3B94-451E-B4AA-A43506699A50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {09B6171D-3B94-451E-B4AA-A43506699A50}.Debug|Any CPU.Build.0 = Debug|Any CPU - {09B6171D-3B94-451E-B4AA-A43506699A50}.Release|Any CPU.ActiveCfg = Release|Any CPU - {09B6171D-3B94-451E-B4AA-A43506699A50}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -372,12 +368,11 @@ Global {EAFFE339-26CB-4019-991D-BCCE8E7D33A1} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} {58AD8E1D-83BD-4950-A324-1A20677D78D9} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} + {3D83C6DB-ACEA-48F3-959F-145CCD2EE135} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} {AAD593FE-A49B-425E-A9FE-A0022CD25E3D} = {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} {C3740DF1-18B1-4607-81E4-302F0308C848} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} {23A028D3-5EB1-4FA0-9CD1-A1340B830579} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} - {09B6171D-3B94-451E-B4AA-A43506699A50} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B} diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs index 61c604b33eb4..a2a970bebb41 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/AgentGrpcTests.cs @@ -2,14 +2,14 @@ // AgentGrpcTests.cs using FluentAssertions; using Microsoft.AutoGen.Contracts; +// using Microsoft.AutoGen.Core.Tests; +using Microsoft.AutoGen.Core.Grpc.Tests.Protobuf; using Microsoft.Extensions.Logging; -using Microsoft.AutoGen.Core.Tests; - using Xunit; namespace Microsoft.AutoGen.Core.Grpc.Tests; -[Trait("Category", "UnitV2")] +[Trait("Category", "GRPC")] public class AgentGrpcTests { [Fact] @@ -19,11 +19,11 @@ public async Task AgentShouldNotReceiveMessagesWhenNotSubscribedTest() var runtime = (GrpcAgentRuntime)await fixture.Start(); Logger logger = new(new LoggerFactory()); - TestAgent agent = null!; + TestProtobufAgent agent = null!; await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => { - agent = new TestAgent(id, runtime, logger); + agent = new TestProtobufAgent(id, runtime, logger); return await ValueTask.FromResult(agent); }); @@ -35,7 +35,7 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => var topicType = "TestTopic"; - await runtime.PublishMessageAsync(new Contracts.TextMessage { Source = topicType, TextMessage_ = "test" }, new TopicId(topicType)).ConfigureAwait(true); + await runtime.PublishMessageAsync(new Protobuf.TextMessage { Source = topicType, Content = "test" }, new TopicId(topicType)).ConfigureAwait(true); agent.ReceivedMessages.Any().Should().BeFalse("Agent should not receive messages when not subscribed."); fixture.Dispose(); @@ -48,11 +48,11 @@ public async Task AgentShouldReceiveMessagesWhenSubscribedTest() var runtime = (GrpcAgentRuntime)await fixture.Start(); Logger logger = new(new LoggerFactory()); - SubscribedAgent agent = null!; + SubscribedProtobufAgent agent = null!; await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => { - agent = new SubscribedAgent(id, runtime, logger); + agent = new SubscribedProtobufAgent(id, runtime, logger); return await ValueTask.FromResult(agent); }); @@ -62,11 +62,14 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => // Validate agent ID agentId.Should().Be(agent.Id, "Agent ID should match the registered agent"); - await runtime.RegisterImplicitAgentSubscriptionsAsync("MyAgent"); + await runtime.RegisterImplicitAgentSubscriptionsAsync("MyAgent"); var topicType = "TestTopic"; - await runtime.PublishMessageAsync(new Contracts.TextMessage { Source = topicType, TextMessage_ = "test" }, new TopicId(topicType)).ConfigureAwait(true); + await runtime.PublishMessageAsync(new TextMessage { Source = topicType, Content = "test" }, new TopicId(topicType)).ConfigureAwait(true); + + // Wait for the message to be processed + await Task.Delay(100); agent.ReceivedMessages.Any().Should().BeTrue("Agent should receive messages when subscribed."); fixture.Dispose(); @@ -80,30 +83,27 @@ public async Task SendMessageAsyncShouldReturnResponseTest() var runtime = (GrpcAgentRuntime)await fixture.Start(); Logger logger = new(new LoggerFactory()); - await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => await ValueTask.FromResult(new TestAgent(id, runtime, logger))); - await runtime.RegisterImplicitAgentSubscriptionsAsync("MyAgent"); - - var agentId = new AgentId("MyAgent", "TestAgent"); - + await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => await ValueTask.FromResult(new TestProtobufAgent(id, runtime, logger))); + var agentId = new AgentId("MyAgent", "default"); var response = await runtime.SendMessageAsync(new RpcTextMessage { Source = "TestTopic", Content = "Request" }, agentId); // Assert Assert.NotNull(response); - Assert.IsType(response); - if (response is string responseString) + Assert.IsType(response); + if (response is RpcTextMessage responseString) { - Assert.Equal("Request", responseString); + Assert.Equal("Request", responseString.Content); } fixture.Dispose(); } public class ReceiverAgent(AgentId id, IAgentRuntime runtime) : BaseAgent(id, runtime, "Receiver Agent", null), - IHandle + IHandle { - public ValueTask HandleAsync(string item, MessageContext messageContext) + public ValueTask HandleAsync(TextMessage item, MessageContext messageContext) { - ReceivedItems.Add(item); + ReceivedItems.Add(item.Content); return ValueTask.CompletedTask; } @@ -128,7 +128,7 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => Assert.True(agent.ReceivedItems.Count == 0); var topicTypeName = "TestTopic"; - await runtime.PublishMessageAsync("info", new TopicId(topicTypeName)); + await runtime.PublishMessageAsync(new TextMessage { Source = "topic", Content = "test" }, new TopicId(topicTypeName)); await Task.Delay(100); Assert.True(agent.ReceivedItems.Count == 0); @@ -136,40 +136,17 @@ await runtime.RegisterAgentFactoryAsync("MyAgent", async (id, runtime) => var subscription = new TypeSubscription(topicTypeName, "MyAgent"); await runtime.AddSubscriptionAsync(subscription); - await runtime.PublishMessageAsync("info", new TopicId(topicTypeName)); + await runtime.PublishMessageAsync(new TextMessage { Source = "topic", Content = "test" }, new TopicId(topicTypeName)); await Task.Delay(100); Assert.True(agent.ReceivedItems.Count == 1); - Assert.Equal("info", agent.ReceivedItems[0]); + Assert.Equal("test", agent.ReceivedItems[0]); await runtime.RemoveSubscriptionAsync(subscription.Id); - await runtime.PublishMessageAsync("info", new TopicId(topicTypeName)); + await runtime.PublishMessageAsync(new TextMessage { Source = "topic", Content = "test" }, new TopicId(topicTypeName)); await Task.Delay(100); Assert.True(agent.ReceivedItems.Count == 1); fixture.Dispose(); } - - [Fact] - public async Task AgentShouldSaveStateCorrectlyTest() - { - - var fixture = new GrpcAgentRuntimeFixture(); - var runtime = (GrpcAgentRuntime)await fixture.Start(); - - Logger logger = new(new LoggerFactory()); - TestAgent agent = new TestAgent(new AgentId("TestType", "TestKey"), runtime, logger); - - var state = await agent.SaveStateAsync(); - - // Ensure state is a dictionary - state.Should().NotBeNull(); - state.Should().BeOfType>(); - state.Should().BeEmpty("Default SaveStateAsync should return an empty dictionary."); - - // Add a sample value and verify it updates correctly - state["testKey"] = "testValue"; - state.Should().ContainKey("testKey").WhoseValue.Should().Be("testValue"); - fixture.Dispose(); - } } diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentRuntimeFixture.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentRuntimeFixture.cs index f9adaf0659b7..bade7f785757 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentRuntimeFixture.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentRuntimeFixture.cs @@ -2,7 +2,7 @@ // GrpcAgentRuntimeFixture.cs using Microsoft.AspNetCore.Builder; using Microsoft.AutoGen.Contracts; -using Microsoft.AutoGen.Core.Tests; +// using Microsoft.AutoGen.Core.Tests; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -44,7 +44,7 @@ private static async Task ClientBuilder() { var appBuilder = new AgentsAppBuilder(); appBuilder.AddGrpcAgentWorker(); - appBuilder.AddAgent("TestAgent"); + appBuilder.AddAgent("TestAgent"); return await appBuilder.BuildAsync(); } private static async Task ServerBuilder() @@ -80,4 +80,4 @@ public void Dispose() Stop(); } -} \ No newline at end of file +} diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentServiceFixture.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentServiceFixture.cs index 75eba7af47a7..98c47764269d 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentServiceFixture.cs +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/GrpcAgentServiceFixture.cs @@ -5,7 +5,7 @@ namespace Microsoft.AutoGen.Core.Grpc.Tests; /// -/// This fixture is largely just a loopback as we are testing the client side logic of the GrpcAgentRuntime in isolation from the rest of the system. +/// This fixture is largely just a loopback as we are testing the client side logic of the GrpcAgentRuntime in isolation from the rest of the system. /// public sealed class GrpcAgentServiceFixture() : AgentRpc.AgentRpcBase { @@ -25,10 +25,10 @@ public override async Task OpenChannel(IAsyncStreamReader requestStream throw; } } - public override async Task GetState(Protobuf.AgentId request, ServerCallContext context) => new GetStateResponse { AgentState = new AgentState { AgentId = request } }; + public override async Task GetState(AgentId request, ServerCallContext context) => new GetStateResponse { AgentState = new AgentState { AgentId = request } }; public override async Task SaveState(AgentState request, ServerCallContext context) => new SaveStateResponse { }; public override async Task AddSubscription(AddSubscriptionRequest request, ServerCallContext context) => new AddSubscriptionResponse { }; public override async Task RemoveSubscription(RemoveSubscriptionRequest request, ServerCallContext context) => new RemoveSubscriptionResponse { }; public override async Task GetSubscriptions(GetSubscriptionsRequest request, ServerCallContext context) => new GetSubscriptionsResponse { }; public override async Task RegisterAgent(RegisterAgentTypeRequest request, ServerCallContext context) => new RegisterAgentTypeResponse { }; -} \ No newline at end of file +} diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj index 8c47b5326094..e3573c93451a 100644 --- a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/Microsoft.AutoGen.Core.Grpc.Tests.csproj @@ -10,8 +10,17 @@ - + + + + + + + + + + diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/TestProtobufAgent.cs b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/TestProtobufAgent.cs new file mode 100644 index 000000000000..6f5ad4aa9e5b --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/TestProtobufAgent.cs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// TestProtobufAgent.cs + +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Core.Grpc.Tests.Protobuf; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AutoGen.Core.Grpc.Tests; + +/// +/// The test agent is a simple agent that is used for testing purposes. +/// +public class TestProtobufAgent(AgentId id, + IAgentRuntime runtime, + Logger? logger = null) : BaseAgent(id, runtime, "Test Agent", logger), + IHandle, + IHandle + +{ + public ValueTask HandleAsync(TextMessage item, MessageContext messageContext) + { + ReceivedMessages[item.Source] = item.Content; + return ValueTask.CompletedTask; + } + + public ValueTask HandleAsync(RpcTextMessage item, MessageContext messageContext) + { + ReceivedMessages[item.Source] = item.Content; + return ValueTask.FromResult(new RpcTextMessage { Source = item.Source, Content = item.Content }); + } + + public List ReceivedItems { get; private set; } = []; + + /// + /// Key: source + /// Value: message + /// + private readonly Dictionary _receivedMessages = new(); + public Dictionary ReceivedMessages => _receivedMessages; +} + +[TypeSubscription("TestTopic")] +public class SubscribedProtobufAgent : TestProtobufAgent +{ + public SubscribedProtobufAgent(AgentId id, + IAgentRuntime runtime, + Logger? logger = null) : base(id, runtime, logger) + { + } +} diff --git a/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/messages.proto b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/messages.proto new file mode 100644 index 000000000000..7f2c275e691f --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Core.Grpc.Tests/messages.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +option csharp_namespace = "Microsoft.AutoGen.Core.Grpc.Tests.Protobuf"; + +message TextMessage { + string content = 1; + string source = 2; +} + +message RpcTextMessage { + string content = 1; + string source = 2; +} \ No newline at end of file From 4aa987e83b1fbab88bd951378340958eb2b78fc2 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Wed, 5 Feb 2025 09:13:42 -0800 Subject: [PATCH 19/53] re-add project --- dotnet/AutoGen.sln | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln index 74b7ac965592..aef3ee2ac361 100644 --- a/dotnet/AutoGen.sln +++ b/dotnet/AutoGen.sln @@ -122,6 +122,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingStartedGrpc", "sampl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Core.Grpc.Tests", "test\Microsoft.AutoGen.Core.Grpc.Tests\Microsoft.AutoGen.Core.Grpc.Tests.csproj", "{23A028D3-5EB1-4FA0-9CD1-A1340B830579}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.RuntimeGateway.Grpc", "src\Microsoft.AutoGen\RuntimeGateway.Grpc\Microsoft.AutoGen.RuntimeGateway.Grpc.csproj", "{BE420A71-7615-4DFD-BE94-9409397949F1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -318,6 +320,10 @@ Global {23A028D3-5EB1-4FA0-9CD1-A1340B830579}.Debug|Any CPU.Build.0 = Debug|Any CPU {23A028D3-5EB1-4FA0-9CD1-A1340B830579}.Release|Any CPU.ActiveCfg = Release|Any CPU {23A028D3-5EB1-4FA0-9CD1-A1340B830579}.Release|Any CPU.Build.0 = Release|Any CPU + {BE420A71-7615-4DFD-BE94-9409397949F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE420A71-7615-4DFD-BE94-9409397949F1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE420A71-7615-4DFD-BE94-9409397949F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE420A71-7615-4DFD-BE94-9409397949F1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -373,6 +379,7 @@ Global {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} {C3740DF1-18B1-4607-81E4-302F0308C848} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} {23A028D3-5EB1-4FA0-9CD1-A1340B830579} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} + {BE420A71-7615-4DFD-BE94-9409397949F1} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B} From 73ece882584786bf13b19a9f458105467ab65573 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Wed, 5 Feb 2025 09:37:31 -0800 Subject: [PATCH 20/53] fixing up the rename --- dotnet/AutoGen.sln | 7 +++ .../src/Microsoft.AutoGen/AgentHost/Host.cs | 2 +- .../Microsoft.AutoGen.AgentHost.csproj | 2 +- .../Microsoft.AutoGen/AgentHost/Program.cs | 2 +- .../Abstractions/IAgentGrain.cs | 2 +- .../Abstractions/IConnection.cs | 10 ++++ .../Abstractions/IGateway.cs | 2 +- .../Abstractions/IGatewayRegistry.cs | 7 +-- .../Abstractions/IRegistry.cs | 57 +++++++++++++++++++ .../Abstractions/IRegistryGrain.cs | 4 +- .../Services/AgentWorkerHostingExtensions.cs | 2 +- .../Services/Grpc/GrpcGateway.cs | 12 ++-- .../Services/Grpc/GrpcGatewayService.cs | 3 +- .../Services/Grpc/GrpcWorkerConnection.cs | 2 +- .../Services/Orleans/AgentStateGrain.cs | 4 +- .../Services/Orleans/ISubscriptionsGrain.cs | 2 +- .../Orleans/OrleansRuntimeHostingExtenions.cs | 2 +- .../Services/Orleans/RegistryGrain.cs | 4 +- .../Services/Orleans/SubscriptionsGrain.cs | 2 +- .../AddSubscriptionRequestSurrogate.cs | 2 +- .../AddSubscriptionResponseSurrogate.cs | 2 +- .../Orleans/Surrogates/AgentIdSurrogate.cs | 2 +- .../Orleans/Surrogates/AgentStateSurrogate.cs | 2 +- .../Orleans/Surrogates/CloudEventSurrogate.cs | 2 +- .../Surrogates/GetSubscriptionsRequest.cs | 2 +- .../RegisterAgentTypeRequestSurrogate.cs | 2 +- .../RegisterAgentTypeResponseSurrogate.cs | 2 +- .../Surrogates/RemoveSubscriptionRequest.cs | 2 +- .../Surrogates/RemoveSubscriptionResponse.cs | 2 +- .../Orleans/Surrogates/RpcRequestSurrogate.cs | 2 +- .../Surrogates/RpcResponseSurrogate.cs | 2 +- .../Surrogates/SubscriptionSurrogate.cs | 2 +- .../TypePrefixSubscriptionSurrogate.cs | 2 +- .../Surrogates/TypeSubscriptionSurrogate.cs | 2 +- .../GrpcGatewayServiceTests.cs | 6 +- .../Helpers/Grpc/TestAsyncStreamReader.cs | 2 +- .../Helpers/Grpc/TestGrpcClient.cs | 2 +- .../Helpers/Grpc/TestServerCallContext.cs | 2 +- .../Helpers/Grpc/TestServerStreamWriter.cs | 2 +- .../Helpers/Orleans/ClusterCollection.cs | 2 +- .../Helpers/Orleans/ClusterFixture.cs | 2 +- .../Orleans/SiloBuilderConfigurator.cs | 2 +- ....AutoGen.RuntimeGateway.Grpc.Tests.csproj} | 2 +- .../TestAgent.cs | 2 +- 44 files changed, 128 insertions(+), 54 deletions(-) create mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IConnection.cs create mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs rename dotnet/test/{Microsoft.AutoGen.Runtime.Grpc.Tests => Microsoft.AutoGen.RuntimeGateway.Grpc.Tests}/GrpcGatewayServiceTests.cs (97%) rename dotnet/test/{Microsoft.AutoGen.Runtime.Grpc.Tests => Microsoft.AutoGen.RuntimeGateway.Grpc.Tests}/Helpers/Grpc/TestAsyncStreamReader.cs (96%) rename dotnet/test/{Microsoft.AutoGen.Runtime.Grpc.Tests => Microsoft.AutoGen.RuntimeGateway.Grpc.Tests}/Helpers/Grpc/TestGrpcClient.cs (94%) rename dotnet/test/{Microsoft.AutoGen.Runtime.Grpc.Tests => Microsoft.AutoGen.RuntimeGateway.Grpc.Tests}/Helpers/Grpc/TestServerCallContext.cs (97%) rename dotnet/test/{Microsoft.AutoGen.Runtime.Grpc.Tests => Microsoft.AutoGen.RuntimeGateway.Grpc.Tests}/Helpers/Grpc/TestServerStreamWriter.cs (97%) rename dotnet/test/{Microsoft.AutoGen.Runtime.Grpc.Tests => Microsoft.AutoGen.RuntimeGateway.Grpc.Tests}/Helpers/Orleans/ClusterCollection.cs (78%) rename dotnet/test/{Microsoft.AutoGen.Runtime.Grpc.Tests => Microsoft.AutoGen.RuntimeGateway.Grpc.Tests}/Helpers/Orleans/ClusterFixture.cs (87%) rename dotnet/test/{Microsoft.AutoGen.Runtime.Grpc.Tests => Microsoft.AutoGen.RuntimeGateway.Grpc.Tests}/Helpers/Orleans/SiloBuilderConfigurator.cs (89%) rename dotnet/test/{Microsoft.AutoGen.Runtime.Grpc.Tests/Microsoft.AutoGen.Runtime.Grpc.Tests.csproj => Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.csproj} (93%) rename dotnet/test/{Microsoft.AutoGen.Runtime.Grpc.Tests => Microsoft.AutoGen.RuntimeGateway.Grpc.Tests}/TestAgent.cs (96%) diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln index aef3ee2ac361..90c9d219ccce 100644 --- a/dotnet/AutoGen.sln +++ b/dotnet/AutoGen.sln @@ -124,6 +124,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Core.Grpc EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.RuntimeGateway.Grpc", "src\Microsoft.AutoGen\RuntimeGateway.Grpc\Microsoft.AutoGen.RuntimeGateway.Grpc.csproj", "{BE420A71-7615-4DFD-BE94-9409397949F1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.RuntimeGateway.Grpc.Tests", "test\Microsoft.AutoGen.RuntimeGateway.Grpc.Tests\Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.csproj", "{CDD859F3-1B60-4ECE-8472-54DF8EFCA682}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -324,6 +326,10 @@ Global {BE420A71-7615-4DFD-BE94-9409397949F1}.Debug|Any CPU.Build.0 = Debug|Any CPU {BE420A71-7615-4DFD-BE94-9409397949F1}.Release|Any CPU.ActiveCfg = Release|Any CPU {BE420A71-7615-4DFD-BE94-9409397949F1}.Release|Any CPU.Build.0 = Release|Any CPU + {CDD859F3-1B60-4ECE-8472-54DF8EFCA682}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDD859F3-1B60-4ECE-8472-54DF8EFCA682}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDD859F3-1B60-4ECE-8472-54DF8EFCA682}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDD859F3-1B60-4ECE-8472-54DF8EFCA682}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -380,6 +386,7 @@ Global {C3740DF1-18B1-4607-81E4-302F0308C848} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} {23A028D3-5EB1-4FA0-9CD1-A1340B830579} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} {BE420A71-7615-4DFD-BE94-9409397949F1} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} + {CDD859F3-1B60-4ECE-8472-54DF8EFCA682} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B} diff --git a/dotnet/src/Microsoft.AutoGen/AgentHost/Host.cs b/dotnet/src/Microsoft.AutoGen/AgentHost/Host.cs index 1ecf42c79589..0176b3faa3e3 100644 --- a/dotnet/src/Microsoft.AutoGen/AgentHost/Host.cs +++ b/dotnet/src/Microsoft.AutoGen/AgentHost/Host.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Hosting; -namespace Microsoft.AutoGen.Runtime.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc; public static class Host { diff --git a/dotnet/src/Microsoft.AutoGen/AgentHost/Microsoft.AutoGen.AgentHost.csproj b/dotnet/src/Microsoft.AutoGen/AgentHost/Microsoft.AutoGen.AgentHost.csproj index 33b051ad917b..d16b68625184 100644 --- a/dotnet/src/Microsoft.AutoGen/AgentHost/Microsoft.AutoGen.AgentHost.csproj +++ b/dotnet/src/Microsoft.AutoGen/AgentHost/Microsoft.AutoGen.AgentHost.csproj @@ -15,7 +15,7 @@ - + \ No newline at end of file diff --git a/dotnet/src/Microsoft.AutoGen/AgentHost/Program.cs b/dotnet/src/Microsoft.AutoGen/AgentHost/Program.cs index 024ca0d4309f..3e32c50a3a0f 100644 --- a/dotnet/src/Microsoft.AutoGen/AgentHost/Program.cs +++ b/dotnet/src/Microsoft.AutoGen/AgentHost/Program.cs @@ -2,5 +2,5 @@ // Program.cs using Microsoft.Extensions.Hosting; -var app = await Microsoft.AutoGen.Runtime.Grpc.Host.StartAsync(local: false, useGrpc: true).ConfigureAwait(false); +var app = await Microsoft.AutoGen.RuntimeGateway.Grpc.Host.StartAsync(local: false, useGrpc: true).ConfigureAwait(false); await app.WaitForShutdownAsync(); diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs index 874c4d2da1ba..47abf2b7cfcc 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs @@ -2,7 +2,7 @@ // IAgentGrain.cs using Microsoft.AutoGen.Protobuf; -namespace Microsoft.AutoGen.Runtime.Grpc.Abstractions; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; internal interface IAgentGrain : IGrainWithStringKey { diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IConnection.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IConnection.cs new file mode 100644 index 000000000000..7292a16165de --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IConnection.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IConnection.cs +namespace Microsoft.AutoGen.Contracts; +/// +/// Represents a connection to a remote service. +/// Is just used to potentially alow us to add other connection types in the future or to sub a Channel for a Connection. +/// +public interface IConnection +{ +} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs index a63e402e2393..71a029eb98ad 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs @@ -3,7 +3,7 @@ using Microsoft.AutoGen.Contracts; using Microsoft.AutoGen.Protobuf; -namespace Microsoft.AutoGen.Runtime.Grpc.Abstractions; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; public interface IGateway : IGrainObserver { diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs index cb3778418040..f2f86a2e7dbd 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs @@ -1,9 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // IGatewayRegistry.cs +using Microsoft.AutoGen.Protobuf; -using Microsoft.AutoGen.Contracts; - -namespace Microsoft.AutoGen.Runtime.Grpc.Abstractions; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; /// /// Interface for managing agent registration, placement, and subscriptions. @@ -15,7 +14,7 @@ public interface IGatewayRegistry : IRegistry /// /// The ID of the agent. /// A tuple containing the worker and a boolean indicating if it's a new placement. - ValueTask<(IGateway? Worker, bool NewPlacement)> GetOrPlaceAgent(AgentId agentId); + ValueTask<(IGateway? Worker, bool NewPlacement)> GetOrPlaceAgent(Contracts.AgentId agentId); /// /// Removes a worker from the registry. diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs new file mode 100644 index 000000000000..8f36f5de7f8e --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IRegistry.cs +using Microsoft.AutoGen.Protobuf; +using Microsoft.AutoGen.Contracts; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc; + +public interface IRegistry +{ + //AgentsRegistryState State { get; set; } + /// + /// Registers a new agent type with the specified worker. + /// + /// The request containing agent type details. + /// The worker to register the agent type with. + /// A task representing the asynchronous operation. + /// removing CancellationToken from here as it is not compatible with Orleans Serialization + ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, IAgentRuntime worker); + + /// + /// Unregisters an agent type from the specified worker. + /// + /// The type of the agent to unregister. + /// The worker to unregister the agent type from. + /// A task representing the asynchronous operation. + /// removing CancellationToken from here as it is not compatible with Orleans Serialization + ValueTask UnregisterAgentTypeAsync(string type, IAgentRuntime worker); + + /// + /// Gets a list of agents subscribed to and handling the specified topic and event type. + /// + /// The topic to check subscriptions for. + /// The event type to check subscriptions for. + /// A task representing the asynchronous operation, with the list of agent IDs as the result. + ValueTask> GetSubscribedAndHandlingAgentsAsync(string topic, string eventType); + + /// + /// Subscribes an agent to a topic. + /// + /// The subscription request. + /// A task representing the asynchronous operation. + /// removing CancellationToken from here as it is not compatible with Orleans Serialization + ValueTask SubscribeAsync(AddSubscriptionRequest request); + + /// + /// Unsubscribes an agent from a topic. + /// + /// The unsubscription request. + /// A task representing the asynchronous operation. + /// removing CancellationToken from here as it is not compatible with Orleans Serialization + ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request); // TODO: This should have its own request type. + + /// + /// Gets the subscriptions for a specified agent type. + /// + /// A task representing the asynchronous operation, with the subscriptions as the result. + ValueTask> GetSubscriptionsAsync(GetSubscriptionsRequest request); +} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryGrain.cs index 81b59858619c..a44da1ce5b22 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryGrain.cs @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // IRegistryGrain.cs -namespace Microsoft.AutoGen.Runtime.Grpc.Abstractions; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; /// /// Orleans specific interface, needed to mark the key /// -[Alias("Microsoft.AutoGen.Runtime.Grpc.Abstractions.IRegistryGrain")] +[Alias("Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions.IRegistryGrain")] public interface IRegistryGrain : IGatewayRegistry, IGrainWithIntegerKey { } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs index 3b130ca4bed5..2421b9ba1edd 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; -namespace Microsoft.AutoGen.Runtime.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc; public static class AgentWorkerHostingExtensions { public static WebApplicationBuilder AddAgentService(this WebApplicationBuilder builder) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs index a8277571aede..62ecb001bfba 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs @@ -5,11 +5,11 @@ using Grpc.Core; using Microsoft.AutoGen.Contracts; using Microsoft.AutoGen.Protobuf; -using Microsoft.AutoGen.Runtime.Grpc.Abstractions; +using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace Microsoft.AutoGen.Runtime.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc; public sealed class GrpcGateway : BackgroundService, IGateway { @@ -21,7 +21,7 @@ public sealed class GrpcGateway : BackgroundService, IGateway private readonly IGateway _reference; // The agents supported by each worker process. private readonly ConcurrentDictionary> _supportedAgentTypes = []; - public readonly ConcurrentDictionary _workers = new(); + public readonly ConcurrentDictionary _workers = new(); internal readonly ConcurrentDictionary _workersByConnection = new(); private readonly ConcurrentDictionary _subscriptionsByAgentType = new(); private readonly ConcurrentDictionary> _subscriptionsByTopic = new(); @@ -72,7 +72,7 @@ public async ValueTask StoreAsync(AgentState value, CancellationToken cancellati var agentState = _clusterClient.GetGrain($"{value.AgentId.Type}:{value.AgentId.Key}"); await agentState.WriteStateAsync(value, value.ETag); } - public async ValueTask ReadAsync(Contracts.AgentId agentId, CancellationToken cancellationToken = default) + public async ValueTask ReadAsync(Protobuf.AgentId agentId, CancellationToken cancellationToken = default) { var agentState = _clusterClient.GetGrain($"{agentId.Type}:{agentId.Key}"); return await agentState.ReadStateAsync(); @@ -149,8 +149,10 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) internal async Task ConnectToWorkerProcess(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) { _logger.LogInformation("Received new connection from {Peer}.", context.Peer); + var clientId = (context.RequestHeaders.Get("client-id")?.Value) ?? + throw new RpcException(new Status(StatusCode.InvalidArgument, "Client ID is required.")); var workerProcess = new GrpcWorkerConnection(this, requestStream, responseStream, context); - _workers.GetOrAdd(workerProcess, workerProcess); + _workers.GetOrAdd(clientId, workerProcess); _workersByConnection.GetOrAdd(context.Peer, workerProcess); await workerProcess.Connect().ConfigureAwait(false); } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs index e22662c66b96..b0f002c00258 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs @@ -1,10 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // GrpcGatewayService.cs using Grpc.Core; -using Microsoft.AutoGen.Contracts; using Microsoft.AutoGen.Protobuf; -namespace Microsoft.AutoGen.Runtime.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc; // gRPC service which handles communication between the agent worker and the cluster. public sealed class GrpcGatewayService(GrpcGateway gateway) : AgentRpc.AgentRpcBase diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs index 66fd0899d197..7f6fcde4f5c4 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs @@ -4,7 +4,7 @@ using Grpc.Core; using Microsoft.AutoGen.Protobuf; -namespace Microsoft.AutoGen.Runtime.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc; public sealed class GrpcWorkerConnection : IAsyncDisposable { diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs index 97869cd91fd1..4392eec12a01 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AgentStateGrain.cs -using Microsoft.AutoGen.Runtime.Grpc.Abstractions; +using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; -namespace Microsoft.AutoGen.Runtime.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc; internal sealed class AgentStateGrain([PersistentState("state", "AgentStateStore")] IPersistentState state) : Grain, IAgentState, IAgentGrain { diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/ISubscriptionsGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/ISubscriptionsGrain.cs index 60c17b7c6597..c777c61fcbd7 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/ISubscriptionsGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/ISubscriptionsGrain.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ISubscriptionsGrain.cs -namespace Microsoft.AutoGen.Runtime.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc; public interface ISubscriptionsGrain : IGrainWithIntegerKey { ValueTask SubscribeAsync(string agentType, string topic); diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/OrleansRuntimeHostingExtenions.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/OrleansRuntimeHostingExtenions.cs index e83db26ad0b7..9abf9ac048c7 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/OrleansRuntimeHostingExtenions.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/OrleansRuntimeHostingExtenions.cs @@ -9,7 +9,7 @@ using Orleans.Configuration; using Orleans.Serialization; -namespace Microsoft.AutoGen.Runtime.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc; public static class OrleansRuntimeHostingExtenions { diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs index 9de7065fdb62..1966f5c9c662 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // RegistryGrain.cs using Microsoft.AutoGen.Contracts; -using Microsoft.AutoGen.Runtime.Grpc.Abstractions; +using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; -namespace Microsoft.AutoGen.Runtime.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc; internal sealed class RegistryGrain([PersistentState("state", "AgentRegistryStore")] IPersistentState state) : Grain, IRegistryGrain { private readonly Dictionary _workerStates = new(); diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/SubscriptionsGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/SubscriptionsGrain.cs index 632cc7cefde8..fa815d41a2a8 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/SubscriptionsGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/SubscriptionsGrain.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SubscriptionsGrain.cs -namespace Microsoft.AutoGen.Runtime.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc; internal sealed class SubscriptionsGrain([PersistentState("state", "PubSubStore")] IPersistentState state) : Grain, ISubscriptionsGrain { diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionRequestSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionRequestSurrogate.cs index 37e3af1b9d17..a5c6b57e05f9 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionRequestSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionRequestSurrogate.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AddSubscriptionRequestSurrogate.cs -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct AddSubscriptionRequestSurrogate diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionResponseSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionResponseSurrogate.cs index 4c15784e0fcc..c9a9606d1a9c 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionResponseSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionResponseSurrogate.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AddSubscriptionResponseSurrogate.cs -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct AddSubscriptionResponseSurrogate diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentIdSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentIdSurrogate.cs index ddef9e997575..bde3fe7bdb18 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentIdSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentIdSurrogate.cs @@ -5,7 +5,7 @@ // AgentIdSurrogate.cs using Microsoft.AutoGen.Contracts; -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct AgentIdSurrogate diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs index a5291f942155..a2ab0ff96793 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs @@ -4,7 +4,7 @@ using Google.Protobuf; using Microsoft.AutoGen.Contracts; -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct AgentStateSurrogate diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/CloudEventSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/CloudEventSurrogate.cs index 22359a08981c..c1a13400db26 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/CloudEventSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/CloudEventSurrogate.cs @@ -4,7 +4,7 @@ using Google.Protobuf; using Google.Protobuf.WellKnownTypes; -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; // TODO: Add the rest of the properties [GenerateSerializer] diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/GetSubscriptionsRequest.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/GetSubscriptionsRequest.cs index ab4722ff8c74..ea5ccb44865b 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/GetSubscriptionsRequest.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/GetSubscriptionsRequest.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // GetSubscriptionsRequest.cs -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct GetSubscriptionsRequestSurrogate diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeRequestSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeRequestSurrogate.cs index fa50e597fabe..4ec528e939d6 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeRequestSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeRequestSurrogate.cs @@ -3,7 +3,7 @@ using Google.Protobuf.Collections; -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct RegisterAgentTypeRequestSurrogate diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeResponseSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeResponseSurrogate.cs index 2c7d6788a76c..eda268a566f9 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeResponseSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeResponseSurrogate.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // RegisterAgentTypeResponseSurrogate.cs -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct RegisterAgentTypeResponseSurrogate diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionRequest.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionRequest.cs index 27299728baa8..54de9fe313f2 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionRequest.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionRequest.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // RemoveSubscriptionRequest.cs -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct RemoveSubscriptionRequestSurrogate diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionResponse.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionResponse.cs index 88253c99b916..2bb956188361 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionResponse.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionResponse.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // RemoveSubscriptionResponse.cs -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct RemoveSubscriptionResponseSurrogate diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcRequestSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcRequestSurrogate.cs index 9791a68d7952..7c3409d69bb3 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcRequestSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcRequestSurrogate.cs @@ -4,7 +4,7 @@ using Google.Protobuf.Collections; using Microsoft.AutoGen.Contracts; -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct RpcRequestSurrogate diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs index 5c9fac246f84..6a6209c38928 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs @@ -3,7 +3,7 @@ using Google.Protobuf.Collections; -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct RpcResponseSurrogate diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/SubscriptionSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/SubscriptionSurrogate.cs index 1fd56c176278..9cde59cc92fe 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/SubscriptionSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/SubscriptionSurrogate.cs @@ -3,7 +3,7 @@ using Microsoft.AutoGen.Contracts; -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct SubscriptionSurrogate diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypePrefixSubscriptionSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypePrefixSubscriptionSurrogate.cs index ca4d721315e8..50c7908556e0 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypePrefixSubscriptionSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypePrefixSubscriptionSurrogate.cs @@ -3,7 +3,7 @@ using Microsoft.AutoGen.Contracts; -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct TypePrefixSubscriptionSurrogate diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypeSubscriptionSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypeSubscriptionSurrogate.cs index 57fa202ebfc3..9e5799d41846 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypeSubscriptionSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypeSubscriptionSurrogate.cs @@ -3,7 +3,7 @@ using Microsoft.AutoGen.Contracts; -namespace Microsoft.AutoGen.Runtime.Grpc.Orleans.Surrogates; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] public struct TypeSubscriptionSurrogate diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/GrpcGatewayServiceTests.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs similarity index 97% rename from dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/GrpcGatewayServiceTests.cs rename to dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs index 90b6b2dddcf2..c2e761c471b6 100644 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/GrpcGatewayServiceTests.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs @@ -4,13 +4,13 @@ using FluentAssertions; using Microsoft.AutoGen.Contracts; using Microsoft.AutoGen.Core; -using Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; -using Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Orleans; +using Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Grpc; +using Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Orleans; using Microsoft.Extensions.Logging; using Moq; using NewMessageReceived = Tests.Events.NewMessageReceived; -namespace Microsoft.AutoGen.Runtime.Grpc.Tests; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests; [Collection(ClusterCollection.Name)] [Trait("Category", "UnitV2")] public class GrpcGatewayServiceTests diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs similarity index 96% rename from dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs rename to dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs index 4f26711d149f..a0708a13b484 100644 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs @@ -16,7 +16,7 @@ using System.Threading.Channels; using Grpc.Core; -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Grpc; public class TestAsyncStreamReader : IDisposable, IAsyncStreamReader where T : class diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs similarity index 94% rename from dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs rename to dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs index e47f26eda159..bff029c344f5 100644 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs @@ -3,7 +3,7 @@ using Microsoft.AutoGen.Contracts; -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Grpc; internal sealed class TestGrpcClient : IDisposable { public TestAsyncStreamReader RequestStream { get; } diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs similarity index 97% rename from dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs rename to dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs index 47f25155602d..1f0c2e7f6bc9 100644 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs @@ -15,7 +15,7 @@ using Grpc.Core; -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Grpc; public class TestServerCallContext : ServerCallContext { diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs similarity index 97% rename from dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs rename to dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs index ca2aeab2e410..92074b2fabc6 100644 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs @@ -16,7 +16,7 @@ using System.Threading.Channels; using Grpc.Core; -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Grpc; public class TestServerStreamWriter : IDisposable, IServerStreamWriter where T : class { diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs similarity index 78% rename from dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs rename to dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs index d61dc7b21c50..e391a47f3e6c 100644 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ClusterCollection.cs -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Orleans; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Orleans; [CollectionDefinition(Name)] public sealed class ClusterCollection : ICollectionFixture diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs similarity index 87% rename from dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs rename to dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs index 9db2f7f654d4..cb1320a251c3 100644 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs @@ -3,7 +3,7 @@ using Orleans.TestingHost; -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Orleans; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Orleans; public sealed class ClusterFixture : IDisposable { diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs similarity index 89% rename from dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs rename to dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs index bb960f7b1107..731ab83694c8 100644 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs @@ -4,7 +4,7 @@ using Orleans.Serialization; using Orleans.TestingHost; -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Orleans; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Orleans; public class SiloBuilderConfigurator : ISiloConfigurator { diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Microsoft.AutoGen.Runtime.Grpc.Tests.csproj b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.csproj similarity index 93% rename from dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Microsoft.AutoGen.Runtime.Grpc.Tests.csproj rename to dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.csproj index ab0899c0f169..2639fc8b360a 100644 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Microsoft.AutoGen.Runtime.Grpc.Tests.csproj +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/TestAgent.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs similarity index 96% rename from dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/TestAgent.cs rename to dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs index e479a2cd7d33..55a8ac44453e 100644 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/TestAgent.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace Microsoft.AutoGen.Runtime.Grpc.Tests; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests; [TopicSubscription("gh-gh-gh")] public class PBAgent([FromKeyedServices("AgentsMetadata")] AgentsMetadata eventTypes, ILogger? logger = null) From 2d318358d6e4da88ac1daca74d03735a50e57b72 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Wed, 5 Feb 2025 12:41:44 -0800 Subject: [PATCH 21/53] interim --- .../Abstractions/IAgentGrain.cs | 14 ++- .../Abstractions/IConnection.cs | 10 -- .../Abstractions/IGateway.cs | 3 +- .../Abstractions/IGatewayRegistry.cs | 2 +- .../Services/Grpc/GrpcGateway.cs | 91 +++++-------------- .../Services/Grpc/GrpcGatewayService.cs | 1 - .../Services/Grpc/GrpcWorkerConnection.cs | 3 +- 7 files changed, 40 insertions(+), 84 deletions(-) delete mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IConnection.cs diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs index 47abf2b7cfcc..0ca9882247b3 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs @@ -3,9 +3,21 @@ using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; - +/// +/// Interface for managing agent state as an Orleans Grain. +/// internal interface IAgentGrain : IGrainWithStringKey { + /// + /// Reads the state from the Orleans Grain. + /// + /// A task representing the AgentState object ValueTask ReadStateAsync(); + /// + /// Writes the state to the Orleans Grain. + /// + /// + /// used for optimistic concurrency control + /// ValueTask WriteStateAsync(AgentState state, string eTag); } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IConnection.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IConnection.cs deleted file mode 100644 index 7292a16165de..000000000000 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IConnection.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// IConnection.cs -namespace Microsoft.AutoGen.Contracts; -/// -/// Represents a connection to a remote service. -/// Is just used to potentially alow us to add other connection types in the future or to sub a Channel for a Connection. -/// -public interface IConnection -{ -} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs index 71a029eb98ad..4176b933f5d3 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // IGateway.cs +using Grpc.Core; using Microsoft.AutoGen.Contracts; using Microsoft.AutoGen.Protobuf; @@ -11,7 +12,7 @@ public interface IGateway : IGrainObserver ValueTask BroadcastEventAsync(CloudEvent evt); ValueTask StoreAsync(AgentState value); ValueTask ReadAsync(Protobuf.AgentId agentId); - ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request); + ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, ServerCallContext context); ValueTask SubscribeAsync(AddSubscriptionRequest request); ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request); ValueTask> GetSubscriptionsAsync(GetSubscriptionsRequest request); diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs index f2f86a2e7dbd..68850713f6aa 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs @@ -14,7 +14,7 @@ public interface IGatewayRegistry : IRegistry /// /// The ID of the agent. /// A tuple containing the worker and a boolean indicating if it's a new placement. - ValueTask<(IGateway? Worker, bool NewPlacement)> GetOrPlaceAgent(Contracts.AgentId agentId); + ValueTask<(IGateway? Worker, bool NewPlacement)> GetOrPlaceAgent(AgentId agentId); /// /// Removes a worker from the registry. diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs index 62ecb001bfba..63f0cc1ad2b4 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs @@ -21,7 +21,7 @@ public sealed class GrpcGateway : BackgroundService, IGateway private readonly IGateway _reference; // The agents supported by each worker process. private readonly ConcurrentDictionary> _supportedAgentTypes = []; - public readonly ConcurrentDictionary _workers = new(); + public readonly ConcurrentDictionary _workers = new(); internal readonly ConcurrentDictionary _workersByConnection = new(); private readonly ConcurrentDictionary _subscriptionsByAgentType = new(); private readonly ConcurrentDictionary> _subscriptionsByTopic = new(); @@ -77,29 +77,25 @@ public async ValueTask ReadAsync(Protobuf.AgentId agentId, Cancellat var agentState = _clusterClient.GetGrain($"{agentId.Type}:{agentId.Key}"); return await agentState.ReadStateAsync(); } - public async ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, CancellationToken cancellationToken = default) + public async ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, ServerCallContext context, CancellationToken cancellationToken = default) { try { - var connection = _workersByConnection[request.RequestId]; + var clientId = context.RequestHeaders.Get("client-id")?.Value ?? + throw new RpcException(new Status(StatusCode.InvalidArgument, "Grpc Client ID is required.")); + if (!_workers.TryGetValue(clientId, out var connection)) + { + throw new RpcException(new Status(StatusCode.InvalidArgument, $"Grpc Worker Connection not found for ClientId {clientId}.")); + } connection.AddSupportedType(request.Type); _supportedAgentTypes.GetOrAdd(request.Type, _ => []).Add(connection); await _gatewayRegistry.RegisterAgentTypeAsync(request, _reference).ConfigureAwait(true); - return new RegisterAgentTypeResponse - { - Success = true, - RequestId = request.RequestId - }; + return new RegisterAgentTypeResponse {}; } catch (Exception ex) { - return new RegisterAgentTypeResponse - { - Success = false, - RequestId = request.RequestId, - Error = ex.Message - }; + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); } } public async ValueTask SubscribeAsync(AddSubscriptionRequest request, CancellationToken cancellationToken = default) @@ -107,20 +103,11 @@ public async ValueTask SubscribeAsync(AddSubscriptionRe try { await _gatewayRegistry.SubscribeAsync(request).ConfigureAwait(true); - return new AddSubscriptionResponse - { - Success = true, - RequestId = request.RequestId - }; + return new AddSubscriptionResponse { }; } catch (Exception ex) { - return new AddSubscriptionResponse - { - Success = false, - RequestId = request.RequestId, - Error = ex.Message - }; + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); } } protected override async Task ExecuteAsync(CancellationToken stoppingToken) @@ -153,7 +140,6 @@ internal async Task ConnectToWorkerProcess(IAsyncStreamReader requestSt throw new RpcException(new Status(StatusCode.InvalidArgument, "Client ID is required.")); var workerProcess = new GrpcWorkerConnection(this, requestStream, responseStream, context); _workers.GetOrAdd(clientId, workerProcess); - _workersByConnection.GetOrAdd(context.Peer, workerProcess); await workerProcess.Connect().ConfigureAwait(false); } internal async Task SendMessageAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent, CancellationToken cancellationToken = default) @@ -174,12 +160,6 @@ internal async Task OnReceivedMessageAsync(GrpcWorkerConnection connection, Mess case Message.MessageOneofCase.CloudEvent: await DispatchEventAsync(message.CloudEvent, cancellationToken); break; - case Message.MessageOneofCase.RegisterAgentTypeRequest: - await RegisterAgentTypeAsync(connection, message.RegisterAgentTypeRequest); - break; - case Message.MessageOneofCase.AddSubscriptionRequest: - await AddSubscriptionAsync(connection, message.AddSubscriptionRequest); - break; default: // if it wasn't recognized return bad request await RespondBadRequestAsync(connection, $"Unknown message type for message '{message}'."); @@ -196,23 +176,6 @@ private void DispatchResponse(GrpcWorkerConnection connection, RpcResponse respo // Complete the request. completion.SetResult(response); } - private async ValueTask RegisterAgentTypeAsync(GrpcWorkerConnection connection, RegisterAgentTypeRequest msg) - { - connection.AddSupportedType(msg.Type); - _supportedAgentTypes.GetOrAdd(msg.Type, _ => []).Add(connection); - - await _gatewayRegistry.RegisterAgentTypeAsync(msg, _reference).ConfigureAwait(true); - Message response = new() - { - RegisterAgentTypeResponse = new() - { - RequestId = msg.RequestId, - Error = "", - Success = true - } - }; - await connection.ResponseStream.WriteAsync(response).ConfigureAwait(false); - } private async ValueTask DispatchEventAsync(CloudEvent evt, CancellationToken cancellationToken = default) { var registry = _clusterClient.GetGrain(0); @@ -278,7 +241,9 @@ private static async Task InvokeRequestDelegate(GrpcWorkerConnection connection, } internal void OnRemoveWorkerProcess(GrpcWorkerConnection workerProcess) { - _workers.TryRemove(workerProcess, out _); + var clientId = workerProcess.ServerCallContext.RequestHeaders.Get("client-id")?.Value ?? + throw new RpcException(new Status(StatusCode.InvalidArgument, "Grpc Client ID is required.")); + _workers.TryRemove(clientId, out _); var types = workerProcess.GetSupportedTypes(); foreach (var type in types) { @@ -350,15 +315,11 @@ public async ValueTask BroadcastEventAsync(CloudEvent evt, CancellationToken can foreach (var (_, connection) in _supportedAgentTypes) { - tasks.Add(this.SendMessageAsync((IConnection)connection[0], evt, default)); + tasks.Add(this.SendMessageAsync((GrpcWorkerConnection)connection[0], evt, default)); } await Task.WhenAll(tasks).ConfigureAwait(false); } - Task IGateway.SendMessageAsync(IConnection connection, CloudEvent cloudEvent) - { - return this.SendMessageAsync(connection, cloudEvent, default); - } - public async Task SendMessageAsync(IConnection connection, CloudEvent cloudEvent, CancellationToken cancellationToken = default) + public async Task SendMessageAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent, CancellationToken cancellationToken = default) { var queue = (GrpcWorkerConnection)connection; await queue.ResponseStream.WriteAsync(new Message { CloudEvent = cloudEvent }, cancellationToken).ConfigureAwait(false); @@ -369,19 +330,11 @@ public async ValueTask UnsubscribeAsync(RemoveSubscr try { await _gatewayRegistry.UnsubscribeAsync(request).ConfigureAwait(true); - return new RemoveSubscriptionResponse - - { - Success = true, - }; + return new RemoveSubscriptionResponse { }; } catch (Exception ex) { - return new RemoveSubscriptionResponse - { - Success = false, - Error = ex.Message - }; + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); } } public ValueTask> GetSubscriptionsAsync(GetSubscriptionsRequest request, CancellationToken cancellationToken = default) @@ -400,13 +353,13 @@ ValueTask IGateway.StoreAsync(AgentState value) { return StoreAsync(value, default); } - ValueTask IGateway.ReadAsync(AgentId agentId) + ValueTask IGateway.ReadAsync(Protobuf.AgentId agentId) { return ReadAsync(agentId, default); } - ValueTask IGateway.RegisterAgentTypeAsync(RegisterAgentTypeRequest request) + ValueTask IGateway.RegisterAgentTypeAsync(RegisterAgentTypeRequest request, ServerCallContext context) { - return RegisterAgentTypeAsync(request, default); + return RegisterAgentTypeAsync(request, context, default); } ValueTask IGateway.SubscribeAsync(AddSubscriptionRequest request) { diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs index b0f002c00258..fa853f32a071 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs @@ -54,7 +54,6 @@ public override async Task GetSubscriptions(GetSubscri } public override async Task RegisterAgent(RegisterAgentTypeRequest request, ServerCallContext context) { - request.RequestId = context.Peer; return await Gateway.RegisterAgentTypeAsync(request).ConfigureAwait(true); } } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs index 7f6fcde4f5c4..6fe8fb570793 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs @@ -2,11 +2,12 @@ // GrpcWorkerConnection.cs using System.Threading.Channels; using Grpc.Core; +using Microsoft.AutoGen.Contracts; using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc; -public sealed class GrpcWorkerConnection : IAsyncDisposable +public sealed class GrpcWorkerConnection : IAsyncDisposable, IConnection { private static long s_nextConnectionId; private Task _readTask = Task.CompletedTask; From f1aa1fa52bdb14fd2c4fcb20461239b4e64da170 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Wed, 5 Feb 2025 13:08:14 -0800 Subject: [PATCH 22/53] resolve build breaks and add doc comments --- .../Abstractions/IGateway.cs | 2 +- .../Services/Grpc/GrpcGateway.cs | 345 ++++++++++++------ .../Services/Grpc/GrpcGatewayService.cs | 115 +++++- .../Services/Grpc/GrpcWorkerConnection.cs | 7 +- 4 files changed, 335 insertions(+), 134 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs index 4176b933f5d3..32e25bce564e 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs @@ -16,5 +16,5 @@ public interface IGateway : IGrainObserver ValueTask SubscribeAsync(AddSubscriptionRequest request); ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request); ValueTask> GetSubscriptionsAsync(GetSubscriptionsRequest request); - Task SendMessageAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent); + Task WriteResponseAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent); } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs index 63f0cc1ad2b4..205badf2c696 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs @@ -11,26 +11,28 @@ namespace Microsoft.AutoGen.RuntimeGateway.Grpc; +/// +/// Represents the gRPC gateway service that handles communication between the agent worker and the cluster. +/// public sealed class GrpcGateway : BackgroundService, IGateway { private static readonly TimeSpan s_agentResponseTimeout = TimeSpan.FromSeconds(30); private readonly ILogger _logger; private readonly IClusterClient _clusterClient; - //private readonly ConcurrentDictionary _agentState = new(); private readonly IRegistryGrain _gatewayRegistry; private readonly IGateway _reference; - // The agents supported by each worker process. + private readonly ISubscriptionsGrain _subscriptions; private readonly ConcurrentDictionary> _supportedAgentTypes = []; public readonly ConcurrentDictionary _workers = new(); internal readonly ConcurrentDictionary _workersByConnection = new(); - private readonly ConcurrentDictionary _subscriptionsByAgentType = new(); - private readonly ConcurrentDictionary> _subscriptionsByTopic = new(); - private readonly ISubscriptionsGrain _subscriptions; - - // The mapping from agent id to worker process. private readonly ConcurrentDictionary<(string Type, string Key), GrpcWorkerConnection> _agentDirectory = new(); - // RPC private readonly ConcurrentDictionary<(GrpcWorkerConnection, string), TaskCompletionSource> _pendingRequests = new(); + + /// + /// Initializes a new instance of the class. + /// + /// The cluster client. + /// The logger. public GrpcGateway(IClusterClient clusterClient, ILogger logger) { _logger = logger; @@ -39,12 +41,47 @@ public GrpcGateway(IClusterClient clusterClient, ILogger logger) _gatewayRegistry = clusterClient.GetGrain(0); _subscriptions = clusterClient.GetGrain(0); } + + /// + /// Executes the background service. + /// + /// The cancellation token. + /// A task that represents the asynchronous operation. + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + try + { + await _gatewayRegistry.AddWorkerAsync(_reference); + } + catch (Exception exception) + { + _logger.LogWarning(exception, "Error adding worker to registry."); + } + await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken); + } + try + { + await _gatewayRegistry.RemoveWorkerAsync(_reference); + } + catch (Exception exception) + { + _logger.LogWarning(exception, "Error removing worker from registry."); + } + } + + /// + /// Invokes a request asynchronously. + /// + /// The RPC request. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the RPC response. public async ValueTask InvokeRequestAsync(RpcRequest request, CancellationToken cancellationToken = default) { var agentId = (request.Target.Type, request.Target.Key); if (!_agentDirectory.TryGetValue(agentId, out var connection) || connection.Completion.IsCompleted == true) { - // Activate the agent on a compatible worker process. if (_supportedAgentTypes.TryGetValue(request.Target.Type, out var workers)) { connection = workers[Random.Shared.Next(workers.Count)]; @@ -55,28 +92,48 @@ public async ValueTask InvokeRequestAsync(RpcRequest request, Cance return new(new RpcResponse { Error = "Agent not found." }); } } - // Proxy the request to the agent. var originalRequestId = request.RequestId; var newRequestId = Guid.NewGuid().ToString(); var completion = _pendingRequests[(connection, newRequestId)] = new(TaskCreationOptions.RunContinuationsAsynchronously); request.RequestId = newRequestId; await connection.ResponseStream.WriteAsync(new Message { Request = request }, cancellationToken).ConfigureAwait(false); - // Wait for the response and send it back to the caller. var response = await completion.Task.WaitAsync(s_agentResponseTimeout); response.RequestId = originalRequestId; return response; } + + /// + /// Stores the agent state asynchronously. + /// + /// The agent state. + /// The cancellation token. + /// A task that represents the asynchronous operation. public async ValueTask StoreAsync(AgentState value, CancellationToken cancellationToken = default) { _ = value.AgentId ?? throw new ArgumentNullException(nameof(value.AgentId)); var agentState = _clusterClient.GetGrain($"{value.AgentId.Type}:{value.AgentId.Key}"); await agentState.WriteStateAsync(value, value.ETag); } + + /// + /// Reads the agent state asynchronously. + /// + /// The agent ID. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the agent state. public async ValueTask ReadAsync(Protobuf.AgentId agentId, CancellationToken cancellationToken = default) { var agentState = _clusterClient.GetGrain($"{agentId.Type}:{agentId.Key}"); return await agentState.ReadStateAsync(); } + + /// + /// Registers an agent type asynchronously. + /// + /// The register agent type request. + /// The server call context. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the register agent type response. public async ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, ServerCallContext context, CancellationToken cancellationToken = default) { try @@ -98,6 +155,13 @@ public async ValueTask RegisterAgentTypeAsync(Registe throw new RpcException(new Status(StatusCode.Internal, ex.Message)); } } + + /// + /// Subscribes to a topic asynchronously. + /// + /// The add subscription request. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the add subscription response. public async ValueTask SubscribeAsync(AddSubscriptionRequest request, CancellationToken cancellationToken = default) { try @@ -110,29 +174,84 @@ public async ValueTask SubscribeAsync(AddSubscriptionRe throw new RpcException(new Status(StatusCode.Internal, ex.Message)); } } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) + + /// + /// Unsubscribes from a topic asynchronously. + /// + /// The remove subscription request. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the remove subscription response. + public async ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request, CancellationToken cancellationToken = default) { - while (!stoppingToken.IsCancellationRequested) - { - try - { - await _gatewayRegistry.AddWorkerAsync(_reference); - } - catch (Exception exception) - { - _logger.LogWarning(exception, "Error adding worker to registry."); - } - await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken); - } try { - await _gatewayRegistry.RemoveWorkerAsync(_reference); + await _gatewayRegistry.UnsubscribeAsync(request).ConfigureAwait(true); + return new RemoveSubscriptionResponse { }; } - catch (Exception exception) + catch (Exception ex) { - _logger.LogWarning(exception, "Error removing worker from registry."); + throw new RpcException(new Status(StatusCode.Internal, ex.Message)); } } + + /// + /// Gets the subscriptions asynchronously. + /// + /// The get subscriptions request. + /// The cancellation token. + /// A task that represents the asynchronous operation. The task result contains the list of subscriptions. + public ValueTask> GetSubscriptionsAsync(GetSubscriptionsRequest request, CancellationToken cancellationToken = default) + { + return _gatewayRegistry.GetSubscriptionsAsync(request); + } + + async ValueTask IGateway.InvokeRequestAsync(RpcRequest request) + { + return await InvokeRequestAsync(request, default).ConfigureAwait(false); + } + + async ValueTask IGateway.BroadcastEventAsync(CloudEvent evt) + { + await BroadcastEventAsync(evt, default).ConfigureAwait(false); + } + + ValueTask IGateway.StoreAsync(AgentState value) + { + return StoreAsync(value, default); + } + + ValueTask IGateway.ReadAsync(Protobuf.AgentId agentId) + { + return ReadAsync(agentId, default); + } + + ValueTask IGateway.RegisterAgentTypeAsync(RegisterAgentTypeRequest request, ServerCallContext context) + { + return RegisterAgentTypeAsync(request, context, default); + } + + ValueTask IGateway.SubscribeAsync(AddSubscriptionRequest request) + { + return SubscribeAsync(request, default); + } + + ValueTask IGateway.UnsubscribeAsync(RemoveSubscriptionRequest request) + { + return UnsubscribeAsync(request, default); + } + + ValueTask> IGateway.GetSubscriptionsAsync(GetSubscriptionsRequest request) + { + return GetSubscriptionsAsync(request); + } + + /// + /// Connects to a worker process. + /// + /// The request stream. + /// The response stream. + /// The server call context. + /// A task that represents the asynchronous operation. internal async Task ConnectToWorkerProcess(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) { _logger.LogInformation("Received new connection from {Peer}.", context.Peer); @@ -142,10 +261,14 @@ internal async Task ConnectToWorkerProcess(IAsyncStreamReader requestSt _workers.GetOrAdd(clientId, workerProcess); await workerProcess.Connect().ConfigureAwait(false); } - internal async Task SendMessageAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent, CancellationToken cancellationToken = default) - { - await connection.ResponseStream.WriteAsync(new Message { CloudEvent = cloudEvent }, cancellationToken).ConfigureAwait(false); - } + + /// + /// Handles received messages from a worker connection. + /// + /// The worker connection. + /// The received message. + /// The cancellation token. + /// A task that represents the asynchronous operation. internal async Task OnReceivedMessageAsync(GrpcWorkerConnection connection, Message message, CancellationToken cancellationToken = default) { _logger.LogInformation("Received message {Message} from connection {Connection}.", message, connection); @@ -161,11 +284,16 @@ internal async Task OnReceivedMessageAsync(GrpcWorkerConnection connection, Mess await DispatchEventAsync(message.CloudEvent, cancellationToken); break; default: - // if it wasn't recognized return bad request await RespondBadRequestAsync(connection, $"Unknown message type for message '{message}'."); break; }; } + + /// + /// Dispatches a response to a pending request. + /// + /// The worker connection. + /// The RPC response. private void DispatchResponse(GrpcWorkerConnection connection, RpcResponse response) { if (!_pendingRequests.TryRemove((connection, response.RequestId), out var completion)) @@ -173,9 +301,15 @@ private void DispatchResponse(GrpcWorkerConnection connection, RpcResponse respo _logger.LogWarning("Received response for unknown request id: {RequestId}.", response.RequestId); return; } - // Complete the request. completion.SetResult(response); } + + /// + /// Dispatches an event to the appropriate agents. + /// + /// The cloud event. + /// The cancellation token. + /// A task that represents the asynchronous operation. private async ValueTask DispatchEventAsync(CloudEvent evt, CancellationToken cancellationToken = default) { var registry = _clusterClient.GetGrain(0); @@ -193,7 +327,7 @@ private async ValueTask DispatchEventAsync(CloudEvent evt, CancellationToken can var activeConnections = connections.Where(c => c.Completion?.IsCompleted == false).ToList(); foreach (var connection in activeConnections) { - tasks.Add(this.SendMessageAsync(connection, evt, cancellationToken)); + tasks.Add(this.WriteResponseAsync(connection, evt, cancellationToken)); } } } @@ -204,6 +338,13 @@ private async ValueTask DispatchEventAsync(CloudEvent evt, CancellationToken can _logger.LogWarning("No agent types found for event type {EventType}.", evt.Type); } } + + /// + /// Dispatches a request to the appropriate agent. + /// + /// The worker connection. + /// The RPC request. + /// A task that represents the asynchronous operation. private async ValueTask DispatchRequestAsync(GrpcWorkerConnection connection, RpcRequest request) { var requestId = request.RequestId; @@ -226,6 +367,14 @@ await InvokeRequestDelegate(connection, request, async request => return await gateway.InvokeRequestAsync(request).ConfigureAwait(true); }).ConfigureAwait(false); } + + /// + /// Invokes a request delegate. + /// + /// The worker connection. + /// The RPC request. + /// The function to invoke. + /// A task that represents the asynchronous operation. private static async Task InvokeRequestDelegate(GrpcWorkerConnection connection, RpcRequest request, Func> func) { try @@ -239,6 +388,11 @@ private static async Task InvokeRequestDelegate(GrpcWorkerConnection connection, await connection.ResponseStream.WriteAsync(new Message { Response = new RpcResponse { RequestId = request.RequestId, Error = ex.Message } }).ConfigureAwait(false); } } + + /// + /// Handles the removal of a worker process. + /// + /// The worker process. internal void OnRemoveWorkerProcess(GrpcWorkerConnection workerProcess) { var clientId = workerProcess.ServerCallContext.RequestHeaders.Get("client-id")?.Value ?? @@ -252,7 +406,6 @@ internal void OnRemoveWorkerProcess(GrpcWorkerConnection workerProcess) supported.Remove(workerProcess); } } - // Any agents activated on that worker are also gone. foreach (var pair in _agentDirectory) { if (pair.Value == workerProcess) @@ -261,39 +414,24 @@ internal void OnRemoveWorkerProcess(GrpcWorkerConnection workerProcess) } } } + + /// + /// Responds with a bad request error. + /// + /// The worker connection. + /// The error message. + /// A task that represents the asynchronous operation. private static async ValueTask RespondBadRequestAsync(GrpcWorkerConnection connection, string error) { throw new RpcException(new Status(StatusCode.InvalidArgument, error)); } - private async ValueTask AddSubscriptionAsync(GrpcWorkerConnection connection, AddSubscriptionRequest request) - { - var topic = ""; - var agentType = ""; - if (request.Subscription.TypePrefixSubscription is not null) - { - topic = request.Subscription.TypePrefixSubscription.TopicTypePrefix; - agentType = request.Subscription.TypePrefixSubscription.AgentType; - } - else if (request.Subscription.TypeSubscription is not null) - { - topic = request.Subscription.TypeSubscription.TopicType; - agentType = request.Subscription.TypeSubscription.AgentType; - } - _subscriptionsByAgentType[agentType] = request.Subscription; - _subscriptionsByTopic.GetOrAdd(topic, _ => []).Add(agentType); - await _subscriptions.SubscribeAsync(topic, agentType); - //var response = new SubscriptionResponse { RequestId = request.RequestId, Error = "", Success = true }; - Message response = new() - { - AddSubscriptionResponse = new() - { - RequestId = request.RequestId, - Error = "", - Success = true - } - }; - await connection.ResponseStream.WriteAsync(response).ConfigureAwait(false); - } + + /// + /// Dispatches an event to the specified agent types. + /// + /// The agent types. + /// The cloud event. + /// A task that represents the asynchronous operation. private async ValueTask DispatchEventToAgentsAsync(IEnumerable agentTypes, CloudEvent evt) { var tasks = new List(agentTypes.Count()); @@ -303,74 +441,49 @@ private async ValueTask DispatchEventToAgentsAsync(IEnumerable agentType { foreach (var connection in connections) { - tasks.Add(this.SendMessageAsync(connection, evt)); + tasks.Add(this.WriteResponseAsync(connection, evt)); } } } await Task.WhenAll(tasks).ConfigureAwait(false); } + + /// + /// Broadcasts an event to all workers. + /// + /// The cloud event. + /// The cancellation token. + /// A task that represents the asynchronous operation. public async ValueTask BroadcastEventAsync(CloudEvent evt, CancellationToken cancellationToken = default) { var tasks = new List(_workers.Count); foreach (var (_, connection) in _supportedAgentTypes) { - - tasks.Add(this.SendMessageAsync((GrpcWorkerConnection)connection[0], evt, default)); + tasks.Add(this.WriteResponseAsync((GrpcWorkerConnection)connection[0], evt, default)); } await Task.WhenAll(tasks).ConfigureAwait(false); } - public async Task SendMessageAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent, CancellationToken cancellationToken = default) - { - var queue = (GrpcWorkerConnection)connection; - await queue.ResponseStream.WriteAsync(new Message { CloudEvent = cloudEvent }, cancellationToken).ConfigureAwait(false); - } - public async ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request, CancellationToken cancellationToken = default) - { - try - { - await _gatewayRegistry.UnsubscribeAsync(request).ConfigureAwait(true); - return new RemoveSubscriptionResponse { }; - } - catch (Exception ex) - { - throw new RpcException(new Status(StatusCode.Internal, ex.Message)); - } - } - public ValueTask> GetSubscriptionsAsync(GetSubscriptionsRequest request, CancellationToken cancellationToken = default) - { - return _gatewayRegistry.GetSubscriptionsAsync(request); - } - async ValueTask IGateway.InvokeRequestAsync(RpcRequest request) - { - return await InvokeRequestAsync(request, default).ConfigureAwait(false); - } - async ValueTask IGateway.BroadcastEventAsync(CloudEvent evt) - { - await BroadcastEventAsync(evt, default).ConfigureAwait(false); - } - ValueTask IGateway.StoreAsync(AgentState value) + /// + /// Writes a response to a worker connection. + /// + /// The worker connection. + /// The cloud event. + /// The cancellation token. + /// A task that represents the asynchronous operation. + public async Task WriteResponseAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent, CancellationToken cancellationToken = default) { - return StoreAsync(value, default); - } - ValueTask IGateway.ReadAsync(Protobuf.AgentId agentId) - { - return ReadAsync(agentId, default); - } - ValueTask IGateway.RegisterAgentTypeAsync(RegisterAgentTypeRequest request, ServerCallContext context) - { - return RegisterAgentTypeAsync(request, context, default); - } - ValueTask IGateway.SubscribeAsync(AddSubscriptionRequest request) - { - return SubscribeAsync(request, default); - } - ValueTask IGateway.UnsubscribeAsync(RemoveSubscriptionRequest request) - { - return UnsubscribeAsync(request, default); + await connection.ResponseStream.WriteAsync(new Message { CloudEvent = cloudEvent }, cancellationToken).ConfigureAwait(false); } - ValueTask> IGateway.GetSubscriptionsAsync(GetSubscriptionsRequest request) + + /// + /// Writes a response to a worker connection. + /// + /// The worker connection. + /// The cloud event. + /// A task that represents the asynchronous operation. + public async Task WriteResponseAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent) { - return GetSubscriptionsAsync(request); + await WriteResponseAsync(connection, cloudEvent, default).ConfigureAwait(false); } } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs index fa853f32a071..0f0427d83bf3 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs @@ -5,11 +5,20 @@ namespace Microsoft.AutoGen.RuntimeGateway.Grpc; -// gRPC service which handles communication between the agent worker and the cluster. +/// +/// Represents the gRPC service which handles communication between the agent worker and the cluster. +/// public sealed class GrpcGatewayService(GrpcGateway gateway) : AgentRpc.AgentRpcBase { private readonly GrpcGateway Gateway = (GrpcGateway)gateway; + /// + /// Method run on first connect from a worker process. + /// + /// The request stream. + /// The response stream. + /// The server call context. + /// A task that represents the asynchronous operation. public override async Task OpenChannel(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) { try @@ -25,35 +34,115 @@ public override async Task OpenChannel(IAsyncStreamReader requestStream throw; } } + + /// + /// Gets the state of an agent. + /// + /// The agent ID request. + /// The server call context. + /// A task that represents the asynchronous operation. The task result contains the get state response. public override async Task GetState(Protobuf.AgentId request, ServerCallContext context) { - var state = await Gateway.ReadAsync(request); - return new GetStateResponse { AgentState = state }; + try + { + var state = await Gateway.ReadAsync(request).ConfigureAwait(true); + return new GetStateResponse { AgentState = state }; + } + catch (Exception e) + { + throw new RpcException(new Status(StatusCode.Internal, e.Message)); + } } + + /// + /// Saves the state of an agent. + /// + /// The agent state request. + /// The server call context. + /// A task that represents the asynchronous operation. The task result contains the save state response. public override async Task SaveState(AgentState request, ServerCallContext context) { - await Gateway.StoreAsync(request); - return new SaveStateResponse + try + { + await Gateway.StoreAsync(request).ConfigureAwait(true); + return new SaveStateResponse(); + } + catch (Exception e) { - Success = true // TODO: Implement error handling - }; + throw new RpcException(new Status(StatusCode.Internal, e.Message)); + } } + + /// + /// Adds a subscription. + /// + /// The add subscription request. + /// The server call context. + /// A task that represents the asynchronous operation. The task result contains the add subscription response. public override async Task AddSubscription(AddSubscriptionRequest request, ServerCallContext context) { - request.RequestId = context.Peer; - return await Gateway.SubscribeAsync(request).ConfigureAwait(true); + try + { + return await Gateway.SubscribeAsync(request).ConfigureAwait(true); + } + catch (Exception e) + { + throw new RpcException(new Status(StatusCode.Internal, e.Message)); + } } + + /// + /// Removes a subscription. + /// + /// The remove subscription request. + /// The server call context. + /// A task that represents the asynchronous operation. The task result contains the remove subscription response. public override async Task RemoveSubscription(RemoveSubscriptionRequest request, ServerCallContext context) { - return await Gateway.UnsubscribeAsync(request).ConfigureAwait(true); + try + { + return await Gateway.UnsubscribeAsync(request).ConfigureAwait(true); + } + catch (Exception e) + { + throw new RpcException(new Status(StatusCode.Internal, e.Message)); + } } + + /// + /// Gets the subscriptions. + /// + /// The get subscriptions request. + /// The server call context. + /// A task that represents the asynchronous operation. The task result contains the get subscriptions response. public override async Task GetSubscriptions(GetSubscriptionsRequest request, ServerCallContext context) { - var subscriptions = await Gateway.GetSubscriptionsAsync(request); - return new GetSubscriptionsResponse { Subscriptions = { subscriptions } }; + try + { + var subscriptions = await Gateway.GetSubscriptionsAsync(request); + return new GetSubscriptionsResponse { Subscriptions = { subscriptions } }; + } + catch (Exception e) + { + throw new RpcException(new Status(StatusCode.Internal, e.Message)); + } } + + /// + /// Registers an agent type (factory) + /// + /// The register agent type request. + /// The server call context. + /// A task that represents the asynchronous operation. The task result contains the register agent type response. public override async Task RegisterAgent(RegisterAgentTypeRequest request, ServerCallContext context) { - return await Gateway.RegisterAgentTypeAsync(request).ConfigureAwait(true); + try + { + return await Gateway.RegisterAgentTypeAsync(request, context).ConfigureAwait(true); + } + catch (Exception e) + { + throw new RpcException(new Status(StatusCode.Internal, e.Message)); + } } } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs index 6fe8fb570793..8559a0218430 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs @@ -2,12 +2,11 @@ // GrpcWorkerConnection.cs using System.Threading.Channels; using Grpc.Core; -using Microsoft.AutoGen.Contracts; using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc; -public sealed class GrpcWorkerConnection : IAsyncDisposable, IConnection +public sealed class GrpcWorkerConnection : IAsyncDisposable { private static long s_nextConnectionId; private Task _readTask = Task.CompletedTask; @@ -103,9 +102,9 @@ public async Task RunWritePump() await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding); try { - await foreach (var message in _outboundMessages.Reader.ReadAllAsync(_shutdownCancellationToken.Token)) + await foreach (var message in _outboundMessages.Reader.ReadAllAsync(_shutdownCancellationToken.Token).ConfigureAwait(false)) { - await ResponseStream.WriteAsync(message); + await ResponseStream.WriteAsync(message).ConfigureAwait(false); } } catch (OperationCanceledException) From 388f021eb31328d7ffda1b6a0bf65832da7df39c Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Wed, 5 Feb 2025 13:10:04 -0800 Subject: [PATCH 23/53] fix path --- .../AgentHost/Microsoft.AutoGen.AgentHost.csproj | 2 +- .../Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/AgentHost/Microsoft.AutoGen.AgentHost.csproj b/dotnet/src/Microsoft.AutoGen/AgentHost/Microsoft.AutoGen.AgentHost.csproj index d16b68625184..dea603ebc668 100644 --- a/dotnet/src/Microsoft.AutoGen/AgentHost/Microsoft.AutoGen.AgentHost.csproj +++ b/dotnet/src/Microsoft.AutoGen/AgentHost/Microsoft.AutoGen.AgentHost.csproj @@ -15,7 +15,7 @@ - + \ No newline at end of file diff --git a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.csproj b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.csproj index 2639fc8b360a..066a49c2de68 100644 --- a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.csproj +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.csproj @@ -14,7 +14,7 @@ - + From ee7032bbca48c4dd52af4e9e34480a2cb43be39c Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Wed, 5 Feb 2025 14:02:09 -0800 Subject: [PATCH 24/53] more fixup --- .../Core/AgentRuntimeExtensions.cs | 2 +- .../Core/TypePrefixSubscriptionAttribute.cs | 2 +- .../Abstractions/AgentTypes.cs | 18 ++ .../Abstractions/AgentsMetadata.cs | 85 ++++++ .../Abstractions/AgentsRegistryState.cs | 16 ++ .../Abstractions/IAgentState.cs | 26 ++ .../Abstractions/IRegistry.cs | 2 +- .../Abstractions/IRegistryStorage.cs | 20 ++ .../Abstractions/ReflectionHelper.cs | 70 +++++ .../Abstractions/Registry.cs | 269 ++++++++++++++++++ .../TopicSubscriptionAttribute.cs | 10 + .../Services/AgentWorkerHostingExtensions.cs | 3 +- .../Services/Orleans/AgentStateGrain.cs | 4 +- .../AddSubscriptionRequestSurrogate.cs | 3 +- .../AddSubscriptionResponseSurrogate.cs | 16 +- .../Orleans/Surrogates/AgentIdSurrogate.cs | 2 +- .../Orleans/Surrogates/AgentStateSurrogate.cs | 4 +- .../Orleans/Surrogates/CloudEventSurrogate.cs | 2 +- .../Surrogates/GetSubscriptionsRequest.cs | 2 + .../RegisterAgentTypeRequestSurrogate.cs | 3 +- .../RegisterAgentTypeResponseSurrogate.cs | 16 +- .../Surrogates/RemoveSubscriptionRequest.cs | 1 + .../Surrogates/RemoveSubscriptionResponse.cs | 13 +- .../Orleans/Surrogates/RpcRequestSurrogate.cs | 2 +- .../Surrogates/RpcResponseSurrogate.cs | 1 + .../Surrogates/SubscriptionSurrogate.cs | 3 +- .../TypePrefixSubscriptionSurrogate.cs | 2 +- .../Surrogates/TypeSubscriptionSurrogate.cs | 3 +- .../GrpcGatewayServiceTests.cs | 1 + 29 files changed, 546 insertions(+), 55 deletions(-) create mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentTypes.cs create mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs create mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs create mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentState.cs create mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryStorage.cs create mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs create mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/Registry.cs create mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/TopicSubscriptionAttribute.cs diff --git a/dotnet/src/Microsoft.AutoGen/Core/AgentRuntimeExtensions.cs b/dotnet/src/Microsoft.AutoGen/Core/AgentRuntimeExtensions.cs index fd7a479adde3..dfda552956ba 100644 --- a/dotnet/src/Microsoft.AutoGen/Core/AgentRuntimeExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/Core/AgentRuntimeExtensions.cs @@ -62,7 +62,7 @@ private static ISubscriptionDefinition[] BindSubscriptionsForAgentType(AgentType var classSubscriptions = runtimeType.GetCustomAttributes().Select(t => t.Bind(agentType)); subscriptions.AddRange(classSubscriptions); - var prefixSubscriptions = runtimeType.GetCustomAttributes().Select(t => t.Bind(agentType)); + var prefixSubscriptions = runtimeType.GetCustomAttributes().Select(t => t.Bind(agentType)); subscriptions.AddRange(prefixSubscriptions); } diff --git a/dotnet/src/Microsoft.AutoGen/Core/TypePrefixSubscriptionAttribute.cs b/dotnet/src/Microsoft.AutoGen/Core/TypePrefixSubscriptionAttribute.cs index 57105c8b9629..be48ab8b195f 100644 --- a/dotnet/src/Microsoft.AutoGen/Core/TypePrefixSubscriptionAttribute.cs +++ b/dotnet/src/Microsoft.AutoGen/Core/TypePrefixSubscriptionAttribute.cs @@ -6,7 +6,7 @@ namespace Microsoft.AutoGen.Core; [AttributeUsage(AttributeTargets.All)] -public class TopicPrefixSubscriptionAttribute(string topic) : Attribute, IUnboundSubscriptionDefinition +public class TypePrefixSubscriptionAttribute(string topic) : Attribute, IUnboundSubscriptionDefinition { public string Topic { get; } = topic; diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentTypes.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentTypes.cs new file mode 100644 index 000000000000..aa0defe005ba --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentTypes.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// AgentTypes.cs +using Microsoft.AutoGen.Core; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; +public sealed class AgentTypes(Dictionary types) +{ + public Dictionary Types { get; } = types; + public static AgentTypes? GetAgentTypesFromAssembly() + { + var agents = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => ReflectionHelper.IsSubclassOfGeneric(type, typeof(BaseAgent)) + && !type.IsAbstract) + .ToDictionary(type => type.Name, type => type); + + return new AgentTypes(agents); + } +} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs new file mode 100644 index 000000000000..4719fb7a25a1 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// AgentsMetadata.cs + +using System.Collections.Concurrent; +using Google.Protobuf.Reflection; + +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; + +/// +/// Represents a collection of event types and their associated metadata. +/// +public sealed class AgentsMetadata +{ + /// + /// Initializes a new instance of the class. + /// + /// The type registry containing protobuf type information. + /// A dictionary mapping event names to their corresponding types. + /// A dictionary mapping types to a set of event names associated with those types. + public AgentsMetadata(TypeRegistry typeRegistry, Dictionary types, Dictionary> eventsMap, Dictionary> topicsMap) + { + TypeRegistry = typeRegistry; + _types = new(types); + _eventsMap = new(eventsMap); + _topicsMap = new(topicsMap); + } + + /// + /// Gets the type registry containing protobuf type information. + /// + public TypeRegistry TypeRegistry { get; } + + private ConcurrentDictionary _types; + + private ConcurrentDictionary> _eventsMap; + private ConcurrentDictionary> _topicsMap; + + /// + /// Checks if a given type handles a specific event name. + /// + /// The type to check. + /// The event name to check. + /// true if the type handles the event name; otherwise, false. + public bool CheckIfTypeHandles(Type type, string eventName) + { + if (_eventsMap.TryGetValue(type, out var events)) + { + return events.Contains(eventName); + } + return false; + } + + /// + /// Gets the event type by its name. + /// + /// The name of the event type. + /// The event type if found; otherwise, null. + public Type? GetEventTypeByName(string type) + { + if (_types.TryGetValue(type, out var eventType)) + { + return eventType; + } + return null; + } + + public HashSet? GetEventsForAgent(Type agent) + { + if (_eventsMap.TryGetValue(agent, out var events)) + { + return events; + } + return null; + } + + public HashSet? GetTopicsForAgent(Type agent) + { + if (_topicsMap.TryGetValue(agent, out var topics)) + { + return topics; + } + return null; + } +} + diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs new file mode 100644 index 000000000000..59a45df1cbaa --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// AgentsRegistryState.cs +using System.Collections.Concurrent; +using Microsoft.AutoGen.Protobuf; + +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; +public class AgentsRegistryState +{ + public ConcurrentDictionary> AgentsToEventsMap { get; set; } = new ConcurrentDictionary>(); + public ConcurrentDictionary> AgentsToTopicsMap { get; set; } = []; + public ConcurrentDictionary> TopicToAgentTypesMap { get; set; } = []; + public ConcurrentDictionary> EventsToAgentTypesMap { get; set; } = []; + public ConcurrentDictionary> GuidSubscriptionsMap { get; set; } = []; + public ConcurrentDictionary AgentTypes { get; set; } = []; + public string Etag { get; set; } = Guid.NewGuid().ToString(); +} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentState.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentState.cs new file mode 100644 index 000000000000..126a43a15c21 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentState.cs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IAgentState.cs +using Microsoft.AutoGen.Protobuf; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; + +/// +/// Interface for managing the state of an agent. +/// +public interface IAgentState +{ + /// + /// Reads the current state of the agent asynchronously. + /// + /// A token to cancel the operation. + /// A task that represents the asynchronous read operation. The task result contains the current state of the agent. + ValueTask ReadStateAsync(CancellationToken cancellationToken = default); + + /// + /// Writes the specified state of the agent asynchronously. + /// + /// The state to write. + /// The ETag for concurrency control. + /// A token to cancel the operation. + /// A task that represents the asynchronous write operation. The task result contains the ETag of the written state. + ValueTask WriteStateAsync(AgentState state, string eTag, CancellationToken cancellationToken = default); +} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs index 8f36f5de7f8e..f18c6719cead 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs @@ -2,7 +2,7 @@ // IRegistry.cs using Microsoft.AutoGen.Protobuf; using Microsoft.AutoGen.Contracts; -namespace Microsoft.AutoGen.RuntimeGateway.Grpc; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; public interface IRegistry { diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryStorage.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryStorage.cs new file mode 100644 index 000000000000..5e57c3b75a39 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryStorage.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IRegistryStorage.cs + +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; + +public interface IRegistryStorage +{ + /// + /// Populates the Registry state from the storage. + /// + /// + Task ReadStateAsync(CancellationToken cancellationToken = default); + /// + /// Writes the Registry state to the storage. + /// + /// + /// + /// the etag that was written + ValueTask WriteStateAsync(AgentsRegistryState state, CancellationToken cancellationToken = default); +} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs new file mode 100644 index 000000000000..1d4d060e2b70 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ReflectionHelper.cs +using Microsoft.AutoGen.Core; +using Microsoft.AutoGen.Contracts; +using System.Reflection; +using Google.Protobuf; +using Google.Protobuf.Reflection; + +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; +public sealed class ReflectionHelper +{ + public static bool IsSubclassOfGeneric(Type type, Type genericBaseType) + { + while (type != null && type != typeof(object)) + { + if (genericBaseType == (type.IsGenericType ? type.GetGenericTypeDefinition() : type)) + { + return true; + } + if (type.BaseType == null) + { + return false; + } + type = type.BaseType; + } + return false; + } + public static AgentsMetadata GetAgentsMetadata(params Assembly[] assemblies) + { + var interfaceType = typeof(IMessage); + var pairs = assemblies + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => interfaceType.IsAssignableFrom(type) && type.IsClass && !type.IsAbstract) + .Select(t => (t, GetMessageDescriptor(t))); + + var descriptors = pairs.Select(t => t.Item2); + var typeRegistry = TypeRegistry.FromMessages(descriptors); + var types = pairs.ToDictionary(item => item.Item2?.FullName ?? "", item => item.t); + + var eventsMap = assemblies + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => IsSubclassOfGeneric(type, typeof(BaseAgent)) && !type.IsAbstract) + .Select(t => (t, t.GetInterfaces() + .Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IHandle<>)) + .Select(i => GetMessageDescriptor(i.GetGenericArguments().First())?.FullName ?? "").ToHashSet())) + .ToDictionary(item => item.t, item => item.Item2); + var topicsMap = assemblies + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => IsSubclassOfGeneric(type, typeof(BaseAgent)) && !type.IsAbstract) + .Select(t => (t, t.GetCustomAttributes().Select(a => a.Topic).ToHashSet())) + .ToDictionary(item => item.t, item => item.Item2); + var topicsPrefixMap = assemblies + .SelectMany(assembly => assembly.GetTypes()) + .Where(type => IsSubclassOfGeneric(type, typeof(BaseAgent)) && !type.IsAbstract) + .Select(t => (t, t.GetCustomAttributes().Select(a => a.Topic).ToHashSet())) + .ToDictionary(item => item.t, item => item.Item2); + return new AgentsMetadata(typeRegistry, types, eventsMap, topicsMap); + } + + /// + /// Gets the message descriptor for the specified type. + /// + /// The type to get the message descriptor for. + /// The message descriptor if found; otherwise, null. + public static MessageDescriptor? GetMessageDescriptor(Type type) + { + var property = type.GetProperty("Descriptor", BindingFlags.Static | BindingFlags.Public); + return property?.GetValue(null) as MessageDescriptor; + } +} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/Registry.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/Registry.cs new file mode 100644 index 000000000000..03caa9da338a --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/Registry.cs @@ -0,0 +1,269 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Registry.cs +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; +using Microsoft.AutoGen.Core; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; +public class Registry : IRegistry +{ + public AgentsRegistryState State { get; set; } + private readonly IRegistryStorage Storage; + private readonly ILogger _logger; + private string _registryEtag; + private const int _retries = 5; + + public Registry(IRegistryStorage storage, ILogger logger) + { + _logger = logger; + Storage = storage; + State = Storage.ReadStateAsync().ConfigureAwait(true).GetAwaiter().GetResult(); + _registryEtag = State.Etag; + _logger.LogInformation("Registry initialized."); + } + + public ValueTask> GetSubscribedAndHandlingAgentsAsync(string topic, string eventType) + { + UpdateStateIfStale(); + List agents = []; + // get all agent types that are subscribed to the topic + if (State.TopicToAgentTypesMap.TryGetValue(topic, out var subscribedAgentTypes)) + { + /*// get all agent types that are handling the event + if (State.EventsToAgentTypesMap.TryGetValue(eventType, out var handlingAgents)) + { + agents.AddRange(subscribedAgentTypes.Intersect(handlingAgents).ToList()); + }*/ + agents.AddRange(subscribedAgentTypes.ToList()); + } + if (State.TopicToAgentTypesMap.TryGetValue(eventType, out var eventHandlingAgents)) + { + agents.AddRange(eventHandlingAgents.ToList()); + } + if (State.TopicToAgentTypesMap.TryGetValue(topic + "." + eventType, out var combo)) + { + agents.AddRange(combo.ToList()); + } + // instead of an exact match, we can also check for a prefix match where key starts with the eventType + if (State.TopicToAgentTypesMap.Keys.Any(key => key.StartsWith(eventType))) + { + State.TopicToAgentTypesMap.Where( + kvp => kvp.Key.StartsWith(eventType)) + .SelectMany(kvp => kvp.Value) + .Distinct() + .ToList() + .ForEach(async agentType => + { + agents.Add(agentType); + }); + } + agents = agents.Distinct().ToList(); + + return new ValueTask>(agents); + } + public async ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest registration, IAgentRuntime runtime) + { + var retries = _retries; + while (!await RegisterAgentTypeWriteAsync(registration, runtime)) + { + if (retries == 0) + { + throw new IOException($"Failed to register agent type after {_retries} retries."); + } + _logger.LogWarning("Failed to register agent type, retrying..."); + retries--; + } + } + + private async ValueTask RegisterAgentTypeWriteAsync(RegisterAgentTypeRequest registration, IAgentRuntime runtime, CancellationToken cancellationToken = default) + { + UpdateStateIfStale(); + if (registration.Type is null) + { + throw new InvalidOperationException("RegisterAgentType: Agent type is required."); + } + var agentTypes = AgentTypes.GetAgentTypesFromAssembly() + ?? throw new InvalidOperationException("No agent types found in the assembly"); + + if (!agentTypes.Types.TryGetValue(registration.Type, out var value)) + { + throw new InvalidOperationException($"RegisterAgentType: Invalid agent type {registration.Type}."); + } + try + { + var agentInstance = (BaseAgent)runtime.RuntimeServiceProvider.GetRequiredService(value); + _logger.LogWarning("Agent type {agentType} is already registered.", registration.Type); + State.AgentTypes.TryAdd(registration.Type, agentInstance.AgentId); + } + catch (InvalidOperationException) + { + // Agent type was not yet in the registry - it won't be available in DI + _logger.LogInformation("Agent type {agentType} is not yet registered, activating", registration.Type); + var agent = (BaseAgent)ActivatorUtilities.CreateInstance(runtime.RuntimeServiceProvider, instanceType: value); + State.AgentTypes.TryAdd(registration.Type, agent.AgentId); + } + return await WriteStateAsync(State, cancellationToken).ConfigureAwait(false); + } + public async ValueTask SubscribeAsync(AddSubscriptionRequest subscription) + { + var retries = _retries; + while (!await SubscribeWriteAsync(subscription)) + { + if (retries == 0) + { + throw new IOException($"Failed to subscribe after {_retries} retries."); + } + _logger.LogWarning("Failed to subscribe, retrying..."); + retries--; + } + } + private async ValueTask SubscribeWriteAsync(AddSubscriptionRequest subscription, CancellationToken cancellationToken = default) + { + UpdateStateIfStale(); + var guid = Guid.NewGuid().ToString(); + subscription.Subscription.Id = guid; + switch (subscription.Subscription.SubscriptionCase) + { + //TODO: this doesnt look right + case Subscription.SubscriptionOneofCase.TypePrefixSubscription: + break; + case Subscription.SubscriptionOneofCase.TypeSubscription: + { + // add the topic to the set of topics for the agent type + State.AgentsToTopicsMap.TryGetValue(subscription.Subscription.TypeSubscription.AgentType, out var topics); + if (topics is null) + { + topics = new HashSet(); + State.AgentsToTopicsMap[subscription.Subscription.TypeSubscription.AgentType] = topics; + } + topics.Add(subscription.Subscription.TypeSubscription.TopicType); + + // add the agent type to the set of agent types for the topic + State.TopicToAgentTypesMap.TryGetValue(subscription.Subscription.TypeSubscription.TopicType, out var agents); + if (agents is null) + { + agents = new HashSet(); + State.TopicToAgentTypesMap[subscription.Subscription.TypeSubscription.TopicType] = agents; + } + agents.Add(subscription.Subscription.TypeSubscription.AgentType); + + // add the subscription by Guid + State.GuidSubscriptionsMap.TryGetValue(guid, out var existingSubscriptions); + if (existingSubscriptions is null) + { + existingSubscriptions = new HashSet(); + State.GuidSubscriptionsMap[guid] = existingSubscriptions; + } + existingSubscriptions.Add(subscription.Subscription); + break; + } + default: + throw new InvalidOperationException("Invalid subscription type"); + } + return await WriteStateAsync(State, cancellationToken).ConfigureAwait(false); + } + public async ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request) + { + var retries = _retries; + while (!await UnsubscribeWriteAsync(request)) + { + if (retries == 0) + { + throw new IOException($"Failed to unsubscribe after {_retries} retries."); + } + _logger.LogWarning("Failed to unsubscribe, retrying..."); + retries--; + } + } + private async ValueTask UnsubscribeWriteAsync(RemoveSubscriptionRequest request, CancellationToken cancellationToken = default) + { + UpdateStateIfStale(); + var guid = request.Id; + // does the guid parse? + if (!Guid.TryParse(guid, out var _)) + { + throw new InvalidOperationException("Invalid subscription id"); + } + if (State.GuidSubscriptionsMap.TryGetValue(guid, out var subscriptions)) + { + foreach (var subscription in subscriptions) + { + switch (subscription.SubscriptionCase) + { + case Subscription.SubscriptionOneofCase.TypeSubscription: + { + // remove the topic from the set of topics for the agent type + State.AgentsToTopicsMap.TryGetValue(subscription.TypeSubscription.AgentType, out var topics); + topics?.Remove(subscription.TypeSubscription.TopicType); + + // remove the agent type from the set of agent types for the topic + State.TopicToAgentTypesMap.TryGetValue(subscription.TypeSubscription.TopicType, out var agents); + agents?.Remove(subscription.TypeSubscription.AgentType); + + //remove the subscription by Guid + State.GuidSubscriptionsMap.TryGetValue(guid, out var existingSubscriptions); + existingSubscriptions?.Remove(subscription); + break; + } + case Subscription.SubscriptionOneofCase.TypePrefixSubscription: + break; + default: + throw new InvalidOperationException("Invalid subscription type"); + } + } + State.GuidSubscriptionsMap.Remove(guid, out _); + return await WriteStateAsync(State, cancellationToken).ConfigureAwait(false); + } + return true; + } + public ValueTask> GetSubscriptionsAsync(GetSubscriptionsRequest request) + { + var _ = request; + UpdateStateIfStale(); + var subscriptions = new List(); + foreach (var kvp in State.GuidSubscriptionsMap) + { + subscriptions.AddRange(kvp.Value); + } + return new(subscriptions); + } + /// + /// in case there is a write in between our last read and now... + /// + private void UpdateStateIfStale() + { + if (State.Etag != _registryEtag) + { + State = Storage.ReadStateAsync().ConfigureAwait(true).GetAwaiter().GetResult(); + _registryEtag = State.Etag; + } + } + /// + /// Writes the state to the storage. + /// + /// + /// bool true on success, false on failure + private async ValueTask WriteStateAsync(AgentsRegistryState state, CancellationToken cancellationToken = default) + { + try + { + await Storage.WriteStateAsync(state, cancellationToken).ConfigureAwait(false); + _registryEtag = state.Etag; + State = state; + return true; + } + catch (Exception e) + { + _logger.LogError(e, "Failed to write state to storage."); + return false; + } + } + + public ValueTask UnregisterAgentTypeAsync(string type, IAgentRuntime worker) + { + throw new NotImplementedException(); + } +} + diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/TopicSubscriptionAttribute.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/TopicSubscriptionAttribute.cs new file mode 100644 index 000000000000..585da97c8f59 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/TopicSubscriptionAttribute.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// TopicSubscriptionAttribute.cs + +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; + +[AttributeUsage(AttributeTargets.All)] +public class TopicSubscriptionAttribute(string topic) : Attribute +{ + public string Topic { get; } = topic; +} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs index 2421b9ba1edd..e4ee962aee6d 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using Microsoft.AspNetCore.Builder; -using Microsoft.AutoGen.Core; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; @@ -20,7 +19,7 @@ public static WebApplicationBuilder AddAgentService(this WebApplicationBuilder b builder.Services.AddGrpc(); builder.Services.AddKeyedSingleton("AgentsMetadata", (sp, key) => { - return ReflectionHelper.GetAgentsMetadata(AppDomain.CurrentDomain.GetAssemblies()); + return Abstractions.ReflectionHelper.GetAgentsMetadata(AppDomain.CurrentDomain.GetAssemblies()); }); builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => (IHostedService)sp.GetRequiredService()); diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs index 4392eec12a01..670e31e1fc97 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AgentStateGrain.cs - +using Microsoft.AutoGen.Protobuf; using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; namespace Microsoft.AutoGen.RuntimeGateway.Grpc; @@ -14,7 +14,7 @@ public async ValueTask WriteStateAsync(AgentState newState, string eTag, // if the Etag is null, its a new state // if the passed etag is null or empty, we should not check the current state's Etag - caller doesnt care // if both etags are set, they should match or it means that the state has changed since the last read. - if ((string.IsNullOrEmpty(state.Etag)) || (string.IsNullOrEmpty(eTag)) || (string.Equals(state.Etag, eTag, StringComparison.Ordinal))) + if (string.IsNullOrEmpty(state.Etag) || string.IsNullOrEmpty(eTag) || string.Equals(state.Etag, eTag, StringComparison.Ordinal)) { state.State = newState; await state.WriteStateAsync().ConfigureAwait(false); diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionRequestSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionRequestSurrogate.cs index a5c6b57e05f9..793301cf4d5f 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionRequestSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionRequestSurrogate.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AddSubscriptionRequestSurrogate.cs +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; @@ -21,7 +22,6 @@ public AddSubscriptionRequest ConvertFromSurrogate( { var request = new AddSubscriptionRequest() { - RequestId = surrogate.RequestId, Subscription = surrogate.Subscription }; return request; @@ -31,7 +31,6 @@ public AddSubscriptionRequestSurrogate ConvertToSurrogate( in AddSubscriptionRequest value) => new AddSubscriptionRequestSurrogate { - RequestId = value.RequestId, Subscription = value.Subscription }; } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionResponseSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionResponseSurrogate.cs index c9a9606d1a9c..6a07a114d3e6 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionResponseSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AddSubscriptionResponseSurrogate.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AddSubscriptionResponseSurrogate.cs +using Microsoft.AutoGen.Protobuf; + namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] @@ -20,20 +22,10 @@ public sealed class AddSubscriptionResponseSurrogateConverter : { public AddSubscriptionResponse ConvertFromSurrogate( in AddSubscriptionResponseSurrogate surrogate) => - new AddSubscriptionResponse - { - RequestId = surrogate.RequestId, - Success = surrogate.Success, - Error = surrogate.Error - }; + new AddSubscriptionResponse { }; public AddSubscriptionResponseSurrogate ConvertToSurrogate( in AddSubscriptionResponse value) => - new AddSubscriptionResponseSurrogate - { - RequestId = value.RequestId, - Success = value.Success, - Error = value.Error - }; + new AddSubscriptionResponseSurrogate { }; } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentIdSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentIdSurrogate.cs index bde3fe7bdb18..af7728d1254c 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentIdSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentIdSurrogate.cs @@ -3,7 +3,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AgentIdSurrogate.cs -using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs index a2ab0ff96793..377a1da210e8 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs @@ -2,7 +2,7 @@ // AgentStateSurrogate.cs using Google.Protobuf; -using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; @@ -16,7 +16,7 @@ public struct AgentStateSurrogate [Id(2)] public ByteString BinaryData; [Id(3)] - public AgentId AgentId; + public Protobuf.AgentId AgentId; [Id(4)] public string Etag; [Id(5)] diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/CloudEventSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/CloudEventSurrogate.cs index c1a13400db26..a69f7cfc89a5 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/CloudEventSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/CloudEventSurrogate.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // CloudEventSurrogate.cs - using Google.Protobuf; using Google.Protobuf.WellKnownTypes; +using Microsoft.AutoGen.Contracts; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/GetSubscriptionsRequest.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/GetSubscriptionsRequest.cs index ea5ccb44865b..e53948041828 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/GetSubscriptionsRequest.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/GetSubscriptionsRequest.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // GetSubscriptionsRequest.cs +using Microsoft.AutoGen.Protobuf; + namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeRequestSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeRequestSurrogate.cs index 4ec528e939d6..9bcba2391bb4 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeRequestSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeRequestSurrogate.cs @@ -2,6 +2,7 @@ // RegisterAgentTypeRequestSurrogate.cs using Google.Protobuf.Collections; +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; @@ -27,7 +28,6 @@ public RegisterAgentTypeRequest ConvertFromSurrogate( { var request = new RegisterAgentTypeRequest() { - RequestId = surrogate.RequestId, Type = surrogate.Type }; /* future @@ -40,7 +40,6 @@ public RegisterAgentTypeRequestSurrogate ConvertToSurrogate( in RegisterAgentTypeRequest value) => new RegisterAgentTypeRequestSurrogate { - RequestId = value.RequestId, Type = value.Type, /* future Events = value.Events, diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeResponseSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeResponseSurrogate.cs index eda268a566f9..c91fb3833c30 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeResponseSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RegisterAgentTypeResponseSurrogate.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // RegisterAgentTypeResponseSurrogate.cs +using Microsoft.AutoGen.Protobuf; + namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] @@ -20,20 +22,10 @@ public sealed class RegisterAgentTypeResponseSurrogateConverter : { public RegisterAgentTypeResponse ConvertFromSurrogate( in RegisterAgentTypeResponseSurrogate surrogate) => - new RegisterAgentTypeResponse - { - RequestId = surrogate.RequestId, - Success = surrogate.Success, - Error = surrogate.Error - }; + new RegisterAgentTypeResponse { }; public RegisterAgentTypeResponseSurrogate ConvertToSurrogate( in RegisterAgentTypeResponse value) => - new RegisterAgentTypeResponseSurrogate - { - RequestId = value.RequestId, - Success = value.Success, - Error = value.Error - }; + new RegisterAgentTypeResponseSurrogate { }; } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionRequest.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionRequest.cs index 54de9fe313f2..9b397c2bb82d 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionRequest.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionRequest.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // RemoveSubscriptionRequest.cs +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionResponse.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionResponse.cs index 2bb956188361..eec77162942e 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionResponse.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RemoveSubscriptionResponse.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // RemoveSubscriptionResponse.cs +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; @@ -20,18 +21,10 @@ public sealed class SubscriptionResponseSurrogateConverter : { public RemoveSubscriptionResponse ConvertFromSurrogate( in RemoveSubscriptionResponseSurrogate surrogate) => - new RemoveSubscriptionResponse - { - Success = surrogate.Success, - Error = surrogate.Error - }; + new RemoveSubscriptionResponse { }; public RemoveSubscriptionResponseSurrogate ConvertToSurrogate( in RemoveSubscriptionResponse value) => - new RemoveSubscriptionResponseSurrogate - { - Success = value.Success, - Error = value.Error - }; + new RemoveSubscriptionResponseSurrogate { }; } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcRequestSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcRequestSurrogate.cs index 7c3409d69bb3..4b9fdb2500f6 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcRequestSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcRequestSurrogate.cs @@ -2,7 +2,7 @@ // RpcRequestSurrogate.cs using Google.Protobuf.Collections; -using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs index 6a6209c38928..1c2852d811e2 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // RpcResponseSurrogate.cs +using Microsoft.AutoGen.Protobuf; using Google.Protobuf.Collections; diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/SubscriptionSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/SubscriptionSurrogate.cs index 9cde59cc92fe..6942ada72b0b 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/SubscriptionSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/SubscriptionSurrogate.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SubscriptionSurrogate.cs - -using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypePrefixSubscriptionSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypePrefixSubscriptionSurrogate.cs index 50c7908556e0..c38d84641b11 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypePrefixSubscriptionSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypePrefixSubscriptionSurrogate.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // TypePrefixSubscriptionSurrogate.cs -using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypeSubscriptionSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypeSubscriptionSurrogate.cs index 9e5799d41846..958c1c4d682f 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypeSubscriptionSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/TypeSubscriptionSurrogate.cs @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // TypeSubscriptionSurrogate.cs -using Microsoft.AutoGen.Contracts; - +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; [GenerateSerializer] diff --git a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs index c2e761c471b6..8bc96dbc0739 100644 --- a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs @@ -4,6 +4,7 @@ using FluentAssertions; using Microsoft.AutoGen.Contracts; using Microsoft.AutoGen.Core; +using Microsoft.AutoGen.Protobuf; using Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Grpc; using Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Orleans; using Microsoft.Extensions.Logging; From 611b859802bf47bddd5da8a0bede7308f62f29b6 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Thu, 6 Feb 2025 08:28:10 -0800 Subject: [PATCH 25/53] more cleanup --- .../Abstractions/AgentsRegistryState.cs | 3 +- .../Abstractions/IGatewayRegistry.cs | 2 +- .../Abstractions/IRegistry.cs | 9 +- .../Abstractions/Registry.cs | 269 ------------------ ...crosoft.AutoGen.RuntimeGateway.Grpc.csproj | 5 +- .../Services/Grpc/GrpcGateway.cs | 4 +- .../Services/Orleans/RegistryGrain.cs | 28 +- 7 files changed, 17 insertions(+), 303 deletions(-) delete mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/Registry.cs diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs index 59a45df1cbaa..58d6d2a9c11f 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AgentsRegistryState.cs using System.Collections.Concurrent; +using Microsoft.AutoGen.Contracts; using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; @@ -11,6 +12,6 @@ public class AgentsRegistryState public ConcurrentDictionary> TopicToAgentTypesMap { get; set; } = []; public ConcurrentDictionary> EventsToAgentTypesMap { get; set; } = []; public ConcurrentDictionary> GuidSubscriptionsMap { get; set; } = []; - public ConcurrentDictionary AgentTypes { get; set; } = []; + public ConcurrentDictionary> AgentTypes { get; set; } = []; public string Etag { get; set; } = Guid.NewGuid().ToString(); } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs index 68850713f6aa..f2f86a2e7dbd 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs @@ -14,7 +14,7 @@ public interface IGatewayRegistry : IRegistry /// /// The ID of the agent. /// A tuple containing the worker and a boolean indicating if it's a new placement. - ValueTask<(IGateway? Worker, bool NewPlacement)> GetOrPlaceAgent(AgentId agentId); + ValueTask<(IGateway? Worker, bool NewPlacement)> GetOrPlaceAgent(Contracts.AgentId agentId); /// /// Removes a worker from the registry. diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs index f18c6719cead..a25437c3714c 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // IRegistry.cs using Microsoft.AutoGen.Protobuf; -using Microsoft.AutoGen.Contracts; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; public interface IRegistry @@ -11,19 +10,19 @@ public interface IRegistry /// Registers a new agent type with the specified worker. /// /// The request containing agent type details. - /// The worker to register the agent type with. + /// The connection the client conneted on /// A task representing the asynchronous operation. /// removing CancellationToken from here as it is not compatible with Orleans Serialization - ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, IAgentRuntime worker); + ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, string clientId, GrpcWorkerConnection connection); /// /// Unregisters an agent type from the specified worker. /// /// The type of the agent to unregister. - /// The worker to unregister the agent type from. + /// The connection the client conneted on /// A task representing the asynchronous operation. /// removing CancellationToken from here as it is not compatible with Orleans Serialization - ValueTask UnregisterAgentTypeAsync(string type, IAgentRuntime worker); + ValueTask UnregisterAgentTypeAsync(string type, GrpcWorkerConnection connection); /// /// Gets a list of agents subscribed to and handling the specified topic and event type. diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/Registry.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/Registry.cs deleted file mode 100644 index 03caa9da338a..000000000000 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/Registry.cs +++ /dev/null @@ -1,269 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Registry.cs -using Microsoft.AutoGen.Contracts; -using Microsoft.AutoGen.Protobuf; -using Microsoft.AutoGen.Core; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; -public class Registry : IRegistry -{ - public AgentsRegistryState State { get; set; } - private readonly IRegistryStorage Storage; - private readonly ILogger _logger; - private string _registryEtag; - private const int _retries = 5; - - public Registry(IRegistryStorage storage, ILogger logger) - { - _logger = logger; - Storage = storage; - State = Storage.ReadStateAsync().ConfigureAwait(true).GetAwaiter().GetResult(); - _registryEtag = State.Etag; - _logger.LogInformation("Registry initialized."); - } - - public ValueTask> GetSubscribedAndHandlingAgentsAsync(string topic, string eventType) - { - UpdateStateIfStale(); - List agents = []; - // get all agent types that are subscribed to the topic - if (State.TopicToAgentTypesMap.TryGetValue(topic, out var subscribedAgentTypes)) - { - /*// get all agent types that are handling the event - if (State.EventsToAgentTypesMap.TryGetValue(eventType, out var handlingAgents)) - { - agents.AddRange(subscribedAgentTypes.Intersect(handlingAgents).ToList()); - }*/ - agents.AddRange(subscribedAgentTypes.ToList()); - } - if (State.TopicToAgentTypesMap.TryGetValue(eventType, out var eventHandlingAgents)) - { - agents.AddRange(eventHandlingAgents.ToList()); - } - if (State.TopicToAgentTypesMap.TryGetValue(topic + "." + eventType, out var combo)) - { - agents.AddRange(combo.ToList()); - } - // instead of an exact match, we can also check for a prefix match where key starts with the eventType - if (State.TopicToAgentTypesMap.Keys.Any(key => key.StartsWith(eventType))) - { - State.TopicToAgentTypesMap.Where( - kvp => kvp.Key.StartsWith(eventType)) - .SelectMany(kvp => kvp.Value) - .Distinct() - .ToList() - .ForEach(async agentType => - { - agents.Add(agentType); - }); - } - agents = agents.Distinct().ToList(); - - return new ValueTask>(agents); - } - public async ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest registration, IAgentRuntime runtime) - { - var retries = _retries; - while (!await RegisterAgentTypeWriteAsync(registration, runtime)) - { - if (retries == 0) - { - throw new IOException($"Failed to register agent type after {_retries} retries."); - } - _logger.LogWarning("Failed to register agent type, retrying..."); - retries--; - } - } - - private async ValueTask RegisterAgentTypeWriteAsync(RegisterAgentTypeRequest registration, IAgentRuntime runtime, CancellationToken cancellationToken = default) - { - UpdateStateIfStale(); - if (registration.Type is null) - { - throw new InvalidOperationException("RegisterAgentType: Agent type is required."); - } - var agentTypes = AgentTypes.GetAgentTypesFromAssembly() - ?? throw new InvalidOperationException("No agent types found in the assembly"); - - if (!agentTypes.Types.TryGetValue(registration.Type, out var value)) - { - throw new InvalidOperationException($"RegisterAgentType: Invalid agent type {registration.Type}."); - } - try - { - var agentInstance = (BaseAgent)runtime.RuntimeServiceProvider.GetRequiredService(value); - _logger.LogWarning("Agent type {agentType} is already registered.", registration.Type); - State.AgentTypes.TryAdd(registration.Type, agentInstance.AgentId); - } - catch (InvalidOperationException) - { - // Agent type was not yet in the registry - it won't be available in DI - _logger.LogInformation("Agent type {agentType} is not yet registered, activating", registration.Type); - var agent = (BaseAgent)ActivatorUtilities.CreateInstance(runtime.RuntimeServiceProvider, instanceType: value); - State.AgentTypes.TryAdd(registration.Type, agent.AgentId); - } - return await WriteStateAsync(State, cancellationToken).ConfigureAwait(false); - } - public async ValueTask SubscribeAsync(AddSubscriptionRequest subscription) - { - var retries = _retries; - while (!await SubscribeWriteAsync(subscription)) - { - if (retries == 0) - { - throw new IOException($"Failed to subscribe after {_retries} retries."); - } - _logger.LogWarning("Failed to subscribe, retrying..."); - retries--; - } - } - private async ValueTask SubscribeWriteAsync(AddSubscriptionRequest subscription, CancellationToken cancellationToken = default) - { - UpdateStateIfStale(); - var guid = Guid.NewGuid().ToString(); - subscription.Subscription.Id = guid; - switch (subscription.Subscription.SubscriptionCase) - { - //TODO: this doesnt look right - case Subscription.SubscriptionOneofCase.TypePrefixSubscription: - break; - case Subscription.SubscriptionOneofCase.TypeSubscription: - { - // add the topic to the set of topics for the agent type - State.AgentsToTopicsMap.TryGetValue(subscription.Subscription.TypeSubscription.AgentType, out var topics); - if (topics is null) - { - topics = new HashSet(); - State.AgentsToTopicsMap[subscription.Subscription.TypeSubscription.AgentType] = topics; - } - topics.Add(subscription.Subscription.TypeSubscription.TopicType); - - // add the agent type to the set of agent types for the topic - State.TopicToAgentTypesMap.TryGetValue(subscription.Subscription.TypeSubscription.TopicType, out var agents); - if (agents is null) - { - agents = new HashSet(); - State.TopicToAgentTypesMap[subscription.Subscription.TypeSubscription.TopicType] = agents; - } - agents.Add(subscription.Subscription.TypeSubscription.AgentType); - - // add the subscription by Guid - State.GuidSubscriptionsMap.TryGetValue(guid, out var existingSubscriptions); - if (existingSubscriptions is null) - { - existingSubscriptions = new HashSet(); - State.GuidSubscriptionsMap[guid] = existingSubscriptions; - } - existingSubscriptions.Add(subscription.Subscription); - break; - } - default: - throw new InvalidOperationException("Invalid subscription type"); - } - return await WriteStateAsync(State, cancellationToken).ConfigureAwait(false); - } - public async ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request) - { - var retries = _retries; - while (!await UnsubscribeWriteAsync(request)) - { - if (retries == 0) - { - throw new IOException($"Failed to unsubscribe after {_retries} retries."); - } - _logger.LogWarning("Failed to unsubscribe, retrying..."); - retries--; - } - } - private async ValueTask UnsubscribeWriteAsync(RemoveSubscriptionRequest request, CancellationToken cancellationToken = default) - { - UpdateStateIfStale(); - var guid = request.Id; - // does the guid parse? - if (!Guid.TryParse(guid, out var _)) - { - throw new InvalidOperationException("Invalid subscription id"); - } - if (State.GuidSubscriptionsMap.TryGetValue(guid, out var subscriptions)) - { - foreach (var subscription in subscriptions) - { - switch (subscription.SubscriptionCase) - { - case Subscription.SubscriptionOneofCase.TypeSubscription: - { - // remove the topic from the set of topics for the agent type - State.AgentsToTopicsMap.TryGetValue(subscription.TypeSubscription.AgentType, out var topics); - topics?.Remove(subscription.TypeSubscription.TopicType); - - // remove the agent type from the set of agent types for the topic - State.TopicToAgentTypesMap.TryGetValue(subscription.TypeSubscription.TopicType, out var agents); - agents?.Remove(subscription.TypeSubscription.AgentType); - - //remove the subscription by Guid - State.GuidSubscriptionsMap.TryGetValue(guid, out var existingSubscriptions); - existingSubscriptions?.Remove(subscription); - break; - } - case Subscription.SubscriptionOneofCase.TypePrefixSubscription: - break; - default: - throw new InvalidOperationException("Invalid subscription type"); - } - } - State.GuidSubscriptionsMap.Remove(guid, out _); - return await WriteStateAsync(State, cancellationToken).ConfigureAwait(false); - } - return true; - } - public ValueTask> GetSubscriptionsAsync(GetSubscriptionsRequest request) - { - var _ = request; - UpdateStateIfStale(); - var subscriptions = new List(); - foreach (var kvp in State.GuidSubscriptionsMap) - { - subscriptions.AddRange(kvp.Value); - } - return new(subscriptions); - } - /// - /// in case there is a write in between our last read and now... - /// - private void UpdateStateIfStale() - { - if (State.Etag != _registryEtag) - { - State = Storage.ReadStateAsync().ConfigureAwait(true).GetAwaiter().GetResult(); - _registryEtag = State.Etag; - } - } - /// - /// Writes the state to the storage. - /// - /// - /// bool true on success, false on failure - private async ValueTask WriteStateAsync(AgentsRegistryState state, CancellationToken cancellationToken = default) - { - try - { - await Storage.WriteStateAsync(state, cancellationToken).ConfigureAwait(false); - _registryEtag = state.Etag; - State = state; - return true; - } - catch (Exception e) - { - _logger.LogError(e, "Failed to write state to storage."); - return false; - } - } - - public ValueTask UnregisterAgentTypeAsync(string type, IAgentRuntime worker) - { - throw new NotImplementedException(); - } -} - diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Microsoft.AutoGen.RuntimeGateway.Grpc.csproj b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Microsoft.AutoGen.RuntimeGateway.Grpc.csproj index b29db7a7a36a..f9a568e89ade 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Microsoft.AutoGen.RuntimeGateway.Grpc.csproj +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Microsoft.AutoGen.RuntimeGateway.Grpc.csproj @@ -7,6 +7,7 @@ + @@ -30,10 +31,6 @@ - - - - diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs index 205badf2c696..062d044d0e4d 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs @@ -21,7 +21,6 @@ public sealed class GrpcGateway : BackgroundService, IGateway private readonly IClusterClient _clusterClient; private readonly IRegistryGrain _gatewayRegistry; private readonly IGateway _reference; - private readonly ISubscriptionsGrain _subscriptions; private readonly ConcurrentDictionary> _supportedAgentTypes = []; public readonly ConcurrentDictionary _workers = new(); internal readonly ConcurrentDictionary _workersByConnection = new(); @@ -39,7 +38,6 @@ public GrpcGateway(IClusterClient clusterClient, ILogger logger) _clusterClient = clusterClient; _reference = clusterClient.CreateObjectReference(this); _gatewayRegistry = clusterClient.GetGrain(0); - _subscriptions = clusterClient.GetGrain(0); } /// @@ -147,7 +145,7 @@ public async ValueTask RegisterAgentTypeAsync(Registe connection.AddSupportedType(request.Type); _supportedAgentTypes.GetOrAdd(request.Type, _ => []).Add(connection); - await _gatewayRegistry.RegisterAgentTypeAsync(request, _reference).ConfigureAwait(true); + await _gatewayRegistry.RegisterAgentTypeAsync(request, clientId, _reference).ConfigureAwait(true); return new RegisterAgentTypeResponse {}; } catch (Exception ex) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs index 1966f5c9c662..02433415315c 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // RegistryGrain.cs -using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; namespace Microsoft.AutoGen.RuntimeGateway.Grpc; @@ -96,16 +96,6 @@ public ValueTask RemoveWorkerAsync(IGateway worker) } public async ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest registration, IGateway gateway) { - if (!_supportedAgentTypes.TryGetValue(registration.Type, out var supportedAgentTypes)) - { - supportedAgentTypes = _supportedAgentTypes[registration.Type] = []; - } - - if (!supportedAgentTypes.Contains(gateway)) - { - supportedAgentTypes.Add(gateway); - } - var workerState = GetOrAddWorker(gateway); workerState.SupportedTypes.Add(registration.Type); @@ -160,8 +150,6 @@ private WorkerState GetOrAddWorker(IGateway worker) return workerState; } - public ValueTask GetCompatibleWorkerAsync(string type) => new(GetCompatibleWorkerCore(type)); - private IGateway? GetCompatibleWorkerCore(string type) { if (_supportedAgentTypes.TryGetValue(type, out var workers)) @@ -265,17 +253,17 @@ public ValueTask> GetSubscriptionsAsync(GetSubscriptionsReque } return new(subscriptions); } - public ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, IAgentRuntime worker) + + public ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, string clientId, Contracts.IAgentRuntime worker) { - var (_, _) = (request, worker); - var e = "RegisterAgentTypeAsync(RegisterAgentTypeRequest request, IAgentRuntime worker) is not implemented when using the Grpc runtime."; - throw new NotImplementedException(e); + throw new NotImplementedException(); } - public ValueTask UnregisterAgentTypeAsync(string type, IAgentRuntime worker) + + public ValueTask UnregisterAgentTypeAsync(string type, Contracts.IAgentRuntime worker) { - var e = "UnregisterAgentTypeAsync(string type, IAgentRuntime worker) is not implemented when using the Grpc runtime."; - throw new NotImplementedException(e); + throw new NotImplementedException(); } + private sealed class WorkerState { public HashSet SupportedTypes { get; set; } = []; From d3d912896f44f0e52bc5acb64356392ef5d24a31 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Thu, 6 Feb 2025 17:14:37 -0800 Subject: [PATCH 26/53] add topicPrefix --- .../Abstractions/AgentsRegistryState.cs | 2 + .../Abstractions/IGatewayRegistry.cs | 4 +- .../Services/Orleans/RegistryGrain.cs | 79 +++++++++++-------- 3 files changed, 50 insertions(+), 35 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs index 58d6d2a9c11f..aaea55e02478 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs @@ -9,7 +9,9 @@ public class AgentsRegistryState { public ConcurrentDictionary> AgentsToEventsMap { get; set; } = new ConcurrentDictionary>(); public ConcurrentDictionary> AgentsToTopicsMap { get; set; } = []; + public ConcurrentDictionary> AgentsToTopicsPrefixMap { get; set; } = []; public ConcurrentDictionary> TopicToAgentTypesMap { get; set; } = []; + public ConcurrentDictionary> TopicPrefixToAgentTypesMap { get; set; } = []; public ConcurrentDictionary> EventsToAgentTypesMap { get; set; } = []; public ConcurrentDictionary> GuidSubscriptionsMap { get; set; } = []; public ConcurrentDictionary> AgentTypes { get; set; } = []; diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs index f2f86a2e7dbd..3d47696da3ee 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGatewayRegistry.cs @@ -14,7 +14,7 @@ public interface IGatewayRegistry : IRegistry /// /// The ID of the agent. /// A tuple containing the worker and a boolean indicating if it's a new placement. - ValueTask<(IGateway? Worker, bool NewPlacement)> GetOrPlaceAgent(Contracts.AgentId agentId); + ValueTask<(IGateway? Worker, bool NewPlacement)> GetOrPlaceAgent(AgentId agentId); /// /// Removes a worker from the registry. @@ -29,7 +29,7 @@ public interface IGatewayRegistry : IRegistry /// The request containing agent type details. /// The worker to register the agent type with. /// A task representing the asynchronous operation. - ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, IGateway worker); + ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, string clientId, IGateway worker); /// /// Adds a new worker to the registry. diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs index 02433415315c..4d00bbe813a7 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs @@ -37,11 +37,11 @@ public ValueTask> GetSubscribedAndHandlingAgentsAsync(string topic, { agents.AddRange(combo.ToList()); } - // instead of an exact match, we can also check for a prefix match where key starts with the eventType - if (state.State.TopicToAgentTypesMap.Keys.Any(key => key.StartsWith(eventType))) + // instead of an exact match, we can also check for a prefix match from the TopicPrefixToAgentTypesMap + if (state.State.TopicPrefixToAgentTypesMap.Keys.Any(key => key.StartsWith(topic))) { - state.State.TopicToAgentTypesMap.Where( - kvp => kvp.Key.StartsWith(eventType)) + state.State.TopicPrefixToAgentTypesMap.Where( + kvp => kvp.Key.StartsWith(topic)) .SelectMany(kvp => kvp.Value) .Distinct() .ToList() @@ -51,7 +51,6 @@ public ValueTask> GetSubscribedAndHandlingAgentsAsync(string topic, }); } agents = agents.Distinct().ToList(); - return new ValueTask>(agents); } public ValueTask<(IGateway? Worker, bool NewPlacement)> GetOrPlaceAgent(AgentId agentId) @@ -94,7 +93,7 @@ public ValueTask RemoveWorkerAsync(IGateway worker) } return ValueTask.CompletedTask; } - public async ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest registration, IGateway gateway) + public async ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest registration, string clientId, IGateway gateway) { var workerState = GetOrAddWorker(gateway); workerState.SupportedTypes.Add(registration.Type); @@ -166,9 +165,27 @@ public async ValueTask SubscribeAsync(AddSubscriptionRequest subscription) subscription.Subscription.Id = guid; switch (subscription.Subscription.SubscriptionCase) { - //TODO: this doesnt look right case Subscription.SubscriptionOneofCase.TypePrefixSubscription: - break; + { + // add the topic to the set of topics for the agent type + state.State.AgentsToTopicsMap.TryGetValue(subscription.Subscription.TypePrefixSubscription.AgentType, out var topics); + if (topics is null) + { + topics = new HashSet(); + state.State.AgentsToTopicsPrefixMap[subscription.Subscription.TypePrefixSubscription.AgentType] = topics; + } + topics.Add(subscription.Subscription.TypePrefixSubscription.TopicTypePrefix); + + // add the agent type to the set of agent types for the topic + state.State.TopicPrefixToAgentTypesMap.TryGetValue(subscription.Subscription.TypePrefixSubscription.TopicTypePrefix, out var agents); + if (agents is null) + { + agents = new HashSet(); + state.State.TopicPrefixToAgentTypesMap[subscription.Subscription.TypePrefixSubscription.TopicTypePrefix] = agents; + } + agents.Add(subscription.Subscription.TypePrefixSubscription.AgentType); + break; + } case Subscription.SubscriptionOneofCase.TypeSubscription: { // add the topic to the set of topics for the agent type @@ -188,20 +205,19 @@ public async ValueTask SubscribeAsync(AddSubscriptionRequest subscription) state.State.TopicToAgentTypesMap[subscription.Subscription.TypeSubscription.TopicType] = agents; } agents.Add(subscription.Subscription.TypeSubscription.AgentType); - - // add the subscription by Guid - state.State.GuidSubscriptionsMap.TryGetValue(guid, out var existingSubscriptions); - if (existingSubscriptions is null) - { - existingSubscriptions = new HashSet(); - state.State.GuidSubscriptionsMap[guid] = existingSubscriptions; - } - existingSubscriptions.Add(subscription.Subscription); break; } default: throw new InvalidOperationException("Invalid subscription type"); } + // add the subscription by Guid + state.State.GuidSubscriptionsMap.TryGetValue(guid, out var existingSubscriptions); + if (existingSubscriptions is null) + { + existingSubscriptions = new HashSet(); + state.State.GuidSubscriptionsMap[guid] = existingSubscriptions; + } + existingSubscriptions.Add(subscription.Subscription); await state.WriteStateAsync().ConfigureAwait(false); } public async ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request) @@ -227,17 +243,25 @@ public async ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request) // remove the agent type from the set of agent types for the topic state.State.TopicToAgentTypesMap.TryGetValue(subscription.TypeSubscription.TopicType, out var agents); agents?.Remove(subscription.TypeSubscription.AgentType); - - //remove the subscription by Guid - state.State.GuidSubscriptionsMap.TryGetValue(guid, out var existingSubscriptions); - existingSubscriptions?.Remove(subscription); break; } case Subscription.SubscriptionOneofCase.TypePrefixSubscription: - break; + { + // remove the topic from the set of topics for the agent type + state.State.AgentsToTopicsPrefixMap.TryGetValue(subscription.TypePrefixSubscription.AgentType, out var topics); + topics?.Remove(subscription.TypePrefixSubscription.TopicTypePrefix); + + // remove the agent type from the set of agent types for the topic + state.State.TopicPrefixToAgentTypesMap.TryGetValue(subscription.TypePrefixSubscription.TopicTypePrefix, out var agents); + agents?.Remove(subscription.TypePrefixSubscription.AgentType); + break; + } default: throw new InvalidOperationException("Invalid subscription type"); } + //remove the subscription by Guid + state.State.GuidSubscriptionsMap.TryGetValue(guid, out var existingSubscriptions); + existingSubscriptions?.Remove(subscription); } state.State.GuidSubscriptionsMap.Remove(guid, out _); } @@ -253,17 +277,6 @@ public ValueTask> GetSubscriptionsAsync(GetSubscriptionsReque } return new(subscriptions); } - - public ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, string clientId, Contracts.IAgentRuntime worker) - { - throw new NotImplementedException(); - } - - public ValueTask UnregisterAgentTypeAsync(string type, Contracts.IAgentRuntime worker) - { - throw new NotImplementedException(); - } - private sealed class WorkerState { public HashSet SupportedTypes { get; set; } = []; From d16a97887d98cb1fb2ce43b043bdf8b502f478dd Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Thu, 6 Feb 2025 17:17:14 -0800 Subject: [PATCH 27/53] remove unused --- .../Abstractions/IRegistry.cs | 18 ------------------ .../Services/Orleans/RegistryGrain.cs | 2 +- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs index a25437c3714c..2c2d47297982 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs @@ -5,24 +5,6 @@ namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; public interface IRegistry { - //AgentsRegistryState State { get; set; } - /// - /// Registers a new agent type with the specified worker. - /// - /// The request containing agent type details. - /// The connection the client conneted on - /// A task representing the asynchronous operation. - /// removing CancellationToken from here as it is not compatible with Orleans Serialization - ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, string clientId, GrpcWorkerConnection connection); - - /// - /// Unregisters an agent type from the specified worker. - /// - /// The type of the agent to unregister. - /// The connection the client conneted on - /// A task representing the asynchronous operation. - /// removing CancellationToken from here as it is not compatible with Orleans Serialization - ValueTask UnregisterAgentTypeAsync(string type, GrpcWorkerConnection connection); /// /// Gets a list of agents subscribed to and handling the specified topic and event type. diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs index 4d00bbe813a7..b41815591ed8 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs @@ -148,7 +148,7 @@ private WorkerState GetOrAddWorker(IGateway worker) workerState.LastSeen = DateTimeOffset.UtcNow; return workerState; } - + public ValueTask GetCompatibleWorkerAsync(string type) => new(GetCompatibleWorkerCore(type)); private IGateway? GetCompatibleWorkerCore(string type) { if (_supportedAgentTypes.TryGetValue(type, out var workers)) From 759a4da2ed509c778e25fe37b15117ccdb8e3313 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Thu, 6 Feb 2025 17:37:40 -0800 Subject: [PATCH 28/53] update test helpers --- .../Helpers/Grpc/TestGrpcClient.cs | 4 +- .../TestAgent.cs | 45 +++++++++---------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs index bff029c344f5..54f745f313a4 100644 --- a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // TestGrpcClient.cs - -using Microsoft.AutoGen.Contracts; - +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Grpc; internal sealed class TestGrpcClient : IDisposable { diff --git a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs index 55a8ac44453e..0dc8ea01b1f9 100644 --- a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs @@ -1,46 +1,43 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // TestAgent.cs - -using System.Collections.Concurrent; -using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Protobuf; using Microsoft.AutoGen.Core; -using Microsoft.Extensions.DependencyInjection; +using System.Collections.Concurrent; using Microsoft.Extensions.Logging; +using Microsoft.AutoGen.Contracts; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests; -[TopicSubscription("gh-gh-gh")] -public class PBAgent([FromKeyedServices("AgentsMetadata")] AgentsMetadata eventTypes, ILogger? logger = null) - : Agent(eventTypes, logger) - , IHandle - , IHandle +[TypeSubscription("gh-gh-gh")] +public class PBAgent(Contracts.AgentId id, IAgentRuntime runtime, ILogger? logger = null) + : BaseAgent(id, runtime, "Test Agent", logger), + IHandle, + IHandle { - public async Task Handle(NewMessageReceived item, CancellationToken cancellationToken = default) + public async ValueTask HandleAsync(NewMessageReceived item, MessageContext messageContext) { - ReceivedMessages[AgentId.Key] = item.Message; + var key = messageContext.MessageId ?? Guid.NewGuid().ToString(); + ReceivedMessages.AddOrUpdate(key, item.Message, (k, v) => item.Message); var hello = new Hello { Message = item.Message }; - await PublishMessageAsync(hello); + await PublishMessageAsync(hello, new TopicId("gh-gh-gh")); } - public Task Handle(GoodBye item, CancellationToken cancellationToken) + public async ValueTask HandleAsync(GoodBye item, MessageContext context) { _logger.LogInformation($"Received GoodBye message {item.Message}"); - return Task.CompletedTask; } - public static ConcurrentDictionary ReceivedMessages { get; private set; } = new(); } -[TopicSubscription("gh-gh-gh")] -public class GMAgent([FromKeyedServices("AgentsMetadata")] AgentsMetadata eventTypes, ILogger? logger = null) - : Agent(eventTypes, logger) - , IHandle +[TypeSubscription("gh-gh-gh")] +public class GMAgent(Contracts.AgentId id, IAgentRuntime runtime, ILogger? logger = null) + : BaseAgent(id, runtime, "Test Agent", logger), + IHandle { - public async Task Handle(Hello item, CancellationToken cancellationToken) + public async ValueTask HandleAsync(Hello item, MessageContext messageContext) { - _logger.LogInformation($"Received Hello message {item.Message}"); - ReceivedMessages[AgentId.Key] = item.Message; - await PublishMessageAsync(new GoodBye { Message = "" }); + var key = messageContext.MessageId ?? Guid.NewGuid().ToString(); + ReceivedMessages.AddOrUpdate(key, item.Message, (k, v) => item.Message); + await PublishMessageAsync(new GoodBye { Message = "" }, new TopicId("gh-gh-gh")); } - public static ConcurrentDictionary ReceivedMessages { get; private set; } = new(); } From 50007b2bf91fe3e3161ff224cf98344a46f105ca Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Thu, 6 Feb 2025 18:33:52 -0800 Subject: [PATCH 29/53] cleaning up tests --- .../GrpcGatewayServiceTests.cs | 130 ++++++++++++------ .../Helpers/Grpc/TestGrpcClient.cs | 1 - 2 files changed, 85 insertions(+), 46 deletions(-) diff --git a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs index 8bc96dbc0739..8ce1b1f301c7 100644 --- a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs @@ -3,8 +3,8 @@ using FluentAssertions; using Microsoft.AutoGen.Contracts; -using Microsoft.AutoGen.Core; using Microsoft.AutoGen.Protobuf; +using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; using Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Grpc; using Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Orleans; using Microsoft.Extensions.Logging; @@ -45,10 +45,12 @@ public async Task Test_Message_Exchange_Through_Gateway() var service = new GrpcGatewayService(gateway); var client = new TestGrpcClient(); var task = OpenChannel(service: service, client); - await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(PBAgent), client.CallContext.Peer), client.CallContext); - await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(GMAgent), client.CallContext.Peer), client.CallContext); + await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(PBAgent)), client.CallContext); + await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(GMAgent)), client.CallContext); - var inputEvent = new NewMessageReceived { Message = $"Start-{client.CallContext.Peer}" }.ToCloudEvent("gh-gh-gh", "gh-gh-gh"); + //var inputEvent = new NewMessageReceived { Message = $"Start-{client.CallContext.Peer}" }.ToCloudEvent("gh-gh-gh", "gh-gh-gh"); + var newMessage = new NewMessageReceived { Message = $"Start-{client.CallContext.Peer}" }; + var inputEvent = CloudEventExtensions.CreateCloudEvent(Google.Protobuf.WellKnownTypes.Any.Pack(newMessage), new TopicId("gh-gh-gh"), "Tests.Events.NewMessageReceived", null, Guid.NewGuid().ToString()); client.AddMessage(new Message { CloudEvent = inputEvent }); var newMessageReceived = await client.ReadNext(); @@ -58,7 +60,9 @@ public async Task Test_Message_Exchange_Through_Gateway() secondMessage!.CloudEvent.Type.Should().Be(GetFullName(typeof(NewMessageReceived))); // Simulate an agent, by publishing a new message in the request stream - var helloEvent = new Hello { Message = $"Hello test-{client.CallContext.Peer}" }.ToCloudEvent("gh-gh-gh", "gh-gh-gh"); + //var helloEvent = new Hello { Message = $"Hello test-{client.CallContext.Peer}" }.ToCloudEvent("gh-gh-gh", "gh-gh-gh"); + var hello = new Hello { Message = $"Hello test-{client.CallContext.Peer}" }; + var helloEvent = CloudEventExtensions.CreateCloudEvent(Google.Protobuf.WellKnownTypes.Any.Pack(hello), new TopicId("gh-gh-gh"), "Tests.Events.Hello", null, Guid.NewGuid().ToString()); client.AddMessage(new Message { CloudEvent = helloEvent }); var helloMessageReceived = await client.ReadNext(); helloMessageReceived!.CloudEvent.Type.Should().Be(GetFullName(typeof(Hello))); @@ -75,24 +79,12 @@ public async Task Test_RegisterAgent_Should_Succeed() var service = new GrpcGatewayService(gateway); var client = new TestGrpcClient(); var task = OpenChannel(service: service, client); - var response = await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(PBAgent), client.CallContext.Peer), client.CallContext); - response.Success.Should().BeTrue(); + var response = await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(PBAgent)), client.CallContext); + response.GetType().Should().Be(typeof(RegisterAgentTypeResponse)); client.Dispose(); await task; } - [Fact] - public async Task Test_RegisterAgent_Should_Fail_For_Wrong_ConnectionId() - { - var logger = Mock.Of>(); - var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); - var service = new GrpcGatewayService(gateway); - var client = new TestGrpcClient(); - var response = await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(PBAgent), "faulty_connection_id"), client.CallContext); - response.Success.Should().BeFalse(); - client.Dispose(); - } - [Fact] public async Task Test_SaveState() { @@ -100,7 +92,7 @@ public async Task Test_SaveState() var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); var service = new GrpcGatewayService(gateway); var callContext = TestServerCallContext.Create(); - var response = await service.SaveState(new AgentState { AgentId = new AgentId { Key = "Test", Type = "test" } }, callContext); + var response = await service.SaveState(new AgentState { AgentId = new Protobuf.AgentId { Key = "Test", Type = "test" } }, callContext); response.Should().NotBeNull(); } @@ -111,19 +103,18 @@ public async Task Test_GetState() var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); var service = new GrpcGatewayService(gateway); var callContext = TestServerCallContext.Create(); - var response = await service.GetState(new AgentId { Key = "", Type = "" }, callContext); + var response = await service.GetState(new Protobuf.AgentId { Key = "", Type = "" }, callContext); response.Should().NotBeNull(); } - private async Task CreateRegistrationRequest(GrpcGatewayService service, Type type, string requestId) + private async Task CreateRegistrationRequest(GrpcGatewayService service, Type type) { var registration = new RegisterAgentTypeRequest { Type = type.Name, - RequestId = requestId }; var assembly = type.Assembly; - var eventTypes = ReflectionHelper.GetAgentsMetadata(assembly); + var eventTypes = Abstractions.ReflectionHelper.GetAgentsMetadata(assembly); var events = eventTypes.GetEventsForAgent(type)?.ToList(); var topics = eventTypes.GetTopicsForAgent(type)?.ToList(); if (events is not null && topics is not null) { events.AddRange(topics); } @@ -133,22 +124,20 @@ private async Task CreateRegistrationRequest(GrpcGatew { foreach (var e in events) { - var subscriptionRequest = new Message + var subscriptionRequest = new AddSubscriptionRequest { - AddSubscriptionRequest = new AddSubscriptionRequest + Subscription = new Subscription { - RequestId = Guid.NewGuid().ToString(), - Subscription = new Subscription + Id = Guid.NewGuid().ToString(), + TypeSubscription = new Protobuf.TypeSubscription { - TypeSubscription = new TypeSubscription - { - AgentType = type.Name, - TopicType = type.Name + "." + e - } + AgentType = type.Name, + TopicType = type.Name + "." + e } } + }; - await service.AddSubscription(subscriptionRequest.AddSubscriptionRequest, client.CallContext); + await service.AddSubscription(subscriptionRequest, client.CallContext); } } var topicTypes = type.GetCustomAttributes(typeof(TopicSubscriptionAttribute), true).Cast().Select(t => t.Topic).ToList(); @@ -156,22 +145,20 @@ private async Task CreateRegistrationRequest(GrpcGatew { foreach (var topicType in topicTypes) { - var subscriptionRequest = new Message + var subscriptionRequest = new AddSubscriptionRequest { - AddSubscriptionRequest = new AddSubscriptionRequest + Subscription = new Subscription { - RequestId = Guid.NewGuid().ToString(), - Subscription = new Subscription + Id = Guid.NewGuid().ToString(), + TypeSubscription = new Protobuf.TypeSubscription { - TypeSubscription = new TypeSubscription - { - AgentType = type.Name, - TopicType = topicType - } + AgentType = type.Name, + TopicType = type.Name } } + }; - await service.AddSubscription(subscriptionRequest.AddSubscriptionRequest, client.CallContext); + await service.AddSubscription(subscriptionRequest, client.CallContext); } } return registration; @@ -183,6 +170,59 @@ private Task OpenChannel(GrpcGatewayService service, TestGrpcClient client) } private string GetFullName(Type type) { - return ReflectionHelper.GetMessageDescriptor(type)!.FullName; + return Abstractions.ReflectionHelper.GetMessageDescriptor(type)!.FullName; + } + /// duplicate code here because I could not get InternalsVisibleTo to work + internal static class Constants + { + public const string DATA_CONTENT_TYPE_PROTOBUF_VALUE = "application/x-protobuf"; + public const string DATA_CONTENT_TYPE_JSON_VALUE = "application/json"; + public const string DATA_CONTENT_TYPE_TEXT_VALUE = "text/plain"; + + public const string DATA_CONTENT_TYPE_ATTR = "datacontenttype"; + public const string DATA_SCHEMA_ATTR = "dataschema"; + public const string AGENT_SENDER_TYPE_ATTR = "agagentsendertype"; + public const string AGENT_SENDER_KEY_ATTR = "agagentsenderkey"; + + public const string MESSAGE_KIND_ATTR = "agmsgkind"; + public const string MESSAGE_KIND_VALUE_PUBLISH = "publish"; + public const string MESSAGE_KIND_VALUE_RPC_REQUEST = "rpc_request"; + public const string MESSAGE_KIND_VALUE_RPC_RESPONSE = "rpc_response"; + } + internal static class CloudEventExtensions + { + // Convert an ISubscrptionDefinition to a Protobuf Subscription + internal static CloudEvent CreateCloudEvent(Google.Protobuf.WellKnownTypes.Any payload, TopicId topic, string dataType, Contracts.AgentId? sender, string messageId) + { + var attributes = new Dictionary + { + { + Constants.DATA_CONTENT_TYPE_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = Constants.DATA_CONTENT_TYPE_PROTOBUF_VALUE } + }, + { + Constants.DATA_SCHEMA_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = dataType } + }, + { + Constants.MESSAGE_KIND_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = Constants.MESSAGE_KIND_VALUE_PUBLISH } + } + }; + + if (sender != null) + { + var senderNonNull = (Contracts.AgentId)sender; + attributes.Add(Constants.AGENT_SENDER_TYPE_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = senderNonNull.Type }); + attributes.Add(Constants.AGENT_SENDER_KEY_ATTR, new CloudEvent.Types.CloudEventAttributeValue { CeString = senderNonNull.Key }); + } + + return new CloudEvent + { + ProtoData = payload, + Type = topic.Type, + Source = topic.Source, + Id = messageId, + Attributes = { attributes } + }; + + } } } diff --git a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs index 54f745f313a4..7655191e77d1 100644 --- a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs @@ -26,7 +26,6 @@ public void AddMessage(Message message) { RequestStream.AddMessage(message); } - public void Dispose() { CallContextCancellation.Cancel(); From c8d5d6b60ad0ba67174959f0d3d0a6bac425194e Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Thu, 6 Feb 2025 22:12:07 -0800 Subject: [PATCH 30/53] tests working --- .../Abstractions/AgentsMetadata.cs | 21 ++++++++- .../Abstractions/IConnection.cs | 7 +++ .../Abstractions/IGateway.cs | 2 +- .../Abstractions/ReflectionHelper.cs | 2 +- .../Services/Grpc/GrpcGateway.cs | 4 +- .../Services/Grpc/GrpcWorkerConnection.cs | 3 +- .../GrpcGatewayServiceTests.cs | 45 ++++++++++++++++--- .../Helpers/Grpc/TestGrpcClient.cs | 2 +- .../Helpers/Grpc/TestServerCallContext.cs | 1 + .../TestAgent.cs | 6 ++- 10 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IConnection.cs diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs index 4719fb7a25a1..abbd43a89c25 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs @@ -17,12 +17,21 @@ public sealed class AgentsMetadata /// The type registry containing protobuf type information. /// A dictionary mapping event names to their corresponding types. /// A dictionary mapping types to a set of event names associated with those types. - public AgentsMetadata(TypeRegistry typeRegistry, Dictionary types, Dictionary> eventsMap, Dictionary> topicsMap) + /// A dictionary mapping types to a set of topics associated with those types. + /// A dictionary mapping types to a set of topics associated with those types. + /// + public AgentsMetadata( + TypeRegistry typeRegistry, + Dictionary types, + Dictionary> eventsMap, + Dictionary> topicsMap, + Dictionary> topicsPrefixMap) { TypeRegistry = typeRegistry; _types = new(types); _eventsMap = new(eventsMap); _topicsMap = new(topicsMap); + _topicsPrefixMap = new(topicsPrefixMap); } /// @@ -34,6 +43,7 @@ public AgentsMetadata(TypeRegistry typeRegistry, Dictionary types, private ConcurrentDictionary> _eventsMap; private ConcurrentDictionary> _topicsMap; + private ConcurrentDictionary> _topicsPrefixMap; /// /// Checks if a given type handles a specific event name. @@ -81,5 +91,14 @@ public bool CheckIfTypeHandles(Type type, string eventName) } return null; } + + public HashSet? GetTopicsPrefixForAgent(Type type) + { + if (_topicsPrefixMap.TryGetValue(type, out var topics)) + { + return topics; + } + return null; + } } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IConnection.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IConnection.cs new file mode 100644 index 000000000000..a5044ff69b4f --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IConnection.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IConnection.cs + +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; +public interface IConnection +{ +} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs index 32e25bce564e..c573649d805c 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs @@ -16,5 +16,5 @@ public interface IGateway : IGrainObserver ValueTask SubscribeAsync(AddSubscriptionRequest request); ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request); ValueTask> GetSubscriptionsAsync(GetSubscriptionsRequest request); - Task WriteResponseAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent); + Task WriteResponseAsync(IConnection connection, CloudEvent cloudEvent); } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs index 1d4d060e2b70..0a7534a397de 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs @@ -54,7 +54,7 @@ public static AgentsMetadata GetAgentsMetadata(params Assembly[] assemblies) .Where(type => IsSubclassOfGeneric(type, typeof(BaseAgent)) && !type.IsAbstract) .Select(t => (t, t.GetCustomAttributes().Select(a => a.Topic).ToHashSet())) .ToDictionary(item => item.t, item => item.Item2); - return new AgentsMetadata(typeRegistry, types, eventsMap, topicsMap); + return new AgentsMetadata(typeRegistry, types, eventsMap, topicsMap, topicsPrefixMap); } /// diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs index 062d044d0e4d..a6bba74e90f8 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs @@ -480,8 +480,8 @@ public async Task WriteResponseAsync(GrpcWorkerConnection connection, CloudEvent /// The worker connection. /// The cloud event. /// A task that represents the asynchronous operation. - public async Task WriteResponseAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent) + public async Task WriteResponseAsync(IConnection connection, CloudEvent cloudEvent) { - await WriteResponseAsync(connection, cloudEvent, default).ConfigureAwait(false); + await WriteResponseAsync((GrpcWorkerConnection)connection, cloudEvent, default).ConfigureAwait(false); } } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs index 8559a0218430..0cdfc9a2898b 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcWorkerConnection.cs @@ -3,10 +3,11 @@ using System.Threading.Channels; using Grpc.Core; using Microsoft.AutoGen.Protobuf; +using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; namespace Microsoft.AutoGen.RuntimeGateway.Grpc; -public sealed class GrpcWorkerConnection : IAsyncDisposable +public sealed class GrpcWorkerConnection : IAsyncDisposable, IConnection { private static long s_nextConnectionId; private Task _readTask = Task.CompletedTask; diff --git a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs index 8ce1b1f301c7..b0e2300fd183 100644 --- a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs @@ -3,8 +3,8 @@ using FluentAssertions; using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Core; using Microsoft.AutoGen.Protobuf; -using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; using Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Grpc; using Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.Helpers.Orleans; using Microsoft.Extensions.Logging; @@ -50,7 +50,13 @@ public async Task Test_Message_Exchange_Through_Gateway() //var inputEvent = new NewMessageReceived { Message = $"Start-{client.CallContext.Peer}" }.ToCloudEvent("gh-gh-gh", "gh-gh-gh"); var newMessage = new NewMessageReceived { Message = $"Start-{client.CallContext.Peer}" }; - var inputEvent = CloudEventExtensions.CreateCloudEvent(Google.Protobuf.WellKnownTypes.Any.Pack(newMessage), new TopicId("gh-gh-gh"), "Tests.Events.NewMessageReceived", null, Guid.NewGuid().ToString()); + var eventType = GetFullName(typeof(NewMessageReceived)); + var inputEvent = CloudEventExtensions.CreateCloudEvent( + Google.Protobuf.WellKnownTypes.Any.Pack(newMessage), + new TopicId(eventType, "gh-gh-gh"), + eventType, + null, + Guid.NewGuid().ToString()); client.AddMessage(new Message { CloudEvent = inputEvent }); var newMessageReceived = await client.ReadNext(); @@ -62,7 +68,14 @@ public async Task Test_Message_Exchange_Through_Gateway() // Simulate an agent, by publishing a new message in the request stream //var helloEvent = new Hello { Message = $"Hello test-{client.CallContext.Peer}" }.ToCloudEvent("gh-gh-gh", "gh-gh-gh"); var hello = new Hello { Message = $"Hello test-{client.CallContext.Peer}" }; - var helloEvent = CloudEventExtensions.CreateCloudEvent(Google.Protobuf.WellKnownTypes.Any.Pack(hello), new TopicId("gh-gh-gh"), "Tests.Events.Hello", null, Guid.NewGuid().ToString()); + var eventTypeHello = GetFullName(typeof(Hello)); + var helloEvent = CloudEventExtensions.CreateCloudEvent( + Google.Protobuf.WellKnownTypes.Any.Pack(message: hello), + new TopicId(eventTypeHello, "gh-gh-gh"), + eventTypeHello, + null, + Guid.NewGuid().ToString() + ); client.AddMessage(new Message { CloudEvent = helloEvent }); var helloMessageReceived = await client.ReadNext(); helloMessageReceived!.CloudEvent.Type.Should().Be(GetFullName(typeof(Hello))); @@ -117,6 +130,7 @@ private async Task CreateRegistrationRequest(GrpcGatew var eventTypes = Abstractions.ReflectionHelper.GetAgentsMetadata(assembly); var events = eventTypes.GetEventsForAgent(type)?.ToList(); var topics = eventTypes.GetTopicsForAgent(type)?.ToList(); + var topicsPrefix = eventTypes.GetTopicsPrefixForAgent(type)?.ToList(); if (events is not null && topics is not null) { events.AddRange(topics); } var client = new TestGrpcClient(); @@ -140,7 +154,7 @@ private async Task CreateRegistrationRequest(GrpcGatew await service.AddSubscription(subscriptionRequest, client.CallContext); } } - var topicTypes = type.GetCustomAttributes(typeof(TopicSubscriptionAttribute), true).Cast().Select(t => t.Topic).ToList(); + var topicTypes = type.GetCustomAttributes(typeof(TypeSubscriptionAttribute), true).Cast().Select(t => t.Topic).ToList(); if (topicTypes != null) { foreach (var topicType in topicTypes) @@ -153,7 +167,28 @@ private async Task CreateRegistrationRequest(GrpcGatew TypeSubscription = new Protobuf.TypeSubscription { AgentType = type.Name, - TopicType = type.Name + TopicType = topicType + } + } + + }; + await service.AddSubscription(subscriptionRequest, client.CallContext); + } + } + var topicPrefixTypes = type.GetCustomAttributes(typeof(TypePrefixSubscriptionAttribute), true).Cast().Select(t => t.Topic).ToList(); + if (topicPrefixTypes != null) + { + foreach (var topicType in topicPrefixTypes) + { + var subscriptionRequest = new AddSubscriptionRequest + { + Subscription = new Subscription + { + Id = Guid.NewGuid().ToString(), + TypePrefixSubscription = new Protobuf.TypePrefixSubscription + { + AgentType = type.Name, + TopicTypePrefix = topicType } } diff --git a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs index 7655191e77d1..8c325678cbf2 100644 --- a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs @@ -7,7 +7,6 @@ internal sealed class TestGrpcClient : IDisposable public TestAsyncStreamReader RequestStream { get; } public TestServerStreamWriter ResponseStream { get; } public TestServerCallContext CallContext { get; } - private CancellationTokenSource CallContextCancellation = new(); public TestGrpcClient() { @@ -33,3 +32,4 @@ public void Dispose() ResponseStream.Dispose(); } } + diff --git a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs index 1f0c2e7f6bc9..491eb112b4bb 100644 --- a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs @@ -68,6 +68,7 @@ protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders) public static TestServerCallContext Create(Metadata? requestHeaders = null, CancellationToken cancellationToken = default) { + requestHeaders ??= new Metadata() { { "client-id", Guid.NewGuid().ToString() } }; return new TestServerCallContext(requestHeaders ?? new Metadata(), cancellationToken); } } diff --git a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs index 0dc8ea01b1f9..44818661af4c 100644 --- a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs @@ -19,7 +19,8 @@ public async ValueTask HandleAsync(NewMessageReceived item, MessageContext messa var key = messageContext.MessageId ?? Guid.NewGuid().ToString(); ReceivedMessages.AddOrUpdate(key, item.Message, (k, v) => item.Message); var hello = new Hello { Message = item.Message }; - await PublishMessageAsync(hello, new TopicId("gh-gh-gh")); + var typeFullName = typeof(Hello).FullName ?? throw new InvalidOperationException("Type full name is null"); + await PublishMessageAsync(hello, new TopicId(typeFullName), "gh-gh-gh"); } public async ValueTask HandleAsync(GoodBye item, MessageContext context) { @@ -37,7 +38,8 @@ public async ValueTask HandleAsync(Hello item, MessageContext messageContext) { var key = messageContext.MessageId ?? Guid.NewGuid().ToString(); ReceivedMessages.AddOrUpdate(key, item.Message, (k, v) => item.Message); - await PublishMessageAsync(new GoodBye { Message = "" }, new TopicId("gh-gh-gh")); + var typeFullName = typeof(GoodBye).FullName ?? throw new InvalidOperationException("Type full name is null"); + await PublishMessageAsync(new GoodBye { Message = "" }, new TopicId(typeFullName, "gh-gh-gh")); } public static ConcurrentDictionary ReceivedMessages { get; private set; } = new(); } From 3b5149512ca0bc763cf3c73b30d0f9532c0fd807 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Thu, 6 Feb 2025 22:37:09 -0800 Subject: [PATCH 31/53] add back the test projects --- dotnet/AutoGen.sln | 21 +++++++++++++++++++ .../HelloAgent.AppHost.csproj | 2 +- ...Microsoft.AutoGen.Integration.Tests.csproj | 4 ++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln index 90c9d219ccce..fc66102e8678 100644 --- a/dotnet/AutoGen.sln +++ b/dotnet/AutoGen.sln @@ -126,6 +126,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.RuntimeGa EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.RuntimeGateway.Grpc.Tests", "test\Microsoft.AutoGen.RuntimeGateway.Grpc.Tests\Microsoft.AutoGen.RuntimeGateway.Grpc.Tests.csproj", "{CDD859F3-1B60-4ECE-8472-54DF8EFCA682}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Integration.Tests", "test\Microsoft.AutoGen.Integration.Tests\Microsoft.AutoGen.Integration.Tests.csproj", "{7A11022E-4E5D-4A4A-AADF-E715C2ECF800}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.AgentHost", "src\Microsoft.AutoGen\AgentHost\Microsoft.AutoGen.AgentHost.csproj", "{50C2E8D5-68AB-45A3-B96F-355E1F8AC039}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hello.AppHost", "samples\Hello\Hello.AppHost\Hello.AppHost.csproj", "{B8E77E57-C983-4EEA-9589-906271486D80}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -330,6 +336,18 @@ Global {CDD859F3-1B60-4ECE-8472-54DF8EFCA682}.Debug|Any CPU.Build.0 = Debug|Any CPU {CDD859F3-1B60-4ECE-8472-54DF8EFCA682}.Release|Any CPU.ActiveCfg = Release|Any CPU {CDD859F3-1B60-4ECE-8472-54DF8EFCA682}.Release|Any CPU.Build.0 = Release|Any CPU + {7A11022E-4E5D-4A4A-AADF-E715C2ECF800}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A11022E-4E5D-4A4A-AADF-E715C2ECF800}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A11022E-4E5D-4A4A-AADF-E715C2ECF800}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A11022E-4E5D-4A4A-AADF-E715C2ECF800}.Release|Any CPU.Build.0 = Release|Any CPU + {50C2E8D5-68AB-45A3-B96F-355E1F8AC039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50C2E8D5-68AB-45A3-B96F-355E1F8AC039}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50C2E8D5-68AB-45A3-B96F-355E1F8AC039}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50C2E8D5-68AB-45A3-B96F-355E1F8AC039}.Release|Any CPU.Build.0 = Release|Any CPU + {B8E77E57-C983-4EEA-9589-906271486D80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8E77E57-C983-4EEA-9589-906271486D80}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8E77E57-C983-4EEA-9589-906271486D80}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8E77E57-C983-4EEA-9589-906271486D80}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -387,6 +405,9 @@ Global {23A028D3-5EB1-4FA0-9CD1-A1340B830579} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} {BE420A71-7615-4DFD-BE94-9409397949F1} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} {CDD859F3-1B60-4ECE-8472-54DF8EFCA682} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} + {7A11022E-4E5D-4A4A-AADF-E715C2ECF800} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} + {50C2E8D5-68AB-45A3-B96F-355E1F8AC039} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} + {B8E77E57-C983-4EEA-9589-906271486D80} = {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B} diff --git a/dotnet/test/Microsoft.AutoGen.Integration.Tests.AppHosts/HelloAgent.AppHost/HelloAgent.AppHost.csproj b/dotnet/test/Microsoft.AutoGen.Integration.Tests.AppHosts/HelloAgent.AppHost/HelloAgent.AppHost.csproj index 1442ebe3d05d..441d48d18cb5 100644 --- a/dotnet/test/Microsoft.AutoGen.Integration.Tests.AppHosts/HelloAgent.AppHost/HelloAgent.AppHost.csproj +++ b/dotnet/test/Microsoft.AutoGen.Integration.Tests.AppHosts/HelloAgent.AppHost/HelloAgent.AppHost.csproj @@ -16,6 +16,6 @@ - + diff --git a/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj b/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj index 320aa44deccb..2ef1763c3db5 100644 --- a/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj +++ b/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj @@ -28,8 +28,8 @@ - - + + From df4a3b8479b6c819a9e96baeafac445fd4734c7b Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Thu, 6 Feb 2025 22:50:20 -0800 Subject: [PATCH 32/53] adding back Agents folder --- .../Agents/AIAgent/InferenceAgent.cs | 44 +++++++++++ .../IOAgent/ConsoleAgent/IHandleConsole.cs | 67 +++++++++++++++++ .../Agents/IOAgent/FileAgent/IHandleFileIO.cs | 75 +++++++++++++++++++ .../Agents/IOAgent/IProcessIO.cs | 23 ++++++ .../Agents/Microsoft.AutoGen.Agents.csproj | 24 ++++++ .../Agents/protos/agent_events.proto | 43 +++++++++++ 6 files changed, 276 insertions(+) create mode 100644 dotnet/src/Microsoft.AutoGen/Agents/AIAgent/InferenceAgent.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Agents/IOAgent/FileAgent/IHandleFileIO.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Agents/IOAgent/IProcessIO.cs create mode 100644 dotnet/src/Microsoft.AutoGen/Agents/Microsoft.AutoGen.Agents.csproj create mode 100644 dotnet/src/Microsoft.AutoGen/Agents/protos/agent_events.proto diff --git a/dotnet/src/Microsoft.AutoGen/Agents/AIAgent/InferenceAgent.cs b/dotnet/src/Microsoft.AutoGen/Agents/AIAgent/InferenceAgent.cs new file mode 100644 index 000000000000..d3dc100012eb --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Agents/AIAgent/InferenceAgent.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// InferenceAgent.cs +using Google.Protobuf; +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Core; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.Logging; +namespace Microsoft.AutoGen.Agents; +/// +/// Base class for inference agents using the Microsoft.Extensions.AI library. +/// +/// +/// +/// +/// +/// +/// +public abstract class InferenceAgent( + AgentId id, + IAgentRuntime runtime, + string name, + ILogger>? logger, + IChatClient client) + : BaseAgent(id, runtime, name, logger) + where T : IMessage, new() +{ + protected IChatClient ChatClient { get; } = client; + private ILogger>? Logger => _logger as ILogger>; + private Task CompleteAsync( + IList chatMessages, + ChatOptions? options = null, + CancellationToken cancellationToken = default) + { + return ChatClient.CompleteAsync(chatMessages, options, cancellationToken); + } + private IAsyncEnumerable CompleteStreamingAsync( + IList chatMessages, + ChatOptions? options = null, + CancellationToken cancellationToken = default) + { + return ChatClient.CompleteStreamingAsync(chatMessages, options, cancellationToken); + } + +} diff --git a/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs new file mode 100644 index 000000000000..1c2a0c3de226 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IHandleConsole.cs +using Google.Protobuf; +using Microsoft.AutoGen.Contracts; + +namespace Microsoft.AutoGen.Agents; +/// +/// Default interface methods for an event handler for Input and Output that writes or reads from the console +/// Can be used inside your agents by inheriting from this interface +/// public class MyAgent : BaseAgent, IHandleConsole +/// +public interface IHandleConsole : IHandle, IHandle, IProcessIO +{ + /// + /// Prototype for Publish Message Async method + /// + /// + /// + /// + /// + /// + /// ValueTask + ValueTask PublishMessageAsync(T message, TopicId topic, string? messageId, CancellationToken token = default) where T : IMessage; + + /// + /// Receives events of type Output and writes them to the console + /// then runs the ProcessOutputAsync method which you should implement in your agent + /// + /// + /// + /// + /// ValueTask + async ValueTask IHandle.HandleAsync(Output item, MessageContext messageContext, CancellationToken? cancellationToken) + { + // Assuming item has a property `Message` that we want to write to the console + Console.WriteLine(item.Message); + await ProcessOutputAsync(item.Message); + + var evt = new OutputWritten + { + Route = "console" + }; + await PublishMessageAsync(evt, new TopicId("OutputWritten"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Receives events of type Input and reads from the console, then runs the ProcessInputAsync method + /// which you should implement in your agent + /// + /// + /// + /// + /// + async ValueTask IHandle.HandleAsync(Input item, MessageContext messageContext, CancellationToken? cancellationToken) + { + Console.WriteLine("Please enter input:"); + string content = Console.ReadLine() ?? string.Empty; + + await ProcessInputAsync(content); + + var evt = new InputProcessed + { + Route = "console" + }; + await PublishMessageAsync(evt, new TopicId("InputProcessed"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + } +} diff --git a/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/FileAgent/IHandleFileIO.cs b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/FileAgent/IHandleFileIO.cs new file mode 100644 index 000000000000..1982929658e6 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/FileAgent/IHandleFileIO.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IHandleFileIO.cs + +using Google.Protobuf; +using Microsoft.AutoGen.Contracts; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AutoGen.Agents; +/// +/// Default interface methods for an event handler for Input and Output that writes or reads from a file +/// Can be used inside your agents by inheriting from this interface +/// public class MyAgent : BaseAgent, IHandleFileIO +/// +public interface IHandleFileIO : IHandle, IHandle, IProcessIO +{ + // A Logger instance to log messages + ILogger LogTarget { get; } + // The path to the input file + string InputPath { get; } + // The path to the output file + string OutputPath { get; } + // The route of the agent (used in the post-process events) + const string Route = "Microsoft.AutoGen.Agents.IHandleFileIO"; + + /// + /// Prototype for Publish Message Async method + /// + /// + /// + /// + /// + /// + /// ValueTask + ValueTask PublishMessageAsync(T message, TopicId topic, string? messageId, CancellationToken token = default) where T : IMessage; + async ValueTask IHandle.HandleAsync(Input item, MessageContext messageContext, CancellationToken? cancellationToken) + { + + // validate that the file exists + if (!File.Exists(InputPath)) + { + var errorMessage = $"File not found: {InputPath}"; + LogTarget.LogError(errorMessage); + //publish IOError event + var err = new IOError + { + Message = errorMessage + }; + await PublishMessageAsync(err, new TopicId("IOError"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + return; + } + string content; + using (var reader = new StreamReader(item.Message)) + { + content = await reader.ReadToEndAsync(cancellationToken ?? CancellationToken.None); + } + await ProcessInputAsync(content); + var evt = new InputProcessed + { + Route = Route + }; + await PublishMessageAsync(evt, new TopicId("InputProcessed"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + } + async ValueTask IHandle.HandleAsync(Output item, MessageContext messageContext, CancellationToken? cancellationToken) + { + using (var writer = new StreamWriter(OutputPath, append: true)) + { + await writer.WriteLineAsync(item.Message); + } + var evt = new OutputWritten + { + Route = Route + }; + await PublishMessageAsync(evt, new TopicId("OutputWritten"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + } +} diff --git a/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/IProcessIO.cs b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/IProcessIO.cs new file mode 100644 index 000000000000..e348f3e1ca71 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/IProcessIO.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// IProcessIO.cs + +namespace Microsoft.AutoGen.Agents; + +/// +/// Default Interface methods for processing input and output shared by IOAgents that should be implemented in your agent +/// +public interface IProcessIO +{ + /// + /// Implement this method in your agent to process the input + /// + /// + /// Task + static Task ProcessOutputAsync(string message) { return Task.CompletedTask; } + /// + /// Implement this method in your agent to process the output + /// + /// + /// Task + static Task ProcessInputAsync(string message) { return Task.FromResult(message); } +} diff --git a/dotnet/src/Microsoft.AutoGen/Agents/Microsoft.AutoGen.Agents.csproj b/dotnet/src/Microsoft.AutoGen/Agents/Microsoft.AutoGen.Agents.csproj new file mode 100644 index 000000000000..5032e95a12a0 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Agents/Microsoft.AutoGen.Agents.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/dotnet/src/Microsoft.AutoGen/Agents/protos/agent_events.proto b/dotnet/src/Microsoft.AutoGen/Agents/protos/agent_events.proto new file mode 100644 index 000000000000..414d79f9678c --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/Agents/protos/agent_events.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +package agents; + +option csharp_namespace = "Microsoft.AutoGen.Agents"; +message TextMessage { + string textMessage = 1; + string source = 2; +} +message Input { + string message = 1; +} +message InputProcessed { + string route = 1; +} +message Output { + string message = 1; +} +message OutputWritten { + string route = 1; +} +message IOError { + string message = 1; +} +message NewMessageReceived { + string message = 1; +} +message ResponseGenerated { + string response = 1; +} +message GoodBye { + string message = 1; +} +message MessageStored { + string message = 1; +} +message ConversationClosed { + string user_id = 1; + string user_message = 2; +} +message Shutdown { + string message = 1; +} From e825443f5920164d7353dc6164271abed74c605a Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Thu, 6 Feb 2025 23:05:46 -0800 Subject: [PATCH 33/53] adding back agents package and agent_events.proto used by xlang integration sample --- dotnet/AutoGen.sln | 22 +++++++++++++------ .../IOAgent/ConsoleAgent/IHandleConsole.cs | 10 ++++----- .../Agents/IOAgent/FileAgent/IHandleFileIO.cs | 12 +++++----- python/pyproject.toml | 2 +- .../protos/agent_events_pb2.py | 4 ++-- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln index fc66102e8678..c49229c0640b 100644 --- a/dotnet/AutoGen.sln +++ b/dotnet/AutoGen.sln @@ -132,6 +132,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.AgentHost EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hello.AppHost", "samples\Hello\Hello.AppHost\Hello.AppHost.csproj", "{B8E77E57-C983-4EEA-9589-906271486D80}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.AutoGen", "Microsoft.AutoGen", "{81BA12F2-2D2F-42C1-AF83-FBDAA1A78A45}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Agents", "src\Microsoft.AutoGen\Agents\Microsoft.AutoGen.Agents.csproj", "{EF954ED3-87D5-40F1-8557-E7179F43EA0E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -310,16 +314,14 @@ Global {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D}.Release|Any CPU.Build.0 = Release|Any CPU - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.CoreOnly|Any CPU.ActiveCfg = Debug|Any CPU - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.CoreOnly|Any CPU.Build.0 = Debug|Any CPU - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Release|Any CPU.Build.0 = Release|Any CPU {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Release|Any CPU.ActiveCfg = Release|Any CPU {AAD593FE-A49B-425E-A9FE-A0022CD25E3D}.Release|Any CPU.Build.0 = Release|Any CPU + {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D83C6DB-ACEA-48F3-959F-145CCD2EE135}.Release|Any CPU.Build.0 = Release|Any CPU {C3740DF1-18B1-4607-81E4-302F0308C848}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C3740DF1-18B1-4607-81E4-302F0308C848}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3740DF1-18B1-4607-81E4-302F0308C848}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -348,6 +350,10 @@ Global {B8E77E57-C983-4EEA-9589-906271486D80}.Debug|Any CPU.Build.0 = Debug|Any CPU {B8E77E57-C983-4EEA-9589-906271486D80}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8E77E57-C983-4EEA-9589-906271486D80}.Release|Any CPU.Build.0 = Release|Any CPU + {EF954ED3-87D5-40F1-8557-E7179F43EA0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF954ED3-87D5-40F1-8557-E7179F43EA0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF954ED3-87D5-40F1-8557-E7179F43EA0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF954ED3-87D5-40F1-8557-E7179F43EA0E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -398,9 +404,9 @@ Global {EAFFE339-26CB-4019-991D-BCCE8E7D33A1} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} {58AD8E1D-83BD-4950-A324-1A20677D78D9} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} {70A8D4B5-D0A6-4098-A6F3-6ED274B65E7D} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} - {3D83C6DB-ACEA-48F3-959F-145CCD2EE135} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} {AAD593FE-A49B-425E-A9FE-A0022CD25E3D} = {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} + {3D83C6DB-ACEA-48F3-959F-145CCD2EE135} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} {C3740DF1-18B1-4607-81E4-302F0308C848} = {CE0AA8D5-12B8-4628-9589-DAD8CB0DDCF6} {23A028D3-5EB1-4FA0-9CD1-A1340B830579} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} {BE420A71-7615-4DFD-BE94-9409397949F1} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} @@ -408,6 +414,8 @@ Global {7A11022E-4E5D-4A4A-AADF-E715C2ECF800} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} {50C2E8D5-68AB-45A3-B96F-355E1F8AC039} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} {B8E77E57-C983-4EEA-9589-906271486D80} = {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} + {81BA12F2-2D2F-42C1-AF83-FBDAA1A78A45} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} + {EF954ED3-87D5-40F1-8557-E7179F43EA0E} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B} diff --git a/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs index 1c2a0c3de226..651be87314e0 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/ConsoleAgent/IHandleConsole.cs @@ -28,9 +28,8 @@ public interface IHandleConsole : IHandle, IHandle, IProcessIO /// /// /// - /// /// ValueTask - async ValueTask IHandle.HandleAsync(Output item, MessageContext messageContext, CancellationToken? cancellationToken) + async ValueTask IHandle.HandleAsync(Output item, MessageContext messageContext) { // Assuming item has a property `Message` that we want to write to the console Console.WriteLine(item.Message); @@ -40,7 +39,7 @@ async ValueTask IHandle.HandleAsync(Output item, MessageContext messageC { Route = "console" }; - await PublishMessageAsync(evt, new TopicId("OutputWritten"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + await PublishMessageAsync(evt, new TopicId("OutputWritten"), null, token: CancellationToken.None).ConfigureAwait(false); } /// @@ -49,9 +48,8 @@ async ValueTask IHandle.HandleAsync(Output item, MessageContext messageC /// /// /// - /// /// - async ValueTask IHandle.HandleAsync(Input item, MessageContext messageContext, CancellationToken? cancellationToken) + async ValueTask IHandle.HandleAsync(Input item, MessageContext messageContext) { Console.WriteLine("Please enter input:"); string content = Console.ReadLine() ?? string.Empty; @@ -62,6 +60,6 @@ async ValueTask IHandle.HandleAsync(Input item, MessageContext messageCon { Route = "console" }; - await PublishMessageAsync(evt, new TopicId("InputProcessed"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + await PublishMessageAsync(evt, new TopicId("InputProcessed"), null, token: CancellationToken.None).ConfigureAwait(false); } } diff --git a/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/FileAgent/IHandleFileIO.cs b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/FileAgent/IHandleFileIO.cs index 1982929658e6..b3d670004e19 100644 --- a/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/FileAgent/IHandleFileIO.cs +++ b/dotnet/src/Microsoft.AutoGen/Agents/IOAgent/FileAgent/IHandleFileIO.cs @@ -32,7 +32,7 @@ public interface IHandleFileIO : IHandle, IHandle, IProcessIO /// /// ValueTask ValueTask PublishMessageAsync(T message, TopicId topic, string? messageId, CancellationToken token = default) where T : IMessage; - async ValueTask IHandle.HandleAsync(Input item, MessageContext messageContext, CancellationToken? cancellationToken) + async ValueTask IHandle.HandleAsync(Input item, MessageContext messageContext) { // validate that the file exists @@ -45,22 +45,22 @@ async ValueTask IHandle.HandleAsync(Input item, MessageContext messageCon { Message = errorMessage }; - await PublishMessageAsync(err, new TopicId("IOError"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + await PublishMessageAsync(err, new TopicId("IOError"), null, token: CancellationToken.None).ConfigureAwait(false); return; } string content; using (var reader = new StreamReader(item.Message)) { - content = await reader.ReadToEndAsync(cancellationToken ?? CancellationToken.None); + content = await reader.ReadToEndAsync(CancellationToken.None); } await ProcessInputAsync(content); var evt = new InputProcessed { Route = Route }; - await PublishMessageAsync(evt, new TopicId("InputProcessed"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + await PublishMessageAsync(evt, new TopicId("InputProcessed"), null, token: CancellationToken.None).ConfigureAwait(false); } - async ValueTask IHandle.HandleAsync(Output item, MessageContext messageContext, CancellationToken? cancellationToken) + async ValueTask IHandle.HandleAsync(Output item, MessageContext messageContext) { using (var writer = new StreamWriter(OutputPath, append: true)) { @@ -70,6 +70,6 @@ async ValueTask IHandle.HandleAsync(Output item, MessageContext messageC { Route = Route }; - await PublishMessageAsync(evt, new TopicId("OutputWritten"), null, token: cancellationToken ?? CancellationToken.None).ConfigureAwait(false); + await PublishMessageAsync(evt, new TopicId("OutputWritten"), null, token: CancellationToken.None).ConfigureAwait(false); } } diff --git a/python/pyproject.toml b/python/pyproject.toml index 29cf8f963f09..832f7518a870 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -103,7 +103,7 @@ gen-proto = [ ] -gen-proto-samples = "python -m grpc_tools.protoc --python_out=./samples/core_xlang_hello_python_agent/protos --grpc_python_out=./samples/core_xlang_hello_python_agent/protos --mypy_out=./samples/core_xlang_hello_python_agent/protos --mypy_grpc_out=./samples/core_xlang_hello_python_agent/protos --proto_path ../protos/ agent_events.proto" +gen-proto-samples = "python -m grpc_tools.protoc --python_out=./samples/core_xlang_hello_python_agent/protos --grpc_python_out=./samples/core_xlang_hello_python_agent/protos --mypy_out=./samples/core_xlang_hello_python_agent/protos --mypy_grpc_out=./samples/core_xlang_hello_python_agent/protos --proto_path ../dotnet/src/Microsoft.AutoGen/Agents/protos/ agent_events.proto" [[tool.poe.tasks.gen-test-proto.sequence]] cmd = "python -m grpc_tools.protoc --python_out=./packages/autogen-core/tests/protos --grpc_python_out=./packages/autogen-core/tests/protos --mypy_out=./packages/autogen-core/tests/protos --mypy_grpc_out=./packages/autogen-core/tests/protos --proto_path ./packages/autogen-core/tests/protos serialization_test.proto" diff --git a/python/samples/core_xlang_hello_python_agent/protos/agent_events_pb2.py b/python/samples/core_xlang_hello_python_agent/protos/agent_events_pb2.py index fdd42804947d..4d65bcefd3cc 100644 --- a/python/samples/core_xlang_hello_python_agent/protos/agent_events_pb2.py +++ b/python/samples/core_xlang_hello_python_agent/protos/agent_events_pb2.py @@ -24,14 +24,14 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12\x61gent_events.proto\x12\x06\x61gents\"2\n\x0bTextMessage\x12\x13\n\x0btextMessage\x18\x01 \x01(\t\x12\x0e\n\x06source\x18\x02 \x01(\t\"\x18\n\x05Input\x12\x0f\n\x07message\x18\x01 \x01(\t\"\x1f\n\x0eInputProcessed\x12\r\n\x05route\x18\x01 \x01(\t\"\x19\n\x06Output\x12\x0f\n\x07message\x18\x01 \x01(\t\"\x1e\n\rOutputWritten\x12\r\n\x05route\x18\x01 \x01(\t\"\x1a\n\x07IOError\x12\x0f\n\x07message\x18\x01 \x01(\t\"%\n\x12NewMessageReceived\x12\x0f\n\x07message\x18\x01 \x01(\t\"%\n\x11ResponseGenerated\x12\x10\n\x08response\x18\x01 \x01(\t\"\x1a\n\x07GoodBye\x12\x0f\n\x07message\x18\x01 \x01(\t\" \n\rMessageStored\x12\x0f\n\x07message\x18\x01 \x01(\t\";\n\x12\x43onversationClosed\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x14\n\x0cuser_message\x18\x02 \x01(\t\"\x1b\n\x08Shutdown\x12\x0f\n\x07message\x18\x01 \x01(\tB\x1e\xaa\x02\x1bMicrosoft.AutoGen.Contractsb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12\x61gent_events.proto\x12\x06\x61gents\"2\n\x0bTextMessage\x12\x13\n\x0btextMessage\x18\x01 \x01(\t\x12\x0e\n\x06source\x18\x02 \x01(\t\"\x18\n\x05Input\x12\x0f\n\x07message\x18\x01 \x01(\t\"\x1f\n\x0eInputProcessed\x12\r\n\x05route\x18\x01 \x01(\t\"\x19\n\x06Output\x12\x0f\n\x07message\x18\x01 \x01(\t\"\x1e\n\rOutputWritten\x12\r\n\x05route\x18\x01 \x01(\t\"\x1a\n\x07IOError\x12\x0f\n\x07message\x18\x01 \x01(\t\"%\n\x12NewMessageReceived\x12\x0f\n\x07message\x18\x01 \x01(\t\"%\n\x11ResponseGenerated\x12\x10\n\x08response\x18\x01 \x01(\t\"\x1a\n\x07GoodBye\x12\x0f\n\x07message\x18\x01 \x01(\t\" \n\rMessageStored\x12\x0f\n\x07message\x18\x01 \x01(\t\";\n\x12\x43onversationClosed\x12\x0f\n\x07user_id\x18\x01 \x01(\t\x12\x14\n\x0cuser_message\x18\x02 \x01(\t\"\x1b\n\x08Shutdown\x12\x0f\n\x07message\x18\x01 \x01(\tB\x1b\xaa\x02\x18Microsoft.AutoGen.Agentsb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'agent_events_pb2', _globals) if not _descriptor._USE_C_DESCRIPTORS: _globals['DESCRIPTOR']._loaded_options = None - _globals['DESCRIPTOR']._serialized_options = b'\252\002\033Microsoft.AutoGen.Contracts' + _globals['DESCRIPTOR']._serialized_options = b'\252\002\030Microsoft.AutoGen.Agents' _globals['_TEXTMESSAGE']._serialized_start=30 _globals['_TEXTMESSAGE']._serialized_end=80 _globals['_INPUT']._serialized_start=82 From fbbcfdcc4a366ab3b16446dbc6b3bd28e7fa55e3 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Thu, 6 Feb 2025 23:38:00 -0800 Subject: [PATCH 34/53] trying to avoid messy rebase --- .../GrpcGatewayServiceTests.cs | 187 ++++++++++++++++++ .../Helpers/Grpc/TestAsyncStreamReader.cs | 69 +++++++ .../Helpers/Grpc/TestGrpcClient.cs | 38 ++++ .../Helpers/Grpc/TestServerCallContext.cs | 73 +++++++ .../Helpers/Grpc/TestServerStreamWriter.cs | 86 ++++++++ .../Helpers/Orleans/ClusterCollection.cs | 10 + .../Helpers/Orleans/ClusterFixture.cs | 21 ++ .../Orleans/SiloBuilderConfigurator.cs | 22 +++ ...icrosoft.AutoGen.Runtime.Grpc.Tests.csproj | 30 +++ .../TestAgent.cs | 46 +++++ 10 files changed, 582 insertions(+) create mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/GrpcGatewayServiceTests.cs create mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs create mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs create mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs create mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs create mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs create mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs create mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs create mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Microsoft.AutoGen.Runtime.Grpc.Tests.csproj create mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/TestAgent.cs diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/GrpcGatewayServiceTests.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/GrpcGatewayServiceTests.cs new file mode 100644 index 000000000000..fcae1ec3dcdb --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/GrpcGatewayServiceTests.cs @@ -0,0 +1,187 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// GrpcGatewayServiceTests.cs + +using FluentAssertions; +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Core; +using Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; +using Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Orleans; +using Microsoft.Extensions.Logging; +using Moq; +using NewMessageReceived = Tests.Events.NewMessageReceived; + +namespace Microsoft.AutoGen.Runtime.Grpc.Tests; +[Collection(ClusterCollection.Name)] +[Trait("Category", "GRPC")] +public class GrpcGatewayServiceTests +{ + private readonly ClusterFixture _fixture; + + public GrpcGatewayServiceTests(ClusterFixture fixture) + { + _fixture = fixture; + } + [Fact] + public async Task Test_OpenChannel() + { + var logger = Mock.Of>(); + var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); + var service = new GrpcGatewayService(gateway); + var client = new TestGrpcClient(); + + gateway._workers.Count.Should().Be(0); + var task = OpenChannel(service, client); + gateway._workers.Count.Should().Be(1); + client.Dispose(); + await task; + } + + [Fact] + public async Task Test_Message_Exchange_Through_Gateway() + { + var logger = Mock.Of>(); + var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); + var service = new GrpcGatewayService(gateway); + var client = new TestGrpcClient(); + var task = OpenChannel(service: service, client); + await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(PBAgent), client.CallContext.Peer), client.CallContext); + await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(GMAgent), client.CallContext.Peer), client.CallContext); + + var inputEvent = new NewMessageReceived { Message = $"Start-{client.CallContext.Peer}" }.ToCloudEvent("gh-gh-gh", "gh-gh-gh"); + + client.AddMessage(new Message { CloudEvent = inputEvent }); + var newMessageReceived = await client.ReadNext(); + newMessageReceived!.CloudEvent.Type.Should().Be(GetFullName(typeof(NewMessageReceived))); + newMessageReceived.CloudEvent.Source.Should().Be("gh-gh-gh"); + var secondMessage = await client.ReadNext(); + secondMessage!.CloudEvent.Type.Should().Be(GetFullName(typeof(NewMessageReceived))); + + // Simulate an agent, by publishing a new message in the request stream + var helloEvent = new Hello { Message = $"Hello test-{client.CallContext.Peer}" }.ToCloudEvent("gh-gh-gh", "gh-gh-gh"); + client.AddMessage(new Message { CloudEvent = helloEvent }); + var helloMessageReceived = await client.ReadNext(); + helloMessageReceived!.CloudEvent.Type.Should().Be(GetFullName(typeof(Hello))); + helloMessageReceived.CloudEvent.Source.Should().Be("gh-gh-gh"); + client.Dispose(); + await task; + } + + [Fact] + public async Task Test_RegisterAgent_Should_Succeed() + { + var logger = Mock.Of>(); + var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); + var service = new GrpcGatewayService(gateway); + var client = new TestGrpcClient(); + var task = OpenChannel(service: service, client); + var response = await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(PBAgent), client.CallContext.Peer), client.CallContext); + response.Success.Should().BeTrue(); + client.Dispose(); + await task; + } + + [Fact] + public async Task Test_RegisterAgent_Should_Fail_For_Wrong_ConnectionId() + { + var logger = Mock.Of>(); + var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); + var service = new GrpcGatewayService(gateway); + var client = new TestGrpcClient(); + var response = await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(PBAgent), "faulty_connection_id"), client.CallContext); + response.Success.Should().BeFalse(); + client.Dispose(); + } + + [Fact] + public async Task Test_SaveState() + { + var logger = Mock.Of>(); + var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); + var service = new GrpcGatewayService(gateway); + var callContext = TestServerCallContext.Create(); + var response = await service.SaveState(new AgentState { AgentId = new AgentId { Key = "Test", Type = "test" } }, callContext); + response.Should().NotBeNull(); + } + + [Fact] + public async Task Test_GetState() + { + var logger = Mock.Of>(); + var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); + var service = new GrpcGatewayService(gateway); + var callContext = TestServerCallContext.Create(); + var response = await service.GetState(new AgentId { Key = "", Type = "" }, callContext); + response.Should().NotBeNull(); + } + + private async Task CreateRegistrationRequest(GrpcGatewayService service, Type type, string requestId) + { + var registration = new RegisterAgentTypeRequest + { + Type = type.Name, + RequestId = requestId + }; + var assembly = type.Assembly; + var eventTypes = ReflectionHelper.GetAgentsMetadata(assembly); + var events = eventTypes.GetEventsForAgent(type)?.ToList(); + var topics = eventTypes.GetTopicsForAgent(type)?.ToList(); + if (events is not null && topics is not null) { events.AddRange(topics); } + var client = new TestGrpcClient(); + + if (events != null) + { + foreach (var e in events) + { + var subscriptionRequest = new Message + { + AddSubscriptionRequest = new AddSubscriptionRequest + { + RequestId = Guid.NewGuid().ToString(), + Subscription = new Subscription + { + TypeSubscription = new TypeSubscription + { + AgentType = type.Name, + TopicType = type.Name + "." + e + } + } + } + }; + await service.AddSubscription(subscriptionRequest.AddSubscriptionRequest, client.CallContext); + } + } + var topicTypes = type.GetCustomAttributes(typeof(TopicSubscriptionAttribute), true).Cast().Select(t => t.Topic).ToList(); + if (topicTypes != null) + { + foreach (var topicType in topicTypes) + { + var subscriptionRequest = new Message + { + AddSubscriptionRequest = new AddSubscriptionRequest + { + RequestId = Guid.NewGuid().ToString(), + Subscription = new Subscription + { + TypeSubscription = new TypeSubscription + { + AgentType = type.Name, + TopicType = topicType + } + } + } + }; + await service.AddSubscription(subscriptionRequest.AddSubscriptionRequest, client.CallContext); + } + } + return registration; + } + + private Task OpenChannel(GrpcGatewayService service, TestGrpcClient client) + { + return service.OpenChannel(client.RequestStream, client.ResponseStream, client.CallContext); + } + private string GetFullName(Type type) + { + return ReflectionHelper.GetMessageDescriptor(type)!.FullName; + } +} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs new file mode 100644 index 000000000000..4f26711d149f --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs @@ -0,0 +1,69 @@ +#pragma warning disable IDE0073 +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Threading.Channels; +using Grpc.Core; + +namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; + +public class TestAsyncStreamReader : IDisposable, IAsyncStreamReader + where T : class +{ + private readonly Channel _channel; + private readonly ServerCallContext _serverCallContext; + + public T Current { get; private set; } = null!; + + public TestAsyncStreamReader(ServerCallContext serverCallContext) + { + _channel = Channel.CreateUnbounded(); + _serverCallContext = serverCallContext; + } + + public void AddMessage(T message) + { + if (!_channel.Writer.TryWrite(message)) + { + throw new InvalidOperationException("Unable to write message."); + } + } + + public void Complete() + { + _channel.Writer.Complete(); + } + + public async Task MoveNext(CancellationToken cancellationToken) + { + _serverCallContext.CancellationToken.ThrowIfCancellationRequested(); + + if (await _channel.Reader.WaitToReadAsync(cancellationToken) && + _channel.Reader.TryRead(out var message)) + { + Current = message; + return true; + } + else + { + Current = null!; + return false; + } + } + + public void Dispose() + { + Complete(); + } +} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs new file mode 100644 index 000000000000..e47f26eda159 --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// TestGrpcClient.cs + +using Microsoft.AutoGen.Contracts; + +namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; +internal sealed class TestGrpcClient : IDisposable +{ + public TestAsyncStreamReader RequestStream { get; } + public TestServerStreamWriter ResponseStream { get; } + public TestServerCallContext CallContext { get; } + + private CancellationTokenSource CallContextCancellation = new(); + public TestGrpcClient() + { + CallContext = TestServerCallContext.Create(cancellationToken: CallContextCancellation.Token); + RequestStream = new TestAsyncStreamReader(CallContext); + ResponseStream = new TestServerStreamWriter(CallContext); + } + + public async Task ReadNext() + { + var response = await ResponseStream.ReadNextAsync(); + return response!; + } + + public void AddMessage(Message message) + { + RequestStream.AddMessage(message); + } + + public void Dispose() + { + CallContextCancellation.Cancel(); + RequestStream.Dispose(); + ResponseStream.Dispose(); + } +} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs new file mode 100644 index 000000000000..47f25155602d --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs @@ -0,0 +1,73 @@ +#pragma warning disable IDE0073 +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Grpc.Core; + +namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; + +public class TestServerCallContext : ServerCallContext +{ + private readonly Metadata _requestHeaders; + private readonly CancellationToken _cancellationToken; + private readonly Metadata _responseTrailers; + private readonly AuthContext _authContext; + private readonly Dictionary _userState; + private WriteOptions? _writeOptions; + + public Metadata? ResponseHeaders { get; private set; } + + private TestServerCallContext(Metadata requestHeaders, CancellationToken cancellationToken) + { + _requestHeaders = requestHeaders; + _cancellationToken = cancellationToken; + _responseTrailers = new Metadata(); + _authContext = new AuthContext(string.Empty, new Dictionary>()); + _userState = new Dictionary(); + } + + protected override string MethodCore => "MethodName"; + protected override string HostCore => "HostName"; + protected override string PeerCore => "PeerName"; + protected override DateTime DeadlineCore { get; } + protected override Metadata RequestHeadersCore => _requestHeaders; + protected override CancellationToken CancellationTokenCore => _cancellationToken; + protected override Metadata ResponseTrailersCore => _responseTrailers; + protected override Status StatusCore { get; set; } + protected override WriteOptions? WriteOptionsCore { get => _writeOptions; set { _writeOptions = value; } } + protected override AuthContext AuthContextCore => _authContext; + + protected override ContextPropagationToken CreatePropagationTokenCore(ContextPropagationOptions? options) + { + throw new NotImplementedException(); + } + + protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders) + { + if (ResponseHeaders != null) + { + throw new InvalidOperationException("Response headers have already been written."); + } + + ResponseHeaders = responseHeaders; + return Task.CompletedTask; + } + + protected override IDictionary UserStateCore => _userState; + + public static TestServerCallContext Create(Metadata? requestHeaders = null, CancellationToken cancellationToken = default) + { + return new TestServerCallContext(requestHeaders ?? new Metadata(), cancellationToken); + } +} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs new file mode 100644 index 000000000000..ca2aeab2e410 --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs @@ -0,0 +1,86 @@ +#pragma warning disable IDE0073 +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System.Threading.Channels; +using Grpc.Core; + +namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; + +public class TestServerStreamWriter : IDisposable, IServerStreamWriter where T : class +{ + private readonly ServerCallContext _serverCallContext; + private readonly Channel _channel; + + public WriteOptions? WriteOptions { get; set; } + + public TestServerStreamWriter(ServerCallContext serverCallContext) + { + _channel = Channel.CreateUnbounded(); + + _serverCallContext = serverCallContext; + } + + public void Complete() + { + _channel.Writer.Complete(); + } + + public IAsyncEnumerable ReadAllAsync() + { + return _channel.Reader.ReadAllAsync(); + } + + public async Task ReadNextAsync() + { + if (await _channel.Reader.WaitToReadAsync()) + { + _channel.Reader.TryRead(out var message); + return message; + } + else + { + return null; + } + } + + public Task WriteAsync(T message, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + if (_serverCallContext.CancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(_serverCallContext.CancellationToken); + } + + if (!_channel.Writer.TryWrite(message)) + { + throw new InvalidOperationException("Unable to write message."); + } + + return Task.CompletedTask; + } + + public Task WriteAsync(T message) + { + return WriteAsync(message, CancellationToken.None); + } + + public void Dispose() + { + Complete(); + } +} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs new file mode 100644 index 000000000000..d61dc7b21c50 --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ClusterCollection.cs + +namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Orleans; + +[CollectionDefinition(Name)] +public sealed class ClusterCollection : ICollectionFixture +{ + public const string Name = nameof(ClusterCollection); +} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs new file mode 100644 index 000000000000..9db2f7f654d4 --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ClusterFixture.cs + +using Orleans.TestingHost; + +namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Orleans; + +public sealed class ClusterFixture : IDisposable +{ + public ClusterFixture() + { + var builder = new TestClusterBuilder(); + builder.AddSiloBuilderConfigurator(); + Cluster = builder.Build(); + Cluster.Deploy(); + + } + public TestCluster Cluster { get; } + + void IDisposable.Dispose() => Cluster.StopAllSilos(); +} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs new file mode 100644 index 000000000000..bb960f7b1107 --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SiloBuilderConfigurator.cs + +using Orleans.Serialization; +using Orleans.TestingHost; + +namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Orleans; + +public class SiloBuilderConfigurator : ISiloConfigurator +{ + public void Configure(ISiloBuilder siloBuilder) + { + siloBuilder.ConfigureServices(services => + { + services.AddSerializer(a => a.AddProtobufSerializer()); + }); + siloBuilder.AddMemoryStreams("StreamProvider") + .AddMemoryGrainStorage("PubSubStore") + .AddMemoryGrainStorage("AgentRegistryStore") + .AddMemoryGrainStorage("AgentStateStore"); + } +} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Microsoft.AutoGen.Runtime.Grpc.Tests.csproj b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Microsoft.AutoGen.Runtime.Grpc.Tests.csproj new file mode 100644 index 000000000000..c8b00ee268b0 --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Microsoft.AutoGen.Runtime.Grpc.Tests.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + True + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/TestAgent.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/TestAgent.cs new file mode 100644 index 000000000000..e479a2cd7d33 --- /dev/null +++ b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/TestAgent.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// TestAgent.cs + +using System.Collections.Concurrent; +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Core; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AutoGen.Runtime.Grpc.Tests; + +[TopicSubscription("gh-gh-gh")] +public class PBAgent([FromKeyedServices("AgentsMetadata")] AgentsMetadata eventTypes, ILogger? logger = null) + : Agent(eventTypes, logger) + , IHandle + , IHandle +{ + public async Task Handle(NewMessageReceived item, CancellationToken cancellationToken = default) + { + ReceivedMessages[AgentId.Key] = item.Message; + var hello = new Hello { Message = item.Message }; + await PublishMessageAsync(hello); + } + public Task Handle(GoodBye item, CancellationToken cancellationToken) + { + _logger.LogInformation($"Received GoodBye message {item.Message}"); + return Task.CompletedTask; + } + + public static ConcurrentDictionary ReceivedMessages { get; private set; } = new(); +} + +[TopicSubscription("gh-gh-gh")] +public class GMAgent([FromKeyedServices("AgentsMetadata")] AgentsMetadata eventTypes, ILogger? logger = null) + : Agent(eventTypes, logger) + , IHandle +{ + public async Task Handle(Hello item, CancellationToken cancellationToken) + { + _logger.LogInformation($"Received Hello message {item.Message}"); + ReceivedMessages[AgentId.Key] = item.Message; + await PublishMessageAsync(new GoodBye { Message = "" }); + } + + public static ConcurrentDictionary ReceivedMessages { get; private set; } = new(); +} From 7312e67debbaefad78af9e5dbf9672f094f65313 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Thu, 6 Feb 2025 23:39:20 -0800 Subject: [PATCH 35/53] removing this again --- .../GrpcGatewayServiceTests.cs | 187 ------------------ .../Helpers/Grpc/TestAsyncStreamReader.cs | 69 ------- .../Helpers/Grpc/TestGrpcClient.cs | 38 ---- .../Helpers/Grpc/TestServerCallContext.cs | 73 ------- .../Helpers/Grpc/TestServerStreamWriter.cs | 86 -------- .../Helpers/Orleans/ClusterCollection.cs | 10 - .../Helpers/Orleans/ClusterFixture.cs | 21 -- .../Orleans/SiloBuilderConfigurator.cs | 22 --- ...icrosoft.AutoGen.Runtime.Grpc.Tests.csproj | 30 --- .../TestAgent.cs | 46 ----- 10 files changed, 582 deletions(-) delete mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/GrpcGatewayServiceTests.cs delete mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs delete mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs delete mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs delete mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs delete mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs delete mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs delete mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs delete mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Microsoft.AutoGen.Runtime.Grpc.Tests.csproj delete mode 100644 dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/TestAgent.cs diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/GrpcGatewayServiceTests.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/GrpcGatewayServiceTests.cs deleted file mode 100644 index fcae1ec3dcdb..000000000000 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/GrpcGatewayServiceTests.cs +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// GrpcGatewayServiceTests.cs - -using FluentAssertions; -using Microsoft.AutoGen.Contracts; -using Microsoft.AutoGen.Core; -using Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; -using Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Orleans; -using Microsoft.Extensions.Logging; -using Moq; -using NewMessageReceived = Tests.Events.NewMessageReceived; - -namespace Microsoft.AutoGen.Runtime.Grpc.Tests; -[Collection(ClusterCollection.Name)] -[Trait("Category", "GRPC")] -public class GrpcGatewayServiceTests -{ - private readonly ClusterFixture _fixture; - - public GrpcGatewayServiceTests(ClusterFixture fixture) - { - _fixture = fixture; - } - [Fact] - public async Task Test_OpenChannel() - { - var logger = Mock.Of>(); - var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); - var service = new GrpcGatewayService(gateway); - var client = new TestGrpcClient(); - - gateway._workers.Count.Should().Be(0); - var task = OpenChannel(service, client); - gateway._workers.Count.Should().Be(1); - client.Dispose(); - await task; - } - - [Fact] - public async Task Test_Message_Exchange_Through_Gateway() - { - var logger = Mock.Of>(); - var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); - var service = new GrpcGatewayService(gateway); - var client = new TestGrpcClient(); - var task = OpenChannel(service: service, client); - await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(PBAgent), client.CallContext.Peer), client.CallContext); - await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(GMAgent), client.CallContext.Peer), client.CallContext); - - var inputEvent = new NewMessageReceived { Message = $"Start-{client.CallContext.Peer}" }.ToCloudEvent("gh-gh-gh", "gh-gh-gh"); - - client.AddMessage(new Message { CloudEvent = inputEvent }); - var newMessageReceived = await client.ReadNext(); - newMessageReceived!.CloudEvent.Type.Should().Be(GetFullName(typeof(NewMessageReceived))); - newMessageReceived.CloudEvent.Source.Should().Be("gh-gh-gh"); - var secondMessage = await client.ReadNext(); - secondMessage!.CloudEvent.Type.Should().Be(GetFullName(typeof(NewMessageReceived))); - - // Simulate an agent, by publishing a new message in the request stream - var helloEvent = new Hello { Message = $"Hello test-{client.CallContext.Peer}" }.ToCloudEvent("gh-gh-gh", "gh-gh-gh"); - client.AddMessage(new Message { CloudEvent = helloEvent }); - var helloMessageReceived = await client.ReadNext(); - helloMessageReceived!.CloudEvent.Type.Should().Be(GetFullName(typeof(Hello))); - helloMessageReceived.CloudEvent.Source.Should().Be("gh-gh-gh"); - client.Dispose(); - await task; - } - - [Fact] - public async Task Test_RegisterAgent_Should_Succeed() - { - var logger = Mock.Of>(); - var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); - var service = new GrpcGatewayService(gateway); - var client = new TestGrpcClient(); - var task = OpenChannel(service: service, client); - var response = await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(PBAgent), client.CallContext.Peer), client.CallContext); - response.Success.Should().BeTrue(); - client.Dispose(); - await task; - } - - [Fact] - public async Task Test_RegisterAgent_Should_Fail_For_Wrong_ConnectionId() - { - var logger = Mock.Of>(); - var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); - var service = new GrpcGatewayService(gateway); - var client = new TestGrpcClient(); - var response = await service.RegisterAgent(await CreateRegistrationRequest(service, typeof(PBAgent), "faulty_connection_id"), client.CallContext); - response.Success.Should().BeFalse(); - client.Dispose(); - } - - [Fact] - public async Task Test_SaveState() - { - var logger = Mock.Of>(); - var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); - var service = new GrpcGatewayService(gateway); - var callContext = TestServerCallContext.Create(); - var response = await service.SaveState(new AgentState { AgentId = new AgentId { Key = "Test", Type = "test" } }, callContext); - response.Should().NotBeNull(); - } - - [Fact] - public async Task Test_GetState() - { - var logger = Mock.Of>(); - var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); - var service = new GrpcGatewayService(gateway); - var callContext = TestServerCallContext.Create(); - var response = await service.GetState(new AgentId { Key = "", Type = "" }, callContext); - response.Should().NotBeNull(); - } - - private async Task CreateRegistrationRequest(GrpcGatewayService service, Type type, string requestId) - { - var registration = new RegisterAgentTypeRequest - { - Type = type.Name, - RequestId = requestId - }; - var assembly = type.Assembly; - var eventTypes = ReflectionHelper.GetAgentsMetadata(assembly); - var events = eventTypes.GetEventsForAgent(type)?.ToList(); - var topics = eventTypes.GetTopicsForAgent(type)?.ToList(); - if (events is not null && topics is not null) { events.AddRange(topics); } - var client = new TestGrpcClient(); - - if (events != null) - { - foreach (var e in events) - { - var subscriptionRequest = new Message - { - AddSubscriptionRequest = new AddSubscriptionRequest - { - RequestId = Guid.NewGuid().ToString(), - Subscription = new Subscription - { - TypeSubscription = new TypeSubscription - { - AgentType = type.Name, - TopicType = type.Name + "." + e - } - } - } - }; - await service.AddSubscription(subscriptionRequest.AddSubscriptionRequest, client.CallContext); - } - } - var topicTypes = type.GetCustomAttributes(typeof(TopicSubscriptionAttribute), true).Cast().Select(t => t.Topic).ToList(); - if (topicTypes != null) - { - foreach (var topicType in topicTypes) - { - var subscriptionRequest = new Message - { - AddSubscriptionRequest = new AddSubscriptionRequest - { - RequestId = Guid.NewGuid().ToString(), - Subscription = new Subscription - { - TypeSubscription = new TypeSubscription - { - AgentType = type.Name, - TopicType = topicType - } - } - } - }; - await service.AddSubscription(subscriptionRequest.AddSubscriptionRequest, client.CallContext); - } - } - return registration; - } - - private Task OpenChannel(GrpcGatewayService service, TestGrpcClient client) - { - return service.OpenChannel(client.RequestStream, client.ResponseStream, client.CallContext); - } - private string GetFullName(Type type) - { - return ReflectionHelper.GetMessageDescriptor(type)!.FullName; - } -} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs deleted file mode 100644 index 4f26711d149f..000000000000 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestAsyncStreamReader.cs +++ /dev/null @@ -1,69 +0,0 @@ -#pragma warning disable IDE0073 -// Copyright 2019 The gRPC Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Threading.Channels; -using Grpc.Core; - -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; - -public class TestAsyncStreamReader : IDisposable, IAsyncStreamReader - where T : class -{ - private readonly Channel _channel; - private readonly ServerCallContext _serverCallContext; - - public T Current { get; private set; } = null!; - - public TestAsyncStreamReader(ServerCallContext serverCallContext) - { - _channel = Channel.CreateUnbounded(); - _serverCallContext = serverCallContext; - } - - public void AddMessage(T message) - { - if (!_channel.Writer.TryWrite(message)) - { - throw new InvalidOperationException("Unable to write message."); - } - } - - public void Complete() - { - _channel.Writer.Complete(); - } - - public async Task MoveNext(CancellationToken cancellationToken) - { - _serverCallContext.CancellationToken.ThrowIfCancellationRequested(); - - if (await _channel.Reader.WaitToReadAsync(cancellationToken) && - _channel.Reader.TryRead(out var message)) - { - Current = message; - return true; - } - else - { - Current = null!; - return false; - } - } - - public void Dispose() - { - Complete(); - } -} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs deleted file mode 100644 index e47f26eda159..000000000000 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestGrpcClient.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// TestGrpcClient.cs - -using Microsoft.AutoGen.Contracts; - -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; -internal sealed class TestGrpcClient : IDisposable -{ - public TestAsyncStreamReader RequestStream { get; } - public TestServerStreamWriter ResponseStream { get; } - public TestServerCallContext CallContext { get; } - - private CancellationTokenSource CallContextCancellation = new(); - public TestGrpcClient() - { - CallContext = TestServerCallContext.Create(cancellationToken: CallContextCancellation.Token); - RequestStream = new TestAsyncStreamReader(CallContext); - ResponseStream = new TestServerStreamWriter(CallContext); - } - - public async Task ReadNext() - { - var response = await ResponseStream.ReadNextAsync(); - return response!; - } - - public void AddMessage(Message message) - { - RequestStream.AddMessage(message); - } - - public void Dispose() - { - CallContextCancellation.Cancel(); - RequestStream.Dispose(); - ResponseStream.Dispose(); - } -} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs deleted file mode 100644 index 47f25155602d..000000000000 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerCallContext.cs +++ /dev/null @@ -1,73 +0,0 @@ -#pragma warning disable IDE0073 -// Copyright 2019 The gRPC Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Grpc.Core; - -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; - -public class TestServerCallContext : ServerCallContext -{ - private readonly Metadata _requestHeaders; - private readonly CancellationToken _cancellationToken; - private readonly Metadata _responseTrailers; - private readonly AuthContext _authContext; - private readonly Dictionary _userState; - private WriteOptions? _writeOptions; - - public Metadata? ResponseHeaders { get; private set; } - - private TestServerCallContext(Metadata requestHeaders, CancellationToken cancellationToken) - { - _requestHeaders = requestHeaders; - _cancellationToken = cancellationToken; - _responseTrailers = new Metadata(); - _authContext = new AuthContext(string.Empty, new Dictionary>()); - _userState = new Dictionary(); - } - - protected override string MethodCore => "MethodName"; - protected override string HostCore => "HostName"; - protected override string PeerCore => "PeerName"; - protected override DateTime DeadlineCore { get; } - protected override Metadata RequestHeadersCore => _requestHeaders; - protected override CancellationToken CancellationTokenCore => _cancellationToken; - protected override Metadata ResponseTrailersCore => _responseTrailers; - protected override Status StatusCore { get; set; } - protected override WriteOptions? WriteOptionsCore { get => _writeOptions; set { _writeOptions = value; } } - protected override AuthContext AuthContextCore => _authContext; - - protected override ContextPropagationToken CreatePropagationTokenCore(ContextPropagationOptions? options) - { - throw new NotImplementedException(); - } - - protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders) - { - if (ResponseHeaders != null) - { - throw new InvalidOperationException("Response headers have already been written."); - } - - ResponseHeaders = responseHeaders; - return Task.CompletedTask; - } - - protected override IDictionary UserStateCore => _userState; - - public static TestServerCallContext Create(Metadata? requestHeaders = null, CancellationToken cancellationToken = default) - { - return new TestServerCallContext(requestHeaders ?? new Metadata(), cancellationToken); - } -} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs deleted file mode 100644 index ca2aeab2e410..000000000000 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Grpc/TestServerStreamWriter.cs +++ /dev/null @@ -1,86 +0,0 @@ -#pragma warning disable IDE0073 -// Copyright 2019 The gRPC Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System.Threading.Channels; -using Grpc.Core; - -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Grpc; - -public class TestServerStreamWriter : IDisposable, IServerStreamWriter where T : class -{ - private readonly ServerCallContext _serverCallContext; - private readonly Channel _channel; - - public WriteOptions? WriteOptions { get; set; } - - public TestServerStreamWriter(ServerCallContext serverCallContext) - { - _channel = Channel.CreateUnbounded(); - - _serverCallContext = serverCallContext; - } - - public void Complete() - { - _channel.Writer.Complete(); - } - - public IAsyncEnumerable ReadAllAsync() - { - return _channel.Reader.ReadAllAsync(); - } - - public async Task ReadNextAsync() - { - if (await _channel.Reader.WaitToReadAsync()) - { - _channel.Reader.TryRead(out var message); - return message; - } - else - { - return null; - } - } - - public Task WriteAsync(T message, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - if (_serverCallContext.CancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(_serverCallContext.CancellationToken); - } - - if (!_channel.Writer.TryWrite(message)) - { - throw new InvalidOperationException("Unable to write message."); - } - - return Task.CompletedTask; - } - - public Task WriteAsync(T message) - { - return WriteAsync(message, CancellationToken.None); - } - - public void Dispose() - { - Complete(); - } -} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs deleted file mode 100644 index d61dc7b21c50..000000000000 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterCollection.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ClusterCollection.cs - -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Orleans; - -[CollectionDefinition(Name)] -public sealed class ClusterCollection : ICollectionFixture -{ - public const string Name = nameof(ClusterCollection); -} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs deleted file mode 100644 index 9db2f7f654d4..000000000000 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/ClusterFixture.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ClusterFixture.cs - -using Orleans.TestingHost; - -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Orleans; - -public sealed class ClusterFixture : IDisposable -{ - public ClusterFixture() - { - var builder = new TestClusterBuilder(); - builder.AddSiloBuilderConfigurator(); - Cluster = builder.Build(); - Cluster.Deploy(); - - } - public TestCluster Cluster { get; } - - void IDisposable.Dispose() => Cluster.StopAllSilos(); -} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs deleted file mode 100644 index bb960f7b1107..000000000000 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Helpers/Orleans/SiloBuilderConfigurator.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SiloBuilderConfigurator.cs - -using Orleans.Serialization; -using Orleans.TestingHost; - -namespace Microsoft.AutoGen.Runtime.Grpc.Tests.Helpers.Orleans; - -public class SiloBuilderConfigurator : ISiloConfigurator -{ - public void Configure(ISiloBuilder siloBuilder) - { - siloBuilder.ConfigureServices(services => - { - services.AddSerializer(a => a.AddProtobufSerializer()); - }); - siloBuilder.AddMemoryStreams("StreamProvider") - .AddMemoryGrainStorage("PubSubStore") - .AddMemoryGrainStorage("AgentRegistryStore") - .AddMemoryGrainStorage("AgentStateStore"); - } -} diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Microsoft.AutoGen.Runtime.Grpc.Tests.csproj b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Microsoft.AutoGen.Runtime.Grpc.Tests.csproj deleted file mode 100644 index c8b00ee268b0..000000000000 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/Microsoft.AutoGen.Runtime.Grpc.Tests.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - net8.0 - enable - enable - True - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - - - - - - diff --git a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/TestAgent.cs b/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/TestAgent.cs deleted file mode 100644 index e479a2cd7d33..000000000000 --- a/dotnet/test/Microsoft.AutoGen.Runtime.Grpc.Tests/TestAgent.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// TestAgent.cs - -using System.Collections.Concurrent; -using Microsoft.AutoGen.Contracts; -using Microsoft.AutoGen.Core; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AutoGen.Runtime.Grpc.Tests; - -[TopicSubscription("gh-gh-gh")] -public class PBAgent([FromKeyedServices("AgentsMetadata")] AgentsMetadata eventTypes, ILogger? logger = null) - : Agent(eventTypes, logger) - , IHandle - , IHandle -{ - public async Task Handle(NewMessageReceived item, CancellationToken cancellationToken = default) - { - ReceivedMessages[AgentId.Key] = item.Message; - var hello = new Hello { Message = item.Message }; - await PublishMessageAsync(hello); - } - public Task Handle(GoodBye item, CancellationToken cancellationToken) - { - _logger.LogInformation($"Received GoodBye message {item.Message}"); - return Task.CompletedTask; - } - - public static ConcurrentDictionary ReceivedMessages { get; private set; } = new(); -} - -[TopicSubscription("gh-gh-gh")] -public class GMAgent([FromKeyedServices("AgentsMetadata")] AgentsMetadata eventTypes, ILogger? logger = null) - : Agent(eventTypes, logger) - , IHandle -{ - public async Task Handle(Hello item, CancellationToken cancellationToken) - { - _logger.LogInformation($"Received Hello message {item.Message}"); - ReceivedMessages[AgentId.Key] = item.Message; - await PublishMessageAsync(new GoodBye { Message = "" }); - } - - public static ConcurrentDictionary ReceivedMessages { get; private set; } = new(); -} From 92dc5a7b5dfcd86dbc85cdfa477a3e962869b6ce Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Thu, 6 Feb 2025 23:41:31 -0800 Subject: [PATCH 36/53] format --- .../RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs | 10 +++++----- .../RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs | 2 -- .../RuntimeGateway.Grpc/Abstractions/IAgentState.cs | 1 - .../Abstractions/ReflectionHelper.cs | 4 ++-- .../RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs | 2 +- .../Services/Orleans/AgentStateGrain.cs | 1 - .../Services/Orleans/Surrogates/AgentStateSurrogate.cs | 1 - .../Orleans/Surrogates/RpcResponseSurrogate.cs | 3 +-- .../TestAgent.cs | 6 +++--- 9 files changed, 12 insertions(+), 18 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs index abbd43a89c25..5e0ecd876031 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs @@ -21,17 +21,17 @@ public sealed class AgentsMetadata /// A dictionary mapping types to a set of topics associated with those types. /// public AgentsMetadata( - TypeRegistry typeRegistry, - Dictionary types, - Dictionary> eventsMap, - Dictionary> topicsMap, + TypeRegistry typeRegistry, + Dictionary types, + Dictionary> eventsMap, + Dictionary> topicsMap, Dictionary> topicsPrefixMap) { TypeRegistry = typeRegistry; _types = new(types); _eventsMap = new(eventsMap); _topicsMap = new(topicsMap); - _topicsPrefixMap = new(topicsPrefixMap); + _topicsPrefixMap = new(topicsPrefixMap); } /// diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs index 0ca9882247b3..25fbe3ce58ab 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs @@ -1,7 +1,5 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // IAgentGrain.cs -using Microsoft.AutoGen.Protobuf; - namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; /// /// Interface for managing agent state as an Orleans Grain. diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentState.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentState.cs index 126a43a15c21..b8781c066288 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentState.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentState.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // IAgentState.cs -using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; /// diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs index 0a7534a397de..0523a97dad6e 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ReflectionHelper.cs -using Microsoft.AutoGen.Core; -using Microsoft.AutoGen.Contracts; using System.Reflection; using Google.Protobuf; using Google.Protobuf.Reflection; +using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Core; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; public sealed class ReflectionHelper diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs index a6bba74e90f8..a21a1da45100 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs @@ -146,7 +146,7 @@ public async ValueTask RegisterAgentTypeAsync(Registe _supportedAgentTypes.GetOrAdd(request.Type, _ => []).Add(connection); await _gatewayRegistry.RegisterAgentTypeAsync(request, clientId, _reference).ConfigureAwait(true); - return new RegisterAgentTypeResponse {}; + return new RegisterAgentTypeResponse { }; } catch (Exception ex) { diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs index 670e31e1fc97..d6c6bfed311b 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AgentStateGrain.cs -using Microsoft.AutoGen.Protobuf; using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; namespace Microsoft.AutoGen.RuntimeGateway.Grpc; diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs index 377a1da210e8..c02952338648 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs @@ -2,7 +2,6 @@ // AgentStateSurrogate.cs using Google.Protobuf; -using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs index 1c2852d811e2..999ae2bf6502 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/RpcResponseSurrogate.cs @@ -1,8 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // RpcResponseSurrogate.cs -using Microsoft.AutoGen.Protobuf; - using Google.Protobuf.Collections; +using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; diff --git a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs index 44818661af4c..493b0370433a 100644 --- a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/TestAgent.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // TestAgent.cs -using Microsoft.AutoGen.Protobuf; -using Microsoft.AutoGen.Core; using System.Collections.Concurrent; -using Microsoft.Extensions.Logging; using Microsoft.AutoGen.Contracts; +using Microsoft.AutoGen.Core; +using Microsoft.AutoGen.Protobuf; +using Microsoft.Extensions.Logging; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests; From c97339ecca0918185df5aa6f7d9aba580c924b95 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 00:13:53 -0800 Subject: [PATCH 37/53] updating to deal with the proto changes --- .../Abstractions/AgentState.cs | 14 +++++++ .../Services/Grpc/GrpcGatewayService.cs | 38 ------------------- .../Orleans/Surrogates/AgentStateSurrogate.cs | 17 ++++++--- .../GrpcGatewayServiceTests.cs | 22 ----------- 4 files changed, 25 insertions(+), 66 deletions(-) create mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentState.cs diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentState.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentState.cs new file mode 100644 index 000000000000..5416f1a64f82 --- /dev/null +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentState.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// AgentState.cs +using Google.Protobuf; +using Microsoft.AutoGen.Protobuf; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; +public class AgentState +{ + public required AgentId AgentId { get; set; } + public string ETag { get; set; } = Guid.NewGuid().ToString(); + public object? Data { get; set; } + public string? TextData { get; set; } + public ByteString? BinaryData { get; set; } + public Google.Protobuf.WellKnownTypes.Any? ProtoData { get; set; } +} \ No newline at end of file diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs index 0f0427d83bf3..1f04647db322 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGatewayService.cs @@ -35,44 +35,6 @@ public override async Task OpenChannel(IAsyncStreamReader requestStream } } - /// - /// Gets the state of an agent. - /// - /// The agent ID request. - /// The server call context. - /// A task that represents the asynchronous operation. The task result contains the get state response. - public override async Task GetState(Protobuf.AgentId request, ServerCallContext context) - { - try - { - var state = await Gateway.ReadAsync(request).ConfigureAwait(true); - return new GetStateResponse { AgentState = state }; - } - catch (Exception e) - { - throw new RpcException(new Status(StatusCode.Internal, e.Message)); - } - } - - /// - /// Saves the state of an agent. - /// - /// The agent state request. - /// The server call context. - /// A task that represents the asynchronous operation. The task result contains the save state response. - public override async Task SaveState(AgentState request, ServerCallContext context) - { - try - { - await Gateway.StoreAsync(request).ConfigureAwait(true); - return new SaveStateResponse(); - } - catch (Exception e) - { - throw new RpcException(new Status(StatusCode.Internal, e.Message)); - } - } - /// /// Adds a subscription. /// diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs index c02952338648..41481a607eef 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AgentStateSurrogate.cs - +using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; using Google.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; @@ -11,15 +11,17 @@ public struct AgentStateSurrogate [Id(0)] public string Id; [Id(1)] - public string TextData; + public string? TextData; [Id(2)] - public ByteString BinaryData; + public ByteString? BinaryData; [Id(3)] public Protobuf.AgentId AgentId; [Id(4)] public string Etag; [Id(5)] - public ByteString ProtoData; + public Google.Protobuf.WellKnownTypes.Any? ProtoData; + [Id(6)] + public object? Data; } [RegisterConverter] @@ -34,7 +36,9 @@ public AgentState ConvertFromSurrogate( AgentId = surrogate.AgentId, BinaryData = surrogate.BinaryData, TextData = surrogate.TextData, - ETag = surrogate.Etag + ETag = surrogate.Etag, + ProtoData = surrogate.ProtoData, + Data = surrogate.Data }; //agentState.ProtoData = surrogate.ProtoData; return agentState; @@ -48,7 +52,8 @@ public AgentStateSurrogate ConvertToSurrogate( BinaryData = value.BinaryData, TextData = value.TextData, Etag = value.ETag, - //ProtoData = value.ProtoData.Value + ProtoData = value.ProtoData, + Data = value.Data }; } diff --git a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs index b0e2300fd183..c19a12a24d1a 100644 --- a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs @@ -98,28 +98,6 @@ public async Task Test_RegisterAgent_Should_Succeed() await task; } - [Fact] - public async Task Test_SaveState() - { - var logger = Mock.Of>(); - var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); - var service = new GrpcGatewayService(gateway); - var callContext = TestServerCallContext.Create(); - var response = await service.SaveState(new AgentState { AgentId = new Protobuf.AgentId { Key = "Test", Type = "test" } }, callContext); - response.Should().NotBeNull(); - } - - [Fact] - public async Task Test_GetState() - { - var logger = Mock.Of>(); - var gateway = new GrpcGateway(_fixture.Cluster.Client, logger); - var service = new GrpcGatewayService(gateway); - var callContext = TestServerCallContext.Create(); - var response = await service.GetState(new Protobuf.AgentId { Key = "", Type = "" }, callContext); - response.Should().NotBeNull(); - } - private async Task CreateRegistrationRequest(GrpcGatewayService service, Type type) { var registration = new RegisterAgentTypeRequest From c8ebf4ec8f0872d4b5aa127db9aef8ac04dea9d8 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 00:21:49 -0800 Subject: [PATCH 38/53] format --- .../RuntimeGateway.Grpc/Abstractions/AgentState.cs | 2 +- .../Services/Orleans/Surrogates/AgentStateSurrogate.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentState.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentState.cs index 5416f1a64f82..fb5202bd0054 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentState.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentState.cs @@ -11,4 +11,4 @@ public class AgentState public string? TextData { get; set; } public ByteString? BinaryData { get; set; } public Google.Protobuf.WellKnownTypes.Any? ProtoData { get; set; } -} \ No newline at end of file +} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs index 41481a607eef..1b7694b552e7 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AgentStateSurrogate.cs -using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; using Google.Protobuf; +using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; From 7fe1b485e48e88c45b90e1907a9fa913c6dccf18 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 08:19:06 -0800 Subject: [PATCH 39/53] move to tests --- .../GrpcGatewayServiceTests.cs | 4 ++-- .../Helpers}/AgentsMetadata.cs | 2 +- .../Helpers}/ReflectionHelper.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename dotnet/{src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions => test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers}/AgentsMetadata.cs (98%) rename dotnet/{src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions => test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers}/ReflectionHelper.cs (98%) diff --git a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs index c19a12a24d1a..e9009c92394e 100644 --- a/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/GrpcGatewayServiceTests.cs @@ -105,7 +105,7 @@ private async Task CreateRegistrationRequest(GrpcGatew Type = type.Name, }; var assembly = type.Assembly; - var eventTypes = Abstractions.ReflectionHelper.GetAgentsMetadata(assembly); + var eventTypes = ReflectionHelper.GetAgentsMetadata(assembly); var events = eventTypes.GetEventsForAgent(type)?.ToList(); var topics = eventTypes.GetTopicsForAgent(type)?.ToList(); var topicsPrefix = eventTypes.GetTopicsPrefixForAgent(type)?.ToList(); @@ -183,7 +183,7 @@ private Task OpenChannel(GrpcGatewayService service, TestGrpcClient client) } private string GetFullName(Type type) { - return Abstractions.ReflectionHelper.GetMessageDescriptor(type)!.FullName; + return ReflectionHelper.GetMessageDescriptor(type)!.FullName; } /// duplicate code here because I could not get InternalsVisibleTo to work internal static class Constants diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/AgentsMetadata.cs similarity index 98% rename from dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs rename to dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/AgentsMetadata.cs index 5e0ecd876031..016bfc329bfe 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsMetadata.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/AgentsMetadata.cs @@ -4,7 +4,7 @@ using System.Collections.Concurrent; using Google.Protobuf.Reflection; -namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests; /// /// Represents a collection of event types and their associated metadata. diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/ReflectionHelper.cs similarity index 98% rename from dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs rename to dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/ReflectionHelper.cs index 0523a97dad6e..12e9b799b97c 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/ReflectionHelper.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/ReflectionHelper.cs @@ -6,7 +6,7 @@ using Microsoft.AutoGen.Contracts; using Microsoft.AutoGen.Core; -namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests; public sealed class ReflectionHelper { public static bool IsSubclassOfGeneric(Type type, Type genericBaseType) From a42ab96015fdc390b01930d82276e03e514fc661 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 08:28:07 -0800 Subject: [PATCH 40/53] remove agentsmetadata --- .../Services/AgentWorkerHostingExtensions.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs index e4ee962aee6d..37b9e1f1b6df 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/AgentWorkerHostingExtensions.cs @@ -17,10 +17,6 @@ public static WebApplicationBuilder AddAgentService(this WebApplicationBuilder b builder.Services.TryAddSingleton(DistributedContextPropagator.Current); builder.Services.AddGrpc(); - builder.Services.AddKeyedSingleton("AgentsMetadata", (sp, key) => - { - return Abstractions.ReflectionHelper.GetAgentsMetadata(AppDomain.CurrentDomain.GetAssemblies()); - }); builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => (IHostedService)sp.GetRequiredService()); From 273df31e71c39bb4b0a0722a4ce1161a89d5270f Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 08:54:37 -0800 Subject: [PATCH 41/53] remove unused. --- .../Abstractions/IGateway.cs | 2 -- .../Services/Grpc/GrpcGateway.cs | 35 ------------------- ...Microsoft.AutoGen.Integration.Tests.csproj | 8 +++-- 3 files changed, 6 insertions(+), 39 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs index c573649d805c..7db9b2b04283 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs @@ -10,8 +10,6 @@ public interface IGateway : IGrainObserver { ValueTask InvokeRequestAsync(RpcRequest request); ValueTask BroadcastEventAsync(CloudEvent evt); - ValueTask StoreAsync(AgentState value); - ValueTask ReadAsync(Protobuf.AgentId agentId); ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, ServerCallContext context); ValueTask SubscribeAsync(AddSubscriptionRequest request); ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request); diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs index a21a1da45100..d9ec82f36133 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs @@ -100,31 +100,6 @@ public async ValueTask InvokeRequestAsync(RpcRequest request, Cance return response; } - /// - /// Stores the agent state asynchronously. - /// - /// The agent state. - /// The cancellation token. - /// A task that represents the asynchronous operation. - public async ValueTask StoreAsync(AgentState value, CancellationToken cancellationToken = default) - { - _ = value.AgentId ?? throw new ArgumentNullException(nameof(value.AgentId)); - var agentState = _clusterClient.GetGrain($"{value.AgentId.Type}:{value.AgentId.Key}"); - await agentState.WriteStateAsync(value, value.ETag); - } - - /// - /// Reads the agent state asynchronously. - /// - /// The agent ID. - /// The cancellation token. - /// A task that represents the asynchronous operation. The task result contains the agent state. - public async ValueTask ReadAsync(Protobuf.AgentId agentId, CancellationToken cancellationToken = default) - { - var agentState = _clusterClient.GetGrain($"{agentId.Type}:{agentId.Key}"); - return await agentState.ReadStateAsync(); - } - /// /// Registers an agent type asynchronously. /// @@ -213,16 +188,6 @@ async ValueTask IGateway.BroadcastEventAsync(CloudEvent evt) await BroadcastEventAsync(evt, default).ConfigureAwait(false); } - ValueTask IGateway.StoreAsync(AgentState value) - { - return StoreAsync(value, default); - } - - ValueTask IGateway.ReadAsync(Protobuf.AgentId agentId) - { - return ReadAsync(agentId, default); - } - ValueTask IGateway.RegisterAgentTypeAsync(RegisterAgentTypeRequest request, ServerCallContext context) { return RegisterAgentTypeAsync(request, context, default); diff --git a/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj b/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj index 2ef1763c3db5..79351406e7f2 100644 --- a/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj +++ b/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj @@ -36,7 +36,7 @@ .venv - $(RepoRoot)..\python + $(RepoRoot)..\python @@ -44,10 +44,14 @@ $(PythonVenvRoot)\$(PythonVirtualEnvironmentName)\ True + ~/.local/bin/uv + True + uv + $(Uv) - + From 46f6b81d181cc567727ff80be378872f17870331 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 09:07:31 -0800 Subject: [PATCH 42/53] try output logging --- .../Microsoft.AutoGen.Integration.Tests.csproj | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj b/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj index 79351406e7f2..0c6f947dd263 100644 --- a/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj +++ b/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj @@ -51,7 +51,10 @@ - + + + + From 2c00f2e54c4abd875f2caf8cc752a24a39d4ec15 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 09:31:39 -0800 Subject: [PATCH 43/53] remove unused --- .../Abstractions/AgentState.cs | 14 ----- .../Abstractions/IAgentGrain.cs | 21 ------- .../Abstractions/IAgentState.cs | 25 -------- .../Services/Orleans/AgentStateGrain.cs | 45 -------------- .../Services/Orleans/ISubscriptionsGrain.cs | 10 ---- .../Services/Orleans/SubscriptionsGrain.cs | 50 ---------------- .../Orleans/Surrogates/AgentStateSurrogate.cs | 59 ------------------- 7 files changed, 224 deletions(-) delete mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentState.cs delete mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs delete mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentState.cs delete mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs delete mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/ISubscriptionsGrain.cs delete mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/SubscriptionsGrain.cs delete mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentState.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentState.cs deleted file mode 100644 index fb5202bd0054..000000000000 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentState.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// AgentState.cs -using Google.Protobuf; -using Microsoft.AutoGen.Protobuf; -namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; -public class AgentState -{ - public required AgentId AgentId { get; set; } - public string ETag { get; set; } = Guid.NewGuid().ToString(); - public object? Data { get; set; } - public string? TextData { get; set; } - public ByteString? BinaryData { get; set; } - public Google.Protobuf.WellKnownTypes.Any? ProtoData { get; set; } -} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs deleted file mode 100644 index 25fbe3ce58ab..000000000000 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentGrain.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// IAgentGrain.cs -namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; -/// -/// Interface for managing agent state as an Orleans Grain. -/// -internal interface IAgentGrain : IGrainWithStringKey -{ - /// - /// Reads the state from the Orleans Grain. - /// - /// A task representing the AgentState object - ValueTask ReadStateAsync(); - /// - /// Writes the state to the Orleans Grain. - /// - /// - /// used for optimistic concurrency control - /// - ValueTask WriteStateAsync(AgentState state, string eTag); -} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentState.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentState.cs deleted file mode 100644 index b8781c066288..000000000000 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IAgentState.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// IAgentState.cs -namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; - -/// -/// Interface for managing the state of an agent. -/// -public interface IAgentState -{ - /// - /// Reads the current state of the agent asynchronously. - /// - /// A token to cancel the operation. - /// A task that represents the asynchronous read operation. The task result contains the current state of the agent. - ValueTask ReadStateAsync(CancellationToken cancellationToken = default); - - /// - /// Writes the specified state of the agent asynchronously. - /// - /// The state to write. - /// The ETag for concurrency control. - /// A token to cancel the operation. - /// A task that represents the asynchronous write operation. The task result contains the ETag of the written state. - ValueTask WriteStateAsync(AgentState state, string eTag, CancellationToken cancellationToken = default); -} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs deleted file mode 100644 index d6c6bfed311b..000000000000 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/AgentStateGrain.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// AgentStateGrain.cs -using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; - -namespace Microsoft.AutoGen.RuntimeGateway.Grpc; - -internal sealed class AgentStateGrain([PersistentState("state", "AgentStateStore")] IPersistentState state) : Grain, IAgentState, IAgentGrain -{ - /// - public async ValueTask WriteStateAsync(AgentState newState, string eTag, CancellationToken cancellationToken = default) - { - // etags for optimistic concurrency control - // if the Etag is null, its a new state - // if the passed etag is null or empty, we should not check the current state's Etag - caller doesnt care - // if both etags are set, they should match or it means that the state has changed since the last read. - if (string.IsNullOrEmpty(state.Etag) || string.IsNullOrEmpty(eTag) || string.Equals(state.Etag, eTag, StringComparison.Ordinal)) - { - state.State = newState; - await state.WriteStateAsync().ConfigureAwait(false); - } - else - { - //TODO - this is probably not the correct behavior to just throw - I presume we want to somehow let the caller know that the state has changed and they need to re-read it - throw new ArgumentException( - "The provided ETag does not match the current ETag. The state has been modified by another request."); - } - return state.Etag; - } - - /// - public ValueTask ReadStateAsync(CancellationToken cancellationToken = default) - { - return ValueTask.FromResult(state.State); - } - - ValueTask IAgentGrain.ReadStateAsync() - { - return ReadStateAsync(); - } - - ValueTask IAgentGrain.WriteStateAsync(AgentState state, string eTag) - { - return WriteStateAsync(state, eTag); - } -} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/ISubscriptionsGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/ISubscriptionsGrain.cs deleted file mode 100644 index c777c61fcbd7..000000000000 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/ISubscriptionsGrain.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ISubscriptionsGrain.cs - -namespace Microsoft.AutoGen.RuntimeGateway.Grpc; -public interface ISubscriptionsGrain : IGrainWithIntegerKey -{ - ValueTask SubscribeAsync(string agentType, string topic); - ValueTask UnsubscribeAsync(string agentType, string topic); - ValueTask>> GetSubscriptions(string agentType); -} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/SubscriptionsGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/SubscriptionsGrain.cs deleted file mode 100644 index fa815d41a2a8..000000000000 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/SubscriptionsGrain.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// SubscriptionsGrain.cs - -namespace Microsoft.AutoGen.RuntimeGateway.Grpc; - -internal sealed class SubscriptionsGrain([PersistentState("state", "PubSubStore")] IPersistentState state) : Grain, ISubscriptionsGrain -{ - private readonly Dictionary> _subscriptions = new(); - public ValueTask>> GetSubscriptions(string? agentType = null) - { - //if agentType is null, return all subscriptions else filter on agentType - if (agentType != null) - { - return new ValueTask>>(_subscriptions.Where(x => x.Value.Contains(agentType)).ToDictionary(x => x.Key, x => x.Value)); - } - return new ValueTask>>(_subscriptions); - } - public async ValueTask SubscribeAsync(string agentType, string topic) - { - if (!_subscriptions.TryGetValue(topic, out var subscriptions)) - { - subscriptions = _subscriptions[topic] = []; - } - if (!subscriptions.Contains(agentType)) - { - subscriptions.Add(agentType); - } - _subscriptions[topic] = subscriptions; - state.State.Subscriptions = _subscriptions; - await state.WriteStateAsync().ConfigureAwait(false); - } - public async ValueTask UnsubscribeAsync(string agentType, string topic) - { - if (!_subscriptions.TryGetValue(topic, out var subscriptions)) - { - subscriptions = _subscriptions[topic] = []; - } - if (!subscriptions.Contains(agentType)) - { - subscriptions.Remove(agentType); - } - _subscriptions[topic] = subscriptions; - state.State.Subscriptions = _subscriptions; - await state.WriteStateAsync(); - } -} -public sealed class SubscriptionsState -{ - public Dictionary> Subscriptions { get; set; } = new(); -} diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs deleted file mode 100644 index 1b7694b552e7..000000000000 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/Surrogates/AgentStateSurrogate.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// AgentStateSurrogate.cs -using Google.Protobuf; -using Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; - -namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Orleans.Surrogates; - -[GenerateSerializer] -public struct AgentStateSurrogate -{ - [Id(0)] - public string Id; - [Id(1)] - public string? TextData; - [Id(2)] - public ByteString? BinaryData; - [Id(3)] - public Protobuf.AgentId AgentId; - [Id(4)] - public string Etag; - [Id(5)] - public Google.Protobuf.WellKnownTypes.Any? ProtoData; - [Id(6)] - public object? Data; -} - -[RegisterConverter] -public sealed class AgentStateSurrogateConverter : - IConverter -{ - public AgentState ConvertFromSurrogate( - in AgentStateSurrogate surrogate) - { - var agentState = new AgentState - { - AgentId = surrogate.AgentId, - BinaryData = surrogate.BinaryData, - TextData = surrogate.TextData, - ETag = surrogate.Etag, - ProtoData = surrogate.ProtoData, - Data = surrogate.Data - }; - //agentState.ProtoData = surrogate.ProtoData; - return agentState; - } - - public AgentStateSurrogate ConvertToSurrogate( - in AgentState value) => - new AgentStateSurrogate - { - AgentId = value.AgentId, - BinaryData = value.BinaryData, - TextData = value.TextData, - Etag = value.ETag, - ProtoData = value.ProtoData, - Data = value.Data - }; -} - From 349577aadfa059d90b0ce386b9f573063860c72e Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 09:45:28 -0800 Subject: [PATCH 44/53] add python and uv setup to dotnet build --- .github/workflows/dotnet-build.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 8539502b9e33..ca55a6f142c7 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -65,6 +65,18 @@ jobs: - uses: actions/checkout@v4 with: lfs: true + - uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + version: "0.5.18" + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - run: uv sync --locked --all-extras + working-directory: ./python + - name: Prepare python venv + run: | + source ${{ github.workspace }}/python/.venv/bin/activate - name: Setup .NET 8.0 uses: actions/setup-dotnet@v4 with: From 97bf2c59dc80d22dc595a2733f235eb75d33de1b Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 09:54:48 -0800 Subject: [PATCH 45/53] remove uv output redir --- .../Microsoft.AutoGen.Integration.Tests.csproj | 2 +- python/uv.lock | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj b/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj index 0c6f947dd263..b3d7cbf8776a 100644 --- a/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj +++ b/dotnet/test/Microsoft.AutoGen.Integration.Tests/Microsoft.AutoGen.Integration.Tests.csproj @@ -54,7 +54,7 @@ - + diff --git a/python/uv.lock b/python/uv.lock index 87c5320f0343..b6c8dda7f020 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -4306,7 +4306,6 @@ name = "nvidia-cublas-cu12" version = "12.4.5.8" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/7f/7fbae15a3982dc9595e49ce0f19332423b260045d0a6afe93cdbe2f1f624/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0f8aa1706812e00b9f19dfe0cdb3999b092ccb8ca168c0db5b8ea712456fd9b3", size = 363333771 }, { url = "https://files.pythonhosted.org/packages/ae/71/1c91302526c45ab494c23f61c7a84aa568b8c1f9d196efa5993957faf906/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b", size = 363438805 }, ] @@ -4315,7 +4314,6 @@ name = "nvidia-cuda-cupti-cu12" version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/b5/9fb3d00386d3361b03874246190dfec7b206fd74e6e287b26a8fcb359d95/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:79279b35cf6f91da114182a5ce1864997fd52294a87a16179ce275773799458a", size = 12354556 }, { url = "https://files.pythonhosted.org/packages/67/42/f4f60238e8194a3106d06a058d494b18e006c10bb2b915655bd9f6ea4cb1/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb", size = 13813957 }, ] @@ -4324,7 +4322,6 @@ name = "nvidia-cuda-nvrtc-cu12" version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/aa/083b01c427e963ad0b314040565ea396f914349914c298556484f799e61b/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0eedf14185e04b76aa05b1fea04133e59f465b6f960c0cbf4e37c3cb6b0ea198", size = 24133372 }, { url = "https://files.pythonhosted.org/packages/2c/14/91ae57cd4db3f9ef7aa99f4019cfa8d54cb4caa7e00975df6467e9725a9f/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338", size = 24640306 }, ] @@ -4333,7 +4330,6 @@ name = "nvidia-cuda-runtime-cu12" version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/aa/b656d755f474e2084971e9a297def515938d56b466ab39624012070cb773/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:961fe0e2e716a2a1d967aab7caee97512f71767f852f67432d572e36cb3a11f3", size = 894177 }, { url = "https://files.pythonhosted.org/packages/ea/27/1795d86fe88ef397885f2e580ac37628ed058a92ed2c39dc8eac3adf0619/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5", size = 883737 }, ] @@ -4356,7 +4352,6 @@ dependencies = [ { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/8a/0e728f749baca3fbeffad762738276e5df60851958be7783af121a7221e7/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5dad8008fc7f92f5ddfa2101430917ce2ffacd86824914c82e28990ad7f00399", size = 211422548 }, { url = "https://files.pythonhosted.org/packages/27/94/3266821f65b92b3138631e9c8e7fe1fb513804ac934485a8d05776e1dd43/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9", size = 211459117 }, ] @@ -4365,7 +4360,6 @@ name = "nvidia-curand-cu12" version = "10.3.5.147" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/9c/a79180e4d70995fdf030c6946991d0171555c6edf95c265c6b2bf7011112/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1f173f09e3e3c76ab084aba0de819c49e56614feae5c12f69883f4ae9bb5fad9", size = 56314811 }, { url = "https://files.pythonhosted.org/packages/8a/6d/44ad094874c6f1b9c654f8ed939590bdc408349f137f9b98a3a23ccec411/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b", size = 56305206 }, ] @@ -4379,7 +4373,6 @@ dependencies = [ { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/46/6b/a5c33cf16af09166845345275c34ad2190944bcc6026797a39f8e0a282e0/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d338f155f174f90724bbde3758b7ac375a70ce8e706d70b018dd3375545fc84e", size = 127634111 }, { url = "https://files.pythonhosted.org/packages/3a/e1/5b9089a4b2a4790dfdea8b3a006052cfecff58139d5a4e34cb1a51df8d6f/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260", size = 127936057 }, ] @@ -4391,7 +4384,6 @@ dependencies = [ { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/96/a9/c0d2f83a53d40a4a41be14cea6a0bf9e668ffcf8b004bd65633f433050c0/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9d32f62896231ebe0480efd8a7f702e143c98cfaa0e8a76df3386c1ba2b54df3", size = 207381987 }, { url = "https://files.pythonhosted.org/packages/db/f7/97a9ea26ed4bbbfc2d470994b8b4f338ef663be97b8f677519ac195e113d/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1", size = 207454763 }, ] @@ -4408,7 +4400,6 @@ name = "nvidia-nvjitlink-cu12" version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/45/239d52c05074898a80a900f49b1615d81c07fceadd5ad6c4f86a987c0bc4/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4abe7fef64914ccfa909bc2ba39739670ecc9e820c83ccc7a6ed414122599b83", size = 20552510 }, { url = "https://files.pythonhosted.org/packages/ff/ff/847841bacfbefc97a00036e0fce5a0f086b640756dc38caea5e1bb002655/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57", size = 21066810 }, ] @@ -4417,7 +4408,6 @@ name = "nvidia-nvtx-cu12" version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/39/471f581edbb7804b39e8063d92fc8305bdc7a80ae5c07dbe6ea5c50d14a5/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3", size = 100417 }, { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 }, ] From 65ea8a6df5512f9e39b518f0883c621c66a08b62 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 12:12:22 -0800 Subject: [PATCH 46/53] one more move --- .../Helpers}/AgentTypes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename dotnet/{src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions => test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers}/AgentTypes.cs (92%) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentTypes.cs b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/AgentTypes.cs similarity index 92% rename from dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentTypes.cs rename to dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/AgentTypes.cs index aa0defe005ba..d8e286b1fbb3 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentTypes.cs +++ b/dotnet/test/Microsoft.AutoGen.RuntimeGateway.Grpc.Tests/Helpers/AgentTypes.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AgentTypes.cs using Microsoft.AutoGen.Core; -namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; +namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Tests; public sealed class AgentTypes(Dictionary types) { public Dictionary Types { get; } = types; From 8d5152a457ed9862ac7320d4002590b5ad94c43e Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 12:22:50 -0800 Subject: [PATCH 47/53] cleaning up unused --- .../Abstractions/AgentsRegistryState.cs | 1 - .../Abstractions/IGateway.cs | 3 --- .../Services/Grpc/GrpcGateway.cs | 23 +------------------ 3 files changed, 1 insertion(+), 26 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs index aaea55e02478..2e573db4e068 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs @@ -7,7 +7,6 @@ namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; public class AgentsRegistryState { - public ConcurrentDictionary> AgentsToEventsMap { get; set; } = new ConcurrentDictionary>(); public ConcurrentDictionary> AgentsToTopicsMap { get; set; } = []; public ConcurrentDictionary> AgentsToTopicsPrefixMap { get; set; } = []; public ConcurrentDictionary> TopicToAgentTypesMap { get; set; } = []; diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs index 7db9b2b04283..b8aeae041e86 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IGateway.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // IGateway.cs using Grpc.Core; -using Microsoft.AutoGen.Contracts; using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; @@ -9,10 +8,8 @@ namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; public interface IGateway : IGrainObserver { ValueTask InvokeRequestAsync(RpcRequest request); - ValueTask BroadcastEventAsync(CloudEvent evt); ValueTask RegisterAgentTypeAsync(RegisterAgentTypeRequest request, ServerCallContext context); ValueTask SubscribeAsync(AddSubscriptionRequest request); ValueTask UnsubscribeAsync(RemoveSubscriptionRequest request); ValueTask> GetSubscriptionsAsync(GetSubscriptionsRequest request); - Task WriteResponseAsync(IConnection connection, CloudEvent cloudEvent); } diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs index d9ec82f36133..f46f57d47960 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs @@ -183,11 +183,6 @@ async ValueTask IGateway.InvokeRequestAsync(RpcRequest request) return await InvokeRequestAsync(request, default).ConfigureAwait(false); } - async ValueTask IGateway.BroadcastEventAsync(CloudEvent evt) - { - await BroadcastEventAsync(evt, default).ConfigureAwait(false); - } - ValueTask IGateway.RegisterAgentTypeAsync(RegisterAgentTypeRequest request, ServerCallContext context) { return RegisterAgentTypeAsync(request, context, default); @@ -411,22 +406,6 @@ private async ValueTask DispatchEventToAgentsAsync(IEnumerable agentType await Task.WhenAll(tasks).ConfigureAwait(false); } - /// - /// Broadcasts an event to all workers. - /// - /// The cloud event. - /// The cancellation token. - /// A task that represents the asynchronous operation. - public async ValueTask BroadcastEventAsync(CloudEvent evt, CancellationToken cancellationToken = default) - { - var tasks = new List(_workers.Count); - foreach (var (_, connection) in _supportedAgentTypes) - { - tasks.Add(this.WriteResponseAsync((GrpcWorkerConnection)connection[0], evt, default)); - } - await Task.WhenAll(tasks).ConfigureAwait(false); - } - /// /// Writes a response to a worker connection. /// @@ -434,7 +413,7 @@ public async ValueTask BroadcastEventAsync(CloudEvent evt, CancellationToken can /// The cloud event. /// The cancellation token. /// A task that represents the asynchronous operation. - public async Task WriteResponseAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent, CancellationToken cancellationToken = default) + private async Task WriteResponseAsync(GrpcWorkerConnection connection, CloudEvent cloudEvent, CancellationToken cancellationToken = default) { await connection.ResponseStream.WriteAsync(new Message { CloudEvent = cloudEvent }, cancellationToken).ConfigureAwait(false); } From 207794f25d1bcdbd432670f7af9b9903d053af7c Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 12:30:16 -0800 Subject: [PATCH 48/53] rename variable for clarity --- .../RuntimeGateway.Grpc/Abstractions/IRegistry.cs | 4 ++-- .../RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs index 2c2d47297982..215ace6e5dda 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistry.cs @@ -10,9 +10,9 @@ public interface IRegistry /// Gets a list of agents subscribed to and handling the specified topic and event type. /// /// The topic to check subscriptions for. - /// The event type to check subscriptions for. + /// The event type to check subscriptions for. /// A task representing the asynchronous operation, with the list of agent IDs as the result. - ValueTask> GetSubscribedAndHandlingAgentsAsync(string topic, string eventType); + ValueTask> GetSubscribedAndHandlingAgentsAsync(string topic, string key); /// /// Subscribes an agent to a topic. diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs index b41815591ed8..522c20614606 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Orleans/RegistryGrain.cs @@ -16,7 +16,7 @@ public override Task OnActivateAsync(CancellationToken cancellationToken) this.RegisterGrainTimer(static state => state.PurgeInactiveWorkers(), this, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30)); return base.OnActivateAsync(cancellationToken); } - public ValueTask> GetSubscribedAndHandlingAgentsAsync(string topic, string eventType) + public ValueTask> GetSubscribedAndHandlingAgentsAsync(string topic, string key) { List agents = []; // get all agent types that are subscribed to the topic @@ -29,11 +29,11 @@ public ValueTask> GetSubscribedAndHandlingAgentsAsync(string topic, }*/ agents.AddRange(subscribedAgentTypes.ToList()); } - if (state.State.TopicToAgentTypesMap.TryGetValue(eventType, out var eventHandlingAgents)) + if (state.State.TopicToAgentTypesMap.TryGetValue(key, out var eventHandlingAgents)) { agents.AddRange(eventHandlingAgents.ToList()); } - if (state.State.TopicToAgentTypesMap.TryGetValue(topic + "." + eventType, out var combo)) + if (state.State.TopicToAgentTypesMap.TryGetValue(topic + "." + key, out var combo)) { agents.AddRange(combo.ToList()); } From dbe744cc4eec0374bed478d55da98c02142c1dd2 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 12:30:58 -0800 Subject: [PATCH 49/53] remove unused --- .../Abstractions/TopicSubscriptionAttribute.cs | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/TopicSubscriptionAttribute.cs diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/TopicSubscriptionAttribute.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/TopicSubscriptionAttribute.cs deleted file mode 100644 index 585da97c8f59..000000000000 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/TopicSubscriptionAttribute.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// TopicSubscriptionAttribute.cs - -namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; - -[AttributeUsage(AttributeTargets.All)] -public class TopicSubscriptionAttribute(string topic) : Attribute -{ - public string Topic { get; } = topic; -} From f97d8c3f5e591b085042c93e3d3827bfd4682aa1 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 12:32:09 -0800 Subject: [PATCH 50/53] remove unused --- .../Abstractions/IRegistryStorage.cs | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryStorage.cs diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryStorage.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryStorage.cs deleted file mode 100644 index 5e57c3b75a39..000000000000 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/IRegistryStorage.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// IRegistryStorage.cs - -namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; - -public interface IRegistryStorage -{ - /// - /// Populates the Registry state from the storage. - /// - /// - Task ReadStateAsync(CancellationToken cancellationToken = default); - /// - /// Writes the Registry state to the storage. - /// - /// - /// - /// the etag that was written - ValueTask WriteStateAsync(AgentsRegistryState state, CancellationToken cancellationToken = default); -} From ded46df8325e1143f8360bdd88bf329fc2a43408 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 12:54:58 -0800 Subject: [PATCH 51/53] remove unused --- .../RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs index f46f57d47960..e657ac36fef0 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Services/Grpc/GrpcGateway.cs @@ -23,7 +23,6 @@ public sealed class GrpcGateway : BackgroundService, IGateway private readonly IGateway _reference; private readonly ConcurrentDictionary> _supportedAgentTypes = []; public readonly ConcurrentDictionary _workers = new(); - internal readonly ConcurrentDictionary _workersByConnection = new(); private readonly ConcurrentDictionary<(string Type, string Key), GrpcWorkerConnection> _agentDirectory = new(); private readonly ConcurrentDictionary<(GrpcWorkerConnection, string), TaskCompletionSource> _pendingRequests = new(); From 57b36a6733e85f47ac40b9bddcbad595816268fa Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 12:58:42 -0800 Subject: [PATCH 52/53] remove unused --- .../RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs index 2e573db4e068..2baa70b33ef8 100644 --- a/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs +++ b/dotnet/src/Microsoft.AutoGen/RuntimeGateway.Grpc/Abstractions/AgentsRegistryState.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // AgentsRegistryState.cs using System.Collections.Concurrent; -using Microsoft.AutoGen.Contracts; using Microsoft.AutoGen.Protobuf; namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions; @@ -11,8 +10,6 @@ public class AgentsRegistryState public ConcurrentDictionary> AgentsToTopicsPrefixMap { get; set; } = []; public ConcurrentDictionary> TopicToAgentTypesMap { get; set; } = []; public ConcurrentDictionary> TopicPrefixToAgentTypesMap { get; set; } = []; - public ConcurrentDictionary> EventsToAgentTypesMap { get; set; } = []; public ConcurrentDictionary> GuidSubscriptionsMap { get; set; } = []; - public ConcurrentDictionary> AgentTypes { get; set; } = []; public string Etag { get; set; } = Guid.NewGuid().ToString(); } From 9d018e5b212e26d3869019d2bec81dd5e66b8343 Mon Sep 17 00:00:00 2001 From: Ryan Sweet Date: Fri, 7 Feb 2025 15:02:54 -0800 Subject: [PATCH 53/53] add test app host back into sln --- dotnet/AutoGen.sln | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln index c49229c0640b..92ad4cc77629 100644 --- a/dotnet/AutoGen.sln +++ b/dotnet/AutoGen.sln @@ -136,6 +136,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.AutoGen", "Micros EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.Agents", "src\Microsoft.AutoGen\Agents\Microsoft.AutoGen.Agents.csproj", "{EF954ED3-87D5-40F1-8557-E7179F43EA0E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloAgent.AppHost", "test\Microsoft.AutoGen.Integration.Tests.AppHosts\HelloAgent.AppHost\HelloAgent.AppHost.csproj", "{50082F76-917F-42EE-8869-8C72630423A7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -354,6 +356,10 @@ Global {EF954ED3-87D5-40F1-8557-E7179F43EA0E}.Debug|Any CPU.Build.0 = Debug|Any CPU {EF954ED3-87D5-40F1-8557-E7179F43EA0E}.Release|Any CPU.ActiveCfg = Release|Any CPU {EF954ED3-87D5-40F1-8557-E7179F43EA0E}.Release|Any CPU.Build.0 = Release|Any CPU + {50082F76-917F-42EE-8869-8C72630423A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50082F76-917F-42EE-8869-8C72630423A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50082F76-917F-42EE-8869-8C72630423A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50082F76-917F-42EE-8869-8C72630423A7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -416,6 +422,7 @@ Global {B8E77E57-C983-4EEA-9589-906271486D80} = {F42F9C8E-7BD9-4687-9B63-AFFA461AF5C1} {81BA12F2-2D2F-42C1-AF83-FBDAA1A78A45} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} {EF954ED3-87D5-40F1-8557-E7179F43EA0E} = {18BF8DD7-0585-48BF-8F97-AD333080CE06} + {50082F76-917F-42EE-8869-8C72630423A7} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B}