# Sessioni

Esegui una sessione, ricevi i suoi eventi in streaming e gestisci gli errori che può generare.

Canonical page: https://beeuni.ai/docs/it/sdk/sessions

Una sessione è una singola esecuzione di un agente, nella propria sandbox e sul proprio branch git.
`beeuni.session(projectId, sessionId)` restituisce l'handle per tutto ciò che una
sessione fa: avviarla, inviare prompt, ricevere eventi in streaming e leggere lo stato. Questa
pagina descrive l'handle, l'handshake di disponibilità, lo streaming e gli errori
tipizzati che una chiamata SDK può generare.

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

`s` è l'handle per tutto ciò che una sessione fa. L'ID della sessione, l'ID della sandbox
e il nome del branch hanno lo stesso valore. Vedi
[Sessioni](/docs/it/work/sessions) per il concetto.

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

## Ciclo di vita della sessione

| Metodo | Incapsula | Cosa fa |
|---|---|---|
| `s.get(opts?)` | `GET /projects/:pid/sessions/:sid` | Legge i dettagli della sessione |
| `s.update(input)` | `PATCH …/sessions/:sid` | Rinomina la sessione, sostituisce le sue etichette o unisce le chiavi dei metadati |
| `s.start(waitMs?)` | `POST …/sessions/:sid/start` | Predispone e avvia il runtime |
| `s.restart()` | `POST …/sessions/:sid/restart` | Riavvia il runtime; mantiene la stessa sandbox |
| `s.configState()` | `GET …/sessions/:sid/config` | Legge quanto è aggiornata la configurazione (`stale`) e lo stato del rilascio della configurazione (`release`) |
| `s.reloadConfig(input?)` | `POST …/sessions/:sid/reload` | Ricompila la configurazione dell'agente e sostituisce il runtime dopo la validazione |
| `s.reloadConfigStream(input, onEvent)` | `POST …/sessions/:sid/reload-stream` | Esegue lo stesso ricaricamento ed emette fasi di avanzamento confermate dal server |
| `s.stop()` | `POST …/sessions/:sid/stop` | Arresta il runtime; la sessione rimane |
| `s.delete()` | `DELETE …/sessions/:sid` | Elimina la sessione |
| `s.setSharing(intent)` | `PUT …/sharing` | Imposta condivisione e visibilità |
| `s.participants()` | `GET …/sessions/:sid/participants` | Legge chi può aprire la sessione e chi ha inviato ciascun prompt |
| `s.watch()` | `GET …/sessions/:sid/watch` | Legge se ricevi le [notifiche](/docs/it/work/notifications) di questa sessione. Richiede `notification_center`; `403` `feature_disabled` mentre è disattivato |
| `s.setWatch(watching)` | `PUT …/sessions/:sid/watch` | Attiva (`true`) o silenzia (`false`) le notifiche della sessione per te. Richiede `notification_center`; `403` `feature_disabled` mentre è disattivato |
| `s.presence({ tab_id, active, alerts? }, { keepalive? })` | `PUT …/sessions/:sid/presence` | Segnala una scheda aperta. `alerts: true` indica che la scheda mostra le proprie notifiche, quindi la notifica push sul telefono viene trattenuta. Con `notification_center` disattivato, qualsiasi scheda presente la trattiene. `keepalive` consente alla richiesta di sopravvivere alla chiusura della pagina; attivo per impostazione predefinita per una segnalazione di assenza |
| `s.cost()` | `GET /usage/session-costs/:sid` | Legge il costo definitivo di LLM e calcolo senza avviare il runtime |
| `s.scope()` | `GET …/sessions/:sid/scope` | Legge le restrizioni dei segreti memorizzate e le associazioni di connessione materializzate |
| `s.rescope(input)` | `PUT …/sessions/:sid/scope` | Sostituisce i campi di ambito forniti per il prossimo prompt o la prossima chiamata a uno strumento |
| `s.commit(input?)` | — | Esegue il commit del lavoro dell'agente |
| `s.providerSecretPool.get(providerId)` | `GET …/provider-secret-pools/:providerId` | Legge gli ID dei segreti dell'account selezionati |
| `s.providerSecretPool.list()` | `GET …/provider-secret-pools` | Elenca i pool configurati, inclusi quelli vuoti, e riporta `can_edit` |
| `s.providerSecretPool.set(providerId, secretIds)` | `PUT …/provider-secret-pools/:providerId` | Sostituisce la selezione; `null` ripristina l'impostazione predefinita del progetto |

> `s.delete()` elimina la sessione e il suo runtime. Questa operazione è irreversibile. Per mettere in pausa una sessione
> senza perderla, chiama invece `s.stop()`.

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

## Etichette e metadati

Ogni sessione contiene `labels: string[]` e un oggetto `metadata`. Usali per
classificare le sessioni e decidere come la tua app le visualizza.

- **Etichette** sono a formato libero: gli spazi iniziali e finali vengono rimossi, ciascuna contiene 1–64 caratteri, al massimo 20 per
  sessione, i duplicati vengono rimossi. La corrispondenza è esatta e distingue maiuscole e minuscole.
- **Metadati** sono un oggetto JSON a formato libero per le tue chiavi, con un massimo di 16.384
  caratteri JSON per scrittura. Il server vi memorizza anche le proprie chiavi
  (`custom_name`, `source`, …); una richiesta che specifica una chiave gestita dal server viene
  rifiutata con `400`. Aggiungi un prefisso alle tue chiavi (`app_…`) per evitare conflitti.

```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
}
```

