Pingerchips LogoPingerchips
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-server

pingerchips-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

On this page