Tool handlers & deep links
The bot has two kinds of tools.
- Server-side catalog tools (
search_catalog,get_product,find_similar_products,compare_products,list_subcategories,list_top_categories, site-nav resolution,end_session) run entirely on the backend. The bot speaks the result; your app does nothing. - Client-executed actions (open a product, add to cart, apply a filter, …) are pushed to the SDK as an
actionmessage. The SDK runs your handler or fires a deep link, then sends theaction_ackback to the backend for you.
The mobile profile has no DOM to manipulate, so UI actions map to either a deep link (navigation) or a native callback (mutate your cart, set a filter). You register these; the SDK wires the acknowledgement.
Register handlers and deep links
- React Native
- Flutter
// Native callback — mutate the cart, apply a filter, etc.
client.registerToolHandler('apply_filter', (args) => {
productList.setFilter(args);
return { success: true };
});
// Deep link — navigate via your router. Returns a route URI.
client.registerDeepLinkMap('open_product', ({ id }) => `myapp://product/${String(id)}`);
client.registerDeepLinkMap('view_cart', () => 'myapp://cart');
Deep links open through React Native Linking by default — override with a deepLinkOpener if you route
differently.
// Native callback — returns a result map.
client.registerToolHandler('apply_filter', (args) async {
productList.setFilter(args);
return {'success': true};
});
// Deep link — return a route URI; emitted on session.deepLinks.
client.registerDeepLinkMap('open_product', (args) => 'myapp://product/${args['id']}');
client.registerDeepLinkMap('view_cart', (args) => 'myapp://cart');
Listen to the resolved navigations on session.deepLinks:
session.deepLinks.listen((d) => router.go(d.uri));
The action table
Each action the backend can push, and how to map it on mobile. This is the authoritative mapping from the
wire protocol §9.
action | Mobile mapping |
|---|---|
open_product / navigate / open_category / view_cart / open_checkout | deep link (registerDeepLinkMap) → your route; fallback native callback |
add_to_cart / remove_from_cart / update_cart_qty | native callback (your cart API); no handler → { ok: false, error: "no_handler" } |
apply_filter / filter_by_attribute / clear_filter | native callback (your product list) |
highlight / scroll_to | native callback, or a no-op ok: true if the list is not addressable |
show_toast | native callback (snackbar) |
set_delivery_method / set_payment_method / update_order_note / confirm_order | native callback if implemented, else disabled (user_must_confirm) |
submit_form | server-side plugin callback (unchanged), or a native override |
open_popup / switch_language | native callback, or disabled |
no_handler — safe degradation
An action with no registered handler never crashes your app. The SDK auto-replies:
{ "success": false, "error": "no_handler" }
and the bot adapts — for example, it stops offering to do that action and steers the conversation
elsewhere. You only need to register the actions your app actually supports. (The handler-result key the SDK
sends back is success on the client side; on the wire it is reflected into the action_ack ok field — see
§7.)
You can also declare your capabilities up front so the bot only offers what you can execute — the SDK sends
the set of registered handler names in the hello.capabilities frame (see
§10).
How an action flows
bot decides to open a product
│
▼
server executes the tool, pushes: { "type":"action", "action":"open_product", "action_id":"…", "params":{ "id": 42 } }
│
▼
SDK looks up "open_product"
├─ deep-link map → resolve URI → open via Linking / emit on deepLinks
└─ tool handler → run your callback → get a result
│
▼
SDK sends: { "type":"action_ack", "action_id":"…", "ok": true, "result": {…} }
│
▼
(no handler registered) → SDK sends ok:false, error:"no_handler"; bot adapts
See also
- Context enrichment — tell the bot what's on screen so it picks better actions.
- Wire protocol §6 — the
actionandtool_callframes.