mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-25 20:33:58 +02:00
feat: add sidecar turn cancellation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -219,6 +219,7 @@ public sealed class SidecarProtocolHostTests
|
||||
{
|
||||
Assert.Equal("turn-complete", completionEvent.GetProperty("type").GetString());
|
||||
Assert.Equal("session-1", completionEvent.GetProperty("sessionId").GetString());
|
||||
Assert.False(completionEvent.GetProperty("cancelled").GetBoolean());
|
||||
JsonElement[] messages = completionEvent.GetProperty("messages").EnumerateArray().ToArray();
|
||||
Assert.Single(messages);
|
||||
Assert.Equal("Hello world", messages[0].GetProperty("content").GetString());
|
||||
@@ -287,6 +288,7 @@ public sealed class SidecarProtocolHostTests
|
||||
completionEvent =>
|
||||
{
|
||||
Assert.Equal("turn-complete", completionEvent.GetProperty("type").GetString());
|
||||
Assert.False(completionEvent.GetProperty("cancelled").GetBoolean());
|
||||
},
|
||||
commandCompleteEvent =>
|
||||
{
|
||||
@@ -295,6 +297,75 @@ public sealed class SidecarProtocolHostTests
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CancelTurnCommand_CancelsInProgressTurnAndCompletesBothCommands()
|
||||
{
|
||||
SidecarProtocolHost host = new(
|
||||
new PatternValidator(),
|
||||
new FakeWorkflowRunner(async (command, onDelta, onActivity, onApproval, cancellationToken) =>
|
||||
{
|
||||
await Task.Delay(Timeout.Infinite, cancellationToken);
|
||||
return [];
|
||||
}));
|
||||
|
||||
IReadOnlyList<JsonElement> events = await RunHostAsync(
|
||||
[
|
||||
CreateRunTurnCommand(requestId: "turn-cancel"),
|
||||
new CancelTurnCommandDto
|
||||
{
|
||||
Type = "cancel-turn",
|
||||
RequestId = "cancel-command-1",
|
||||
TargetRequestId = "turn-cancel",
|
||||
},
|
||||
],
|
||||
host);
|
||||
|
||||
JsonElement turnCompleteEvent = AssertSingleEvent(events, "turn-complete", "turn-cancel");
|
||||
Assert.Equal("session-1", turnCompleteEvent.GetProperty("sessionId").GetString());
|
||||
Assert.True(turnCompleteEvent.GetProperty("cancelled").GetBoolean());
|
||||
Assert.Empty(turnCompleteEvent.GetProperty("messages").EnumerateArray().ToArray());
|
||||
|
||||
AssertSingleEvent(events, "command-complete", "turn-cancel");
|
||||
AssertSingleEvent(events, "command-complete", "cancel-command-1");
|
||||
Assert.DoesNotContain(events, evt => evt.GetProperty("type").GetString() == "command-error");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CancelTurnCommand_UnknownTarget_CompletesWithoutError()
|
||||
{
|
||||
IReadOnlyList<JsonElement> events = await RunHostAsync(new CancelTurnCommandDto
|
||||
{
|
||||
Type = "cancel-turn",
|
||||
RequestId = "cancel-command-unknown",
|
||||
TargetRequestId = "missing-turn",
|
||||
});
|
||||
|
||||
JsonElement completionEvent = Assert.Single(events);
|
||||
Assert.Equal("command-complete", completionEvent.GetProperty("type").GetString());
|
||||
Assert.Equal("cancel-command-unknown", completionEvent.GetProperty("requestId").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CancelTurnCommand_AfterTurnCompletion_IsNoOp()
|
||||
{
|
||||
SidecarProtocolHost host = new(
|
||||
new PatternValidator(),
|
||||
new FakeWorkflowRunner(async (command, onDelta, onActivity, onApproval, cancellationToken) => []));
|
||||
|
||||
await RunHostAsync(CreateRunTurnCommand(requestId: "turn-completed"), host);
|
||||
|
||||
IReadOnlyList<JsonElement> events = await RunHostAsync(new CancelTurnCommandDto
|
||||
{
|
||||
Type = "cancel-turn",
|
||||
RequestId = "cancel-command-completed",
|
||||
TargetRequestId = "turn-completed",
|
||||
}, host);
|
||||
|
||||
JsonElement completionEvent = Assert.Single(events);
|
||||
Assert.Equal("command-complete", completionEvent.GetProperty("type").GetString());
|
||||
Assert.Equal("cancel-command-completed", completionEvent.GetProperty("requestId").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveApprovalCommand_DelegatesToWorkflowRunnerAndCompletes()
|
||||
{
|
||||
@@ -369,7 +440,17 @@ public sealed class SidecarProtocolHostTests
|
||||
object command,
|
||||
SidecarProtocolHost? host = null)
|
||||
{
|
||||
string input = JsonSerializer.Serialize(command, JsonOptions) + Environment.NewLine;
|
||||
return await RunHostAsync([command], host);
|
||||
}
|
||||
|
||||
private static async Task<IReadOnlyList<JsonElement>> RunHostAsync(
|
||||
IReadOnlyList<object> commands,
|
||||
SidecarProtocolHost? host = null)
|
||||
{
|
||||
string input = string.Join(
|
||||
Environment.NewLine,
|
||||
commands.Select(command => JsonSerializer.Serialize(command, JsonOptions)))
|
||||
+ Environment.NewLine;
|
||||
|
||||
using StringReader reader = new(input);
|
||||
using StringWriter writer = new();
|
||||
@@ -378,6 +459,16 @@ public sealed class SidecarProtocolHostTests
|
||||
return ParseEvents(writer.ToString());
|
||||
}
|
||||
|
||||
private static JsonElement AssertSingleEvent(
|
||||
IEnumerable<JsonElement> events,
|
||||
string eventType,
|
||||
string requestId)
|
||||
{
|
||||
return Assert.Single(events.Where(evt =>
|
||||
evt.GetProperty("type").GetString() == eventType
|
||||
&& evt.GetProperty("requestId").GetString() == requestId));
|
||||
}
|
||||
|
||||
private static SidecarProtocolHost CreateHostForTests()
|
||||
{
|
||||
return new SidecarProtocolHost(
|
||||
@@ -474,6 +565,41 @@ public sealed class SidecarProtocolHostTests
|
||||
};
|
||||
}
|
||||
|
||||
private static RunTurnCommandDto CreateRunTurnCommand(
|
||||
string requestId = "turn-1",
|
||||
string sessionId = "session-1")
|
||||
{
|
||||
return new RunTurnCommandDto
|
||||
{
|
||||
Type = "run-turn",
|
||||
RequestId = requestId,
|
||||
SessionId = sessionId,
|
||||
ProjectPath = "C:\\workspace\\project",
|
||||
Pattern = new PatternDefinitionDto
|
||||
{
|
||||
Id = "pattern-1",
|
||||
Name = "Single Agent",
|
||||
Mode = "single",
|
||||
Availability = "available",
|
||||
Agents =
|
||||
[
|
||||
CreateAgent(name: "Primary"),
|
||||
],
|
||||
},
|
||||
Messages =
|
||||
[
|
||||
new ChatMessageDto
|
||||
{
|
||||
Id = "user-1",
|
||||
Role = "user",
|
||||
AuthorName = "You",
|
||||
Content = "Hello",
|
||||
CreatedAt = "2026-01-01T00:00:00.0000000Z",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
private sealed class FakeWorkflowRunner : ITurnWorkflowRunner
|
||||
{
|
||||
private readonly Func<
|
||||
|
||||
Reference in New Issue
Block a user