Vai al contenuto
BeeuniBeeuni
Italiano
Esc
↑↓naviga↵apri⌘Janteprima
In questa pagina

Sessioni

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

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.

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 per il concetto.

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

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.
// 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.

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:

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.

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.

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. .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 — 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.

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.

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.

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.

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)

Quale modello ha risposto a un turno

Un messaggio della trascrizione indica il modello richiesto dal suo turno. Quando la catena di fallback 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.

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.

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.

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

Durante un avvio a freddo, ensureReady() può generare RUNTIME_UNAVAILABLE. Vedi Riprovare durante un avvio a freddo per sapere cosa significa e come riprovare.

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

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:

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.

Inviare un prompt

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:

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.

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:

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:

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:

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:

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().

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
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.

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 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
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.

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.

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: 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.
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:

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.

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.

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.

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 per la struttura completa di ApiError. In React, useSession esegue nuovi tentativi di /start per te, quindi non devi usare questo schema.

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.

{
  "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"
    }
  }
}

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.

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.

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.

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 per l’elenco completo dei metodi.

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.

// 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.

Preriscaldare una sessione di progetto

Chiama ensureWarm() quando la pagina iniziale di un progetto richiede un runtime pronto prima del primo prompt.

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.

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.

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

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.

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.

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 per il pattern completo. In React, useSession riprova per te e lo espone tramite il valore phase invece di generarlo.

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
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:

interface BeeuniSendError {
  kind: 'billing' | 'runtime-not-ready' | 'runtime-error';
  message: string;
  billing?: BillingError; // set when kind is 'billing'
  cause: unknown;
}
const s = useSession(projectId, sessionId);

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

Vedi Hook React per il resto di useSession.

Questa pagina ti è stata utile?