Zum Inhalt springen
BeeuniBeeuni
Deutsch
Esc
↑↓navigieren↵öffnen⌘Jvorschau
Auf dieser Seite

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 mit 400 abgelehnt. 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). Mit getPublicSessionShareMessages(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 als Blob, 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. Übergib limit (höchstens 500) für das neueste Fenster und den Wert next_cursor eines Fensters als before, um das davorliegende zu lesen. Ein Fenster endet auch bei 1.000.000 JSON-Zeichen: Es behält seine neuesten Nachrichten, und next_cursor benennt dann die älteste beibehaltene Nachricht. Übergib child (die ses_…-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. source ist mirror; complete ist 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 mit available: true, complete: true, total: 0 und 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 plus ask_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:

  1. Der send()-Aufruf.
  2. Der setModel()- oder setAgent()-Wert des Handles.
  3. 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:

  1. Rufe zuerst ensureReady() auf. Die Laufzeit existiert erst, wenn die Sandbox startet.
  2. Öffne den Stream, bevor du eine Nachricht sendest, damit du keine frühen Ereignisse verpasst.
  3. Sende die Nachricht.
  4. Schließe den Stream, sobald du session.idle siehst.
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 einem PUT an upload.url mit upload.headers und ohne Authorization-Header. Mit XMLHttpRequest (Browser, React Native) meldet der Fortschritt die gesendeten Bytes. Mit fetch meldet 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 dasselbe attachment_id neu signiert. Ein 409 von Storage bedeutet, dass ein früherer Versuch die Datei gespeichert hat.
  • kind: 'chunked' sendet nacheinander authentifizierte PUT mit jeweils upload.chunk_size Bytes. Das SDK akzeptiert jedes positive chunk_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, endpoint und timeout zeigen, 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.

War diese Seite hilfreich?