refactor: unify tool call stream tracking

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-04-13 15:33:27 +02:00
co-authored by Copilot
parent 7b9c4d140c
commit 4e34d2abfc
9 changed files with 438 additions and 236 deletions
@@ -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,