Subscribe to live PromptJuggler token streams from the browser. Everything in this package is safe to ship to an end user — it never sees your API key.
- Your backend calls
createStreamToken(threadId)with a server-side SDK (@promptjuggler/sdk, or the PHP/Python/Java/Go equivalent) and returns the response to your frontend. The credential grants read access to one thread and nothing else. - Your frontend hands that response to this SDK and renders events.
- Connect before triggering runs: a fresh subscription starts at the
live tip. Reconnects resume exactly where they left off; when the server
cannot resume without loss it says
stale, and terminal events carry agappedflag —truemeans fetch the run for the authoritative result.
import { PromptJugglerStream } from '@promptjuggler/browser';
const stream = new PromptJugglerStream({
// Called on connect and on every reconnect, so expiry renews itself.
getToken: async () => {
const res = await fetch(`/my-api/stream-token?thread=${threadId}`);
return res.json(); // { token, url } — createStreamToken's response, verbatim
},
});
stream.on('text', ({ runId, text }) => render(runId, text)); // full text, maintained for you
stream.on('data', ({ runId, data }) => renderCards(runId, data)); // emit-tool payloads, maintained
stream.on('transcript', ({ runId, transcript }) => renderChat(runId, transcript)); // text + tools + payloads, in order
stream.on('done', ({ runId, gapped }) => gapped && refetch(runId)); // fetch only when told to
stream.on('failure', ({ runId, code, message }) => showError(runId, message));
stream.connect();An emit tool lets a prompt return structured data beside its prose (e.g. a
list of ids to render as cards). The data event carries the run's maintained
list of { tool, payload } — cast payload to whatever the tool's schema
describes. Render it next to text and you have prose-plus-cards.
The transcript event carries the same run as an ordered sequence — blocks of
prose, the tools the model used (pending → ok/error), and emit payloads in
position. It contains everything text and data do, and it is the same shape
getPromptRun returns as transcript, so one component renders a live run and a
reloaded one. Use it when the UI shows tool activity; text alone is fine when it
does not.
token (raw deltas), reset, gap, stale, connected and disconnected
events are also emitted for append-style UIs — segment handling, resets, and
resume are already applied to the text view, so most apps never need them.
One edge to know: gap can fire even after a clean done (a late token
proving the finished text incomplete) — treat it as a refetch cue for runs
you have already settled. The React hook handles this for you.
Workflows stream every prompt node; follow specific conversation lanes with
channels: ['support'] (the channel each node declares in the workflow editor).
import { usePromptJugglerStream } from '@promptjuggler/browser/react';
const { connected, runs } = usePromptJugglerStream({ getToken }, [threadId]);
// runs[runId] = { text, data?, transcript?, status: 'streaming' | 'done' | 'failed', gapped?, error? }import { injectPromptJugglerStream } from '@promptjuggler/browser/angular';
// Signals read during the factory call drive resubscription — hoist them into
// locals (a read deferred into getToken happens too late to be tracked).
readonly stream = injectPromptJugglerStream(() => {
const thread = this.threadId();
return { getToken: () => this.mintToken(thread) };
});
// stream.connected: Signal<boolean>; stream.runs: Signal<Record<string, RunState>>Automatic: exponential backoff after failures, immediate when the server hands
the connection off during a deploy. disconnect() stops everything.