In React, passa `labels` a `useProjectSessions(projectId, { labels })`; ogni
insieme di etichette ha la propria voce nella cache. Gli agenti impostano gli stessi campi dalla CLI
(`beeuni sessions new --label … --meta k=v`, `beeuni sessions update --label …`)
e dal server MCP ospitato (`start_session` `labels` / `metadata`,
`list_sessions` `labels`). Una versione eseguibile è `packages/sdk/examples/12-session-labels.ts`.

Con `pooled_provider_secrets` e `llm_gateway` abilitati, una sessione può selezionare
più segreti dell'account disponibili per un provider. Crea una risorsa con ambito di progetto tramite
`beeuni.accounts.secretResources.create(accountId, { project_id: projectId, ...input })`.
Per impostazione predefinita, ogni membro del progetto può usarla. Limitane l'accesso con
`.setAccess(accountId, secretId, 'members', userIds)`. Le letture restituiscono metadati e autorizzazioni, mai
il valore del segreto. Dopo un `429` del provider prima dell'inizio dell'output, il gateway
prova un'altra chiave selezionata.

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

Usa il metodo in streaming quando il chiamante visualizza l'avanzamento del ricaricamento:

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

Le fasi sono `checking-session`, `refreshing-workspace`, `compiling-config`,
`applying-config` e `confirming-config`. Il server omette
`refreshing-workspace` quando `refresh_repo` è `false`. La
fase `applying-config` include la sostituzione del runtime validata dal daemon.

`s.configState()` restituisce `release` quando l'API supporta i rilasci della configurazione.
Il suo tipo è `SessionConfigRelease`. I risultati del ricaricamento contengono lo stesso oggetto.

