---
title: "Sessions"
description: "Exécutez une session, recevez ses événements en continu et gérez les erreurs qu’elle peut générer."
---

Une session est une exécution d’agent, dans son propre bac à sable, sur sa propre branche git.
`beeuni.session(projectId, sessionId)` renvoie l’objet de contrôle de toutes les opérations d’une
session : la démarrer, envoyer des prompts, recevoir les événements en continu et consulter son état. Cette
page présente cet objet, la négociation de disponibilité, le streaming et les erreurs typées
qu’un appel au SDK peut générer.

```ts
const s = beeuni.session(projectId, sessionId);
```

`s` est l’objet de contrôle de toutes les opérations d’une session. L’ID de session, l’ID du bac à sable
et le nom de branche ont la même valeur. Voir
[Sessions](/docs/fr/work/sessions) pour le concept.

<a id="session-lifecycle"></a>

## Cycle de vie d’une session

| Méthode | Encapsule | Fonction |
|---|---|---|
| `s.get(opts?)` | `GET /projects/:pid/sessions/:sid` | Lit les détails de la session |
| `s.update(input)` | `PATCH …/sessions/:sid` | Renomme la session, remplace ses étiquettes ou fusionne les clés de métadonnées |
| `s.start(waitMs?)` | `POST …/sessions/:sid/start` | Provisionne et démarre l’environnement d’exécution |
| `s.restart()` | `POST …/sessions/:sid/restart` | Redémarre l’environnement d’exécution ; conserve le même bac à sable |
| `s.configState()` | `GET …/sessions/:sid/config` | Lit l’état d’actualisation de la configuration (`stale`) et l’état de publication de la configuration (`release`) |
| `s.reloadConfig(input?)` | `POST …/sessions/:sid/reload` | Recompile la configuration de l’agent et remplace l’environnement d’exécution après validation |
| `s.reloadConfigStream(input, onEvent)` | `POST …/sessions/:sid/reload-stream` | Effectue le même rechargement et émet les phases de progression confirmées par le serveur |
| `s.stop()` | `POST …/sessions/:sid/stop` | Arrête l’environnement d’exécution ; la session est conservée |
| `s.delete()` | `DELETE …/sessions/:sid` | Supprime la session |
| `s.setSharing(intent)` | `PUT …/sharing` | Définit le partage et la visibilité |
| `s.participants()` | `GET …/sessions/:sid/participants` | Lit qui peut ouvrir la session et qui a envoyé chaque prompt |
| `s.watch()` | `GET …/sessions/:sid/watch` | Lit si vous recevez les [notifications](/docs/fr/work/notifications) de cette session. Nécessite `notification_center` ; `403` `feature_disabled` lorsque cette option est désactivée |
| `s.setWatch(watching)` | `PUT …/sessions/:sid/watch` | Active (`true`) ou coupe (`false`) les notifications de la session pour vous. Nécessite `notification_center` ; `403` `feature_disabled` lorsque cette option est désactivée |
| `s.presence({ tab_id, active, alerts? }, { keepalive? })` | `PUT …/sessions/:sid/presence` | Signale un onglet ouvert. `alerts: true` indique que l’onglet affiche ses propres notifications, ce qui bloque la notification push sur le téléphone. Lorsque `notification_center` est désactivé, tout onglet présent la bloque. `keepalive` permet à la requête de se poursuivre après la fermeture d’une page ; activé par défaut pour un signalement d’absence |
| `s.cost()` | `GET /usage/session-costs/:sid` | Lit le coût finalisé du LLM et du calcul sans démarrer l’environnement d’exécution |
| `s.scope()` | `GET …/sessions/:sid/scope` | Lit les restrictions de secrets enregistrées et les liaisons de connexion matérialisées |
| `s.rescope(input)` | `PUT …/sessions/:sid/scope` | Remplace les champs de portée fournis pour le prochain prompt ou appel d’outil |
| `s.commit(input?)` | — | Crée un commit du travail de l’agent |
| `s.providerSecretPool.get(providerId)` | `GET …/provider-secret-pools/:providerId` | Lit les ID des secrets de compte sélectionnés |
| `s.providerSecretPool.list()` | `GET …/provider-secret-pools` | Liste les pools configurés, y compris les pools vides, et indique `can_edit` |
| `s.providerSecretPool.set(providerId, secretIds)` | `PUT …/provider-secret-pools/:providerId` | Remplace la sélection ; `null` rétablit la valeur par défaut du projet |

:::warning
  `s.delete()` supprime la session et son environnement d’exécution. Cette opération est irréversible. Pour mettre une session en pause
  sans la perdre, appelez plutôt `s.stop()`.
:::

<a id="labels-and-metadata"></a>

## Étiquettes et métadonnées

Chaque session possède `labels: string[]` et un objet `metadata`. Utilisez-les pour
classer les sessions et décider comment votre application les affiche.

- Les **étiquettes** sont libres : les espaces en début et en fin sont supprimés, chacune compte de 1 à 64 caractères, avec un maximum de 20 par
  session et suppression des doublons. La correspondance est exacte et sensible à la casse.
- Les **métadonnées** sont un objet JSON libre pour vos propres clés, limité à 16 384
  caractères de JSON par écriture. Le serveur y stocke aussi ses propres clés
  (`custom_name`, `source`, …) ; une requête qui nomme une clé gérée par le serveur est
  refusée avec `400`. Préfixez vos clés (`app_…`) pour éviter les conflits.

```ts
// Set both at create.
const project = beeuni.project(projectId)
const created = await project.sessions.create({
  initial_prompt: 'Triage ticket T-142',
  labels: ['support', 'tier:gold'],
  metadata: { app_ticket: 'T-142', app_priority: 2 },
})

// Update later. `labels` replaces the list; `metadata` merges keys and a
// `null` value removes that key.
const s = beeuni.session(projectId, created.session_id)
await s.update({ labels: ['support', 'resolved'], metadata: { app_priority: null } })

// List by label, server-side: a session must carry EVERY given label.
const { items } = await project.sessions.listPage({ labels: ['support', 'resolved'] })

// Render conditionally.
for (const session of items) {
  const badge = session.labels?.includes('tier:gold') ? 'Gold' : null
  const ticket = session.metadata.app_ticket as string | undefined
}
```

Dans React, passez `labels` à `useProjectSessions(projectId, { labels })` ; chaque
ensemble d’étiquettes dispose de sa propre entrée de cache. Les agents définissent les mêmes champs depuis la CLI
(`beeuni sessions new --label … --meta k=v`, `beeuni sessions update --label …`)
et depuis le serveur MCP hébergé (`start_session` `labels` / `metadata`,
`list_sessions` `labels`). Une version exécutable est disponible dans `packages/sdk/examples/12-session-labels.ts`.

Lorsque `pooled_provider_secrets` et `llm_gateway` sont activés, une session peut sélectionner
plusieurs secrets de compte disponibles pour un même fournisseur. Créez une ressource à portée de projet via
`beeuni.accounts.secretResources.create(accountId, { project_id: projectId, ...input })`.
Par défaut, chaque membre du projet peut l’utiliser. Restreignez-la avec
`.setAccess(accountId, secretId, 'members', userIds)`. Les lectures renvoient les métadonnées et les autorisations, jamais
la valeur du secret. Après un `429` du fournisseur avant le début de la sortie, la passerelle
essaie une autre clé sélectionnée.

```ts
await s.providerSecretPool.set('anthropic', [primarySecretId, backupSecretId]);
const selected = await s.providerSecretPool.get('anthropic');
await s.providerSecretPool.set('anthropic', null); // inherit project default
```

Utilisez la méthode en streaming lorsque l’appelant affiche la progression du rechargement :

```ts
await s.reloadConfigStream({ refresh_repo: false }, (event) => {
  if (event.type === 'phase') console.log(event.phase);
});
```

Les phases sont `checking-session`, `refreshing-workspace`, `compiling-config`,
`applying-config` et `confirming-config`. Le serveur omet
`refreshing-workspace` lorsque `refresh_repo` vaut `false`. La
phase `applying-config` inclut le remplacement de l’environnement d’exécution validé par le démon.

`s.configState()` renvoie `release` lorsque l’API prend en charge les publications de configuration.
Son type est `SessionConfigRelease`. Les résultats du rechargement contiennent le même objet.

```ts
const { stale, release } = await s.configState();
if (release?.fallback_reason) {
  // The desired config failed on the box. An earlier config runs the session.
  console.warn(release.fallback_reason, release.running_release_id);
} else if (stale === true) {
  // Moves the session onto the base branch's current config AND
  // fast-forwards its /workspace checkout. `workspace_checkout` says what
  // happened to the checkout; `detail` words both halves.
  const result = await s.reloadConfig();
  console.log(result.workspace_checkout, result.detail);
}
```

