Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,14 @@ protected override async IAsyncEnumerable<AgentResponseUpdate> RunCoreStreamingA
channel.Writer.TryWrite(this.ConvertToAgentResponseUpdate(assistantMessage));
break;

case AssistantReasoningDeltaEvent reasoningDeltaEvent:
channel.Writer.TryWrite(this.ConvertToAgentResponseUpdate(reasoningDeltaEvent));
break;

case AssistantReasoningEvent reasoningEvent:
channel.Writer.TryWrite(this.ConvertToAgentResponseUpdate(reasoningEvent));
break;

case AssistantUsageEvent usageEvent:
channel.Writer.TryWrite(this.ConvertToAgentResponseUpdate(usageEvent));
break;
Expand Down Expand Up @@ -385,6 +393,34 @@ private AgentResponseUpdate ConvertToAgentResponseUpdate(AssistantUsageEvent usa
};
}

private AgentResponseUpdate ConvertToAgentResponseUpdate(AssistantReasoningDeltaEvent reasoningDeltaEvent)
{
TextReasoningContent reasoningContent = new(reasoningDeltaEvent.Data?.DeltaContent ?? string.Empty)
{
RawRepresentation = reasoningDeltaEvent
};

return new AgentResponseUpdate(ChatRole.Assistant, [reasoningContent])
{
AgentId = this.Id,
CreatedAt = reasoningDeltaEvent.Timestamp
};
}

private AgentResponseUpdate ConvertToAgentResponseUpdate(AssistantReasoningEvent reasoningEvent)
{
TextReasoningContent reasoningContent = new(reasoningEvent.Data?.Content ?? string.Empty)
{
RawRepresentation = reasoningEvent
};

return new AgentResponseUpdate(ChatRole.Assistant, [reasoningContent])
{
AgentId = this.Id,
CreatedAt = reasoningEvent.Timestamp
};
}

