npm package: @resolvekit/nextjs
Description: ResolveKit Next.js SDK with runtime parity and browser tool packs
Latest version: 1.0.4

# @resolvekit/nextjs

Universal Next.js SDK for ResolveKit

## Install

```bash
npm install @resolvekit/nextjs
```

## 1) Add Secure Server Routes (App Router)

### `app/api/resolvekit/client-token/route.ts`

```ts
import { createResolveKitClientTokenHandler } from "@resolvekit/nextjs/server";

const handler = createResolveKitClientTokenHandler({
  agentBaseUrl: process.env.RESOLVEKIT_AGENT_URL ?? "https://agent.resolvekit.app",
  resolveApiKey: () => process.env.RESOLVEKIT_API_KEY,
  authorizeRequest: async ({ request }) => {
    const origin = request.headers.get("origin");
    return origin === "https://app.example.com";
  },
  allowedOrigins: ["https://app.example.com"],
  // allowMissingOrigin: true // optional for trusted server-to-server traffic
  // forwardOriginHeader: false // optional for local dev if key origin allowlist blocks localhost
});

export const POST = handler;
```

### `app/api/resolvekit/actions/route.ts`

```ts
import { createResolveKitActionsProxyHandler } from "@resolvekit/nextjs/server";

const handler = createResolveKitActionsProxyHandler({
  targetBaseUrl: process.env.APP_BACKEND_URL ?? "http://localhost:8000",
  allowlist: [
    { path: "/v1/workflows/run", methods: ["POST"] },
    { path: "/v1/automation/trigger", methods: ["POST"] }
  ],
  authorizeRequest: async ({ request }) => {
    const origin = request.headers.get("origin");
    return origin === "https://app.example.com";
  },
  allowedOrigins: ["https://app.example.com"],
  forwardRequestHeaders: ["x-request-id"],
  resolveAuthHeader: () => {
    const token = process.env.APP_BACKEND_SERVICE_TOKEN;
    return token ? `Bearer ${token}` : undefined;
  }
});

export const POST = handler;
```

## 2) Configure Runtime (Client Component)

```tsx
"use client";

import {
  ResolveKitAction,
  ResolveKitDevtools,
  ResolveKitProvider,
  ResolveKitChat,
  ResolveKitWidget,
  createClientTokenAuthProvider,
  createRecommendedBrowserToolsPack,
  type ResolveKitConfiguration
} from "@resolvekit/nextjs";

const authProvider = createClientTokenAuthProvider({
  endpoint: "/api/resolvekit/client-token"
});

const browserTools = createRecommendedBrowserToolsPack({
  apiEndpoint: "/api/resolvekit/actions"
});

const config: ResolveKitConfiguration = {
  baseUrl: process.env.NEXT_PUBLIC_RESOLVEKIT_AGENT_URL ?? "https://agent.resolvekit.app",
  sdkVersion: "1.0.0",
  authProvider,
  functions: [
    ...browserTools.functions,
    {
      name: "get_local_time",
      description: "Returns current local time",
      parametersSchema: { type: "object", properties: {}, required: [] },
      requiresApproval: true,
      invoke: async () => ({ now: new Date().toISOString() })
    }
  ]
};

export default function ResolveKitHost() {
  return (
    <ResolveKitProvider configuration={config}>
      <ResolveKitWidget />
      <ResolveKitDevtools
        browserToolsOptions={{
          discoveryMode: "annotatedOnly",
          apiEndpoint: "/api/resolvekit/actions"
        }}
      />
      {/* Or render the headless/inline component if you need full custom layout */}
      {/* <ResolveKitChat /> */}
    </ResolveKitProvider>
  );
}
```

`ResolveKitWidget` ships as the default polished chat surface:

- Floating launcher with a Figma-style popup panel
- Theme defaults to `system` (inherits OS preference on first load)
- Optional single-click toggle to switch to the opposite theme (`light` <-> `dark`)
- Uses backend chat theme tokens (iOS parity)
- Inherits chat name and message placeholder from backend session/localization
- Shows typing bubbles while waiting for assistant response
- Renders suggested reply chips, richer tool-approval cards, and disabled attachment affordance
- Uses backend-provided tool-call explanations in approval cards
- Renders in a Shadow DOM root to isolate SDK styles from host app global CSS

`ResolveKitChat` now uses the same visual system as the widget, rendered inline inside its own Shadow DOM host so host page global styles cannot override it.

Key props:

- `showThemeToggle?: boolean` (default: `true`)
- `launcherLabel?: string` (default: `Need help?`)
- `position?: "bottom-right" | "bottom-left"` (default: `"bottom-right"`)

## Developer-Friendly Browser Tools

The recommended setup is now:

- `createRecommendedBrowserToolsPack(...)` for secure defaults
- `ResolveKitAction` or `useResolveKitActionProps(...)` to register UI affordances
- `ResolveKitDevtools` during development so you can see what the agent can currently discover

The recommended helper only exposes discovery-based browser actions plus `call_backend_api`. If you explicitly need raw selector tools like `highlight_element`, `click_element`, or `navigate_route`, use `createBrowserToolsPack(...)` instead.

### React Registration Helpers

Use `ResolveKitAction` when the SDK owns the element:

```tsx
<ResolveKitAction
  as="button"
  actionId="open-billing-modal"
  actionRole="dialog-trigger"
  description="Open the billing modal"
  type="button"
>
  Open billing
</ResolveKitAction>
```

