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énement | Rôle |
|---|---|
activity | Progression produit visible : routage, recherche, écriture, code, completion ou erreur. |
delta | Fragment de texte généré. |
done | Métadonnées finales et conversation persistée. |
error | Erreur 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.
É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
doneen succès ; - un
erroren é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.
Pièces jointes et recherche web
Les mêmes validations que sur /api/ai/generate s’appliquent avant streaming.
Les pièces jointes exigent actuellement OrbitWake Auto ou OpenAI. La recherche web live exige elle aussi OrbitWake Auto ou OpenAI dans le gateway actuel.
Parsing utilisé par le client OrbitWake
Le client web officiel :
- vérifie
response.oket la présence deresponse.body; - obtient un reader avec
response.body.getReader(); - décode les bytes avec
TextDecoder; - normalise
\r\nen\n; - accumule les données partielles dans un buffer ;
- découpe chaque bloc sur
\n\n; - lit le champ
event:et concatène les lignesdata:; - 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.