> This page is for Server SDKs.

> Fetch clean Markdown by appending `.md` to any page URL under https://signalwire.com/docs or requesting it with the HTTP header `Accept: text/markdown`. The root index at https://signalwire.com/docs/llms.txt lists the available documentation indexes.

# prepare

> Validate a browser request and build the call to the chat service.

[chatgateway]: /docs/server-sdks/reference/typescript/agents/chat-gateway

[rawpost]: /docs/server-sdks/reference/typescript/agents/ai-chat-client/raw-post

[router]: /docs/server-sdks/reference/typescript/agents/chat-gateway/router

[readhandle]: /docs/server-sdks/reference/typescript/agents/chat-gateway/read-handle

[visiblemessages]: /docs/server-sdks/reference/typescript/agents/chat-gateway/visible-messages

[lastactivity]: /docs/server-sdks/reference/typescript/agents/chat-gateway/last-activity

[createconversation]: /docs/server-sdks/reference/typescript/agents/ai-chat-client/create-conversation

[log]: /docs/server-sdks/reference/typescript/agents/ai-chat-client/log

[end]: /docs/server-sdks/reference/typescript/agents/ai-chat-client/end

Validates a browser request and builds the call to forward. [`router()`][router] uses it; call it
yourself to serve the gateway from a framework other than Hono. It checks the key, then the origin.

Everything the browser could use to widen its own access is refused or replaced here: `method` must
be one of `start`, `chat`, `log`, or `end` (default `chat`), the conversation comes from a signed
handle, and `config_url` is the gateway's. The one field forwarded is `user_meta_data`, after the
checks described under [`ChatGateway`][chatgateway]. A chat message over 8 KiB of UTF-8 is refused
with `413` before a conversation is created or a turn is counted.

`prepare()` doesn't check the size of the request body. [`router()`][router] limits it to 64 KiB
before parsing; apply a limit of your own when you call `prepare()` directly.

A `log` or `end` request needs a handle. A `start` or `chat` without one creates a conversation,
which counts against `maxNewConversations`; a `chat` counts against `maxTurns`.

## Parameters

**`body`** `Record<string, unknown>` — required

The browser's request body: `{ method?, handle?, message?, user_meta_data? }`.

---

**`opts`** `{ origin?: string | null; key?: string | null }` — required

The request's credentials.

---

**`opts.origin`** `string | null`

The `Origin` header.

---

**`opts.key`** `string | null`

The presented publishable key.

---

## Returns

`[string, Record<string, unknown>, string | null]` -- a tuple of `[method, params, mintedHandle]`.

`method` is the service method to call: `create_conversation` for `start`, `chat` for `chat`,
`chat_log` for `log`, and `end_conversation` for `end`. `params` are its parameters. `mintedHandle`
is the new handle when this call created the conversation, and `null` otherwise; return it to the
browser.

Send each method the way [`router()`][router] does:

| `method`              | Send it with                                 | Answer the browser with                                                                                                                                              |
| --------------------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `chat`                | [`rawPost()`][rawpost]                       | The response body, streamed through as it arrives                                                                                                                    |
| `create_conversation` | [`createConversation()`][createconversation] | `{ greeting, status, timeout }`                                                                                                                                      |
| `chat_log`            | [`log()`][log]                               | `{ messages, timeout, last_activity }`, with `messages` filtered by [`visibleMessages()`][visiblemessages] and `last_activity` from [`lastActivity()`][lastactivity] |
| `end_conversation`    | [`end()`][end]                               | `{ status: 'ended' }`                                                                                                                                                |

Never relay a `chat_log` response as it comes back. The full log carries the system prompt and
tool traffic, which the browser must not see.

## Throws

`GatewayRejection` when the request is refused: `401` for a bad key, `403` for a disallowed origin
or a bad handle, `400` for a malformed request, `413` for an oversized message or metadata, and
`429` when a cap was reached.

## Examples

```typescript
import { AIChatClient, ChatGateway, GatewayRejection } from '@signalwire/sdk';

const gateway = new ChatGateway({
  configUrl: 'https://bayview-taxi.example.com/dispatch',
  key: '<YOUR_PUBLISHABLE_KEY>',
  allowedOrigins: ['https://bayviewtaxi.example.com'],
});
const client = new AIChatClient({ space: 'your-space' });

async function forward(
  method: string,
  params: Record<string, unknown>,
  headers: Headers,
): Promise<Response> {
  const id = params.id as string;
  const timeout = gateway.effectiveTimeout;
  if (method === 'end_conversation') {
    await client.end(id);
    return Response.json({ status: 'ended' }, { headers });
  }
  if (method === 'create_conversation') {
    const info = await client.createConversation(id, {
      configUrl: params.config_url as string,
      timeout: params.conversation_timeout as number | undefined,
      userMetadata: params.user_meta_data as Record<string, unknown> | undefined,
    });
    return Response.json({ greeting: info.initialMessage, status: info.status, timeout }, { headers });
  }
  if (method === 'chat_log') {
    // Filter the log: the full chat_log isn't for the browser.
    const log = await client.log(id);
    return Response.json(
      {
        messages: ChatGateway.visibleMessages(log.messages),
        timeout,
        last_activity: ChatGateway.lastActivity(log.messages),
      },
      { headers },
    );
  }
  // chat: stream the reply through as it arrives.
  const upstream = await client.rawPost(method, params);
  headers.set('Content-Type', 'application/json');
  return new Response(upstream.body, { status: 200, headers });
}

export async function relay(
  body: Record<string, unknown>,
  origin: string | null,
  key: string | null,
): Promise<Response> {
  try {
    const [method, params, minted] = gateway.prepare(body, { origin, key });
    const headers = new Headers();
    if (minted) headers.set('X-Chat-Handle', minted);
    return await forward(method, params, headers);
  } catch (err) {
    if (err instanceof GatewayRejection) {
      return Response.json({ error: err.reason }, { status: err.status });
    }
    throw err;
  }
}
```