Skip to content

.NET: [Bug]: GitHubCopilotAgent leaves finish reason, model and reasoning tokens unset and emits empty AIContent parts in OpenTelemetry output #9684

Open

Description

Observed Behavior

When a GitHubCopilotAgent is wrapped with UseOpenTelemetry, the invoke_agent span is incomplete:

  • gen_ai.response.finish_reasons, gen_ai.request.model, gen_ai.response.model and gen_ai.usage.reasoning.output_tokens are never set.
  • With sensitive data enabled, gen_ai.output.messages is filled with empty parts such as {"type": "Microsoft.Extensions.AI.AIContent", "content": {}}. One streaming turn with three model calls and two tool calls produced 53 empty parts and one tool_call part.

From GitHubCopilotAgent.cs on main (dotnet-1.26.0):

  • No AgentResponseUpdate sets FinishReason, and the model is never exposed (OpenTelemetryAgent reads ModelId from a ChatResponseUpdate RawRepresentation, which the adapter does not produce), although AssistantUsageEvent.Data carries both FinishReason and Model.
  • ConvertToAgentResponseUpdate(AssistantUsageEvent) maps input, output and cached tokens but not AssistantUsageData.ReasoningTokens to UsageDetails.ReasoningTokenCount.
  • The default branch of the event switch wraps every other SessionEvent (reasoning and streaming deltas, turn start/end, model call start/finished, and so on) as an empty AIContent, and in streaming mode an AssistantMessageEvent also becomes an empty AIContent. These reach the response and are serialized as empty output parts.

Expected Behavior

  • The final update carries FinishReason (from AssistantUsageData.FinishReason) and the model (from AssistantUsageData.Model), so invoke_agent gets gen_ai.response.finish_reasons and gen_ai.response.model.
  • UsageDetails.ReasoningTokenCount is populated from AssistantUsageData.ReasoningTokens.
  • Events with no content representation do not appear as empty parts in gen_ai.output.messages.

Steps to Reproduce

  1. Create a CopilotClient and an agent with AsAIAgent, with at least one custom tool.
  2. Wrap it: agent.AsBuilder().UseOpenTelemetry("test", o => o.EnableSensitiveData = true).Build().
  3. Add an ActivityListener for source "test".
  4. Run a streaming turn whose prompt makes the model call the tool.
  5. Inspect the invoke_agent activity's tags.

Minimal Reproduction

using var listener = new ActivityListener
{
    ShouldListenTo = s => s.Name == "test",
    Sample = (ref ActivityCreationOptions<ActivityContext> _) => ActivitySamplingResult.AllDataAndRecorded,
    ActivityStopped = a =>
    {
        Console.WriteLine(a.DisplayName);
        foreach (var tag in a.TagObjects) Console.WriteLine($"  {tag.Key} = {tag.Value}");
    },
};
ActivitySource.AddActivityListener(listener);

await using var client = new CopilotClient();
await client.StartAsync();
AIAgent agent = client.AsAIAgent(new SessionConfig
{
    Tools = [AIFunctionFactory.Create(() => "42", "get_answer", "Returns the answer.")],
});
agent = agent.AsBuilder().UseOpenTelemetry("test", o => o.EnableSensitiveData = true).Build();

await foreach (var _ in agent.RunStreamingAsync("Call get_answer and tell me the result.")) { }
// invoke_agent: no gen_ai.response.finish_reasons, gen_ai.response.model or
// gen_ai.usage.reasoning.output_tokens; gen_ai.output.messages contains empty AIContent parts.

Error Messages and Stack Traces


Package Versions

Microsoft.Agents.AI.GitHub.Copilot 1.23.0, GitHub.Copilot.SDK 1.0.14 (same code on main at dotnet-1.26.0)

.NET Version

.NET 10.0

Operating System

Linux

Regression

Unknown

Additional Context

Source: https://github.com/microsoft/agent-framework/blob/main/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs

We currently set the missing invoke_agent attributes ourselves from Copilot session events as a workaround.

Acknowledgements

  • I searched existing issues and did not find a duplicate.
  • I personally verified this behavior and the reproduction details are authentic.
  • I will wait for explicit maintainer agreement before starting implementation of a non-trivial change.

Activity

  1. added
    reproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow
    on Oct 25, 2026
  2. github-actions commented on Oct 25, 2026

    @github-actions
    Contributor

    🤖 Automated triage reproduction notes (agent-authored — trust but verify)

    Agent analysis

    GitHubCopilotAgent.cs::ConvertToAgentResponseUpdate drops finish reason, model, and reasoning tokens from AssistantUsageEvent and emits bare AIContent for streaming assistant and fallback session events. The failures occur when usage data contains those fields or streaming receives metadata-only events. Minimal repro: construct those SDK events, invoke the conversion methods, and assert populated telemetry metadata and no empty content parts.

    • Failing test: dotnet/tests/Microsoft.Agents.AI.GitHub.Copilot.UnitTests/GitHubCopilotAgentTests.cs
    • Files examined: dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs, dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs, dotnet/tests/Microsoft.Agents.AI.GitHub.Copilot.UnitTests/GitHubCopilotAgentTests.cs, dotnet/nuget-package.props
    • Tests run: GitHubCopilotAgentTests.ConvertToAgentResponseUpdate_AssistantMessageEventWhenStreaming_DoesNotEmitTextContent, focused ConvertToAgentResponseUpdate_* suite
    • Reported version: 1.23.0
    • Current version: 1.26.0
  3. added
    agentsUsage: [Issues, PRs], Target: Single agent
    and removed
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

.NETUsage: [Issues, PRs], Target: .NetagentsUsage: [Issues, PRs], Target: Single agentreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions