mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-04 19:08:40 +02:00
refactor: unify tool call stream tracking
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -110,10 +110,9 @@ public sealed class CopilotTurnExecutionStateTests
|
||||
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.True(state.ToolCalls.TryGetToolName("tool-call-1", out string? toolName));
|
||||
Assert.Equal("view", toolName);
|
||||
Assert.True(state.ToolCallHasArgumentsById.TryGetValue("tool-call-1", out bool hasArguments));
|
||||
Assert.True(hasArguments);
|
||||
Assert.True(state.ToolCalls.HasTrackedArguments("tool-call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -129,8 +128,7 @@ public sealed class CopilotTurnExecutionStateTests
|
||||
|
||||
AgentActivityEventDto toolActivity = Assert.Single(state.DrainPendingEvents().OfType<AgentActivityEventDto>());
|
||||
Assert.Null(toolActivity.ToolArguments);
|
||||
Assert.True(state.ToolCallHasArgumentsById.TryGetValue("tool-call-1", out bool hasArguments));
|
||||
Assert.False(hasArguments);
|
||||
Assert.False(state.ToolCalls.HasTrackedArguments("tool-call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -253,10 +251,9 @@ public sealed class CopilotTurnExecutionStateTests
|
||||
"""{"type":"tool.execution_start","data":{"toolCallId":"tool-call-1","toolName":"handoff_to_specialist"},"id":"1ce9d1dc-68f1-4df5-9728-f97017233279","timestamp":"2026-03-27T00:00:00Z"}"""));
|
||||
|
||||
Assert.Empty(state.DrainPendingEvents().OfType<AgentActivityEventDto>());
|
||||
Assert.True(state.ToolNamesByCallId.TryGetValue("tool-call-1", out string? toolName));
|
||||
Assert.True(state.ToolCalls.TryGetToolName("tool-call-1", out string? toolName));
|
||||
Assert.Equal("handoff_to_specialist", toolName);
|
||||
Assert.True(state.ToolCallHasArgumentsById.TryGetValue("tool-call-1", out bool hasArguments));
|
||||
Assert.False(hasArguments);
|
||||
Assert.False(state.ToolCalls.HasTrackedArguments("tool-call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -375,14 +372,12 @@ public sealed class CopilotTurnExecutionStateTests
|
||||
Assert.Contains(toolActivities, activity => activity.ToolCallId == "tool-call-2" && activity.ToolName == "view");
|
||||
MessageReclassifiedEventDto reclassified = Assert.Single(pending.OfType<MessageReclassifiedEventDto>());
|
||||
Assert.Equal("msg-3", reclassified.MessageId);
|
||||
Assert.True(state.ToolNamesByCallId.TryGetValue("tool-call-1", out string? firstToolName));
|
||||
Assert.True(state.ToolCalls.TryGetToolName("tool-call-1", out string? firstToolName));
|
||||
Assert.Equal("rg", firstToolName);
|
||||
Assert.True(state.ToolNamesByCallId.TryGetValue("tool-call-2", out string? secondToolName));
|
||||
Assert.True(state.ToolCalls.TryGetToolName("tool-call-2", out string? secondToolName));
|
||||
Assert.Equal("view", secondToolName);
|
||||
Assert.True(state.ToolCallHasArgumentsById.TryGetValue("tool-call-1", out bool firstHasArguments));
|
||||
Assert.False(firstHasArguments);
|
||||
Assert.True(state.ToolCallHasArgumentsById.TryGetValue("tool-call-2", out bool secondHasArguments));
|
||||
Assert.False(secondHasArguments);
|
||||
Assert.False(state.ToolCalls.HasTrackedArguments("tool-call-1"));
|
||||
Assert.False(state.ToolCalls.HasTrackedArguments("tool-call-2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -776,8 +776,165 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
Assert.Equal("tool-call-1", activity.ToolCallId);
|
||||
Assert.NotNull(activity.ToolArguments);
|
||||
Assert.Equal(@"C:\workspace\README.md", activity.ToolArguments["path"]);
|
||||
Assert.True(state.ToolCallHasArgumentsById.TryGetValue("tool-call-1", out bool hasArguments));
|
||||
Assert.True(hasArguments);
|
||||
Assert.True(state.ToolCalls.HasTrackedArguments("tool-call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ObserveSessionEvent_ToolExecutionStart_DoesNotDuplicateTrackedRequestInfoActivity()
|
||||
{
|
||||
RunTurnCommandDto command = CreateApprovalCommand();
|
||||
CopilotTurnExecutionState state = new(command);
|
||||
WorkflowNodeDto agent = CreateAgent("agent-1", "Primary");
|
||||
state.ObserveSessionEvent(
|
||||
agent,
|
||||
SessionEvent.FromJson(
|
||||
"""
|
||||
{
|
||||
"type": "assistant.message_delta",
|
||||
"data": {
|
||||
"messageId": "msg-1",
|
||||
"deltaContent": "Inspecting"
|
||||
},
|
||||
"id": "b61652d1-120e-4a9f-8f0e-1dbf04fb18da",
|
||||
"timestamp": "2026-03-27T00:00:00Z"
|
||||
}
|
||||
"""));
|
||||
_ = state.DrainPendingEvents();
|
||||
|
||||
RequestInfoEvent requestInfo = CreateRequestInfoEvent(
|
||||
new FunctionCallContent("tool-call-1", "view", new Dictionary<string, object?>
|
||||
{
|
||||
["path"] = @"C:\workspace\README.md",
|
||||
}));
|
||||
List<AgentActivityEventDto> requestActivities = [];
|
||||
|
||||
MethodInfo handleWorkflowEvent = typeof(CopilotWorkflowRunner).GetMethod(
|
||||
"HandleWorkflowEventAsync",
|
||||
BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
Task<bool> handleTask = (Task<bool>)handleWorkflowEvent.Invoke(
|
||||
null,
|
||||
[
|
||||
command,
|
||||
requestInfo,
|
||||
Array.Empty<ChatMessage>(),
|
||||
state,
|
||||
(Func<TurnDeltaEventDto, Task>)(_ => Task.CompletedTask),
|
||||
(Func<SidecarEventDto, Task>)(sidecarEvent =>
|
||||
{
|
||||
requestActivities.Add(Assert.IsType<AgentActivityEventDto>(sidecarEvent));
|
||||
return Task.CompletedTask;
|
||||
}),
|
||||
])!;
|
||||
|
||||
bool shouldEndTurn = await handleTask;
|
||||
|
||||
Assert.False(shouldEndTurn);
|
||||
AgentActivityEventDto requestActivity = Assert.Single(requestActivities);
|
||||
Assert.Equal("tool-calling", requestActivity.ActivityType);
|
||||
Assert.True(state.ToolCalls.HasTrackedArguments("tool-call-1"));
|
||||
|
||||
state.ObserveSessionEvent(
|
||||
agent,
|
||||
SessionEvent.FromJson(
|
||||
"""
|
||||
{
|
||||
"type": "tool.execution_start",
|
||||
"data": {
|
||||
"toolCallId": "tool-call-1",
|
||||
"toolName": "view",
|
||||
"arguments": {
|
||||
"path": "C:\\workspace\\README.md"
|
||||
}
|
||||
},
|
||||
"id": "c61652d1-120e-4a9f-8f0e-1dbf04fb18da",
|
||||
"timestamp": "2026-03-27T00:00:01Z"
|
||||
}
|
||||
"""));
|
||||
|
||||
IReadOnlyList<SidecarEventDto> pending = state.DrainPendingEvents();
|
||||
Assert.DoesNotContain(
|
||||
pending.OfType<AgentActivityEventDto>(),
|
||||
activity => activity.ActivityType == "tool-calling");
|
||||
|
||||
MessageReclassifiedEventDto reclassified = Assert.Single(pending.OfType<MessageReclassifiedEventDto>());
|
||||
Assert.Equal("msg-1", reclassified.MessageId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ObserveSessionEvent_ToolExecutionStart_EmitsEnrichmentWhenRequestInfoWasMissingArguments()
|
||||
{
|
||||
RunTurnCommandDto command = CreateApprovalCommand();
|
||||
CopilotTurnExecutionState state = new(command);
|
||||
WorkflowNodeDto agent = CreateAgent("agent-1", "Primary");
|
||||
state.ObserveSessionEvent(
|
||||
agent,
|
||||
SessionEvent.FromJson(
|
||||
"""
|
||||
{
|
||||
"type": "assistant.message_delta",
|
||||
"data": {
|
||||
"messageId": "msg-2",
|
||||
"deltaContent": "Inspecting"
|
||||
},
|
||||
"id": "d61652d1-120e-4a9f-8f0e-1dbf04fb18da",
|
||||
"timestamp": "2026-03-27T00:00:00Z"
|
||||
}
|
||||
"""));
|
||||
_ = state.DrainPendingEvents();
|
||||
|
||||
RequestInfoEvent requestInfo = CreateRequestInfoEvent(
|
||||
new FunctionCallContent("tool-call-1", "view", new Dictionary<string, object?>()));
|
||||
List<AgentActivityEventDto> requestActivities = [];
|
||||
|
||||
MethodInfo handleWorkflowEvent = typeof(CopilotWorkflowRunner).GetMethod(
|
||||
"HandleWorkflowEventAsync",
|
||||
BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
Task<bool> handleTask = (Task<bool>)handleWorkflowEvent.Invoke(
|
||||
null,
|
||||
[
|
||||
command,
|
||||
requestInfo,
|
||||
Array.Empty<ChatMessage>(),
|
||||
state,
|
||||
(Func<TurnDeltaEventDto, Task>)(_ => Task.CompletedTask),
|
||||
(Func<SidecarEventDto, Task>)(sidecarEvent =>
|
||||
{
|
||||
requestActivities.Add(Assert.IsType<AgentActivityEventDto>(sidecarEvent));
|
||||
return Task.CompletedTask;
|
||||
}),
|
||||
])!;
|
||||
|
||||
bool shouldEndTurn = await handleTask;
|
||||
|
||||
Assert.False(shouldEndTurn);
|
||||
AgentActivityEventDto requestActivity = Assert.Single(requestActivities);
|
||||
Assert.Null(requestActivity.ToolArguments);
|
||||
Assert.False(state.ToolCalls.HasTrackedArguments("tool-call-1"));
|
||||
|
||||
state.ObserveSessionEvent(
|
||||
agent,
|
||||
SessionEvent.FromJson(
|
||||
"""
|
||||
{
|
||||
"type": "tool.execution_start",
|
||||
"data": {
|
||||
"toolCallId": "tool-call-1",
|
||||
"toolName": "view",
|
||||
"arguments": {
|
||||
"path": "C:\\workspace\\README.md"
|
||||
}
|
||||
},
|
||||
"id": "e61652d1-120e-4a9f-8f0e-1dbf04fb18da",
|
||||
"timestamp": "2026-03-27T00:00:01Z"
|
||||
}
|
||||
"""));
|
||||
|
||||
AgentActivityEventDto enrichment = Assert.Single(
|
||||
state.DrainPendingEvents().OfType<AgentActivityEventDto>(),
|
||||
activity => activity.ActivityType == "tool-calling");
|
||||
Assert.NotNull(enrichment.ToolArguments);
|
||||
Assert.Equal(@"C:\workspace\README.md", enrichment.ToolArguments["path"]);
|
||||
Assert.True(state.ToolCalls.HasTrackedArguments("tool-call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -1372,14 +1529,12 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
[Fact]
|
||||
public void TryGetApprovalToolName_UsesToolCallLookupForPermissionCategoriesWithoutDirectToolNames()
|
||||
{
|
||||
Dictionary<string, string> toolNamesByCallId = new(StringComparer.Ordinal)
|
||||
{
|
||||
["tool-call-url"] = "web_fetch",
|
||||
["tool-call-shell"] = "shell",
|
||||
["tool-call-read"] = "view",
|
||||
["tool-call-write"] = "write_file",
|
||||
["tool-call-memory"] = "store_memory",
|
||||
};
|
||||
ToolCallRegistry toolCalls = CreateToolCallRegistry(
|
||||
("tool-call-url", "web_fetch"),
|
||||
("tool-call-shell", "shell"),
|
||||
("tool-call-read", "view"),
|
||||
("tool-call-write", "write_file"),
|
||||
("tool-call-memory", "store_memory"));
|
||||
|
||||
Assert.True(
|
||||
CopilotApprovalCoordinator.TryGetApprovalToolName(
|
||||
@@ -1390,7 +1545,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
Intention = "Fetch the requested page",
|
||||
Url = "https://example.com/docs",
|
||||
},
|
||||
toolNamesByCallId,
|
||||
toolCalls,
|
||||
out string? urlToolName));
|
||||
Assert.Equal("web_fetch", urlToolName);
|
||||
|
||||
@@ -1408,7 +1563,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
HasWriteFileRedirection = false,
|
||||
CanOfferSessionApproval = false,
|
||||
},
|
||||
toolNamesByCallId,
|
||||
toolCalls,
|
||||
out string? shellToolName));
|
||||
Assert.Equal("shell", shellToolName);
|
||||
|
||||
@@ -1421,7 +1576,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
Intention = "Inspect a file",
|
||||
Path = "README.md",
|
||||
},
|
||||
toolNamesByCallId,
|
||||
toolCalls,
|
||||
out string? readToolName));
|
||||
Assert.Equal("view", readToolName);
|
||||
|
||||
@@ -1435,7 +1590,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
FileName = "README.md",
|
||||
Diff = "@@ -1 +1 @@",
|
||||
},
|
||||
toolNamesByCallId,
|
||||
toolCalls,
|
||||
out string? writeToolName));
|
||||
Assert.Equal("write_file", writeToolName);
|
||||
|
||||
@@ -1449,7 +1604,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
Fact = "Use Bun for script execution.",
|
||||
Citations = "package.json",
|
||||
},
|
||||
toolNamesByCallId,
|
||||
toolCalls,
|
||||
out string? memoryToolName));
|
||||
Assert.Equal("store_memory", memoryToolName);
|
||||
}
|
||||
@@ -1960,7 +2115,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
{
|
||||
SessionId = "copilot-session-1",
|
||||
},
|
||||
new Dictionary<string, string>(StringComparer.Ordinal),
|
||||
CreateToolCallRegistry(),
|
||||
approval =>
|
||||
{
|
||||
observedApproval = approval;
|
||||
@@ -2007,10 +2162,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
{
|
||||
SessionId = "copilot-session-1",
|
||||
},
|
||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["tool-call-write-1"] = "apply_patch",
|
||||
},
|
||||
CreateToolCallRegistry(("tool-call-write-1", "apply_patch")),
|
||||
activity =>
|
||||
{
|
||||
observedActivity = activity;
|
||||
@@ -2067,7 +2219,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
{
|
||||
SessionId = "copilot-session-1",
|
||||
},
|
||||
new Dictionary<string, string>(StringComparer.Ordinal),
|
||||
CreateToolCallRegistry(),
|
||||
approval =>
|
||||
{
|
||||
sawApproval = true;
|
||||
@@ -2104,7 +2256,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
{
|
||||
SessionId = "copilot-session-1",
|
||||
},
|
||||
new Dictionary<string, string>(StringComparer.Ordinal),
|
||||
CreateToolCallRegistry(),
|
||||
approval =>
|
||||
{
|
||||
sawApproval = true;
|
||||
@@ -2137,10 +2289,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
{
|
||||
SessionId = "copilot-session-1",
|
||||
},
|
||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["tool-call-read-1"] = "view",
|
||||
},
|
||||
CreateToolCallRegistry(("tool-call-read-1", "view")),
|
||||
approval =>
|
||||
{
|
||||
firstApproval = approval;
|
||||
@@ -2178,10 +2327,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
{
|
||||
SessionId = "copilot-session-1",
|
||||
},
|
||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["tool-call-read-2"] = "grep",
|
||||
},
|
||||
CreateToolCallRegistry(("tool-call-read-2", "grep")),
|
||||
approval =>
|
||||
{
|
||||
sawSecondApproval = true;
|
||||
@@ -2214,10 +2360,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
{
|
||||
SessionId = "copilot-session-1",
|
||||
},
|
||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["tool-call-read-1"] = "view",
|
||||
},
|
||||
CreateToolCallRegistry(("tool-call-read-1", "view")),
|
||||
approval =>
|
||||
{
|
||||
firstApproval = approval;
|
||||
@@ -2254,10 +2397,7 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
{
|
||||
SessionId = "copilot-session-1",
|
||||
},
|
||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["tool-call-read-2"] = "grep",
|
||||
},
|
||||
CreateToolCallRegistry(("tool-call-read-2", "grep")),
|
||||
approval =>
|
||||
{
|
||||
secondApproval = approval;
|
||||
@@ -2548,6 +2688,17 @@ public sealed class CopilotWorkflowRunnerTests
|
||||
};
|
||||
}
|
||||
|
||||
private static ToolCallRegistry CreateToolCallRegistry(params (string ToolCallId, string ToolName)[] toolCalls)
|
||||
{
|
||||
ToolCallRegistry registry = new();
|
||||
foreach ((string toolCallId, string toolName) in toolCalls)
|
||||
{
|
||||
registry.RecordToolStart(toolCallId, toolName, toolArguments: null);
|
||||
}
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
private static RunTurnCommandDto CreateRequestPortCommand()
|
||||
{
|
||||
return new RunTurnCommandDto
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using Aryx.AgentHost.Contracts;
|
||||
@@ -27,8 +26,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
CreateSingleAgentCommand(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-1", "Primary"),
|
||||
tracking.ToolNamesByCallId,
|
||||
tracking.ToolCallHasArgumentsById);
|
||||
tracking);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.Equal("tool-calling", activity.ActivityType);
|
||||
@@ -38,8 +36,9 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
Assert.NotNull(activity.ToolArguments);
|
||||
Assert.Equal(@"C:\workspace\file.txt", activity.ToolArguments["path"]);
|
||||
Assert.Equal([10, 25], Assert.IsAssignableFrom<IReadOnlyList<object?>>(activity.ToolArguments["viewRange"]));
|
||||
Assert.Equal("view", tracking.ToolNamesByCallId["call-1"]);
|
||||
Assert.True(tracking.ToolCallHasArgumentsById["call-1"]);
|
||||
Assert.True(tracking.TryGetToolName("call-1", out string? toolName));
|
||||
Assert.Equal("view", toolName);
|
||||
Assert.True(tracking.HasTrackedArguments("call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -61,8 +60,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
CreateSingleAgentCommand(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-1", "Primary"),
|
||||
tracking.ToolNamesByCallId,
|
||||
tracking.ToolCallHasArgumentsById);
|
||||
tracking);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.Equal("tool-calling", activity.ActivityType);
|
||||
@@ -70,8 +68,9 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
Assert.NotNull(activity.ToolArguments);
|
||||
Assert.Equal(@"C:\workspace", activity.ToolArguments["path"]);
|
||||
Assert.Equal(true, activity.ToolArguments["includeIgnored"]);
|
||||
Assert.Equal("git.status", tracking.ToolNamesByCallId["call-1"]);
|
||||
Assert.True(tracking.ToolCallHasArgumentsById["call-1"]);
|
||||
Assert.True(tracking.TryGetToolName("call-1", out string? toolName));
|
||||
Assert.Equal("git.status", toolName);
|
||||
Assert.True(tracking.HasTrackedArguments("call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -85,8 +84,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
CreateSingleAgentCommand(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-1", "Primary"),
|
||||
tracking.ToolNamesByCallId,
|
||||
tracking.ToolCallHasArgumentsById);
|
||||
tracking);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.Equal("tool-calling", activity.ActivityType);
|
||||
@@ -95,8 +93,9 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
Assert.Equal(
|
||||
["print('hello')"],
|
||||
Assert.IsAssignableFrom<IReadOnlyList<object?>>(activity.ToolArguments["inputs"]));
|
||||
Assert.Equal("code interpreter", tracking.ToolNamesByCallId["call-1"]);
|
||||
Assert.True(tracking.ToolCallHasArgumentsById["call-1"]);
|
||||
Assert.True(tracking.TryGetToolName("call-1", out string? toolName));
|
||||
Assert.Equal("code interpreter", toolName);
|
||||
Assert.True(tracking.HasTrackedArguments("call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -109,15 +108,14 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
CreateSingleAgentCommand(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-1", "Primary"),
|
||||
tracking.ToolNamesByCallId,
|
||||
tracking.ToolCallHasArgumentsById);
|
||||
tracking);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.Equal("tool-calling", activity.ActivityType);
|
||||
Assert.Equal("image generation", activity.ToolName);
|
||||
Assert.Null(activity.ToolArguments);
|
||||
Assert.Empty(tracking.ToolNamesByCallId);
|
||||
Assert.Empty(tracking.ToolCallHasArgumentsById);
|
||||
Assert.False(tracking.TryGetToolName("call-1", out _));
|
||||
Assert.False(tracking.HasTrackedArguments("call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -135,12 +133,11 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
CreateSingleAgentCommand(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-1", "Primary"),
|
||||
tracking.ToolNamesByCallId,
|
||||
tracking.ToolCallHasArgumentsById);
|
||||
tracking);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.Null(activity.ToolArguments);
|
||||
Assert.False(tracking.ToolCallHasArgumentsById["call-1"]);
|
||||
Assert.False(tracking.HasTrackedArguments("call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -160,21 +157,25 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
CreateSingleAgentCommand(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-1", "Primary"),
|
||||
tracking.ToolNamesByCallId,
|
||||
tracking.ToolCallHasArgumentsById);
|
||||
tracking);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.NotNull(activity.ToolArguments);
|
||||
Assert.Equal("[truncated]", activity.ToolArguments["command"]);
|
||||
Assert.True(tracking.ToolCallHasArgumentsById["call-1"]);
|
||||
Assert.True(tracking.HasTrackedArguments("call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryCreateActivityFromRequest_SkipsDuplicateTrackedToolCallIdsThatAlreadyHaveArguments()
|
||||
{
|
||||
var tracking = CreateToolTracking();
|
||||
tracking.ToolNamesByCallId["call-1"] = "view";
|
||||
tracking.ToolCallHasArgumentsById["call-1"] = true;
|
||||
tracking.RecordToolStart(
|
||||
"call-1",
|
||||
"view",
|
||||
new Dictionary<string, object?>
|
||||
{
|
||||
["path"] = @"C:\workspace\seed.txt",
|
||||
});
|
||||
RequestInfoEvent requestInfo = CreateRequestInfoEvent(
|
||||
new FunctionCallContent("call-1", "view", new Dictionary<string, object?>
|
||||
{
|
||||
@@ -185,20 +186,19 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
CreateSingleAgentCommand(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-1", "Primary"),
|
||||
tracking.ToolNamesByCallId,
|
||||
tracking.ToolCallHasArgumentsById);
|
||||
tracking);
|
||||
|
||||
Assert.Null(activity);
|
||||
Assert.Equal("view", tracking.ToolNamesByCallId["call-1"]);
|
||||
Assert.True(tracking.ToolCallHasArgumentsById["call-1"]);
|
||||
Assert.True(tracking.TryGetToolName("call-1", out string? toolName));
|
||||
Assert.Equal("view", toolName);
|
||||
Assert.True(tracking.HasTrackedArguments("call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryCreateActivityFromRequest_EmitsEnrichmentWhenTrackedToolCallWasMissingArguments()
|
||||
{
|
||||
var tracking = CreateToolTracking();
|
||||
tracking.ToolNamesByCallId["call-1"] = "view";
|
||||
tracking.ToolCallHasArgumentsById["call-1"] = false;
|
||||
tracking.RecordToolStart("call-1", "view", toolArguments: null);
|
||||
RequestInfoEvent requestInfo = CreateRequestInfoEvent(
|
||||
new FunctionCallContent("call-1", "view", new Dictionary<string, object?>
|
||||
{
|
||||
@@ -209,16 +209,16 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
CreateSingleAgentCommand(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-1", "Primary"),
|
||||
tracking.ToolNamesByCallId,
|
||||
tracking.ToolCallHasArgumentsById);
|
||||
tracking);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.Equal("tool-calling", activity.ActivityType);
|
||||
Assert.Equal("call-1", activity.ToolCallId);
|
||||
Assert.NotNull(activity.ToolArguments);
|
||||
Assert.Equal(@"C:\workspace\file.txt", activity.ToolArguments["path"]);
|
||||
Assert.Equal("view", tracking.ToolNamesByCallId["call-1"]);
|
||||
Assert.True(tracking.ToolCallHasArgumentsById["call-1"]);
|
||||
Assert.True(tracking.TryGetToolName("call-1", out string? toolName));
|
||||
Assert.Equal("view", toolName);
|
||||
Assert.True(tracking.HasTrackedArguments("call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -232,8 +232,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
CreateHandoffCommand(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-handoff-triage", "Triage"),
|
||||
tracking.ToolNamesByCallId,
|
||||
tracking.ToolCallHasArgumentsById);
|
||||
tracking);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.Equal("handoff", activity.ActivityType);
|
||||
@@ -242,8 +241,8 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
Assert.Equal("agent-handoff-triage", activity.SourceAgentId);
|
||||
Assert.Equal("Triage", activity.SourceAgentName);
|
||||
Assert.Null(activity.ToolName);
|
||||
Assert.Empty(tracking.ToolNamesByCallId);
|
||||
Assert.Empty(tracking.ToolCallHasArgumentsById);
|
||||
Assert.False(tracking.TryGetToolName("call-1", out _));
|
||||
Assert.False(tracking.HasTrackedArguments("call-1"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -263,8 +262,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
"agent-reviewer",
|
||||
"Reviewer",
|
||||
new SubworkflowContext("subworkflow-review", "Review Lane")),
|
||||
tracking.ToolNamesByCallId,
|
||||
tracking.ToolCallHasArgumentsById);
|
||||
tracking);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.Equal("tool-calling", activity.ActivityType);
|
||||
@@ -283,8 +281,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
CreateHandoffCommandWithReferencedSubworkflow(),
|
||||
requestInfo,
|
||||
new AgentIdentity("agent-handoff-triage", "Triage"),
|
||||
tracking.ToolNamesByCallId,
|
||||
tracking.ToolCallHasArgumentsById);
|
||||
tracking);
|
||||
|
||||
Assert.NotNull(activity);
|
||||
Assert.Equal("handoff", activity.ActivityType);
|
||||
@@ -417,10 +414,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
||||
workflowLibrary: [nestedWorkflow]);
|
||||
}
|
||||
|
||||
private static (
|
||||
ConcurrentDictionary<string, string> ToolNamesByCallId,
|
||||
ConcurrentDictionary<string, bool> ToolCallHasArgumentsById) CreateToolTracking()
|
||||
=> (new(StringComparer.Ordinal), new(StringComparer.Ordinal));
|
||||
private static ToolCallRegistry CreateToolTracking() => new();
|
||||
|
||||
private static RunTurnCommandDto CreateCommand(
|
||||
string orchestrationMode,
|
||||
|
||||
Reference in New Issue
Block a user