```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` ha tre stati. `null` significa che Beeuni non ha potuto determinarlo. Verifica `stale === true`,
mai `!stale`. Un'API meno recente omette `release`. Vedi
[Convergenza della configurazione](/docs/it/work/runtime#config-convergence).

`s.participants()` restituisce `{ participants, total, multi_user }`. `participants`
elenca le persone che possono aprire la sessione ora, prima il proprietario, al massimo 20, ciascuna
`{ user_id, name, email, avatar_url, is_viewer }`; `total` è il conteggio totale.
`multi_user` è `true` quando due o più persone possono aprire la sessione. In React,
`useSessionParticipants(projectId, sessionId)` legge gli stessi dati. Chi ha scritto
ciascun messaggio è `s.messageAuthors()`.

Altri tre metodi di lettura completano l'handle:

- `s.previews()` — possibili porte di anteprima esposte dal runtime.
- `s.publicShares.list()` / `.create(input)` / `.revoke(shareId)` — link di condivisione pubblici.
- `s.reminders.list()` / `.create(input)` / `.update(reminderId, { enabled })` / `.remove(reminderId)` — prompt programmati per questa sessione. Vedi [Promemoria](/docs/it/connect/reminders).
  `.create({ transcript: true })` crea un link di sola lettura alla conversazione (un solo link attivo per sessione; una seconda chiamata restituisce lo stesso link). Leggila anonimamente con `getPublicSessionShareMessages(token)`; `findActiveTranscriptShare(shares)` seleziona quello attivo da `.list()`.
- `s.audit(limit?)` — il registro di audit delle azioni dell'agente nella sessione.
- `s.attachments.upload(file, options?)` — salva un file privato prima dell'avvio. Massimo 50 MiB. Restituisce un URL stabile per una parte file di un prompt.
- `s.attachments.read(attachmentId, signal?)` — legge i byte salvati come `Blob` senza avviare la sandbox.
- `s.transcript(options?)` — una trascrizione compatta lato server (testo e chiamate agli strumenti, senza input o output degli strumenti). Funziona con un token di sessione con ambito di progetto.
- `s.transcriptSync(options?)` — la trascrizione che il server salva alla fine di ogni turno, sotto forma di envelope di messaggi runtime. Ogni parte viene mantenuta 1:1 — testo, ragionamento e ogni chiamata a uno strumento con input, output, errore e metadati — tranne i byte degli allegati. Risponde mentre la sandbox è arrestata o ancora in fase di avvio, senza avviarla. Passa `limit` (massimo 500) per la finestra più recente e il valore `next_cursor` di una finestra come `before` per leggere quella precedente. Una finestra si ferma anche a 1.000.000 di caratteri JSON: conserva i messaggi più recenti e `next_cursor` indica quindi il più vecchio tra quelli conservati. Passa `child` (l'id `ses_…` di un sottoagente, la sessione aperta dalla riga di un sottoagente) per leggere la trascrizione salvata di quel sottoagente; viene salvato ogni sottoagente avviato dalla conversazione. `source` è `mirror`; `complete` è true solo quando la finestra contiene tutti i messaggi salvati e la copia salvata arriva al primo messaggio della sessione. Una sessione il cui runtime è stato letto completamente e non conteneva messaggi risponde con `available: true`, `complete: true`, `total: 0` e nessun messaggio. Una chiamata a uno strumento salvata prima del 2026-09-28, quando la cronologia salvata ometteva gli input e gli output degli strumenti, viene restituita con l'input attestato dal titolo o dai metadati e, se si tratta di un comando, anche con il suo output.
- `s.voiceTranscript(options?)` — la trascrizione della chiamata vocale in corso di questa sessione (turni parlati più chiamate agli strumenti dei worker `ask_beeuni`/`run_command`). Restituisce un elenco vuoto quando la sessione non ha una chiamata in corso, non un 404.

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

### File archiviati da una sessione

`findSessionAttachments(messages)` elenca ogni file archiviato a cui una trascrizione
fa riferimento, in ordine e una sola volta: un caricamento dell'utente, un riferimento `<file>` in un
prompt o la copia che la cronologia salvata conserva di un file mostrato dall'agente. Legge sia una
trascrizione in tempo reale sia una salvata e non genera mai eccezioni se è malformata.

```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)
}
```

Ogni risultato è `{ url, attachment_id, filename, mime, message_id, role }`.
`role` è `user` per un caricamento e `assistant` per un file mostrato dall'agente;
`mime` è null per quest'ultimo e i byte contengono il proprio tipo.

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

## Chi ha scritto ogni messaggio

Ogni messaggio ha un autore. Il server lo ricava dalla credenziale che ha inviato il
prompt: un membro del progetto o l'agente di un'altra sessione. La trascrizione del runtime non
lo contiene.

```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` è indicizzato per id del messaggio del runtime. `initial_author` è la sessione padre per
il primo messaggio di una sessione creata. È `null` quando la sessione non aveva un primo
prompt. Ogni autore è un `SessionMessageAuthor`:

| `kind` | Campi |
| --- | --- |
| `'member'` | `user_id`, `name`, `email` (`string \| null`) |
| `'session'` | `session_id`, `name` (il titolo della sessione), `agent` (l'agente che esegue, se ha un nome) |

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

## Quale modello ha risposto a un turno

Un messaggio della trascrizione indica il modello richiesto dal suo turno. Quando la
[catena di fallback](/docs/it/project/models) del progetto risponde usando un altro modello, solo il
record della richiesta nel gateway contiene il modello che ha risposto e quanto Beeuni ha addebitato.

```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` è `null` prima della prima risposta. `billed_cost` è l'importo in USD addebitato da Beeuni per
le chiamate ai modelli della sessione. `turns` è indicizzato per l'id del messaggio del runtime del prompt che
ha avviato ogni turno. `served_models` elenca i modelli che hanno risposto in quel turno, prima quelli con il maggior numero di
richieste. In React, `useSessionModelUsage(projectId, sessionId)` legge lo stesso
record; chiama il suo `refetch()` quando un turno termina.

In React, `useSessionMessageAuthors(projectId, sessionId, messageCount)` legge gli
stessi dati. Passa il numero di messaggi della trascrizione. Un nuovo messaggio è l'unico evento che
aggiunge un autore, quindi la query recupera nuovamente i dati quando il conteggio cambia.

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

## La disponibilità richiede un handshake

Prima di inviare un prompt, chiama `ensureReady()`. Predispone la sandbox se
necessario, attende l'avvio del runtime e restituisce il runtime risolto.

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

Durante un avvio a freddo, `ensureReady()` può generare `RUNTIME_UNAVAILABLE`. Vedi
[Riprovare durante un avvio a freddo](#retry-on-a-cold-boot) per sapere cosa significa e come
riprovare.

`s.send()` e `s.abort()` chiamano `ensureReady()` per te.

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

### Inizializzare un pin di sessione del runtime autorizzato dal server

Un host React con rendering lato server può fornire il pin di sessione del runtime già persistito per
la stessa sessione Beeuni:

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

Il valore iniziale idrata solo il contenuto della trascrizione nella cache mentre `/start` è in esecuzione. Non
sovrascrive l'identità del runtime. Il pin restituito da `/start` è
quello autorevole e sostituisce un valore iniziale obsoleto.

`opencode_session_id` è il nome deprecato di `runtime_session_id`. Entrambi
contengono lo stesso valore; leggi il vecchio nome solo come fallback, come sopra.

Non accettare questo valore da un selettore di tenant non attendibile. Non creare una
sessione del runtime nell'host. Beeuni crea e persiste la sessione radice.
Le cache delle query del runtime e i controller delle trascrizioni hanno come ambito il runtime
della sandbox, quindi id di sessione del runtime uguali in sandbox diverse non condividono
voci della cache.

I tentativi in sospeso di reinvio dei messaggi mantengono l'URL della sandbox di origine quando cambi
sessione. Una risposta a un messaggio `404` o `410` interrompe i tentativi automatici senza
cancellare la trascrizione. Una riconciliazione esplicita può ritentare la lettura e
ripristinare la normale sincronizzazione dopo una risposta riuscita.

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

## Inviare 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()` inserisce il prompt nella casella di posta persistente dei prompt della sessione
(`POST .../prompts`), il percorso usato dal compositore web e dalla CLI. Si risolve
quando il prompt viene archiviato, con la riga della casella di posta (`prompt_id`, `state`,
`message_id`), non quando il turno termina. Leggi la risposta da `s.stream()` o dalla
trascrizione.

Per le sessioni REST runtime, il primo `send()` su un handle legge il modello e
l'agente persistiti nella sessione Beeuni. Questo impedisce a una sessione
runtime ereditata da uno snapshot di riutilizzare valori predefiniti obsoleti dello snapshot.

L'ordine di precedenza delle scelte del prompt è:

1. La chiamata `send()`.
2. Il valore `setModel()` o `setAgent()` dell'handle.
3. Il valore predefinito persistito della sessione Beeuni.

`setModel` sceglie solo ciò che il successivo `send` locale richiede: non esce mai
dall'handle. Per **persistere** un nuovo modello lato server per una sessione in esecuzione, usa
`changeModel`:

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

La modifica entra in vigore riavviando il runtime, quindi un turno in corso termina.
`applied_live` è `true` quando una sessione in esecuzione l'ha applicata subito, `false` quando
si applica all'avvio successivo. Solo il proprietario della sessione o un chiamante con autorizzazioni da responsabile
di progetto può cambiare il modello; chiunque altro riceve `403`.

`send()` risolve il runtime, poi gli invia il prompt. `abort()` interrompe l'esecuzione
corrente senza eliminare la sessione.

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

## Ambito e costo della sessione

Leggi la restrizione dei segreti memorizzata e le associazioni delle connessioni materializzate.
`secrets_allowlist: null` indica che si applica l'autorizzazione ai segreti dell'agente:

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

`connector_bindings` è la mappa RESOLVED, quindi appare identica per una sessione
che ha sovrascritto i propri connettori e una che eredita i valori predefiniti del progetto. Leggi
`connector_bindings_configured` per distinguerle prima di visualizzare l'ambito o
reinviarlo.

Sostituisci uno o entrambi i campi dell'ambito:

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

Ogni campo fornito sostituisce interamente il suo valore precedente. Ometti un campo per lasciarlo
invariato. Le modifiche alle connessioni si applicano alla successiva chiamata a uno strumento.
La rimozione di un segreto ne interrompe la consegna futura, ma non può rimuovere un valore già divulgato
dal contesto del modello o da un processo esistente.

Entrambi gli assi hanno un modo esplicito per tornare al valore predefinito. Non equivalgono a un
valore vuoto:

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

`secrets: []` e `connector_bindings: {}` sono l'istruzione opposta: un
esplicito "nessun segreto del progetto" e "nessun connettore", inclusi i valori predefiniti
del progetto. Una sessione che invia `{}` quando intendeva `null` nega l'accesso a ogni
alias che non ha specificato.

Leggi il record unificato dei costi:

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

Il record combina il costo LLM definitivo, il costo di calcolo della sandbox fatturato, l'utilizzo
del modello, i totali dei token, la durata del calcolo e le voci del registro contabile. `s.cost()` non
chiama `ensureReady()`.

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

## Stato del runtime e anteprime

| Metodo                      | Restituisce                        | Utilizzo                                          |
| --------------------------- | ------------------------------ | -------------------------------------------- |
| `s.health(init?)`           | `{ status, ok, health, body }` | Verificare se il runtime è attivo           |
| `s.previewUrl(port, path?)` | `string`                       | Ottenere un URL proxy per una porta esposta dall'agente |
| `s.proxyUrl(url?)`          | `string \| undefined`          | Riscrivere un URL localhost stampato dall'agente    |

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

`s.health()` non genera mai eccezioni. Chiamalo in qualsiasi momento, anche prima che la sessione abbia un
runtime. `s.previewUrl()` e `s.proxyUrl()` richiedono un runtime risolto: chiama
prima `s.ensureReady()`, altrimenti generano `SessionNotReadyError`. Vedi
[Errori di disponibilità della sessione](#session-readiness-errors).

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

### Cosa supporta il runtime

`health.capabilities` elenca le funzionalità della sessione offerte dal runtime. Leggine una
con `runtimeSupports(capabilities, capability)` e nascondi il controllo quando
restituisce `false`. Una sessione runtime le offre tutte e dieci; una sessione pi offre
solo `session.subagents` e risponde alle altre con `501 feature_not_supported`
(`404` per il documento di configurazione). [Harness](/docs/it/work/harnesses) contiene la
matrice completa di runtime e pi.

| Capacità | Funzionalità |
| --- | --- |
| `session.rewind` | Tornare a un messaggio e ripristinarlo |
| `session.compact` | Riassumere la conversazione su richiesta |
| `session.commands` | Comandi slash del progetto |
| `session.fork` | Creare una diramazione della sessione a partire da un messaggio |
| `session.subagents` | Sessioni figlie dei sottoagenti |
| `session.mcp` | Server MCP a cui il runtime si connette autonomamente |
| `session.todo` | La lista delle attività del runtime |
| `session.shell` | Un comando shell eseguito come turno |
| `session.attach` | Collegare il client terminale dell'harness |
| `session.config` | Un documento di configurazione del runtime (`/global/config`); in entrambi i casi, agenti e modelli provengono dal progetto |

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

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

`runtimeSupports` restituisce `true` prima che arrivi una risposta sullo stato di salute e per un
runtime che non elenca alcuna capacità `session.*`: un daemon compilato prima dell'introduzione delle capacità
esegue runtime, che offre tutte le funzionalità. In React, usa
[`useRuntimeSupports`](/docs/it/sdk/react).

Anche la risposta `start` contiene l'elenco: `SessionStartResult.capabilities` viene
impostato con `stage: 'ready'`. `useSession` lo registra, quindi un controllo delle capacità è
corretto prima che risponda la prima verifica dello stato di salute.

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

## Termini di stato

Ogni host usa lo stesso vocabolario, quindi ogni stato ha un unico termine su tutte le schermate.

| Esportazione                                     | Utilizzo                                                                                          |
| ------------------------------------------ | -------------------------------------------------------------------------------------------- |
| `sessionListStatus(session, reviewCount?)` | Una sessione in un elenco: `needs-you`, `starting`, `running`, `done`, `stopped`, `failed`, `legacy` |
| `SESSION_LIST_STATUS[status]`              | `{ label, tone, description }` per quello stato                                               |
| `sessionConnectionLabel(connection)`       | Un'etichetta breve per il computer della sessione, oppure `null` per `unknown` e `live`                  |
| `SESSION_NOTICE`                           | La frase del compositore mentre il computer non è pronto                                      |
| `turnRetryLabel(secondsLeft)`              | `Retrying in 5s`, `Retrying now` o `Waiting to retry` quando non è noto alcun conto alla rovescia           |

I toni sono `actionable`, `live`, `progress`, `muted` e `danger`. Il verde è riservato
solo a `live` e `actionable`; una sessione terminata è `muted`. Uno stato inviato da un'API
più recente e non riconosciuto da questa build viene visualizzato come `stopped`, mai come `failed`.

## Streaming

Usa `s.stream()` per ricevere eventi in tempo reale in uno script o su un server. In un'app
React, usa invece [`useSession`](/docs/it/sdk/react): gestisce per te l'intero
ciclo di vita della sessione.

`s.stream()` è il flusso di eventi compatibile con REST di runtime. L'API Beeuni
lo inoltra dalla sandbox tramite proxy. Non esiste un endpoint WebSocket separato. Il
trasporto è `fetch` con un corpo della risposta in streaming, letto tramite
`ReadableStream` e `TextDecoderStream`. L'SDK gestisce la riconnessione,
il backoff e un controllo heartbeat.

Avvia lo streaming di una sessione:

1. Chiama prima `ensureReady()`. Il runtime non esiste finché la sandbox
   non si avvia.
2. Apri il flusso prima di inviare un messaggio, per non perdere gli
   eventi iniziali.
3. Invia il messaggio.
4. Chiudi il flusso quando vedi `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');
```

Il flusso consegna gli eventi in batch, a intervalli di 16 ms. Gli eventi
`message.part.delta` consecutivi per un campo di una parte in un batch arrivano come un unico evento.
Il suo `properties.delta` è il loro testo, concatenato in ordine. Il suo `id` è l'id
dell'ultimo evento. Il suo array `coalesced` elenca gli eventi che ha sostituito. Aggiungi `delta`
al campo della parte, come per un singolo delta.

Lo streaming richiede `fetch` con un vero corpo `ReadableStream` e
`TextDecoderStream`. Browser, Node 18 e versioni successive, Bun e Cloudflare Workers
lo supportano tutti.

Un host il cui `fetch` non supporta lo streaming fornisce il proprio trasporto. L'SDK lo chiama
una volta per connessione e gestisce autonomamente riconnessione, ripresa `Last-Event-ID`, backoff e
heartbeat:

```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 non dispone di `fetch` in streaming per impostazione predefinita. L'app mobile Beeuni usa
questo punto di integrazione con `react-native-sse`. `createHttpSessionSyncController` resta
disponibile per un host che richiede solo una cronologia limitata e la sincronizzazione
dello stato.

Il controller carica prima i 10 messaggi più recenti. `loadOlder()` segue il
cursore del server. `loadHttpSessionHistory()` segue ogni cursore per le esportazioni
esplicite.

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

### Tipi di evento

Ogni evento ha un `type` e un oggetto `properties` che contiene i suoi dati, per
esempio `event.properties.sessionID`.

| `type`                                          | Quando si attiva                                      |
| ----------------------------------------------- | --------------------------------------------------- |
| `message.updated` / `message.removed`           | Un messaggio è stato modificato o eliminato.                   |
| `message.part.updated` / `message.part.removed` | Una parte (testo, chiamata a uno strumento, file) è cresciuta o è stata rimossa. |
| `session.status`                                | Lo stato di attività della sessione è cambiato.                   |
| `session.idle`                                  | Il turno è terminato.                                  |
| `session.error`                                 | Il turno non è riuscito. L'evento contiene l'errore.       |
| `question.asked`                                | L'agente ha richiesto un input.                          |
| `question.replied` / `question.rejected`        | È arrivata la risposta a una domanda.                   |

Trasforma i messaggi e le parti non elaborati in output visualizzabile con `classifyTurn`. Consulta
[il riferimento SDK](/docs/it/sdk/reference).

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

## Nuovo tentativo all'avvio a freddo

`ensureReady()` interroga l'endpoint `/start` della sessione — ogni chiamata esegue un long polling
fino a 30 s — finché il runtime non raggiunge una fase terminale `ready`/`failed`/`stopped`
o non scade il suo termine (`readyTimeoutMs`, ~180 s per impostazione predefinita). In una sessione
calda, il primo polling restituisce subito `ready`. Durante un avvio a freddo continua a eseguire il polling
mentre la sandbox segnala `retriable: true`, quindi un avvio lento richiede semplicemente più tempo
invece di generare un errore. Genera un errore `ApiError` con `code:
'RUNTIME_UNAVAILABLE'` if the runtime is still not `ready` solo alla scadenza del termine.

`ensureReady()` è idempotente, quindi le chiamate simultanee per la stessa sessione condividono
una sola richiesta `/start` invece di inviarne diverse. L'helper `retryUntilReady`
qui sotto è ora facoltativo — `ensureReady()` esegue già nuovi tentativi internamente — ma rimane
utile se vuoi un budget totale più lungo del valore predefinito `readyTimeoutMs`.

```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));
    }
  }
}
```

Consulta [Classi di errore](#error-classes) per la struttura completa di `ApiError`. In React,
[`useSession`](/docs/it/sdk/react) esegue nuovi tentativi di `/start` per te, quindi non devi
usare questo schema.

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

## Cosa indica `/start`

Ogni risposta `/start` descrive **quella chiamata**, non la cronologia accumulata
della riga. Quattro campi la descrivono.

| Campo | Significato |
| --- | --- |
| `observed_at` | Un unico orologio per l'intera risposta. |
| `action` | Cosa ha fatto il server: `inspected`, `checked_provider`, `resumed`, `provisioned`, `restored`, `reconciled`, `awaited_wake`, `cooling_down`. |
| `observation` | Cosa ha verificato il server. `known: false` significa **non verificato in questa chiamata** — mai "verificato e non trovato nulla". |
| `boot` | `phase` (`provisioning` / `resuming` / `booting` / `ready` / `parked` / `failed`), `since` e `actively_starting`. |

`boot.actively_starting` risponde alla domanda "è in corso un'operazione del provider per questa
sessione in questo momento?". Un payload `starting` con `actively_starting: false` significa
che il server sta attendendo il termine del cooldown di un nuovo tentativo, non che una macchina si stia avviando.

```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 avvio non riuscito viene ritentato automaticamente

