> ## Documentation Index
> Fetch the complete documentation index at: https://developers.firmly.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Wire up @firmly/ask-agent-sdk and render your first Ask Agent turn

<Info>
  This walks through building a minimal Ask Agent chat surface with
  `@firmly/ask-agent-sdk` on top of the [Vercel AI SDK UI](https://ai-sdk.dev)
  (`@ai-sdk/svelte`'s `Chat`). Every **SDK** call below uses the real exported
  function names and parameters — nothing here is illustrative pseudocode. A few
  host-supplied glue functions (e.g. `currentShopper`, `runMerchantHook`) are your
  own code, not SDK exports — each is marked inline where it appears.
</Info>

<Steps>
  <Step title="Install">
    <Note>
      `@firmly/ask-agent-sdk` isn't on npm yet — it's available on request. Contact
      Firmly for access to the package.
    </Note>

    The SDK ships as an ES module with a single **optional** peer dependency:

    ```json package.json theme={null}
    "peerDependencies": {
      "ai": "^6.0.0"
    }
    ```

    `ai` (v6) is only required once you actually construct a transport or chat —
    a host that only needs `defineCapabilities`/`createToolDispatcher` can import
    the SDK without it. For this Svelte walkthrough, also install the AI SDK's
    Svelte binding:

    ```bash theme={null}
    npm install @ai-sdk/svelte ai
    ```
  </Step>

  <Step title="Fetch the merchant's config">
    `fetchAskAgentConfig` calls the backend's `/api/embed/capabilities` endpoint
    and returns only client-safe data — the merchant's effective tool/component
    set and presentation config, never the system prompt or hook implementations.

    ```javascript theme={null}
    import { fetchAskAgentConfig } from '@firmly/ask-agent-sdk';

    const config = await fetchAskAgentConfig({ merchant: 'your-store.com' });
    // config.enabledComponents -> capability keys the merchant has turned on
    ```
  </Step>

  <Step title="Declare capabilities">
    Tell the SDK which UI components your host can render. It resolves that list
    down to the concrete tool names — `supportedTools` — that ride on every chat
    request. Your declaration can only narrow the merchant's enabled tool set,
    never widen it.

    ```javascript theme={null}
    import { defineCapabilities } from '@firmly/ask-agent-sdk';

    const { supportedTools } = defineCapabilities({
      components: config.enabledComponents
    });
    ```
  </Step>

  <Step title="Bootstrap the session">
    A standalone host running on the merchant's own origin needs a first-party
    browser-session token before it can call `/api/chat`. `createSession` mints
    or refreshes it over the shopper's existing session cookie — no API keys in
    the browser.

    ```javascript theme={null}
    import { createSession } from '@firmly/ask-agent-sdk';

    const { ensureSession } = createSession({ merchant: 'your-store.com' });
    await ensureSession();
    ```
  </Step>

  <Step title="Create the transport">
    `createAskAgentTransport` returns an AI SDK v6 `ChatTransport` that posts to
    `/api/chat` and injects `pageContext`/`shopperContext`/`supportedTools` into
    the request body on every turn.

    ```javascript theme={null}
    import { createAskAgentTransport } from '@firmly/ask-agent-sdk';

    const transport = createAskAgentTransport({
      api: '/api/chat',
      merchant: 'your-store.com',
      supportedTools,
      getPageContext,
      getShopperContext
    });
    ```

    `getPageContext` and `getShopperContext` are functions you supply, each
    returning a plain object (or a `Promise` of one) fetched fresh per turn —
    e.g. the current PDP/category the shopper is on, or `{ user, orders }` from
    your own session state.
  </Step>

  <Step title="Build the tool dispatcher">
    Some tools are **client-executing**: the agent asks a question only your
    host page can answer (because only the merchant's own JavaScript has the
    shopper's session), and your dispatcher handlers answer it.

    ```javascript theme={null}
    import { createToolDispatcher } from '@firmly/ask-agent-sdk';

    const dispatch = createToolDispatcher({
      handlers: {
        runDataHook,
        onCheckout,
        onBuyNow
      }
    });
    ```

    * **`runDataHook({ name, input })`** — runs the merchant's data-hook JS for
      the requested hook name and returns its JSON-serializable result; the SDK
      feeds it back to the model via `addToolResult`.
    * **`onCheckout(input)`** — fires when the agent calls `proceed_to_checkout`;
      swap your UI into checkout. The server tool already resolved, so this
      handler does **not** call `addToolResult`.
    * **`onBuyNow(input)`** — resolves a `buy_now` call (variant lookup +
      cart/checkout handoff) and returns output the SDK feeds back so the model
      can confirm the action.
  </Step>

  <Step title="Wire the chat">
    Drop the transport and dispatcher into `@ai-sdk/svelte`'s `Chat`:

    ```svelte theme={null}
    <script>
      import { Chat } from '@ai-sdk/svelte';
      import { stepCountIs, hasToolCall } from 'ai';
      import { shouldResumeAfterHook } from '@firmly/ask-agent-sdk';

      const chat = new Chat({
        transport,
        onToolCall: ({ toolCall }) =>
          dispatch({ toolCall, addToolResult: chat.addToolResult }),
        sendAutomaticallyWhen: ({ messages }) => shouldResumeAfterHook(messages),
        stopWhen: [stepCountIs(25), hasToolCall('suggest_followups')]
      });
    </script>
    ```
  </Step>
</Steps>

## Complete example

Putting the six pieces together in one component:

<CodeGroup>
  ```svelte quickstart.svelte theme={null}
  <script>
    import { Chat } from '@ai-sdk/svelte';
    import { stepCountIs, hasToolCall } from 'ai';
    import {
      fetchAskAgentConfig,
      defineCapabilities,
      createSession,
      createAskAgentTransport,
      createToolDispatcher,
      shouldResumeAfterHook
    } from '@firmly/ask-agent-sdk';

    const MERCHANT = 'your-store.com';

    const config = await fetchAskAgentConfig({ merchant: MERCHANT });
    const { supportedTools } = defineCapabilities({
      components: config.enabledComponents
    });

    const { ensureSession } = createSession({ merchant: MERCHANT });
    await ensureSession();

    function getPageContext() {
      return { url: window.location.href };
    }
    function getShopperContext() {
      return { user: currentShopper() }; // currentShopper: your own implementation — not part of the SDK
    }

    const transport = createAskAgentTransport({
      api: '/api/chat',
      merchant: MERCHANT,
      supportedTools,
      getPageContext,
      getShopperContext
    });

    const dispatch = createToolDispatcher({
      handlers: {
        runDataHook: ({ name, input }) => runMerchantHook(name, input), // runMerchantHook: your own implementation — not part of the SDK
        onCheckout: (input) => goToCheckout(input), // goToCheckout: your own implementation — not part of the SDK
        onBuyNow: (input) => resolveBuyNow(input) // resolveBuyNow: your own implementation — not part of the SDK
      }
    });

    const chat = new Chat({
      transport,
      onToolCall: ({ toolCall }) =>
        dispatch({ toolCall, addToolResult: chat.addToolResult }),
      sendAutomaticallyWhen: ({ messages }) => shouldResumeAfterHook(messages),
      stopWhen: [stepCountIs(25), hasToolCall('suggest_followups')]
    });
  </script>

  {#each chat.messages as message (message.id)}
    <!-- render message.parts with your own components -->
  {/each}
  ```
</CodeGroup>

<Note>
  `shouldResumeAfterHook` is the **hooks-only baseline** for `sendAutomaticallyWhen`
  — it resumes the turn once every data-hook tool call in the latest message has a
  result. Hosts that also intercept custom UI components or `buy_now` outside the
  dispatcher (as Firmly's own dogfood widget does) may need a richer resume
  predicate that additionally accounts for those tool calls.
</Note>

The SDK is framework-agnostic (built on AI SDK v6): the same `defineCapabilities`,
`createSession`, `createAskAgentTransport`, and `createToolDispatcher` calls work
unchanged with React's `useChat` or any other AI SDK UI binding — only the chat
hook itself differs.

<Tabs>
  <Tab title="Perfect for the headless SDK">
    You need the assistant to match your app's design system, render inline in a
    native (non-iframe) surface, or run inside a framework shell the drop-in
    embed can't reach.
  </Tab>

  <Tab title="Consider the drop-in embed instead">
    You want Ask Agent live in minutes with zero application code — see the
    script-tag option in the [overview](/ask-agent/overview).
  </Tab>
</Tabs>

## Next steps

<CardGroup cols={2}>
  <Card title="Reference" icon="book" href="/ask-agent/reference">
    Full reference for capabilities, transport, session, and the tool dispatcher.
  </Card>

  <Card title="Overview" icon="robot" href="/ask-agent/overview">
    How the headless SDK fits alongside the drop-in embed.
  </Card>
</CardGroup>
