diff --git a/src/Observability/Runtime/Tracing/Scopes/ExecuteToolScope.cs b/src/Observability/Runtime/Tracing/Scopes/ExecuteToolScope.cs
index 9a64e5c8..91f5bb0e 100644
--- a/src/Observability/Runtime/Tracing/Scopes/ExecuteToolScope.cs
+++ b/src/Observability/Runtime/Tracing/Scopes/ExecuteToolScope.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System;
using System.Diagnostics;
using Microsoft.Agents.A365.Observability.Runtime.Tracing.Contracts;
@@ -30,6 +31,8 @@ public sealed class ExecuteToolScope : OpenTelemetryScope
/// Optional metadata describing the source of the call (e.g., component, file, line) for observability.
/// Optional threat diagnostics summary containing security-related information about blocked actions.
/// Optional details about the non-agentic caller.
+ /// Optional explicit start time. Useful when recording a tool call after execution has already completed.
+ /// Optional explicit end time. When provided, the span will use this timestamp when disposed instead of the current wall-clock time.
/// A new ExecuteToolScope instance.
///
///
@@ -44,15 +47,17 @@ public sealed class ExecuteToolScope : OpenTelemetryScope
/// Learn more about certification requirements
///
///
- public static ExecuteToolScope Start(ToolCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null, CallerDetails? callerDetails = null) => new ExecuteToolScope(details, agentDetails, tenantDetails, parentId, conversationId, sourceMetadata, threatDiagnosticsSummary, callerDetails);
+ public static ExecuteToolScope Start(ToolCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null, CallerDetails? callerDetails = null, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null) => new ExecuteToolScope(details, agentDetails, tenantDetails, parentId, conversationId, sourceMetadata, threatDiagnosticsSummary, callerDetails, startTime, endTime);
- private ExecuteToolScope(ToolCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null, CallerDetails? callerDetails = null)
+ private ExecuteToolScope(ToolCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null, CallerDetails? callerDetails = null, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null)
: base(
kind: ActivityKind.Internal,
agentDetails: agentDetails,
tenantDetails: tenantDetails,
operationName: OperationName,
activityName: $"{OperationName} {details.ToolName}",
+ startTime: startTime,
+ endTime: endTime,
parentId: parentId,
conversationId: conversationId,
sourceMetadata: sourceMetadata,
diff --git a/src/Observability/Runtime/Tracing/Scopes/InferenceScope.cs b/src/Observability/Runtime/Tracing/Scopes/InferenceScope.cs
index fa79988e..eb440ac4 100644
--- a/src/Observability/Runtime/Tracing/Scopes/InferenceScope.cs
+++ b/src/Observability/Runtime/Tracing/Scopes/InferenceScope.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System;
using System.Diagnostics;
using Microsoft.Agents.A365.Observability.Runtime.Tracing.Contracts;
using static Microsoft.Agents.A365.Observability.Runtime.Tracing.Scopes.OpenTelemetryConstants;
@@ -25,6 +26,8 @@ public sealed class InferenceScope : OpenTelemetryScope
/// Optional conversation or session correlation ID for the inference.
/// Optional metadata describing the source of the call (e.g., component, file, line) for observability.
/// Optional details about the non-agentic caller.
+ /// Optional explicit start time. Useful when recording an inference call after execution has already completed.
+ /// Optional explicit end time. When provided, the span will use this timestamp when disposed instead of the current wall-clock time.
/// A new InferenceScope instance.
///
///
@@ -39,15 +42,17 @@ public sealed class InferenceScope : OpenTelemetryScope
/// Learn more about certification requirements
///
///
- public static InferenceScope Start(InferenceCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, CallerDetails? callerDetails = null) => new InferenceScope(details, agentDetails, tenantDetails, parentId, conversationId, sourceMetadata, callerDetails);
+ public static InferenceScope Start(InferenceCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, CallerDetails? callerDetails = null, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null) => new InferenceScope(details, agentDetails, tenantDetails, parentId, conversationId, sourceMetadata, callerDetails, startTime, endTime);
- private InferenceScope(InferenceCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, CallerDetails? callerDetails = null)
+ private InferenceScope(InferenceCallDetails details, AgentDetails agentDetails, TenantDetails tenantDetails, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, CallerDetails? callerDetails = null, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null)
: base(
kind: ActivityKind.Client,
agentDetails: agentDetails,
tenantDetails: tenantDetails,
operationName: details.OperationName.ToString(),
activityName: $"{details.OperationName} {details.Model}",
+ startTime: startTime,
+ endTime: endTime,
parentId: parentId,
conversationId: conversationId,
sourceMetadata: sourceMetadata,
diff --git a/src/Observability/Runtime/Tracing/Scopes/InvokeAgentScope.cs b/src/Observability/Runtime/Tracing/Scopes/InvokeAgentScope.cs
index 755221aa..5f0b4fac 100644
--- a/src/Observability/Runtime/Tracing/Scopes/InvokeAgentScope.cs
+++ b/src/Observability/Runtime/Tracing/Scopes/InvokeAgentScope.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System;
using System.Diagnostics;
using Microsoft.Agents.A365.Observability.Runtime.Tracing.Contracts;
@@ -31,6 +32,8 @@ public sealed class InvokeAgentScope : OpenTelemetryScope
/// The details of the non-agentic caller.
/// The conversation ID for the agent invocation.
/// Optional threat diagnostics summary containing security-related information about blocked actions.
+ /// Optional explicit start time. Useful when recording an agent invocation after execution has already completed.
+ /// Optional explicit end time. When provided, the span will use this timestamp when disposed instead of the current wall-clock time.
/// A new InvokeAgentScope instance.
///
///
@@ -50,9 +53,9 @@ public sealed class InvokeAgentScope : OpenTelemetryScope
///
///
public static InvokeAgentScope Start(
- InvokeAgentDetails invokeAgentDetails, TenantDetails tenantDetails, Request? request = null, AgentDetails? callerAgentDetails = null, CallerDetails? callerDetails = null, string? conversationId = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null) => new InvokeAgentScope(invokeAgentDetails, tenantDetails, request, callerAgentDetails, callerDetails, conversationId, threatDiagnosticsSummary);
+ InvokeAgentDetails invokeAgentDetails, TenantDetails tenantDetails, Request? request = null, AgentDetails? callerAgentDetails = null, CallerDetails? callerDetails = null, string? conversationId = null, ThreatDiagnosticsSummary? threatDiagnosticsSummary = null, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null) => new InvokeAgentScope(invokeAgentDetails, tenantDetails, request, callerAgentDetails, callerDetails, conversationId, threatDiagnosticsSummary, startTime, endTime);
- private InvokeAgentScope(InvokeAgentDetails invokeAgentDetails, TenantDetails tenantDetails, Request? request, AgentDetails? callerAgentDetails, CallerDetails? callerDetails, string? conversationId, ThreatDiagnosticsSummary? threatDiagnosticsSummary)
+ private InvokeAgentScope(InvokeAgentDetails invokeAgentDetails, TenantDetails tenantDetails, Request? request, AgentDetails? callerAgentDetails, CallerDetails? callerDetails, string? conversationId, ThreatDiagnosticsSummary? threatDiagnosticsSummary, DateTimeOffset? startTime, DateTimeOffset? endTime)
: base(
kind: ActivityKind.Client,
agentDetails: invokeAgentDetails.Details,
@@ -61,6 +64,8 @@ private InvokeAgentScope(InvokeAgentDetails invokeAgentDetails, TenantDetails te
activityName: string.IsNullOrWhiteSpace(invokeAgentDetails.Details.AgentName)
? OperationName
: $"invoke_agent {invokeAgentDetails.Details.AgentName}",
+ startTime: startTime,
+ endTime: endTime,
conversationId: conversationId,
sourceMetadata: request?.SourceMetadata,
callerDetails: callerDetails)
diff --git a/src/Observability/Runtime/Tracing/Scopes/OpenTelemetryScope.cs b/src/Observability/Runtime/Tracing/Scopes/OpenTelemetryScope.cs
index 14e22b42..c3db1c04 100644
--- a/src/Observability/Runtime/Tracing/Scopes/OpenTelemetryScope.cs
+++ b/src/Observability/Runtime/Tracing/Scopes/OpenTelemetryScope.cs
@@ -43,13 +43,15 @@ public abstract class OpenTelemetryScope : IDisposable
/// The name of the operation being traced.
/// The name of the activity for display purposes.
/// Optional custom start time for the scope. If not provided, the current time is used.
+ /// Optional custom end time for the scope. When provided, the span will use this timestamp when disposed instead of the current wall-clock time.
/// Optional parent ID for the activity.
/// Optional conversation id.
/// Optional source metadata.
/// Optional details about the non-agentic caller.
- protected OpenTelemetryScope(ActivityKind kind, AgentDetails agentDetails, TenantDetails tenantDetails, string operationName, string activityName, DateTimeOffset? startTime = null, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, CallerDetails? callerDetails = null)
+ protected OpenTelemetryScope(ActivityKind kind, AgentDetails agentDetails, TenantDetails tenantDetails, string operationName, string activityName, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null, string? parentId = null, string? conversationId = null, SourceMetadata? sourceMetadata = null, CallerDetails? callerDetails = null)
{
customStartTime = startTime;
+ customEndTime = endTime;
activity = ActivitySource.CreateActivity(activityName, kind, default(ActivityContext));
if (!string.IsNullOrEmpty(parentId))
{
diff --git a/src/Observability/Runtime/Tracing/Scopes/OutputScope.cs b/src/Observability/Runtime/Tracing/Scopes/OutputScope.cs
index 3c24fb34..2c00557e 100644
--- a/src/Observability/Runtime/Tracing/Scopes/OutputScope.cs
+++ b/src/Observability/Runtime/Tracing/Scopes/OutputScope.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Agents.A365.Observability.Runtime.Tracing.Contracts;
@@ -26,17 +27,21 @@ public sealed class OutputScope : OpenTelemetryScope
/// Tenant context used for telemetry enrichment and correlation.
/// Response containing output messages.
/// Optional parent Activity ID used to link this span to an upstream operation.
+ /// Optional explicit start time. Useful when recording an output operation after execution has already completed.
+ /// Optional explicit end time. When provided, the span will use this timestamp when disposed instead of the current wall-clock time.
/// A new OutputScope instance.
- public static OutputScope Start(AgentDetails agentDetails, TenantDetails tenantDetails, Response response, string? parentId = null)
- => new OutputScope(agentDetails, tenantDetails, response, parentId);
+ public static OutputScope Start(AgentDetails agentDetails, TenantDetails tenantDetails, Response response, string? parentId = null, DateTimeOffset? startTime = null, DateTimeOffset? endTime = null)
+ => new OutputScope(agentDetails, tenantDetails, response, parentId, startTime, endTime);
- private OutputScope(AgentDetails agentDetails, TenantDetails tenantDetails, Response response, string? parentId)
+ private OutputScope(AgentDetails agentDetails, TenantDetails tenantDetails, Response response, string? parentId, DateTimeOffset? startTime, DateTimeOffset? endTime)
: base(
kind: ActivityKind.Client,
agentDetails: agentDetails,
tenantDetails: tenantDetails,
operationName: OperationName,
activityName: $"{OperationName} {agentDetails?.AgentId}",
+ startTime: startTime,
+ endTime: endTime,
parentId: parentId)
{
if (response.Messages.Count > 0)
diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/ExecuteToolScopeTest.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/ExecuteToolScopeTest.cs
index 3bcc5f65..89830cfa 100644
--- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/ExecuteToolScopeTest.cs
+++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/ExecuteToolScopeTest.cs
@@ -258,4 +258,73 @@ public void Start_SetsCallerDetails_WhenProvided()
activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerClientIpKey, callerDetails.CallerClientIP!.ToString());
activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerTenantIdKey, callerDetails.TenantId!);
}
+
+ [TestMethod]
+ public void Start_WithCustomStartTime_SetsActivityStartTime()
+ {
+ // Arrange
+ var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 20, TimeSpan.Zero);
+
+ // Act
+ var activity = ListenForActivity(() =>
+ {
+ using var scope = ExecuteToolScope.Start(
+ new ToolCallDetails("TestTool", "args"),
+ Util.GetAgentDetails(),
+ Util.GetTenantDetails(),
+ startTime: customStartTime);
+ });
+
+ // Assert
+ var startTime = new DateTimeOffset(activity.StartTimeUtc);
+ startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
+ }
+
+ [TestMethod]
+ public void Start_WithCustomStartAndEndTime_SetsActivityTimes()
+ {
+ // Arrange
+ var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 20, TimeSpan.Zero);
+ var customEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 25, TimeSpan.Zero); // 5 seconds later
+
+ // Act
+ var activity = ListenForActivity(() =>
+ {
+ using var scope = ExecuteToolScope.Start(
+ new ToolCallDetails("TestTool", "args"),
+ Util.GetAgentDetails(),
+ Util.GetTenantDetails(),
+ startTime: customStartTime,
+ endTime: customEndTime);
+ });
+
+ // Assert - Start time should be set to custom time
+ var startTime = new DateTimeOffset(activity.StartTimeUtc);
+ startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
+ }
+
+ [TestMethod]
+ public void SetEndTime_OverridesEndTime()
+ {
+ // Arrange
+ var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 40, TimeSpan.Zero);
+ var initialEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 45, TimeSpan.Zero);
+ var laterEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 48, TimeSpan.Zero);
+
+ // Act
+ var activity = ListenForActivity(() =>
+ {
+ using var scope = ExecuteToolScope.Start(
+ new ToolCallDetails("TestTool", "args"),
+ Util.GetAgentDetails(),
+ Util.GetTenantDetails(),
+ startTime: customStartTime,
+ endTime: initialEndTime);
+ scope.SetEndTime(laterEndTime);
+ });
+
+ // Assert - The start time should be set
+ var startTime = new DateTimeOffset(activity.StartTimeUtc);
+ startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
+ }
}
\ No newline at end of file
diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/InferenceScopeTest.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/InferenceScopeTest.cs
index 798f2d2e..634779fa 100644
--- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/InferenceScopeTest.cs
+++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/InferenceScopeTest.cs
@@ -280,4 +280,85 @@ public void Start_SetsCallerDetails_WhenProvided()
activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerClientIpKey, callerDetails.CallerClientIP!.ToString());
activity.ShouldHaveTag(OpenTelemetryConstants.GenAiCallerTenantIdKey, callerDetails.TenantId!);
}
+
+ [TestMethod]
+ public void Start_WithCustomStartTime_SetsActivityStartTime()
+ {
+ // Arrange
+ var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 20, TimeSpan.Zero);
+ var details = new InferenceCallDetails(
+ InferenceOperationType.Chat,
+ "gpt-4o",
+ "openai");
+
+ // Act
+ var activity = ListenForActivity(() =>
+ {
+ using var scope = InferenceScope.Start(
+ details,
+ Util.GetAgentDetails(),
+ Util.GetTenantDetails(),
+ startTime: customStartTime);
+ });
+
+ // Assert
+ var startTime = new DateTimeOffset(activity.StartTimeUtc);
+ startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
+ }
+
+ [TestMethod]
+ public void Start_WithCustomStartAndEndTime_SetsActivityTimes()
+ {
+ // Arrange
+ var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 20, TimeSpan.Zero);
+ var customEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 25, TimeSpan.Zero); // 5 seconds later
+ var details = new InferenceCallDetails(
+ InferenceOperationType.Chat,
+ "gpt-4o",
+ "openai");
+
+ // Act
+ var activity = ListenForActivity(() =>
+ {
+ using var scope = InferenceScope.Start(
+ details,
+ Util.GetAgentDetails(),
+ Util.GetTenantDetails(),
+ startTime: customStartTime,
+ endTime: customEndTime);
+ });
+
+ // Assert - Start time should be set to custom time
+ var startTime = new DateTimeOffset(activity.StartTimeUtc);
+ startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
+ }
+
+ [TestMethod]
+ public void SetEndTime_OverridesEndTime()
+ {
+ // Arrange
+ var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 40, TimeSpan.Zero);
+ var initialEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 45, TimeSpan.Zero);
+ var laterEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 48, TimeSpan.Zero);
+ var details = new InferenceCallDetails(
+ InferenceOperationType.Chat,
+ "gpt-4o",
+ "openai");
+
+ // Act
+ var activity = ListenForActivity(() =>
+ {
+ using var scope = InferenceScope.Start(
+ details,
+ Util.GetAgentDetails(),
+ Util.GetTenantDetails(),
+ startTime: customStartTime,
+ endTime: initialEndTime);
+ scope.SetEndTime(laterEndTime);
+ });
+
+ // Assert - The start time should be set
+ var startTime = new DateTimeOffset(activity.StartTimeUtc);
+ startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
+ }
}
diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/InvokeAgentScopeTest.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/InvokeAgentScopeTest.cs
index 5601e393..7acb1499 100644
--- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/InvokeAgentScopeTest.cs
+++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/InvokeAgentScopeTest.cs
@@ -306,4 +306,70 @@ public void RecordThreatDiagnosticsSummary_SetsTagCorrectly()
tagValue.Should().Contain("\"reason\":\"Blocked due to policy violation.\"");
tagValue.Should().Contain("data-loss-prevention");
}
+
+ [TestMethod]
+ public void Start_WithCustomStartTime_SetsActivityStartTime()
+ {
+ // Arrange
+ var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 20, TimeSpan.Zero);
+
+ // Act
+ var activity = ListenForActivity(() =>
+ {
+ using var scope = InvokeAgentScope.Start(
+ Details,
+ Util.GetTenantDetails(),
+ startTime: customStartTime);
+ });
+
+ // Assert
+ var startTime = new DateTimeOffset(activity.StartTimeUtc);
+ startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
+ }
+
+ [TestMethod]
+ public void Start_WithCustomStartAndEndTime_SetsActivityTimes()
+ {
+ // Arrange
+ var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 20, TimeSpan.Zero);
+ var customEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 25, TimeSpan.Zero); // 5 seconds later
+
+ // Act
+ var activity = ListenForActivity(() =>
+ {
+ using var scope = InvokeAgentScope.Start(
+ Details,
+ Util.GetTenantDetails(),
+ startTime: customStartTime,
+ endTime: customEndTime);
+ });
+
+ // Assert - Start time should be set to custom time
+ var startTime = new DateTimeOffset(activity.StartTimeUtc);
+ startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
+ }
+
+ [TestMethod]
+ public void SetEndTime_OverridesEndTime()
+ {
+ // Arrange
+ var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 40, TimeSpan.Zero);
+ var initialEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 45, TimeSpan.Zero);
+ var laterEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 48, TimeSpan.Zero);
+
+ // Act
+ var activity = ListenForActivity(() =>
+ {
+ using var scope = InvokeAgentScope.Start(
+ Details,
+ Util.GetTenantDetails(),
+ startTime: customStartTime,
+ endTime: initialEndTime);
+ scope.SetEndTime(laterEndTime);
+ });
+
+ // Assert - The start time should be set
+ var startTime = new DateTimeOffset(activity.StartTimeUtc);
+ startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
+ }
}
\ No newline at end of file
diff --git a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/OutputScopeTest.cs b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/OutputScopeTest.cs
index 20da5dd4..020e30b4 100644
--- a/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/OutputScopeTest.cs
+++ b/src/Tests/Microsoft.Agents.A365.Observability.Runtime.Tests/Tracing/Scopes/OutputScopeTest.cs
@@ -90,4 +90,82 @@ public void Start_WithParentId_SetsParentIdCorrectly()
childActivity.ShouldHaveTag(OpenTelemetryConstants.GenAiOperationNameKey, OutputScope.OperationName);
childActivity.ShouldHaveTag(OpenTelemetryConstants.GenAiOutputMessagesKey, "Test message");
}
+
+ [TestMethod]
+ public void Start_WithCustomStartTime_SetsActivityStartTime()
+ {
+ // Arrange
+ var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 20, TimeSpan.Zero);
+ var response = new Response(new[] { "Test message" });
+ var agentDetails = Util.GetAgentDetails();
+ var tenantDetails = Util.GetTenantDetails();
+
+ // Act
+ var activity = ListenForActivity(() =>
+ {
+ using var scope = OutputScope.Start(
+ agentDetails,
+ tenantDetails,
+ response,
+ startTime: customStartTime);
+ });
+
+ // Assert
+ var startTime = new DateTimeOffset(activity.StartTimeUtc);
+ startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
+ }
+
+ [TestMethod]
+ public void Start_WithCustomStartAndEndTime_SetsActivityTimes()
+ {
+ // Arrange
+ var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 20, TimeSpan.Zero);
+ var customEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 25, TimeSpan.Zero); // 5 seconds later
+ var response = new Response(new[] { "Test message" });
+ var agentDetails = Util.GetAgentDetails();
+ var tenantDetails = Util.GetTenantDetails();
+
+ // Act
+ var activity = ListenForActivity(() =>
+ {
+ using var scope = OutputScope.Start(
+ agentDetails,
+ tenantDetails,
+ response,
+ startTime: customStartTime,
+ endTime: customEndTime);
+ });
+
+ // Assert - Start time should be set to custom time
+ var startTime = new DateTimeOffset(activity.StartTimeUtc);
+ startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
+ }
+
+ [TestMethod]
+ public void SetEndTime_OverridesEndTime()
+ {
+ // Arrange
+ var customStartTime = new DateTimeOffset(2023, 11, 14, 22, 13, 40, TimeSpan.Zero);
+ var initialEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 45, TimeSpan.Zero);
+ var laterEndTime = new DateTimeOffset(2023, 11, 14, 22, 13, 48, TimeSpan.Zero);
+ var response = new Response(new[] { "Test message" });
+ var agentDetails = Util.GetAgentDetails();
+ var tenantDetails = Util.GetTenantDetails();
+
+ // Act
+ var activity = ListenForActivity(() =>
+ {
+ using var scope = OutputScope.Start(
+ agentDetails,
+ tenantDetails,
+ response,
+ startTime: customStartTime,
+ endTime: initialEndTime);
+ scope.SetEndTime(laterEndTime);
+ });
+
+ // Assert - The start time should be set
+ var startTime = new DateTimeOffset(activity.StartTimeUtc);
+ startTime.Should().BeCloseTo(customStartTime, TimeSpan.FromMilliseconds(100));
+ }
}