Skip to Content
    QAFKA
    CTRL K
    CTRL K
    • Introduction
      • Quick Start
      • Configuration
      • React Native Widget
      • Theming
      • Context
      • Navigation
      • External Navigation
      • Handling Tools
      • Voice Chat
      • Sub-Projects
      • Error Handling
      • CLI
      • Dashboard
      • Invitations
      • Settings
        • Project
        • Overview
        • Conversations
        • Chat Test
        • Sub-Projects
        • Analysis
        • Configuration
        • Members
        • Documents
        • Tools
        • Action Logs
        • Navigation Rules
        • External Destinations
        • Chat Theme
        • API Keys
      • API Key Security
    • Introduction
      • Quick Start
      • Configuration
      • React Native Widget
      • Theming
      • Context
      • Navigation
      • External Navigation
      • Handling Tools
      • Voice Chat
      • Sub-Projects
      • Error Handling
      • CLI
      • Dashboard
      • Invitations
      • Settings
        • Project
        • Overview
        • Conversations
        • Chat Test
        • Sub-Projects
        • Analysis
        • Configuration
        • Members
        • Documents
        • Tools
        • Action Logs
        • Navigation Rules
        • External Destinations
        • Chat Theme
        • API Keys
      • API Key Security

    On This Page

    • How It Works
    • Execution Modes
    • Handling Tools in the SDK
    • addResponse Modes
    • Imperative loading pill
    • onStepCompleted
    • onFileUploadRequest
    • onExtractionResult
    • onActionResult
    • Custom Rendering
    • Eligibility
    Question? Give us feedback Edit this page 
    GuidesHandling Tools

    Handling Tools

    Tools are how the AI does things inside your app — fetch data, kick off backend actions, ask the user to upload a file, render a card. The product team defines tools from the dashboard (Dashboard › Tools); the SDK handles them at runtime via callbacks on the <Qafka /> widget.

    This page covers the runtime side: lifecycle, execution modes, and the callbacks you wire up in your React Native app.

    How It Works

    User message ↓ AI matches the message against your enabled tools (using description, tags, examples) ↓ Backend emits a tool_suggestions event with the matched tool + extracted parameters ↓ Based on the tool's execution mode: • custom → SDK calls onToolSuggested → your code runs → addResponse renders / replies • server → backend runs the tool → AI streams a bridge text now, the final answer next turn • custom-with-ai → SDK runs your code → posts the result back → AI streams the final answer

    For the AI to suggest a tool, it has to be enabled, the user’s message has to match its description, and any required context keys (configured per-endpoint in the dashboard) must be present in the runtime context.

    Execution Modes

    The execution mode is set per tool from the dashboard. It dictates where the tool runs and who writes the final reply — which in turn shapes how your SDK callback is expected to behave.

    ModeSDK callback firesYour addResponse shouldFinal reply written by
    custom (default)Yes — onToolSuggestedRender the result and close outYour app (the rendered UI itself is the reply)
    serverNo — backend handles it; SDK just streams a bridge sentencen/aAI, in a follow-up turn
    custom-with-aiYes — onToolSuggestedReturn the data with mode 'data' so the SDK posts it back to the backendAI, in a follow-up turn

    For a field-by-field walkthrough of how to configure each mode in the dashboard, see Dashboard › Tools › Execution Mode.

    Handling Tools in the SDK

    Wire onToolSuggested on the widget to receive matched tools at runtime:

    <Qafka onToolSuggested={async (tools, addResponse) => { const tool = tools[0] const { apiBaseUrl } = tool.customData ?? {} const res = await fetch(`${apiBaseUrl}${tool.endpoint.url}`) const data = await res.json() addResponse(data, tool) }} />

    tools is an array of matched tool definitions (with their dashboard config + extracted params). addResponse(data, tool, mode?) decides what happens with the result.

    Tip — central dispatcher pattern. Inlining one big switch statement scales badly past 3–4 tools. The Qafka CLI scaffolds a handlers.ts dispatcher with a typed ToolHandler per tool, generates parameter destructuring + addResponse payload skeletons that match each tool’s dashboard dataPath, and keeps everything in sync as you add tools. onToolSuggested then just delegates: onToolSuggested={(t, a) => handleToolSuggested(t, a, ctx)}.

    addResponse Modes

    The third argument controls what the SDK does with your call. Same semantics in text and voice chat:

    ModeUpdates UICommits to flowUse case
    'both' (default)✓✓The normal path — render the result and close the tool out.
    'ui'✓✗Optimistic / progressive render. Shows a partial card, doesn’t end the flow. You must call addResponse again later with 'both' or 'data' to finalize.
    'data'✗✓Skip the UI render, just commit. In custom-with-ai mode this posts the data back to the backend. In plain custom it just clears the loading pill / unmutes the mic.
    // 1) Optimistic render then real result addResponse({ skeleton: true }, tool, 'ui') const real = await fetchData() addResponse(real, tool, 'both') // 2) custom-with-ai — return data, let the AI write the prose addResponse({ items: results }, tool, 'data') // 3) Voice flow where you only need to drop the loading pill addResponse({}, tool, 'data')

    Imperative loading pill

    For async work that doesn’t fit inside onToolSuggested (e.g. user picks an option from a custom UI you rendered, then you fetch), drive the voice loading pill manually via the widget ref:

    qafkaRef.current?.setLoading(true, 'Fetching…') const data = await doWork() qafkaRef.current?.setLoading(false)

    See QafkaHandle for the full ref API.

    onStepCompleted

    Fires whenever the AI emits a step marker for a multi-step tool (configured under Steps in the dashboard):

    <Qafka onStepCompleted={({ tool, step, data, actionResults }) => { if (step === 'complete') { Toast.show('Booking confirmed!') } }} />

    actionResults lists any side-effect actions that ran with this step (e.g. confirmation email).

    onFileUploadRequest

    Fires when the AI decides the tool needs a file (tools with File Input configured). Open your own picker and call submit():

    <Qafka onFileUploadRequest={({ toolId, fileInput, submit, cancel }) => { ImagePicker.launchImageLibrary( { mediaType: 'photo' }, (res) => { const a = res.assets?.[0] if (!a) return cancel() submit({ uri: a.uri!, name: a.fileName!, type: a.type! }) } ) }} />

    fileInput carries the dashboard config (accept, maxSize, sources) so you can constrain the picker accordingly.

    onExtractionResult

    Fires when document extraction finishes (only for tools with Document Extractor configured):

    <Qafka onExtractionResult={({ toolId, data, status, incompleteFields }) => { if (status === 'incomplete') { // Ask the user to fill missing fields promptFor(incompleteFields) } else { saveExtraction(data) } }} />

    onActionResult

    Fires when backend actions (email, webhook) finish executing:

    <Qafka onActionResult={(results) => { results.forEach((r) => { if (r.data?.skipped) { // Action was skipped because its conditional firing rule didn't match — // not an error, just informational. r.data.reason holds the AI's rationale. return } if (!r.success) console.error(`${r.actionType} failed: ${r.message}`) }) }} />

    For tools with conditional actions, a result with success: true and data.skipped: true means the action was eligible to run but the AI’s evaluation of its condition (or the multi-target email’s targets) said “don’t fire.” Treat this as informational, not as a failure.

    Custom Rendering

    Each tool can declare a custom component name in UI Config › Item component. Map that name to a real React component via the widget’s components prop:

    <Qafka components={{ MyProductCard: ({ data, tool, theme, onAction }) => ( <Pressable onPress={() => onAction?.('view', data)}> <Image source={{ uri: data.image }} /> <Text>{data.name}</Text> </Pressable> ), }} />

    If the dashboard Item component value doesn’t match a registered key, the widget falls back to the default renderer for the chosen Response type (Card / List / Detail / Table). See Customizing Components for the prop signature.

    Eligibility

    For a tool to fire, three things have to be true:

    1. The tool is enabled in the dashboard
    2. The user’s message matches its description well enough that the AI picks it
    3. Any required context keys configured on the tool’s endpoint are present in the live context

    Tool support is also gated by your project’s subscription plan — if your plan doesn’t include tools, no matching happens at the backend.

    Last updated on June 3, 2026
    External NavigationVoice Chat

    MIT 2026 QAFKA