OrbitWake API

Streaming

OrbitWake diffuse les réponses AI via Server-Sent Events sur POST /api/ai/stream. Le serveur ouvre une réponse HTTP 200, envoie des événements nommés au fil de l’exécution, puis ferme le flux après done ou error.

Endpoint

POST https://orbitwake.com/api/ai/stream

Le body d’entrée est le même que pour /api/ai/generate : modèle, mode, messages, conversationId optionnel et maxOutputTokens optionnel.

L’authentification et l’éligibilité AI sont vérifiées avant l’ouverture du flux.

Erreurs avant ouverture du flux

Si la requête est rejetée avant la création du stream, OrbitWake renvoie une réponse JSON normale avec son status HTTP réel.

Exemples : 401 si la session web est absente, 403 si le compte n’a pas accès à AI, 400 pour un modèle ou un mode invalide, 413 pour un contexte trop volumineux, ou 503 si le budget provider quotidien est atteint.

Dans ce cas, le client ne doit pas tenter de parser la réponse comme SSE.

Headers du flux

Content-Type: text/event-stream; charset=utf-8
Cache-Control: no-cache, no-transform
X-Accel-Buffering: no

no-transform évite qu’un intermédiaire transforme le corps, et X-Accel-Buffering: no demande aux proxies compatibles de ne pas bufferiser les événements.

Format d’un événement SSE

Chaque événement est encodé avec un nom, une ligne de données JSON et une ligne vide de séparation :

event: delta
data: {"text":"Bonjour"}

Le séparateur logique entre événements est donc \n\n.

Événements émis

ÉvénementRôle
activityProgression produit visible : routage, recherche, écriture, code, completion ou erreur.
deltaFragment de texte généré.
doneMétadonnées finales et conversation persistée.
errorErreur survenue après ouverture du stream.

Événement activity

Chaque activité reçoit un identifiant UUID et un timestamp millisecondes côté serveur :

event: activity
data: {
  "kind": "routing",
  "label": "Using OpenAI",
  "detail": "OrbitWake Auto selected this model.",
  "id": "uuid",
  "at": 1791050000000
}

Les kinds reconnus dans le type actuel sont :

request
routing
thinking
search
writing
code
complete
error

Ordre typique des activités

Le serveur émet toujours une activité request dès le démarrage du stream :

Request accepted

Le gateway ajoute ensuite généralement routing, puis thinking. Au premier texte produit, OrbitWake émet writing ou code selon le mode. Une activité complete est ajoutée lorsque la génération se termine.

En mode Research ou lorsqu’une recherche web est déclenchée avec OpenAI, le provider peut aussi émettre des activités search comme Searching the web et Web search complete.

Événement delta

Le texte est envoyé par fragments :

event: delta
data: {"text":"premier fragment"}

event: delta
data: {"text":" puis la suite"}

Le client officiel concatène simplement payload.text dans le message assistant courant.

Un delta n’est pas une phrase complèteNe faites aucune hypothèse sur la taille d’un fragment, son alignement avec des mots, des paragraphes ou du Markdown complet.

Événement done

Après la génération, le serveur enregistre l’usage, tente de persister la conversation, puis envoie :

event: done
data: {
  "conversation": { "...": "..." },
  "response": {
    "id": "provider-request-id",
    "provider": "openai",
    "providerModel": "...",
    "model": "...",
    "modelLabel": "...",
    "routingTier": "...",
    "routingReason": "...",
    "sources": [],
    "usage": { "...": "..." },
    "latencyMs": 1530,
    "estimatedCredits": 0.3
  }
}

Le texte complet n’est pas renvoyé dans done, car il a déjà été transmis via les événements delta.

Conversation dans done

Si la persistance réussit, conversation contient le résumé de la conversation courante. Le client officiel utilise notamment son id et son title pour mettre à jour l’URL et l’interface.

Si la persistance de l’historique échoue, la génération peut tout de même se terminer et conversation peut rester null.

Événement error après ouverture du flux

Une erreur runtime survenue après que le HTTP 200 a déjà été envoyé ne peut plus devenir un nouveau status HTTP. OrbitWake l’envoie donc dans le flux :

event: error
data: {
  "error": "Message lisible",
  "status": 503
}

Le champ status représente le status logique de l’erreur, pas le status HTTP du stream déjà ouvert.

Activity error + événement error

Avant l’événement error, OrbitWake émet également une activité :

{
  "kind": "error",
  "label": "Request failed",
  "detail": "..."
}

Un client peut donc afficher une timeline d’activité et traiter séparément l’échec final.

