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 con400. 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 congetPublicSessionShareMessages(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 comeBlobsenza 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. Passalimit(massimo 500) per la finestra più recente e il valorenext_cursordi una finestra comebeforeper leggere quella precedente. Una finestra si ferma anche a 1.000.000 di caratteri JSON: conserva i messaggi più recenti enext_cursorindica quindi il più vecchio tra quelli conservati. Passachild(l’idses_…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 conavailable: true,complete: true,total: 0e 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 workerask_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 è:
- La chiamata
send(). - Il valore
setModel()osetAgent()dell’handle. - 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:
- Chiama prima
ensureReady(). Il runtime non esiste finché la sandbox non si avvia. - Apri il flusso prima di inviare un messaggio, per non perdere gli eventi iniziali.
- Invia il messaggio.
- 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 unicoPUTaupload.urlconupload.headerse senza header Authorization. ConXMLHttpRequest(browser, React Native), l’avanzamento indica i byte inviati. Confetch, 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 stessoattachment_id. Un409da Storage significa che un tentativo precedente ha memorizzato il file.kind: 'chunked'invia richiestePUTautenticate in sequenza daupload.chunk_sizebyte. L’SDK accetta qualsiasichunk_sizepositivo. 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,endpointetimeoutmostrano 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.