mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-06 19:58:43 +02:00
refactor: unify tool call stream tracking
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -326,8 +326,7 @@ public class AgentWorkflowTurnRunner : ITurnWorkflowRunner
|
|||||||
command,
|
command,
|
||||||
requestInfo,
|
requestInfo,
|
||||||
state.ActiveAgent,
|
state.ActiveAgent,
|
||||||
state.ToolNamesByCallId,
|
state.ToolCalls);
|
||||||
state.ToolCallHasArgumentsById);
|
|
||||||
|
|
||||||
if (activity is null)
|
if (activity is null)
|
||||||
{
|
{
|
||||||
|
|||||||
+12
-12
@@ -70,7 +70,7 @@ internal sealed class CopilotApprovalCoordinator
|
|||||||
WorkflowNodeDto agent,
|
WorkflowNodeDto agent,
|
||||||
PermissionRequest request,
|
PermissionRequest request,
|
||||||
PermissionInvocation invocation,
|
PermissionInvocation invocation,
|
||||||
IReadOnlyDictionary<string, string> toolNamesByCallId,
|
ToolCallRegistry toolCalls,
|
||||||
Func<ApprovalRequestedEventDto, Task> onApproval,
|
Func<ApprovalRequestedEventDto, Task> onApproval,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -79,7 +79,7 @@ internal sealed class CopilotApprovalCoordinator
|
|||||||
agent,
|
agent,
|
||||||
request,
|
request,
|
||||||
invocation,
|
invocation,
|
||||||
toolNamesByCallId,
|
toolCalls,
|
||||||
onActivity: null,
|
onActivity: null,
|
||||||
onApproval,
|
onApproval,
|
||||||
cancellationToken)
|
cancellationToken)
|
||||||
@@ -91,12 +91,12 @@ internal sealed class CopilotApprovalCoordinator
|
|||||||
WorkflowNodeDto agent,
|
WorkflowNodeDto agent,
|
||||||
PermissionRequest request,
|
PermissionRequest request,
|
||||||
PermissionInvocation invocation,
|
PermissionInvocation invocation,
|
||||||
IReadOnlyDictionary<string, string> toolNamesByCallId,
|
ToolCallRegistry toolCalls,
|
||||||
Func<AgentActivityEventDto, Task>? onActivity,
|
Func<AgentActivityEventDto, Task>? onActivity,
|
||||||
Func<ApprovalRequestedEventDto, Task> onApproval,
|
Func<ApprovalRequestedEventDto, Task> onApproval,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
string? toolName = ResolveApprovalToolName(request, toolNamesByCallId);
|
string? toolName = ResolveApprovalToolName(request, toolCalls);
|
||||||
string? autoApprovedToolName = ResolveAutoApprovedToolName(request);
|
string? autoApprovedToolName = ResolveAutoApprovedToolName(request);
|
||||||
string? mcpServerApprovalKey = ResolveMcpServerApprovalKey(request, command.Tooling?.McpServers);
|
string? mcpServerApprovalKey = ResolveMcpServerApprovalKey(request, command.Tooling?.McpServers);
|
||||||
string? approvalCacheKey = ResolveApprovalCacheKey(toolName, autoApprovedToolName);
|
string? approvalCacheKey = ResolveApprovalCacheKey(toolName, autoApprovedToolName);
|
||||||
@@ -339,15 +339,15 @@ internal sealed class CopilotApprovalCoordinator
|
|||||||
|
|
||||||
internal static bool TryGetApprovalToolName(
|
internal static bool TryGetApprovalToolName(
|
||||||
PermissionRequest request,
|
PermissionRequest request,
|
||||||
IReadOnlyDictionary<string, string>? toolNamesByCallId,
|
ToolCallRegistry? toolCalls,
|
||||||
out string? toolName)
|
out string? toolName)
|
||||||
{
|
{
|
||||||
toolName = ResolveApprovalToolName(request, toolNamesByCallId);
|
toolName = ResolveApprovalToolName(request, toolCalls);
|
||||||
return toolName is not null;
|
return toolName is not null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool TryGetApprovalToolName(PermissionRequest request, out string? toolName)
|
internal static bool TryGetApprovalToolName(PermissionRequest request, out string? toolName)
|
||||||
=> TryGetApprovalToolName(request, toolNamesByCallId: null, out toolName);
|
=> TryGetApprovalToolName(request, toolCalls: null, out toolName);
|
||||||
|
|
||||||
internal void ClearRequestApprovals(string requestId)
|
internal void ClearRequestApprovals(string requestId)
|
||||||
{
|
{
|
||||||
@@ -404,10 +404,10 @@ internal sealed class CopilotApprovalCoordinator
|
|||||||
|
|
||||||
private static string? ResolveApprovalToolName(
|
private static string? ResolveApprovalToolName(
|
||||||
PermissionRequest request,
|
PermissionRequest request,
|
||||||
IReadOnlyDictionary<string, string>? toolNamesByCallId)
|
ToolCallRegistry? toolCalls)
|
||||||
{
|
{
|
||||||
return GetDirectToolName(request)
|
return GetDirectToolName(request)
|
||||||
?? ResolveToolNameFromLookup(request, toolNamesByCallId)
|
?? ResolveToolNameFromLookup(request, toolCalls)
|
||||||
?? GetFallbackToolName(request);
|
?? GetFallbackToolName(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,16 +480,16 @@ internal sealed class CopilotApprovalCoordinator
|
|||||||
|
|
||||||
private static string? ResolveToolNameFromLookup(
|
private static string? ResolveToolNameFromLookup(
|
||||||
PermissionRequest request,
|
PermissionRequest request,
|
||||||
IReadOnlyDictionary<string, string>? toolNamesByCallId)
|
ToolCallRegistry? toolCalls)
|
||||||
{
|
{
|
||||||
if (toolNamesByCallId is null)
|
if (toolCalls is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
string? toolCallId = GetToolCallId(request);
|
string? toolCallId = GetToolCallId(request);
|
||||||
if (toolCallId is null
|
if (toolCallId is null
|
||||||
|| !toolNamesByCallId.TryGetValue(toolCallId, out string? resolvedToolName))
|
|| !toolCalls.TryGetToolName(toolCallId, out string? resolvedToolName))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ internal sealed class CopilotTurnRunnerSupport : IProviderTurnSupport
|
|||||||
agent,
|
agent,
|
||||||
request,
|
request,
|
||||||
invocation,
|
invocation,
|
||||||
state.ToolNamesByCallId,
|
state.ToolCalls,
|
||||||
activity => AgentWorkflowTurnRunner.EmitActivityAsync(command, state, activity, onEvent),
|
activity => AgentWorkflowTurnRunner.EmitActivityAsync(command, state, activity, onEvent),
|
||||||
onApproval,
|
onApproval,
|
||||||
runCancellation.Token),
|
runCancellation.Token),
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Aryx.AgentHost.Contracts;
|
||||||
|
|
||||||
|
namespace Aryx.AgentHost.Services;
|
||||||
|
|
||||||
|
internal sealed class ToolCallRegistry
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<string, ProviderToolExecutionSnapshot> _toolExecutionsByCallId = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
public bool TryGetExecution(string? toolCallId, [NotNullWhen(true)] out ProviderToolExecutionSnapshot? snapshot)
|
||||||
|
{
|
||||||
|
snapshot = null;
|
||||||
|
return !string.IsNullOrWhiteSpace(toolCallId)
|
||||||
|
&& _toolExecutionsByCallId.TryGetValue(toolCallId, out snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetToolName(string? toolCallId, [NotNullWhen(true)] out string? toolName)
|
||||||
|
{
|
||||||
|
toolName = null;
|
||||||
|
return TryGetExecution(toolCallId, out ProviderToolExecutionSnapshot? snapshot)
|
||||||
|
&& !string.IsNullOrWhiteSpace(snapshot.ToolName)
|
||||||
|
&& (toolName = snapshot.ToolName) is not null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasTrackedArguments(string? toolCallId)
|
||||||
|
{
|
||||||
|
return TryGetExecution(toolCallId, out ProviderToolExecutionSnapshot? snapshot)
|
||||||
|
&& snapshot.ToolArguments is { Count: > 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RecordToolStart(
|
||||||
|
string toolCallId,
|
||||||
|
string toolName,
|
||||||
|
IReadOnlyDictionary<string, object?>? toolArguments)
|
||||||
|
{
|
||||||
|
_toolExecutionsByCallId.AddOrUpdate(
|
||||||
|
toolCallId,
|
||||||
|
static (id, state) => new ProviderToolExecutionSnapshot
|
||||||
|
{
|
||||||
|
ToolCallId = id,
|
||||||
|
ToolName = state.ToolName,
|
||||||
|
ToolArguments = state.ToolArguments,
|
||||||
|
Status = ProviderToolExecutionStatus.Running,
|
||||||
|
},
|
||||||
|
static (_, existing, state) => existing with
|
||||||
|
{
|
||||||
|
ToolName = state.ToolName,
|
||||||
|
ToolArguments = state.ToolArguments,
|
||||||
|
Status = ProviderToolExecutionStatus.Running,
|
||||||
|
},
|
||||||
|
(ToolName: toolName, ToolArguments: toolArguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryRecordToolRequest(
|
||||||
|
string? toolCallId,
|
||||||
|
string toolName,
|
||||||
|
IReadOnlyDictionary<string, object?>? toolArguments)
|
||||||
|
{
|
||||||
|
bool hasToolArguments = toolArguments is { Count: > 0 };
|
||||||
|
string? normalizedToolCallId = NormalizeOptionalString(toolCallId);
|
||||||
|
if (normalizedToolCallId is null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_toolExecutionsByCallId.TryGetValue(normalizedToolCallId, out ProviderToolExecutionSnapshot? existing))
|
||||||
|
{
|
||||||
|
bool trackedHasArguments = existing.ToolArguments is { Count: > 0 };
|
||||||
|
if (trackedHasArguments || !hasToolArguments)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_toolExecutionsByCallId.AddOrUpdate(
|
||||||
|
normalizedToolCallId,
|
||||||
|
id => new ProviderToolExecutionSnapshot
|
||||||
|
{
|
||||||
|
ToolCallId = id,
|
||||||
|
ToolName = toolName,
|
||||||
|
ToolArguments = toolArguments,
|
||||||
|
Status = ProviderToolExecutionStatus.Running,
|
||||||
|
},
|
||||||
|
(_, existing) => existing with
|
||||||
|
{
|
||||||
|
ToolName = toolName,
|
||||||
|
ToolArguments = toolArguments,
|
||||||
|
Status = existing.Status is ProviderToolExecutionStatus.Completed or ProviderToolExecutionStatus.Failed
|
||||||
|
? existing.Status
|
||||||
|
: ProviderToolExecutionStatus.Running,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RecordProgress(string toolCallId, string? progressMessage)
|
||||||
|
{
|
||||||
|
string? normalizedProgress = NormalizeOptionalString(progressMessage);
|
||||||
|
_toolExecutionsByCallId.AddOrUpdate(
|
||||||
|
toolCallId,
|
||||||
|
id => new ProviderToolExecutionSnapshot
|
||||||
|
{
|
||||||
|
ToolCallId = id,
|
||||||
|
Status = ProviderToolExecutionStatus.Running,
|
||||||
|
LatestProgressMessage = normalizedProgress,
|
||||||
|
},
|
||||||
|
(_, existing) => existing with
|
||||||
|
{
|
||||||
|
Status = existing.Status is ProviderToolExecutionStatus.Completed or ProviderToolExecutionStatus.Failed
|
||||||
|
? existing.Status
|
||||||
|
: ProviderToolExecutionStatus.Running,
|
||||||
|
LatestProgressMessage = normalizedProgress ?? existing.LatestProgressMessage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RecordPartialResult(string toolCallId, string? partialOutput)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(partialOutput))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_toolExecutionsByCallId.AddOrUpdate(
|
||||||
|
toolCallId,
|
||||||
|
id => new ProviderToolExecutionSnapshot
|
||||||
|
{
|
||||||
|
ToolCallId = id,
|
||||||
|
Status = ProviderToolExecutionStatus.Running,
|
||||||
|
PartialOutput = partialOutput,
|
||||||
|
},
|
||||||
|
(_, existing) => existing with
|
||||||
|
{
|
||||||
|
Status = existing.Status is ProviderToolExecutionStatus.Completed or ProviderToolExecutionStatus.Failed
|
||||||
|
? existing.Status
|
||||||
|
: ProviderToolExecutionStatus.Running,
|
||||||
|
PartialOutput = string.Concat(existing.PartialOutput, partialOutput),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RecordCompletion(ProviderToolExecutionCompleteEvent toolExecution)
|
||||||
|
{
|
||||||
|
_toolExecutionsByCallId.AddOrUpdate(
|
||||||
|
toolExecution.ToolCallId,
|
||||||
|
id => new ProviderToolExecutionSnapshot
|
||||||
|
{
|
||||||
|
ToolCallId = id,
|
||||||
|
Status = toolExecution.Success ? ProviderToolExecutionStatus.Completed : ProviderToolExecutionStatus.Failed,
|
||||||
|
ResultContent = toolExecution.ResultContent,
|
||||||
|
DetailedResultContent = toolExecution.DetailedResultContent,
|
||||||
|
Error = toolExecution.Error,
|
||||||
|
},
|
||||||
|
(_, existing) => existing with
|
||||||
|
{
|
||||||
|
Status = toolExecution.Success ? ProviderToolExecutionStatus.Completed : ProviderToolExecutionStatus.Failed,
|
||||||
|
ResultContent = toolExecution.ResultContent ?? existing.ResultContent,
|
||||||
|
DetailedResultContent = toolExecution.DetailedResultContent ?? existing.DetailedResultContent,
|
||||||
|
Error = toolExecution.Error ?? existing.Error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? NormalizeOptionalString(string? value)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,6 @@ internal class TurnExecutionState
|
|||||||
private readonly ConcurrentQueue<SidecarEventDto> _pendingEvents = new();
|
private readonly ConcurrentQueue<SidecarEventDto> _pendingEvents = new();
|
||||||
private readonly ConcurrentQueue<McpOauthRequiredEventDto> _pendingMcpOauthRequests = new();
|
private readonly ConcurrentQueue<McpOauthRequiredEventDto> _pendingMcpOauthRequests = new();
|
||||||
private readonly ConcurrentDictionary<string, AgentIdentity> _observedAgentsByMessageId = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, AgentIdentity> _observedAgentsByMessageId = new(StringComparer.Ordinal);
|
||||||
private readonly ConcurrentDictionary<string, ProviderToolExecutionSnapshot> _toolExecutionsByCallId = new(StringComparer.Ordinal);
|
|
||||||
private readonly ConcurrentDictionary<string, ProviderReasoningSnapshot> _reasoningById = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, ProviderReasoningSnapshot> _reasoningById = new(StringComparer.Ordinal);
|
||||||
private readonly ConcurrentDictionary<string, string> _latestIntentByAgentId = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, string> _latestIntentByAgentId = new(StringComparer.Ordinal);
|
||||||
private readonly StreamingTranscriptBuffer _transcriptBuffer = new();
|
private readonly StreamingTranscriptBuffer _transcriptBuffer = new();
|
||||||
@@ -29,9 +28,7 @@ internal class TurnExecutionState
|
|||||||
_agentSubworkflowIndex = AgentIdentityResolver.BuildAgentSubworkflowIndex(command.Workflow, _workflowLibrary);
|
_agentSubworkflowIndex = AgentIdentityResolver.BuildAgentSubworkflowIndex(command.Workflow, _workflowLibrary);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConcurrentDictionary<string, string> ToolNamesByCallId { get; } = new(StringComparer.Ordinal);
|
public ToolCallRegistry ToolCalls { get; } = new();
|
||||||
|
|
||||||
public ConcurrentDictionary<string, bool> ToolCallHasArgumentsById { get; } = new(StringComparer.Ordinal);
|
|
||||||
|
|
||||||
public AgentIdentity? ActiveAgent { get; private set; }
|
public AgentIdentity? ActiveAgent { get; private set; }
|
||||||
|
|
||||||
@@ -166,13 +163,16 @@ internal class TurnExecutionState
|
|||||||
case ProviderToolExecutionStartEvent toolExecutionStart:
|
case ProviderToolExecutionStartEvent toolExecutionStart:
|
||||||
string toolCallId = toolExecutionStart.ToolCallId;
|
string toolCallId = toolExecutionStart.ToolCallId;
|
||||||
string toolName = toolExecutionStart.ToolName;
|
string toolName = toolExecutionStart.ToolName;
|
||||||
TrackToolCall(toolCallId, toolName, toolExecutionStart.ToolArguments);
|
bool shouldQueueToolActivity = TrackToolCall(toolCallId, toolName, toolExecutionStart.ToolArguments);
|
||||||
ActiveAgent = agent;
|
ActiveAgent = agent;
|
||||||
AgentActivityEventDto? toolActivity = CreateToolCallingActivity(
|
if (shouldQueueToolActivity)
|
||||||
agent, toolName, toolCallId, toolExecutionStart.ToolArguments);
|
|
||||||
if (toolActivity is not null)
|
|
||||||
{
|
{
|
||||||
_pendingEvents.Enqueue(toolActivity);
|
AgentActivityEventDto? toolActivity = CreateToolCallingActivity(
|
||||||
|
agent, toolName, toolCallId, toolExecutionStart.ToolArguments);
|
||||||
|
if (toolActivity is not null)
|
||||||
|
{
|
||||||
|
_pendingEvents.Enqueue(toolActivity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QueueMessageReclassifiedIfNeeded(_lastObservedMessageId);
|
QueueMessageReclassifiedIfNeeded(_lastObservedMessageId);
|
||||||
@@ -338,9 +338,7 @@ internal class TurnExecutionState
|
|||||||
|
|
||||||
public bool TryGetToolExecution(string? toolCallId, [NotNullWhen(true)] out ProviderToolExecutionSnapshot? snapshot)
|
public bool TryGetToolExecution(string? toolCallId, [NotNullWhen(true)] out ProviderToolExecutionSnapshot? snapshot)
|
||||||
{
|
{
|
||||||
snapshot = null;
|
return ToolCalls.TryGetExecution(toolCallId, out snapshot);
|
||||||
return !string.IsNullOrWhiteSpace(toolCallId)
|
|
||||||
&& _toolExecutionsByCallId.TryGetValue(toolCallId, out snapshot);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetReasoning(string? reasoningId, [NotNullWhen(true)] out ProviderReasoningSnapshot? snapshot)
|
public bool TryGetReasoning(string? reasoningId, [NotNullWhen(true)] out ProviderReasoningSnapshot? snapshot)
|
||||||
@@ -393,102 +391,27 @@ internal class TurnExecutionState
|
|||||||
_lastObservedMessageId = messageId;
|
_lastObservedMessageId = messageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TrackToolCall(
|
private bool TrackToolCall(
|
||||||
string toolCallId,
|
string toolCallId,
|
||||||
string toolName,
|
string toolName,
|
||||||
IReadOnlyDictionary<string, object?>? toolArguments)
|
IReadOnlyDictionary<string, object?>? toolArguments)
|
||||||
{
|
{
|
||||||
ToolNamesByCallId[toolCallId] = toolName;
|
return ToolCalls.TryRecordToolRequest(toolCallId, toolName, toolArguments);
|
||||||
ToolCallHasArgumentsById[toolCallId] = toolArguments is { Count: > 0 };
|
|
||||||
TrackToolExecutionStart(toolCallId, toolName, toolArguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TrackToolExecutionStart(
|
|
||||||
string toolCallId,
|
|
||||||
string toolName,
|
|
||||||
IReadOnlyDictionary<string, object?>? toolArguments)
|
|
||||||
{
|
|
||||||
_toolExecutionsByCallId.AddOrUpdate(
|
|
||||||
toolCallId,
|
|
||||||
static (id, state) => new ProviderToolExecutionSnapshot
|
|
||||||
{
|
|
||||||
ToolCallId = id,
|
|
||||||
ToolName = state.ToolName,
|
|
||||||
ToolArguments = state.ToolArguments,
|
|
||||||
Status = ProviderToolExecutionStatus.Running,
|
|
||||||
},
|
|
||||||
static (_, existing, state) => existing with
|
|
||||||
{
|
|
||||||
ToolName = state.ToolName,
|
|
||||||
ToolArguments = state.ToolArguments,
|
|
||||||
Status = ProviderToolExecutionStatus.Running,
|
|
||||||
},
|
|
||||||
(ToolName: toolName, ToolArguments: toolArguments));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TrackToolExecutionProgress(string toolCallId, string? progressMessage)
|
private void TrackToolExecutionProgress(string toolCallId, string? progressMessage)
|
||||||
{
|
{
|
||||||
string? normalizedProgress = NormalizeOptionalString(progressMessage);
|
ToolCalls.RecordProgress(toolCallId, progressMessage);
|
||||||
_toolExecutionsByCallId.AddOrUpdate(
|
|
||||||
toolCallId,
|
|
||||||
id => new ProviderToolExecutionSnapshot
|
|
||||||
{
|
|
||||||
ToolCallId = id,
|
|
||||||
Status = ProviderToolExecutionStatus.Running,
|
|
||||||
LatestProgressMessage = normalizedProgress,
|
|
||||||
},
|
|
||||||
(_, existing) => existing with
|
|
||||||
{
|
|
||||||
Status = existing.Status is ProviderToolExecutionStatus.Completed or ProviderToolExecutionStatus.Failed
|
|
||||||
? existing.Status
|
|
||||||
: ProviderToolExecutionStatus.Running,
|
|
||||||
LatestProgressMessage = normalizedProgress ?? existing.LatestProgressMessage,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TrackToolExecutionPartialResult(string toolCallId, string? partialOutput)
|
private void TrackToolExecutionPartialResult(string toolCallId, string? partialOutput)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(partialOutput))
|
ToolCalls.RecordPartialResult(toolCallId, partialOutput);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_toolExecutionsByCallId.AddOrUpdate(
|
|
||||||
toolCallId,
|
|
||||||
id => new ProviderToolExecutionSnapshot
|
|
||||||
{
|
|
||||||
ToolCallId = id,
|
|
||||||
Status = ProviderToolExecutionStatus.Running,
|
|
||||||
PartialOutput = partialOutput,
|
|
||||||
},
|
|
||||||
(_, existing) => existing with
|
|
||||||
{
|
|
||||||
Status = existing.Status is ProviderToolExecutionStatus.Completed or ProviderToolExecutionStatus.Failed
|
|
||||||
? existing.Status
|
|
||||||
: ProviderToolExecutionStatus.Running,
|
|
||||||
PartialOutput = string.Concat(existing.PartialOutput, partialOutput),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TrackToolExecutionComplete(ProviderToolExecutionCompleteEvent toolExecution)
|
private void TrackToolExecutionComplete(ProviderToolExecutionCompleteEvent toolExecution)
|
||||||
{
|
{
|
||||||
_toolExecutionsByCallId.AddOrUpdate(
|
ToolCalls.RecordCompletion(toolExecution);
|
||||||
toolExecution.ToolCallId,
|
|
||||||
id => new ProviderToolExecutionSnapshot
|
|
||||||
{
|
|
||||||
ToolCallId = id,
|
|
||||||
Status = toolExecution.Success ? ProviderToolExecutionStatus.Completed : ProviderToolExecutionStatus.Failed,
|
|
||||||
ResultContent = toolExecution.ResultContent,
|
|
||||||
DetailedResultContent = toolExecution.DetailedResultContent,
|
|
||||||
Error = toolExecution.Error,
|
|
||||||
},
|
|
||||||
(_, existing) => existing with
|
|
||||||
{
|
|
||||||
Status = toolExecution.Success ? ProviderToolExecutionStatus.Completed : ProviderToolExecutionStatus.Failed,
|
|
||||||
ResultContent = toolExecution.ResultContent ?? existing.ResultContent,
|
|
||||||
DetailedResultContent = toolExecution.DetailedResultContent ?? existing.DetailedResultContent,
|
|
||||||
Error = toolExecution.Error ?? existing.Error,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TrackLatestIntent(string agentId, string? intent)
|
private void TrackLatestIntent(string agentId, string? intent)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Aryx.AgentHost.Contracts;
|
using Aryx.AgentHost.Contracts;
|
||||||
using Microsoft.Agents.AI.Workflows;
|
using Microsoft.Agents.AI.Workflows;
|
||||||
@@ -20,8 +19,7 @@ internal static class WorkflowRequestInfoInterpreter
|
|||||||
RunTurnCommandDto command,
|
RunTurnCommandDto command,
|
||||||
RequestInfoEvent requestInfo,
|
RequestInfoEvent requestInfo,
|
||||||
AgentIdentity? activeAgent,
|
AgentIdentity? activeAgent,
|
||||||
ConcurrentDictionary<string, string> toolNamesByCallId,
|
ToolCallRegistry toolCalls)
|
||||||
ConcurrentDictionary<string, bool> toolCallHasArgumentsById)
|
|
||||||
{
|
{
|
||||||
RequestInterpretation interpretation = InterpretRequest(command, requestInfo);
|
RequestInterpretation interpretation = InterpretRequest(command, requestInfo);
|
||||||
return interpretation switch
|
return interpretation switch
|
||||||
@@ -29,7 +27,7 @@ internal static class WorkflowRequestInfoInterpreter
|
|||||||
HandoffRequestInterpretation handoff =>
|
HandoffRequestInterpretation handoff =>
|
||||||
CreateHandoffActivity(command, handoff.TargetAgent, activeAgent),
|
CreateHandoffActivity(command, handoff.TargetAgent, activeAgent),
|
||||||
ToolRequestInterpretation tool when activeAgent.HasValue =>
|
ToolRequestInterpretation tool when activeAgent.HasValue =>
|
||||||
CreateToolCallingActivity(command, activeAgent.Value, tool, toolNamesByCallId, toolCallHasArgumentsById),
|
CreateToolCallingActivity(command, activeAgent.Value, tool, toolCalls),
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -66,22 +64,13 @@ internal static class WorkflowRequestInfoInterpreter
|
|||||||
RunTurnCommandDto command,
|
RunTurnCommandDto command,
|
||||||
AgentIdentity activeAgent,
|
AgentIdentity activeAgent,
|
||||||
ToolRequestInterpretation tool,
|
ToolRequestInterpretation tool,
|
||||||
ConcurrentDictionary<string, string> toolNamesByCallId,
|
ToolCallRegistry toolCalls)
|
||||||
ConcurrentDictionary<string, bool> toolCallHasArgumentsById)
|
|
||||||
{
|
{
|
||||||
bool hasToolArguments = tool.ToolArguments is { Count: > 0 };
|
if (!toolCalls.TryRecordToolRequest(tool.ToolCallId, tool.ToolName, tool.ToolArguments))
|
||||||
if (tool.ToolCallId is not null && toolNamesByCallId.ContainsKey(tool.ToolCallId))
|
|
||||||
{
|
{
|
||||||
bool trackedHasArguments = toolCallHasArgumentsById.TryGetValue(tool.ToolCallId, out bool hasTrackedArguments)
|
return null;
|
||||||
&& hasTrackedArguments;
|
|
||||||
if (trackedHasArguments || !hasToolArguments)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackToolCallId(toolNamesByCallId, toolCallHasArgumentsById, tool.ToolCallId, tool.ToolName, hasToolArguments);
|
|
||||||
|
|
||||||
return new AgentActivityEventDto
|
return new AgentActivityEventDto
|
||||||
{
|
{
|
||||||
Type = "agent-activity",
|
Type = "agent-activity",
|
||||||
@@ -98,20 +87,6 @@ internal static class WorkflowRequestInfoInterpreter
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void TrackToolCallId(
|
|
||||||
ConcurrentDictionary<string, string> toolNamesByCallId,
|
|
||||||
ConcurrentDictionary<string, bool> toolCallHasArgumentsById,
|
|
||||||
string? toolCallId,
|
|
||||||
string toolName,
|
|
||||||
bool hasToolArguments)
|
|
||||||
{
|
|
||||||
if (toolCallId is not null)
|
|
||||||
{
|
|
||||||
toolNamesByCallId[toolCallId] = toolName;
|
|
||||||
toolCallHasArgumentsById[toolCallId] = hasToolArguments;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RequestInterpretation InterpretRequest(
|
private static RequestInterpretation InterpretRequest(
|
||||||
RunTurnCommandDto command,
|
RunTurnCommandDto command,
|
||||||
RequestInfoEvent requestInfo)
|
RequestInfoEvent requestInfo)
|
||||||
|
|||||||
@@ -110,10 +110,9 @@ public sealed class CopilotTurnExecutionStateTests
|
|||||||
Assert.Equal("tool-call-1", toolActivity.ToolCallId);
|
Assert.Equal("tool-call-1", toolActivity.ToolCallId);
|
||||||
Assert.NotNull(toolActivity.ToolArguments);
|
Assert.NotNull(toolActivity.ToolArguments);
|
||||||
Assert.Equal("/src/main.ts", toolActivity.ToolArguments["path"]);
|
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.Equal("view", toolName);
|
||||||
Assert.True(state.ToolCallHasArgumentsById.TryGetValue("tool-call-1", out bool hasArguments));
|
Assert.True(state.ToolCalls.HasTrackedArguments("tool-call-1"));
|
||||||
Assert.True(hasArguments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -129,8 +128,7 @@ public sealed class CopilotTurnExecutionStateTests
|
|||||||
|
|
||||||
AgentActivityEventDto toolActivity = Assert.Single(state.DrainPendingEvents().OfType<AgentActivityEventDto>());
|
AgentActivityEventDto toolActivity = Assert.Single(state.DrainPendingEvents().OfType<AgentActivityEventDto>());
|
||||||
Assert.Null(toolActivity.ToolArguments);
|
Assert.Null(toolActivity.ToolArguments);
|
||||||
Assert.True(state.ToolCallHasArgumentsById.TryGetValue("tool-call-1", out bool hasArguments));
|
Assert.False(state.ToolCalls.HasTrackedArguments("tool-call-1"));
|
||||||
Assert.False(hasArguments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[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"}"""));
|
"""{"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.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.Equal("handoff_to_specialist", toolName);
|
||||||
Assert.True(state.ToolCallHasArgumentsById.TryGetValue("tool-call-1", out bool hasArguments));
|
Assert.False(state.ToolCalls.HasTrackedArguments("tool-call-1"));
|
||||||
Assert.False(hasArguments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -375,14 +372,12 @@ public sealed class CopilotTurnExecutionStateTests
|
|||||||
Assert.Contains(toolActivities, activity => activity.ToolCallId == "tool-call-2" && activity.ToolName == "view");
|
Assert.Contains(toolActivities, activity => activity.ToolCallId == "tool-call-2" && activity.ToolName == "view");
|
||||||
MessageReclassifiedEventDto reclassified = Assert.Single(pending.OfType<MessageReclassifiedEventDto>());
|
MessageReclassifiedEventDto reclassified = Assert.Single(pending.OfType<MessageReclassifiedEventDto>());
|
||||||
Assert.Equal("msg-3", reclassified.MessageId);
|
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.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.Equal("view", secondToolName);
|
||||||
Assert.True(state.ToolCallHasArgumentsById.TryGetValue("tool-call-1", out bool firstHasArguments));
|
Assert.False(state.ToolCalls.HasTrackedArguments("tool-call-1"));
|
||||||
Assert.False(firstHasArguments);
|
Assert.False(state.ToolCalls.HasTrackedArguments("tool-call-2"));
|
||||||
Assert.True(state.ToolCallHasArgumentsById.TryGetValue("tool-call-2", out bool secondHasArguments));
|
|
||||||
Assert.False(secondHasArguments);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -776,8 +776,165 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
Assert.Equal("tool-call-1", activity.ToolCallId);
|
Assert.Equal("tool-call-1", activity.ToolCallId);
|
||||||
Assert.NotNull(activity.ToolArguments);
|
Assert.NotNull(activity.ToolArguments);
|
||||||
Assert.Equal(@"C:\workspace\README.md", activity.ToolArguments["path"]);
|
Assert.Equal(@"C:\workspace\README.md", activity.ToolArguments["path"]);
|
||||||
Assert.True(state.ToolCallHasArgumentsById.TryGetValue("tool-call-1", out bool hasArguments));
|
Assert.True(state.ToolCalls.HasTrackedArguments("tool-call-1"));
|
||||||
Assert.True(hasArguments);
|
}
|
||||||
|
|
||||||
|
[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]
|
[Fact]
|
||||||
@@ -1372,14 +1529,12 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void TryGetApprovalToolName_UsesToolCallLookupForPermissionCategoriesWithoutDirectToolNames()
|
public void TryGetApprovalToolName_UsesToolCallLookupForPermissionCategoriesWithoutDirectToolNames()
|
||||||
{
|
{
|
||||||
Dictionary<string, string> toolNamesByCallId = new(StringComparer.Ordinal)
|
ToolCallRegistry toolCalls = CreateToolCallRegistry(
|
||||||
{
|
("tool-call-url", "web_fetch"),
|
||||||
["tool-call-url"] = "web_fetch",
|
("tool-call-shell", "shell"),
|
||||||
["tool-call-shell"] = "shell",
|
("tool-call-read", "view"),
|
||||||
["tool-call-read"] = "view",
|
("tool-call-write", "write_file"),
|
||||||
["tool-call-write"] = "write_file",
|
("tool-call-memory", "store_memory"));
|
||||||
["tool-call-memory"] = "store_memory",
|
|
||||||
};
|
|
||||||
|
|
||||||
Assert.True(
|
Assert.True(
|
||||||
CopilotApprovalCoordinator.TryGetApprovalToolName(
|
CopilotApprovalCoordinator.TryGetApprovalToolName(
|
||||||
@@ -1390,7 +1545,7 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
Intention = "Fetch the requested page",
|
Intention = "Fetch the requested page",
|
||||||
Url = "https://example.com/docs",
|
Url = "https://example.com/docs",
|
||||||
},
|
},
|
||||||
toolNamesByCallId,
|
toolCalls,
|
||||||
out string? urlToolName));
|
out string? urlToolName));
|
||||||
Assert.Equal("web_fetch", urlToolName);
|
Assert.Equal("web_fetch", urlToolName);
|
||||||
|
|
||||||
@@ -1408,7 +1563,7 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
HasWriteFileRedirection = false,
|
HasWriteFileRedirection = false,
|
||||||
CanOfferSessionApproval = false,
|
CanOfferSessionApproval = false,
|
||||||
},
|
},
|
||||||
toolNamesByCallId,
|
toolCalls,
|
||||||
out string? shellToolName));
|
out string? shellToolName));
|
||||||
Assert.Equal("shell", shellToolName);
|
Assert.Equal("shell", shellToolName);
|
||||||
|
|
||||||
@@ -1421,7 +1576,7 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
Intention = "Inspect a file",
|
Intention = "Inspect a file",
|
||||||
Path = "README.md",
|
Path = "README.md",
|
||||||
},
|
},
|
||||||
toolNamesByCallId,
|
toolCalls,
|
||||||
out string? readToolName));
|
out string? readToolName));
|
||||||
Assert.Equal("view", readToolName);
|
Assert.Equal("view", readToolName);
|
||||||
|
|
||||||
@@ -1435,7 +1590,7 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
FileName = "README.md",
|
FileName = "README.md",
|
||||||
Diff = "@@ -1 +1 @@",
|
Diff = "@@ -1 +1 @@",
|
||||||
},
|
},
|
||||||
toolNamesByCallId,
|
toolCalls,
|
||||||
out string? writeToolName));
|
out string? writeToolName));
|
||||||
Assert.Equal("write_file", writeToolName);
|
Assert.Equal("write_file", writeToolName);
|
||||||
|
|
||||||
@@ -1449,7 +1604,7 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
Fact = "Use Bun for script execution.",
|
Fact = "Use Bun for script execution.",
|
||||||
Citations = "package.json",
|
Citations = "package.json",
|
||||||
},
|
},
|
||||||
toolNamesByCallId,
|
toolCalls,
|
||||||
out string? memoryToolName));
|
out string? memoryToolName));
|
||||||
Assert.Equal("store_memory", memoryToolName);
|
Assert.Equal("store_memory", memoryToolName);
|
||||||
}
|
}
|
||||||
@@ -1960,7 +2115,7 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
{
|
{
|
||||||
SessionId = "copilot-session-1",
|
SessionId = "copilot-session-1",
|
||||||
},
|
},
|
||||||
new Dictionary<string, string>(StringComparer.Ordinal),
|
CreateToolCallRegistry(),
|
||||||
approval =>
|
approval =>
|
||||||
{
|
{
|
||||||
observedApproval = approval;
|
observedApproval = approval;
|
||||||
@@ -2007,10 +2162,7 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
{
|
{
|
||||||
SessionId = "copilot-session-1",
|
SessionId = "copilot-session-1",
|
||||||
},
|
},
|
||||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
CreateToolCallRegistry(("tool-call-write-1", "apply_patch")),
|
||||||
{
|
|
||||||
["tool-call-write-1"] = "apply_patch",
|
|
||||||
},
|
|
||||||
activity =>
|
activity =>
|
||||||
{
|
{
|
||||||
observedActivity = activity;
|
observedActivity = activity;
|
||||||
@@ -2067,7 +2219,7 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
{
|
{
|
||||||
SessionId = "copilot-session-1",
|
SessionId = "copilot-session-1",
|
||||||
},
|
},
|
||||||
new Dictionary<string, string>(StringComparer.Ordinal),
|
CreateToolCallRegistry(),
|
||||||
approval =>
|
approval =>
|
||||||
{
|
{
|
||||||
sawApproval = true;
|
sawApproval = true;
|
||||||
@@ -2104,7 +2256,7 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
{
|
{
|
||||||
SessionId = "copilot-session-1",
|
SessionId = "copilot-session-1",
|
||||||
},
|
},
|
||||||
new Dictionary<string, string>(StringComparer.Ordinal),
|
CreateToolCallRegistry(),
|
||||||
approval =>
|
approval =>
|
||||||
{
|
{
|
||||||
sawApproval = true;
|
sawApproval = true;
|
||||||
@@ -2137,10 +2289,7 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
{
|
{
|
||||||
SessionId = "copilot-session-1",
|
SessionId = "copilot-session-1",
|
||||||
},
|
},
|
||||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
CreateToolCallRegistry(("tool-call-read-1", "view")),
|
||||||
{
|
|
||||||
["tool-call-read-1"] = "view",
|
|
||||||
},
|
|
||||||
approval =>
|
approval =>
|
||||||
{
|
{
|
||||||
firstApproval = approval;
|
firstApproval = approval;
|
||||||
@@ -2178,10 +2327,7 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
{
|
{
|
||||||
SessionId = "copilot-session-1",
|
SessionId = "copilot-session-1",
|
||||||
},
|
},
|
||||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
CreateToolCallRegistry(("tool-call-read-2", "grep")),
|
||||||
{
|
|
||||||
["tool-call-read-2"] = "grep",
|
|
||||||
},
|
|
||||||
approval =>
|
approval =>
|
||||||
{
|
{
|
||||||
sawSecondApproval = true;
|
sawSecondApproval = true;
|
||||||
@@ -2214,10 +2360,7 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
{
|
{
|
||||||
SessionId = "copilot-session-1",
|
SessionId = "copilot-session-1",
|
||||||
},
|
},
|
||||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
CreateToolCallRegistry(("tool-call-read-1", "view")),
|
||||||
{
|
|
||||||
["tool-call-read-1"] = "view",
|
|
||||||
},
|
|
||||||
approval =>
|
approval =>
|
||||||
{
|
{
|
||||||
firstApproval = approval;
|
firstApproval = approval;
|
||||||
@@ -2254,10 +2397,7 @@ public sealed class CopilotWorkflowRunnerTests
|
|||||||
{
|
{
|
||||||
SessionId = "copilot-session-1",
|
SessionId = "copilot-session-1",
|
||||||
},
|
},
|
||||||
new Dictionary<string, string>(StringComparer.Ordinal)
|
CreateToolCallRegistry(("tool-call-read-2", "grep")),
|
||||||
{
|
|
||||||
["tool-call-read-2"] = "grep",
|
|
||||||
},
|
|
||||||
approval =>
|
approval =>
|
||||||
{
|
{
|
||||||
secondApproval = 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()
|
private static RunTurnCommandDto CreateRequestPortCommand()
|
||||||
{
|
{
|
||||||
return new RunTurnCommandDto
|
return new RunTurnCommandDto
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Aryx.AgentHost.Contracts;
|
using Aryx.AgentHost.Contracts;
|
||||||
@@ -27,8 +26,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
CreateSingleAgentCommand(),
|
CreateSingleAgentCommand(),
|
||||||
requestInfo,
|
requestInfo,
|
||||||
new AgentIdentity("agent-1", "Primary"),
|
new AgentIdentity("agent-1", "Primary"),
|
||||||
tracking.ToolNamesByCallId,
|
tracking);
|
||||||
tracking.ToolCallHasArgumentsById);
|
|
||||||
|
|
||||||
Assert.NotNull(activity);
|
Assert.NotNull(activity);
|
||||||
Assert.Equal("tool-calling", activity.ActivityType);
|
Assert.Equal("tool-calling", activity.ActivityType);
|
||||||
@@ -38,8 +36,9 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
Assert.NotNull(activity.ToolArguments);
|
Assert.NotNull(activity.ToolArguments);
|
||||||
Assert.Equal(@"C:\workspace\file.txt", activity.ToolArguments["path"]);
|
Assert.Equal(@"C:\workspace\file.txt", activity.ToolArguments["path"]);
|
||||||
Assert.Equal([10, 25], Assert.IsAssignableFrom<IReadOnlyList<object?>>(activity.ToolArguments["viewRange"]));
|
Assert.Equal([10, 25], Assert.IsAssignableFrom<IReadOnlyList<object?>>(activity.ToolArguments["viewRange"]));
|
||||||
Assert.Equal("view", tracking.ToolNamesByCallId["call-1"]);
|
Assert.True(tracking.TryGetToolName("call-1", out string? toolName));
|
||||||
Assert.True(tracking.ToolCallHasArgumentsById["call-1"]);
|
Assert.Equal("view", toolName);
|
||||||
|
Assert.True(tracking.HasTrackedArguments("call-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -61,8 +60,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
CreateSingleAgentCommand(),
|
CreateSingleAgentCommand(),
|
||||||
requestInfo,
|
requestInfo,
|
||||||
new AgentIdentity("agent-1", "Primary"),
|
new AgentIdentity("agent-1", "Primary"),
|
||||||
tracking.ToolNamesByCallId,
|
tracking);
|
||||||
tracking.ToolCallHasArgumentsById);
|
|
||||||
|
|
||||||
Assert.NotNull(activity);
|
Assert.NotNull(activity);
|
||||||
Assert.Equal("tool-calling", activity.ActivityType);
|
Assert.Equal("tool-calling", activity.ActivityType);
|
||||||
@@ -70,8 +68,9 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
Assert.NotNull(activity.ToolArguments);
|
Assert.NotNull(activity.ToolArguments);
|
||||||
Assert.Equal(@"C:\workspace", activity.ToolArguments["path"]);
|
Assert.Equal(@"C:\workspace", activity.ToolArguments["path"]);
|
||||||
Assert.Equal(true, activity.ToolArguments["includeIgnored"]);
|
Assert.Equal(true, activity.ToolArguments["includeIgnored"]);
|
||||||
Assert.Equal("git.status", tracking.ToolNamesByCallId["call-1"]);
|
Assert.True(tracking.TryGetToolName("call-1", out string? toolName));
|
||||||
Assert.True(tracking.ToolCallHasArgumentsById["call-1"]);
|
Assert.Equal("git.status", toolName);
|
||||||
|
Assert.True(tracking.HasTrackedArguments("call-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -85,8 +84,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
CreateSingleAgentCommand(),
|
CreateSingleAgentCommand(),
|
||||||
requestInfo,
|
requestInfo,
|
||||||
new AgentIdentity("agent-1", "Primary"),
|
new AgentIdentity("agent-1", "Primary"),
|
||||||
tracking.ToolNamesByCallId,
|
tracking);
|
||||||
tracking.ToolCallHasArgumentsById);
|
|
||||||
|
|
||||||
Assert.NotNull(activity);
|
Assert.NotNull(activity);
|
||||||
Assert.Equal("tool-calling", activity.ActivityType);
|
Assert.Equal("tool-calling", activity.ActivityType);
|
||||||
@@ -95,8 +93,9 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
["print('hello')"],
|
["print('hello')"],
|
||||||
Assert.IsAssignableFrom<IReadOnlyList<object?>>(activity.ToolArguments["inputs"]));
|
Assert.IsAssignableFrom<IReadOnlyList<object?>>(activity.ToolArguments["inputs"]));
|
||||||
Assert.Equal("code interpreter", tracking.ToolNamesByCallId["call-1"]);
|
Assert.True(tracking.TryGetToolName("call-1", out string? toolName));
|
||||||
Assert.True(tracking.ToolCallHasArgumentsById["call-1"]);
|
Assert.Equal("code interpreter", toolName);
|
||||||
|
Assert.True(tracking.HasTrackedArguments("call-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -109,15 +108,14 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
CreateSingleAgentCommand(),
|
CreateSingleAgentCommand(),
|
||||||
requestInfo,
|
requestInfo,
|
||||||
new AgentIdentity("agent-1", "Primary"),
|
new AgentIdentity("agent-1", "Primary"),
|
||||||
tracking.ToolNamesByCallId,
|
tracking);
|
||||||
tracking.ToolCallHasArgumentsById);
|
|
||||||
|
|
||||||
Assert.NotNull(activity);
|
Assert.NotNull(activity);
|
||||||
Assert.Equal("tool-calling", activity.ActivityType);
|
Assert.Equal("tool-calling", activity.ActivityType);
|
||||||
Assert.Equal("image generation", activity.ToolName);
|
Assert.Equal("image generation", activity.ToolName);
|
||||||
Assert.Null(activity.ToolArguments);
|
Assert.Null(activity.ToolArguments);
|
||||||
Assert.Empty(tracking.ToolNamesByCallId);
|
Assert.False(tracking.TryGetToolName("call-1", out _));
|
||||||
Assert.Empty(tracking.ToolCallHasArgumentsById);
|
Assert.False(tracking.HasTrackedArguments("call-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -135,12 +133,11 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
CreateSingleAgentCommand(),
|
CreateSingleAgentCommand(),
|
||||||
requestInfo,
|
requestInfo,
|
||||||
new AgentIdentity("agent-1", "Primary"),
|
new AgentIdentity("agent-1", "Primary"),
|
||||||
tracking.ToolNamesByCallId,
|
tracking);
|
||||||
tracking.ToolCallHasArgumentsById);
|
|
||||||
|
|
||||||
Assert.NotNull(activity);
|
Assert.NotNull(activity);
|
||||||
Assert.Null(activity.ToolArguments);
|
Assert.Null(activity.ToolArguments);
|
||||||
Assert.False(tracking.ToolCallHasArgumentsById["call-1"]);
|
Assert.False(tracking.HasTrackedArguments("call-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -160,21 +157,25 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
CreateSingleAgentCommand(),
|
CreateSingleAgentCommand(),
|
||||||
requestInfo,
|
requestInfo,
|
||||||
new AgentIdentity("agent-1", "Primary"),
|
new AgentIdentity("agent-1", "Primary"),
|
||||||
tracking.ToolNamesByCallId,
|
tracking);
|
||||||
tracking.ToolCallHasArgumentsById);
|
|
||||||
|
|
||||||
Assert.NotNull(activity);
|
Assert.NotNull(activity);
|
||||||
Assert.NotNull(activity.ToolArguments);
|
Assert.NotNull(activity.ToolArguments);
|
||||||
Assert.Equal("[truncated]", activity.ToolArguments["command"]);
|
Assert.Equal("[truncated]", activity.ToolArguments["command"]);
|
||||||
Assert.True(tracking.ToolCallHasArgumentsById["call-1"]);
|
Assert.True(tracking.HasTrackedArguments("call-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TryCreateActivityFromRequest_SkipsDuplicateTrackedToolCallIdsThatAlreadyHaveArguments()
|
public void TryCreateActivityFromRequest_SkipsDuplicateTrackedToolCallIdsThatAlreadyHaveArguments()
|
||||||
{
|
{
|
||||||
var tracking = CreateToolTracking();
|
var tracking = CreateToolTracking();
|
||||||
tracking.ToolNamesByCallId["call-1"] = "view";
|
tracking.RecordToolStart(
|
||||||
tracking.ToolCallHasArgumentsById["call-1"] = true;
|
"call-1",
|
||||||
|
"view",
|
||||||
|
new Dictionary<string, object?>
|
||||||
|
{
|
||||||
|
["path"] = @"C:\workspace\seed.txt",
|
||||||
|
});
|
||||||
RequestInfoEvent requestInfo = CreateRequestInfoEvent(
|
RequestInfoEvent requestInfo = CreateRequestInfoEvent(
|
||||||
new FunctionCallContent("call-1", "view", new Dictionary<string, object?>
|
new FunctionCallContent("call-1", "view", new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
@@ -185,20 +186,19 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
CreateSingleAgentCommand(),
|
CreateSingleAgentCommand(),
|
||||||
requestInfo,
|
requestInfo,
|
||||||
new AgentIdentity("agent-1", "Primary"),
|
new AgentIdentity("agent-1", "Primary"),
|
||||||
tracking.ToolNamesByCallId,
|
tracking);
|
||||||
tracking.ToolCallHasArgumentsById);
|
|
||||||
|
|
||||||
Assert.Null(activity);
|
Assert.Null(activity);
|
||||||
Assert.Equal("view", tracking.ToolNamesByCallId["call-1"]);
|
Assert.True(tracking.TryGetToolName("call-1", out string? toolName));
|
||||||
Assert.True(tracking.ToolCallHasArgumentsById["call-1"]);
|
Assert.Equal("view", toolName);
|
||||||
|
Assert.True(tracking.HasTrackedArguments("call-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void TryCreateActivityFromRequest_EmitsEnrichmentWhenTrackedToolCallWasMissingArguments()
|
public void TryCreateActivityFromRequest_EmitsEnrichmentWhenTrackedToolCallWasMissingArguments()
|
||||||
{
|
{
|
||||||
var tracking = CreateToolTracking();
|
var tracking = CreateToolTracking();
|
||||||
tracking.ToolNamesByCallId["call-1"] = "view";
|
tracking.RecordToolStart("call-1", "view", toolArguments: null);
|
||||||
tracking.ToolCallHasArgumentsById["call-1"] = false;
|
|
||||||
RequestInfoEvent requestInfo = CreateRequestInfoEvent(
|
RequestInfoEvent requestInfo = CreateRequestInfoEvent(
|
||||||
new FunctionCallContent("call-1", "view", new Dictionary<string, object?>
|
new FunctionCallContent("call-1", "view", new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
@@ -209,16 +209,16 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
CreateSingleAgentCommand(),
|
CreateSingleAgentCommand(),
|
||||||
requestInfo,
|
requestInfo,
|
||||||
new AgentIdentity("agent-1", "Primary"),
|
new AgentIdentity("agent-1", "Primary"),
|
||||||
tracking.ToolNamesByCallId,
|
tracking);
|
||||||
tracking.ToolCallHasArgumentsById);
|
|
||||||
|
|
||||||
Assert.NotNull(activity);
|
Assert.NotNull(activity);
|
||||||
Assert.Equal("tool-calling", activity.ActivityType);
|
Assert.Equal("tool-calling", activity.ActivityType);
|
||||||
Assert.Equal("call-1", activity.ToolCallId);
|
Assert.Equal("call-1", activity.ToolCallId);
|
||||||
Assert.NotNull(activity.ToolArguments);
|
Assert.NotNull(activity.ToolArguments);
|
||||||
Assert.Equal(@"C:\workspace\file.txt", activity.ToolArguments["path"]);
|
Assert.Equal(@"C:\workspace\file.txt", activity.ToolArguments["path"]);
|
||||||
Assert.Equal("view", tracking.ToolNamesByCallId["call-1"]);
|
Assert.True(tracking.TryGetToolName("call-1", out string? toolName));
|
||||||
Assert.True(tracking.ToolCallHasArgumentsById["call-1"]);
|
Assert.Equal("view", toolName);
|
||||||
|
Assert.True(tracking.HasTrackedArguments("call-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -232,8 +232,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
CreateHandoffCommand(),
|
CreateHandoffCommand(),
|
||||||
requestInfo,
|
requestInfo,
|
||||||
new AgentIdentity("agent-handoff-triage", "Triage"),
|
new AgentIdentity("agent-handoff-triage", "Triage"),
|
||||||
tracking.ToolNamesByCallId,
|
tracking);
|
||||||
tracking.ToolCallHasArgumentsById);
|
|
||||||
|
|
||||||
Assert.NotNull(activity);
|
Assert.NotNull(activity);
|
||||||
Assert.Equal("handoff", activity.ActivityType);
|
Assert.Equal("handoff", activity.ActivityType);
|
||||||
@@ -242,8 +241,8 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
Assert.Equal("agent-handoff-triage", activity.SourceAgentId);
|
Assert.Equal("agent-handoff-triage", activity.SourceAgentId);
|
||||||
Assert.Equal("Triage", activity.SourceAgentName);
|
Assert.Equal("Triage", activity.SourceAgentName);
|
||||||
Assert.Null(activity.ToolName);
|
Assert.Null(activity.ToolName);
|
||||||
Assert.Empty(tracking.ToolNamesByCallId);
|
Assert.False(tracking.TryGetToolName("call-1", out _));
|
||||||
Assert.Empty(tracking.ToolCallHasArgumentsById);
|
Assert.False(tracking.HasTrackedArguments("call-1"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -263,8 +262,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
"agent-reviewer",
|
"agent-reviewer",
|
||||||
"Reviewer",
|
"Reviewer",
|
||||||
new SubworkflowContext("subworkflow-review", "Review Lane")),
|
new SubworkflowContext("subworkflow-review", "Review Lane")),
|
||||||
tracking.ToolNamesByCallId,
|
tracking);
|
||||||
tracking.ToolCallHasArgumentsById);
|
|
||||||
|
|
||||||
Assert.NotNull(activity);
|
Assert.NotNull(activity);
|
||||||
Assert.Equal("tool-calling", activity.ActivityType);
|
Assert.Equal("tool-calling", activity.ActivityType);
|
||||||
@@ -283,8 +281,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
CreateHandoffCommandWithReferencedSubworkflow(),
|
CreateHandoffCommandWithReferencedSubworkflow(),
|
||||||
requestInfo,
|
requestInfo,
|
||||||
new AgentIdentity("agent-handoff-triage", "Triage"),
|
new AgentIdentity("agent-handoff-triage", "Triage"),
|
||||||
tracking.ToolNamesByCallId,
|
tracking);
|
||||||
tracking.ToolCallHasArgumentsById);
|
|
||||||
|
|
||||||
Assert.NotNull(activity);
|
Assert.NotNull(activity);
|
||||||
Assert.Equal("handoff", activity.ActivityType);
|
Assert.Equal("handoff", activity.ActivityType);
|
||||||
@@ -417,10 +414,7 @@ public sealed class WorkflowRequestInfoInterpreterTests
|
|||||||
workflowLibrary: [nestedWorkflow]);
|
workflowLibrary: [nestedWorkflow]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (
|
private static ToolCallRegistry CreateToolTracking() => new();
|
||||||
ConcurrentDictionary<string, string> ToolNamesByCallId,
|
|
||||||
ConcurrentDictionary<string, bool> ToolCallHasArgumentsById) CreateToolTracking()
|
|
||||||
=> (new(StringComparer.Ordinal), new(StringComparer.Ordinal));
|
|
||||||
|
|
||||||
private static RunTurnCommandDto CreateCommand(
|
private static RunTurnCommandDto CreateCommand(
|
||||||
string orchestrationMode,
|
string orchestrationMode,
|
||||||
|
|||||||
Reference in New Issue
Block a user