Skip to navigation

Validates a browser request and builds the call to forward. 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. 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() 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() does:

methodSend it withAnswer the browser with
chatrawPost()The response body, streamed through as it arrives
create_conversationcreateConversation(){ greeting, status, timeout }
chat_loglog(){ messages, timeout, last_activity }, with messages filtered by visibleMessages() and last_activity from lastActivity()
end_conversationend(){ 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

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;
}
}