refactor: resolve approval tools from SDK call ids

- correlate permission requests to tool names via ToolCallId when the
  workflow request stream exposes matching CallId values
- keep the url-to-web_fetch alias only as a narrow fallback when a
  matching tool call cannot be recovered
- cover generic permission-category lookup for url, shell, and read in
  sidecar tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-24 21:03:16 +01:00
co-authored by Copilot
parent 7622d047b1
commit 44d9c43b09
2 changed files with 129 additions and 20 deletions
@@ -258,7 +258,7 @@ public sealed class CopilotWorkflowRunnerTests
}
[Fact]
public void TryGetApprovalToolName_ReadsMcpCustomHookAndUrlRequests()
public void TryGetApprovalToolName_ReadsMcpCustomAndHookRequests()
{
Assert.True(
CopilotWorkflowRunner.TryGetApprovalToolName(
@@ -296,18 +296,6 @@ public sealed class CopilotWorkflowRunnerTests
out string? hookToolName));
Assert.Equal("web_fetch", hookToolName);
Assert.True(
CopilotWorkflowRunner.TryGetApprovalToolName(
new PermissionRequestUrl
{
Kind = "url",
ToolCallId = "tool-call-1",
Intention = "Fetch the requested page",
Url = "https://example.com/docs",
},
out string? urlToolName));
Assert.Equal("web_fetch", urlToolName);
Assert.False(
CopilotWorkflowRunner.TryGetApprovalToolName(
new PermissionRequestShell
@@ -325,6 +313,77 @@ public sealed class CopilotWorkflowRunnerTests
Assert.Null(shellToolName);
}
[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",
};
Assert.True(
CopilotWorkflowRunner.TryGetApprovalToolName(
new PermissionRequestUrl
{
Kind = "url",
ToolCallId = "tool-call-url",
Intention = "Fetch the requested page",
Url = "https://example.com/docs",
},
toolNamesByCallId,
out string? urlToolName));
Assert.Equal("web_fetch", urlToolName);
Assert.True(
CopilotWorkflowRunner.TryGetApprovalToolName(
new PermissionRequestShell
{
Kind = "shell",
ToolCallId = "tool-call-shell",
FullCommandText = "curl https://example.com/docs",
Intention = "Fetch documentation with curl",
Commands = [],
PossiblePaths = [],
PossibleUrls = [],
HasWriteFileRedirection = false,
CanOfferSessionApproval = false,
},
toolNamesByCallId,
out string? shellToolName));
Assert.Equal("shell", shellToolName);
Assert.True(
CopilotWorkflowRunner.TryGetApprovalToolName(
new PermissionRequestRead
{
Kind = "read",
ToolCallId = "tool-call-read",
Intention = "Inspect a file",
Path = "README.md",
},
toolNamesByCallId,
out string? readToolName));
Assert.Equal("view", readToolName);
}
[Fact]
public void TryGetApprovalToolName_FallsBackToWebFetchForUncorrelatedUrlRequests()
{
Assert.True(
CopilotWorkflowRunner.TryGetApprovalToolName(
new PermissionRequestUrl
{
Kind = "url",
ToolCallId = "tool-call-1",
Intention = "Fetch the requested page",
Url = "https://example.com/docs",
},
out string? urlToolName));
Assert.Equal("web_fetch", urlToolName);
}
[Fact]
public void BuildPermissionApprovalEvent_IncludesToolContextWhenKnown()
{