Use `useResolveKitActionProps(...)` when you already have a component and only want props to spread:

```tsx
const billingLinkProps = useResolveKitActionProps({
  actionId: "go-to-billing",
  actionRole: "navigation",
  description: "Navigate to billing"
});

<Link {...billingLinkProps} href="/billing">
  Billing
</Link>
```

### Dev Overlay

Use `ResolveKitDevtools` in development to inspect the currently registered actions and routes:

```tsx
<ResolveKitDevtools
  defaultOpen
  browserToolsOptions={{
    discoveryMode: "annotatedOnly",
    apiEndpoint: "/api/resolvekit/actions"
  }}
/>
```

The overlay shows:

- current discovery mode
- current route
- currently discoverable registered actions
- currently discoverable registered routes

### Registration Modes

`createBrowserToolsPack()` supports two discovery modes:

- `discoveryMode: "open"`: default. The SDK can discover common visible controls such as buttons, links, and inputs.
- `discoveryMode: "annotatedOnly"`: hardened mode. The SDK only discovers and acts on elements or routes the app explicitly marks.

Recommended hardened setup:

```tsx
const browserTools = createRecommendedBrowserToolsPack({
  apiEndpoint: "/api/resolvekit/actions",
  // discoveryMode defaults to "annotatedOnly"
});
```

In `annotatedOnly` mode:

- `discover_page_actions` only returns marked elements and links
- `click_discovered_action`, `highlight_discovered_action`, and `navigate_discovered_route` only work on discovered ids from those marked elements
- raw `click_element` and `highlight_element` reject unannotated selectors
- raw `navigate_route` rejects routes that are not registered by an annotated link on the current page

Supported annotation attributes:

- `data-resolvekit-id`: stable developer-owned identifier and preferred selector anchor
- `data-resolvekit-role`: optional semantic hint for tooling and filtering
- `data-resolvekit-description`: optional human hint

Example:

```tsx
<button
  data-resolvekit-id="open-billing-modal"
  data-resolvekit-role="dialog-trigger"
  data-resolvekit-description="Open the billing modal"
>
  Open billing
</button>

<a
  data-resolvekit-id="go-to-billing"
  data-resolvekit-role="navigation"
  href="/billing"
>
  Billing
</a>
```

By default, discovery does not return `data-resolvekit-description` text back to the model. If you want that richer context, opt in explicitly:

```tsx
const browserTools = createBrowserToolsPack({
  apiEndpoint: "/api/resolvekit/actions",
  includeElementDescriptionsInDiscovery: true
});
```

## 3) Custom UI (Headless)

```tsx
"use client";

import { ResolveKitProvider, useResolveKit, type ResolveKitConfiguration } from "@resolvekit/nextjs";

function CustomChat() {
  const { state, sendMessage, approveToolCallBatch, declineToolCallBatch } = useResolveKit();

  return (
    <div>
      <h2>{state.chatTitle}</h2>
      <button onClick={() => void sendMessage("Hello")}>Send</button>
      {state.toolCallBatchState === "awaitingApproval" ? (
        <>
          <button onClick={() => void approveToolCallBatch()}>Approve</button>
          <button onClick={() => void declineToolCallBatch()}>Decline</button>
        </>
      ) : null}
    </div>
  );
}

export function ChatShell({ config }: { config: ResolveKitConfiguration }) {
  return (
    <ResolveKitProvider configuration={config}>
      <CustomChat />
    </ResolveKitProvider>
  );
}
```

## Security Defaults

- API key stays server-side.
- Browser receives short-lived client token from `/api/resolvekit/client-token`.
- Backend action tool is routed through an allowlisted proxy endpoint.
- Server helpers require `authorizeRequest` and `allowedOrigins` in production.
- Browser tools that mutate state remain approval-required; read-only discovery and highlight tools do not.
- Use `discoveryMode: "annotatedOnly"` if you want the browser agent limited to developer-registered UI affordances only.
- `createRecommendedBrowserToolsPack(...)` makes that hardened mode the easiest starting point.
- DOM-derived freeform descriptions are excluded from discovery by default; opt in with `includeElementDescriptionsInDiscovery: true` only when you explicitly want that extra model context.
- Device IDs stay in memory by default. Opt into `deviceIdPersistence: "localStorage"` only if you want sticky client identity.

## Transport

The SDK now communicates with ResolveKit over an HTTP session stream:

```text
Client -> Server: POST /v1/sessions/{id}/messages | POST /v1/sessions/{id}/tool-results
Server -> Client: GET /v1/sessions/{id}/events
```

The browser runtime and server transport share the same streamed HTTP protocol, so they can use HTTP/3 when the platform and backend negotiate it.

For server or SSR flows that need direct access to the transport layer, use `createResolveKitServerTransport` from `@resolvekit/nextjs/server`.

## Scripts

```bash
npm run test:run
npm run typecheck
npm run build
```

## Example App

See [`examples/next-app`](./examples/next-app) for a complete App Router integration that includes:

- `ResolveKitAction` and `useResolveKitActionProps(...)` registration helpers
- `ResolveKitDevtools` showing the current discoverable action surface
- discovery-first browser action demos
- `data-resolvekit-*` annotations for developer-registered browser actions
- discovery-based highlighting and clicking against registered actions
- route navigation between `/` and `/billing`
- button click automation to open/close modal flows
- backend action calls via `/api/resolvekit/actions` to `/api/demo/workflows/run`