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
81 changes: 81 additions & 0 deletions dotnet/src/Generated/Rpc.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

133 changes: 69 additions & 64 deletions dotnet/src/Generated/SessionEvents.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dotnet/src/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ void Handler(SessionEvent evt)
}
break;

case SessionIdleEvent:
case SessionIdleEvent idleEvent when idleEvent.Data.Mode != SessionMode.Autopilot:
LoggingHelpers.LogTiming(_logger, LogLevel.Debug, null,
"CopilotSession.SendAndWaitAsync idle received. Elapsed={Elapsed}, SessionId={SessionId}",
totalTimestamp,
Expand Down
60 changes: 60 additions & 0 deletions dotnet/test/Unit/ClientSessionLifetimeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,66 @@ public async Task Generated_Session_Rpc_Throws_When_Session_Disposed()
await Assert.ThrowsAsync<ObjectDisposedException>(() => session.Rpc.Model.GetCurrentAsync());
}

[Fact]
public async Task SendAndWaitAsync_Skips_Autopilot_Continuation_Idle()
{
await using var server = await FakeCopilotServer.StartAsync();
await using var client = new CopilotClient(new CopilotClientOptions { Connection = RuntimeConnection.ForUri(server.Url) });
await using var session = await client.CreateSessionAsync(new SessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll
});

var sendTask = session.SendAndWaitAsync(new MessageOptions { Prompt = "keep going" });
await WaitForRequestAsync(server, "session.send");

var continuationIdleProcessed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
using var subscription = session.On<SessionIdleEvent>(idle =>
{
if (idle.Data.Mode == SessionMode.Autopilot)
{
continuationIdleProcessed.TrySetResult();
}
});

DispatchEvent(session, new AssistantMessageEvent
{
Id = Guid.NewGuid(),
Data = new AssistantMessageData
{
Content = "intermediate",
MessageId = "assistant-1"
}
});
DispatchEvent(session, new SessionIdleEvent
{
Id = Guid.NewGuid(),
Data = new SessionIdleData { Mode = SessionMode.Autopilot }
});

await continuationIdleProcessed.Task.WaitAsync(TimeSpan.FromSeconds(5));
Assert.False(sendTask.IsCompleted);

DispatchEvent(session, new AssistantMessageEvent
{
Id = Guid.NewGuid(),
Data = new AssistantMessageData
{
Content = "final",
MessageId = "assistant-2"
}
});
DispatchEvent(session, new SessionIdleEvent
{
Id = Guid.NewGuid(),
Data = new SessionIdleData { Mode = SessionMode.Interactive }
});

var result = await sendTask.WaitAsync(TimeSpan.FromSeconds(5));
Assert.NotNull(result);
Assert.Equal("final", result.Data.Content);
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static async Task<WeakReference<CopilotSession>> CreateDroppedSessionAsync(CopilotClient client)
{
Expand Down
23 changes: 23 additions & 0 deletions go/rpc/zrpc.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions go/rpc/zsession_events.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading