// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Agents.AI;

/// <summary>
/// Provides the base abstraction for all AI agents, defining the core interface for agent interactions and conversation management.
/// </summary>
/// <remarks>
/// <see cref="AIAgent"/> serves as the foundational class for implementing AI agents that can participate in conversations
/// and process user requests. An agent instance may participate in multiple concurrent conversations, and each conversation
/// may involve multiple agents working together.
/// <para>
/// <strong>Security considerations:</strong> An <see cref="AIAgent"/> orchestrates data flow across trust boundaries —
/// messages are sent to external AI services, context providers, chat history stores, and function tools. Agent Framework
/// passes messages through as-is without validation or sanitization. Developers must be aware that:
/// <list type="bullet">
/// <item><description>User-supplied messages may contain prompt injection attempts designed to manipulate LLM behavior.</description></item>
/// <item><description>LLM responses should be treated as untrusted output — they may contain hallucinations, malicious payloads (e.g., scripts, SQL),
/// or content influenced by indirect prompt injection. Always validate and sanitize LLM output before rendering in HTML, executing as code,
/// or using in database queries.</description></item>
/// <item><description>Messages with different roles carry different trust levels: <c>system</c> messages have the highest trust and must be developer-controlled;
/// <c>user</c>, <c>assistant</c>, and <c>tool</c> messages should be treated as untrusted.</description></item>
/// </list>
/// </para>
/// </remarks>
[DebuggerDisplay("{DebuggerDisplay,nq}")]
public abstract partial class AIAgent
{
    private static readonly AsyncLocal<AgentRunContext?> s_currentContext = new();

    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private string DebuggerDisplay =>
        this.Name is { } name ? $"Id = {this.Id}, Name = {name}" : $"Id = {this.Id}";

    /// <summary>
    /// Gets the unique identifier for this agent instance.
    /// </summary>
    /// <value>
    /// A unique string identifier for the agent. For in-memory agents, this defaults to a randomly-generated ID,
    /// while service-backed agents typically use the identifier assigned by the backing service.
    /// </value>
    /// <remarks>
    /// Agent identifiers are used for tracking, telemetry, and distinguishing between different
    /// agent instances in multi-agent scenarios. They should remain stable for the lifetime
    /// of the agent instance.
    /// </remarks>
    public string Id { get => this.IdCore ?? field; } = Guid.NewGuid().ToString("N");

    /// <summary>
    /// Gets a custom identifier for the agent, which can be overridden by derived classes.
    /// </summary>
    /// <value>
    /// A string representing the agent's identifier, or <see langword="null"/> if the default ID should be used.
    /// </value>
    /// <remarks>
    /// Derived classes can override this property to provide a custom identifier.
    /// When <see langword="null"/> is returned, the <see cref="Id"/> property will use the default randomly-generated identifier.
    /// </remarks>
    protected virtual string? IdCore => null;

    /// <summary>
    /// Gets the human-readable name of the agent.
    /// </summary>
    /// <value>
    /// The agent's name, or <see langword="null"/> if no name has been assigned.
    /// </value>
    /// <remarks>
    /// The agent name is typically used for display purposes and to help users identify
    /// the agent's purpose or capabilities in user interfaces.
    /// </remarks>
    public virtual string? Name { get; }

    /// <summary>
    /// Gets a description of the agent's purpose, capabilities, or behavior.
    /// </summary>
    /// <value>
    /// A descriptive text explaining what the agent does, or <see langword="null"/> if no description is available.
    /// </value>
    /// <remarks>
    /// The description helps models and users understand the agent's intended purpose and capabilities,
    /// which is particularly useful in multi-agent systems.
    /// </remarks>
    public virtual string? Description { get; }

    /// <summary>
    /// Gets or sets the <see cref="AgentRunContext"/> for the current agent run.
    /// </summary>
    /// <remarks>
    /// This value flows across async calls.
    /// </remarks>
    public static AgentRunContext? CurrentRunContext
    {
        get => s_currentContext.Value;
        protected set => s_currentContext.Value = value;
    }

