Client SDK
The browser-side chat API — PingerchipsChat, ChatSession, and View.
import { PingerchipsChat } from 'pingerchips-js/chat';Also exported from pingerchips-js/chat: ChatSession, View, ActiveRun.
Building a React chat UI on the Vercel AI SDK? Use
@pingerchips/ai instead —
it gives you a useChat() transport with resumable streams. This page is the
lower-level primitive.
PingerchipsChat
Constructor
new PingerchipsChat(appKey: string, options?: ChatOptions)The first argument is the App Key string.
| Option | Type | Description |
|---|---|---|
endpoint | string | WebSocket URL. Default wss://queue.pingerchips.com/socket (prod) / ws://localhost:4000/socket (dev). |
realtime | Pingerchips | Reuse an existing Pingerchips socket instead of opening a new one. |
authEndpoint | string | URL your app exposes that mints a capability token (via POST /api/auth). The SDK POSTs { socket_id, thread_id, client_id } and expects { auth } back. |
authCallback | function | Alternative to authEndpoint — (params, done) => … where params is { socket_id, thread_id, client_id }. Call done(null, token) or return the token. |
authHeaders | object | Extra headers on the authEndpoint request. |
authParams | object | Extra body fields on the authEndpoint request. |
serializer / messageFormat | "msgpack" | Switch the socket to MessagePack binary frames. |
reconnectAfterMs / rejoinAfterMs | number | fn | Phoenix socket backoff. |
autoConnect | boolean | false disconnects the socket immediately after construction. |
connect(threadId, options?)
connect(threadId: string, options?: {
clientId: string; // required — identifies the human
afterLogId?: number; // resume: replay entries after this log_id
}): Promise<ChatSession>Joins the thread channel as a human client and returns a ChatSession.
Resolves the auth token via authEndpoint / authCallback first.
const chat = new PingerchipsChat('YOUR_APP_KEY', {
authEndpoint: '/chat/auth',
});
const session = await chat.connect('thread-uuid', { clientId: 'user-42' });connectAgent(threadId, options?)
connectAgent(threadId: string, options?: {
secret: string; // your App Secret — server-side only
agentId: string;
afterLogId?: number;
}): Promise<ChatSession>Joins as an agent (authenticates with the App Secret directly). Prefer the Agent SDK for agent processes — it wraps this with a run lifecycle.
ChatSession
Returned by connect() / connectAgent(). Properties: threadId, appKey,
view.
| Method | Description |
|---|---|
cancel(runId) → Promise | Cancel an in-progress run. |
push(event, payload) → Promise | Low-level: push a raw channel event. |
close() | Leave the thread channel. |
on(event, cb) → this | Subscribe (delegates to view — see events below). |
off(event, cb) → this | Unsubscribe. |
All reading and message-sending happens through session.view.
View
session.view — the read model plus the write commands for a thread.
Reading
| Method | Returns |
|---|---|
getMessages() | Messages on the currently selected branch. |
getChannelMessages() | Every message the channel has seen (all branches). |
runs() | Array of RunInfo for runs in this thread. |
hasOlder() | boolean — is there history before the loaded window. |
getStream(runId) | The full accumulated token string for an active run ("" if none). |
get logId | Current log position. |
Sending
Each of these pushes to the channel and resolves to an ActiveRun (except
loadOlder). IDs default to generated UUIDs.
| Method | Signature |
|---|---|
send(text, opts?) | send(text, { parentId?, messageId?, runId?, role? }) → Promise<ActiveRun> |
regenerate(messageId, opts?) | regenerate(messageId, { runId?, parentId? }) → Promise<ActiveRun> |
edit(messageId, content, opts?) | edit(messageId, content, { newMessageId?, runId? }) → Promise<ActiveRun> |
loadOlder(limit?) | loadOlder(limit = 50) → Promise<Message[]> — takes a count, walks back from the oldest loaded message |
selectSibling(parentId, messageId) | Switch the visible branch at a fork. |
approveTool(runId, toolCallId) | Promise — approve a suspended tool call. |
rejectTool(runId, toolCallId) | Promise — reject it. |
Events
view.on(event, cb) / session.on(event, cb). There is no per-token
callback — on "update", read view.getStream(runId) for the current text.
| Event | Payload | Fires when |
|---|---|---|
update | — | Any thread state change (new message, token flush, run status). Re-read view.getMessages() / getStream(). |
run | RunInfo | A run's info changes. |
run:start | { runId, ownerClientId, startedAt } | Agent starts a run. |
run:end | { runId, reason, endedAt } | Run terminates (reason: complete | cancelled | error). Followed by a state update with the finalised message. |
run:suspend / run:suspended | { runId, toolCallId, toolName, args } | Run pauses for tool approval (both event names fire). |
run:resume | { runId } | Run resumes. |
ActiveRun
Returned by view.send() / regenerate() / edit().
Properties: runId, messageId, forkOf, regeneratesMessageId, serial,
status.
| Method | Description |
|---|---|
cancel() → Promise | Cancel this run. |
toJSON() | { threadId, runId } |
Putting it together
import { PingerchipsChat } from 'pingerchips-js/chat';
const chat = new PingerchipsChat('YOUR_APP_KEY', { authEndpoint: '/chat/auth' });
const session = await chat.connect('thread-uuid', { clientId: 'user-42' });
const { view } = session;
function render() {
const msgs = view.getMessages();
for (const run of view.runs()) {
if (run.status === 'active') {
// show the streaming bubble with the current text
showStreaming(run.runId, view.getStream(run.runId));
}
}
paint(msgs);
}
view.on('update', render);
render();
await view.send('What can you help me with?');Message shape (wire)
interface Message {
messageId: string; // client-minted UUID
runId: string;
ownerClientId: string;
role: 'user' | 'assistant';
content: unknown; // codec-defined; plain text by default
parentId: string | null;
forkOf: string | null;
msgRegenerate: string | null;
status: 'streaming' | 'complete' | 'cancelled' | 'error';
createdAt: number; // unix ms
}interface RunInfo {
runId: string;
ownerClientId: string;
status: 'pending' | 'active' | 'suspended' | 'complete' | 'cancelled' | 'error';
startedAt: number;
endedAt: number | null;
suspendedTool: { toolCallId: string; toolName: string; args: object } | null;
parentId: string | null;
forkOf: string | null;
msgRegenerate: string | null;
}