mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-29 06:07:11 +02:00
fix: only project handoff tool calls as FunctionCallContent
AryxCopilotAgent was converting ALL Copilot tool requests (ask_user, MCP tools, etc.) into FunctionCallContent. The Agent Framework's AIAgentHostExecutor tracks every FunctionCallContent as an outstanding request. Since SDK-handled tools like ask_user never produce a matching FunctionResultContent, HasOutstandingRequests stayed true and TurnToken was never emitted — stalling group-chat advancement after an ask_user interaction. Now only handoff_to_* tool requests are projected as FunctionCallContent. All other tool calls remain invisible to the Agent Framework executor, matching the upstream GitHubCopilotAgent behaviour. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -225,6 +225,16 @@ internal sealed class AryxCopilotAgent : AIAgent, IAsyncDisposable
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only project handoff tool calls as FunctionCallContent for the Agent Framework.
|
||||||
|
// Other tool calls (ask_user, MCP tools, etc.) are resolved by the Copilot SDK
|
||||||
|
// internally and must not be surfaced, because AIAgentHostExecutor tracks every
|
||||||
|
// FunctionCallContent as an outstanding request. An unmatched request prevents
|
||||||
|
// the executor from emitting a TurnToken, which stalls group-chat advancement.
|
||||||
|
if (!IsHandoffToolName(toolRequest.Name))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
contents.Add(new FunctionCallContent(
|
contents.Add(new FunctionCallContent(
|
||||||
toolRequest.ToolCallId,
|
toolRequest.ToolCallId,
|
||||||
toolRequest.Name,
|
toolRequest.Name,
|
||||||
@@ -234,6 +244,12 @@ internal sealed class AryxCopilotAgent : AIAgent, IAsyncDisposable
|
|||||||
return contents;
|
return contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsHandoffToolName(string? name)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrWhiteSpace(name)
|
||||||
|
&& name.StartsWith(HandoffToolPrefix, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task EnsureClientStartedAsync(CancellationToken cancellationToken)
|
private async Task EnsureClientStartedAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_copilotClient.State != ConnectionState.Connected)
|
if (_copilotClient.State != ConnectionState.Connected)
|
||||||
@@ -344,8 +360,7 @@ internal sealed class AryxCopilotAgent : AIAgent, IAsyncDisposable
|
|||||||
|
|
||||||
private static bool IsHandoffDeclaration(AIFunctionDeclaration declaration)
|
private static bool IsHandoffDeclaration(AIFunctionDeclaration declaration)
|
||||||
{
|
{
|
||||||
return !string.IsNullOrWhiteSpace(declaration.Name)
|
return IsHandoffToolName(declaration.Name);
|
||||||
&& declaration.Name.StartsWith(HandoffToolPrefix, StringComparison.Ordinal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AIFunction CreateInvokableHandoffFunction(AIFunctionDeclaration declaration)
|
private static AIFunction CreateInvokableHandoffFunction(AIFunctionDeclaration declaration)
|
||||||
|
|||||||
@@ -125,6 +125,24 @@ public sealed class CopilotAgentBundleTests
|
|||||||
Assert.Equal("frontend specialist", functionCall.Arguments["reasonForHandoff"]?.ToString());
|
Assert.Equal("frontend specialist", functionCall.Arguments["reasonForHandoff"]?.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ConvertToolRequestsToFunctionCalls_SkipsNonHandoffToolCalls()
|
||||||
|
{
|
||||||
|
AssistantMessageDataToolRequestsItem[] toolRequests =
|
||||||
|
{
|
||||||
|
new() { ToolCallId = "call-001", Name = "ask_user" },
|
||||||
|
new() { ToolCallId = "call-002", Name = "web_fetch" },
|
||||||
|
new() { ToolCallId = "call-003", Name = "handoff_to_reviewer" },
|
||||||
|
new() { ToolCallId = "call-004", Name = "grep" },
|
||||||
|
};
|
||||||
|
|
||||||
|
IReadOnlyList<FunctionCallContent> result = AryxCopilotAgent.ConvertToolRequestsToFunctionCalls(toolRequests);
|
||||||
|
|
||||||
|
FunctionCallContent single = Assert.Single(result);
|
||||||
|
Assert.Equal("call-003", single.CallId);
|
||||||
|
Assert.Equal("handoff_to_reviewer", single.Name);
|
||||||
|
}
|
||||||
|
|
||||||
private static AIFunction CreateTool()
|
private static AIFunction CreateTool()
|
||||||
{
|
{
|
||||||
ToolTarget target = new();
|
ToolTarget target = new();
|
||||||
|
|||||||
Reference in New Issue
Block a user