Handle settings logs agent failures
PR and Push Build/Test / build-and-test (push) Successful in 9m8s

This commit is contained in:
2026-06-09 17:10:06 +02:00
parent 8af89a622d
commit ecdd23bde7
8 changed files with 147 additions and 15 deletions
@@ -126,12 +126,14 @@ public sealed class LiteLlmResponsesChatClientTests
}
""")
});
using var client = CreateClient(handler, reconnectionAttempts: 1);
var retryCount = 0;
using var client = CreateClient(handler, reconnectionAttempts: 1, retrying: () => retryCount++);
var response = await client.GetResponseAsync([new ChatMessage(ChatRole.User, "write summary")]);
Assert.Equal("Done.", response.Text);
Assert.Equal(2, handler.RequestCount);
Assert.Equal(1, retryCount);
}
[Fact]
@@ -303,7 +305,8 @@ public sealed class LiteLlmResponsesChatClientTests
private static LiteLlmResponsesChatClient CreateClient(
HttpMessageHandler handler,
int reconnectionAttempts,
LiteLlmResponsesCompactionOptions? compactionOptions = null)
LiteLlmResponsesCompactionOptions? compactionOptions = null,
Action? retrying = null)
{
return new LiteLlmResponsesChatClient(
new HttpClient(handler)
@@ -316,7 +319,8 @@ public sealed class LiteLlmResponsesChatClientTests
reasoningEffort: "none",
reconnectionAttempts,
TimeSpan.Zero,
compactionOptions);
compactionOptions,
retrying: retrying);
}
private sealed class SequencedHttpMessageHandler : HttpMessageHandler
@@ -853,6 +853,52 @@ public sealed class WorkflowRulesEditorTests
Assert.Equal("Updated rules.", viewModel.Messages[1].Content);
}
[Fact]
public async Task ViewModelShowsAgentErrorWhenRequestTimesOut()
{
var viewModel = new WorkflowRulesEditorChatViewModel(
new ThrowingRulesEditorPipeline(new TaskCanceledException("The request timed out.")))
{
Draft = "check logs"
};
await viewModel.SendAsync();
Assert.False(viewModel.IsThinking);
Assert.Equal("", viewModel.Draft);
Assert.Equal(
[
new WorkflowRulesEditorChatMessage(WorkflowRulesEditorChatRole.User, "check logs"),
new WorkflowRulesEditorChatMessage(
WorkflowRulesEditorChatRole.Agent,
"Settings and logs failed: The request timed out.")
],
viewModel.Messages.ToArray());
}
[Fact]
public async Task ViewModelShowsReconnectStatusReportedByPipeline()
{
var pipeline = new StatusReportingRulesEditorPipeline("Updated rules.");
var viewModel = new WorkflowRulesEditorChatViewModel(pipeline)
{
Draft = "check logs"
};
var sendTask = viewModel.SendAsync();
await pipeline.Reported.Task.WaitAsync(TimeSpan.FromSeconds(2));
Assert.True(viewModel.IsThinking);
Assert.Equal("Reconnecting...", viewModel.ActivityMessage);
pipeline.Release.SetResult();
await sendTask.WaitAsync(TimeSpan.FromSeconds(2));
Assert.False(viewModel.IsThinking);
Assert.Equal("Thinking...", viewModel.ActivityMessage);
Assert.Equal("Updated rules.", viewModel.Messages[1].Content);
}
[Theory]
[InlineData(0, 500, 0, true)]
[InlineData(0, 500, 400, true)]
@@ -1149,7 +1195,8 @@ public sealed class WorkflowRulesEditorTests
public async Task<WorkflowRulesEditorChatResult> SendAsync(
IReadOnlyList<WorkflowRulesEditorChatMessage> conversation,
string userMessage,
CancellationToken cancellationToken)
CancellationToken cancellationToken,
Action<string>? statusChanged = null)
{
Started.SetResult();
await Release.Task.WaitAsync(cancellationToken);
@@ -1162,6 +1209,56 @@ public sealed class WorkflowRulesEditorTests
}
}
private sealed class ThrowingRulesEditorPipeline : IWorkflowRulesEditorChatPipeline
{
private readonly Exception exception;
public ThrowingRulesEditorPipeline(Exception exception)
{
this.exception = exception;
}
public Task<WorkflowRulesEditorChatResult> SendAsync(
IReadOnlyList<WorkflowRulesEditorChatMessage> conversation,
string userMessage,
CancellationToken cancellationToken,
Action<string>? statusChanged = null)
{
throw exception;
}
}
private sealed class StatusReportingRulesEditorPipeline : IWorkflowRulesEditorChatPipeline
{
private readonly string response;
public StatusReportingRulesEditorPipeline(string response)
{
this.response = response;
}
public TaskCompletionSource Reported { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
public TaskCompletionSource Release { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously);
public async Task<WorkflowRulesEditorChatResult> SendAsync(
IReadOnlyList<WorkflowRulesEditorChatMessage> conversation,
string userMessage,
CancellationToken cancellationToken,
Action<string>? statusChanged = null)
{
statusChanged?.Invoke("Reconnecting...");
Reported.SetResult();
await Release.Task.WaitAsync(cancellationToken);
return new WorkflowRulesEditorChatResult(
response,
conversation
.Append(new WorkflowRulesEditorChatMessage(WorkflowRulesEditorChatRole.User, userMessage))
.Append(new WorkflowRulesEditorChatMessage(WorkflowRulesEditorChatRole.Agent, response))
.ToList());
}
}
private sealed class CapturingLogger : ILogger
{
public List<string> Messages { get; } = [];