    /// <summary>Asks the <see cref="AIAgent"/> for an object of the specified type <paramref name="serviceType"/>.</summary>
    /// <param name="serviceType">The type of object being requested.</param>
    /// <param name="serviceKey">An optional key that can be used to help identify the target service.</param>
    /// <returns>The found object, otherwise <see langword="null"/>.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="serviceType"/> is <see langword="null"/>.</exception>
    /// <remarks>
    /// The purpose of this method is to allow for the retrieval of strongly-typed services that might be provided by the <see cref="AIAgent"/>,
    /// including itself or any services it might be wrapping. For example, to access the <see cref="AIAgentMetadata"/> for the instance,
    /// <see cref="GetService"/> may be used to request it.
    /// </remarks>
    public virtual object? GetService(Type serviceType, object? serviceKey = null)
    {
        _ = Throw.IfNull(serviceType);

        return serviceKey is null && serviceType.IsInstanceOfType(this)
            ? this
            : null;
    }

    /// <summary>Asks the <see cref="AIAgent"/> for an object of type <typeparamref name="TService"/>.</summary>
    /// <typeparam name="TService">The type of the object to be retrieved.</typeparam>
    /// <param name="serviceKey">An optional key that can be used to help identify the target service.</param>
    /// <returns>The found object, otherwise <see langword="null"/>.</returns>
    /// <remarks>
    /// The purpose of this method is to allow for the retrieval of strongly typed services that may be provided by the <see cref="AIAgent"/>,
    /// including itself or any services it might be wrapping.
    /// </remarks>
    public TService? GetService<TService>(object? serviceKey = null)
        => this.GetService(typeof(TService), serviceKey) is TService service ? service : default;

    /// <summary>
    /// Creates a new conversation session that is compatible with this agent.
    /// </summary>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A value task that represents the asynchronous operation. The task result contains a new <see cref="AgentSession"/> instance ready for use with this agent.</returns>
    /// <remarks>
    /// <para>
    /// This method creates a fresh conversation session that can be used to maintain state
    /// and context for interactions with this agent. Each session represents an independent
    /// conversation session.
    /// </para>
    /// <para>
    /// If the agent supports multiple session types, this method returns the default or
    /// configured session type. For service-backed agents, the actual session creation
    /// may be deferred until first use to optimize performance.
    /// </para>
    /// </remarks>
    public ValueTask<AgentSession> CreateSessionAsync(CancellationToken cancellationToken = default)
        => this.CreateSessionCoreAsync(cancellationToken);

    /// <summary>
    /// Core implementation of session creation logic.
    /// </summary>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A value task that represents the asynchronous operation. The task result contains a new <see cref="AgentSession"/> instance ready for use with this agent.</returns>
    /// <remarks>
    /// This is the primary session creation method that implementations must override.
    /// </remarks>
    protected abstract ValueTask<AgentSession> CreateSessionCoreAsync(CancellationToken cancellationToken = default);

    /// <summary>
    /// Serializes an agent session to its JSON representation.
    /// </summary>
    /// <param name="session">The <see cref="AgentSession"/> to serialize.</param>
    /// <param name="jsonSerializerOptions">Optional settings to customize the serialization process.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A value task that represents the asynchronous operation. The task result contains a <see cref="JsonElement"/> with the serialized session state.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="session"/> is <see langword="null"/>.</exception>
    /// <exception cref="InvalidOperationException">The type of <paramref name="session"/> is not supported by this agent.</exception>
    /// <remarks>
    /// This method enables saving conversation sessions to persistent storage,
    /// allowing conversations to resume across application restarts or be migrated between
    /// different agent instances. Use <see cref="DeserializeSessionAsync"/> to restore the session.
    /// <para>
    /// <strong>Security consideration:</strong> Serialized sessions may contain conversation content, session identifiers,
    /// and other potentially sensitive data including PII. Ensure that serialized session data is stored securely with
    /// appropriate access controls and encryption at rest.
    /// </para>
    /// </remarks>
    public ValueTask<JsonElement> SerializeSessionAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
        => this.SerializeSessionCoreAsync(session, jsonSerializerOptions, cancellationToken);

    /// <summary>
    /// Core implementation of session serialization logic.
    /// </summary>
    /// <param name="session">The <see cref="AgentSession"/> to serialize.</param>
    /// <param name="jsonSerializerOptions">Optional settings to customize the serialization process.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A value task that represents the asynchronous operation. The task result contains a <see cref="JsonElement"/> with the serialized session state.</returns>
    /// <remarks>
    /// This is the primary session serialization method that implementations must override.
    /// </remarks>
    protected abstract ValueTask<JsonElement> SerializeSessionCoreAsync(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default);