Un avvio non riuscito imposta un **cooldown**, non un verdetto permanente. Il successivo
`/start` dopo il cooldown riprova automaticamente il risveglio. Il cooldown aumenta
con i fallimenti consecutivi (2 min, 5 min, 10 min). Dopo cinque fallimenti consecutivi,
`/start` restituisce `stage: "failed"` con il numero di tentativi in
`failure.message`; questo verdetto scade 30 minuti dopo l'ultimo fallimento e
`POST …/restart` lo annulla immediatamente.

`retriable` viene derivato a ogni chiamata. Uno stato che il server può ancora ritentare
non contiene mai `retriable: false`.

`failure.evidence` indica il controllo che ha prodotto l'esito negativo, quando è stato eseguito
e quando il server ritenterà. Ogni fallimento `/start` lo contiene.

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

## Allegati del prompt

Carica i file locali quando gli utenti li aggiungono a un composer. I caricamenti privati appartengono al
progetto e non richiedono una sessione in esecuzione.

```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 })` si risolve quando ogni caricamento è pronto, nell'ordine `ids`.
Viene rifiutato quando un caricamento non riesce, viene interrotto o rimosso oppure quando `signal`
viene interrotto. `submit(ids)` assegna le voci a quell'invio: escono da `attachments`, smettono
di essere conteggiate nei limiti e continuano il caricamento anche dopo lo smontaggio del composer.
Chiama `forget(ids)` dopo il completamento del POST oppure `reclaim(ids)` per restituirle al composer dopo un invio non riuscito.
La parte file risolta contiene `attachment_id`, `filename` e `mime`.
Non contiene URL né byte del file. Il server associa e materializza il caricamento.
Ogni file raggiunge il computer della sessione, incluse immagini e PDF: prima dell'esecuzione
del prompt, il server lo scrive in `/workspace/uploads/.beeuni-inbox/` e il prompt vi fa riferimento tramite il percorso.
Nel prompt non vengono trasmessi byte del file. Con la cronologia salvata, il server conserva anche
una copia per la trascrizione. Una copia non riuscita non conserva mai un file sul computer. Un file
che il server non riesce a scrivere blocca il prompt, che viene ritentato.
Usa queste parti nelle route di creazione delle sessioni della piattaforma, di avvio delle sessioni calde
o della posta in arrivo dei prompt. Non passare direttamente gli handle privati al runtime `sendParts`.
Le parti file legacy della piattaforma con `url` continuano a funzionare.

