Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion agent/harness/toolapproval/toolapproval.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ type Config struct {
// without prompting the caller. Returning an error fails the current run.
AutoApprovalRules []AutoApprovalRule

// DisableNonApprovalRequiredToolBypassing disables the default behavior that
// auto-approves any approval request whose tool does not actually require
// approval. When true, such requests are surfaced to the caller instead of
// being transparently approved, unless they are auto-approved by a standing
// rule or by a configured AutoApprovalRules entry.
DisableNonApprovalRequiredToolBypassing bool

// DisableApprovalResponseBinding disables rebinding inbound approval responses
// to the tool approval requests previously surfaced by this middleware.
//
Expand Down Expand Up @@ -507,7 +514,7 @@ func isNotApprovalRequired(req *message.ToolApprovalRequestContent, opts []agent
// configured auto-approval rules. This matches the .NET MatchesRule || MatchesAutoApprovalRule
// evaluation pattern used in ToolApprovalAgent.
func isAutoApprovable(ctx context.Context, cfg Config, rules []Rule, requestMessages []*message.Message, opts []agent.Option, req *message.ToolApprovalRequestContent) (bool, error) {
if matchesRule(rules, req) || isNotApprovalRequired(req, opts) {
if matchesRule(rules, req) || (!cfg.DisableNonApprovalRequiredToolBypassing && isNotApprovalRequired(req, opts)) {
return true, nil
}
return matchesAutoApprovalRules(ctx, cfg.AutoApprovalRules, requestMessages, opts, req)
Expand Down
60 changes: 60 additions & 0 deletions agent/harness/toolapproval/toolapproval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1055,6 +1055,66 @@ func TestToolApproval_NonApprovalRequiredQueuedRequestDrained(t *testing.T) {
}
}

func TestToolApproval_DisableNonApprovalRequiredToolBypassing_SurfacesQueuedNonApprovalRequest(t *testing.T) {
deployFCC := &message.FunctionCallContent{CallID: "c-deploy", Name: "deploy"}
listFCC := &message.FunctionCallContent{CallID: "c-list", Name: "list"}

runner := &agenttest.Runner{
Responses: agenttest.NewResponseBuilder().
Add(&agent.ResponseUpdate{
Role: message.RoleAssistant,
Contents: []message.Content{
&message.ToolApprovalRequestContent{RequestID: "r-deploy", ToolCall: deployFCC},
&message.ToolApprovalRequestContent{RequestID: "r-list", ToolCall: listFCC},
},
}).
Build(),
}

mw := toolapproval.New(toolapproval.Config{
DisableNonApprovalRequiredToolBypassing: true,
})
session := agenttest.CreateSession()

turn1 := collectUpdates(t, mw, runner.Run,
[]*message.Message{{Role: message.RoleUser, Contents: []message.Content{&message.TextContent{Text: "go"}}}},
agent.WithSession(session),
)

var deployReq *message.ToolApprovalRequestContent
for _, u := range turn1 {
for _, c := range u.Contents {
if r, ok := c.(*message.ToolApprovalRequestContent); ok && r.RequestID == "r-deploy" {
deployReq = r
}
}
}
if deployReq == nil {
t.Fatal("expected deploy approval request in turn 1")
}

turn2 := collectUpdates(t, mw, runner.Run,
[]*message.Message{{Role: message.RoleUser, Contents: []message.Content{deployReq.CreateResponse(true, "")}}},
agent.WithSession(session),
agent.WithTool(newNoopTool("list")),
)

var approvalReqs []*message.ToolApprovalRequestContent
for _, u := range turn2 {
for _, c := range u.Contents {
if req, ok := c.(*message.ToolApprovalRequestContent); ok {
approvalReqs = append(approvalReqs, req)
}
if tc, ok := c.(*message.TextContent); ok && tc.Text == "done" {
t.Fatal("expected queued non-approval-required request to be surfaced before inner agent resumed")
}
}
}
if len(approvalReqs) != 1 || approvalReqs[0].RequestID != "r-list" {
t.Fatalf("expected queued list approval request to be surfaced, got %#v", approvalReqs)
}
}

func TestToolApproval_AutoApprovalRule_ApprovesMatchingTool(t *testing.T) {
fcc := &message.FunctionCallContent{CallID: "c1", Name: "ReadTool", Arguments: `{}`}

Expand Down
2 changes: 1 addition & 1 deletion docs/dotnet-go-sdk-feature-comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Intentional contract choices in this parity pass:
| Function tools | `AIFunction`, `AITool`, function tools, plugins, dynamic function tools, tool argument matching in evals. Tool selection supports auto, none, require-any, or one required function. | `tool.Tool`, `tool.FuncTool`, `functool.New`, typed input/output schemas, `ToolModeAuto`, `ToolModeNone`, `ToolModeRequired`, `RequireTool`, and a plugin-style grouping sample. | Partial | Core tool-selection semantics align. Go has typed function tools and plugin-style tool grouping, but no first-class plugin abstraction or dynamic tool sample equivalent to .NET steps 12 and 20. |
| Shell tool and environment context | `Microsoft.Agents.AI.Tools.Shell`: `LocalShellExecutor`, `ShellPolicy` (allow/deny-list), `ShellResult`, stateless and persistent shell execution modes, approval-in-the-loop gate, configurable tool name/description, head-tail output truncation, `ShellEnvironmentProvider`, `ShellEnvironmentSnapshot`, shell-family instructions, common CLI probing. | `tool/shelltool.NewLocal`, `shelltool.LocalConfig` (name, description, mode, timeout, max output, policy, acknowledge unsafe), `shelltool.Policy`, `shelltool.Result.FormatForModel`, `shelltool.Executor`, `shelltool.NewEnvironmentProvider`, `EnvironmentProviderConfig`, `ShellEnvironmentSnapshot`, `DefaultShellEnvironmentInstructions`. | Aligned | Go mirrors the .NET design for local execution, policy allow/deny-list, approval-required by default, configurable tool name/description, stateless/persistent modes, output truncation, environment snapshot probing, cached first-probe behavior, refresh, current snapshot access, shell-family prompt instructions, invalid/duplicate probe handling, stderr version fallback, caller cancellation, and probe timeout handling. Docker shell executor not ported (Go has no equivalent `DockerShellExecutor`). Go represents tool-version nullability with `ToolVersion{Found bool}` rather than nullable strings. |
| Tool auto-calling | Provider/tool-call loop, concurrent invocation, tool approval agent, and the separate `MessageInjectingChatClient` decorator. | `agent/harness/toolautocall` is installed by supporting providers. Supplying `agent.Config.MessageInjector` enables the corresponding internal provider-call decorator; callers queue and inspect messages through that `agent.MessageInjector`. Auto-call supports concurrent invocation, approval-response binding, and approval-not-required call bypass. | Aligned | Go keeps auto-call in explicit middleware and uses an explicit injector, while preserving .NET's separate inner-decorator behavior. |
| Tool approval | Tool approval request/response content, tool approval agent and builder extensions, auto-approval rules (heuristics). | `message.ToolApprovalRequestContent`, `message.ToolApprovalResponseContent`, `tool.ApprovalRequiredFunc`, `agent/harness/toolautocall` approval flow, `agent/harness/toolapproval` middleware for standing-rule and auto-approval-rule approval management, AGUI HITL sample. Approval responses are rebound by request ID to session-snapshotted calls; unknown and duplicate responses are ignored. | Aligned | API shape differs: .NET uses a `ToolApprovalAgent` delegating-agent wrapper with `ToolApprovalAgentOptions`; Go uses idiomatic middleware (`toolapproval.New(toolapproval.Config{AutoApprovalRules: ..., DisableApprovalResponseBinding: ...})`). Standing approval rules, queued-request batching, `AlwaysApprove*` response content, approval-response rebinding to surfaced requests by default, and auto-approval rules (heuristics) are now present in both SDKs. |
| Tool approval | Tool approval request/response content, tool approval agent and builder extensions, auto-approval rules (heuristics). | `message.ToolApprovalRequestContent`, `message.ToolApprovalResponseContent`, `tool.ApprovalRequiredFunc`, `agent/harness/toolautocall` approval flow, `agent/harness/toolapproval` middleware for standing-rule and auto-approval-rule approval management, AGUI HITL sample. Approval responses are rebound by request ID to session-snapshotted calls; unknown and duplicate responses are ignored. | Aligned | API shape differs: .NET uses a `ToolApprovalAgent` delegating-agent wrapper with `ToolApprovalAgentOptions`; Go uses idiomatic middleware (`toolapproval.New(toolapproval.Config{AutoApprovalRules: ..., DisableApprovalResponseBinding: ..., DisableNonApprovalRequiredToolBypassing: ...})`). Standing approval rules, queued-request batching, `AlwaysApprove*` response content, approval-response rebinding to surfaced requests by default, auto-approval rules (heuristics), and the public opt-out for bypassing non-approval-required tools are now present in both SDKs. |
| Hosted/server-side tools | Foundry/OpenAI samples for code interpreter, file search, web search, OpenAPI, Bing custom search, SharePoint, Microsoft Fabric, memory search, Toolbox, hosted MCP. | `tool/hostedtool` declarations for web search, file search, code interpreter, MCP server; Foundry-first samples cover code interpreter, web search, MCP client tools, and local MCP tools; OpenAI Responses hosted-tool coverage remains provider-specific. | Partial | Go has declaration types and initial Foundry/OpenAI Responses hosted-tool coverage, but fewer service-specific Foundry hosted tool integrations and no Foundry toolbox lifecycle sample. |
| Agent as function tool | Agents can be converted/bound as tools in samples and workflow builders. | `tool/agenttool.New` wraps an agent as a `FuncTool`. | Aligned | API shape differs; Go exposes a direct package. |
| Agent as MCP tool/server | .NET sample `Agent_Step07_AsMcpTool` and durable sample for agent as MCP tool. | `tool/mcptool.AddTool`, `examples/02-agents/mcp/agent_mcp_server`, `step10_as_mcp_tool`. | Aligned | Durable MCP hosting is .NET only. |
Expand Down
Loading