Tool Call Consent
When the Bot Provider configures a Toolset that requires user consent, the SDK automatically pops up a consent modal before the agent runs the tool, asking the user to make a decision for each tool call before execution continues.
No extra wiring is required — ToolCallConsentGate is built into the
<Chatbot> component.
Three Decisions
The modal buttons currently render in Traditional Chinese (no i18n yet):
| Decision (button text) | Result | Description |
|---|---|---|
| 本次對話皆允許 (Allow for This Chat) | ALLOW_ALWAYS | Approve this call and auto-approve every subsequent call to the same tool for the rest of this conversation |
| 僅此次允許 (Allow Once) | ALLOW_ONCE | Approve only this call |
| 拒絕 (Deny) | DENY_ONCE | Reject this call; you may add an optional reason so the agent knows why it was denied |
Auto-skip Rules
The SDK does not show the modal and handles the call directly in two cases:
alreadyAllowed: true— the backend marked it as already authorized (e.g. an earlier turn chose Allow for This Chat); autoALLOW_ONCE.- Allow for This Chat within this batch — once a tool has been granted Allow for This Chat in the same batch, later calls to the same toolset / toolName are auto
ALLOW_ALWAYS.
Usage
No configuration is needed — as long as the Bot Provider's Toolset has consent enabled, the SDK handles it automatically:
<Chatbot
config={{
apiKey: 'your-api-key',
botProviderEndpoint: 'https://api.asgard-ai.com/ns/{namespace}/bot-provider/{botProviderId}',
}}
customChannelId="your-channel-id"
/>
Two-step Deny
To avoid accidental clicks, Deny is a two-step submit:
- First 拒絕 (Deny) click — expands the reason input, and the button changes to 送出拒絕 (Send Deny).
- Second 送出拒絕 (Send Deny) click — actually submits the rejection (reason may be left empty).
Other Turns Are Refused While a Consent Is Pending
While the consent modal is up, the channel is paused on the server and accepts only a
consent reply; every other turn is rejected outright ("use RespondToolCallConsent"). So
sendMessage() and nudge() refuse locally, up front, throwing
ChannelAwaitingConsentError:
import { isChannelAwaitingConsentError } from "@asgard-js/core";
try {
await channel.sendMessage({ text: "never mind, different question" });
} catch (error) {
if (isChannelAwaitingConsentError(error)) {
// Answer the consent first — that is the only way forward
}
}
The "a run already holds the channel" guard cannot catch this: the consent frame arrives before the run's terminal event, so by the time the modal is on screen the run has already ended and the channel looks idle. Refusing locally is what keeps a doomed turn from first pushing an optimistic user bubble and then writing a backend error into the thread — a refused turn leaves no trace in the thread at all.
The only way out is replyToolCallConsents(); the guard releases itself once the consent is
answered. The error's processId is the paused batch's process id, but it is an empty
string after a rejoin (a prompt rebuilt from the backend's durable pause state deliberately
leaves it blank), so treat it as a diagnostic hint, never as a key.
With the built-in <Chatbot> you do not wire any of this: the composer and the Nudge entry
point are both locked while a consent is pending, so the user cannot reach them. The guard is
there for headless / custom shells.
If the consent reply itself fails, the pending state is restored and the modal stays put for a retry rather than leaving you with neither.
Theming
The modal follows the Chatbot's --asg-color-* design tokens by default, so no
extra setup is needed.
In addition, since 0.2.61, the modal's accent color (tool-name highlight,
primary button, focus ring) automatically follows the brand color you set via
theme — the resolution order is chatbot.primaryComponent.mainColor →
chatbot.mainColor → userMessage.backgroundColor, and the text painted on the
accent comes from the corresponding secondaryColor. So once you set a brand
color through theme, the consent modal usually needs no manual CSS-variable
overrides.
If you still need to override individual colors, set the CSS variables on any parent element:
.my-chatbot-wrapper {
--asgard-consent-modal-bg: #0f172a;
--asgard-consent-modal-accent: #6366f1;
--asgard-consent-modal-danger: #ef4444;
}
| CSS variable | Purpose | Fallback |
|---|---|---|
--asgard-consent-modal-bg | Modal background | --asg-color-surface |
--asgard-consent-modal-border | Border color | --asg-color-border |
--asgard-consent-modal-headline | Modal title color ("允許使用工具…?") | --asg-color-text-primary |
--asgard-consent-modal-title | Body / emphasis text color (tool info, deny reason, textarea) | --asg-color-text-primary |
--asgard-consent-modal-muted | Secondary text color | --asg-color-text-secondary |
--asgard-consent-modal-accent | Accent color (Allow for This Chat button) | --asg-color-primary |
--asgard-consent-modal-danger | Danger color (Deny button) | --asg-color-error |
--asgard-consent-modal-input-bg | Deny Reason input background | --asg-color-bg |