REFERENCE
SDK Demos
How to use @asgard-js/core and @asgard-js/react, with demos you can try on the page.
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_ALWAYSApprove this call and auto-approve every subsequent call to the same tool for the rest of this conversation 僅此次允許 (Allow Once) ALLOW_ONCEApprove only this call 拒絕 (Deny) DENY_ONCEReject 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()andnudge()refuse locally, up front, throwingChannelAwaitingConsentError: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
}
}Why a local guard is neededThe "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'sprocessIdis 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 viatheme— the resolution order ischatbot.primaryComponent.mainColor→chatbot.mainColor→userMessage.backgroundColor, and the text painted on the accent comes from the correspondingsecondaryColor. So once you set a brand color throughtheme, 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-bgModal background --asg-color-surface--asgard-consent-modal-borderBorder color --asg-color-border--asgard-consent-modal-headlineModal title color ("允許使用工具…?") --asg-color-text-primary--asgard-consent-modal-titleBody / emphasis text color (tool info, deny reason, textarea) --asg-color-text-primary--asgard-consent-modal-mutedSecondary text color --asg-color-text-secondary--asgard-consent-modal-accentAccent color (Allow for This Chat button) --asg-color-primary--asgard-consent-modal-dangerDanger color (Deny button) --asg-color-error--asgard-consent-modal-input-bgDeny Reason input background --asg-color-bg