# Configure TypeScript SDK

Configure is a personalization SDK for agents. It gives your agent user-approved context from other agents, app contexts, preferences, memories, and more over API, MCP, or tool calls.

Most projects should integrate Configure into an existing agent: add hosted Link or a Personalization entry in the browser, send the `configure:linked` token to your backend, expose Configure tools, route `configure_*` calls, and commit after read-backed turns. Use the packaged template only when you want a fresh Configure-backed chat shell.

Install and setup:

```bash
npx configure setup
npm install configure
```

Then prove the credentials work and generate the callback:

```bash
npx configure verify
npx configure add callback --framework next
npx configure add origin https://yourapp.com/auth/configure/callback
```

`verify` completes a real sign-in, exchanges the code, and reads a profile (`--offline` checks credentials and callback registration without a browser). `add callback` takes `next`, `express`, or `vite` and writes the callback route, the server-side exchange, and the sign-in button, keeping the client secret off the browser. The generated callback exchanges the single-use code once, recovers the PKCE verifier when `state` is missing, and hands off to the opener in a popup instead of navigating. `add origin` registers a deployed callback on the same client before you ship: it opens the dashboard to confirm the exact client and callback, since an `sk_` key cannot change an OAuth client, and registration is additive so `CONFIGURE_OAUTH_CLIENT_ID` never changes.

Choose **For my users** when adding Configure to a product you already ship. Choose your existing agent handle at setup; register a new handle only for a fresh agent shell. Setup opens Configure in your browser, creates production credentials, and writes `CONFIGURE_API_KEY`, `CONFIGURE_PUBLISHABLE_KEY`, `CONFIGURE_AGENT`, `CONFIGURE_OAUTH_CLIENT_ID`, and `CONFIGURE_OAUTH_CLIENT_SECRET` to `.env`. If all five already exist, reuse them and skip setup; if the OAuth pair is missing, rerun setup to add it.

The public server-side SDK shape is:

```ts
import { Configure } from "configure";

const configure = new Configure({
  apiKey: process.env.CONFIGURE_API_KEY,
  agent: process.env.CONFIGURE_AGENT,
});

const profile = configure.profile({ token });

// Patch your EXISTING model loop. Expose Configure tools alongside your own,
// then route configure_* calls to Configure. profile.tools() is Anthropic-native
// ({ name, description, input_schema }); wrap with toOpenAIFunctions() for OpenAI.
// executeTool accepts { name, arguments } or { name, input }.
const tools = [
  ...yourTools,
  ...profile.tools(),
];
const dispatch = (toolCall) =>
  toolCall.name.startsWith("configure_")
    ? profile.executeTool(toolCall)
    : executeYourTool(toolCall);

const response = await runYourModelLoop({ messages, tools, dispatch });

// Commit bounded turn evidence after a read-backed turn.
await profile.commit({ messages, response, memories: response.memoryCandidates });
```

Concrete OpenAI and Anthropic loops: <https://docs.configure.dev/guides/tool-calling>.

Use Configure tools as the normal personalization path. Configure does not own or mutate your system prompt. If your app deliberately supports a preloaded approved context slot, you can read explicit sections and format them there, while keeping Configure tools available for model-driven reads and searches:

```ts
const read = await profile.read({
  sections: ["identity", "preferences", "summary"],
});
const approvedContext = read.profile.format({ guidelines: false });
```

For concrete or source-specific questions, let the model call `configure_profile_search`, for example `source: "chatgpt", query: "*"`.

For tight prompt budgets, choose narrower optional `sections` and keep `profile.search()` / `configure_profile_search` available for concrete follow-up retrieval.

The stable default behavior remains tool-driven: expose `profile.tools()` and route `configure_*` calls through `profile.executeTool()`. When your hosted/product surface requested connector or action capabilities and the app supports them, add those tools explicitly. Tool visibility means app capability, not user authorization; `profile.executeTool()` and the backend still fail closed when the user is unlinked, disconnected, underscoped, missing permissions, unapproved, or needs recovery:

```ts
const tools = [
  ...yourTools,
  ...profile.tools({
    connectors: ["gmail", "calendar", "drive", "notion"],
    actions: ["email.send", "calendar.create_event"],
  }),
];
```

When a connector or action is unavailable, catch the structured Configure failure and send the user through the hosted connect, reconnect, permissions, or approval surface instead of inventing URLs or permission logic in the model. The model may see supported write capabilities, but final execution still requires a clear user request plus the relevant connection, permission, scope, approval, and runtime policy.

Verify the wiring without a model — execute the read tool directly. When the model calls `configure_profile_read` in your loop, that is the same dispatch:

