Repository navigation
fix(aspnetcore): hang up and warn when the OpenAI live sideband closes mid-call - #42
Merged
Merged
Conversation
A Closed event or a sideband that ends mid-call now hangs the call up and logs a warning with the cause, so callers no longer sit in silence.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
yeah no luck on the phone, just build it now
Context: this is the build of a plan to move Spirit's phone line to the direct SIP path GoTo -> Asterisk -> OpenAI. Live tests proved a SIP REFER from Asterisk to GoTo with Refer-To sip:@ is accepted (202, NOTIFY 200) and moves the caller. GoTo's web-calls blind-transfer API rejects the SIP X-ref id (404), so the plan is that the AI session sends the REFER (AgentCore's LiveReferTransfer, used when the host registers no TransferCommand handler) and Asterisk passes it on to GoTo. Separately, the OpenAI live session's audio stopped silently at about 218 s and 278 s in two tests, and AgentCore neither hung the call up nor logged anything, so callers sat in silence. The AgentCore part of the plan: in OpenAiLiveCall, when the sideband receive loop ends on a null message and on a LiveEvent.Closed, hang the call up and finish instead of only finishing, and log a Warning naming the call id and cause on both paths; tests for both. No transfer change. The full OpenAI REFER -> Asterisk -> GoTo chain is not yet proven by a live call.
What Changed
OpenAiLiveCall: when the sideband receive loop ends with noClosedevent (sideband_closed), the call now hangs up and then finishes (HangUpAndFinishAsync, reasonFaulted). Before, it only finished, so the phone line stayed open in silence.OpenAiLiveLog: new WarningSessionEndedByOpenAi(EventId 329). It names the call id and the cause, and is logged on that sideband-closed path.OpenAiLiveCallEndTests: a sideband that closes mid-call hangs up once and logs one Warning withsideband_closed. ALiveEvent.Closed(a caller hang-up,close_requested) still finishes the call with reasonCallerHungUp, with no hang-up request and no warning.Risk Assessment
✅ Low: The production change is small: only the sideband-closed (null message) path now hangs up and logs Warning 329. The Closed branch is back to the original FinishAsync with no log, which matches the user's round-1 decision. The only gap is a minor loss of test coverage.
Testing
I drove three call scenarios through the real AgentCore web host. The host got a signed webhook, attached a fake OpenAI sideband, and sent its call-control requests to a recording fake. For each run I saved the hangup requests, the log lines, and the end reason. Sideband close, mid-call or at once, gives exactly one hangup and one Warning 329, also when the host stops after it. A recorded caller hang-up gives no hangup and no warning. The base code fails the sideband scenarios, so the drivers catch the bug. A real OpenAI/SIP phone call was not possible: there is no OpenAI key or SIP trunk here. One run ran the whole AspNetCore test project by accident (all passed). The temporary driver file is deleted, and the worktree is clean. This is not a UI change, so there are no screenshots.
Evidence: Sideband closes mid-call (fixed code): one hangup + Warning 329
POST /v1/live/sessions/rtc_s1/accept POST /v1/live/sessions/rtc_s1/hangup [Warning] SessionEndedByOpenAi (329): the session of the call rtc_s1 ended on OpenAI's side (sideband_closed); the call is hung up. reason=Faulted callId=rtc_s1 cause=sideband_closedEvidence: Caller hang-up via session.closed: no hangup, no warning
Evidence: Sideband closes at once then host stops: still exactly one hangup
~/.no-mistakes/evidence/01M5JR93DWYJWBNFR7Z1NA20J3/before-fix)Evidence: Targeted test run on the target commit
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
src/AgentCore.AspNetCore/Vendors/OpenAiLive/Call/OpenAiLiveCall.cs:274- The code maps LiveEvent.Closed to ConversationEndReason.CallerHungUp, and the old test was named 'ACallerHangUpBeforeTheAckFinishesTheCallWithNoHangUp'. So OpenAI's session.closed is also how a normal caller hang-up arrives. With this change, every normal caller hang-up now does three things: (1) it logs the new Warning 329 'ended on OpenAI's side ... the call is hung up', which is the wrong label for a caller who left; (2) it POSTs /hangup for a call that is already gone; (3) it logs HangupFailed (316) when OpenAI answers that POST with non-2xx (LiveCallFinish.cs:32). The intent asks for the hang-up and the warning on Closed, so the behavior itself conforms. The question is the log noise and the wording: should the warning text be neutral (for example 'OpenAI closed the session ({Cause})'), or should Warning 329 and HangupFailed be skipped when the cause is a normal caller hang-up? The same new log call is at OpenAiLiveCall.cs:177 (null-message path). There the wording is accurate.🔧 Fix applied.
1 info still open:
tests/AgentCore.AspNetCore.Tests/Vendors/OpenAiLive/OpenAiLiveCallEndTests.cs:143- The round-1 fix rewrote the old test ACallerHangUpBeforeTheAckFinishesTheCallWithNoHangUp. It now calls RunningLiveCall.StartAsync without EndsAfterTheFirstAskYaml. The old test ran with that plan, so an engine end was waiting for the ack when the Closed event came. It proved that the waiting end does not hang up a caller who already left. Without the plan, nothing in the test could start a hang-up except the Closed branch, so that race is no longer covered. The user asked to restore the original assertions. Fix: pass EndsAfterTheFirstAskYaml again, e.g. StartAsync(new StallOnCueChatClient(), [hook], EndsAfterTheFirstAskYaml, warned.CreateLogger("test")). Keep the new CallerHungUp/close_requested and zero-warning checks.✅ **Test** - passed
✅ No issues found.
Temporary xUnit driver tests/AgentCore.AspNetCore.Tests/Vendors/OpenAiLive/ZzScratchHangupEvidence.cs (deleted after the run). It starts the real ASP.NET host (OpenAiLiveHost, Kestrel on 127.0.0.1:0), posts a signed incoming-call webhook to /v1/main/call, feeds the recorded p1-a sideband events, and records every request to the fake OpenAI call-control API plus the real ILoggerFactory outputdotnet test tests/AgentCore.AspNetCore.Tests --filter "FullyQualifiedName~ZzScratchHangupEvidence|FullyQualifiedName~OpenAiLiveCallEndTests"on the target commit: 13 passedRegression check: put OpenAiLiveCall.cs back to base 0c6f06c and ran the driver again. S1 and S3 failed with no hangup request and no warning. Restored the file withgit checkoutAn earlier run ofdotnet test tests/AgentCore.AspNetCore.Testsignored the class filter and ran the whole AspNetCore test project: 862 passed✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.
Release
Packages ship by
v*tag; the latest isv0.17.0. This fix should ship as v0.17.1 (bug fix, no API change). Not tagged here.Note on
LiveEvent.ClosedThe recorded
Closedreasons (for exampleclose_requested) cannot tell a caller hang-up from an AI-side end. So theClosedpath is unchanged (finishes asCallerHungUp, no hang-up request, no warning). Only the sideband-closed path (null message, noClosedevent) hangs up and logs Warning 329.