Ogni elemento ha un `id` stabile, il riferimento `file` originale, `receivedBytes` e
uno stato: `pending`, `uploading`, `processing`, `ready`, `error` o `aborted`.
I byte inviati determinano l'avanzamento. Il controller pubblica un'istantanea dell'avanzamento solo
per una variazione di una percentuale intera, al massimo dieci volte al secondo per caricamento.
`processing` può rimanere al 100% finché il server non completa il caricamento. I limiti sono 50 MiB per
file, 100 MiB per messaggio e 20 file. I file devono essere non vuoti. Per impostazione predefinita,
due file vengono caricati contemporaneamente.

`retry(id)` riprende lo stesso handle. Dopo `attachment_size_mismatch` o
`attachment_failed` il server non conserva alcun handle utilizzabile, quindi `retry(id)` carica di nuovo
il File come nuovo allegato. Un caricamento scaduto non può essere ritentato: `retry(id)`
genera un errore e l'errore dell'elemento contiene il codice `attachment_expired`. `abort(id)` annulla
il lavoro non completato.
`remove(id)` rimuove la voce, interrompe il relativo caricamento e si risolve immediatamente. La DELETE
del server è best-effort e non viene mai rifiutata; un caricamento che non può essere eliminato
scade dopo 24 ore.
`forget(ids)` rilascia le voci senza eliminare gli oggetti del server.
L'hook interrompe il lavoro elencato allo smontaggio o al cambio di progetto ed elimina i relativi caricamenti
in modalità best-effort: nessun invio li conserva e le bozze non mantengono alcun handle. I caricamenti
trasferiti continuano.

