Installation
Install the SDK via your preferred package manager:
npm install @modelriver/client# oryarn add @modelriver/client# orpnpm add @modelriver/clientOr use the CDN for quick prototyping:
1<script src="https://cdn.modelriver.com/client/latest/modelriver.min.js"></script>How It Works
- Your backend calls the ModelRiver async API (e.g.
/api/v1/ai/async) to start a background AI request. - ModelRiver returns an async response with
channel_id, a short‑lived one‑timews_token, and WebSocket connection details. - Your frontend uses this SDK to connect via WebSocket using
channel_id+ws_tokenand receive streaming responses. - The SDK handles heartbeats, channel joins, and automatic reconnection for transient network issues (while the page is open).
- For page refresh recovery, use the persistence + reconnect helpers (
persist,hasPendingRequest,reconnect,reconnectWithBackend) together with your backend/api/v1/ai/reconnectendpoint.
Frontend Your Backend ModelRiver │ │ │ │ 1. Request AI │ │ │───────────────────>│ │ │ │ 2. POST /api/v1/ai/async │ │ │───────────────────>│ │ │ │ │ │ 3. channel_id, │ │ │ ws_token, │ │ │ websocket_url │ │ │<───────────────────│ │ 4. Return token │ │ │<───────────────────│ │ │ │ │ │ 5. SDK connects via WebSocket │ │─────────────────────────────────────────>│ │ │ │ │ 6. AI response streamed │ │<─────────────────────────────────────────│Quick Start
1. Get a token from your backend
Your backend makes the async request and returns the token:
1// Frontend: call your backend2const response = await fetch('/api/ai/chat', {3 method: 'POST',4 headers: { 'Content-Type': 'application/json' },5 body: JSON.stringify({ message: 'Hello AI' }),6});7 8const { ws_token } = await response.json();2. Connect with the SDK
1import { ModelRiverClient } from '@modelriver/client';2 3const client = new ModelRiverClient({4 baseUrl: 'wss://api.modelriver.com/socket',5});6 7// Listen for responses8client.on('response', (data) => {9 console.log('AI Response:', data.data);10});11 12client.on('error', (error) => {13 console.error('Error:', error);14});15 16// Connect with the token17client.connect({ wsToken: ws_token });Framework Adapters
The SDK includes first-class adapters for popular frameworks.
React
1import { useModelRiver } from '@modelriver/client/react';2 3function ChatComponent() {4 const { 5 connect, 6 response, 7 error, 8 isConnected, 9 steps 10 } = useModelRiver({11 baseUrl: 'wss://api.modelriver.com/socket',12 });13 14 const handleSend = async () => {15 const { ws_token } = await yourAPI.createRequest(message);16 connect({ wsToken: ws_token });17 };18 19 return (20 <div>21 <button onClick={handleSend} disabled={isConnected}>22 Send23 </button>24 25 {/* Progress steps */}26 {steps.map((step) => (27 <div key={step.id} className={step.status}>28 {step.name}29 </div>30 ))}31 32 {/* Response */}33 {response && <pre>{JSON.stringify(response.data, null, 2)}</pre>}34 35 {/* Error */}36 {error && <p className="error">{error}</p>}37 </div>38 );39}Vue
1<script setup>2import { useModelRiver } from '@modelriver/client/vue';3 4const { connect, response, error, isConnected, steps } = useModelRiver({5 baseUrl: 'wss://api.modelriver.com/socket',6});7 8async function handleSend() {9 const { ws_token } = await yourAPI.createRequest(message);10 connect({ wsToken: ws_token });11}12</script>13 14<template>15 <button @click="handleSend" :disabled="isConnected">Send</button>16 17 <div v-for="step in steps" :key="step.id" :class="step.status">18 {{ step.name }}19 </div>20 21 <pre v-if="response">{{ response.data }}</pre>22 <p v-if="error" class="error">{{ error }}</p>23</template>Angular
1import { Component } from '@angular/core';2import { ModelRiverService } from '@modelriver/client/angular';3 4@Component({5 selector: 'app-chat',6 providers: [ModelRiverService],7 template: `8 <button (click)="send()" [disabled]="modelRiver.isConnected">Send</button>9 10 <div *ngFor="let step of modelRiver.steps$ | async" [class]="step.status">11 {{ step.name }}12 </div>13 14 <pre *ngIf="modelRiver.response$ | async as res">{{ res.data | json }}</pre>15 <p *ngIf="modelRiver.error$ | async as err" class="error">{{ err }}</p>16 `,17})18export class ChatComponent {19 constructor(public modelRiver: ModelRiverService) {20 this.modelRiver.init({ baseUrl: 'wss://api.modelriver.com/socket' });21 }22 23 async send() {24 const { ws_token } = await this.api.createRequest(message);25 this.modelRiver.connect({ wsToken: ws_token });26 }27}Svelte
1<script>2 import { createModelRiver } from '@modelriver/client/svelte';3 4 const { response, error, isConnected, steps, connect } = createModelRiver({5 baseUrl: 'wss://api.modelriver.com/socket',6 });7 8 async function send() {9 const { ws_token } = await api.createRequest(message);10 connect({ wsToken: ws_token });11 }12</script>13 14<button on:click={send} disabled={$isConnected}>Send</button>15 16{#each $steps as step}17 <div class={step.status}>{step.name}</div>18{/each}19 20{#if $response}21 <pre>{JSON.stringify($response.data, null, 2)}</pre>22{/if}23 24{#if $error}25 <p class="error">{$error}</p>26{/if}Configuration Options
1interface ModelRiverClientOptions {2 // WebSocket URL (default: 'wss://api.modelriver.com/socket')3 baseUrl?: string;4 5 // Optional HTTP base URL for backend reconnect helper.6 // When set, the SDK can call your backend's /api/v1/ai/reconnect7 // to obtain a fresh ws_token for an existing async request after8 // a page refresh (see "WebSocket Reconnect & Page Refresh Recovery").9 apiBaseUrl?: string;10 11 // Enable debug logging (default: false)12 debug?: boolean;13 14 // Enable localStorage persistence for page refresh recovery (default: true)15 persist?: boolean;16 17 // Storage key prefix (default: 'modelriver_')18 storageKeyPrefix?: string;19 20 // Heartbeat interval in ms (default: 30000)21 heartbeatInterval?: number;22 23 // Request timeout in ms (default: 300000)24 requestTimeout?: number;25}Events
| Event | Payload | Description |
|---|---|---|
| connecting | - | Connection attempt started |
| connected | - | Successfully connected to WebSocket |
| disconnected | reason?: string | Disconnected from WebSocket |
| response | AIResponse | AI response received |
| error | Error or string | Error occurred |
| step | WorkflowStep | Workflow step status updated |
| channel_joined | - | Successfully joined the channel |
| channel_error | reason: string | Failed to join channel |
Response Format
Async API Response
When you call /api/ai/async, you receive:
1interface AsyncResponse {2 message: string; // "success"3 status: 'pending'; // Always "pending" for async4 channel_id: string; // Unique channel ID5 websocket_url: string; // WebSocket URL to connect to6 websocket_channel: string; // Full channel name (e.g., "ai_response:uuid")7 instructions?: {8 websocket?: string;9 webhook?: string;10 };11 test_mode?: boolean; // Present in test mode12}WebSocket Response
When the AI completes, you receive an AIResponse via WebSocket:
1interface AIResponse {2 status: string; // 'success' or 'error'3 channel_id?: string;4 content?: string; // AI response text5 model?: string; // Model used (e.g., 'gpt-4')6 data?: unknown; // Structured output data7 meta?: {8 workflow?: string;9 status?: string;10 duration_ms?: number;11 usage?: {12 prompt_tokens?: number;13 completion_tokens?: number;14 total_tokens?: number;15 };16 };17 error?: {18 message: string;19 details?: unknown;20 };21}WebSocket Reconnect & Page Refresh Recovery
Async requests are processed in the background and identified by a channel_id.
The WebSocket ws_token returned from /api/v1/ai/async is:
- short‑lived (≈5 minutes)
- single‑use (consumed on first successful WebSocket authentication)
This means you cannot safely reuse the original ws_token after a page refresh.
Instead, you should:
- Persist the active request (
channel_id, etc.) inlocalStorage(the SDK does this whenpersist: true) - On page load, ask your backend for a fresh
ws_tokenfor thatchannel_idvia/api/v1/ai/reconnect - Use the new token to reconnect the WebSocket
Backend: /api/v1/ai/reconnect
On your backend, expose a simple endpoint that:
- Verifies the
channel_idbelongs to the current user/project in your own database - Calls ModelRiver’s reconnect API with your project API key
- Returns only the safe connection fields (
channel_id,ws_token,websocket_url,websocket_channel) to the frontend
In pseudocode:
1// Backend (Node / any server framework)2async function reconnectAsync(channel_id: string) {3 // 1) Validate channel_id belongs to the current user/project in your DB4 const job = await findPendingAsyncJobInYourDB(channel_id);5 if (!job) throw new Error('No pending async request found');6 7 // 2) Call ModelRiver's reconnect endpoint with your API key8 const res = await fetch('https://api.modelriver.com/v1/ai/reconnect', {9 method: 'POST',10 headers: {11 'Content-Type': 'application/json',12 'Authorization': `Bearer ${process.env.MODELRIVER_API_KEY}`, // never send this to the browser13 },14 body: JSON.stringify({ channel_id }),15 });16 17 if (!res.ok) throw new Error('ModelRiver reconnect failed');18 19 // 3) Return only what the frontend needs to reconnect20 const {21 channel_id: new_channel_id,22 ws_token,23 websocket_url,24 websocket_channel,25 } = await res.json();26 27 return { channel_id: new_channel_id, ws_token, websocket_url, websocket_channel };28}Security: This function lives only on your backend.
The browser never seesMODELRIVER_API_KEYor calls ModelRiver HTTP APIs directly.
Frontend: reconnecting with the SDK (TypeScript)
Configure the client with both baseUrl (WebSocket) and apiBaseUrl (HTTP base for your backend):
1import { ModelRiverClient } from '@modelriver/client';2 3const client = new ModelRiverClient({4 baseUrl: 'wss://your-app.com/socket',5 apiBaseUrl: 'https://your-app.com', // your backend base URL6 persist: true,7});8 9// On initial request:10// 1) Your frontend calls your backend: POST /api/v1/ai/async (backend → ModelRiver)11// 2) Backend returns { channel_id, ws_token, websocket_url, websocket_channel }12// 3) Frontend connects:13client.connect({14 channelId: asyncResponse.channel_id,15 wsToken: asyncResponse.ws_token,16 websocketUrl: asyncResponse.websocket_url,17 websocketChannel: asyncResponse.websocket_channel,18});19 20// On page load, attempt to resume any pending request:21if (client.getState().hasPendingRequest) {22 // This helper:23 // - Reads the stored channel_id from localStorage24 // - Calls your backend's /api/v1/ai/reconnect (using apiBaseUrl)25 // - Connects with the fresh ws_token + websocket_url/channel26 client.reconnectWithBackend().catch((err) => {27 console.error('Reconnect failed', err);28 });29}For CDN usage, the same pattern applies: your backend should call /api/v1/ai/async and /api/v1/ai/reconnect, and the browser should talk only to your backend, never sending the ModelRiver API key directly.
Workflow Steps
The SDK tracks progress through four steps:
| Step | Description |
|---|---|
| queue | Request is being queued |
| process | AI is processing the request |
| receive | Waiting for response delivery |
| complete | Response received successfully |
Each step has a status: pending, loading, success, or error.
Persistence
By default, the SDK persists active requests to localStorage. If the user refreshes the page mid-request, the SDK automatically reconnects and resumes receiving the response.
Disable persistence if you don't need it:
1const client = new ModelRiverClient({2 persist: false,3});Security
The ws_token is a short-lived JWT that:
- Expires after 5 minutes
- Contains
project_id,channel_id, and connection details - Is consumed on first use (one-time token)
Important: Always obtain tokens from your backend. Never expose your ModelRiver API key in frontend code.
Browser Support
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
CDN Usage
For projects without a build step:
1<!DOCTYPE html>2<html>3<head>4 <script src="https://cdn.modelriver.com/client/latest/modelriver.min.js"></script>5</head>6<body>7 <button id="send">Send</button>8 <pre id="response"></pre>9 10 <script>11 const client = new ModelRiver.ModelRiverClient({12 baseUrl: 'wss://api.modelriver.com/socket',13 });14 15 client.on('response', (data) => {16 document.getElementById('response').textContent = 17 JSON.stringify(data, null, 2);18 });19 20 document.getElementById('send').addEventListener('click', async () => {21 const res = await fetch('/api/ai/request', { method: 'POST' });22 const { ws_token } = await res.json();23 client.connect({ wsToken: ws_token });24 });25 </script>26</body>27</html>Next Steps
- Review the API documentation to understand backend integration
- Explore Workflows to configure AI models and fallbacks
- Check Troubleshooting for common issues