Cleanup logging

This commit is contained in:
Javier Calvarro Nelson
2025-12-10 19:00:30 +01:00
Unverified
parent 495c2e9a92
commit 38f3e6a5ec
9 changed files with 4 additions and 285 deletions
@@ -29,13 +29,10 @@ public partial class AgentStateBoundary<TState> : IComponent, IDisposable
/// </summary>
[Parameter] public EventCallback<IAgentBoundaryContext> OnContextCreated { get; set; }
[Inject] private ILogger<AgentStateBoundary<TState>> Logger { get; set; } = default!;
public void Attach(RenderHandle renderHandle)
{
this._renderHandle = renderHandle;
this._renderWithState = this.RenderWithState;
Log.AgentBoundaryAttached(this.Logger);
}
// Agent boundary renders once when it receives the initial set of parameters
@@ -62,17 +59,6 @@ public partial class AgentStateBoundary<TState> : IComponent, IDisposable
bool refresh = agentChanged || threadChanged || stateChanged;
Log.AgentBoundaryRefreshCheck(
this.Logger,
refresh,
agentChanged,
threadChanged,
stateChanged,
this.Agent?.GetHashCode() ?? 0,
this._currentAgent?.GetHashCode() ?? 0,
thread?.GetHashCode() ?? 0,
this._currentThread?.GetHashCode() ?? 0);
if (refresh)
{
this._context?.Dispose();
@@ -80,14 +66,12 @@ public partial class AgentStateBoundary<TState> : IComponent, IDisposable
this._currentThread = null;
}
Log.AgentBoundaryParametersSet(this.Logger, refresh);
this._currentAgent = this.Agent;
this._currentThread = thread;
// Agent is validated non-null above, thread is either from Thread parameter or GetNewThread()
bool isNewContext = this._context == null;
this._context ??= new AgentBoundaryContext<TState>(this.Agent!, thread!, this.Logger);
this._context ??= new AgentBoundaryContext<TState>(this.Agent!, thread!);
this._context.CurrentState = this.State;
// Invoke the context created callback if we created a new context
@@ -106,7 +90,6 @@ public partial class AgentStateBoundary<TState> : IComponent, IDisposable
private void Render()
{
Log.AgentBoundaryRendering(this.Logger);
this._renderHandle.Render(builder =>
{
builder.OpenComponent<CascadingValue<AgentBoundaryContext<TState>>>(0);
@@ -138,30 +121,11 @@ public partial class AgentStateBoundary<TState> : IComponent, IDisposable
{
if (disposing)
{
Log.AgentBoundaryDisposed(this.Logger);
this._context?.Dispose();
}
this._disposed = true;
}
}
private static partial class Log
{
[LoggerMessage(Level = LogLevel.Debug, Message = "AgentStateBoundary attached to render handle")]
public static partial void AgentBoundaryAttached(ILogger logger);
[LoggerMessage(Level = LogLevel.Debug, Message = "AgentStateBoundary refresh check: refresh={Refresh}, agentChanged={AgentChanged}, threadChanged={ThreadChanged}, stateChanged={StateChanged}, agentHash={AgentHash}, currentAgentHash={CurrentAgentHash}, threadHash={ThreadHash}, currentThreadHash={CurrentThreadHash}")]
public static partial void AgentBoundaryRefreshCheck(ILogger logger, bool refresh, bool agentChanged, bool threadChanged, bool stateChanged, int agentHash, int currentAgentHash, int threadHash, int currentThreadHash);
[LoggerMessage(Level = LogLevel.Debug, Message = "AgentStateBoundary parameters set, refresh required: {RefreshRequired}")]
public static partial void AgentBoundaryParametersSet(ILogger logger, bool refreshRequired);
[LoggerMessage(Level = LogLevel.Debug, Message = "AgentStateBoundary rendering")]
public static partial void AgentBoundaryRendering(ILogger logger);
[LoggerMessage(Level = LogLevel.Debug, Message = "AgentStateBoundary disposed")]
public static partial void AgentBoundaryDisposed(ILogger logger);
}
}
public class AgentBoundary : AgentStateBoundary<object?>
@@ -10,7 +10,6 @@ public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext
private readonly AIAgent _agent;
private readonly AgentThread _thread;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly ILogger _logger;
private readonly List<ChatMessage> _messages = [];
private readonly List<ChatMessage> _pendingMessages = [];
private readonly List<Action> _messageChangeSubscribers = [];
@@ -30,15 +29,11 @@ public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext
public ChatResponseUpdate? CurrentUpdate { get; private set; }
ILogger IAgentBoundaryContext.Logger => this._logger;
public AgentBoundaryContext(AIAgent agent, AgentThread thread, ILogger logger)
public AgentBoundaryContext(AIAgent agent, AgentThread thread)
{
this._agent = agent;
this._thread = thread;
this._cancellationTokenSource = new CancellationTokenSource();
this._logger = logger;
Log.AgentBoundaryContextCreated(this._logger);
}
/// <summary>
@@ -49,7 +44,6 @@ public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext
{
ArgumentNullException.ThrowIfNull(tool);
this._tools.Add(tool);
Log.ToolRegistered(this._logger, tool.Name);
}
/// <summary>
@@ -73,7 +67,6 @@ public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext
{
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
this._pendingResponses[key] = tcs;
Log.WaitForResponseStarted(this._logger, key);
return tcs.Task;
}
@@ -90,17 +83,14 @@ public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext
{
this._pendingResponses.Remove(key);
tcs.TrySetResult(response);
Log.ResponseProvided(this._logger, key);
return true;
}
Log.ResponseNotFound(this._logger, key);
return false;
}
public void Dispose()
{
Log.AgentBoundaryContextDisposed(this._logger);
this._cancellationTokenSource.Cancel();
this._cancellationTokenSource.Dispose();
GC.SuppressFinalize(this);
@@ -108,8 +98,6 @@ public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext
public async Task SendAsync(params ChatMessage[] userMessages)
{
Log.SendAsyncStarted(this._logger, userMessages.Length);
// This starts a new turn. Once completed, we will add the new messages to the conversation.
this._pendingMessages.Clear();
this._pendingMessages.AddRange(userMessages);
@@ -125,7 +113,6 @@ public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext
{
Tools = [.. this._tools]
});
Log.RunningWithTools(this._logger, this._tools.Count);
}
// Start a turn. Collect all updates as we stream them.
@@ -138,16 +125,14 @@ public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext
var chatUpdate = update.AsChatResponseUpdate();
this.CurrentUpdate = chatUpdate;
Log.ChatResponseUpdateReceived(this._logger);
// Notify subscribers of the new update, this always happens before a new message is created/updated.
this.TriggerChatResponseUpdate();
// This creates or adds a new message to the pending messages as needed.
var isNewMessage = MessageHelpers.ProcessUpdate(chatUpdate, this._pendingMessages, this._logger);
var isNewMessage = MessageHelpers.ProcessUpdate(chatUpdate, this._pendingMessages);
if (isNewMessage)
{
Log.NewMessageCreated(this._logger, this._pendingMessages.Count);
this.CurrentMessage = this._pendingMessages[this._pendingMessages.Count - 1];
// Finalize the previous message's content if we have 2 or more messages now.
@@ -178,13 +163,10 @@ public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext
// Notify subscribers
this.TriggerChatResponseUpdate();
this.TriggerMessageChanges();
Log.SendAsyncCompleted(this._logger, this._messages.Count);
}
private void TriggerChatResponseUpdate()
{
Log.ResponseUpdateSubscribersNotified(this._logger, this._responseUpdateSubscribers.Count);
// Iterate backwards to avoid issues if subscribers are removed during iteration
for (var i = this._responseUpdateSubscribers.Count - 1; i >= 0; i--)
{
@@ -194,7 +176,6 @@ public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext
private void TriggerMessageChanges()
{
Log.MessageChangeSubscribersNotified(this._logger, this._messageChangeSubscribers.Count);
// Iterate backwards to avoid issues if subscribers are removed during iteration
for (var i = this._messageChangeSubscribers.Count - 1; i >= 0; i--)
{
@@ -211,48 +192,6 @@ public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext
{
return new ResponseUpdateSubscription(this._responseUpdateSubscribers, onChatResponse);
}
private static partial class Log
{
[LoggerMessage(Level = LogLevel.Debug, Message = "AgentBoundaryContext created for thread")]
public static partial void AgentBoundaryContextCreated(ILogger logger);
[LoggerMessage(Level = LogLevel.Debug, Message = "AgentBoundaryContext disposed")]
public static partial void AgentBoundaryContextDisposed(ILogger logger);
[LoggerMessage(Level = LogLevel.Debug, Message = "SendAsync started with {MessageCount} user message(s)")]
public static partial void SendAsyncStarted(ILogger logger, int messageCount);
[LoggerMessage(Level = LogLevel.Debug, Message = "SendAsync completed, total messages: {TotalMessages}")]
public static partial void SendAsyncCompleted(ILogger logger, int totalMessages);
[LoggerMessage(Level = LogLevel.Debug, Message = "New message created during streaming, pending count: {PendingCount}")]
public static partial void NewMessageCreated(ILogger logger, int pendingCount);
[LoggerMessage(Level = LogLevel.Debug, Message = "Chat response update received")]
public static partial void ChatResponseUpdateReceived(ILogger logger);
[LoggerMessage(Level = LogLevel.Debug, Message = "Message change subscribers notified, subscriber count: {SubscriberCount}")]
public static partial void MessageChangeSubscribersNotified(ILogger logger, int subscriberCount);
[LoggerMessage(Level = LogLevel.Debug, Message = "Response update subscribers notified, subscriber count: {SubscriberCount}")]
public static partial void ResponseUpdateSubscribersNotified(ILogger logger, int subscriberCount);
[LoggerMessage(Level = LogLevel.Debug, Message = "Tool registered: {ToolName}")]
public static partial void ToolRegistered(ILogger logger, string toolName);
[LoggerMessage(Level = LogLevel.Debug, Message = "Running with {ToolCount} registered tool(s)")]
public static partial void RunningWithTools(ILogger logger, int toolCount);
[LoggerMessage(Level = LogLevel.Debug, Message = "WaitForResponse started for key: {Key}")]
public static partial void WaitForResponseStarted(ILogger logger, string key);
[LoggerMessage(Level = LogLevel.Debug, Message = "Response provided for key: {Key}")]
public static partial void ResponseProvided(ILogger logger, string key);
[LoggerMessage(Level = LogLevel.Warning, Message = "No pending response found for key: {Key}")]
public static partial void ResponseNotFound(ILogger logger, string key);
}
}
public interface IAgentBoundaryContext
@@ -271,8 +210,6 @@ public interface IAgentBoundaryContext
ChatResponseUpdate? CurrentUpdate { get; }
ILogger Logger { get; }
// Triggered any time there is a change on a message.
MessageSubscription SubscribeToMessageChanges(Action onNewMessage);
@@ -12,8 +12,6 @@ public partial class AgentInput : IComponent
[CascadingParameter] public AgentBoundaryContext<object?>? AgentContext { get; set; }
[Inject] private ILogger<AgentInput> Logger { get; set; } = default!;
[Parameter] public string Placeholder { get; set; } = "Type a message...";
public void Attach(RenderHandle renderHandle)
@@ -25,14 +23,12 @@ public partial class AgentInput : IComponent
{
parameters.SetParameterProperties(this);
this._context = this.AgentContext;
Log.AgentInputAttached(this.Logger);
this.Render();
return Task.CompletedTask;
}
private void Render()
{
Log.AgentInputRendering(this.Logger);
this._renderHandle.Render(builder =>
{
builder.OpenElement(0, "div");
@@ -61,27 +57,10 @@ public partial class AgentInput : IComponent
if (!string.IsNullOrWhiteSpace(this._inputText) && this._context != null)
{
var text = this._inputText;
Log.AgentInputSendingMessage(this.Logger, text.Length);
this._inputText = ""; // Clear input immediately
this.Render(); // Re-render to clear input
await this._context.SendAsync(new ChatMessage(ChatRole.User, text));
Log.AgentInputMessageSent(this.Logger);
}
}
private static partial class Log
{
[LoggerMessage(Level = LogLevel.Debug, Message = "AgentInput attached to render handle")]
public static partial void AgentInputAttached(ILogger logger);
[LoggerMessage(Level = LogLevel.Debug, Message = "AgentInput rendering")]
public static partial void AgentInputRendering(ILogger logger);
[LoggerMessage(Level = LogLevel.Debug, Message = "AgentInput sending message: {MessageLength} characters")]
public static partial void AgentInputSendingMessage(ILogger logger, int messageLength);
[LoggerMessage(Level = LogLevel.Debug, Message = "AgentInput message sent successfully")]
public static partial void AgentInputMessageSent(ILogger logger);
}
}
@@ -251,7 +251,7 @@ internal static class MessageHelpers
}
}
internal static bool ProcessUpdate(ChatResponseUpdate update, List<ChatMessage> messages, ILogger? logger = null)
internal static bool ProcessUpdate(ChatResponseUpdate update, List<ChatMessage> messages)
{
// If there is no message created yet, or if the last update we saw had a different
// identifying parts, create a new message.
@@ -263,10 +263,6 @@ internal static class MessageHelpers
NotEmptyOrEqual(update.AuthorName, lastMessage.AuthorName) ||
NotEmptyOrEqual(update.MessageId, lastMessage.MessageId) ||
NotNullOrEqual(update.Role, lastMessage.Role);
logger?.LogDebug(
"ProcessUpdate checking: update.MessageId={UpdateMessageId}, lastMessage.MessageId={LastMessageId}, isNewMessage={IsNew}",
update.MessageId, lastMessage.MessageId, isNewMessage);
}
// Get the message to target, either a new one or the last ones.
@@ -275,7 +271,6 @@ internal static class MessageHelpers
{
message = new(ChatRole.Assistant, []);
messages.Add(message);
logger?.LogDebug("ProcessUpdate: Created new message, total count={Count}", messages.Count);
}
else
{
@@ -306,15 +301,12 @@ internal static class MessageHelpers
{
// Note that this must come after the message checks earlier, as they depend
// on this value for change detection.
var oldId = message.MessageId;
message.MessageId = update.MessageId;
logger?.LogDebug("ProcessUpdate: Set MessageId from {OldId} to {NewId}", oldId, update.MessageId);
}
foreach (var content in update.Contents)
{
message.Contents.Add(content);
logger?.LogDebug("ProcessUpdate: Added content type {ContentType}", content.GetType().Name);
}
return isNewMessage;
@@ -34,8 +34,6 @@ internal sealed partial class MessageList : IComponent, IDisposable
// Subscribe to message updates and response updates for streaming
this._messageSubscription = this.MessageListContext.AgentBoundaryContext.SubscribeToMessageChanges(this.ProcessUpdate);
this._responseSubscription = this.MessageListContext.AgentBoundaryContext.SubscribeToResponseUpdates(this.ProcessUpdate);
Log.MessageListAttached(this.MessageListContext.AgentBoundaryContext.Logger);
Log.MessageListSubscribed(this.MessageListContext.AgentBoundaryContext.Logger);
// Initial render. This component will only render once since the only parameter is a cascading parameter and it's fixed.
this._renderHandle.Render(this.Render);
@@ -77,11 +75,6 @@ internal sealed partial class MessageList : IComponent, IDisposable
public void Render(RenderTreeBuilder builder)
{
Log.MessageListRendering(
this.MessageListContext.AgentBoundaryContext.Logger,
this.MessageListContext.AgentBoundaryContext.CompletedMessages.Count,
this.MessageListContext.AgentBoundaryContext.PendingMessages.Count);
// Track all render keys to detect duplicates
var allRenderKeys = new HashSet<string?>();
@@ -89,21 +82,6 @@ internal sealed partial class MessageList : IComponent, IDisposable
{
var renderKey = GetUniqueRenderKey(message);
Log.RenderingCompletedMessage(
this.MessageListContext.AgentBoundaryContext.Logger,
message.MessageId,
renderKey,
message.Role.Value,
message.Contents.Count);
if (renderKey != null && !allRenderKeys.Add(renderKey))
{
Log.DuplicateRenderKeyDetected(
this.MessageListContext.AgentBoundaryContext.Logger,
renderKey,
"completed");
}
// Calling GetTemplate will stop template collection on the first message if it
// was still ongoing.
builder.OpenComponent<ContentBlock>(0);
@@ -116,21 +94,6 @@ internal sealed partial class MessageList : IComponent, IDisposable
{
var renderKey = GetUniqueRenderKey(message);
Log.RenderingPendingMessage(
this.MessageListContext.AgentBoundaryContext.Logger,
message.MessageId,
renderKey,
message.Role.Value,
message.Contents.Count);
if (renderKey != null && !allRenderKeys.Add(renderKey))
{
Log.DuplicateRenderKeyDetected(
this.MessageListContext.AgentBoundaryContext.Logger,
renderKey,
"pending");
}
builder.OpenComponent<ContentBlock>(1);
builder.SetKey(renderKey);
builder.AddComponentParameter(1, "ChildContent", this.MessageListContext.GetTemplate(message));
@@ -162,32 +125,7 @@ internal sealed partial class MessageList : IComponent, IDisposable
public void Dispose()
{
Log.MessageListDisposed(this.MessageListContext.AgentBoundaryContext.Logger);
((IDisposable)this._messageSubscription).Dispose();
((IDisposable)this._responseSubscription).Dispose();
}
private static partial class Log
{
[LoggerMessage(Level = LogLevel.Debug, Message = "MessageList attached to render handle")]
public static partial void MessageListAttached(ILogger logger);
[LoggerMessage(Level = LogLevel.Debug, Message = "MessageList subscribed to message changes")]
public static partial void MessageListSubscribed(ILogger logger);
[LoggerMessage(Level = LogLevel.Debug, Message = "MessageList rendering, completed: {CompletedCount}, pending: {PendingCount}")]
public static partial void MessageListRendering(ILogger logger, int completedCount, int pendingCount);
[LoggerMessage(Level = LogLevel.Debug, Message = "Rendering completed message: MessageId={MessageId}, RenderKey={RenderKey}, Role={Role}, ContentCount={ContentCount}")]
public static partial void RenderingCompletedMessage(ILogger logger, string? messageId, string? renderKey, string role, int contentCount);
[LoggerMessage(Level = LogLevel.Debug, Message = "Rendering pending message: MessageId={MessageId}, RenderKey={RenderKey}, Role={Role}, ContentCount={ContentCount}")]
public static partial void RenderingPendingMessage(ILogger logger, string? messageId, string? renderKey, string role, int contentCount);
[LoggerMessage(Level = LogLevel.Warning, Message = "DUPLICATE RenderKey detected: {RenderKey} in {Location} messages")]
public static partial void DuplicateRenderKeyDetected(ILogger logger, string renderKey, string location);
[LoggerMessage(Level = LogLevel.Debug, Message = "MessageList disposed")]
public static partial void MessageListDisposed(ILogger logger);
}
}
@@ -20,7 +20,6 @@ public sealed partial class MessageListContext
public MessageListContext(IAgentBoundaryContext context)
{
this.AgentBoundaryContext = context;
Log.MessageListContextCreated(this.AgentBoundaryContext.Logger);
}
public IAgentBoundaryContext AgentBoundaryContext { get; }
@@ -35,7 +34,6 @@ public sealed partial class MessageListContext
this._templates.Clear();
this._contentTemplates.Clear();
this._templateCache.Clear();
Log.BeganCollectingTemplates(this.AgentBoundaryContext.Logger);
}
public void RegisterTemplate(MessageTemplateBase template)
@@ -43,7 +41,6 @@ public sealed partial class MessageListContext
if (this._collectingTemplates)
{
this._templates.Add(template);
Log.MessageTemplateRegistered(this.AgentBoundaryContext.Logger, this._templates.Count);
}
}
@@ -52,7 +49,6 @@ public sealed partial class MessageListContext
if (this._collectingTemplates)
{
this._contentTemplates.Add(template);
Log.ContentTemplateRegistered(this.AgentBoundaryContext.Logger, this._contentTemplates.Count);
}
}
@@ -67,7 +63,6 @@ public sealed partial class MessageListContext
{
context = new InvocationContext(call);
this._invocationMap[call.CallId] = context;
Log.InvocationRegistered(this.AgentBoundaryContext.Logger, call.CallId, call.Name);
}
return context;
@@ -82,7 +77,6 @@ public sealed partial class MessageListContext
if (this._invocationMap.TryGetValue(result.CallId, out var context))
{
context.SetResult(result);
Log.ResultAssociated(this.AgentBoundaryContext.Logger, result.CallId);
}
}
@@ -120,7 +114,6 @@ public sealed partial class MessageListContext
// if it doesn't define the rendering for a content type.
var renderer = chosen.ChildContent(messageContext);
this._templateCache[message] = renderer;
Log.TemplateResolved(this.AgentBoundaryContext.Logger, message.Role.Value);
return renderer;
}
}
@@ -141,28 +134,4 @@ public sealed partial class MessageListContext
throw new InvalidOperationException($"No content template found for content of type {content.GetType().Name}.");
}
private static partial class Log
{
[LoggerMessage(Level = LogLevel.Debug, Message = "MessageListContext created")]
public static partial void MessageListContextCreated(ILogger logger);
[LoggerMessage(Level = LogLevel.Debug, Message = "MessageListContext began collecting templates")]
public static partial void BeganCollectingTemplates(ILogger logger);
[LoggerMessage(Level = LogLevel.Debug, Message = "Message template registered, total: {TemplateCount}")]
public static partial void MessageTemplateRegistered(ILogger logger, int templateCount);
[LoggerMessage(Level = LogLevel.Debug, Message = "Content template registered, total: {TemplateCount}")]
public static partial void ContentTemplateRegistered(ILogger logger, int templateCount);
[LoggerMessage(Level = LogLevel.Debug, Message = "Template resolved for message with role: {Role}")]
public static partial void TemplateResolved(ILogger logger, string role);
[LoggerMessage(Level = LogLevel.Debug, Message = "Invocation registered for CallId: {CallId}, Function: {FunctionName}")]
public static partial void InvocationRegistered(ILogger logger, string callId, string functionName);
[LoggerMessage(Level = LogLevel.Debug, Message = "Result associated with CallId: {CallId}")]
public static partial void ResultAssociated(ILogger logger, string callId);
}
}
@@ -53,9 +53,7 @@ public partial class Messages : IComponent
if (this._messageListContext == null)
{
Log.MessagesAttached(this._context.Logger);
this._messageListContext = new MessageListContext(this.AgentContext);
Log.MessagesInitialized(this._context.Logger);
this.Render();
}
@@ -64,7 +62,6 @@ public partial class Messages : IComponent
private void Render()
{
Log.MessagesRendering(this._context?.Logger!);
this._renderHandle.Render(this.RenderCore);
}
@@ -86,16 +83,4 @@ public partial class Messages : IComponent
builder.OpenComponent<MessageList>(3);
builder.CloseComponent();
}
private static partial class Log
{
[LoggerMessage(Level = LogLevel.Debug, Message = "Messages component attached to render handle")]
public static partial void MessagesAttached(ILogger logger);
[LoggerMessage(Level = LogLevel.Debug, Message = "Messages component initialized with MessageListContext")]
public static partial void MessagesInitialized(ILogger logger);
[LoggerMessage(Level = LogLevel.Debug, Message = "Messages component rendering")]
public static partial void MessagesRendering(ILogger logger);
}
}
@@ -1,10 +1,8 @@
@* Copyright (c) Microsoft. All rights reserved. *@
@using Microsoft.AspNetCore.Components.AI
@using Microsoft.Extensions.AI
@using Microsoft.Extensions.Logging
@using System.Text.Json
@implements IDisposable
@inject ILogger<PlanCard> Logger
@if (IsWaitingForPlan)
{
@@ -139,21 +137,13 @@ else if (WasRejected)
protected override void OnInitialized()
{
Logger.LogInformation("PlanCard OnInitialized called");
Logger.LogInformation("BoundaryContext is {Status}", BoundaryContext is null ? "NULL" : "present");
Logger.LogInformation("Invocation is {Status}, HasResult: {HasResult}",
Invocation is null ? "NULL" : "present",
Invocation?.HasResult ?? false);
// The plan comes from the create_plan function result
if (Invocation?.HasResult == true)
{
Logger.LogInformation("Invocation already has result, parsing plan");
TryParsePlanFromResult();
}
else if (Invocation is not null)
{
Logger.LogInformation("Subscribing to Invocation.ResultArrived");
// Subscribe to wait for the result
Invocation.ResultArrived += OnCreatePlanResultArrived;
}
@@ -161,18 +151,12 @@ else if (WasRejected)
// Subscribe to response updates to detect confirm_plan and update_plan_step calls
if (BoundaryContext is not null)
{
Logger.LogInformation("Subscribing to response updates");
_responseSubscription = BoundaryContext.SubscribeToResponseUpdates(OnResponseUpdate);
}
else
{
Logger.LogWarning("BoundaryContext is null, cannot subscribe to response updates!");
}
}
private void OnCreatePlanResultArrived()
{
Logger.LogInformation("OnCreatePlanResultArrived called");
TryParsePlanFromResult();
InvokeAsync(StateHasChanged);
}
@@ -180,7 +164,6 @@ else if (WasRejected)
private void TryParsePlanFromResult()
{
var plan = Invocation?.GetResult<Plan>();
Logger.LogInformation("TryParsePlanFromResult: plan is {Status}", plan is null ? "NULL" : $"present with {plan.Steps.Count} steps");
if (plan is not null)
{
CurrentPlan = plan;
@@ -201,42 +184,30 @@ else if (WasRejected)
selectedSteps.Add(i);
}
}
Logger.LogInformation("InitializeSelectedSteps: selected {Count} steps", selectedSteps.Count);
}
}
private void OnResponseUpdate()
{
Logger.LogInformation("OnResponseUpdate called");
// Check the current update for tool calls
var update = BoundaryContext?.CurrentUpdate;
if (update is null)
{
Logger.LogInformation("CurrentUpdate is null");
return;
}
Logger.LogInformation("CurrentUpdate has {Count} contents", update.Contents?.Count ?? 0);
if (update.Contents is not null)
{
foreach (var content in update.Contents)
{
Logger.LogInformation("Content type: {Type}", content.GetType().Name);
if (content is FunctionCallContent call)
{
Logger.LogInformation("Found FunctionCallContent: {Name}, CallId: {CallId}", call.Name, call.CallId);
if (string.Equals(call.Name, "confirm_plan", StringComparison.OrdinalIgnoreCase))
{
Logger.LogInformation("Detected confirm_plan call!");
HandleConfirmPlanCall(call);
}
else if (string.Equals(call.Name, "update_plan_step", StringComparison.OrdinalIgnoreCase))
{
Logger.LogInformation("Detected update_plan_step call!");
HandleUpdatePlanStepCall(call);
}
}
@@ -248,15 +219,11 @@ else if (WasRejected)
private void HandleConfirmPlanCall(FunctionCallContent call)
{
Logger.LogInformation("HandleConfirmPlanCall: AwaitingConfirmation={Awaiting}, WasRejected={Rejected}, _confirmPlanCallId={CallId}",
AwaitingConfirmation, WasRejected, _confirmPlanCallId);
// When confirm_plan is called, show the confirmation UI
if (!AwaitingConfirmation && !WasRejected && _confirmPlanCallId is null)
{
_confirmPlanCallId = call.CallId;
AwaitingConfirmation = true;
Logger.LogInformation("Set AwaitingConfirmation to true, _confirmPlanCallId={CallId}", _confirmPlanCallId);
}
}
@@ -264,14 +231,12 @@ else if (WasRejected)
{
if (CurrentPlan is null)
{
Logger.LogWarning("HandleUpdatePlanStepCall: CurrentPlan is null");
return;
}
// Get the index and status from arguments
if (call.Arguments is null)
{
Logger.LogWarning("HandleUpdatePlanStepCall: Arguments is null");
return;
}
@@ -298,9 +263,6 @@ else if (WasRejected)
else if (descObj is JsonElement je && je.ValueKind == JsonValueKind.String) description = je.GetString();
}
Logger.LogInformation("HandleUpdatePlanStepCall: index={Index}, status={Status}, description={Description}",
index, status, description);
// Apply the update
if (index.HasValue && index.Value >= 0 && index.Value < CurrentPlan.Steps.Count)
{
@@ -312,7 +274,6 @@ else if (WasRejected)
{
CurrentPlan.Steps[index.Value].Description = description;
}
Logger.LogInformation("Updated step {Index}", index.Value);
}
}
@@ -331,7 +292,6 @@ else if (WasRejected)
private void ConfirmPlan()
{
Logger.LogInformation("ConfirmPlan called with {Count} selected steps", selectedSteps.Count);
AwaitingConfirmation = false;
var result = new PlanConfirmationResult
{
@@ -343,7 +303,6 @@ else if (WasRejected)
private void RejectPlan()
{
Logger.LogInformation("RejectPlan called");
WasRejected = true;
AwaitingConfirmation = false;
var result = new PlanConfirmationResult
@@ -356,7 +315,6 @@ else if (WasRejected)
public void Dispose()
{
Logger.LogInformation("PlanCard Dispose called");
if (Invocation is not null)
{
Invocation.ResultArrived -= OnCreatePlanResultArrived;
@@ -6,10 +6,8 @@
@using AGUIDojoClient.Components.Shared
@using System.Text.Json
@using System.ComponentModel
@using Microsoft.Extensions.Logging
@implements IDisposable
@inject IServiceProvider ServiceProvider
@inject ILogger<PredictiveStateUpdatesDemo> Logger
<PageTitle>AI Document Editor</PageTitle>
@@ -148,7 +146,6 @@
string.Equals(call.Name, "confirm_changes", StringComparison.OrdinalIgnoreCase))
{
// Show confirmation dialog when confirm_changes is called
Logger.LogInformation("Detected confirm_changes call, showing confirmation dialog");
awaitingConfirmation = true;
isStreaming = false;
}