Le selezioni risiedono solo in memoria. Non rendere mai persistenti gli oggetti File, gli URL blob,
gli URL firmati o gli handle di caricamento in una bozza. I caricamenti abbandonati scadono dopo 24 ore.

Gli host senza framework usano `beeuni.project(projectId).attachments.createController()`
con gli stessi metodi. Sottoscriviti tramite `subscribe(listener)` e leggi
`getSnapshot()`. Chiama `dispose()` quando il composer si chiude.
I chiamanti diretti possono usare `attachments.upload(file, { signal, onProgress, onUpload,
resume })` and `attachments.delete(attachmentId)`.
Conserva i metadati `onUpload` per la ripresa manuale con lo stesso ID.

Il server seleziona il trasporto nel campo `upload` dell'handle:

- `kind: 'direct'` è l'impostazione predefinita. L'SDK invia l'intero file in un unico `PUT` a
  `upload.url` con `upload.headers` e senza header Authorization. Con
  `XMLHttpRequest` (browser, React Native), l'avanzamento indica i byte inviati. Con
  `fetch`, l'avanzamento indica 0 e poi la dimensione totale. Un URL scaduto, o uno che
  Storage rifiuta con 400, 401 o 403, viene firmato nuovamente una sola volta per lo stesso
  `attachment_id`. Un `409` da Storage significa che un tentativo precedente ha memorizzato il file.