private static AdditionalPropertiesDictionary<long>? GetAdditionalCounts(AssistantUsageEvent usageEvent)
{
if (usageEvent.Data is null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Reflection;
using GitHub.Copilot.SDK;
using Microsoft.Extensions.AI;

namespace Microsoft.Agents.AI.GitHub.Copilot.UnitTests;

/// <summary>
/// Unit tests for the <see cref="GitHubCopilotAgent"/> reasoning event handling.
/// </summary>
public sealed class GitHubCopilotAgentReasoningTests
{
/// <summary>
/// Tests that ConvertToAgentResponseUpdate correctly handles AssistantReasoningDeltaEvent.
/// </summary>
[Fact]
public void ConvertToAgentResponseUpdate_WithReasoningDeltaEvent_CreatesTextReasoningContent()
{
// Arrange
CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
GitHubCopilotAgent agent = new(copilotClient, sessionConfig: null, ownsClient: false);

// Create an AssistantReasoningDeltaEvent using reflection
AssistantReasoningDeltaEvent reasoningDeltaEvent = new()
{
Data = new AssistantReasoningDeltaData
{
ReasoningId = "reasoning-123",
DeltaContent = "Thinking step "
},
Timestamp = DateTimeOffset.UtcNow
};

// Act - Use reflection to call the private method
MethodInfo? method = typeof(GitHubCopilotAgent).GetMethod(
"ConvertToAgentResponseUpdate",
BindingFlags.NonPublic | BindingFlags.Instance,
null,
[typeof(AssistantReasoningDeltaEvent)],
null);

Assert.NotNull(method);

AgentResponseUpdate? result = method.Invoke(agent, [reasoningDeltaEvent]) as AgentResponseUpdate;

// Assert
Assert.NotNull(result);
Assert.Equal(ChatRole.Assistant, result.Role);
Assert.NotEmpty(result.Contents);

AIContent content = result.Contents[0];
Assert.IsType<TextReasoningContent>(content);

TextReasoningContent reasoningContent = (TextReasoningContent)content;
Assert.Equal("Thinking step ", reasoningContent.Text);
Assert.Equal(reasoningDeltaEvent, reasoningContent.RawRepresentation);
Assert.Equal(agent.Id, result.AgentId);
Assert.Equal(reasoningDeltaEvent.Timestamp, result.CreatedAt);
}

/// <summary>
/// Tests that ConvertToAgentResponseUpdate correctly handles AssistantReasoningEvent.
/// </summary>
[Fact]
public void ConvertToAgentResponseUpdate_WithReasoningEvent_CreatesTextReasoningContent()
{
// Arrange
CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
GitHubCopilotAgent agent = new(copilotClient, sessionConfig: null, ownsClient: false);

// Create an AssistantReasoningEvent using reflection
AssistantReasoningEvent reasoningEvent = new()
{
Data = new AssistantReasoningData
{
ReasoningId = "reasoning-456",
Content = "Complete reasoning content"
},
Timestamp = DateTimeOffset.UtcNow
};

// Act - Use reflection to call the private method
MethodInfo? method = typeof(GitHubCopilotAgent).GetMethod(
"ConvertToAgentResponseUpdate",
BindingFlags.NonPublic | BindingFlags.Instance,
null,
[typeof(AssistantReasoningEvent)],
null);

Assert.NotNull(method);

AgentResponseUpdate? result = method.Invoke(agent, [reasoningEvent]) as AgentResponseUpdate;

// Assert
Assert.NotNull(result);
Assert.Equal(ChatRole.Assistant, result.Role);
Assert.NotEmpty(result.Contents);

AIContent content = result.Contents[0];
Assert.IsType<TextReasoningContent>(content);

TextReasoningContent reasoningContent = (TextReasoningContent)content;
Assert.Equal("Complete reasoning content", reasoningContent.Text);
Assert.Equal(reasoningEvent, reasoningContent.RawRepresentation);
Assert.Equal(agent.Id, result.AgentId);
Assert.Equal(reasoningEvent.Timestamp, result.CreatedAt);
}

/// <summary>
/// Tests that ConvertToAgentResponseUpdate handles null data in AssistantReasoningDeltaEvent.
/// </summary>
[Fact]
public void ConvertToAgentResponseUpdate_WithNullDataInReasoningDeltaEvent_CreatesEmptyTextReasoningContent()
{
// Arrange
CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
GitHubCopilotAgent agent = new(copilotClient, sessionConfig: null, ownsClient: false);

// Create an AssistantReasoningDeltaEvent with null data
AssistantReasoningDeltaEvent reasoningDeltaEvent = new()
{
Data = null!,
Timestamp = DateTimeOffset.UtcNow
};

// Act - Use reflection to call the private method
MethodInfo? method = typeof(GitHubCopilotAgent).GetMethod(
"ConvertToAgentResponseUpdate",
BindingFlags.NonPublic | BindingFlags.Instance,
null,
[typeof(AssistantReasoningDeltaEvent)],
null);

Assert.NotNull(method);

AgentResponseUpdate? result = method.Invoke(agent, [reasoningDeltaEvent]) as AgentResponseUpdate;

// Assert
Assert.NotNull(result);
Assert.Equal(ChatRole.Assistant, result.Role);
Assert.NotEmpty(result.Contents);

AIContent content = result.Contents[0];
Assert.IsType<TextReasoningContent>(content);

TextReasoningContent reasoningContent = (TextReasoningContent)content;
Assert.Equal(string.Empty, reasoningContent.Text);
}

/// <summary>
/// Tests that ConvertToAgentResponseUpdate handles null content in AssistantReasoningEvent.
/// </summary>
[Fact]
public void ConvertToAgentResponseUpdate_WithNullDataInReasoningEvent_CreatesEmptyTextReasoningContent()
{
// Arrange
CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
GitHubCopilotAgent agent = new(copilotClient, sessionConfig: null, ownsClient: false);

// Create an AssistantReasoningEvent with null data
AssistantReasoningEvent reasoningEvent = new()
{
Data = null!,
Timestamp = DateTimeOffset.UtcNow
};

// Act - Use reflection to call the private method
MethodInfo? method = typeof(GitHubCopilotAgent).GetMethod(
"ConvertToAgentResponseUpdate",
BindingFlags.NonPublic | BindingFlags.Instance,
null,
[typeof(AssistantReasoningEvent)],
null);

Assert.NotNull(method);

AgentResponseUpdate? result = method.Invoke(agent, [reasoningEvent]) as AgentResponseUpdate;

// Assert
Assert.NotNull(result);
Assert.Equal(ChatRole.Assistant, result.Role);
Assert.NotEmpty(result.Contents);

AIContent content = result.Contents[0];
Assert.IsType<TextReasoningContent>(content);

TextReasoningContent reasoningContent = (TextReasoningContent)content;
Assert.Equal(string.Empty, reasoningContent.Text);
}

/// <summary>
/// Tests that ConvertToAgentResponseUpdate handles null DeltaContent in AssistantReasoningDeltaEvent.
/// </summary>
[Fact]
public void ConvertToAgentResponseUpdate_WithNullDeltaContent_CreatesEmptyTextReasoningContent()
{
// Arrange
CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
GitHubCopilotAgent agent = new(copilotClient, sessionConfig: null, ownsClient: false);

// Create an AssistantReasoningDeltaEvent with null DeltaContent
AssistantReasoningDeltaEvent reasoningDeltaEvent = new()
{
Data = new AssistantReasoningDeltaData
{
ReasoningId = "reasoning-789",
DeltaContent = null!
},
Timestamp = DateTimeOffset.UtcNow
};

// Act - Use reflection to call the private method
MethodInfo? method = typeof(GitHubCopilotAgent).GetMethod(
"ConvertToAgentResponseUpdate",
BindingFlags.NonPublic | BindingFlags.Instance,
null,
[typeof(AssistantReasoningDeltaEvent)],
null);

Assert.NotNull(method);

AgentResponseUpdate? result = method.Invoke(agent, [reasoningDeltaEvent]) as AgentResponseUpdate;

// Assert
Assert.NotNull(result);
AIContent content = result.Contents[0];
TextReasoningContent reasoningContent = Assert.IsType<TextReasoningContent>(content);
Assert.Equal(string.Empty, reasoningContent.Text);
}

/// <summary>
/// Tests that ConvertToAgentResponseUpdate handles null Content in AssistantReasoningEvent.
/// </summary>
[Fact]
public void ConvertToAgentResponseUpdate_WithNullContent_CreatesEmptyTextReasoningContent()
{
// Arrange
CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
GitHubCopilotAgent agent = new(copilotClient, sessionConfig: null, ownsClient: false);

// Create an AssistantReasoningEvent with null Content
AssistantReasoningEvent reasoningEvent = new()
{
Data = new AssistantReasoningData
{
ReasoningId = "reasoning-999",
Content = null!
},
Timestamp = DateTimeOffset.UtcNow
};

// Act - Use reflection to call the private method
MethodInfo? method = typeof(GitHubCopilotAgent).GetMethod(
"ConvertToAgentResponseUpdate",
BindingFlags.NonPublic | BindingFlags.Instance,
null,
[typeof(AssistantReasoningEvent)],
null);

Assert.NotNull(method);

AgentResponseUpdate? result = method.Invoke(agent, [reasoningEvent]) as AgentResponseUpdate;

// Assert
Assert.NotNull(result);
AIContent content = result.Contents[0];
TextReasoningContent reasoningContent = Assert.IsType<TextReasoningContent>(content);
Assert.Equal(string.Empty, reasoningContent.Text);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,22 @@ def event_handler(event: SessionEvent) -> None:
raw_representation=event,
)
queue.put_nowait(update)
elif event.type == SessionEventType.ASSISTANT_REASONING_DELTA:
if event.data.delta_content:
update = AgentResponseUpdate(
role="assistant",
contents=[Content.from_text_reasoning(text=event.data.delta_content)],
raw_representation=event,
)
queue.put_nowait(update)
elif event.type == SessionEventType.ASSISTANT_REASONING:
if event.data.content:
update = AgentResponseUpdate(
role="assistant",
contents=[Content.from_text_reasoning(text=event.data.content)],
raw_representation=event,
)
queue.put_nowait(update)
elif event.type == SessionEventType.SESSION_IDLE:
queue.put_nowait(None)
elif event.type == SessionEventType.SESSION_ERROR:
Expand Down
Loading
Loading