Actions are the API operations you've registered for the agent under [Actions](/actions). The actions adapter is how they run: it's the code in your page that makes the request. The assistant has no credentials of its own, so every action runs in the user's own browser session, and the agent can do nothing the user couldn't.

## How an action runs

1. The agent decides to call an action. It calls it as a tool named `action__<slug>`, where the slug comes from your catalog.
2. If the action needs approval, the assistant asks the user first, in its own card. Nothing reaches your adapter until they approve.
3. Teaflask builds the request from your catalog entry, an `ActionIntent`, and sends it to the page. The method and path come from the catalog. The agent supplies only argument values, which are checked against the action's schemas.
4. Your adapter makes the request to your API.
5. The response's status and body go back to the agent. With the `cookies` and `headers` kinds, a `404` or `422` counts as your API's answer, not a failure, so the agent can explain it to the user. Bodies over about 16,000 characters are cut short.

The intent never contains an origin. The request goes wherever your adapter sends it and nowhere else.

## Choose a kind

| Kind      | Use it when your API…                       | The package                                            |
| --------- | ------------------------------------------- | ------------------------------------------------------ |
| `cookies` | authenticates with a session cookie         | builds the URL and fetches with the browser's cookies  |
| `headers` | needs an `Authorization` header or similar  | builds the URL and fetches with the headers you return |
| `request` | is best reached through your own API client | hands you the intent; you make the request             |

Prefer `cookies` or `headers` when either fits. The package reads the response itself, so the agent gets your API's real status and error body. A `request` adapter reports only what you return or throw.

### Cookies

```ts
import type { ActionsAdapter } from "@teaflask/assistant";

export const actionsAdapter: ActionsAdapter = {
  kind: "cookies",
  baseUrl: "https://app.example.com/api",
};
```

The package fetches `baseUrl` plus the action's path with `credentials: "include"`. If your API is on a different origin from the page, it has to allow credentialed CORS from the page's origin, and your cookie settings, including `SameSite`, have to let the browser send the cookie.

### Headers

```ts
import type { ActionsAdapter } from "@teaflask/assistant";

export const actionsAdapter: ActionsAdapter = {
  kind: "headers",
  baseUrl: "https://api.example.com/v1",
  getHeaders: async () => ({
    Authorization: `Bearer ${await getAccessToken()}`,
  }),
};
```

`getAccessToken` stands for your own auth code. `getHeaders` runs before every request and receives the intent. It can be async, so it can refresh a token. Its headers override the package's `Content-Type`, and if it throws, the action is reported as failed.

This kind also suits a same-origin cookie session that needs an extra header, such as a CSRF token: the browser sends the cookie and you return the header. It doesn't send cookies to another origin. For a cross-origin API that needs cookies and custom headers together, use `request`.

### Your own client

```ts
import type { ActionIntent, ActionsAdapter } from "@teaflask/assistant";

export const actionsAdapter: ActionsAdapter = {
  kind: "request",
  async execute(intent: ActionIntent) {
    const response = await apiClient.request({
      method: intent.method,
      url: expandPath(intent.pathTemplate, intent.pathParams),
      params: intent.query,
      data: intent.body,
    });
    return response.data;
  },
};
```

`apiClient` stands for your authenticated client and `expandPath` for a helper that fills each `{name}` in the path with its URL-encoded value.

Return the parsed response body. Whatever `execute` resolves with reaches the agent as a success with status 200. To report a failure, throw.

That makes one thing important: a client that resolves on error responses will tell the agent a failed action succeeded, and the agent will tell your user so. If your client doesn't throw on non-2xx responses, check the status in `execute` and throw.

The agent gets more to work with if what you throw carries your API's answer. An error with a numeric `status` and an `error` object holding a string `code`, plus optionally `details` as a list of `{ path, message }` problems, reaches the agent with that status, code and field problems. Then it can tell the user which field to fix. A plain `Error` reaches it as its message alone.

## Give the assistant the adapter

Define the adapter once, at module scope, and pass it to the assistant you mounted in your app shell:

```tsx
<TeaflaskAssistant publishableKey="pk_live_…" actionsAdapter={actionsAdapter}/>
```

If you mount a `TeaflaskAssistantProvider` yourself, for a full page or your own interface, pass it there instead. On the script tag, it's a property on the element:

```html
<script type="module">
  document.querySelector("teaflask-assistant").actionsAdapter = {
    kind: "cookies",
    baseUrl: "https://app.example.com/api",
  };
</script>
```

If your page sends a Content Security Policy, add your API's origin to `connect-src`.

## What the intent contains

```ts
interface ActionIntent {
  action: { slug: string; title: string };
  method: "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE";
  pathTemplate: string; // "/orders/{order_id}"
  pathParams: Record<string, unknown>; // { order_id: "ord_123" }
  query: Record<string, unknown>;
  body?: unknown;
}
```

The `cookies` and `headers` kinds turn it into a request for you:

- They fill the path's placeholders with URL-encoded values.
- They add each query value as its own parameter. Strings, numbers and booleans are sent as text, arrays and objects as JSON text, and `null` or missing values are left out.
- They send the body as JSON.

A `request` adapter does all of that itself.

## Without an adapter

An assistant with no adapter still works. It answers questions and can still navigate and read the page if those are enabled. The agent knows it can't run actions on this page, so it asks the user to do the step themselves instead of trying and failing.

## More resources

- [Actions](/actions) – registering actions, their schemas and their approval policies
- [Tool views](/tool-views) – drawing an action's result with your own components
- [Identity](/identity) – who the user is, a separate question from what they may do
- [Install](/install) – passing settings on the npm package and the script tag, and the CSP entries