- `kind: 'chunked'` invia richieste `PUT` autenticate in sequenza da `upload.chunk_size`
  byte. L'SDK accetta qualsiasi `chunk_size` positivo. Solo un deployment il cui edge
  scarta corpi di richiesta di grandi dimensioni seleziona questa modalità.

Il completamento verifica la dimensione memorizzata e SHA-256. Se il completamento risponde `409
attachment_not_uploaded`, `onUpload` riporta l'handle diretto con
`received_bytes: 0`, quindi `retry(id)` o una ripresa invia nuovamente il file.
L'avvio, il caricamento e il completamento ritentano in caso di timeout, errori di rete, 429 e
5xx con backoff esponenziale e jitter. Il tempo massimo è di 60 secondi dal primo
errore, quindi anche un caricamento lungo che fallisce verso la fine viene ritentato. Il completamento ritenta anche
`attachment_processing`, con un tempo massimo di cinque minuti. L'avvio non ritenta mai 402
(un `BillingError`: l'account non può essere eseguito) o 429 `attachment_budget_exceeded` (40
caricamenti incompleti o 500 MiB di caricamenti non inviati per l'utente; i caricamenti inutilizzati
scadono entro 24 ore). Il server risponde o rifiuta un completamento entro 105
secondi. Ogni richiesta di completamento consente 120 secondi. Le interruzioni da parte del chiamante non vengono mai ritentate.

Il riferimento a un allegato inviato viene rilasciato 1 ora dopo la consegna del suo prompt,
e quando la sua sessione o il suo progetto viene eliminato. Il successivo ciclo di manutenzione
rimuove quindi il file e il suo `attachment_id` non può più essere inviato.

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

## File

`s.files` legge e scrive nella sandbox della sessione: `list`, `read`, `readBlob`,
`status`, `findFiles`, `findText`, `upload`, `create`, `copy`, `remove`,
`mkdir`, `rename`. Ogni chiamata risolve prima il runtime e si rivolge sempre
alla sandbox di questa sessione. Consulta il [riferimento SDK](/docs/it/sdk/reference) per
l'elenco completo dei metodi.

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

## Verbi della sessione

L'handle legge il runtime e gli risponde per te. Ogni verbo predispone prima la
sessione e si rivolge alla sandbox di questa sessione, qualunque sia l'harness che la esegue.

```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`, il client di compatibilità REST grezzo, è deprecato. Usa i verbi
qui sopra con `send`, `abort`, `rewind` e `stream`.

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

## Preriscaldare una sessione di progetto

Chiama `ensureWarm()` quando la pagina iniziale di un progetto richiede un runtime pronto prima
del primo 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()` crea, o restituisce, una sessione inutilizzata per l'utente corrente. Si
tratta della stessa creazione eseguita da `sessions.create()`, con le impostazioni predefinite del progetto: stessi
vincoli di fatturazione, stessi requisiti dei connettori. L'unica
differenza è `metadata.warm`, che nasconde la sessione da
`sessions.list()` finché non arriva il primo prompt.

Considerala un'operazione speculativa. Un `409 WARM_SESSION_UNAVAILABLE` significa che il progetto
non può essere preriscaldato al momento: passa a `sessions.create()`, che riporta
il motivo reale.

La sessione preriscaldata usa l'agente DEFAULT e la sandbox del progetto. Se l'utente
ne sceglie uno diverso, abbandonala e chiama `sessions.create()`: una sessione preriscaldata
inutilizzata è nascosta e viene rimossa automaticamente.

> `claimWarm()` è deprecato. Una sessione preriscaldata è una sessione ordinaria, quindi non c'è
> nulla da rivendicare: aprila e inviale un prompt. La chiamata funziona ancora per
> chi è vincolato alla struttura precedente e verrà rimossa nella prossima versione principale.

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

## Gestione degli errori

Ogni chiamata tramite `createBeeuni` viene rifiutata con una sottoclasse tipizzata di `Error`,
mai con un semplice oggetto. Intercetta l'errore, controlla `instanceof` e scegli il percorso in base a
`.status` o `.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>

### Classi di errore

| Classe                  | Estende    | Quando viene generata                                                                 | Campi principali                                                           |
| ---------------------- | ---------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------- |
| `ApiError`             | `Error`    | Predefinita per qualsiasi richiesta non riuscita: stato errato, errore di rete, timeout o interruzione | `status`, `code`, `detail`, `response`, `url`, `endpoint`, `timeout` |
| `AuthError`            | `ApiError` | `getToken` ha restituito `null`. Beeuni non ha mai inviato la richiesta                      | `code` è sempre `'NO_SESSION'`                                      |
| `BillingError`         | `ApiError` | HTTP `402`. L'unica classe di errore di fatturazione                                       | `status` (`402`), `code`, `detail.message`                           |
| `RequestTooLargeError` | `ApiError` | HTTP `431`. Di solito troppi file in una richiesta                              | `detail.suggestion`                                                  |
| `SessionNotReadyError` | `Error`    | Un accessor del runtime è stato eseguito prima di `ensureReady()`                                  | `name` è `'SessionNotReadyError'`                                   |