```ts
const result = await profile.executeTool({
  name: "configure_profile_read",
  arguments: { sections: ["identity", "summary", "preferences", "imports"] },
});
```

For app-local users that have not linked a Configure identity yet, pass your stable user identifier as `externalId`:

```ts
const profile = configure.profile({ externalId: "customer-123" });
```

## Message-agent SSO

Every registered agent has one hosted sign-in URL. Send the `sign_in_url` returned by the agent API, or get the same value from `configure.auth.signInUrl()`:

```ts
await message.reply(`Connect your profile: ${configure.auth.signInUrl()}`);
```

Configure resolves the browser credential, branding, phone verification, consent, and connectors server-side. Multiple Gmail accounts work by default. New integrations do not pass a publishable key, delivery mode, or phone number in the URL.

For message channels that provide provider-signed sender binding or need a thread-specific completion webhook, use the advanced SDK helpers below.

```ts
const subjectKey = message.sender?.id || space.id;
const agentPhone = await smsProvider.currentPhone();

await configure.auth.registerMessageLine({
  channel: "sms",
  phone: agentPhone,
  label: "Primary SMS line",
  metadata: { provider: "your-sms-provider" },
});

const handoff = await configure.auth.createMessageSignInUrl({
  reason: "signin",
  channel: "sms",
  subject: {
    key: subjectKey,
    externalId: `sms:${subjectKey}`,
    senderId: message.sender?.id,
  },
  thread: {
    key: space.id,
    spaceId: space.id,
    messageId: message.id,
  },
  messageSenderProof: providerSignedMessageSenderProof,
  agentPhone,
  messageBody: "done",
  connectors: ["gmail", "calendar"],
  idempotencyKey: `${space.id}:${message.id}:signin`,
});

await message.reply(`Connect your profile: ${handoff.url}`);
```

This advanced path binds the provider's current return line and signed sender evidence. Register `agentPhone` before generating a managed URL. `createMessageSignInUrl()` falls back to the canonical hosted URL when Configure cannot verify sender proof. Application code always sends the returned URL unchanged.

On each inbound message, resolve the best Configure identity. A linked user returns a token; everyone else keeps working with your app-local `externalId`:

```ts
const subjectKey = message.sender?.id || space.id;
const stored = await store.get(subjectKey);

const identity = await configure.auth.resolveMessageIdentity({
  externalId: `sms:${subjectKey}`,
  token: stored?.configureToken,
  phoneCandidates: [
    message.sender?.phone,
    message.sender?.address,
    message.sender?.id,
    space.phone,
  ].filter((value): value is string => Boolean(value)),
});

if (identity.token && identity.token !== stored?.configureToken) {
  await store.save(subjectKey, {
    configureToken: identity.token,
    configureUserId: identity.userId,
  });
}
```

For existing Better Auth apps, pass `configureBetterAuthOAuthProvider()` into Better Auth's Generic OAuth plugin:

```ts
import { configureBetterAuthOAuthProvider } from "configure";
```

Use `configure.auth.signInUrl({ returnTo, state, ... })`, `auth.allowSignInReturnTo()`, and `auth.exchangeSignInCode()` when Configure is an account connection inside an already signed-in app session.

`baseUrl` is only for internal staging/local development and advanced deployments; it should not appear in the normal production path.

Default model tools are only:

- `configure_profile_read`
- `configure_profile_search`
- `configure_profile_remember`

Everything else is explicit app capability. Connector tools, action tools, utility web tools, raw file tools, and hosted UI tool helpers are optional surfaces; expose only the categories your runtime actually supports, then let `profile.executeTool()` enforce user state and permissions.

`profile.search()` returns compact attributed hits by default. Compact hits omit raw CFS paths and provenance; pass `detail: "full"` when an inspector or admin flow needs safe metadata such as `path`, `markers`, `provenance`, and `updated_at`.

Use `sections: ["imports"]` or `profile.search({ query: "*", source: "chatgpt" })` for user-directed ChatGPT/Claude/etc. imported memories. Use `integrations` for connected tools such as Gmail, Outlook, Calendar, Drive, and Notion.

Outlook-aware runtimes opt in with `connectors: ["outlook"]`. This adds `configure_email_search`; omitting `provider` and `account` searches every permitted Gmail and Outlook account. Gmail-only runtimes keep the existing `configure_gmail_search` and email-send schema.

Connector and action tools are enabled explicitly when the hosted/product surface requested those capabilities and the app supports them:

```ts
profile.tools({
  connectors: ["gmail", "calendar", "drive", "notion"],
  actions: ["email.send", "calendar.create_event"],
});
```

You can also enable only the action subset your app supports. Let execution enforce linked state, connector state, permissions, scopes, and any required approval flow:

```ts
profile.tools({
  connectors: ["gmail"],
  actions: ["email.send"],
});
```

Utility web tools are advanced runtime capabilities:

```ts
profile.tools({
  advanced: { utilitySearch: true },
});
```

Raw file path access is advanced and lives under `configure.files.*`. Raw agent filesystem APIs are not part of the public default SDK shape.

Bulk historical/onboarding backfill is separate from runtime `profile.commit()`:

```ts
const job = await configure.importProfiles({
  mode: "backfill",
  users: [
    {
      externalId: "customer-123",
      profile: { preferences: ["Prefers concise replies."] },
      conversations: [{
        id: "thread-1",
        messages: [{ role: "user", content: "I usually fly out of SFO." }],
      }],
    },
  ],
});

const status = await configure.importJobs.get(job.id);
```

Import is server-side only, requires an `sk_` key, and is not exposed as a model-facing tool.

## Browser linking and components

Production browser integrations should use the hosted script:

```html
<script src="https://configure.dev/js/configure.js"></script>
```

`Configure.link()` handles user-present identity, seeding, consent, iframe isolation, resizing, and the `configure:linked` event. The host sends the returned agent-scoped token to its backend, then the backend uses `configure.profile({ token })`.

For chat inputs, `Configure.personalizationButton()` renders a compact `+` menu entry with the canonical Personalization toggle. The entry should trigger Configure, while `linkEl` mounts Configure Link in a dismissible inline chat panel. Let the panel span the assistant message lane (`width: min(100%, var(--chat-max, 640px))`) and do not cap the host at 420px or mount it inside the composer row. This is the default placement for chat products — not a settings page or a standalone chat-bar button. The `onEvent` callback exposes the agent-scoped token at `event.payload.token`; send only that token to your backend:

```js
Configure.personalizationButton({
  el: "#configure-entry",
  linkEl: "#configure-link-host",
  publishableKey: "pk_...",
  agent: "your-agent",
  displayName: "Your Agent",
  font: "Inter, -apple-system, BlinkMacSystemFont, sans-serif",
  onImage: () => openImagePicker(),
  onFile: () => openFilePicker(),
  onEvent(event) {
    if (event.type === "configure:personalization-open") {
      showInlineConfigurePanel();
      return;
    }
    if (event.type === "configure:linked") {
      hideInlineConfigurePanel();
      fetch("/api/configure/session", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ token: event.payload.token }),
      });
    }
  },
});
```

`displayName` is cosmetic UI copy. The `agent` handle is the identity used for tokens, storage, permissions, and attribution.

Images and Files stay host-owned; the helper also emits `configure:image-select` and `configure:file-select`. (The declarative `data-configure-link` trigger instead exposes the token at `event.detail.token` — use it only for settings pages or static buttons.)

If your chat UI already has an inline integrations list instead of a `+` menu, render Configure as a rounded lockup button:

```js
Configure.personalizationButton({
  el: "#configure-integration",
  publishableKey: "pk_...",
  agent: "your-agent",
  displayName: "Your Agent",
  variant: "integration",
});
```

Never expose `sk_` keys in browser code. Browser code uses publishable keys (`pk_...`); the model receives Configure tool results or formatted approved profile context, not Link tokens or user IDs.

When Configure is the auth handoff for your app, use the hosted first-party surface instead of building a custom OTP flow:

```js
Configure.signInWithPopup({
  publishableKey: "pk_...",
  agent: "your-agent",
  displayName: "Your Agent",
  returnTo: "https://app.example/auth/configure/callback",
  state: "opaque",
  fallback: "redirect",
});
```

Allowlist `returnTo` with `POST /v1/auth/sign-in/return-destinations` using your `sk_` key, then exchange the returned `cfgsic_...` code server-side with `POST /v1/auth/sign-in/exchange`. Store the returned agent-scoped token in your app session and pass it into later inline Configure surfaces with `Configure.link({ token, userId, ... })` so the user does not repeat OTP. Use the popup path when preserving desktop app context matters; redirect to `https://accounts.configure.dev/?pk=...&agent=...&return_to=...` for mobile, popup-blocked browsers, or simpler auth routes.

Raw web components are included for local labs and advanced self-hosted surfaces:

```ts
import "configure/components";
```

The browser bundle is also packaged at `configure/components/cdn`, and the narrow `configure/browser` entry point exists for browser bundlers. The hosted script remains the recommended production path.
