Update meeting summary agent UI

This commit is contained in:
2026-07-01 10:30:28 +02:00
parent 4787bf8cec
commit 92e359646b
18 changed files with 731 additions and 101 deletions
@@ -22,6 +22,7 @@ public sealed class LiteLlmResponsesChatClient : IChatClient
private readonly LiteLlmResponsesCompactionOptions? compactionOptions;
private readonly ILogger? logger;
private readonly Action? retrying;
private readonly Action<string>? reasoningSummaryChanged;
private int responsesRequestCount;
public LiteLlmResponsesChatClient(
@@ -35,7 +36,8 @@ public sealed class LiteLlmResponsesChatClient : IChatClient
LiteLlmResponsesCompactionOptions? compactionOptions = null,
ILogger? logger = null,
bool firstRequestIsUser = true,
Action? retrying = null)
Action? retrying = null,
Action<string>? reasoningSummaryChanged = null)
: this(
new HttpClient { BaseAddress = NormalizeEndpoint(endpoint) },
apiKey,
@@ -47,7 +49,8 @@ public sealed class LiteLlmResponsesChatClient : IChatClient
compactionOptions,
logger,
firstRequestIsUser,
retrying)
retrying,
reasoningSummaryChanged)
{
}
@@ -62,7 +65,8 @@ public sealed class LiteLlmResponsesChatClient : IChatClient
LiteLlmResponsesCompactionOptions? compactionOptions = null,
ILogger? logger = null,
bool firstRequestIsUser = true,
Action? retrying = null)
Action? retrying = null,
Action<string>? reasoningSummaryChanged = null)
{
this.httpClient = httpClient;
this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
@@ -74,6 +78,7 @@ public sealed class LiteLlmResponsesChatClient : IChatClient
this.compactionOptions = compactionOptions;
this.logger = logger;
this.retrying = retrying;
this.reasoningSummaryChanged = reasoningSummaryChanged;
responsesRequestCount = firstRequestIsUser ? 0 : 1;
}
@@ -90,7 +95,8 @@ public sealed class LiteLlmResponsesChatClient : IChatClient
var payload = await CreateCompactedPayloadAsync(messages.ToList(), options, cancellationToken).ConfigureAwait(false);
var responseJson = await PostWithRetryAsync(payload, cancellationToken).ConfigureAwait(false);
var response = ParseResponseJson(responseJson);
var response = ParseResponseJson(responseJson, out var reasoningSummaries);
ReportVisibleReasoningSummaries(reasoningSummaries);
LogResponseDiagnostics(responseJson, response);
ThrowIfResponseHasNoContent(responseJson, response);
LogResponseUsage(response.Usage);
@@ -122,10 +128,18 @@ public sealed class LiteLlmResponsesChatClient : IChatClient
}
public static ChatResponse ParseResponseJson(string responseJson)
{
return ParseResponseJson(responseJson, out _);
}
private static ChatResponse ParseResponseJson(
string responseJson,
out IReadOnlyList<string> reasoningSummaries)
{
using var document = JsonDocument.Parse(responseJson);
var root = document.RootElement;
var contents = new List<AIContent>();
reasoningSummaries = ExtractVisibleReasoningSummaries(root);
if (root.TryGetProperty("output", out var output) && output.ValueKind == JsonValueKind.Array)
{
@@ -162,6 +176,54 @@ public sealed class LiteLlmResponsesChatClient : IChatClient
};
}
internal static IReadOnlyList<string> ExtractVisibleReasoningSummaries(string responseJson)
{
using var document = JsonDocument.Parse(responseJson);
return ExtractVisibleReasoningSummaries(document.RootElement);
}
private static IReadOnlyList<string> ExtractVisibleReasoningSummaries(JsonElement root)
{
var summaries = new List<string>();
if (!root.TryGetProperty("output", out var output) || output.ValueKind != JsonValueKind.Array)
{
return summaries;
}
foreach (var item in output.EnumerateArray())
{
if (GetString(item, "type") == "reasoning")
{
AddVisibleReasoningSummaryText(summaries, item);
}
}
return summaries;
}
private void ReportVisibleReasoningSummaries(IReadOnlyList<string> summaries)
{
if (reasoningSummaryChanged is null)
{
return;
}
foreach (var summary in summaries)
{
try
{
reasoningSummaryChanged(summary);
}
catch (Exception exception)
{
logger?.LogWarning(
exception,
"Reasoning summary callback failed; continuing with the LiteLLM response.");
}
}
}
private async Task<JsonObject> CreateCompactedPayloadAsync(
IReadOnlyList<ChatMessage> messages,
ChatOptions? options,
@@ -660,6 +722,66 @@ public sealed class LiteLlmResponsesChatClient : IChatClient
}
}
private static void AddVisibleReasoningSummaryText(List<string> summaries, JsonElement item)
{
AddVisibleReasoningSummaryText(summaries, item, "summary");
AddVisibleReasoningSummaryText(summaries, item, "content");
}
private static void AddVisibleReasoningSummaryText(
List<string> summaries,
JsonElement item,
string propertyName)
{
if (!item.TryGetProperty(propertyName, out var property))
{
return;
}
if (property.ValueKind == JsonValueKind.String)
{
AddNonEmpty(summaries, property.GetString());
return;
}
if (property.ValueKind != JsonValueKind.Array)
{
return;
}
foreach (var summaryItem in property.EnumerateArray())
{
if (summaryItem.ValueKind == JsonValueKind.String)
{
AddNonEmpty(summaries, summaryItem.GetString());
}
else if (summaryItem.ValueKind == JsonValueKind.Object)
{
if (propertyName == "content" && !IsSummaryContent(summaryItem))
{
continue;
}
AddNonEmpty(summaries, GetString(summaryItem, "text"));
}
}
}
private static bool IsSummaryContent(JsonElement item)
{
var type = GetString(item, "type");
return !string.IsNullOrWhiteSpace(type)
&& type.Contains("summary", StringComparison.OrdinalIgnoreCase);
}
private static void AddNonEmpty(List<string> values, string? value)
{
if (!string.IsNullOrWhiteSpace(value))
{
values.Add(value.Trim());
}
}
private static Dictionary<string, object?> ParseArguments(string? arguments)
{
if (string.IsNullOrWhiteSpace(arguments))