Skip to content
Tracon

IToolApprovalPresenter

Namespace Tracon · Assembly Tracon.Abstractions.dll

Resolves a human-readable presentation for a tool call that is about to publish an approval request — turning, for example, { "orderId": "ORD-1001" } into “Cancel order for Priya Shah”.

public interface IToolApprovalPresenter
public sealed class OrderApprovalPresenter(IServiceScopeFactory scopes) : IToolApprovalPresenter
{
public async ValueTask<ToolApprovalPresentation?> PresentAsync(
ToolApprovalContext context, CancellationToken cancellationToken = default)
{
if (context.GetString("orderId") is not { } orderId)
{
return null;
}
using var scope = scopes.CreateScope();
var orders = scope.ServiceProvider.GetRequiredService<IOrderRepository>();
var customerName = await orders.GetAsync(orderId);
return new ToolApprovalPresentation
{
EntityType = "order",
EntityId = orderId,
EntityName = $"Order {orderId}",
Message = $"Cancel order {orderId} for {customerName}.",
};
}
}

Read-only and best-effort. This is not an authorization boundary — IToolAuthorizationHandler and IToolArgumentsValidator are the gates, and both fail closed. A presenter fails open: registered or not, resolving or not, timing out or throwing, the approval request is published either way. A presentation error must never suppress a request that needs a decision. The raw arguments (PendingApproval.Arguments) are always available beside whatever this method returns, so an operator can still decide even when it returns null.

Register it as a singleton and resolve scoped dependencies through IServiceScopeFactory. This method runs on the tool-call path, where Microsoft Agent Framework hands every tool an empty service provider (AIFunctionArguments.Services resolves nothing — see the framework notes on IToolAuthorizationHandler). The same limit applies here: a scoped dependency, such as a DbContext, must be resolved through an injected IServiceScopeFactory, not a constructor parameter.

Tenant behavior — EXPECTED tenant. The tenant is carried by ToolApprovalContext.TenantId, the same way the code-defined approval policy callback (ITraconBuilder.AddToolApprovalPolicy) reads it — never the ambient tenant, since a presenter’s own scoped lookup must never leak another tenant’s entity name into this one’s approval card.

PresentAsync(ToolApprovalContext, CancellationToken)

Section titled “ PresentAsync(ToolApprovalContext, CancellationToken)”

Resolves a presentation for one pending tool call.

ValueTask<ToolApprovalPresentation?> PresentAsync(ToolApprovalContext context, CancellationToken cancellationToken = default)

context ToolApprovalContext

The tool call the approval request was raised for.

cancellationToken CancellationToken

The cancellation token.

ValueTask<ToolApprovalPresentation?>

The presentation, or null when nothing could be resolved. A thrown exception or a call that runs past the configured timeout (TraconToolOptions.ApprovalPresentationTimeout) is treated the same way.