Fermeture du stream

Le ReadableStream est fermé dans un bloc finally. Il se termine donc après :

  • un done en succès ;
  • un error en échec après ouverture ;
  • ou une fin de traitement imprévue gérée par le serveur.

Le protocole actuel n’utilise pas un événement SSE dédié de type close.

Fallback provider avec OrbitWake Auto

Le gateway peut essayer un provider de fallback seulement lorsque :

  • le modèle demandé est orbitwake-auto ;
  • l’erreur provider est marquée retryable ;
  • aucun texte n’a encore été émis.

Dans ce cas, une nouvelle activité routing peut apparaître avec un label comme Switching to ....

Dès qu’un premier delta a été envoyé, le gateway n’effectue plus ce fallback automatique afin d’éviter de mélanger deux sorties provider dans le même message.

Parsing utilisé par le client OrbitWake

Le client web officiel :

  1. vérifie response.ok et la présence de response.body ;
  2. obtient un reader avec response.body.getReader() ;
  3. décode les bytes avec TextDecoder ;
  4. normalise \r\n en \n ;
  5. accumule les données partielles dans un buffer ;
  6. découpe chaque bloc sur \n\n ;
  7. lit le champ event: et concatène les lignes data: ;
  8. parse le JSON du payload.

Exemple de parser minimal

const response = await fetch("/api/ai/stream", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(payload)
});

if (!response.ok || !response.body) {
  const error = await response.json();
  throw new Error(error.error);
}

const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";

while (true) {
  const { done, value } = await reader.read();
  buffer += decoder.decode(value ?? new Uint8Array(), { stream: !done });
  buffer = buffer.replace(/\r\n/g, "\n");

  let boundary = buffer.indexOf("\n\n");
  while (boundary >= 0) {
    const block = buffer.slice(0, boundary);
    buffer = buffer.slice(boundary + 2);

    // Lire event:, concaténer data:, puis JSON.parse(data).
    boundary = buffer.indexOf("\n\n");
  }

  if (done) break;
}

Cet exemple montre la stratégie de buffering actuelle ; il ne constitue pas encore un SDK officiel.

Pourquoi un buffer est nécessaire

Un chunk réseau peut contenir un demi-événement, plusieurs événements complets ou la fin d’un événement plus le début du suivant.

Le client ne doit donc pas appeler JSON.parse() directement sur chaque chunk reçu.

Réponse interrompue

Dans le client officiel, si un texte partiel a déjà été reçu puis que le stream échoue, le message assistant reste affiché avec le meta Response interrupted.

Si aucun texte n’a été reçu, il est marqué Request failed.

Cette distinction est un comportement UI actuel, pas un champ du protocole SSE.

Sources

Les sources de recherche ne sont pas envoyées dans chaque delta. Elles sont disponibles dans done.response.sources lorsqu’elles existent.

Le client officiel attache ensuite ces sources au message assistant après réception de done.

Activity n’est pas une chaîne de raisonnement privée

Les activités sont des signaux produit explicites : requête reçue, routage, recherche, écriture, code, succès ou erreur.

Le client OrbitWake affiche d’ailleurs que cette timeline montre des actions et statuts utiles, pas le raisonnement privé du modèle.

Pourquoi le client n’utilise pas EventSource

Le endpoint utilise POST avec un body JSON riche. Le client officiel utilise donc fetch() + ReadableStream plutôt que l’API navigateur EventSource, principalement orientée GET.

Reconnexion

Le stream actuel ne définit pas d’IDs SSE de reprise ni de directive retry:.

Il n’existe donc pas aujourd’hui de reprise automatique d’un message à partir du dernier événement reçu. Relancer une requête constitue une nouvelle exécution et doit être fait avec prudence.

Heartbeat

Le protocole actuel n’émet pas de commentaire SSE ou heartbeat périodique dédié.

La connexion reste active grâce aux événements produits par la génération elle-même et aux réglages d’infrastructure du service.

Limites actuelles

  • pas de version de protocole SSE exposée ;
  • pas d’event ID pour reprise ;
  • pas de heartbeat dédié ;
  • pas de reconnexion automatique documentée ;
  • pas de SDK officiel qui encapsule le parser ;
  • les shapes décrites restent celles du runtime actuel, non d’une API publique versionnée.

Étape suivante

La page Erreurs détaillera les status HTTP, erreurs JSON, erreurs SSE, erreurs AI gateway, Connectors, IDE et rate limits.

PrécédentAPI — RéponsesSuivantAPI — Erreurs