Skip to content
4 changes: 2 additions & 2 deletions dotnet/samples/A2AClientServer/A2AClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ private static async Task HandleCommandsAsync(CancellationToken cancellationToke
// Create the Host agent
var hostAgent = new HostClientAgent(loggerFactory);
await hostAgent.InitializeAgentAsync(modelId, apiKey, agentUrls!.Split(";"));
AgentThread thread = await hostAgent.Agent!.GetNewThreadAsync(cancellationToken);
AgentSession session = await hostAgent.Agent!.GetNewSessionAsync(cancellationToken);
try
{
while (true)
Expand All @@ -61,7 +61,7 @@ private static async Task HandleCommandsAsync(CancellationToken cancellationToke
break;
}

var agentResponse = await hostAgent.Agent!.RunAsync(message, thread, cancellationToken: cancellationToken);
var agentResponse = await hostAgent.Agent!.RunAsync(message, session, cancellationToken: cancellationToken);
foreach (var chatMessage in agentResponse.Messages)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Expand Down
14 changes: 7 additions & 7 deletions dotnet/samples/AGUIClientServer/AGUIClient/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ private static async Task HandleCommandsAsync(CancellationToken cancellationToke
description: "AG-UI Client Agent",
tools: [changeBackground, readClientClimateSensors]);

AgentThread thread = await agent.GetNewThreadAsync(cancellationToken);
AgentSession session = await agent.GetNewSessionAsync(cancellationToken);
List<ChatMessage> messages = [new(ChatRole.System, "You are a helpful assistant.")];
try
{
Expand All @@ -112,23 +112,23 @@ private static async Task HandleCommandsAsync(CancellationToken cancellationToke

// Call RunStreamingAsync to get streaming updates
bool isFirstUpdate = true;
string? threadId = null;
string? sessionId = null;
var updates = new List<ChatResponseUpdate>();
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, thread, cancellationToken: cancellationToken))
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session, cancellationToken: cancellationToken))
{
// Use AsChatResponseUpdate to access ChatResponseUpdate properties
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
updates.Add(chatUpdate);
if (chatUpdate.ConversationId != null)
{
threadId = chatUpdate.ConversationId;
sessionId = chatUpdate.ConversationId;
}

// Display run started information from the first update
if (isFirstUpdate && threadId != null && update.ResponseId != null)
if (isFirstUpdate && sessionId != null && update.ResponseId != null)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"\n[Run Started - Thread: {threadId}, Run: {update.ResponseId}]");
Console.WriteLine($"\n[Run Started - Session: {sessionId}, Run: {update.ResponseId}]");
Console.ResetColor();
isFirstUpdate = false;
}
Expand Down Expand Up @@ -177,7 +177,7 @@ private static async Task HandleCommandsAsync(CancellationToken cancellationToke
var lastUpdate = updates[^1];
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine();
Console.WriteLine($"[Run Ended - Thread: {threadId}, Run: {lastUpdate.ResponseId}]");
Console.WriteLine($"[Run Ended - Session: {sessionId}, Run: {lastUpdate.ResponseId}]");
Console.ResetColor();
}
messages.Clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ public AgenticUIAgent(AIAgent innerAgent, JsonSerializerOptions jsonSerializerOp
this._jsonSerializerOptions = jsonSerializerOptions;
}

protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
return this.RunCoreStreamingAsync(messages, session, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
}

protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
IEnumerable<ChatMessage> messages,
AgentThread? thread = null,
AgentSession? session = null,
AgentRunOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// Track function calls that should trigger state events
var trackedFunctionCalls = new Dictionary<string, FunctionCallContent>();

