Sitzungen
Führe eine Sitzung aus, streame ihre Ereignisse und behandle die Fehler, die sie auslösen kann.
Eine Sitzung ist ein einzelner Agentenlauf in einer eigenen Sandbox auf einem eigenen Git-Branch.
beeuni.session(projectId, sessionId) gibt das Handle für alles zurück, was eine
Sitzung tut: sie starten, Prompts senden, Ereignisse streamen und den Status lesen. Diese
Seite behandelt das Handle, den Bereitschafts-Handshake, das Streaming und die typisierten
Fehler, die ein SDK-Aufruf auslösen kann.
const s = beeuni.session(projectId, sessionId);
s ist das Handle für alles, was eine Sitzung tut. Sitzungs-ID, Sandbox-
ID und Branch-Name haben denselben Wert. Das Konzept wird unter
Sitzungen erläutert.
Lebenszyklus einer Sitzung
| Methode | Kapselt | Funktion |
|---|---|---|
s.get(opts?) |
GET /projects/:pid/sessions/:sid |
Liest Sitzungsdetails |
s.update(input) |
PATCH …/sessions/:sid |
Benennt die Sitzung um, ersetzt ihre Labels oder führt Metadatenschlüssel zusammen |
s.start(waitMs?) |
POST …/sessions/:sid/start |
Stellt die Laufzeitumgebung bereit und startet sie |
s.restart() |
POST …/sessions/:sid/restart |
Startet die Laufzeitumgebung neu; behält dieselbe Sandbox bei |
s.configState() |
GET …/sessions/:sid/config |
Liest die Aktualität der Konfiguration (stale) und den Freigabestatus der Konfiguration (release) |
s.reloadConfig(input?) |
POST …/sessions/:sid/reload |
Kompiliert die Agentenkonfiguration neu und ersetzt die Laufzeitumgebung nach der Validierung |
s.reloadConfigStream(input, onEvent) |
POST …/sessions/:sid/reload-stream |
Führt denselben Neuladevorgang aus und gibt serverbestätigte Fortschrittsphasen aus |
s.stop() |
POST …/sessions/:sid/stop |
Stoppt die Laufzeitumgebung; die Sitzung bleibt bestehen |
s.delete() |
DELETE …/sessions/:sid |
Löscht die Sitzung |
s.setSharing(intent) |
PUT …/sharing |
Legt Freigabe und Sichtbarkeit fest |
s.participants() |
GET …/sessions/:sid/participants |
Liest, wer die Sitzung öffnen kann und wer jeden Prompt gesendet hat |
s.watch() |
GET …/sessions/:sid/watch |
Liest, ob du die Benachrichtigungen dieser Sitzung erhältst. Benötigt notification_center; 403 feature_disabled, solange es deaktiviert ist |
s.setWatch(watching) |
PUT …/sessions/:sid/watch |
Aktiviert (true) oder deaktiviert (false) die Benachrichtigungen der Sitzung für dich. Benötigt notification_center; 403 feature_disabled, solange es deaktiviert ist |
s.presence({ tab_id, active, alerts? }, { keepalive? }) |
PUT …/sessions/:sid/presence |
Meldet einen geöffneten Tab. alerts: true gibt an, dass der Tab seine eigenen Benachrichtigungen anzeigt, sodass die Push-Benachrichtigung auf dem Smartphone zurückgehalten wird. Wenn notification_center deaktiviert ist, hält jeder vorhandene Tab sie zurück. keepalive lässt die Anfrage über das Schließen einer Seite hinaus bestehen; bei einer Abwesenheitsmeldung standardmäßig aktiviert |
s.cost() |
GET /usage/session-costs/:sid |
Liest endgültige LLM- und Rechenkosten, ohne die Laufzeitumgebung zu starten |
s.scope() |
GET …/sessions/:sid/scope |
Liest die gespeicherte Einschränkung von Secrets und materialisierte Verbindungsbindungen |
s.rescope(input) |
PUT …/sessions/:sid/scope |
Ersetzt die angegebenen Geltungsbereichsfelder für den nächsten Prompt oder Tool-Aufruf |
s.commit(input?) |
— | Committet die Arbeit des Agenten |
s.providerSecretPool.get(providerId) |
GET …/provider-secret-pools/:providerId |
Liest ausgewählte Account-Secret-IDs |
s.providerSecretPool.list() |
GET …/provider-secret-pools |
Listet konfigurierte Pools einschließlich leerer Pools auf und meldet can_edit |
s.providerSecretPool.set(providerId, secretIds) |
PUT …/provider-secret-pools/:providerId |
Ersetzt die Auswahl; null stellt den Projektstandard wieder her |
Labels und Metadaten
Jede Sitzung enthält labels: string[] und ein metadata-Objekt. Verwende sie, um
Sitzungen zu klassifizieren und festzulegen, wie deine App sie darstellt.
- Labels sind frei definierbar: Jedes wird von führenden und nachfolgenden Leerzeichen bereinigt und umfasst 1–64 Zeichen; pro Sitzung sind höchstens 20 erlaubt, Duplikate werden entfernt. Der Abgleich ist exakt und berücksichtigt Groß- und Kleinschreibung.
- Metadaten sind ein frei definierbares JSON-Objekt für deine eigenen Schlüssel mit höchstens 16.384
Zeichen JSON pro Schreibvorgang. Der Server speichert dort auch seine eigenen Schlüssel
(
custom_name,source, …); eine Anfrage, die einen serververwalteten Schlüssel angibt, wird mit400abgelehnt. Verwende ein Präfix für deine Schlüssel (app_…), um Konflikte zu vermeiden.
// 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
}
Übergib in React labels an useProjectSessions(projectId, { labels }); jeder
Label-Satz erhält einen eigenen Cache-Eintrag. Agenten setzen dieselben Felder über die CLI
(beeuni sessions new --label … --meta k=v, beeuni sessions update --label …)
und über den gehosteten MCP-Server (start_session labels / metadata,
list_sessions labels). Eine ausführbare Version ist packages/sdk/examples/12-session-labels.ts.
Wenn pooled_provider_secrets und llm_gateway aktiviert sind, kann eine Sitzung
mehrere verfügbare Account-Secrets für einen Anbieter auswählen. Erstelle über
beeuni.accounts.secretResources.create(accountId, { project_id: projectId, ...input }) eine projektbezogene Ressource.
Standardmäßig kann jedes Projektmitglied sie verwenden. Schränke sie mit
.setAccess(accountId, secretId, 'members', userIds) ein. Lesevorgänge geben Metadaten und Berechtigungen zurück, niemals
den Secret-Wert. Nach einem 429 des Anbieters, bevor die Ausgabe beginnt, versucht das Gateway
einen anderen ausgewählten Schlüssel.
await s.providerSecretPool.set('anthropic', [primarySecretId, backupSecretId]);
const selected = await s.providerSecretPool.get('anthropic');
await s.providerSecretPool.set('anthropic', null); // inherit project default
Verwende die Streaming-Methode, wenn der Aufrufer den Fortschritt des Neuladens anzeigt:
await s.reloadConfigStream({ refresh_repo: false }, (event) => {
if (event.type === 'phase') console.log(event.phase);
});
Die Phasen sind checking-session, refreshing-workspace, compiling-config,
applying-config und confirming-config. Der Server lässt
refreshing-workspace aus, wenn refresh_repo gleich false ist. Die
Phase applying-config umfasst den vom Daemon validierten Austausch der Laufzeitumgebung.
s.configState() gibt release zurück, wenn die API Konfigurationsfreigaben unterstützt.
Der Typ ist SessionConfigRelease. Die Ergebnisse des Neuladens enthalten dasselbe Objekt.
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 hat drei Zustände. null bedeutet, dass Beeuni es nicht feststellen konnte. Prüfe stale === true,
niemals !stale. Eine ältere API lässt release aus. Siehe
Konfigurationskonvergenz.
s.participants() gibt { participants, total, multi_user } zurück. participants
listet die Personen auf, die die Sitzung aktuell öffnen können: den Eigentümer zuerst, höchstens 20, jeweils
{ user_id, name, email, avatar_url, is_viewer }; total ist die Gesamtzahl.
multi_user ist true, wenn zwei oder mehr Personen die Sitzung öffnen können. In React
liest useSessionParticipants(projectId, sessionId) dieselben Daten. Wer
jede Nachricht geschrieben hat, steht in s.messageAuthors().
Drei weitere Lesemethoden vervollständigen das Handle:
s.previews()— mögliche Vorschau-Ports, die die Runtime bereitstellt.s.publicShares.list()/.create(input)/.revoke(shareId)— öffentliche Freigabelinks.s.reminders.list()/.create(input)/.update(reminderId, { enabled })/.remove(reminderId)— geplante Prompts für diese Sitzung. Siehe Erinnerungen..create({ transcript: true })erstellt einen schreibgeschützten Link zur Unterhaltung (ein aktiver Link pro Sitzung; ein zweiter Aufruf gibt denselben Link zurück). MitgetPublicSessionShareMessages(token)kannst du sie anonym lesen;findActiveTranscriptShare(shares)wählt den aktiven Link aus.list().s.audit(limit?)— das Audit-Protokoll der Agentenaktionen dieser Sitzung.s.attachments.upload(file, options?)— speichert eine private Datei vor dem Start. Maximal 50 MiB. Gibt eine stabile URL für einen Dateiteil eines Prompts zurück.s.attachments.read(attachmentId, signal?)— liest gespeicherte Bytes alsBlob, ohne die Sandbox zu starten.s.transcript(options?)— ein kompaktes serverseitiges Transkript (Text und Tool-Aufrufe, keine Tool-Eingaben oder -Ausgaben). Dies funktioniert mit einem auf das Projekt beschränkten Sitzungstoken.s.transcriptSync(options?)— das Transkript, das der Server am Ende jedes Turns als runtime-Nachrichten-Envelopes speichert. Jeder Teil bleibt 1 erhalten — Text, Reasoning und jeder Tool-Aufruf mit Eingabe, Ausgabe, Fehler und Metadaten — mit Ausnahme der Anhangsbytes. Es antwortet, während die Sandbox gestoppt ist oder noch hochfährt, ohne sie zu starten. Übergiblimit(höchstens 500) für das neueste Fenster und den Wertnext_cursoreines Fensters alsbefore, um das davorliegende zu lesen. Ein Fenster endet auch bei 1.000.000 JSON-Zeichen: Es behält seine neuesten Nachrichten, undnext_cursorbenennt dann die älteste beibehaltene Nachricht. Übergibchild(dieses_…-ID eines Subagenten, also die Sitzung, die eine Subagenten-Zeile öffnet), um das eigene gespeicherte Transkript dieses Subagenten zu lesen; jeder von der Unterhaltung gestartete Subagent wird gespeichert.sourceistmirror;completeist nur dann true, wenn das Fenster alle gespeicherten Nachrichten enthält und die gespeicherte Kopie bis zur ersten Nachricht der Sitzung reicht. Eine Sitzung, deren Runtime vollständig gelesen wurde und keine Nachrichten enthielt, antwortet mitavailable: true,complete: true,total: 0und ohne Nachrichten. Ein vor dem 2026-09-28 gespeicherter Tool-Aufruf, als der gespeicherte Verlauf Tool-Eingaben und -Ausgaben noch verwarf, wird mit der Eingabe ausgeliefert, die sein Titel oder seine Metadaten belegen, und ein Befehl zusätzlich mit seiner Ausgabe.s.voiceTranscript(options?)— das Live-Sprachanruf-Transkript dieser Sitzung (gesprochene Turns plusask_beeuni/run_command-Tool-Aufrufe der Worker). Gibt eine leere Liste zurück, wenn die Sitzung keinen aktiven Anruf hat, keinen 404.
Von einer Sitzung gespeicherte Dateien
findSessionAttachments(messages) listet jede gespeicherte Datei auf, auf die ein Transkript
verweist, in der Reihenfolge und jeweils einmal: einen Benutzer-Upload, eine <file>-Referenz in einem
Prompt oder die Kopie einer vom Agenten gezeigten Datei, die der gespeicherte Verlauf aufbewahrt. Es liest ein
Live-Transkript ebenso wie ein gespeichertes und löst bei einem fehlerhaften Transkript niemals eine Ausnahme aus.
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)
}
Jedes Ergebnis ist { url, attachment_id, filename, mime, message_id, role }.
role ist user für einen Upload und assistant für eine vom Agenten gezeigte Datei;
mime ist bei Letzterer null, und die Bytes tragen ihren eigenen Typ.
Wer die einzelnen Nachrichten geschrieben hat
Jede Nachricht hat einen Autor. Der Server liest ihn aus den Zugangsdaten, mit denen der Prompt gesendet wurde: ein Projektmitglied oder der Agent einer anderen Sitzung. Das Runtime-Transkript enthält ihn nicht.
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 ist nach Runtime-Nachrichten-ID indiziert. initial_author ist die übergeordnete Sitzung für
die erste Nachricht einer gestarteten Sitzung. Es ist null, wenn die Sitzung keinen ersten
Prompt hatte. Jeder Autor ist ein SessionMessageAuthor:
kind |
Felder |
|---|---|
'member' |
user_id, name, email (string | null) |
'session' |
session_id, name (der Sitzungstitel), agent (der ausgeführte Agent, sofern benannt) |
Welches Modell einen Turn beantwortet hat
Eine Transkript-Nachricht nennt das Modell, das ihr Turn angefordert hat. Wenn die Fallback-Kette des Projekts mit einem anderen Modell antwortet, enthält nur der Anfragedatensatz des Gateways das tatsächlich antwortende Modell und den von Beeuni abgerechneten Betrag.
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 ist vor der ersten Antwort null. billed_cost ist der USD-Betrag, den Beeuni für
die Modellaufrufe der Sitzung abgebucht hat. turns ist nach der Runtime-Nachrichten-ID des Prompts indiziert, der
jeden Turn gestartet hat. served_models listet die Modelle auf, die in diesem Turn geantwortet haben, mit den
meisten Anfragen zuerst. In React liest useSessionModelUsage(projectId, sessionId) denselben
Datensatz; rufe dessen refetch() auf, wenn ein Turn endet.
In React liest useSessionMessageAuthors(projectId, sessionId, messageCount) die
gleichen Daten. Übergib die Nachrichtenanzahl des Transkripts. Eine neue Nachricht ist das einzige Ereignis, das
einen Autor hinzufügt, daher ruft die Abfrage die Daten erneut ab, wenn sich die Anzahl ändert.
Bereitschaft ist ein Handshake
Bevor du einen Prompt sendest, rufe ensureReady() auf. Es stellt bei Bedarf die Sandbox
bereit, wartet auf den Start der Runtime und gibt die aufgelöste Runtime zurück.
const { runtimeSessionId, runtimeUrl, sandboxId } = await s.ensureReady();
Bei einem Kaltstart kann ensureReady() RUNTIME_UNAVAILABLE auslösen. Siehe
Bei einem Kaltstart erneut versuchen, um zu erfahren, was das bedeutet und wie du
den Versuch wiederholst.
s.send() und s.abort() rufen ensureReady() für dich auf.
Einen serverseitig autorisierten Runtime-Sitzungs-Pin vorbelegen
Ein serverseitig gerenderter React-Host kann den bereits für dieselbe Beeuni-Sitzung gespeicherten Runtime-Sitzungs-Pin bereitstellen:
const session = useSession(projectId, sessionId, {
initialRuntimeSessionId: persistedSession.runtime_session_id ?? persistedSession.opencode_session_id,
});
Die Vorbelegung hydriert nur zwischengespeicherte Transkript-Inhalte, während /start läuft. Sie
überschreibt nicht die Runtime-Identität. Der von /start zurückgegebene Pin ist
maßgeblich und ersetzt eine veraltete Vorbelegung.
opencode_session_id ist der veraltete Name von runtime_session_id. Beide
enthalten denselben Wert; lies den alten Namen nur als den oben genannten Fallback.
Akzeptiere diesen Wert nicht aus einem nicht vertrauenswürdigen Mandantenselektor. Erstelle keine Runtime-Sitzung im Host. Beeuni erstellt und speichert die Root-Sitzung. Runtime-Abfrage-Caches und Transkript-Controller sind auf die Sandbox-Runtime beschränkt, sodass gleiche Runtime-Sitzungs-IDs aus verschiedenen Sandboxes keine Cache-Einträge teilen.
Ausstehende Wiederholungsversuche für Nachrichten behalten beim Wechseln der
Sitzung ihre ursprüngliche Sandbox-URL. Eine 404- oder 410-Nachrichtenantwort beendet automatische Wiederholungsversuche, ohne
das Transkript zu leeren. Ein expliziter Abgleich kann den Lesevorgang erneut versuchen und
nach einer erfolgreichen Antwort die normale Synchronisierung wiederherstellen.
Einen Prompt senden
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() legt den Prompt im dauerhaften Prompt-Posteingang der Sitzung ab
(POST .../prompts), dem Pfad, den der Web-Composer und die CLI verwenden. Es wird aufgelöst,
sobald der Prompt gespeichert ist, mit der Posteingangszeile (prompt_id, state,
message_id), nicht erst, wenn der Turn endet. Lies die Antwort aus s.stream() oder dem
Transkript.
Bei runtime-REST-Sitzungen liest das erste send() auf einem Handle das Modell und den
Agenten, die in der Beeuni-Sitzung gespeichert sind. Dadurch wird verhindert, dass eine aus einem Snapshot übernommene
runtime-Sitzung veraltete Snapshot-Standardwerte wiederverwendet.
Für die Prompt-Auswahl gilt folgende Priorität:
- Der
send()-Aufruf. - Der
setModel()- odersetAgent()-Wert des Handles. - Der gespeicherte Standardwert der Beeuni-Sitzung.
setModel wählt nur aus, was das nächste lokale send anfordert — der Wert verlässt nie das
Handle. Um ein neues Modell für eine laufende Sitzung serverseitig zu speichern, verwende
changeModel:
const { applied_live } = await s.changeModel('anthropic/claude-opus-4-8');
Die Änderung wird durch einen Neustart der Runtime wirksam, daher endet ein laufender Turn.
applied_live ist true, wenn eine laufende Sitzung die Änderung sofort übernommen hat, und false, wenn sie
beim nächsten Start wirksam wird. Nur der Sitzungseigentümer oder ein Aufrufer mit Projektmanager-
Berechtigungen darf das Modell ändern; alle anderen erhalten 403.
send() löst die Runtime auf und sendet dann einen Prompt an sie. abort() stoppt den aktuellen
Lauf, ohne die Sitzung zu löschen.
Sitzungsumfang und Kosten
Lies die gespeicherte Einschränkung für Secrets und die materialisierten Verbindungsbindungen.
secrets_allowlist: null bedeutet, dass die Secret-Freigabe des Agenten gilt:
const scope = await s.scope();
scope.connector_bindings_configured; // false = inherits the project defaults
connector_bindings ist die AUFGELÖSTE Zuordnung und sieht daher bei einer Sitzung,
die ihre Konnektoren überschrieben hat, genauso aus wie bei einer, die die Projektstandards übernimmt. Lies
connector_bindings_configured, um sie zu unterscheiden, bevor du den Geltungsbereich darstellst oder
zurücksendest.
Ersetze eines oder beide Felder des Geltungsbereichs:
await s.rescope({
secrets: ['DATABASE_URL'],
connector_bindings: {
github: { connection_id: connectionId },
},
});
Jedes angegebene Feld ersetzt seinen gesamten vorherigen Wert. Lass ein Feld weg, um es unverändert zu lassen. Verbindungsänderungen gelten ab dem nächsten Tool-Aufruf. Das Entfernen eines Secrets stoppt dessen künftige Übermittlung, kann aber einen bereits offengelegten Wert nicht aus dem Modellkontext oder einem bestehenden Prozess entfernen.
Beide Achsen haben einen expliziten Weg zurück zum Standard. Das ist nicht dasselbe wie ein leerer Wert:
await s.rescope({
secrets: null, // inherit the agent's secret grant
connector_bindings: null, // drop the override; inherit the project defaults
});
secrets: [] und connector_bindings: {} sind die gegenteilige Anweisung: ein
explizites „keine Projekt-Secrets“ und „überhaupt keine Konnektoren“, einschließlich der
Projektstandards. Eine Sitzung, die {} sendet, obwohl sie null meint, verweigert sicherheitshalber den Zugriff auf jeden
Alias, den sie nicht genannt hat.
Lies den zusammengefassten Kostendatensatz:
const cost = await s.cost();
Der Datensatz kombiniert endgültige LLM-Kosten, abgerechnete Sandbox-Rechenkosten, Modellnutzung,
Token-Gesamtsummen, Rechendauer und Buchungseinträge. s.cost() ruft
ensureReady() nicht auf.
Laufzeitstatus und Vorschauen
| Methode | Rückgabe | Verwendung |
|---|---|---|
s.health(init?) |
{ status, ok, health, body } |
Prüfen, ob die Laufzeit aktiv ist |
s.previewUrl(port, path?) |
string |
Eine Proxy-URL für einen vom Agenten freigegebenen Port abrufen |
s.proxyUrl(url?) |
string | undefined |
Eine vom Agenten ausgegebene localhost-URL umschreiben |
const { ok, health } = await s.health();
const url = s.previewUrl(3000, '/docs');
s.health() löst nie eine Ausnahme aus. Du kannst es jederzeit aufrufen, auch bevor die Sitzung eine
Laufzeit hat. s.previewUrl() und s.proxyUrl() benötigen eine aufgelöste Laufzeit — rufe
zuerst s.ensureReady() auf, sonst lösen sie SessionNotReadyError aus. Siehe
Fehler bei der Sitzungsbereitschaft.
Was die Laufzeit unterstützt
health.capabilities listet die Sitzungsfunktionen auf, die die Laufzeit bereitstellt. Prüfe eine davon
mit runtimeSupports(capabilities, capability) und blende das Bedienelement aus, wenn
false zurückgegeben wird. Eine runtime-Sitzung stellt alle zehn bereit; eine pi-Sitzung stellt
nur session.subagents bereit und beantwortet die anderen mit 501 feature_not_supported
(404 für das Konfigurationsdokument). Harnesses enthält die
vollständige Matrix für runtime und pi.
| Fähigkeit | Funktion |
|---|---|
session.rewind |
Zu einer Nachricht zurückkehren und sie wiederherstellen |
session.compact |
Die Unterhaltung bei Bedarf zusammenfassen |
session.commands |
Slash-Befehle des Projekts |
session.fork |
Eine Sitzung an einer Nachricht abzweigen |
session.subagents |
Untergeordnete Sitzungen von Subagenten |
session.mcp |
MCP-Server, mit denen sich die Laufzeit selbst verbindet |
session.todo |
Die Aufgabenliste der Laufzeit |
session.shell |
Ein als Turn ausgeführter Shell-Befehl |
session.attach |
Den eigenen Terminal-Client des Harness anbinden |
session.config |
Ein Laufzeit-Konfigurationsdokument (/global/config); Agenten und Modelle stammen in jedem Fall aus dem Projekt |
import { runtimeSupports } from '@beeuni/sdk';
const { health } = await s.health();
if (!runtimeSupports(health?.capabilities, 'session.compact')) hideCompact();
runtimeSupports gibt true zurück, bevor eine Zustandsantwort eintrifft, sowie für eine
Laufzeit, die keine session.*-Fähigkeit auflistet: Ein Daemon, der vor der Einführung von Fähigkeiten erstellt wurde,
führt runtime aus, das alle Funktionen bereitstellt. Verwende in React
useRuntimeSupports.
Die start-Antwort enthält ebenfalls die Liste: SessionStartResult.capabilities wird
mit stage: 'ready' gesetzt. useSession speichert sie, sodass eine Fähigkeitsprüfung
schon vor der Antwort der ersten Zustandsabfrage korrekt ist.
Statusbegriffe
Jeder Host verwendet dasselbe Vokabular, sodass ein Zustand auf jedem Bildschirm denselben Begriff hat.
| Export | Verwendung |
|---|---|
sessionListStatus(session, reviewCount?) |
Eine Sitzung in einer Liste: needs-you, starting, running, done, stopped, failed, legacy |
SESSION_LIST_STATUS[status] |
{ label, tone, description } für diesen Status |
sessionConnectionLabel(connection) |
Eine kurze Bezeichnung für den Computer der Sitzung oder null für unknown und live |
SESSION_NOTICE |
Der Satz im Nachrichteneditor, solange der Computer nicht bereit ist |
turnRetryLabel(secondsLeft) |
Retrying in 5s, Retrying now oder Waiting to retry, wenn kein Countdown bekannt ist |
Die Farbstimmungen sind actionable, live, progress, muted und danger. Grün ist
nur für live und actionable vorgesehen; eine abgeschlossene Sitzung ist muted. Ein Status, den eine neuere
API sendet und dieser Build nicht kennt, wird als stopped angezeigt, niemals als failed.
Streaming
Verwende s.stream(), um Live-Ereignisse in einem Skript oder auf einem Server zu empfangen. Verwende in einer React-
App stattdessen useSession — es verwaltet den gesamten
Sitzungslebenszyklus für dich.
s.stream() ist der Ereignisstream für die runtime-REST-Kompatibilität. Die Beeuni API
leitet ihn aus der Sandbox weiter. Es gibt keinen separaten WebSocket-Endpunkt. Der
Transport ist fetch mit einem Streaming-Antwortbody, der über
ReadableStream und TextDecoderStream gelesen wird. Das SDK übernimmt Wiederverbindungen,
Backoff und eine Heartbeat-Prüfung.
Streame eine Sitzung:
- Rufe zuerst
ensureReady()auf. Die Laufzeit existiert erst, wenn die Sandbox startet. - Öffne den Stream, bevor du eine Nachricht sendest, damit du keine frühen Ereignisse verpasst.
- Sende die Nachricht.
- Schließe den Stream, sobald du
session.idlesiehst.
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');
Der Stream liefert Ereignisse in Batches im Abstand von 16 ms. Aufeinanderfolgende
message.part.delta-Ereignisse für ein Part-Feld innerhalb eines Batches kommen als ein Ereignis an.
Sein properties.delta enthält ihren Text, in der ursprünglichen Reihenfolge zusammengefügt. Sein id ist die ID des letzten
Ereignisses. Sein coalesced-Array listet die ersetzten Ereignisse auf. Hänge delta
an das Feld des Parts an, wie bei einem einzelnen Delta.
Streaming benötigt fetch mit einem echten ReadableStream-Body und
TextDecoderStream. Browser, Node ab Version 18, Bun und Cloudflare Workers
unterstützen dies alle.
Ein Host, dessen fetch kein Streaming unterstützt, stellt seinen eigenen Transport bereit. Das SDK ruft ihn
einmal pro Verbindung auf und übernimmt weiterhin selbst Wiederverbindungen, die Fortsetzung mit Last-Event-ID, Backoff und
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 bietet standardmäßig kein Streaming-fetch. Die mobile Beeuni-App nutzt
diese Schnittstelle mit react-native-sse. createHttpSessionSyncController bleibt
für Hosts verfügbar, die nur einen begrenzten Verlauf und eine
Statussynchronisierung benötigen.
Der Controller lädt zuerst die neuesten 10 Nachrichten. loadOlder() folgt dem
Server-Cursor. loadHttpSessionHistory() folgt für explizite
Exporte jedem Cursor.
Ereignistypen
Jedes Ereignis hat einen type und ein properties-Objekt, das seine Daten enthält, zum
Beispiel event.properties.sessionID.
type |
Wann es ausgelöst wird |
|---|---|
message.updated / message.removed |
Eine Nachricht wurde geändert oder gelöscht. |
message.part.updated / message.part.removed |
Ein Teil (Text, Tool-Aufruf, Datei) wurde erweitert oder entfernt. |
session.status |
Der Beschäftigungsstatus der Sitzung hat sich geändert. |
session.idle |
Der Turn wurde abgeschlossen. |
session.error |
Der Turn ist fehlgeschlagen. Das Ereignis enthält den Fehler. |
question.asked |
Der Agent hat um Eingabe gebeten. |
question.replied / question.rejected |
Die Antwort auf eine Frage ist eingegangen. |
Wandle rohe Nachrichten und Teile mit classifyTurn in darstellbare Ausgabe um. Siehe
SDK-Referenz.
Wiederholungsversuche bei einem Kaltstart
ensureReady() fragt den Endpunkt /start der Sitzung ab — jeder Aufruf nutzt Long-Polling für bis
zu 30 s — bis die Laufzeit eine abschließende Phase ready/failed/stopped erreicht
oder die Frist (readyTimeoutMs, standardmäßig ca. 180 s) abläuft. Bei einer bereits gestarteten Sitzung
liefert die erste Abfrage sofort ready. Bei einem Kaltstart wird weiter abgefragt,
solange die Sandbox retriable: true meldet. Ein langsamer Start dauert also nur länger,
statt einen Fehler auszulösen. Erst wenn die Frist
abläuft, wird ein ApiError mit code: 'RUNTIME_UNAVAILABLE' if the runtime is still not ready ausgelöst.
ensureReady() ist idempotent, sodass gleichzeitige Aufrufe für dieselbe Sitzung
eine /start-Anfrage teilen, statt mehrere zu senden. Die Hilfsfunktion retryUntilReady
unten ist jetzt optional — ensureReady() führt bereits intern Wiederholungsversuche durch —, bleibt aber
nützlich, wenn du ein längeres Gesamtzeitbudget als den Standardwert readyTimeoutMs möchtest.
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));
}
}
}
Siehe Fehlerklassen für die vollständige Struktur von ApiError. In React
wiederholt useSession /start für dich, sodass du
dieses Muster nicht brauchst.
Was /start dir mitteilt
Jede /start-Antwort beschreibt diesen Aufruf, nicht den gesammelten
Verlauf des Datensatzes. Vier Felder enthalten diese Informationen.
| Feld | Bedeutung |
|---|---|
observed_at |
Eine gemeinsame Zeitbasis für die gesamte Antwort. |
action |
Was der Server getan hat: inspected, checked_provider, resumed, provisioned, restored, reconciled, awaited_wake, cooling_down. |
observation |
Was der Server geprüft hat. known: false bedeutet bei diesem Aufruf nicht geprüft — niemals „geprüft und nichts gefunden“. |
boot |
phase (provisioning / resuming / booting / ready / parked / failed), since und actively_starting. |
boot.actively_starting beantwortet die Frage „Läuft gerade ein Anbietervorgang für diese
Sitzung?“. Eine starting-Payload mit actively_starting: false bedeutet,
dass der Server eine Wartezeit vor dem nächsten Versuch abwartet, nicht dass ein Computer startet.
{
"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"
}
}
}
Ein fehlgeschlagener Start wird automatisch wiederholt
Ein fehlgeschlagener Start setzt eine Wartezeit, kein dauerhaftes Urteil. Das nächste
/start nach der Wartezeit versucht selbstständig erneut, die Sitzung aufzuwecken. Die Wartezeit steigt
bei aufeinanderfolgenden Fehlern (2 min, 5 min, 10 min). Nach fünf aufeinanderfolgenden
Fehlern antwortet /start mit stage: "failed" und der Anzahl der Versuche in
failure.message; dieses Urteil läuft 30 Minuten nach dem letzten Fehler ab, und
POST …/restart hebt es sofort auf.
retriable wird bei jedem Aufruf abgeleitet. Ein Zustand, bei dem der Server noch einen erneuten Versuch unternehmen kann,
trägt niemals retriable: false.
failure.evidence nennt die Prüfung, die das negative Ergebnis geliefert hat, wann sie ausgeführt wurde und
wann der Server es erneut versucht. Jeder /start-Fehler enthält diese Angaben.
Prompt-Anhänge
Lade lokale Dateien hoch, wenn Nutzer sie einem Eingabefeld hinzufügen. Private Uploads gehören zum Projekt und erfordern keine laufende Sitzung.
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 }) wird erfüllt, sobald alle Uploads bereit sind, in der Reihenfolge von ids.
Es wird abgelehnt, wenn ein Upload fehlschlägt, abgebrochen oder entfernt wird oder signal abbricht.
submit(ids) übergibt die Einträge an diesen Sendevorgang: Sie verlassen attachments, zählen
nicht mehr zu den Limits und werden weiter hochgeladen, nachdem das Eingabefeld ausgehängt wurde.
Rufe forget(ids) auf, nachdem der POST erfolgreich war, oder reclaim(ids), um sie
nach einem fehlgeschlagenen Sendevorgang an das Eingabefeld zurückzugeben.
Der aufgelöste Dateiteil enthält attachment_id, filename und mime.
Er enthält weder eine URL noch Dateibytes. Der Server bindet den Upload und materialisiert ihn.
Jede Datei erreicht den Computer der Sitzung, einschließlich Bildern und PDFs: Bevor der
Prompt ausgeführt wird, schreibt der Server sie nach /workspace/uploads/.beeuni-inbox/, und der
Prompt verweist per Pfad darauf. Im Prompt selbst werden keine Dateibytes übertragen. Wenn
das Speichern des Verlaufs aktiviert ist, behält der Server außerdem eine Kopie für das Transkript. Eine fehlgeschlagene Kopie
verhindert nie, dass eine Datei den Computer erreicht. Kann der Server eine Datei nicht schreiben, wird der
Prompt zurückgehalten und erneut versucht.
Verwende diese Teile bei der Erstellung von Plattformsitzungen, beim Start bereits gestarteter Sitzungen oder auf Prompt-Inbox-
Routen. Übergib private Handles nicht direkt an sendParts der runtime-Laufzeit.
Ältere Plattform-Dateiteile mit url funktionieren weiterhin.
Jeder Eintrag hat ein stabiles id, die ursprüngliche file-Referenz, receivedBytes und
einen Status: pending, uploading, processing, ready, error oder aborted.
Die gesendeten Bytes bestimmen den Fortschritt. Der Controller veröffentlicht einen Fortschritts-Snapshot nur
bei einer Änderung um einen ganzen Prozentpunkt, höchstens zehn pro Sekunde und Upload. processing kann
bei 100 % bleiben, bis der Server den Upload abschließt. Die Limits betragen 50 MiB pro
Datei, 100 MiB pro Nachricht und 20 Dateien. Dateien dürfen nicht leer sein. Standardmäßig werden zwei Dateien
gleichzeitig hochgeladen.
retry(id) setzt denselben Handle fort. Nach attachment_size_mismatch oder
attachment_failed behält der Server keinen nutzbaren Handle, daher lädt retry(id) das
File erneut als neuen Anhang hoch. Ein abgelaufener Upload kann nicht erneut versucht werden: retry(id)
löst einen Fehler aus, und der Fehler des Eintrags enthält den Code attachment_expired. abort(id) bricht
unabgeschlossene Vorgänge ab.
remove(id) entfernt den Eintrag, bricht seinen Upload ab und wird sofort erfüllt. Das
DELETE auf dem Server erfolgt nach bestem Bemühen und wird nie abgelehnt; ein Upload, der nicht gelöscht werden kann,
läuft nach 24 Stunden ab.
forget(ids) gibt Einträge frei, ohne Serverobjekte zu löschen.
Der Hook bricht aufgelistete Vorgänge beim Aushängen oder Projektwechsel ab und löscht seine Uploads
nach bestem Bemühen: Kein Sendevorgang hält sie fest, und Entwürfe behalten keinen Handle. Übergebene Uploads
laufen weiter.
Die Auswahl bleibt nur im Arbeitsspeicher. Speichere niemals File-Objekte, Blob-URLs, signierte URLs oder Upload-Handles in einem Entwurf. Verwaiste Uploads laufen nach 24 Stunden ab.
Hosts ohne Framework verwenden beeuni.project(projectId).attachments.createController()
mit denselben Methoden. Abonniere über subscribe(listener) und lies
getSnapshot(). Rufe dispose() auf, wenn das Eingabefeld geschlossen wird.
Direkte Aufrufer können attachments.upload(file, { signal, onProgress, onUpload, resume }) and attachments.delete(attachmentId) verwenden.
Bewahre die Metadaten von onUpload auf, um den Upload manuell mit derselben ID fortzusetzen.
Der Server wählt den Transport im Feld upload des Handles aus:
kind: 'direct'ist der Standard. Das SDK sendet die gesamte Datei in einemPUTanupload.urlmitupload.headersund ohne Authorization-Header. MitXMLHttpRequest(Browser, React Native) meldet der Fortschritt die gesendeten Bytes. Mitfetchmeldet der Fortschritt 0 und dann die volle Größe. Eine abgelaufene URL oder eine, die Storage mit 400, 401 oder 403 ablehnt, wird einmal für dasselbeattachment_idneu signiert. Ein409von Storage bedeutet, dass ein früherer Versuch die Datei gespeichert hat.kind: 'chunked'sendet nacheinander authentifiziertePUTmit jeweilsupload.chunk_sizeBytes. Das SDK akzeptiert jedes positivechunk_size. Nur ein Deployment, dessen Edge große Request-Bodys verwirft, wählt diesen Modus.
Der Abschluss überprüft die gespeicherte Größe und SHA-256. Wenn der Abschluss mit 409 attachment_not_uploaded, onUpload antwortet, meldet es das direkte Handle mit
received_bytes: 0, sodass retry(id) oder eine Wiederaufnahme die Datei erneut sendet.
Initiierung, Upload und Abschluss wiederholen bei Timeouts, Netzwerkfehlern, 429 und
5xx den Versuch mit exponentiellem Backoff und Jitter. Das Zeitbudget beträgt 60 Sekunden ab dem ersten
Fehler, sodass auch ein langer Upload, der spät fehlschlägt, erneut versucht wird. Der Abschluss wiederholt auch bei
attachment_processing den Versuch, mit einem Zeitbudget von fünf Minuten. Die Initiierung wiederholt niemals bei 402
(ein BillingError: das Konto kann nicht ausgeführt werden) oder 429 attachment_budget_exceeded (40
unfertige Uploads oder 500 MiB nicht gesendete Uploads für den Benutzer; ungenutzte Uploads
laufen innerhalb von 24 Stunden ab). Der Server beantwortet einen Abschluss oder lehnt ihn innerhalb von 105
Sekunden ab. Jede Abschlussanfrage lässt 120 Sekunden zu. Bei Abbrüchen durch den Aufrufer wird niemals erneut versucht.
Die Referenz eines gesendeten Anhangs wird 1 Stunde nach Zustellung seines Prompts
sowie beim Löschen seiner Sitzung oder seines Projekts freigegeben. Der nächste Wartungsdurchlauf
entfernt dann die Datei, und ihr attachment_id kann nicht mehr gesendet werden.
Dateien
s.files liest und schreibt in der Sandbox der Sitzung: list, read, readBlob,
status, findFiles, findText, upload, create, copy, remove,
mkdir, rename. Jeder Aufruf löst zuerst die Laufzeit auf und richtet sich immer an
die eigene Sandbox dieser Sitzung. Die vollständige Methodenliste findest du in der SDK-Referenz.
Sitzungsverben
Das Handle liest die Laufzeit aus und antwortet ihr für dich. Jedes Verb stellt zuerst die Sitzung bereit und richtet sich an die eigene Sandbox dieser Sitzung, unabhängig davon, welcher Harness sie ausführt.
// 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, der rohe REST-Kompatibilitätsclient, ist veraltet. Verwende die Verben
oben mit send, abort, rewind und stream.
Eine Projektsitzung vorwärmen
Rufe ensureWarm() auf, wenn eine Projekt-Landingpage vor dem
ersten Prompt eine einsatzbereite Laufzeit benötigt.
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() erstellt eine ungenutzte Sitzung für den aktuellen Benutzer oder gibt eine zurück. Es
ist derselbe Erstellungsvorgang, den sessions.create() ausführt, mit den Standardeinstellungen des Projekts: dieselbe
Abrechnungsprüfung, dieselben Connector-Anforderungen. Der einzige
Unterschied ist metadata.warm, das die Sitzung vor
sessions.list() verbirgt, bis ihr erster Prompt eintrifft.
Betrachte dies als spekulativ. Ein 409 WARM_SESSION_UNAVAILABLE bedeutet, dass das Projekt
momentan nicht vorgewärmt werden kann — fahre mit sessions.create() fort, das
den tatsächlichen Grund meldet.
Die vorgewärmte Sitzung verwendet den DEFAULT-Agenten und die Sandbox des Projekts. Wenn der Benutzer
andere auswählt, verwirf sie und rufe sessions.create() auf: Eine ungenutzte vorgewärmte
Sitzung ist verborgen und wird automatisch bereinigt.
Fehlerbehandlung
Jeder Aufruf über createBeeuni wird mit einer typisierten Unterklasse von Error abgelehnt,
niemals mit einem einfachen Objekt. Fange den Fehler ab, prüfe instanceof und verzweige anhand von
.status oder .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;
}
}
Fehlerklassen
| Klasse | Erweitert | Wann sie ausgelöst wird | Wichtige Felder |
|---|---|---|---|
ApiError |
Error |
Standard für jede fehlgeschlagene Anfrage: Fehlerstatus, Netzwerkfehler, Timeout oder Abbruch | status, code, detail, response, url, endpoint, timeout |
AuthError |
ApiError |
getToken gab null zurück. Beeuni hat die Anfrage nie gesendet |
code ist immer 'NO_SESSION' |
BillingError |
ApiError |
HTTP 402. Die einzige Abrechnungsfehlerklasse |
status (402), code, detail.message |
RequestTooLargeError |
ApiError |
HTTP 431. Meist zu viele Dateien in einer Anfrage |
detail.suggestion |
SessionNotReadyError |
Error |
Ein Laufzeit-Accessor wurde vor ensureReady() ausgeführt |
name ist 'SessionNotReadyError' |
ApiError.name ist standardmäßig 'ApiError'. Zwei Fälle überschreiben dies:
name: 'AbortError',code: 'ABORTED'— die Anfrage wurde abgebrochen, zum Beispiel durch Navigation. Das ist kein Fehler. Ignoriere ihn.code: 'TIMEOUT'— das eigene Timeout der Anfrage ist abgelaufen.url,endpointundtimeoutzeigen, was das Timeout erreicht hat.
Bei jedem anderen Fehler enthält status den HTTP-Statuscode. code stammt aus dem error_code des Backends oder verwendet ersatzweise den Status als Zeichenfolge. message ist eine aufzählbare eigene Eigenschaft von ApiError und bleibt daher bei JSON.stringify und Object Spread erhalten.
Beeuni sendet eine Anfrage, die 401 erhält, einmal erneut mit einem frischen Token von getToken(), wenn sich dieses Token vom gesendeten unterscheidet. Eine Anfrage mit einem nur einmal lesbaren Stream-Body wird nicht erneut gesendet.
Beeuni wiederholt manche Anfragen, bevor dein Code einen Fehler sieht. Wenn eine GET- oder HEAD-Anfrage 502, 503 oder 504 zurückgibt, wiederholt Beeuni sie bis zu 2 Mal, mit einer Verzögerung von erst 250 ms und dann 500 ms. Ein vorübergehender Transportfehler bei einer GET- oder HEAD-Anfrage — ein Netzwerkfehler, kein Statuscode — wird auf dieselbe Weise erneut versucht. Ein erfolgreicher Wiederholungsversuch erreicht onError niemals. Beeuni wiederholt niemals POST-, PUT-, PATCH- oder DELETE-Anfragen oder eine 500-Antwort.
Beeuni löst AuthError auf dem Client aus, bevor es eine Anfrage sendet, wenn getToken() null zurückgibt. AuthError erweitert ApiError, daher trifft err instanceof ApiError weiterhin zu. Prüfe err instanceof AuthError oder err.code === 'NO_SESSION', um „nicht angemeldet“ getrennt von einem Backend-Fehler zu behandeln.
Beeuni löst BillingError für jede HTTP-402-Antwort aus: Guthaben aufgebraucht, Tariflimit überschritten oder eine andere Abrechnungssperre. detail.message enthält den Grund aus dem Backend, und code enthält den maschinenlesbaren Code des Backends (zum Beispiel app_account_unfunded). BillingError erweitert ApiError, daher prüfe zuerst BillingError, wenn du beide behandelst.
Beeuni löst RequestTooLargeError für HTTP 431 aus. Das bedeutet meist, dass die Anfrage zu viele Dateien enthielt. detail.suggestion enthält einen direkt anzeigbaren Hinweis für den Benutzer.
Fehler bei der Sitzungsbereitschaft
Zwei Fehler bedeuten, dass die Sandbox der Sitzung noch nicht bereit ist. Behandle jeden davon unterschiedlich.
SessionNotReadyError löst synchron einen Fehler aus, wenn du einen Laufzeit-Accessor — session.previewUrl() oder session.proxyUrl() — aufrufst, bevor dieses Session-Handle seine Sandbox aufgelöst hat. Ein Session-Handle löst nur seine eigene Sandbox auf; es greift niemals auf die Sandbox einer anderen Session zurück.
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();
}
}
Rufe zuerst await session.ensureReady() auf oder rufe send() auf, wodurch die Session intern vorbereitet wird. session.health() ist der einzige Accessor, der diesen Fehler niemals auslöst. Du kannst ihn daher abfragen, bevor die Session startet.
RUNTIME_UNAVAILABLE ist der zweite Fehler — er bedeutet, dass ensureReady() selbst beim Warten auf einen Kaltstart das Zeitlimit überschritten hat. Das vollständige Vorgehen findest du unter Bei einem Kaltstart erneut versuchen. In React übernimmt useSession die Wiederholungsversuche für dich und stellt den Fehler über den Wert phase bereit, statt ihn auszulösen.
Hilfsfunktionen
| Hilfsfunktion | Signatur | Funktion |
|---|---|---|
parseBillingError(error) |
(error) => Error |
Wandelt eine 402-Antwort in ein BillingError um. Gibt andere Fehler unverändert zurück |
isBillingError(error) |
(error) => boolean |
Gibt error instanceof BillingError zurück |
formatBillingErrorForUI(error) |
(error) => BillingErrorUI | null |
Gibt bei Fehlern ohne Abrechnungsbezug null zurück. Gibt andernfalls { alertTitle, alertSubtitle } für ein Upgrade-Modal zurück |
isRuntimeNotReadyResponse(error) |
(error) => boolean |
Wahr für 503 des Sandbox-Daemons, solange die Session-Laufzeit noch keine Anfrage annehmen kann. Die Anfrage wurde nicht weitergeleitet, daher ist ein erneuter Versuch sicher. Dasselbe Ergebnis bei runtime und pi |
isSandboxNotReadyError(error) |
(error) => boolean |
Wahr für den obigen Fall und für eine Sandbox, die gestoppt ist, startet oder geparkt ist. Zeige sie als „wird aufgeweckt“ an und frage sie weiterhin ab |
isRuntimeStartingError(error) |
(error) => boolean |
Wahr für beide obigen Fälle und für eine Laufzeit-URL, die noch nicht festgelegt ist. Eine Error Boundary versucht es in diesem Fall erneut, statt einen Absturz anzuzeigen |
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 exportiert BillingError, RequestTooLargeError, parseBillingError, isBillingError und formatBillingErrorForUI erneut. ApiError und AuthError werden nicht erneut exportiert — importiere sie aus @beeuni/sdk.
useSession ordnet jeden Fehler von send, answerQuestion, answerPermission und rejectQuestion einem sendError-Objekt zu, sodass du keine instanceof-Prüfungen von Hand schreiben musst:
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);
}
Weitere Informationen zu useSession findest du unter React-Hooks.