    /// <summary>
    /// Deserializes an agent session from its JSON serialized representation.
    /// </summary>
    /// <param name="serializedState">A <see cref="JsonElement"/> containing the serialized session state.</param>
    /// <param name="jsonSerializerOptions">Optional settings to customize the deserialization process.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A value task that represents the asynchronous operation. The task result contains a restored <see cref="AgentSession"/> instance with the state from <paramref name="serializedState"/>.</returns>
    /// <exception cref="ArgumentException">The <paramref name="serializedState"/> is not in the expected format.</exception>
    /// <exception cref="JsonException">The serialized data is invalid or cannot be deserialized.</exception>
    /// <remarks>
    /// This method enables restoration of conversation sessions from previously saved state,
    /// allowing conversations to resume across application restarts or be migrated between
    /// different agent instances.
    /// <para>
    /// <strong>Security consideration:</strong> Restoring a session from an untrusted source is equivalent to accepting untrusted input.
    /// Serialized sessions may contain conversation content, session identifiers, and potentially sensitive data. A compromised
    /// storage backend could alter message roles to escalate trust, or inject adversarial content that influences LLM behavior.
    /// Treat serialized session data as sensitive and ensure it is stored and transmitted securely.
    /// </para>
    /// </remarks>
    public ValueTask<AgentSession> DeserializeSessionAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default)
        => this.DeserializeSessionCoreAsync(serializedState, jsonSerializerOptions, cancellationToken);

    /// <summary>
    /// Core implementation of session deserialization logic.
    /// </summary>
    /// <param name="serializedState">A <see cref="JsonElement"/> containing the serialized session state.</param>
    /// <param name="jsonSerializerOptions">Optional settings to customize the deserialization process.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A value task that represents the asynchronous operation. The task result contains a restored <see cref="AgentSession"/> instance with the state from <paramref name="serializedState"/>.</returns>
    /// <remarks>
    /// This is the primary session deserialization method that implementations must override.
    /// </remarks>
    protected abstract ValueTask<AgentSession> DeserializeSessionCoreAsync(JsonElement serializedState, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default);

    /// <summary>
    /// Run the agent with no message assuming that all required instructions are already provided to the agent or on the session.
    /// </summary>
    /// <param name="session">
    /// The conversation session to use for this invocation. If <see langword="null"/>, a new session will be created.
    /// The session will be updated with any response messages generated during invocation.
    /// </param>
    /// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentResponse"/> with the agent's output.</returns>
    /// <remarks>
    /// This overload is useful when the agent has sufficient context from previous messages in the session
    /// or from its initial configuration to generate a meaningful response without additional input.
    /// </remarks>
    public Task<AgentResponse> RunAsync(
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default) =>
        this.RunAsync([], session, options, cancellationToken);

    /// <summary>
    /// Runs the agent with a text message from the user.
    /// </summary>
    /// <param name="message">The user message to send to the agent.</param>
    /// <param name="session">
    /// The conversation session to use for this invocation. If <see langword="null"/>, a new session will be created.
    /// The session will be updated with the input message and any response messages generated during invocation.
    /// </param>
    /// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentResponse"/> with the agent's output.</returns>
    /// <exception cref="ArgumentException"><paramref name="message"/> is <see langword="null"/>, empty, or contains only whitespace.</exception>
    /// <remarks>
    /// The provided text will be wrapped in a <see cref="ChatMessage"/> with the <see cref="ChatRole.User"/> role
    /// before being sent to the agent. This is a convenience method for simple text-based interactions.
    /// </remarks>
    public Task<AgentResponse> RunAsync(
        string message,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        _ = Throw.IfNullOrWhitespace(message);

        return this.RunAsync(new ChatMessage(ChatRole.User, message), session, options, cancellationToken);
    }

    /// <summary>
    /// Runs the agent with a single chat message.
    /// </summary>
    /// <param name="message">The chat message to send to the agent.</param>
    /// <param name="session">
    /// The conversation session to use for this invocation. If <see langword="null"/>, a new session will be created.
    /// The session will be updated with the input message and any response messages generated during invocation.
    /// </param>
    /// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentResponse"/> with the agent's output.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="message"/> is <see langword="null"/>.</exception>
    public Task<AgentResponse> RunAsync(
        ChatMessage message,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        _ = Throw.IfNull(message);

        return this.RunAsync([message], session, options, cancellationToken);
    }

    /// <summary>
    /// Runs the agent with a collection of chat messages, providing the core invocation logic that all other overloads delegate to.
    /// </summary>
    /// <param name="messages">The collection of messages to send to the agent for processing.</param>
    /// <param name="session">
    /// The conversation session to use for this invocation. If <see langword="null"/>, a new session will be created.
    /// The session will be updated with the input messages and any response messages generated during invocation.
    /// </param>
    /// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentResponse"/> with the agent's output.</returns>
    /// <remarks>
    /// <para>
    /// This method delegates to <see cref="RunCoreAsync"/> to perform the actual agent invocation. It handles collections of messages,
    /// allowing for complex conversational scenarios including multi-turn interactions, function calls, and
    /// context-rich conversations.
    /// </para>
    /// <para>
    /// The messages are processed in the order provided and become part of the conversation history.
    /// The agent's response will also be added to <paramref name="session"/> if one is provided.
    /// </para>
    /// <para>
    /// <strong>Security consideration:</strong> Agent Framework does not validate or sanitize message content — it is passed through
    /// to the underlying AI service as-is. If input messages include untrusted user content, developers should be aware of prompt injection risks.
    /// System-role messages must be developer-controlled and should never contain end-user input.
    /// </para>
    /// </remarks>
    public Task<AgentResponse> RunAsync(
        IEnumerable<ChatMessage> messages,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        CurrentRunContext = new(this, session, messages as IReadOnlyCollection<ChatMessage> ?? messages.ToList(), options);
        return this.RunCoreAsync(messages, session, options, cancellationToken);
    }

    /// <summary>
    /// Core implementation of the agent invocation logic with a collection of chat messages.
    /// </summary>
    /// <param name="messages">The collection of messages to send to the agent for processing.</param>
    /// <param name="session">
    /// The conversation session to use for this invocation. If <see langword="null"/>, a new session will be created.
    /// The session will be updated with the input messages and any response messages generated during invocation.
    /// </param>
    /// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>A task that represents the asynchronous operation. The task result contains an <see cref="AgentResponse"/> with the agent's output.</returns>
    /// <remarks>
    /// <para>
    /// This is the primary invocation method that implementations must override. It handles collections of messages,
    /// allowing for complex conversational scenarios including multi-turn interactions, function calls, and
    /// context-rich conversations.
    /// </para>
    /// <para>
    /// The messages are processed in the order provided and become part of the conversation history.
    /// The agent's response will also be added to <paramref name="session"/> if one is provided.
    /// </para>
    /// </remarks>
    protected abstract Task<AgentResponse> RunCoreAsync(
        IEnumerable<ChatMessage> messages,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default);

    /// <summary>
    /// Runs the agent in streaming mode without providing new input messages, relying on existing context and instructions.
    /// </summary>
    /// <param name="session">
    /// The conversation session to use for this invocation. If <see langword="null"/>, a new session will be created.
    /// The session will be updated with any response messages generated during invocation.
    /// </param>
    /// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>An asynchronous enumerable of <see cref="AgentResponseUpdate"/> instances representing the streaming response.</returns>
    public IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default) =>
        this.RunStreamingAsync([], session, options, cancellationToken);

    /// <summary>
    /// Runs the agent in streaming mode with a text message from the user.
    /// </summary>
    /// <param name="message">The user message to send to the agent.</param>
    /// <param name="session">
    /// The conversation session to use for this invocation. If <see langword="null"/>, a new session will be created.
    /// The session will be updated with the input message and any response messages generated during invocation.
    /// </param>
    /// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>An asynchronous enumerable of <see cref="AgentResponseUpdate"/> instances representing the streaming response.</returns>
    /// <exception cref="ArgumentException"><paramref name="message"/> is <see langword="null"/>, empty, or contains only whitespace.</exception>
    /// <remarks>
    /// The provided text will be wrapped in a <see cref="ChatMessage"/> with the <see cref="ChatRole.User"/> role.
    /// Streaming invocation provides real-time updates as the agent generates its response.
    /// </remarks>
    public IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
        string message,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        _ = Throw.IfNullOrWhitespace(message);

        return this.RunStreamingAsync(new ChatMessage(ChatRole.User, message), session, options, cancellationToken);
    }

    /// <summary>
    /// Runs the agent in streaming mode with a single chat message.
    /// </summary>
    /// <param name="message">The chat message to send to the agent.</param>
    /// <param name="session">
    /// The conversation session to use for this invocation. If <see langword="null"/>, a new session will be created.
    /// The session will be updated with the input message and any response messages generated during invocation.
    /// </param>
    /// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>An asynchronous enumerable of <see cref="AgentResponseUpdate"/> instances representing the streaming response.</returns>
    /// <exception cref="ArgumentNullException"><paramref name="message"/> is <see langword="null"/>.</exception>
    public IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
        ChatMessage message,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        _ = Throw.IfNull(message);

        return this.RunStreamingAsync([message], session, options, cancellationToken);
    }

    /// <summary>
    /// Runs the agent in streaming mode with a collection of chat messages, providing the core streaming invocation logic.
    /// </summary>
    /// <param name="messages">The collection of messages to send to the agent for processing.</param>
    /// <param name="session">
    /// The conversation session to use for this invocation. If <see langword="null"/>, a new session will be created.
    /// The session will be updated with the input messages and any response updates generated during invocation.
    /// </param>
    /// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>An asynchronous enumerable of <see cref="AgentResponseUpdate"/> instances representing the streaming response.</returns>
    /// <remarks>
    /// <para>
    /// This method delegates to <see cref="RunCoreStreamingAsync"/> to perform the actual streaming invocation. It provides real-time
    /// updates as the agent processes the input and generates its response, enabling more responsive user experiences.
    /// </para>
    /// <para>
    /// Each <see cref="AgentResponseUpdate"/> represents a portion of the complete response, allowing consumers
    /// to display partial results, implement progressive loading, or provide immediate feedback to users.
    /// </para>
    /// <para>
    /// <strong>Security consideration:</strong> Agent Framework does not validate or sanitize message content — it is passed through
    /// to the underlying AI service as-is. If input messages include untrusted user content, developers should be aware of prompt injection risks.
    /// System-role messages must be developer-controlled and should never contain end-user input.
    /// </para>
    /// </remarks>
    public async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
        IEnumerable<ChatMessage> messages,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        AgentRunContext context = new(this, session, messages as IReadOnlyCollection<ChatMessage> ?? messages.ToList(), options);
        CurrentRunContext = context;
        await foreach (var update in this.RunCoreStreamingAsync(messages, session, options, cancellationToken).ConfigureAwait(false))
        {
            yield return update;

            // Restore context again when resuming after the caller code executes.
            CurrentRunContext = context;
        }
    }

    /// <summary>
    /// Core implementation of the agent streaming invocation logic with a collection of chat messages.
    /// </summary>
    /// <param name="messages">The collection of messages to send to the agent for processing.</param>
    /// <param name="session">
    /// The conversation session to use for this invocation. If <see langword="null"/>, a new session will be created.
    /// The session will be updated with the input messages and any response updates generated during invocation.
    /// </param>
    /// <param name="options">Optional configuration parameters for controlling the agent's invocation behavior.</param>
    /// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
    /// <returns>An asynchronous enumerable of <see cref="AgentResponseUpdate"/> instances representing the streaming response.</returns>
    /// <remarks>
    /// <para>
    /// This is the primary streaming invocation method that implementations must override. It provides real-time
    /// updates as the agent processes the input and generates its response, enabling more responsive user experiences.
    /// </para>
    /// <para>
    /// Each <see cref="AgentResponseUpdate"/> represents a portion of the complete response, allowing consumers
    /// to display partial results, implement progressive loading, or provide immediate feedback to users.
    /// </para>
    /// </remarks>
    protected abstract IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
        IEnumerable<ChatMessage> messages,
        AgentSession? session = null,
        AgentRunOptions? options = null,
        CancellationToken cancellationToken = default);
}