`stale` possède trois états. `null` signifie que Beeuni n’a pas pu déterminer l’état. Testez `stale === true`,
jamais `!stale`. Une API plus ancienne omet `release`. Voir
[Convergence de la configuration](/docs/fr/work/runtime#config-convergence).

`s.participants()` renvoie `{ participants, total, multi_user }`. `participants`
liste les personnes qui peuvent actuellement ouvrir la session, le propriétaire en premier, avec un maximum de 20, chacune
`{ user_id, name, email, avatar_url, is_viewer }` ; `total` est le nombre total.
`multi_user` vaut `true` lorsque deux personnes ou plus peuvent ouvrir la session. Dans React,
`useSessionParticipants(projectId, sessionId)` lit les mêmes données. L’auteur de
chaque message est indiqué par `s.messageAuthors()`.

Trois autres méthodes de lecture complètent cet objet de contrôle :

- `s.previews()` — ports d’aperçu potentiels exposés par le runtime.
- `s.publicShares.list()` / `.create(input)` / `.revoke(shareId)` — liens de partage publics.
- `s.reminders.list()` / `.create(input)` / `.update(reminderId, { enabled })` / `.remove(reminderId)` — prompts planifiés pour cette session. Voir [Rappels](/docs/fr/connect/reminders).
  `.create({ transcript: true })` crée un lien en lecture seule vers la conversation (un seul lien actif par session ; un deuxième appel renvoie le même lien). Consultez-le anonymement avec `getPublicSessionShareMessages(token)` ; `findActiveTranscriptShare(shares)` sélectionne le lien actif dans `.list()`.
- `s.audit(limit?)` — journal d’audit des actions de l’agent pour la session.
- `s.attachments.upload(file, options?)` — enregistrer un fichier privé avant le démarrage. Maximum 50 MiB. Renvoie une URL stable pour une partie de prompt de type fichier.
- `s.attachments.read(attachmentId, signal?)` — lire les octets enregistrés sous forme de `Blob` sans démarrer le bac à sable.
- `s.transcript(options?)` — une transcription compacte côté serveur (texte et appels d’outils, sans les entrées ni les sorties des outils). Fonctionne avec un jeton de session limité au projet.
- `s.transcriptSync(options?)` — la transcription que le serveur enregistre à la fin de chaque tour, sous forme d’enveloppes de messages runtime. Chaque partie est conservée à l’identique — texte, raisonnement et chaque appel d’outil avec son entrée, sa sortie, son erreur et ses métadonnées — à l’exception des octets des pièces jointes. Répond lorsque le bac à sable est arrêté ou encore en cours de réveil, sans le démarrer. Passez `limit` (500 au maximum) pour obtenir la fenêtre la plus récente, et le `next_cursor` d’une fenêtre comme `before` pour lire la fenêtre précédente. Une fenêtre est également limitée à 1 000 000 de caractères JSON : elle conserve ses messages les plus récents, et `next_cursor` désigne alors le plus ancien message conservé. Passez `child` (l’identifiant `ses_…` d’un sous-agent, la session ouverte par une ligne de sous-agent) pour lire la transcription enregistrée de ce sous-agent ; chaque sous-agent lancé par la conversation est enregistré. `source` vaut `mirror` ; `complete` n’est vrai que lorsque la fenêtre contient tous les messages enregistrés et que la copie enregistrée remonte au premier message de la session. Une session dont le runtime a été lu intégralement et ne contenait aucun message renvoie `available: true`, `complete: true`, `total: 0` et aucun message. Un appel d’outil enregistré avant le 2026-09-28, lorsque l’historique enregistré omettait les entrées et sorties des outils, est fourni avec l’entrée attestée par son titre ou ses métadonnées, et, pour une commande, avec sa sortie également.
- `s.voiceTranscript(options?)` — transcription de l’appel vocal en cours de cette session (tours parlés et appels d’outils des workers `ask_beeuni`/`run_command`). Renvoie une liste vide lorsque la session n’a pas d’appel en cours, et non une erreur 404.

<a id="files-a-session-stored"></a>

### Fichiers stockés par une session

`findSessionAttachments(messages)` répertorie tous les fichiers stockés auxquels une transcription
fait référence, dans l’ordre et une seule fois chacun : un fichier envoyé par un utilisateur, une référence `<file>` dans un
prompt, ou la copie que l’historique enregistré conserve d’un fichier présenté par l’agent. Il lit
indifféremment une transcription en direct ou enregistrée, et ne lève jamais d’exception si elle est mal formée.

```ts
import { findSessionAttachments } from '@beeuni/sdk'

const saved = await s.transcriptSync({ limit: 500 })
for (const file of findSessionAttachments(saved.messages)) {
  const blob = await s.attachments.read(file.attachment_id) // no sandbox needed
  console.log(file.filename, file.role, blob.size)
}
```

Chaque résultat est un `{ url, attachment_id, filename, mime, message_id, role }`.
`role` vaut `user` pour un fichier envoyé et `assistant` pour un fichier présenté par l’agent ;
`mime` vaut null pour ce dernier, et les octets portent leur propre type.

<a id="who-wrote-each-message"></a>

## Auteur de chaque message

Chaque message a un auteur. Le serveur le détermine à partir des identifiants qui ont envoyé le
prompt : un membre du projet ou l’agent d’une autre session. La transcription du runtime ne
le contient pas.

```ts
const { authors, initial_author } = await beeuni.session(projectId, sessionId).messageAuthors()
// getSessionMessageAuthors(projectId, sessionId) is the same call as a function.

const author = authors[runtimeMessageId] // undefined when the sender is unknown
```

`authors` est indexé par identifiant de message du runtime. `initial_author` est la session parente pour
le premier message d’une session créée. Il vaut `null` lorsque la session n’avait pas de premier
prompt. Chaque auteur est un `SessionMessageAuthor` :

| `kind` | Champs |
| --- | --- |
| `'member'` | `user_id`, `name`, `email` (`string \| null`) |
| `'session'` | `session_id`, `name` (le titre de la session), `agent` (l’agent qu’elle exécute, s’il est nommé) |

<a id="which-model-answered-a-turn"></a>

## Modèle ayant répondu à un tour

Un message de transcription indique le modèle demandé pour son tour. Lorsque la
[chaîne de repli](/docs/fr/project/models) du projet répond avec un autre modèle, seul l’enregistrement
de la requête dans la passerelle contient le modèle qui a répondu et ce que Beeuni a facturé.

```ts
const { latest, billed_cost, turns } = await beeuni.session(projectId, sessionId).modelUsage()
// getSessionModelUsage(projectId, sessionId) is the same call as a function.

latest?.served_model   // 'glm-5.3-flash': the model that answered the newest request
latest?.fallback_from  // 'codex/gpt-6.1-sol' when a fallback model answered, else null
turns[userMessageId]   // { served_models, fallback_from, billed_cost } for one turn
```

`latest` vaut `null` avant la première réponse. `billed_cost` est le montant en USD débité par Beeuni pour
les appels aux modèles de la session. `turns` est indexé par l’identifiant de message du runtime du prompt qui
a lancé chaque tour. `served_models` répertorie les modèles qui ont répondu pendant ce tour, par
nombre de requêtes décroissant. Dans React, `useSessionModelUsage(projectId, sessionId)` lit le même
enregistrement ; appelez son `refetch()` à la fin d’un tour.

Dans React, `useSessionMessageAuthors(projectId, sessionId, messageCount)` lit les
mêmes données. Passez le nombre de messages de la transcription. Un nouveau message est le seul événement qui
ajoute un auteur ; la requête récupère donc à nouveau les données lorsque ce nombre change.

<a id="readiness-is-a-handshake"></a>

## La disponibilité passe par une négociation

Avant d’envoyer un prompt, appelez `ensureReady()`. Il provisionne le bac à sable si
nécessaire, attend le démarrage du runtime et renvoie le runtime résolu.

```ts
const { runtimeSessionId, runtimeUrl, sandboxId } = await s.ensureReady();
```

Lors d’un démarrage à froid, `ensureReady()` peut lever `RUNTIME_UNAVAILABLE`. Voir
[Réessayer lors d’un démarrage à froid](#retry-on-a-cold-boot) pour comprendre ce que cela signifie et comment
réessayer.

`s.send()` et `s.abort()` appellent `ensureReady()` pour vous.

<a id="seed-a-server-authorized-runtime-session-pin"></a>

### Initialiser un ancrage de session du runtime autorisé par le serveur

Un hôte React rendu côté serveur peut fournir l’ancrage de session du runtime déjà persisté pour
la même session Beeuni :

```tsx
const session = useSession(projectId, sessionId, {
  initialRuntimeSessionId: persistedSession.runtime_session_id ?? persistedSession.opencode_session_id,
});
```

La valeur initiale hydrate uniquement le contenu de transcription en cache pendant l’exécution de `/start`. Elle ne
remplace pas l’identité du runtime. L’ancrage renvoyé par `/start` fait
autorité et remplace une valeur initiale obsolète.

`opencode_session_id` est l’ancien nom, désormais obsolète, de `runtime_session_id`. Les deux
portent la même valeur ; ne lisez l’ancien nom qu’en dernier recours, comme ci-dessus.

N’acceptez pas cette valeur depuis un sélecteur de locataire non fiable. Ne créez pas de
session du runtime dans l’hôte. Beeuni crée et persiste la session racine.
Les caches de requêtes du runtime et les contrôleurs de transcription sont limités au runtime
du bac à sable ; des identifiants de session du runtime identiques dans des bacs à sable différents ne partagent donc pas
d’entrées de cache.

Les nouvelles tentatives de récupération des messages en attente conservent l’URL de leur bac à sable d’origine lorsque vous changez de
session. Une réponse de message `404` ou `410` arrête les nouvelles tentatives automatiques sans
effacer la transcription. Une réconciliation explicite peut retenter la lecture et
rétablir la synchronisation normale après une réponse réussie.

<a id="send-a-prompt"></a>

## Envoyer un prompt

```ts
s.setModel({ providerID, modelID }); // sticky for later send() calls
s.setAgent('build'); // sticky for later send() calls

await s.send('Refactor the auth module');
await s.send('One-off task', { model, agent }); // overrides for this call only
await s.abort(); // stop the current run
```

`send()` place le prompt dans la boîte de réception durable des prompts de la session
(`POST .../prompts`), le chemin utilisé par l’éditeur web et la CLI. Il se résout
lorsque le prompt est stocké, avec la ligne de la boîte de réception (`prompt_id`, `state`,
`message_id`), et non à la fin du tour. Lisez la réponse dans `s.stream()` ou dans la
transcription.

Pour les sessions REST runtime, le premier `send()` sur un handle lit le modèle et
l’agent persistés dans la session Beeuni. Cela empêche une session
runtime héritée d’un instantané de réutiliser les valeurs par défaut obsolètes de cet instantané.

L’ordre de priorité des choix du prompt est :

1. L’appel `send()`.
2. La valeur `setModel()` ou `setAgent()` du handle.
3. La valeur par défaut persistée de la session Beeuni.

`setModel` choisit uniquement ce que le prochain `send` local demande — cette valeur ne quitte jamais le
handle. Pour **persister** un nouveau modèle côté serveur pour une session en cours, utilisez
`changeModel` :

```ts
const { applied_live } = await s.changeModel('anthropic/claude-opus-4-8');
```

Le changement prend effet en redémarrant le runtime, ce qui met fin à tout tour en cours.
`applied_live` vaut `true` lorsqu’une session en cours l’a appliqué immédiatement, et `false` lorsqu’il
s’applique au prochain démarrage. Seuls le propriétaire de la session ou un appelant disposant des autorisations de gestionnaire de projet
peuvent changer le modèle ; tous les autres reçoivent `403`.

`send()` résout le runtime, puis lui envoie le prompt. `abort()` arrête l’exécution
en cours sans supprimer la session.

<a id="session-scope-and-cost"></a>

## Périmètre et coût de la session

Lisez la restriction des secrets stockée et les liaisons de connexion matérialisées.
`secrets_allowlist: null` signifie que l’autorisation de secrets de l’agent s’applique :

```ts
const scope = await s.scope();
scope.connector_bindings_configured; // false = inherits the project defaults
```

`connector_bindings` est la table RÉSOLUE : elle a donc le même aspect pour une session
qui a remplacé ses connecteurs et pour une session qui hérite des valeurs par défaut du projet. Lisez
`connector_bindings_configured` pour les distinguer avant d’afficher la portée ou de
la renvoyer.

Remplacez un ou les deux champs de portée :

```ts
await s.rescope({
  secrets: ['DATABASE_URL'],
  connector_bindings: {
    github: { connection_id: connectionId },
  },
});
```

Chaque champ fourni remplace intégralement sa valeur précédente. Omettez un champ pour le laisser
inchangé. Les changements de connexion s’appliquent au prochain appel d’outil.
La suppression d’un secret empêche sa transmission future, mais ne peut pas retirer une valeur déjà divulguée
du contexte du modèle ou d’un processus existant.

Les deux axes disposent d’un moyen explicite de revenir à la valeur par défaut. Ce n’est pas la même chose qu’une
valeur vide :

```ts
await s.rescope({
  secrets: null, // inherit the agent's secret grant
  connector_bindings: null, // drop the override; inherit the project defaults
});
```

`secrets: []` et `connector_bindings: {}` donnent l’instruction inverse :
explicitement « aucun secret du projet » et « aucun connecteur », y compris ceux définis par défaut dans le projet.
Une session qui envoie `{}` alors qu’elle voulait `null` refuse par défaut tout
alias qu’elle n’a pas nommé.

Lisez le relevé unifié des coûts :

```ts
const cost = await s.cost();
```

Le relevé regroupe le coût LLM finalisé, le coût facturé du calcul dans le bac à sable, l’utilisation
des modèles, les totaux de tokens, la durée de calcul et les écritures comptables. `s.cost()` n’appelle pas
`ensureReady()`.

<a id="runtime-status-and-previews"></a>

## État de l’environnement d’exécution et aperçus

| Méthode                      | Renvoie                        | Utilisation                                          |
| --------------------------- | ------------------------------ | -------------------------------------------- |
| `s.health(init?)`           | `{ status, ok, health, body }` | Vérifier si l’environnement d’exécution est actif           |
| `s.previewUrl(port, path?)` | `string`                       | Obtenir une URL de proxy pour un port exposé par l’agent |
| `s.proxyUrl(url?)`          | `string \| undefined`          | Réécrire une URL localhost affichée par l’agent    |

```ts
const { ok, health } = await s.health();
const url = s.previewUrl(3000, '/docs');
```

`s.health()` ne lève jamais d’exception. Appelez-le à tout moment, même avant que la session dispose d’un
environnement d’exécution. `s.previewUrl()` et `s.proxyUrl()` nécessitent un environnement d’exécution résolu : appelez
`s.ensureReady()` d’abord, sinon ils lèvent `SessionNotReadyError`. Voir
[Erreurs de disponibilité de session](#session-readiness-errors).

<a id="what-the-runtime-supports"></a>

### Fonctionnalités prises en charge par l’environnement d’exécution

`health.capabilities` liste les fonctionnalités de session fournies par l’environnement d’exécution. Lisez-en une
avec `runtimeSupports(capabilities, capability)` et masquez la commande lorsque cet appel
renvoie `false`. Une session runtime fournit les dix ; une session pi fournit
uniquement `session.subagents` et répond aux autres par `501 feature_not_supported`
(`404` pour le document de configuration). [Harnais](/docs/fr/work/harnesses) présente la
matrice complète pour runtime et pi.

| Capacité | Fonctionnalité |
| --- | --- |
| `session.rewind` | Revenir à un message, puis le restaurer |
| `session.compact` | Résumer la conversation à la demande |
| `session.commands` | Commandes slash du projet |
| `session.fork` | Créer une branche de session à partir d’un message |
| `session.subagents` | Sessions enfants de sous-agents |
| `session.mcp` | Serveurs MCP auxquels l’environnement d’exécution se connecte lui-même |
| `session.todo` | Liste de tâches de l’environnement d’exécution |
| `session.shell` | Commande shell exécutée comme un tour |
| `session.attach` | Connecter le propre client de terminal du harnais |
| `session.config` | Document de configuration de l’environnement d’exécution (`/global/config`) ; dans tous les cas, les agents et les modèles proviennent du projet |

```ts
import { runtimeSupports } from '@beeuni/sdk';

const { health } = await s.health();
if (!runtimeSupports(health?.capabilities, 'session.compact')) hideCompact();
```

`runtimeSupports` renvoie `true` avant l’arrivée d’une réponse de santé, ainsi que pour un
environnement d’exécution qui ne liste aucune capacité `session.*` : un démon créé avant l’existence des capacités
exécute runtime, qui fournit toutes les fonctionnalités. Dans React, utilisez
[`useRuntimeSupports`](/docs/fr/sdk/react).

La réponse `start` contient aussi la liste : `SessionStartResult.capabilities` est
défini avec `stage: 'ready'`. `useSession` l’enregistre ; le contrôle des capacités est donc
correct avant même la réponse de la première sonde de santé.

<a id="status-words"></a>

## Termes d’état

Chaque hôte utilise le même vocabulaire : un état porte donc le même nom sur tous les écrans.

| Export                                     | Utilisation                                                                                          |
| ------------------------------------------ | -------------------------------------------------------------------------------------------- |
| `sessionListStatus(session, reviewCount?)` | Une session dans une liste : `needs-you`, `starting`, `running`, `done`, `stopped`, `failed`, `legacy` |
| `SESSION_LIST_STATUS[status]`              | `{ label, tone, description }` pour cet état                                               |
| `sessionConnectionLabel(connection)`       | Un libellé court pour l’ordinateur de la session, ou `null` pour `unknown` et `live`                  |
| `SESSION_NOTICE`                           | La phrase de la zone de saisie lorsque l’ordinateur n’est pas prêt                                      |
| `turnRetryLabel(secondsLeft)`              | `Retrying in 5s`, `Retrying now` ou `Waiting to retry` lorsqu’aucun compte à rebours n’est connu           |

Les tonalités sont `actionable`, `live`, `progress`, `muted` et `danger`. Le vert est réservé à
`live` et `actionable` ; une session terminée est `muted`. Un état envoyé par une
API plus récente et inconnu de cette version s’affiche comme `stopped`, jamais comme `failed`.

## Streaming

Utilisez `s.stream()` pour recevoir des événements en direct dans un script ou sur un serveur. Dans une application
React, utilisez plutôt [`useSession`](/docs/fr/sdk/react) : il gère tout le
cycle de vie de la session pour vous.

`s.stream()` est le flux d’événements de compatibilité REST d’runtime. L’API Beeuni
le relaie depuis le bac à sable. Il n’existe aucun point de terminaison WebSocket distinct. Le
transport est `fetch` avec un corps de réponse en streaming, lu via
`ReadableStream` et `TextDecoderStream`. Le SDK gère la reconnexion,
les délais progressifs entre tentatives et la vérification des signaux de vie.

Diffusez les événements d’une session :

1. Appelez d’abord `ensureReady()`. L’environnement d’exécution n’existe pas tant que le bac à sable
   n’a pas démarré.
2. Ouvrez le flux avant d’envoyer un message, afin de ne pas manquer les premiers
   événements.
3. Envoyez le message.
4. Fermez le flux lorsque vous voyez `session.idle`.

```ts
const session = beeuni.session(projectId, sessionId);
const { runtimeSessionId } = await session.ensureReady();

const stream = await session.stream({
  onEvent: (event) => {
    if (event.type === 'session.idle' && event.properties.sessionID === runtimeSessionId) {
      onTurnDone();
      stream.close();
    }
  },
});

await session.send('Refactor the auth module');
```

Le flux transmet les événements par lots, espacés de 16 ms. Les événements
`message.part.delta` consécutifs d’un lot, pour un même champ de partie, arrivent sous forme d’un seul événement.
Son `properties.delta` contient leurs textes, concaténés dans l’ordre. Son `id` est l’identifiant du dernier
événement. Son tableau `coalesced` liste les événements qu’il a remplacés. Ajoutez `delta`
au champ de la partie, comme pour un delta unique.

Le streaming nécessite `fetch` avec un véritable corps `ReadableStream` et
`TextDecoderStream`. Les navigateurs, Node 18 et versions ultérieures, Bun et Cloudflare Workers
le prennent tous en charge.

Un hôte dont le `fetch` ne permet pas le streaming fournit son propre transport. Le SDK l’appelle
une fois par connexion et gère lui-même la reconnexion, la reprise `Last-Event-ID`, les délais progressifs et
les signaux de vie :

```ts
import { configureBeeuni, notifyHostSignal, type RuntimeEventTransport } from '@beeuni/sdk';

const transport: RuntimeEventTransport = async function* ({ url, headers, signal }) {
  // Yield { data, id?, retry? } for each server-sent message of ONE connection.
  // Return when the server ends it. Throw (with `status` for a non-2xx answer)
  // when it fails.
};

configureBeeuni({ backendUrl, getToken, eventStreamTransport: transport });

// A host without `visibilitychange` / `online` events reports them itself.
notifyHostSignal('visible'); // the app is back in the foreground
notifyHostSignal('online');  // the network is back
notifyHostSignal('retry');   // a person asked to reconnect now
```

React Native ne dispose pas de `fetch` en streaming par défaut. L’application mobile Beeuni utilise
ce point d’intégration avec `react-native-sse`. `createHttpSessionSyncController` reste
disponible pour un hôte qui a uniquement besoin d’un historique limité et de la synchronisation
de l’état.

Le contrôleur charge d’abord les 10 messages les plus récents. `loadOlder()` suit le
curseur du serveur. `loadHttpSessionHistory()` suit tous les curseurs pour les exports
explicites.

<a id="event-types"></a>

### Types d’événements

Chaque événement possède un `type` et un objet `properties` contenant ses données, par
exemple `event.properties.sessionID`.

| `type`                                          | Quand il se déclenche                              |
| ----------------------------------------------- | --------------------------------------------------- |
| `message.updated` / `message.removed`           | Un message a été modifié ou supprimé.               |
| `message.part.updated` / `message.part.removed` | Une partie (texte, appel d’outil, fichier) a été enrichie ou supprimée. |
| `session.status`                                | L’état d’occupation de la session a changé.          |
| `session.idle`                                  | Le tour est terminé.                                |
| `session.error`                                 | Le tour a échoué. L’événement contient l’erreur.      |
| `question.asked`                                | L’agent a demandé une saisie.                        |
| `question.replied` / `question.rejected`        | La réponse à une question est arrivée.              |

Transformez les messages et les parties bruts en contenu affichable avec `classifyTurn`. Consultez la
[Référence du SDK](/docs/fr/sdk/reference).

<a id="retry-on-a-cold-boot"></a>

## Réessayer lors d’un démarrage à froid

`ensureReady()` interroge le point de terminaison `/start` de la session — chaque appel effectue une interrogation longue pouvant durer
jusqu’à 30 s — jusqu’à ce que l’environnement d’exécution atteigne une étape terminale `ready`/`failed`/`stopped`
ou que son délai (`readyTimeoutMs`, environ 180 s par défaut) expire. Sur une session déjà démarrée,
la première interrogation renvoie immédiatement `ready`. Lors d’un démarrage à froid, les interrogations continuent
tant que le bac à sable signale `retriable: true` : un démarrage lent prend donc simplement plus de temps
au lieu de lever une exception. Une exception `ApiError` avec `code:
'RUNTIME_UNAVAILABLE'` if the runtime is still not `ready` n’est levée que lorsque le délai
expire.

`ensureReady()` est idempotent : les appels simultanés pour la même session partagent
une seule requête `/start` au lieu d’en envoyer plusieurs. La fonction utilitaire `retryUntilReady`
ci-dessous est désormais facultative — `ensureReady()` réessaie déjà en interne — mais reste
utile si vous souhaitez un délai total plus long que le `readyTimeoutMs` par défaut.

```ts
async function retryUntilReady<T>(ensure: () => Promise<T>): Promise<T> {
  const deadline = Date.now() + 300_000;
  for (;;) {
    try {
      return await ensure();
    } catch (error) {
      const provisioning = error instanceof ApiError && error.code === 'RUNTIME_UNAVAILABLE';
      if (!provisioning || Date.now() > deadline) throw error;
      await new Promise((r) => setTimeout(r, 3_000));
    }
  }
}
```

Consultez les [Classes d’erreurs](#error-classes) pour connaître la structure complète de `ApiError`. Dans React,
[`useSession`](/docs/fr/sdk/react) réessaie `/start` pour vous : vous n’avez donc pas besoin
de ce modèle.

<a id="what-start-tells-you"></a>

## Ce que vous indique `/start`

Chaque réponse `/start` décrit **cet appel**, et non l’historique cumulé de la ligne.
Quatre champs portent ces informations.

| Champ | Signification |
| --- | --- |
| `observed_at` | Une horloge unique pour toute la réponse. |
| `action` | Ce que le serveur a fait : `inspected`, `checked_provider`, `resumed`, `provisioned`, `restored`, `reconciled`, `awaited_wake`, `cooling_down`. |
| `observation` | Ce que le serveur a vérifié. `known: false` signifie **non vérifié lors de cet appel** — jamais « vérifié sans rien trouver ». |
| `boot` | `phase` (`provisioning` / `resuming` / `booting` / `ready` / `parked` / `failed`), `since` et `actively_starting`. |

`boot.actively_starting` répond à la question « une opération du fournisseur est-elle en cours pour cette
session en ce moment ? ». Une charge utile `starting` avec `actively_starting: false` signifie
que le serveur attend la fin d’un délai avant nouvelle tentative, et non qu’une machine démarre.

```jsonc
{
  "stage": "starting",
  "retriable": true,
  "reason": "runtime_wake_cooldown",
  "observed_at": "2026-08-26T14:00:00.000Z",
  "action": "cooling_down",
  "boot": { "phase": "resuming", "since": "2026-08-26T13:58:00.000Z", "actively_starting": false },
  "observation": {
    "provider": { "known": false, "status": null, "checked_at": null },
    "runtime": { "known": false, "state": null, "boot_phase": null, "checked_at": null }
  },
  "failure": {
    "category": "sandbox-provider",
    "message": "The runtime did not start (attempt 2). Retrying automatically.",
    "retryable": true,
    "evidence": {
      "check": "provider_not_running",
      "observed_at": "2026-08-26T13:58:00.000Z",
      "error": null,
      "attempts": 2,
      "next_retry_at": "2026-08-26T14:03:00.000Z"
    }
  }
}
```

<a id="a-failed-start-is-retried-for-you"></a>

### Un démarrage qui échoue est réessayé pour vous

Un démarrage qui échoue enregistre un **délai d’attente**, et non un verdict définitif. Le prochain
`/start` après ce délai retente automatiquement le réveil. Le délai augmente
avec les échecs consécutifs (2 min, 5 min, 10 min). Après cinq échecs
consécutifs, `/start` répond `stage: "failed"` avec le nombre de tentatives dans
`failure.message` ; ce verdict expire 30 minutes après le dernier échec, et
`POST …/restart` l’efface immédiatement.

`retriable` est calculé à chaque appel. Un état pour lequel le serveur peut encore réessayer
ne contient jamais `retriable: false`.

`failure.evidence` indique la vérification qui a produit le résultat négatif, quand elle a eu lieu et
quand le serveur réessaiera. Chaque échec `/start` le contient.

<a id="prompt-attachments"></a>

## Pièces jointes aux prompts

Téléversez les fichiers locaux lorsque les utilisateurs les ajoutent à un éditeur de message. Les téléversements privés appartiennent
au projet et ne nécessitent pas de session en cours d’exécution.

```tsx
import { usePromptAttachments } from '@beeuni/sdk/react';

const uploads = usePromptAttachments(projectId);
// File picker, paste, and drop handlers call uploads.add(file) or uploads.addMany(files).
// Render uploads.attachments. Refuse Send only while an attachment is `error` or `aborted`.

async function submit() {
  const ids = uploads.attachments.map((item) => item.id);
  uploads.submit(ids); // Send never waits: the composer clears and uploads continue.
  paintMessage(text, ids);
  try {
    const parts = await uploads.whenReady(ids);
    await beeuni.session(projectId, sessionId).prompts.create({
      clientMessageId,
      messageId,
      parts: [{ type: 'text', text }, ...parts],
    });
    uploads.forget(ids);
  } catch {
    uploads.reclaim(ids);
  }
}
```

`whenReady(ids, { signal })` se résout une fois tous les téléversements prêts, dans l’ordre `ids`.
Il est rejeté lorsqu’un téléversement échoue, est interrompu ou supprimé, ou lorsque `signal` est interrompu.
`submit(ids)` transmet les entrées à cet envoi : elles quittent `attachments`, ne sont plus
comptabilisées dans les limites et continuent à être téléversées après le démontage de l’éditeur.
Appelez `forget(ids)` après la réussite du POST, ou `reclaim(ids)` pour les remettre dans
l’éditeur après un échec d’envoi.
La partie fichier résolue contient `attachment_id`, `filename` et `mime`.
Elle ne contient ni URL ni octets du fichier. Le serveur associe et matérialise le téléversement.
Chaque fichier parvient à l’ordinateur de la session, y compris les images et les PDF : avant
l’exécution du prompt, le serveur l’écrit dans `/workspace/uploads/.beeuni-inbox/` et le
prompt le référence par son chemin. Aucun octet du fichier n’est transmis dans le prompt lui-même. Lorsque
l’enregistrement de l’historique est activé, le serveur conserve aussi une copie pour la transcription. Un échec de copie
n’empêche jamais un fichier d’atteindre l’ordinateur. Un fichier que le serveur ne peut pas écrire bloque le
prompt, qui réessaie.
Utilisez ces parties sur les routes de création de session de la plateforme, de démarrage d’une session déjà démarrée ou de boîte de réception
des prompts. Ne transmettez pas directement les identifiants privés à `sendParts` de l’environnement d’exécution runtime.
Les anciennes parties fichier de la plateforme avec `url` fonctionnent toujours.

Chaque élément possède un `id` stable, la référence `file` d’origine, `receivedBytes` et
un état : `pending`, `uploading`, `processing`, `ready`, `error` ou `aborted`.
Les octets envoyés déterminent la progression. Le contrôleur publie un instantané de progression uniquement
lors d’un changement d’un point de pourcentage entier, au maximum dix fois par seconde et par téléversement. `processing` peut
rester à 100 % jusqu’à ce que le serveur termine le téléversement. Les limites sont de 50 MiB par
fichier, 100 MiB par message et 20 fichiers. Les fichiers ne doivent pas être vides. Deux fichiers
sont téléversés simultanément par défaut.

`retry(id)` reprend le même identifiant. Après `attachment_size_mismatch` ou
`attachment_failed`, le serveur ne conserve aucun identifiant utilisable : `retry(id)` téléverse donc de nouveau le
File en tant que nouvelle pièce jointe. Un téléversement expiré ne peut pas être réessayé : `retry(id)`
lève une exception, et l’erreur de l’élément contient le code `attachment_expired`. `abort(id)` annule
les opérations inachevées.
`remove(id)` supprime l’entrée, interrompt son téléversement et se résout immédiatement. Le
DELETE côté serveur est effectué au mieux et n’est jamais rejeté ; un téléversement qu’il ne peut pas supprimer
expire au bout de 24 heures.
`forget(ids)` libère les entrées sans supprimer les objets côté serveur.
Le hook interrompt les opérations listées lors du démontage ou d’un changement de projet et supprime ses téléversements
au mieux : aucun envoi ne les détient, et les brouillons ne conservent aucun identifiant. Les téléversements
transmis à un envoi continuent.

Les sélections restent uniquement en mémoire. Ne conservez jamais d’objets File, d’URL de blob, d’URL signées
ou d’identifiants de téléversement dans un brouillon. Les téléversements abandonnés expirent au bout de 24 heures.

Les hôtes sans framework utilisent `beeuni.project(projectId).attachments.createController()`
avec les mêmes méthodes. Abonnez-vous via `subscribe(listener)` et lisez
`getSnapshot()`. Appelez `dispose()` lorsque l’éditeur se ferme.
Les appelants directs peuvent utiliser `attachments.upload(file, { signal, onProgress, onUpload,
resume })` and `attachments.delete(attachmentId)`.
Conservez les métadonnées `onUpload` pour reprendre manuellement avec le même identifiant.

Le serveur sélectionne le transport dans le champ `upload` de l’identifiant :

- `kind: 'direct'` est le mode par défaut. Le SDK envoie le fichier entier dans un seul `PUT` à
  `upload.url` avec `upload.headers` et sans en-tête Authorization. Avec
  `XMLHttpRequest` (navigateurs, React Native), la progression indique les octets envoyés. Avec
  `fetch`, la progression indique 0, puis la taille totale. Une URL expirée, ou que
  Storage refuse avec 400, 401 ou 403, est signée à nouveau une seule fois pour le même
  `attachment_id`. Un `409` de Storage signifie qu'une tentative précédente a stocké le fichier.
- `kind: 'chunked'` envoie des `PUT` authentifiés et séquentiels de `upload.chunk_size`
  octets. Le SDK accepte toute valeur positive de `chunk_size`. Seul un déploiement dont la périphérie
  rejette les corps de requête volumineux sélectionne ce mode.

La finalisation vérifie la taille stockée et le SHA-256. Si la finalisation répond `409
attachment_not_uploaded`, `onUpload` signale le handle direct avec
`received_bytes: 0`, de sorte que `retry(id)` ou une reprise renvoie le fichier.
L'initialisation, l'envoi et la finalisation réessaient après des délais d'attente dépassés, des erreurs réseau, des réponses 429 et
5xx avec un délai exponentiel à variation aléatoire. Le délai alloué est de 60 secondes à partir du premier
échec : un envoi long qui échoue tardivement est donc tout de même réessayé. La finalisation réessaie aussi
`attachment_processing`, avec un délai alloué de cinq minutes. L'initialisation ne réessaie jamais après une réponse 402
(un `BillingError` : le compte ne peut pas fonctionner) ou 429 `attachment_budget_exceeded` (40
envois inachevés ou 500 MiB d'envois non transmis pour l'utilisateur ; les envois inutilisés
expirent sous 24 heures). Le serveur répond à une finalisation ou la refuse sous 105
secondes. Chaque requête de finalisation autorise 120 secondes. Les annulations par l'appelant ne sont jamais réessayées.

La référence d'une pièce jointe envoyée est libérée 1 heure après la remise de son prompt,
et lors de la suppression de sa session ou de son projet. Le prochain passage de maintenance
supprime alors le fichier, et son `attachment_id` ne peut plus être envoyé.

<a id="files"></a>

## Fichiers

`s.files` lit et écrit dans le sandbox de la session : `list`, `read`, `readBlob`,
`status`, `findFiles`, `findText`, `upload`, `create`, `copy`, `remove`,
`mkdir`, `rename`. Chaque appel résout d'abord l'environnement d'exécution et cible toujours
le sandbox propre à cette session. Consultez la [référence du SDK](/docs/fr/sdk/reference) pour
la liste complète des méthodes.

<a id="session-verbs"></a>

## Verbes de session

Le handle lit l'environnement d'exécution et lui répond pour vous. Chaque verbe provisionne d'abord la
session et cible le sandbox propre à cette session, quel que soit le harnais qui l'exécute.

```ts
// A page of the transcript, oldest first: { info, parts } messages (beeuni.transcript.v1).
const { messages, hasMore } = await s.messages({ limit: 50 });
// What the agent waits on.
const { statuses, permissions, questions } = await s.pending();
await s.answerPermission(permissions[0].id, 'once'); // 'once' | 'always' | 'reject'
await s.answerQuestion(questions[0].id, [['Yes']]); // null dismisses the question
// Summarize the conversation. Needs the `session.compact` capability (see health()).
await s.compact();
```

`s.runtime`, le client de compatibilité REST brut, est obsolète. Utilisez les verbes
ci-dessus avec `send`, `abort`, `rewind` et `stream`.

<a id="warm-a-project-session"></a>

## Préchauffer une session de projet

Appelez `ensureWarm()` lorsqu'une page d'accueil de projet a besoin d'un environnement d'exécution prêt avant
le premier prompt.

```ts
const project = beeuni.project(projectId);
const warm = await project.sessions.ensureWarm();

// An ORDINARY session. Prompt it like any other.
await beeuni.session(projectId, warm.session.session_id).send("Build me a widget");
```

`ensureWarm()` crée ou renvoie une session inutilisée pour l'utilisateur actuel. Il
s'agit de la même création qu'exécute `sessions.create()`, avec les paramètres par défaut du projet : même
contrôle de facturation, mêmes exigences de connecteurs. La seule
différence est `metadata.warm`, qui masque la session dans
`sessions.list()` jusqu'à l'arrivée de son premier prompt.

Considérez cela comme une opération spéculative. Un `409 WARM_SESSION_UNAVAILABLE` signifie que le projet
ne peut pas être préchauffé pour le moment — passez à `sessions.create()`, qui indique
la véritable raison.

La session préchauffée utilise l'agent DEFAULT et le sandbox du projet. Si l'utilisateur
en choisit un autre, abandonnez-la et appelez `sessions.create()` : une session préchauffée inutilisée
est masquée et supprimée automatiquement.

:::warning
`claimWarm()` est obsolète. Une session préchauffée est une session ordinaire : il n'y a donc
rien à revendiquer — ouvrez-la et envoyez-lui un prompt. L'appel fonctionne toujours pour
les consommateurs qui restent sur l'ancien format et sera supprimé dans la prochaine version majeure.
:::

<a id="handling-errors"></a>

## Gestion des erreurs

Chaque appel via `createBeeuni` est rejeté avec une sous-classe typée de `Error`,
jamais avec un objet simple. Interceptez l'erreur, vérifiez `instanceof` et traitez les cas selon
`.status` ou `.code`.

```ts
import { ApiError, BillingError } from '@beeuni/sdk';

try {
  await beeuni.project(projectId).sessions.create();
} catch (err) {
  if (err instanceof BillingError) {
    // 402 — out of credits or over a plan limit
  } else if (err instanceof ApiError) {
    // any other failed request — err.status, err.code, err.detail
  } else {
    throw err;
  }
}
```

<a id="error-classes"></a>

### Classes d'erreur

| Classe                  | Hérite de    | Quand elle est levée                                                                 | Champs clés                                                           |
| ---------------------- | ---------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------- |
| `ApiError`             | `Error`    | Par défaut pour toute requête échouée : statut incorrect, erreur réseau, délai d'attente dépassé ou annulation | `status`, `code`, `detail`, `response`, `url`, `endpoint`, `timeout` |
| `AuthError`            | `ApiError` | `getToken` a renvoyé `null`. Beeuni n'a jamais envoyé la requête                      | `code` vaut toujours `'NO_SESSION'`                                      |
| `BillingError`         | `ApiError` | HTTP `402`. La seule classe d'erreur de facturation                                       | `status` (`402`), `code`, `detail.message`                           |
| `RequestTooLargeError` | `ApiError` | HTTP `431`. Généralement trop de fichiers dans une requête                              | `detail.suggestion`                                                  |
| `SessionNotReadyError` | `Error`    | Un accesseur de l'environnement d'exécution a été exécuté avant `ensureReady()`                                  | `name` vaut `'SessionNotReadyError'`                                   |

`ApiError.name` vaut `'ApiError'` par défaut. Deux cas remplacent cette valeur :

- `name: 'AbortError'`, `code: 'ABORTED'` — la requête a été annulée, par exemple lors d'une navigation. Ce n'est pas un échec. Ignorez-la.
- `code: 'TIMEOUT'` — le délai d'attente propre à la requête a expiré. `url`, `endpoint` et `timeout` indiquent ce qui a dépassé le délai.

Pour tout autre échec, `status` contient le code de statut HTTP. `code` provient de `error_code` du backend ou, à défaut, du statut sous forme de chaîne. `message` est une propriété propre énumérable de `ApiError` : elle est donc conservée par `JSON.stringify` et par la décomposition d'objet.

Beeuni renvoie une fois une requête qui reçoit `401` avec un nouveau jeton obtenu via `getToken()`, lorsque ce jeton diffère de celui envoyé. Une requête dont le corps est un flux à usage unique n'est pas renvoyée.

Beeuni réessaie certaines requêtes avant que votre code ne voie une erreur. Si une requête `GET` ou `HEAD` renvoie `502`, `503` ou `504`, Beeuni la réessaie jusqu'à 2 fois, après un délai de 250 ms puis de 500 ms. Une erreur de transport transitoire sur un `GET` ou un `HEAD` — une erreur réseau, pas un code de statut — est réessayée de la même manière. Une nouvelle tentative réussie n'atteint jamais `onError`. Beeuni ne réessaie jamais les requêtes `POST`, `PUT`, `PATCH` ou `DELETE`, ni une réponse `500`.

Beeuni lève `AuthError` côté client, avant d'envoyer une requête, lorsque `getToken()` renvoie `null`. `AuthError` hérite de `ApiError`, donc `err instanceof ApiError` correspond toujours. Vérifiez `err instanceof AuthError` ou `err.code === 'NO_SESSION'` pour traiter le cas « non connecté » séparément d'un échec du backend.

Beeuni lève `BillingError` pour chaque réponse HTTP `402` : crédits épuisés, limite du forfait dépassée ou autre contrôle de facturation. `detail.message` contient la raison fournie par le backend, et `code` contient le code machine du backend (par exemple `app_account_unfunded`). `BillingError` hérite de `ApiError` : vérifiez donc `BillingError` en premier lorsque vous gérez les deux.

Beeuni lève `RequestTooLargeError` pour HTTP `431`. Cela signifie généralement que la requête contenait trop de fichiers. `detail.suggestion` contient une indication prête à être affichée à l'utilisateur.

<a id="session-readiness-errors"></a>

### Erreurs de disponibilité de la session

Deux erreurs signifient que le sandbox de la session n'est pas encore prêt. Gérez chacune différemment.

`SessionNotReadyError` lève une erreur de manière synchrone lorsque vous appelez un accesseur du runtime — `session.previewUrl()` ou `session.proxyUrl()` — avant que ce handle de session ait résolu son sandbox. Un handle de session ne résout que son propre sandbox ; il ne se rabat jamais sur celui d’une autre session.

```ts
import { SessionNotReadyError } from '@beeuni/sdk';

const s = beeuni.session(projectId, sessionId);
try {
  const url = s.previewUrl(3000); // throws: not resolved yet
} catch (err) {
  if (err instanceof SessionNotReadyError) {
    await s.ensureReady();
  }
}
```

Appelez d’abord `await session.ensureReady()`, ou appelez `send()`, qui prépare la session en interne. `session.health()` est le seul accesseur qui ne lève jamais cette erreur ; vous pouvez donc l’interroger périodiquement avant le démarrage de la session.

`RUNTIME_UNAVAILABLE` est la deuxième erreur — elle signifie que `ensureReady()` a lui-même dépassé le délai d’attente d’un démarrage à froid. Consultez [Réessayer lors d’un démarrage à froid](#retry-on-a-cold-boot) pour le schéma complet. Dans React, `useSession` réessaie pour vous et expose cette erreur via la valeur `phase` au lieu de la lever.

<a id="helpers"></a>

### Fonctions utilitaires

| Fonction utilitaire              | Signature                           | Rôle                                                                                                          |
| -------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `parseBillingError(error)`       | `(error) => Error`                  | Encapsule une réponse `402` dans un `BillingError`. Renvoie les autres erreurs telles quelles                                  |
| `isBillingError(error)`          | `(error) => boolean`                | Renvoie `error instanceof BillingError`                                                                       |
| `formatBillingErrorForUI(error)` | `(error) => BillingErrorUI \| null` | Renvoie `null` pour les erreurs sans rapport avec la facturation. Sinon, renvoie `{ alertTitle, alertSubtitle }` pour une fenêtre modale de mise à niveau |
| `isRuntimeNotReadyResponse(error)` | `(error) => boolean` | Vrai pour le `503` du démon du sandbox tant que le runtime de la session ne peut pas encore accepter de requête. La requête n’a pas été transmise ; il est donc sûr de réessayer. Même résultat sur runtime et pi |
| `isSandboxNotReadyError(error)` | `(error) => boolean` | Vrai pour le cas ci-dessus et pour un sandbox arrêté, en cours de démarrage ou en veille. Affichez-le comme « en cours de réveil » et continuez à l’interroger périodiquement |
| `isRuntimeStartingError(error)` | `(error) => boolean` | Vrai pour les deux cas ci-dessus et pour une URL de runtime qui n’est pas encore fixée. Une limite d’erreur réessaie dans ce cas au lieu d’afficher un plantage |

```ts
import { formatBillingErrorForUI } from '@beeuni/sdk';

try {
  await beeuni.session(projectId, sessionId).start();
} catch (err) {
  const ui = formatBillingErrorForUI(err);
  if (ui) showUpgradeModal(ui.alertTitle, ui.alertSubtitle);
}
```

<a id="in-beeunisdkreact"></a>

### Dans `@beeuni/sdk/react`

`@beeuni/sdk/react` réexporte `BillingError`, `RequestTooLargeError`, `parseBillingError`, `isBillingError` et `formatBillingErrorForUI`. Il ne réexporte pas `ApiError` ni `AuthError` — importez-les depuis `@beeuni/sdk`.

`useSession` classe chaque échec de `send`, `answerQuestion`, `answerPermission` et `rejectQuestion` dans un objet `sendError`, ce qui vous évite d’écrire manuellement des vérifications `instanceof` :

```ts
interface BeeuniSendError {
  kind: 'billing' | 'runtime-not-ready' | 'runtime-error';
  message: string;
  billing?: BillingError; // set when kind is 'billing'
  cause: unknown;
}
```

```tsx
const s = useSession(projectId, sessionId);

if (s.sendError?.kind === 'billing') {
  const ui = formatBillingErrorForUI(s.sendError.billing);
}
```

Consultez [Hooks React](/docs/fr/sdk/react) pour le reste de `useSession`.
