Manova Developers

Streaming replies

The server-sent events of POST /chat/messages, and how to read them in your language.

A successful send answers 200 with Content-Type: text/event-stream and writes events as the reply is generated. Each event is an event: line and a JSON data: line, followed by a blank line:

event: message.started
data: {"clientRequestId":"6f1d2c9e-...","conversationId":"9b2e4c1a-...","userMessageId":"...","assistantMessageId":"...","generationId":"..."}

event: message.delta
data: {"generationId":"...","sequence":1,"text":"فروشگاه ما"}

event: message.delta
data: {"generationId":"...","sequence":2,"text":" با بیش از ده سال تجربه"}

event: message.completed
data: {"generationId":"...","message":{"id":"...","role":"assistant","content":[{"type":"text","text":"فروشگاه ما با بیش از ده سال تجربه…"}],"status":"completed", "...": "..."}}

event: usage.updated
data: {"usage":{"remainingCredits":868,"usedCredits":132,"...":"..."}}

Errors found before the stream starts (bad input, no credits, a bad token) are normal JSON errors with a 4xx or 5xx status. Once the stream has started, the status is 200 and a failure arrives as message.failed.

Events

EventDataNotes
message.startedclientRequestId, conversationId, userMessageId, assistantMessageId, generationIdAlways first. Save conversationId to continue the conversation.
message.deltagenerationId, sequence, textA piece of the reply. Append text in sequence order.
agent.stepgenerationId, tool, status, detailOnly when answering as an agent: a tool is running, then completed or failed.
message.completedgenerationId, messageThe whole reply as stored. Prefer it over the joined deltas.
message.abortedgenerationId, messageThe reply was stopped; message has what was written so far.
message.failedgenerationId, error (code, message)The model could not finish. Nothing is charged for a failed reply.
usage.updatedusageAlways last. The same object as GET /usage.
memory.suggestedid, noteSomething the chat offers to remember; can be ignored.

Exactly one of message.completed, message.aborted and message.failed ends each reply. Ignore event types you do not know: new ones may be added.

Read the stream

Works in Node.js 18+, Deno, Bun and browsers. EventSource cannot send a POST body or an Authorization header, so read the response body yourself:

const id = crypto.randomUUID();
const response = await fetch('https://manova.ir/api/v1/external/chat/messages', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MANOVA_TOKEN}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': id,
  },
  body: JSON.stringify({ clientRequestId: id, mode: 'auto', content: [{ type: 'text', text: 'سلام!' }] }),
});
if (!response.ok) throw new Error((await response.json()).code);

const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
let buffer = '';
for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += value;
  let end;
  while ((end = buffer.indexOf('\n\n')) !== -1) {
    const block = buffer.slice(0, end);
    buffer = buffer.slice(end + 2);
    const event = /^event: (.*)$/m.exec(block)?.[1];
    const data = JSON.parse(/^data: (.*)$/m.exec(block)?.[1] ?? '{}');
    if (event === 'message.delta') process.stdout.write(data.text);
    if (event === 'message.failed') throw new Error(data.error.code);
  }
}

Long replies and dropped connections

A reply can stream for several minutes. Turn off response buffering in proxies between you and Manova, and use a read timeout of a few minutes rather than seconds.

Closing the connection stops the reply: it is saved as aborted and charged for what was written. Retrying with the same clientRequestId replays that result (message.aborted) without charging again. To get a full answer, send the message again with a new clientRequestId.

On this page