Client: chat

Asking questions and streaming answers, and the union both return.

Browser or server Embed key in the browser, integration key on the server dev · https://api.dev.oprag.ai

chat.ask

TypeScript
import { createClient, citationDisplayLabel } from "@oprag/sdk";

const oprag = createClient({ apiUrl: "https://api.dev.oprag.ai", apiKey: embedKey });

const res = await oprag.chat.ask({ question: "What is your refund policy?" });

if (res.type === "answer") {
  render(res.answer);
  for (const source of res.sources ?? []) {
    console.log(citationDisplayLabel(source));
  }
} else {
  // res.type === "lead_capture_prompt" — there is no `answer` on this branch.
  renderLeadForm(res.promptMessage, res.fields);
}

Multi-turn

Echo the ids back from the previous response and the next answer keeps its context. Omit them and every question is answered cold.

TypeScript
const first = await oprag.chat.ask({ question: "Do you ship to Canada?" });

if (first.type === "answer") {
  const second = await oprag.chat.ask({
    question: "How long does it take?",
    sessionId: first.sessionId,
    conversationId: first.conversationId,
    visitorId: first.visitorId,
  });
}

chat.stream

TypeScript
const controller = new AbortController();

const final = await oprag.chat.stream(
  { question: "Summarize your shipping policy", sessionId },
  {
    // `accumulated` is the whole answer so far — render it directly.
    onToken: (_token, accumulated) => setDraft(accumulated),
    onSources: (sources) => setCitations(sources ?? []),
    signal: controller.signal,
  },
);

stream resolves with the same union ask returns, so the branch check applies there too. A full UI example is in the streaming recipe.

Citations

Every field on a CitationSource is optional, which is why citationDisplayLabel exists — it picks the best available of title, document title, and location, and appends the page number when there is one.

Underneath, both methods call POST /v1/chat.

Ready to ship?

Get started free