Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ internal async Task RunAsync(CancellationToken cancellationToken)

await _hearing.FlushAsync().ConfigureAwait(false);

await _finish.FinishAsync(ConversationEndReason.Faulted, SidebandClosedCause).ConfigureAwait(false);
OpenAiLiveLog.SessionEndedByOpenAi(Logger, CallId, SidebandClosedCause);

await _finish.HangUpAndFinishAsync(ConversationEndReason.Faulted, SidebandClosedCause, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
Expand Down
3 changes: 3 additions & 0 deletions src/AgentCore.AspNetCore/Vendors/OpenAiLive/OpenAiLiveLog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,8 @@ internal static partial class OpenAiLiveLog

[LoggerMessage(EventId = 328, Level = LogLevel.Warning, Message = "a fact for the voice of the call {CallId} could not be sent, so the voice goes on without it.")]
internal static partial void VoiceContextFailed(ILogger logger, string callId, Exception fault);

[LoggerMessage(EventId = 329, Level = LogLevel.Warning, Message = "the session of the call {CallId} ended on OpenAI's side ({Cause}); the call is hung up.")]
internal static partial void SessionEndedByOpenAi(ILogger logger, string callId, string cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using AgentCore.AspNetCore.Vendors.OpenAiLive.Wire;
using AgentCore.Domain.Audit;
using AgentCore.TestSupport;
using Microsoft.Extensions.Logging;
using Xunit;
using static AgentCore.AspNetCore.Tests.Vendors.OpenAiLive.LiveSent;

Expand Down Expand Up @@ -132,21 +133,50 @@ public async Task AMultiPieceAnswerEndsOnlyAfterItsLastPieceIsAcked()
Assert.False(hungUpOnTheFirstAck);
}

// A caller who hangs up before the ack is not hung up again, and the call still finishes.
// A Closed event is how a caller's hang-up arrives, so the call finishes with no hang-up request and no warning.
[Fact(Timeout = 30_000)]
public async Task ACallerHangUpBeforeTheAckFinishesTheCallWithNoHangUp()
public async Task AClosedEventFinishesTheCallWithNoHangUpAndNoWarning()
{
await using RunningLiveCall running = await RunningLiveCall.StartAsync(new StallOnCueChatClient(), [], EndsAfterTheFirstAskYaml);
using EventObservedLoggerProvider warned = new("SessionEndedByOpenAi");
RecordingHook hook = new();
await using RunningLiveCall running = await RunningLiveCall.StartAsync(
new StallOnCueChatClient(), [hook], logger: warned.CreateLogger("test"));
IReadOnlyList<string> log = LiveLog.Inbound("p1-a");
int delegation = LiveLog.IndexOf(log, OpenAiLiveEvents.DelegationCreated);

running.Sideband.Push(log.Take(delegation + 1));
_ = await running.Sideband.WaitForSentAsync(IsCommentary);
running.Sideband.Push([log[LiveLog.IndexOf(log, OpenAiLiveEvents.Closed)]]);
await running.Loop;
ConversationEnded ended = await hook.WaitForAsync<ConversationEnded>();

Assert.False(running.HungUp.Task.IsCompleted);
Assert.True(running.Call.HasEnded);
Assert.Equal((ConversationEndReason.CallerHungUp, "close_requested"), (ended.Reason, ended.Call!.Cause));
Assert.Equal(0, warned.Count);
}

// A sideband that closes mid-call with no Closed event is an end on OpenAI's side: one hang-up, one warning.
[Fact(Timeout = 30_000)]
public async Task ASidebandClosingMidCallHangsTheCallUpOnceAndLogsAWarning()
{
using EventObservedLoggerProvider warned = new("SessionEndedByOpenAi");
RecordingHook hook = new();
await using RunningLiveCall running = await RunningLiveCall.StartAsync(
new StallOnCueChatClient(), [hook], logger: warned.CreateLogger("test"));
IReadOnlyList<string> log = LiveLog.Inbound("p1-a");

running.Sideband.Push(log.Take(LiveLog.IndexOf(log, OpenAiLiveEvents.DelegationCreated) + 1));
_ = await running.Sideband.WaitForSentAsync(IsCommentary);
running.Sideband.Complete();
await running.Loop;
ConversationEnded ended = await hook.WaitForAsync<ConversationEnded>();

Assert.True(running.HungUp.Task.IsCompleted);
Assert.Equal((ConversationEndReason.Faulted, "sideband_closed"), (ended.Reason, ended.Call!.Cause));
Assert.Equal(1, warned.Count);
Assert.Equal(LogLevel.Warning, warned.Level);
Assert.Contains("sideband_closed", warned.Message, StringComparison.Ordinal);
}

// With no commentary to wait for, the hang-up is at once. The clock never moves, so no wait on a
Expand Down
Loading