`ApiError.name` è `'ApiError'` per impostazione predefinita. Due casi lo sovrascrivono:

- `name: 'AbortError'`, `code: 'ABORTED'` — la richiesta è stata annullata, ad esempio dalla navigazione. Non è un errore. Ignoralo.
- `code: 'TIMEOUT'` — è scaduto il timeout della richiesta stessa. `url`, `endpoint` e `timeout` mostrano cosa è andato in timeout.

Per qualsiasi altro errore, `status` contiene il codice di stato HTTP. `code` proviene da `error_code` del backend oppure usa lo stato come stringa. `message` è una proprietà propria enumerabile di `ApiError`, quindi viene mantenuta con `JSON.stringify` e con l'operatore spread sugli oggetti.

Beeuni invia nuovamente una richiesta che riceve `401` con un nuovo token da `getToken()`, quando quel token è diverso da quello inviato. Una richiesta con un corpo stream utilizzabile una sola volta non viene reinviata.

Beeuni ritenta alcune richieste prima che il tuo codice riceva un errore. Se una richiesta `GET` o `HEAD` restituisce `502`, `503` o `504`, Beeuni la ritenta fino a 2 volte, con un ritardo di 250 ms e poi di 500 ms. Un errore transitorio di trasporto su una `GET` o `HEAD` — un errore di rete, non un codice di stato — viene ritentato allo stesso modo. Un tentativo riuscito non raggiunge mai `onError`. Beeuni non ritenta mai richieste `POST`, `PUT`, `PATCH` o `DELETE`, né una risposta `500`.

Beeuni genera `AuthError` sul client, prima di inviare una richiesta, quando `getToken()` restituisce `null`. `AuthError` estende `ApiError`, quindi `err instanceof ApiError` continua a corrispondere. Controlla `err instanceof AuthError` o `err.code === 'NO_SESSION'` per trattare "accesso non effettuato" come un caso distinto da un errore del backend.

Beeuni genera `BillingError` per ogni risposta HTTP `402`: crediti esauriti, limite del piano superato o altro vincolo di fatturazione. `detail.message` contiene il motivo fornito dal backend e `code` contiene il codice macchina del backend (ad esempio `app_account_unfunded`). `BillingError` estende `ApiError`, quindi controlla prima `BillingError` quando gestisci entrambi.

Beeuni genera `RequestTooLargeError` per HTTP `431`. Di solito significa che la richiesta conteneva troppi file. `detail.suggestion` contiene un suggerimento pronto da mostrare all'utente.

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

### Errori di disponibilità della sessione

Due errori indicano che la sandbox della sessione non è ancora pronta. Gestiscili in modo diverso.

`SessionNotReadyError` viene generato in modo sincrono quando chiami un accessor del runtime — `session.previewUrl()` o `session.proxyUrl()` — prima che questo handle di sessione abbia risolto la propria sandbox. Un handle di sessione risolve solo la propria sandbox; non usa mai come fallback la sandbox di un'altra sessione.

```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();
  }
}
```

Chiama prima `await session.ensureReady()`, oppure chiama `send()`, che prepara internamente la sessione. `session.health()` è l'unico accessor che non genera mai questo errore, quindi puoi interrogarlo periodicamente prima che la sessione si avvii.

`RUNTIME_UNAVAILABLE` è il secondo errore: significa che `ensureReady()` stesso ha superato il tempo limite in attesa di un avvio a freddo. Vedi [Riprovare in caso di avvio a freddo](#retry-on-a-cold-boot) per il pattern completo. In React, `useSession` riprova per te e lo espone tramite il valore `phase` invece di generarlo.

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

### Funzioni di supporto

| Funzione di supporto             | Firma                               | Cosa fa                                                                                                       |
| -------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `parseBillingError(error)`       | `(error) => Error`                  | Racchiude una risposta `402` in un `BillingError`. Restituisce gli altri errori invariati                                  |
| `isBillingError(error)`          | `(error) => boolean`                | Restituisce `error instanceof BillingError`                                                                       |
| `formatBillingErrorForUI(error)` | `(error) => BillingErrorUI \| null` | Restituisce `null` per gli errori non relativi alla fatturazione. Altrimenti restituisce `{ alertTitle, alertSubtitle }` per una finestra modale di upgrade |
| `isRuntimeNotReadyResponse(error)` | `(error) => boolean` | True per `503` del daemon della sandbox mentre il runtime della sessione non può ancora accettare una richiesta. La richiesta non è stata inoltrata, quindi è sicuro riprovare. Stesso risultato su runtime e pi |
| `isSandboxNotReadyError(error)` | `(error) => boolean` | True per il caso sopra e per una sandbox arrestata, in avvio o parcheggiata. Visualizzala come "in riattivazione" e continua a interrogarla periodicamente |
| `isRuntimeStartingError(error)` | `(error) => boolean` | True per entrambi i casi sopra e per un URL del runtime non ancora fissato. Un error boundary riprova in questo caso invece di mostrare un arresto anomalo |

```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);
}
```

### In `@beeuni/sdk/react`

`@beeuni/sdk/react` riesporta `BillingError`, `RequestTooLargeError`, `parseBillingError`, `isBillingError` e `formatBillingErrorForUI`. Non riesporta `ApiError` né `AuthError`: importali da `@beeuni/sdk`.

`useSession` classifica ogni errore di `send`, `answerQuestion`, `answerPermission` e `rejectQuestion` in un unico oggetto `sendError`, così non devi scrivere manualmente i controlli `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);
}
```

Vedi [Hook React](/docs/it/sdk/react) per il resto di `useSession`.
