BigHugger

Streaming an answer

POST /api/v1/ask streams. The response is a sequence of lines, each one a complete JSON object carrying a type. Read it line by line and act on the types you care about.

curl -N -X POST https://api.bighugger.com/v1/ask \
  -H 'authorization: Bearer $BIGHUGGER_API_KEY' \
  -H 'content-type: application/json' \
  -d '{"question": "which small models run well on an iPhone?"}'

-N matters: without it curl buffers, and you get the whole answer at the end rather than as it is written.

Event types

typeFieldsWhen
threadid, titleFirst. The thread this question opened.
statusstageThe run moved to a new stage: planning, searching, then reading.
planqueriesThe searches the question was turned into.
sourceshits[] — each with id, title, url, kindWhat retrieval found, before the answer is written.
toolname, inputThe agent started a tool call.
tool_doneerrorThat call finished; error is a boolean.
textdeltaA fragment of the answer. Concatenate these in order.
donethread_id, workbook_url, sourcesLast on a successful run.
errorerror, messageThe run failed. See Errors.

Reading the stream

Two rules make a client that keeps working:

Concatenate text deltas, in order. They are fragments, not sentences. A delta may split a word.

Skip types you do not recognise. New event types are added over time and are not a breaking change. A client that throws on an unknown type will break on a release that adds one; a client that ignores it will not.

A minimal reader:

const res = await fetch("https://api.bighugger.com/v1/ask", {
  method: "POST",
  headers: {
    authorization: `Bearer ${process.env.BIGHUGGER_API_KEY}`,
    "content-type": "application/json",
  },
  body: JSON.stringify({ question: "which small models run well on an iPhone?" }),
});

let answer = "";
for await (const line of lines(res.body)) {
  const event = JSON.parse(line);
  switch (event.type) {
    case "text":    answer += event.delta; break;
    case "sources": console.log(`${event.hits.length} sources`); break;
    case "done":    console.log(answer); break;
    case "error":   throw new Error(`${event.error}: ${event.message}`);
    // Anything else is ignored on purpose.
  }
}

Endings

A run ends with exactly one of done or error. A stream that stops without either was cut in transit — treat it as a failure and retry, remembering that a retried question is billed again.

The whole run is billed once when it finishes, however many stages it passed through.