@runtypelabs/persona-proxy
v4.23.2
Published
Proxy server for @runtypelabs/persona widget - handles flow configuration and forwards requests to AI backends.
Readme
Vanilla Agent Proxy
Proxy server library for @runtypelabs/persona widget. Handles flow and server-pinned agent configuration, CORS, feedback collection, WebMCP/client-tool forwarding, /resume continuations, and secure forwarding to Runtype.
Installation
npm install @runtypelabs/persona-proxyUsage
The proxy server handles server-side flow/agent configuration and forwards requests to Runtype. It mounts both the dispatch endpoint (default /api/chat/dispatch) and a matching child resume endpoint (/api/chat/dispatch/resume) so browser-executed LOCAL tools such as WebMCP page tools, ask_user_question, and suggest_replies can resume a paused execution. You can configure dispatch in five ways:
Option 1: Use default flow (recommended for getting started)
// api/chat.ts
import { createChatProxyApp } from '@runtypelabs/persona-proxy';
export default createChatProxyApp({
path: '/api/chat/dispatch',
allowedOrigins: ['https://www.example.com']
});Option 2: Reference a Runtype flow ID
import { createChatProxyApp } from '@runtypelabs/persona-proxy';
export default createChatProxyApp({
path: '/api/chat/dispatch',
allowedOrigins: ['https://www.example.com'],
flowId: 'flow_abc123' // Flow created in Runtype dashboard or API
});Option 3: Define a custom flow
import { createChatProxyApp } from '@runtypelabs/persona-proxy';
export default createChatProxyApp({
path: '/api/chat/dispatch',
allowedOrigins: ['https://www.example.com'],
flowConfig: {
name: "Custom Chat Flow",
description: "Specialized assistant flow",
steps: [
{
id: "custom_prompt",
name: "Custom Prompt",
type: "prompt",
enabled: true,
config: {
model: "meta/llama3.1-8b-instruct-free",
responseFormat: "markdown",
outputVariable: "prompt_result",
userPrompt: "{{user_message}}",
systemPrompt: "you are a helpful assistant, chatting with a user",
previousMessages: "{{messages}}"
}
}
]
}
});Option 4: Reference a Runtype agent ID
import { createChatProxyApp } from '@runtypelabs/persona-proxy';
export default createChatProxyApp({
path: '/api/chat/dispatch',
allowedOrigins: ['https://www.example.com'],
agentId: 'agent_abc123'
});Option 5: Define a server-pinned agent
import { createChatProxyApp } from '@runtypelabs/persona-proxy';
export default createChatProxyApp({
path: '/api/chat/dispatch',
allowedOrigins: ['https://www.example.com'],
agentConfig: {
name: 'Shopping Assistant',
model: 'gemini-3.7-flash',
systemPrompt: 'You are a concise shopping assistant.',
loopConfig: { maxTurns: 6 }
}
});Hosting on Vercel:
import { createVercelHandler } from '@runtypelabs/persona-proxy';
export default createVercelHandler({
allowedOrigins: ['https://www.example.com'],
flowId: 'flow_abc123' // Optional
});Configuration Options
| Option | Type | Description |
| --- | --- | --- |
| upstreamUrl | string | Runtype API endpoint (defaults to https://api.runtype.com/v1/dispatch) |
| apiKey | string | Runtype API key (defaults to RUNTYPE_API_KEY environment variable) |
| path | string | Proxy endpoint path (defaults to /api/chat/dispatch) |
| allowedOrigins | string[] | CORS allowed origins |
| requestGuard | ProxyRequestGuard | Optional authorization/rate-limit hook run before JSON parsing, feedback handlers, or upstream requests. Return a Response to deny. |
| maxRequestBodyBytes | number | Optional positive-integer UTF-8 JSON body limit for dispatch, resume, and feedback. Disabled by default. |
| flowId | string | Runtype flow ID to use |
| flowConfig | RuntypeFlowConfig | Custom flow configuration |
| feedbackPath | string | Message-feedback endpoint path. Default: /api/feedback. |
| onFeedback | (feedback) => Promise<void> \| void | Optional handler for upvote/downvote payloads. |
| previewOriginPattern | RegExp \| false | Additional dynamic preview-origin allowlist (defaults to https://*.vercel.app; disable with false). |
| agentId | string | Runtype agent ID to use. Mutually exclusive with flowId, flowConfig, and agentConfig. |
| agentConfig | AgentConfig | Server-pinned agent configuration. Mutually exclusive with flowId, flowConfig, and agentId. |
Use requestGuard to connect the proxy to your authenticated session and shared rate-limit store. Guard responses are returned with their status, body, and headers intact:
const app = createChatProxyApp({
maxRequestBodyBytes: 16 * 1024 * 1024,
requestGuard: async ({ request, kind }) => {
const user = await authenticate(request);
if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 });
const limit = await rateLimiter.check(`${user.id}:${kind}`);
if (!limit.allowed) {
return Response.json(
{ error: 'Too many requests' },
{ status: 429, headers: { 'Retry-After': String(limit.retryAfter) } }
);
}
}
});The body limit checks both a valid declared Content-Length and the actual UTF-8 byte count. Use a limit large enough for base64-encoded attachments.
WebMCP and built-in client tools
For flow-dispatch and server-agent requests, the proxy preserves clientTools[] from the widget payload and forwards them upstream so Runtype can register browser-local tools for that turn. Tool results are sent by the widget to ${path}/resume, and the proxy forwards that body to the upstream /resume endpoint using the same API key.
A non-server-agent route rejects a client-supplied agent with HTTP 400. Configure agentId or agentConfig to pin the agent on the server. If browser-selected agent definitions are intentional, send them to a separate backend that authenticates and authorizes that capability instead of relaying them on the proxy's API key.
Server-agent routes (agentId or agentConfig) ignore any client-supplied agent field. The browser can contribute messages, clientTools[], metadata, context, and inputs; the model, system prompt, tools, and loop config stay pinned on the server.
CORS behavior
- If
allowedOriginsis omitted or empty, the proxy reflects the request origin (or*). - If
allowedOriginsis set, exact matches are allowed. NODE_ENV=developmentreflects local dev origins even when they are not inallowedOrigins; an unsetNODE_ENVis treated as production.- Vercel preview deployments (
VERCEL_ENV=preview) and origins matchingpreviewOriginPatternare reflected so per-branch preview URLs work without enumerating them.
CORS is a browser policy, not authentication. Omitting allowedOrigins is permissive and does not prevent direct HTTP callers from using the proxy.
Feedback endpoint
messageActions feedback can POST to feedbackPath (default /api/feedback). The built-in handler validates type (upvote/downvote) and messageId, adds a timestamp, logs in development, and then calls onFeedback if provided.
Environment Setup
Add RUNTYPE_API_KEY to your environment. The proxy constructs the Runtype payload (including flow configuration/client tools) and streams the response back to the client.
Building
pnpm buildThis generates:
dist/index.js(ESM)dist/index.cjs(CJS)- Type definitions in
dist/index.d.ts