await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, thread, options, cancellationToken).ConfigureAwait(false))
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, session, options, cancellationToken).ConfigureAwait(false))
{
// Process contents: track function calls and emit state events for results
List<AIContent> stateEventsToEmit = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ public PredictiveStateUpdatesAgent(AIAgent innerAgent, JsonSerializerOptions jso
this._jsonSerializerOptions = jsonSerializerOptions;
}

protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
return this.RunCoreStreamingAsync(messages, session, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
}

protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
IEnumerable<ChatMessage> messages,
AgentThread? thread = null,
AgentSession? session = null,
AgentRunOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// Track the last emitted document state to avoid duplicates
string? lastEmittedDocument = null;

await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, thread, options, cancellationToken).ConfigureAwait(false))
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, session, options, cancellationToken).ConfigureAwait(false))
{
// Check if we're seeing a write_document tool call and emit predictive state
bool hasToolCall = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,21 @@ public SharedStateAgent(AIAgent innerAgent, JsonSerializerOptions jsonSerializer
this._jsonSerializerOptions = jsonSerializerOptions;
}

protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
protected override Task<AgentResponse> RunCoreAsync(IEnumerable<ChatMessage> messages, AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
{
return this.RunCoreStreamingAsync(messages, thread, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
return this.RunCoreStreamingAsync(messages, session, options, cancellationToken).ToAgentResponseAsync(cancellationToken);
}

protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingAsync(
IEnumerable<ChatMessage> messages,
AgentThread? thread = null,
AgentSession? session = null,
AgentRunOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
if (options is not ChatClientAgentRunOptions { ChatOptions.AdditionalProperties: { } properties } chatRunOptions ||
!properties.TryGetValue("ag_ui_state", out JsonElement state))
{
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, thread, options, cancellationToken).ConfigureAwait(false))
await foreach (var update in this.InnerAgent.RunStreamingAsync(messages, session, options, cancellationToken).ConfigureAwait(false))
{
yield return update;
}
Expand Down Expand Up @@ -64,7 +64,7 @@ protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingA
var firstRunMessages = messages.Append(stateUpdateMessage);

var allUpdates = new List<AgentResponseUpdate>();
await foreach (var update in this.InnerAgent.RunStreamingAsync(firstRunMessages, thread, firstRunOptions, cancellationToken).ConfigureAwait(false))
await foreach (var update in this.InnerAgent.RunStreamingAsync(firstRunMessages, session, firstRunOptions, cancellationToken).ConfigureAwait(false))
{
allUpdates.Add(update);

Expand Down Expand Up @@ -98,7 +98,7 @@ protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingA
ChatRole.System,
[new TextContent("Please provide a concise summary of the state changes in at most two sentences.")]));

await foreach (var update in this.InnerAgent.RunStreamingAsync(secondRunMessages, thread, options, cancellationToken).ConfigureAwait(false))
await foreach (var update in this.InnerAgent.RunStreamingAsync(secondRunMessages, session, options, cancellationToken).ConfigureAwait(false))
{
yield return update;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
chatClientServiceKey: "chat-model")
.WithAITool(new CustomAITool())
.WithAITool(new CustomFunctionTool())
.WithInMemoryThreadStore();
.WithInMemorySessionStore();

var knightsKnavesAgentBuilder = builder.AddAIAgent("knights-and-knaves", (sp, key) =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ public A2AAgentClient(ILogger logger, Uri baseUri)
public override async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
string agentName,
IList<ChatMessage> messages,
string? threadId = null,
string? sessionId = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
this._logger.LogInformation("Running agent {AgentName} with {MessageCount} messages via A2A", agentName, messages.Count);

var (a2aClient, _) = this.ResolveClient(agentName);
var contextId = threadId ?? Guid.NewGuid().ToString("N");
var contextId = sessionId ?? Guid.NewGuid().ToString("N");

// Convert and send messages via A2A without try-catch in yield method
var results = new List<AgentResponseUpdate>();
Expand Down
17 changes: 2 additions & 15 deletions dotnet/samples/AgentWebChat/AgentWebChat.Web/IAgentClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ internal abstract class AgentClientBase
/// </summary>
/// <param name="agentName">The name of the agent to run.</param>
/// <param name="messages">The messages to send to the agent.</param>
/// <param name="threadId">Optional thread identifier for conversation continuity.</param>
/// <param name="sessionId">Optional session identifier for conversation continuity.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>An asynchronous enumerable of agent response updates.</returns>
public abstract IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
string agentName,
IList<ChatMessage> messages,
string? threadId = null,
string? sessionId = null,
CancellationToken cancellationToken = default);

/// <summary>
Expand All @@ -34,16 +34,3 @@ public abstract IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
public virtual Task<AgentCard?> GetAgentCardAsync(string agentName, CancellationToken cancellationToken = default)
=> Task.FromResult<AgentCard?>(null);
}

/// <summary>
/// Helper class to create a thread-like wrapper for agent clients.
/// </summary>
public class AgentClientThread
{
public string ThreadId { get; }

public AgentClientThread(string? threadId = null)
{
this.ThreadId = threadId ?? Guid.NewGuid().ToString("N");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal sealed class OpenAIChatCompletionsAgentClient(HttpClient httpClient) :
public override async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
string agentName,
IList<ChatMessage> messages,
string? threadId = null,
string? sessionId = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
OpenAIClientOptions options = new()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal sealed class OpenAIResponsesAgentClient(HttpClient httpClient) : AgentC
public override async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
string agentName,
IList<ChatMessage> messages,
string? threadId = null,
string? sessionId = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
OpenAIClientOptions options = new()
Expand All @@ -30,7 +30,7 @@ public override async IAsyncEnumerable<AgentResponseUpdate> RunStreamingAsync(
var openAiClient = new ResponsesClient(model: agentName, credential: new ApiKeyCredential("dummy-key"), options: options).AsIChatClient();
var chatOptions = new ChatOptions()
{
ConversationId = threadId
ConversationId = sessionId
};

await foreach (var update in openAiClient.GetStreamingResponseAsync(messages, chatOptions, cancellationToken: cancellationToken))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ public sealed record TextResponse(string Text);
public static async Task<string> RunOrchestrationAsync([OrchestrationTrigger] TaskOrchestrationContext context)
{
DurableAIAgent writer = context.GetAgent("WriterAgent");
AgentThread writerThread = await writer.GetNewThreadAsync();
AgentSession writerSession = await writer.GetNewSessionAsync();

AgentResponse<TextResponse> initial = await writer.RunAsync<TextResponse>(
message: "Write a concise inspirational sentence about learning.",
thread: writerThread);
session: writerSession);

AgentResponse<TextResponse> refined = await writer.RunAsync<TextResponse>(
message: $"Improve this further while keeping it under 25 words: {initial.Result.Text}",
thread: writerThread);
session: writerSession);

return refined.Result.Text;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
: new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential());

// Single agent used by the orchestration to demonstrate sequential calls on the same thread.
// Single agent used by the orchestration to demonstrate sequential calls on the same session.
const string WriterName = "WriterAgent";
const string WriterInstructions =
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Single Agent Orchestration Sample

This sample demonstrates how to use the Durable Agent Framework (DAFx) to create a simple Azure Functions app that orchestrates sequential calls to a single AI agent using the same conversation thread for context continuity.
This sample demonstrates how to use the Durable Agent Framework (DAFx) to create a simple Azure Functions app that orchestrates sequential calls to a single AI agent using the same session for context continuity.

## Key Concepts Demonstrated

- Orchestrating multiple interactions with the same agent in a deterministic order
- Using the same `AgentThread` across multiple calls to maintain conversational context
- Using the same `AgentSession` across multiple calls to maintain conversational context
- Durable orchestration with automatic checkpointing and resumption from failures
- HTTP API integration for starting and monitoring orchestrations

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public static async Task<string> RunOrchestrationAsync([OrchestrationTrigger] Ta

// Get the spam detection agent
DurableAIAgent spamDetectionAgent = context.GetAgent("SpamDetectionAgent");
AgentThread spamThread = await spamDetectionAgent.GetNewThreadAsync();
AgentSession spamSession = await spamDetectionAgent.GetNewSessionAsync();

// Step 1: Check if the email is spam
AgentResponse<DetectionResult> spamDetectionResponse = await spamDetectionAgent.RunAsync<DetectionResult>(
Expand All @@ -31,7 +31,7 @@ public static async Task<string> RunOrchestrationAsync([OrchestrationTrigger] Ta
Email ID: {email.EmailId}
Content: {email.EmailContent}
""",
thread: spamThread);
session: spamSession);
DetectionResult result = spamDetectionResponse.Result;

// Step 2: Conditional logic based on spam detection result
Expand All @@ -43,7 +43,7 @@ public static async Task<string> RunOrchestrationAsync([OrchestrationTrigger] Ta

// Generate and send response for legitimate email
DurableAIAgent emailAssistantAgent = context.GetAgent("EmailAssistantAgent");
AgentThread emailThread = await emailAssistantAgent.GetNewThreadAsync();
AgentSession emailSession = await emailAssistantAgent.GetNewSessionAsync();

AgentResponse<EmailResponse> emailAssistantResponse = await emailAssistantAgent.RunAsync<EmailResponse>(
message:
Expand All @@ -53,7 +53,7 @@ public static async Task<string> RunOrchestrationAsync([OrchestrationTrigger] Ta
Email ID: {email.EmailId}
Content: {email.EmailContent}
""",
thread: emailThread);
session: emailSession);

EmailResponse emailResponse = emailAssistantResponse.Result;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ public static async Task<object> RunOrchestrationAsync(

// Get the writer agent
DurableAIAgent writerAgent = context.GetAgent("WriterAgent");
AgentThread writerThread = await writerAgent.GetNewThreadAsync();
AgentSession writerSession = await writerAgent.GetNewSessionAsync();

// Set initial status
context.SetCustomStatus($"Starting content generation for topic: {input.Topic}");

// Step 1: Generate initial content
AgentResponse<GeneratedContent> writerResponse = await writerAgent.RunAsync<GeneratedContent>(
message: $"Write a short article about '{input.Topic}'.",
thread: writerThread);
session: writerSession);
GeneratedContent content = writerResponse.Result;

// Human-in-the-loop iteration - we set a maximum number of attempts to avoid infinite loops
Expand Down Expand Up @@ -81,7 +81,7 @@ The content was rejected by a human reviewer. Please rewrite the article incorpo

Human Feedback: {humanResponse.Feedback}
""",
thread: writerThread);
session: writerSession);

content = writerResponse.Result;
}
Expand Down
Loading
Loading