mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-23 13:08:36 +02:00
fix: extract tool arguments from ToolExecutionStartEvent in sidecar
The CopilotTurnExecutionState was ignoring ToolExecutionStartData.Arguments when creating tool-calling activity events, despite the Copilot SDK providing them. This caused the activity panel to show 'Viewed a file' / 'Used rg' with no contextual details (file paths, search patterns, etc.). The fix adds NormalizeRawToolArguments to WorkflowRequestInfoInterpreter for converting the raw object? payload (typically JsonElement) into the normalized dictionary, then passes ToolExecutionStartData.Arguments through CreateToolCallingActivity. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Binary file not shown.
BIN
Binary file not shown.
@@ -98,7 +98,8 @@ internal sealed class CopilotTurnExecutionState
|
||||
string toolName = toolExecutionStart.Data.ToolName.Trim();
|
||||
ToolNamesByCallId[toolCallId] = toolName;
|
||||
ActiveAgent = agent;
|
||||
AgentActivityEventDto? toolActivity = CreateToolCallingActivity(agent, toolName, toolCallId);
|
||||
AgentActivityEventDto? toolActivity = CreateToolCallingActivity(
|
||||
agent, toolName, toolCallId, toolExecutionStart.Data.Arguments);
|
||||
if (toolActivity is not null)
|
||||
{
|
||||
_pendingEvents.Enqueue(toolActivity);
|
||||
@@ -295,7 +296,8 @@ internal sealed class CopilotTurnExecutionState
|
||||
private AgentActivityEventDto? CreateToolCallingActivity(
|
||||
AgentIdentity agent,
|
||||
string toolName,
|
||||
string toolCallId)
|
||||
string toolCallId,
|
||||
object? rawArguments = null)
|
||||
{
|
||||
if (toolName.StartsWith("handoff_to_", StringComparison.Ordinal))
|
||||
{
|
||||
@@ -312,6 +314,7 @@ internal sealed class CopilotTurnExecutionState
|
||||
AgentName = agent.AgentName,
|
||||
ToolName = toolName,
|
||||
ToolCallId = toolCallId,
|
||||
ToolArguments = WorkflowRequestInfoInterpreter.NormalizeRawToolArguments(rawArguments),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -205,6 +205,26 @@ internal static class WorkflowRequestInfoInterpreter
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<string, object?>? NormalizeRawToolArguments(object? rawArguments)
|
||||
{
|
||||
return rawArguments switch
|
||||
{
|
||||
null => null,
|
||||
JsonElement { ValueKind: JsonValueKind.Object } element => NormalizeToolArgumentObject(element),
|
||||
IEnumerable<KeyValuePair<string, object?>> dictionary => NormalizeToolArguments(dictionary),
|
||||
_ => NormalizeRawToolArgumentsViaJson(rawArguments),
|
||||
};
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, object?>? NormalizeRawToolArgumentsViaJson(object value)
|
||||
{
|
||||
string json = JsonSerializer.Serialize(value, value.GetType(), JsonOptions);
|
||||
using JsonDocument document = JsonDocument.Parse(json);
|
||||
return document.RootElement.ValueKind == JsonValueKind.Object
|
||||
? NormalizeToolArgumentObject(document.RootElement)
|
||||
: null;
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, object?>? NormalizeToolArguments(
|
||||
IEnumerable<KeyValuePair<string, object?>>? arguments)
|
||||
{
|
||||
|
||||
@@ -68,16 +68,33 @@ public sealed class CopilotTurnExecutionStateTests
|
||||
state.ObserveSessionEvent(
|
||||
command.Workflow.GetAgentNodes()[0],
|
||||
SessionEvent.FromJson(
|
||||
"""{"type":"tool.execution_start","data":{"toolCallId":"tool-call-1","toolName":"view"},"id":"33333333-3333-3333-3333-333333333333","timestamp":"2026-03-27T00:00:00Z"}"""));
|
||||
"""{"type":"tool.execution_start","data":{"toolCallId":"tool-call-1","toolName":"view","arguments":{"path":"/src/main.ts","view_range":[10,20]}},"id":"33333333-3333-3333-3333-333333333333","timestamp":"2026-03-27T00:00:00Z"}"""));
|
||||
|
||||
AgentActivityEventDto toolActivity = Assert.Single(state.DrainPendingEvents().OfType<AgentActivityEventDto>());
|
||||
Assert.Equal("tool-calling", toolActivity.ActivityType);
|
||||
Assert.Equal("view", toolActivity.ToolName);
|
||||
Assert.Equal("tool-call-1", toolActivity.ToolCallId);
|
||||
Assert.NotNull(toolActivity.ToolArguments);
|
||||
Assert.Equal("/src/main.ts", toolActivity.ToolArguments["path"]);
|
||||
Assert.True(state.ToolNamesByCallId.TryGetValue("tool-call-1", out string? toolName));
|
||||
Assert.Equal("view", toolName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObserveSessionEvent_ToolExecutionStart_WithoutArguments_SetsToolArgumentsToNull()
|
||||
{
|
||||
RunTurnCommandDto command = CreateCommand();
|
||||
CopilotTurnExecutionState state = new(command);
|
||||
|
||||
state.ObserveSessionEvent(
|
||||
command.Workflow.GetAgentNodes()[0],
|
||||
SessionEvent.FromJson(
|
||||
"""{"type":"tool.execution_start","data":{"toolCallId":"tool-call-1","toolName":"view"},"id":"33333333-3333-3333-3333-333333333333","timestamp":"2026-03-27T00:00:00Z"}"""));
|
||||
|
||||
AgentActivityEventDto toolActivity = Assert.Single(state.DrainPendingEvents().OfType<AgentActivityEventDto>());
|
||||
Assert.Null(toolActivity.ToolArguments);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObserveSessionEvent_ToolExecutionStart_DoesNotQueueToolActivityForHandoffTools()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using Aryx.AgentHost.Contracts;
|
||||
using Aryx.AgentHost.Services;
|
||||
using Microsoft.Agents.AI;
|
||||
@@ -255,6 +256,35 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
Assert.False(requiresBoundary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NormalizeRawToolArguments_JsonElement_ExtractsArguments()
|
||||
{
|
||||
using JsonDocument doc = JsonDocument.Parse("""{"path":"/src/main.ts","view_range":[10,20]}""");
|
||||
JsonElement element = doc.RootElement.Clone();
|
||||
|
||||
IReadOnlyDictionary<string, object?>? result = WorkflowRequestInfoInterpreter.NormalizeRawToolArguments(element);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("/src/main.ts", result["path"]);
|
||||
IReadOnlyList<object?> viewRange = Assert.IsAssignableFrom<IReadOnlyList<object?>>(result["view_range"]);
|
||||
Assert.Equal(2, viewRange.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NormalizeRawToolArguments_Null_ReturnsNull()
|
||||
{
|
||||
Assert.Null(WorkflowRequestInfoInterpreter.NormalizeRawToolArguments(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NormalizeRawToolArguments_EmptyObject_ReturnsNull()
|
||||
{
|
||||
using JsonDocument doc = JsonDocument.Parse("{}");
|
||||
JsonElement element = doc.RootElement.Clone();
|
||||
|
||||
Assert.Null(WorkflowRequestInfoInterpreter.NormalizeRawToolArguments(element));
|
||||
}
|
||||
|
||||
private static RunTurnCommandDto CreateSingleAgentCommand()
|
||||
=> CreateCommand("single", [CreateAgent("agent-1", "Primary")]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user