Official ModelRiver client SDK for real-time AI response streaming via WebSockets.
npm install @modelriver/client
# or
yarn add @modelriver/client
# or
pnpm add @modelriver/client
<script src="https://cdn.modelriver.com/client/v1.0.0/modelriver.min.js"></script>
<!-- or latest -->
<script src="https://cdn.modelriver.com/client/latest/modelriver.min.js"></script>
Your backend calls the ModelRiver API and receives a WebSocket token:
// Your backend endpoint
const response = await fetch('/api/ai/request', {
method: 'POST',
body: JSON.stringify({ message: 'Hello AI' }),
});
const { ws_token } = await response.json();
import { ModelRiverClient } from '@modelriver/client';
const client = new ModelRiverClient({
baseUrl: 'wss://api.modelriver.com/socket',
});
client.on('response', (data) => {
console.log('AI Response:', data);
});
client.on('error', (error) => {
console.error('Error:', error);
});
client.connect({ wsToken: ws_token });
import { useModelRiver } from '@modelriver/client/react';
function ChatComponent() {
const {
connect,
disconnect,
response,
error,
isConnected,
steps
} = useModelRiver({
baseUrl: 'wss://api.modelriver.com/socket',
persist: true,
});
const handleSend = async () => {
const { ws_token } = await yourBackendAPI.createRequest(message);
connect({ wsToken: ws_token });
};
return (
<div>
<button onClick={handleSend} disabled={isConnected}>
Send
</button>
{/* Show workflow progress */}
{steps.map((step) => (
<div key={step.id} className={step.status}>
{step.name}
</div>
))}
{/* Show response */}
{response && (
<pre>{JSON.stringify(response.data, null, 2)}</pre>
)}
{/* Show error */}
{error && <p className="error">{error}</p>}
</div>
);
}
<script setup>
import { useModelRiver } from '@modelriver/client/vue';
const {
connect,
disconnect,
response,
error,
isConnected,
steps
} = useModelRiver({
baseUrl: 'wss://api.modelriver.com/socket',
});
async function handleSend() {
const { ws_token } = await yourBackendAPI.createRequest(message);
connect({ wsToken: ws_token });
}
</script>
<template>
<div>
<button @click="handleSend" :disabled="isConnected">Send</button>
<div v-for="step in steps" :key="step.id" :class="step.status">
{{ step.name }}
</div>
<pre v-if="response">{{ response.data }}</pre>
<p v-if="error" class="error">{{ error }}</p>
</div>
</template>
import { Component, OnDestroy } from '@angular/core';
import { ModelRiverService } from '@modelriver/client/angular';
@Component({
selector: 'app-chat',
providers: [ModelRiverService],
template: `
<button (click)="send()" [disabled]="modelRiver.isConnected">
Send
</button>
<div *ngFor="let step of modelRiver.steps$ | async" [class]="step.status">
{{ step.name }}
</div>
<pre *ngIf="modelRiver.response$ | async as res">
{{ res.data | json }}
</pre>
<p *ngIf="modelRiver.error$ | async as err" class="error">
{{ err }}
</p>
`,
})
export class ChatComponent implements OnDestroy {
constructor(public modelRiver: ModelRiverService) {
this.modelRiver.init({
baseUrl: 'wss://api.modelriver.com/socket'
});
}
async send() {
const { ws_token } = await this.backendService.createRequest(message);
this.modelRiver.connect({ wsToken: ws_token });
}
ngOnDestroy() {
this.modelRiver.destroy();
}
}
<script>
import { createModelRiver } from '@modelriver/client/svelte';
import { onDestroy } from 'svelte';
const modelRiver = createModelRiver({
baseUrl: 'wss://api.modelriver.com/socket',
});
const { response, error, isConnected, steps, connect, disconnect } = modelRiver;
async function send() {
const { ws_token } = await backendAPI.createRequest(message);
connect({ wsToken: ws_token });
}
onDestroy(() => disconnect());
</script>
<button on:click={send} disabled={$isConnected}>Send</button>
{#each $steps as step}
<div class={step.status}>{step.name}</div>
{/each}
{#if $response}
<pre>{JSON.stringify($response.data, null, 2)}</pre>
{/if}
{#if $error}
<p class="error">{$error}</p>
{/if}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.modelriver.com/client/latest/modelriver.min.js"></script>
</head>
<body>
<button id="send">Send</button>
<pre id="response"></pre>
<script>
const client = new ModelRiver.ModelRiverClient({
baseUrl: 'wss://api.modelriver.com/socket',
});
client.on('response', (data) => {
document.getElementById('response').textContent =
JSON.stringify(data, null, 2);
});
client.on('error', (error) => {
console.error('Error:', error);
});
document.getElementById('send').addEventListener('click', async () => {
// Get token from your backend
const res = await fetch('/api/ai/request', { method: 'POST' });
const { ws_token } = await res.json();
client.connect({ wsToken: ws_token });
});
</script>
</body>
</html>
interface ModelRiverClientOptions {
baseUrl?: string; // WebSocket URL (default: 'wss://api.modelriver.com/socket')
debug?: boolean; // Enable debug logging (default: false)
persist?: boolean; // Enable localStorage persistence (default: true)
storageKeyPrefix?: string; // Storage key prefix (default: 'modelriver_')
heartbeatInterval?: number; // Heartbeat interval in ms (default: 30000)
requestTimeout?: number; // Request timeout in ms (default: 300000)
}
| Method | Description |
|---|---|
connect({ wsToken }) |
Connect to WebSocket with token |
disconnect() |
Disconnect from WebSocket |
reset() |
Reset state and clear stored data |
reconnect() |
Reconnect using stored token |
getState() |
Get current client state |
hasPendingRequest() |
Check if there's a pending request |
on(event, callback) |
Add event listener (returns unsubscribe function) |
off(event, callback) |
Remove event listener |
destroy() |
Clean up all resources |
| Event | Payload | Description |
|---|---|---|
connecting |
- | Connection attempt started |
connected |
- | Successfully connected |
disconnected |
reason?: string |
Disconnected from WebSocket |
response |
AIResponse |
AI response received |
error |
Error | string |
Error occurred |
step |
WorkflowStep |
Workflow step updated |
channel_joined |
- | Successfully joined channel |
channel_error |
reason: string |
Channel join failed |
interface AIResponse {
status: string;
channel_id?: string;
data?: unknown;
meta?: {
workflow?: string;
status?: string;
duration_ms?: number;
usage?: {
prompt_tokens?: number;
completion_tokens?: number;
total_tokens?: number;
};
};
error?: {
message: string;
details?: unknown;
};
}
interface WorkflowStep {
id: string;
name: string;
status: 'pending' | 'loading' | 'success' | 'error';
duration?: number;
errorMessage?: string;
}
ws_token (JWT) containing connection details┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Frontend │ │ Your Backend │ │ ModelRiver │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ 1. Request AI │ │
│─────────────────────>│ │
│ │ 2. Create request │
│ │─────────────────────>│
│ │ │
│ │ 3. Return ws_token │
│ │<─────────────────────│
│ 4. Return token │ │
│<─────────────────────│ │
│ │ │
│ 5. Connect WebSocket (SDK) │
│─────────────────────────────────────────────>│
│ │ │
│ 6. Stream AI response │
│<─────────────────────────────────────────────│
│ │ │
The ws_token is a short-lived JWT that:
project_id, channel_id, and topicImportant: Always obtain tokens from your backend. Never expose your ModelRiver API key in frontend code.
MIT