Chat / SessionsCookbook
Quickstart
A working AI chat thread — client, auth, and agent.
Prerequisites
- A Pingerchips app with App Key and App Secret
- Node.js 18+
- An LLM provider key
1. Install
npm install pingerchips-js pingerchips-js-serverpingerchips-js for the browser client, pingerchips-js-server for the auth
endpoint and the agent.
2. Auth endpoint (server)
The browser needs a token to join a thread. Your server mints it — the App Secret never leaves the backend.
// server.js (Express)
import express from 'express';
import PingerchipsServer from 'pingerchips-js-server';
const app = express();
app.use(express.json());
const pc = new PingerchipsServer(
process.env.PINGERCHIPS_APP_KEY,
process.env.PINGERCHIPS_APP_SECRET,
);
app.post('/chat/auth', async (req, res) => {
const { socket_id, thread_id, client_id } = req.body;
// your authorization check
if (!(await canUserAccessThread(req.user, thread_id))) {
return res.status(403).json({ error: 'Forbidden' });
}
// returns { auth: "<jwt>" }
const token = await pc.authenticateChat(socket_id, thread_id, client_id);
res.json(token);
});
app.listen(3000);authenticateChat calls POST /api/auth for you and returns the JWT
capability token. Pass a 4th argument — a list of chat
verbs — to narrow the grant below the default client set.
3. Connect on the client
import { PingerchipsChat } from 'pingerchips-js/chat';
const chat = new PingerchipsChat('YOUR_APP_KEY', {
authEndpoint: '/chat/auth', // your endpoint from step 2
});
const session = await chat.connect('thread-uuid-here', {
clientId: 'user-123',
});
const { view } = session;4. Render and send
function render() {
const messages = view.getMessages();
// include the in-progress assistant response
for (const run of view.runs()) {
if (run.status === 'active') {
showStreamingBubble(run.runId, view.getStream(run.runId));
}
}
paintMessages(messages);
}
view.on('update', render);
render();
// send a user message
await view.send('Hello! What can you help me with?');view.on('update', …) fires on every change — a new message, a token flush
(~40 ms), a run ending. Re-read view.getMessages() and view.getStream(runId)
each time.
5. The agent
When the user sends a message, your backend gets an HTTP invocation carrying
{ threadId, runId }. Handle it:
import { AgentSession } from 'pingerchips-js-server/agent-session.js';
import Anthropic from '@anthropic-ai/sdk';
export async function onInvocation({ threadId, runId, userText }) {
const agent = new AgentSession(
process.env.PINGERCHIPS_APP_KEY,
process.env.PINGERCHIPS_APP_SECRET,
);
const thread = await agent.connect(threadId, 'my-bot');
const run = thread.createRun(runId);
await run.start();
let text = '';
const stream = new Anthropic().messages.stream(
{
model: 'claude-sonnet-5',
max_tokens: 1024,
messages: [{ role: 'user', content: userText }],
},
{ signal: run.abortSignal },
);
stream.on('text', async (delta) => {
text += delta;
await run.write(text); // full accumulated string
});
await stream.finalMessage();
await run.write(text);
await run.end('complete');
thread.close();
}6. Load history
// walk back in pages of 50
if (view.hasOlder()) {
const older = await view.loadOlder(50);
prependToUI(older);
}What's next
- Core Concepts — threads, runs, the conversation tree
- Client SDK —
PingerchipsChat/View/ActiveRun - Agent SDK —
AgentSession/Run - Authentication — the capability token in detail