diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs index 1e6190ca6b..e89888da9e 100644 --- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs +++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs @@ -31,6 +31,16 @@ internal sealed class UpperCaseParrotAgent : AIAgent public override ValueTask CreateSessionAsync(CancellationToken cancellationToken = default) => new(new CustomAgentSession()); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + if (session is not CustomAgentSession typedSession) + { + throw new ArgumentException($"The provided session is not of type {nameof(CustomAgentSession)}.", nameof(session)); + } + + return typedSession.Serialize(jsonSerializerOptions); + } + public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new CustomAgentSession(serializedSession, jsonSerializerOptions)); @@ -136,6 +146,9 @@ internal CustomAgentSession() { } internal CustomAgentSession(JsonElement serializedSessionState, JsonSerializerOptions? jsonSerializerOptions = null) : base(serializedSessionState, jsonSerializerOptions) { } + + internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + => base.Serialize(jsonSerializerOptions); } } } diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs index 30cccde55a..4a0dbe0839 100644 --- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs @@ -55,7 +55,7 @@ Console.WriteLine(await agent.RunAsync("What do you already know about my upcoming trip?", session)); Console.WriteLine("\n>> Serialize and deserialize the session to demonstrate persisted state\n"); -JsonElement serializedSession = session.Serialize(); +JsonElement serializedSession = agent.SerializeSession(session); AgentSession restoredSession = await agent.DeserializeSessionAsync(serializedSession); Console.WriteLine(await agent.RunAsync("Can you recap the personal details you remember?", restoredSession)); diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs index 42a5e15b64..509b79e53f 100644 --- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs @@ -47,7 +47,7 @@ Console.WriteLine(await agent.RunAsync("I am 20 years old", session)); // We can serialize the session. The serialized state will include the state of the memory component. -var sesionElement = session.Serialize(); +JsonElement sesionElement = agent.SerializeSession(session); Console.WriteLine("\n>> Use deserialized session with previously created memories\n"); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs index 84ba3a918d..8acbff2690 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs @@ -25,7 +25,7 @@ Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session)); // Serialize the session state to a JsonElement, so it can be stored for later use. -JsonElement serializedSession = session.Serialize(); +JsonElement serializedSession = agent.SerializeSession(session); // Save the serialized session to a temporary file (for demonstration purposes). string tempFilePath = Path.GetTempFileName(); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs index a1898ea426..148402c7f3 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs @@ -47,7 +47,7 @@ // Serialize the session state, so it can be stored for later use. // Since the chat history is stored in the vector store, the serialized session // only contains the guid that the messages are stored under in the vector store. -JsonElement serializedSession = session.Serialize(); +JsonElement serializedSession = agent.SerializeSession(session); Console.WriteLine("\n--- Serialized session ---\n"); Console.WriteLine(JsonSerializer.Serialize(serializedSession, new JsonSerializerOptions { WriteIndented = true })); diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs index 5dfae17df0..2104ba536b 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs @@ -40,7 +40,7 @@ // Poll for background responses until complete. while (response.ContinuationToken is not null) { - PersistAgentState(session, response.ContinuationToken); + PersistAgentState(agent, session, response.ContinuationToken); await Task.Delay(TimeSpan.FromSeconds(10)); @@ -52,9 +52,9 @@ Console.WriteLine(response.Text); -void PersistAgentState(AgentSession? session, ResponseContinuationToken? continuationToken) +void PersistAgentState(AIAgent agent, AgentSession? session, ResponseContinuationToken? continuationToken) { - stateStore["session"] = session!.Serialize(); + stateStore["session"] = agent.SerializeSession(session!); stateStore["continuationToken"] = JsonSerializer.SerializeToElement(continuationToken, AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken))); } diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs index 52e5c37cb8..28b9780d17 100644 --- a/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs +++ b/dotnet/samples/GettingStarted/Agents/Agent_Step20_AdditionalAIContext/Program.cs @@ -65,7 +65,7 @@ You remind users of upcoming calendar events when the user interacts with you. Console.WriteLine(await agent.RunAsync("I've taken Sally to soccer practice.", session) + "\n"); // We can serialize the session, and it will contain both the chat history and the data that each AI context provider serialized. -JsonElement serializedSession = session.Serialize(); +JsonElement serializedSession = agent.SerializeSession(session); // Let's print it to console to show the contents. Console.WriteLine(JsonSerializer.Serialize(serializedSession, options: new JsonSerializerOptions() { WriteIndented = true, IndentSize = 2 }) + "\n"); // The serialized session can be stored long term in a persistent store, but in this case we will just deserialize again and continue the conversation. diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs index 20e3471df0..7e839bce95 100644 --- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs +++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs @@ -25,7 +25,7 @@ Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate.", session)); // Serialize the session state to a JsonElement, so it can be stored for later use. -JsonElement serializedSession = session.Serialize(); +JsonElement serializedSession = agent.SerializeSession(session); // Save the serialized session to a temporary file (for demonstration purposes). string tempFilePath = Path.GetTempFileName(); diff --git a/dotnet/samples/M365Agent/AFAgentApplication.cs b/dotnet/samples/M365Agent/AFAgentApplication.cs index 4dea6c6e28..da98150c6d 100644 --- a/dotnet/samples/M365Agent/AFAgentApplication.cs +++ b/dotnet/samples/M365Agent/AFAgentApplication.cs @@ -80,7 +80,7 @@ private async Task MessageActivityAsync(ITurnContext turnContext, ITurnState tur } // Serialize and save the updated conversation history back to turn state. - JsonElement sessionElementEnd = agentSession.Serialize(JsonUtilities.DefaultOptions); + JsonElement sessionElementEnd = this._agent.SerializeSession(agentSession, JsonUtilities.DefaultOptions); turnState.SetValue("conversation.chatHistory", sessionElementEnd); // End the streaming response diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs b/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs index 3ccc9f481e..30676a2336 100644 --- a/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs @@ -65,6 +65,19 @@ public sealed override ValueTask CreateSessionAsync(CancellationTo public ValueTask CreateSessionAsync(string contextId) => new(new A2AAgentSession() { ContextId = contextId }); + /// + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + _ = Throw.IfNull(session); + + if (session is not A2AAgentSession typedSession) + { + throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); + } + + return typedSession.Serialize(jsonSerializerOptions); + } + /// public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new A2AAgentSession(serializedSession, jsonSerializerOptions)); diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgentSession.cs b/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgentSession.cs index 6b415b5b1a..cac9b43a30 100644 --- a/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgentSession.cs +++ b/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgentSession.cs @@ -46,7 +46,7 @@ internal A2AAgentSession(JsonElement serializedSessionState, JsonSerializerOptio public string? TaskId { get; internal set; } /// - public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { var state = new A2AAgentSessionState { diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs index 31f4993723..537df0fc8f 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs @@ -125,6 +125,21 @@ public abstract class AIAgent /// public abstract ValueTask CreateSessionAsync(CancellationToken cancellationToken = default); + /// + /// Serializes an agent session to its JSON representation. + /// + /// The to serialize. + /// Optional settings to customize the serialization process. + /// A containing the serialized session state. + /// is . + /// The type of is not supported by this agent. + /// + /// This method enables saving conversation sessions to persistent storage, + /// allowing conversations to resume across application restarts or be migrated between + /// different agent instances. Use to restore the session. + /// + public abstract JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null); + /// /// Deserializes an agent session from its JSON serialized representation. /// diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentSession.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentSession.cs index 5bcea2239d..3efce9be17 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentSession.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentSession.cs @@ -36,7 +36,7 @@ namespace Microsoft.Agents.AI; /// /// To support conversations that may need to survive application restarts or separate service requests, an can be serialized /// and deserialized, so that it can be saved in a persistent store. -/// The provides the method to serialize the session to a +/// The provides the method to serialize the session to a /// and the method /// can be used to deserialize the session. /// @@ -53,14 +53,6 @@ protected AgentSession() { } - /// - /// Serializes the current object's state to a using the specified serialization options. - /// - /// The JSON serialization options to use. - /// A representation of the object's state. - public virtual JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) - => default; - /// Asks the for an object of the specified type . /// The type of object being requested. /// An optional key that can be used to help identify the target service. diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/DelegatingAIAgent.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/DelegatingAIAgent.cs index 99979871b1..6afc75b1d5 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/DelegatingAIAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/DelegatingAIAgent.cs @@ -76,6 +76,10 @@ protected DelegatingAIAgent(AIAgent innerAgent) /// public override ValueTask CreateSessionAsync(CancellationToken cancellationToken = default) => this.InnerAgent.CreateSessionAsync(cancellationToken); + /// + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + => this.InnerAgent.SerializeSession(session, jsonSerializerOptions); + /// public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => this.InnerAgent.DeserializeSessionAsync(serializedSession, jsonSerializerOptions, cancellationToken); diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryAgentSession.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryAgentSession.cs index f74c4b72a8..f2077cd844 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryAgentSession.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryAgentSession.cs @@ -98,7 +98,7 @@ protected InMemoryAgentSession( /// /// The JSON serialization options to use. /// A representation of the object's state. - public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + protected internal virtual JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { var chatHistoryProviderState = this.ChatHistoryProvider.Serialize(jsonSerializerOptions); diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/ServiceIdAgentSession.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/ServiceIdAgentSession.cs index 3701c75c1d..36557d7204 100644 --- a/dotnet/src/Microsoft.Agents.AI.Abstractions/ServiceIdAgentSession.cs +++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/ServiceIdAgentSession.cs @@ -85,13 +85,9 @@ protected ServiceIdAgentSession( /// /// Serializes the current object's state to a using the specified serialization options. /// - /// The JSON serialization options to use for the serialization process. - /// A representation of the object's state, containing the service session identifier. - /// - /// The serialized state contains only the service session identifier, as all other conversation state - /// is maintained remotely by the backing service. This makes the serialized representation very lightweight. - /// - public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + /// The JSON serialization options to use. + /// A representation of the object's state. + protected internal virtual JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { var state = new ServiceIdAgentSessionState { diff --git a/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgent.cs b/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgent.cs index 378c36298b..100417f9a6 100644 --- a/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgent.cs @@ -53,6 +53,19 @@ public sealed override ValueTask CreateSessionAsync(CancellationTo public ValueTask CreateSessionAsync(string conversationId) => new(new CopilotStudioAgentSession() { ConversationId = conversationId }); + /// + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + Throw.IfNull(session); + + if (session is not CopilotStudioAgentSession typedSession) + { + throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); + } + + return typedSession.Serialize(jsonSerializerOptions); + } + /// public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new CopilotStudioAgentSession(serializedSession, jsonSerializerOptions)); diff --git a/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgentSession.cs b/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgentSession.cs index 66f9d02533..ec3e21ca91 100644 --- a/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgentSession.cs +++ b/dotnet/src/Microsoft.Agents.AI.CopilotStudio/CopilotStudioAgentSession.cs @@ -25,4 +25,12 @@ public string? ConversationId get { return this.ServiceSessionId; } internal set { this.ServiceSessionId = value; } } + + /// + /// Serializes the current object's state to a using the specified serialization options. + /// + /// The JSON serialization options to use. + /// A representation of the object's state. + internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + => base.Serialize(jsonSerializerOptions); } diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md b/dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md index 2c1460b213..c0702abdce 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/CHANGELOG.md @@ -8,6 +8,7 @@ - Switch to new "Run" method name ([#2843](https://github.com/microsoft/agent-framework/pull/2843)) - Removed AgentThreadMetadata and used AgentSessionId directly instead ([#3067](https://github.com/microsoft/agent-framework/pull/3067)); - Renamed AgentThread to AgentSession ([#3430](https://github.com/microsoft/agent-framework/pull/3430)) +- Moved AgentSession.Serialize to AIAgent.SerializeSession ([#3650](https://github.com/microsoft/agent-framework/pull/3650)) ## v1.0.0-preview.251204.1 diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs index a97652bd93..5e9f923f96 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs @@ -40,6 +40,27 @@ public override ValueTask CreateSessionAsync(CancellationToken can return ValueTask.FromResult(new DurableAgentSession(sessionId)); } + /// + /// Serializes an agent session to JSON. + /// + /// The session to serialize. + /// Optional JSON serializer options. + /// A containing the serialized session state. + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + if (session is null) + { + throw new ArgumentNullException(nameof(session)); + } + + if (session is not DurableAgentSession durableSession) + { + throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); + } + + return durableSession.Serialize(jsonSerializerOptions); + } + /// /// Deserializes an agent session from JSON. /// diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgentProxy.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgentProxy.cs index b6d3fa4900..0a1be7028e 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgentProxy.cs +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgentProxy.cs @@ -11,6 +11,21 @@ internal class DurableAIAgentProxy(string name, IDurableAgentClient agentClient) public override string? Name { get; } = name; + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + if (session is null) + { + throw new ArgumentNullException(nameof(session)); + } + + if (session is not DurableAgentSession durableSession) + { + throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); + } + + return durableSession.Serialize(jsonSerializerOptions); + } + public override ValueTask DeserializeSessionAsync( JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAgentSession.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAgentSession.cs index 6bd1c821d2..b9d9807728 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAgentSession.cs +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAgentSession.cs @@ -26,7 +26,7 @@ internal DurableAgentSession(AgentSessionId sessionId) internal AgentSessionId SessionId { get; } /// - public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { return JsonSerializer.SerializeToElement( this, diff --git a/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs b/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs index e2771e21c4..eb4b804ff8 100644 --- a/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs @@ -97,6 +97,19 @@ public sealed override ValueTask CreateSessionAsync(CancellationTo public ValueTask CreateSessionAsync(string sessionId) => new(new GitHubCopilotAgentSession() { SessionId = sessionId }); + /// + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + _ = Throw.IfNull(session); + + if (session is not GitHubCopilotAgentSession typedSession) + { + throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); + } + + return typedSession.Serialize(jsonSerializerOptions); + } + /// public override ValueTask DeserializeSessionAsync( JsonElement serializedSession, diff --git a/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgentSession.cs b/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgentSession.cs index 7d5d8f773a..f514eeb71b 100644 --- a/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgentSession.cs +++ b/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgentSession.cs @@ -36,7 +36,7 @@ internal GitHubCopilotAgentSession(JsonElement serializedThread, JsonSerializerO } /// - public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { State state = new() { diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting/Local/InMemoryAgentSessionStore.cs b/dotnet/src/Microsoft.Agents.AI.Hosting/Local/InMemoryAgentSessionStore.cs index d66192c709..26a07ce573 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting/Local/InMemoryAgentSessionStore.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting/Local/InMemoryAgentSessionStore.cs @@ -33,7 +33,7 @@ public sealed class InMemoryAgentSessionStore : AgentSessionStore public override ValueTask SaveSessionAsync(AIAgent agent, string conversationId, AgentSession session, CancellationToken cancellationToken = default) { var key = GetKey(conversationId, agent.Id); - this._threads[key] = session.Serialize(); + this._threads[key] = agent.SerializeSession(session); return default; } diff --git a/dotnet/src/Microsoft.Agents.AI.Purview/PurviewAgent.cs b/dotnet/src/Microsoft.Agents.AI.Purview/PurviewAgent.cs index a08d978fa8..21343912af 100644 --- a/dotnet/src/Microsoft.Agents.AI.Purview/PurviewAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.Purview/PurviewAgent.cs @@ -29,6 +29,12 @@ public PurviewAgent(AIAgent innerAgent, PurviewWrapper purviewWrapper) this._purviewWrapper = purviewWrapper; } + /// + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + return this._innerAgent.SerializeSession(session, jsonSerializerOptions); + } + /// public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs index 3562747b7c..97c493d045 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs @@ -101,7 +101,8 @@ private async ValueTask EnsureSessionAsync(IWorkflowContext contex protected internal override async ValueTask OnCheckpointingAsync(IWorkflowContext context, CancellationToken cancellationToken = default) { - AIAgentHostState state = new(this._session?.Serialize(), this._currentTurnEmitEvents); + JsonElement? sessionState = this._session is not null ? this._agent.SerializeSession(this._session) : null; + AIAgentHostState state = new(sessionState, this._currentTurnEmitEvents); Task coreStateTask = context.QueueStateUpdateAsync(AIAgentHostStateKey, state, cancellationToken: cancellationToken).AsTask(); Task userInputRequestsTask = this._userInputHandler?.OnCheckpointingAsync(UserInputRequestStateKey, context, cancellationToken).AsTask() ?? Task.CompletedTask; Task functionCallRequestsTask = this._functionCallHandler?.OnCheckpointingAsync(FunctionCallRequestStateKey, context, cancellationToken).AsTask() ?? Task.CompletedTask; diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs index 86c725bca1..3c7ff5542f 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowHostAgent.cs @@ -68,6 +68,18 @@ private async ValueTask ValidateWorkflowAsync() public override ValueTask CreateSessionAsync(CancellationToken cancellationToken = default) => new(new WorkflowSession(this._workflow, this.GenerateNewId(), this._executionEnvironment, this._checkpointManager, this._includeExceptionDetails, this._includeWorkflowOutputsInResponse)); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + _ = Throw.IfNull(session); + + if (session is not WorkflowSession workflowSession) + { + throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); + } + + return workflowSession.Serialize(jsonSerializerOptions); + } + public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new WorkflowSession(this._workflow, serializedSession, this._executionEnvironment, this._checkpointManager, this._includeExceptionDetails, this._includeWorkflowOutputsInResponse, jsonSerializerOptions)); diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs index d7dc1c74ce..7730067e60 100644 --- a/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs +++ b/dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowSession.cs @@ -75,7 +75,7 @@ public WorkflowSession(Workflow workflow, JsonElement serializedSession, IWorkfl public CheckpointInfo? LastCheckpoint { get; set; } - public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { JsonMarshaller marshaller = new(jsonSerializerOptions); SessionState info = new( diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs index 504928d2d6..8c0d2feaa2 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs @@ -385,6 +385,19 @@ public async ValueTask CreateSessionAsync(ChatHistoryProvider chat }; } + /// + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + _ = Throw.IfNull(session); + + if (session is not ChatClientAgentSession typedSession) + { + throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); + } + + return typedSession.Serialize(jsonSerializerOptions); + } + /// public override async ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) { diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentSession.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentSession.cs index e4b28caf30..e5edb3b629 100644 --- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentSession.cs +++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentSession.cs @@ -165,7 +165,7 @@ chatHistoryProviderFactory is not null } /// - public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + internal JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { JsonElement? chatHistoryProviderState = this._chatHistoryProvider?.Serialize(jsonSerializerOptions); diff --git a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs index decfb93a31..42c64dfeec 100644 --- a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs @@ -251,7 +251,7 @@ public async Task DeserializeSession_WithValidState_ReturnsChatClientAgentSessio var chatClient = new AGUIChatClient(httpClient, "http://localhost/agent", null, AGUIJsonSerializerContext.Default.Options); AIAgent agent = chatClient.AsAIAgent(instructions: null, name: "agent1", description: "Test agent", tools: []); AgentSession originalSession = await agent.CreateSessionAsync(); - JsonElement serialized = originalSession.Serialize(); + JsonElement serialized = agent.SerializeSession(originalSession); // Act AgentSession deserialized = await agent.DeserializeSessionAsync(serialized); diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs index 17445eaffa..cd2cbd4700 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs @@ -381,6 +381,9 @@ public MockAgent(string? id = null) public override async ValueTask CreateSessionAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + => throw new NotImplementedException(); + public override async ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentSessionTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentSessionTests.cs index b473b713ab..5a776c9fb0 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentSessionTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentSessionTests.cs @@ -11,14 +11,6 @@ namespace Microsoft.Agents.AI.Abstractions.UnitTests; /// public class AgentSessionTests { - [Fact] - public void Serialize_ReturnsDefaultJsonElement() - { - var session = new TestAgentSession(); - var result = session.Serialize(); - Assert.Equal(default, result); - } - #region GetService Method Tests /// diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs index 930557ab79..a0e3efcbb9 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs @@ -71,6 +71,11 @@ public override ValueTask DeserializeSessionAsync(JsonElement seri throw new NotImplementedException(); } + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + throw new NotImplementedException(); + } + public override ValueTask CreateSessionAsync(CancellationToken cancellationToken = default) { throw new NotImplementedException(); diff --git a/dotnet/tests/Microsoft.Agents.AI.DurableTask.UnitTests/DurableAgentSessionTests.cs b/dotnet/tests/Microsoft.Agents.AI.DurableTask.UnitTests/DurableAgentSessionTests.cs index 218608b179..4bf8ebc718 100644 --- a/dotnet/tests/Microsoft.Agents.AI.DurableTask.UnitTests/DurableAgentSessionTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.DurableTask.UnitTests/DurableAgentSessionTests.cs @@ -10,7 +10,7 @@ public sealed class DurableAgentSessionTests public void BuiltInSerialization() { AgentSessionId sessionId = AgentSessionId.WithRandomKey("test-agent"); - AgentSession session = new DurableAgentSession(sessionId); + DurableAgentSession session = new(sessionId); JsonElement serializedSession = session.Serialize(); diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs index aee568204e..8f2770679a 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs @@ -286,6 +286,9 @@ public override ValueTask CreateSessionAsync(CancellationToken can public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new FakeInMemoryAgentSession(serializedSession, jsonSerializerOptions)); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + => throw new NotImplementedException(); + protected override async Task RunCoreAsync( IEnumerable messages, AgentSession? session = null, @@ -350,6 +353,16 @@ public override ValueTask CreateSessionAsync(CancellationToken can public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new FakeInMemoryAgentSession(serializedSession, jsonSerializerOptions)); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + if (session is not FakeInMemoryAgentSession fakeSession) + { + throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); + } + + return fakeSession.Serialize(jsonSerializerOptions); + } + protected override async Task RunCoreAsync( IEnumerable messages, AgentSession? session = null, @@ -425,6 +438,8 @@ public FakeInMemoryAgentSession(JsonElement serializedSession, JsonSerializerOpt : base(serializedSession, jsonSerializerOptions) { } + internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + => base.Serialize(jsonSerializerOptions); } public override object? GetService(Type serviceType, object? serviceKey = null) => null; diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs index 92c83e06f5..4fad7ff1da 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs @@ -340,6 +340,16 @@ public override ValueTask CreateSessionAsync(CancellationToken can public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new FakeInMemoryAgentSession(serializedSession, jsonSerializerOptions)); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + if (session is not FakeInMemoryAgentSession fakeSession) + { + throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); + } + + return fakeSession.Serialize(jsonSerializerOptions); + } + private sealed class FakeInMemoryAgentSession : InMemoryAgentSession { public FakeInMemoryAgentSession() @@ -351,6 +361,9 @@ public FakeInMemoryAgentSession(JsonElement serializedSession, JsonSerializerOpt : base(serializedSession, jsonSerializerOptions) { } + + internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + => base.Serialize(jsonSerializerOptions); } public override object? GetService(Type serviceType, object? serviceKey = null) => null; diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs index 2d8742e930..03102bf0ca 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs @@ -423,6 +423,16 @@ public override ValueTask CreateSessionAsync(CancellationToken can public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new FakeInMemoryAgentSession(serializedSession, jsonSerializerOptions)); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + if (session is not FakeInMemoryAgentSession fakeSession) + { + throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); + } + + return fakeSession.Serialize(jsonSerializerOptions); + } + private sealed class FakeInMemoryAgentSession : InMemoryAgentSession { public FakeInMemoryAgentSession() @@ -434,6 +444,9 @@ public FakeInMemoryAgentSession(JsonElement serializedSession, JsonSerializerOpt : base(serializedSession, jsonSerializerOptions) { } + + internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + => base.Serialize(jsonSerializerOptions); } public override object? GetService(Type serviceType, object? serviceKey = null) => null; diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs index 544002f34b..d3ae1e5d72 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs @@ -431,6 +431,16 @@ public override ValueTask CreateSessionAsync(CancellationToken can public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new TestInMemoryAgentSession(serializedSession, jsonSerializerOptions)); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + if (session is not TestInMemoryAgentSession testSession) + { + throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); + } + + return testSession.Serialize(jsonSerializerOptions); + } + protected override Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { throw new NotImplementedException(); @@ -507,6 +517,9 @@ public TestInMemoryAgentSession(JsonElement serializedSessionState, JsonSerializ : base(serializedSessionState, jsonSerializerOptions, null) { } + + internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + => base.Serialize(jsonSerializerOptions); } private sealed class TestAgent : AIAgent @@ -521,6 +534,16 @@ public override ValueTask CreateSessionAsync(CancellationToken can public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new TestInMemoryAgentSession(serializedSession, jsonSerializerOptions)); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + if (session is not TestInMemoryAgentSession testSession) + { + throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); + } + + return testSession.Serialize(jsonSerializerOptions); + } + protected override Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { throw new NotImplementedException(); diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/TestAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/TestAgent.cs index a597b26304..8ece75304d 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/TestAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/TestAgent.cs @@ -13,6 +13,9 @@ internal sealed class TestAgent(string name, string description) : AIAgent public override ValueTask CreateSessionAsync(CancellationToken cancellationToken = default) => new(new DummyAgentSession()); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + => throw new NotImplementedException(); + public override ValueTask DeserializeSessionAsync( JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new DummyAgentSession()); diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentExtensionsTests.cs index 569d2c421d..e703976a1e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentExtensionsTests.cs @@ -385,6 +385,9 @@ public TestAgent(string? name, string? description, Exception exceptionToThrow) public override ValueTask CreateSessionAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + => throw new NotImplementedException(); + public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/TestAIAgent.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/TestAIAgent.cs index cadc7bdc38..a394a8967e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/TestAIAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/TestAIAgent.cs @@ -24,6 +24,9 @@ internal sealed class TestAIAgent : AIAgent public override string? Description => this.DescriptionFunc?.Invoke() ?? base.Description; + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + => throw new NotImplementedException(); + public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(this.DeserializeSessionFunc(serializedSession, jsonSerializerOptions)); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs index 59636d519d..8fb1502fda 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs @@ -141,6 +141,9 @@ public override ValueTask CreateSessionAsync(CancellationToken can public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new DoubleEchoAgentSession()); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + => default; + protected override Task RunCoreAsync( IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs index bb7dbf2dda..d24f6e263a 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessExecutionTests.cs @@ -149,6 +149,9 @@ public SimpleTestAgent(string name) public override ValueTask DeserializeSessionAsync(System.Text.Json.JsonElement serializedSession, System.Text.Json.JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new SimpleTestAgentSession()); + public override System.Text.Json.JsonElement SerializeSession(AgentSession session, System.Text.Json.JsonSerializerOptions? jsonSerializerOptions = null) + => default; + protected override Task RunCoreAsync( IEnumerable messages, AgentSession? session = null, diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs index c6f48b2724..b267eb7027 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs @@ -30,6 +30,9 @@ public override ValueTask CreateSessionAsync(CancellationToken can public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + => throw new NotImplementedException(); + protected override Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RoleCheckAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RoleCheckAgent.cs index 611ae3caa2..dabfc149a3 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RoleCheckAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RoleCheckAgent.cs @@ -19,6 +19,9 @@ internal sealed class RoleCheckAgent(bool allowOtherAssistantRoles, string? id = public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new RoleCheckAgentSession()); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + => default; + public override ValueTask CreateSessionAsync(CancellationToken cancellationToken = default) => new(new RoleCheckAgentSession()); protected override Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/06_GroupChat_Workflow.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/06_GroupChat_Workflow.cs index 7941dd6b7a..a19a70345b 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/06_GroupChat_Workflow.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/06_GroupChat_Workflow.cs @@ -66,6 +66,9 @@ public override ValueTask CreateSessionAsync(CancellationToken can public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new HelloAgentSession()); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + => default; + protected override async Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { IEnumerable update = [ diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs index e6592605f1..13cc95e992 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestEchoAgent.cs @@ -21,6 +21,16 @@ public override async ValueTask DeserializeSessionAsync(JsonElemen return serializedSession.Deserialize(jsonSerializerOptions) ?? await this.CreateSessionAsync(cancellationToken); } + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + { + if (session is not EchoAgentSession typedSession) + { + throw new InvalidOperationException("The provided session is not compatible with the agent. Only sessions created by the agent can be serialized."); + } + + return typedSession.Serialize(jsonSerializerOptions); + } + public override ValueTask CreateSessionAsync(CancellationToken cancellationToken = default) => new(new EchoAgentSession()); @@ -89,5 +99,11 @@ protected override async IAsyncEnumerable RunCoreStreamingA } } - private sealed class EchoAgentSession : InMemoryAgentSession; + private sealed class EchoAgentSession : InMemoryAgentSession + { + internal new JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + { + return base.Serialize(jsonSerializerOptions); + } + } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestReplayAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestReplayAgent.cs index c79e0f3a8c..282c168a14 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestReplayAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestReplayAgent.cs @@ -51,6 +51,9 @@ public override ValueTask CreateSessionAsync(CancellationToken can public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => new(new ReplayAgentSession()); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + => default; + public static TestReplayAgent FromStrings(params string[] messages) => new(ToChatMessages(messages)); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs index 447c7f5e89..ce0be1b93e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRequestAgent.cs @@ -45,6 +45,9 @@ public override ValueTask DeserializeSessionAsync(JsonElement seri _ => throw new NotSupportedException(), }); + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + => default; + protected override Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => this.RunStreamingAsync(messages, session, options, cancellationToken).ToAgentResponseAsync(cancellationToken); @@ -361,7 +364,7 @@ public TestRequestAgentSession(JsonElement element, JsonSerializerOptions? jsonS this.PairedRequests = state.PairedRequests; } - public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) + protected override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null) { JsonElement sessionState = base.Serialize(jsonSerializerOptions); diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs index 7bab74c31d..e8b8a229c1 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowHostSmokeTests.cs @@ -51,6 +51,9 @@ public override ValueTask CreateSessionAsync(CancellationToken can return new(new Session()); } + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) + => default; + protected override async Task RunCoreAsync(IEnumerable messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) { return await this.RunStreamingAsync(messages, session, options, cancellationToken)