Sessões
Execute uma sessão, transmita seus eventos e trate os erros que ela pode gerar.
Uma sessão é uma execução de agente, em seu próprio sandbox, em sua própria branch git.
beeuni.session(projectId, sessionId) retorna o handle para tudo o que uma
sessão faz: iniciá-la, enviar prompts, receber eventos em streaming e ler o status. Esta
página aborda o handle, o handshake de prontidão, o streaming e os erros
tipados que uma chamada do SDK pode lançar.
const s = beeuni.session(projectId, sessionId);
s é o handle para tudo o que uma sessão faz. O ID da sessão, o ID do sandbox
e o nome da branch têm o mesmo valor. Veja
Sessões para entender o conceito.
Ciclo de vida da sessão
| Método | Encapsula | O que faz |
|---|---|---|
s.get(opts?) |
GET /projects/:pid/sessions/:sid |
Lê os detalhes da sessão |
s.update(input) |
PATCH …/sessions/:sid |
Renomeia a sessão, substitui seus rótulos ou mescla chaves de metadados |
s.start(waitMs?) |
POST …/sessions/:sid/start |
Provisiona e inicia o runtime |
s.restart() |
POST …/sessions/:sid/restart |
Reinicia o runtime; mantém o mesmo sandbox |
s.configState() |
GET …/sessions/:sid/config |
Lê a atualização da configuração (stale) e o estado da versão da configuração (release) |
s.reloadConfig(input?) |
POST …/sessions/:sid/reload |
Recompila a configuração do agente e substitui o runtime após a validação |
s.reloadConfigStream(input, onEvent) |
POST …/sessions/:sid/reload-stream |
Executa a mesma recarga e emite fases de progresso confirmadas pelo servidor |
s.stop() |
POST …/sessions/:sid/stop |
Para o runtime; a sessão permanece |
s.delete() |
DELETE …/sessions/:sid |
Exclui a sessão |
s.setSharing(intent) |
PUT …/sharing |
Define o compartilhamento e a visibilidade |
s.participants() |
GET …/sessions/:sid/participants |
Lê quem pode abrir a sessão e quem enviou cada prompt |
s.watch() |
GET …/sessions/:sid/watch |
Lê se você recebe as notificações desta sessão. Requer notification_center; 403 feature_disabled enquanto estiver desativado |
s.setWatch(watching) |
PUT …/sessions/:sid/watch |
Ativa (true) ou silencia (false) as notificações da sessão para você. Requer notification_center; 403 feature_disabled enquanto estiver desativado |
s.presence({ tab_id, active, alerts? }, { keepalive? }) |
PUT …/sessions/:sid/presence |
Informa uma aba aberta. alerts: true indica que a aba mostra suas próprias notificações, então o push para o celular é retido. Com notification_center desativado, qualquer aba presente o retém. keepalive permite que a requisição continue após o fechamento da página; ativado por padrão para um informe de ausência |
s.cost() |
GET /usage/session-costs/:sid |
Lê o custo finalizado de LLM e computação sem iniciar o runtime |
s.scope() |
GET …/sessions/:sid/scope |
Lê a restrição de escopo de segredos armazenada e os vínculos de conexão materializados |
s.rescope(input) |
PUT …/sessions/:sid/scope |
Substitui os campos de escopo fornecidos para o próximo prompt ou chamada de ferramenta |
s.commit(input?) |
— | Faz commit do trabalho do agente |
s.providerSecretPool.get(providerId) |
GET …/provider-secret-pools/:providerId |
Lê os IDs dos segredos da conta selecionados |
s.providerSecretPool.list() |
GET …/provider-secret-pools |
Lista os pools configurados, incluindo pools vazios, e informa can_edit |
s.providerSecretPool.set(providerId, secretIds) |
PUT …/provider-secret-pools/:providerId |
Substitui a seleção; null restaura o padrão do projeto |
Rótulos e metadados
Cada sessão contém labels: string[] e um objeto metadata. Use-os para
classificar sessões e decidir como seu app exibe cada uma.
- Rótulos têm formato livre: os espaços nas extremidades são removidos, cada um tem de 1 a 64 caracteres, com no máximo 20 por sessão, e duplicatas são descartadas. A correspondência é exata e diferencia maiúsculas de minúsculas.
- Metadados são um objeto JSON de formato livre para suas próprias chaves, com no máximo 16.384
caracteres de JSON por gravação. O servidor também armazena suas próprias chaves ali
(
custom_name,source, …); uma requisição que especifica uma chave gerenciada pelo servidor é recusada com400. Use prefixos nas suas chaves (app_…) para evitar conflitos.
// 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
}
No React, passe labels para useProjectSessions(projectId, { labels }); cada
conjunto de rótulos recebe sua própria entrada de cache. Os agentes definem os mesmos campos pela CLI
(beeuni sessions new --label … --meta k=v, beeuni sessions update --label …)
e pelo servidor MCP hospedado (start_session labels / metadata,
list_sessions labels). Uma versão executável é packages/sdk/examples/12-session-labels.ts.
Com pooled_provider_secrets e llm_gateway ativados, uma sessão pode selecionar
vários segredos disponíveis da conta para um provedor. Crie um recurso com escopo de projeto por meio de
beeuni.accounts.secretResources.create(accountId, { project_id: projectId, ...input }).
Todos os membros do projeto podem usá-lo por padrão. Restrinja-o com
.setAccess(accountId, secretId, 'members', userIds). As leituras retornam metadados e permissões, nunca
o valor do segredo. Após um 429 do provedor antes do início da saída, o gateway
tenta outra chave selecionada.
await s.providerSecretPool.set('anthropic', [primarySecretId, backupSecretId]);
const selected = await s.providerSecretPool.get('anthropic');
await s.providerSecretPool.set('anthropic', null); // inherit project default
Use o método com streaming quando o chamador exibir o progresso da recarga:
await s.reloadConfigStream({ refresh_repo: false }, (event) => {
if (event.type === 'phase') console.log(event.phase);
});
As fases são checking-session, refreshing-workspace, compiling-config,
applying-config e confirming-config. O servidor omite
refreshing-workspace quando refresh_repo é false. A
fase applying-config inclui a substituição do runtime validada pelo daemon.
s.configState() retorna release quando a API oferece suporte a versões de configuração.
Seu tipo é SessionConfigRelease. Os resultados da recarga contêm o mesmo objeto.
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 tem três estados. null significa que o Beeuni não conseguiu determinar. Teste stale === true,
nunca !stale. Uma API mais antiga omite release. Veja
Convergência de configuração.
s.participants() retorna { participants, total, multi_user }. participants
lista as pessoas que podem abrir a sessão agora, com o proprietário primeiro, no máximo 20, cada uma
{ user_id, name, email, avatar_url, is_viewer }; total é a contagem total.
multi_user é true quando duas ou mais pessoas podem abrir a sessão. No React,
useSessionParticipants(projectId, sessionId) lê os mesmos dados. Quem escreveu
cada mensagem é s.messageAuthors().
Mais três métodos de leitura completam o handle:
s.previews()— portas candidatas de pré-visualização que o runtime expõe.s.publicShares.list()/.create(input)/.revoke(shareId)— links públicos de compartilhamento.s.reminders.list()/.create(input)/.update(reminderId, { enabled })/.remove(reminderId)— prompts agendados para esta sessão. Veja Lembretes..create({ transcript: true })cria um link somente leitura para a conversa (um link ativo por sessão; uma segunda chamada retorna o mesmo link). Leia-o anonimamente comgetPublicSessionShareMessages(token);findActiveTranscriptShare(shares)seleciona o link ativo em.list().s.audit(limit?)— a trilha de auditoria das ações do agente na sessão.s.attachments.upload(file, options?)— salva um arquivo privado antes da inicialização. Máximo de 50 MiB. Retorna uma URL estável para uma parte de arquivo do prompt.s.attachments.read(attachmentId, signal?)— lê os bytes salvos como umBlobsem iniciar o sandbox.s.transcript(options?)— uma transcrição compacta no servidor (texto e chamadas de ferramentas, sem entradas ou saídas das ferramentas). Funciona com um token de sessão com escopo de projeto.s.transcriptSync(options?)— a transcrição que o servidor salva ao final de cada turno, como envelopes de mensagens runtime. Cada parte é mantida 1 — texto, raciocínio e cada chamada de ferramenta com sua entrada, saída, erro e metadados — exceto os bytes dos anexos. Responde enquanto o sandbox está parado ou ainda iniciando, sem iniciá-lo. Passelimit(no máximo 500) para obter a janela mais recente, e onext_cursorde uma janela comobeforepara ler a anterior a ela. Uma janela também é limitada a 1.000.000 de caracteres de JSON: ela mantém as mensagens mais recentes, enext_cursorentão identifica a mais antiga que foi mantida. Passechild(o idses_…de um subagente, a sessão que uma linha de subagente abre) para ler a transcrição salva desse subagente; todos os subagentes que a conversa acionou são salvos.sourceémirror;completeé true apenas quando a janela contém todas as mensagens salvas e a cópia salva alcança a primeira mensagem da sessão. Uma sessão cujo runtime foi lido por completo e não continha mensagens responde comavailable: true,complete: true,total: 0e nenhuma mensagem. Uma chamada de ferramenta salva antes de 2026-09-28, quando o histórico salvo descartava entradas e saídas das ferramentas, é fornecida com a entrada comprovada por seu título ou metadados, e um comando também com sua saída.s.voiceTranscript(options?)— a transcrição da chamada de voz ao vivo desta sessão (turnos falados mais chamadas de ferramentas dos workersask_beeuni/run_command). Retorna uma lista vazia quando a sessão não tem uma chamada ao vivo, não um 404.
Arquivos armazenados por uma sessão
findSessionAttachments(messages) lista todos os arquivos armazenados que uma transcrição
referencia, em ordem e cada um uma única vez: um upload do usuário, uma referência <file> em um
prompt ou a cópia que o histórico salvo mantém de um arquivo que o agente mostrou. Lê tanto uma
transcrição ao vivo quanto uma salva, e nunca lança uma exceção por uma transcrição malformada.
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)
}
Cada resultado é { url, attachment_id, filename, mime, message_id, role }.
role é user para um upload e assistant para um arquivo que o agente mostrou;
mime é null no segundo caso, e os bytes carregam seu próprio tipo.
Quem escreveu cada mensagem
Toda mensagem tem um autor. O servidor o lê da credencial que enviou o prompt: um membro do projeto ou o agente de outra sessão. A transcrição do runtime não contém essa informação.
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 usa o id da mensagem do runtime como chave. initial_author é a sessão pai da
primeira mensagem de uma sessão criada. É null quando a sessão não teve um primeiro
prompt. Cada autor é um SessionMessageAuthor:
kind |
Campos |
|---|---|
'member' |
user_id, name, email (string | null) |
'session' |
session_id, name (o título da sessão), agent (o agente que ela executa, quando nomeado) |
Qual modelo respondeu a um turno
Uma mensagem da transcrição identifica o modelo solicitado pelo seu turno. Quando a cadeia de fallback do projeto responde usando outro modelo, apenas o registro de requisição do gateway contém o modelo que respondeu e o que a Beeuni cobrou.
const { latest, billed_cost, turns } = await beeuni.session(projectId, sessionId).modelUsage()
// getSessionModelUsage(projectId, sessionId) is the same call as a function.
latest?.served_model // 'glm-5.3-flash': the model that answered the newest request
latest?.fallback_from // 'codex/gpt-6.1-sol' when a fallback model answered, else null
turns[userMessageId] // { served_models, fallback_from, billed_cost } for one turn
latest é null antes da primeira resposta. billed_cost é o valor em USD que a Beeuni debitou pelas
chamadas de modelo da sessão. turns usa como chave o id da mensagem do runtime do prompt que
iniciou cada turno. served_models lista os modelos que responderam naquele turno, primeiro os que tiveram
mais requisições. No React, useSessionModelUsage(projectId, sessionId) lê o mesmo
registro; chame seu refetch() quando um turno terminar.
No React, useSessionMessageAuthors(projectId, sessionId, messageCount) lê os
mesmos dados. Passe a contagem de mensagens da transcrição. Uma nova mensagem é o único evento que
adiciona um autor, então a consulta busca os dados novamente quando a contagem muda.
A prontidão é um handshake
Antes de enviar um prompt, chame ensureReady(). Ele provisiona o sandbox se
necessário, espera o runtime inicializar e retorna o runtime resolvido.
const { runtimeSessionId, runtimeUrl, sandboxId } = await s.ensureReady();
Em uma inicialização a frio, ensureReady() pode lançar RUNTIME_UNAVAILABLE. Veja
Nova tentativa em uma inicialização a frio para saber o que isso significa e como
tentar novamente.
s.send() e s.abort() chamam ensureReady() para você.
Inicialize com um identificador fixo de sessão do runtime autorizado pelo servidor
Um host React renderizado no servidor pode fornecer o identificador fixo de sessão do runtime já persistido para a mesma sessão Beeuni:
const session = useSession(projectId, sessionId, {
initialRuntimeSessionId: persistedSession.runtime_session_id ?? persistedSession.opencode_session_id,
});
O valor inicial apenas hidrata o conteúdo da transcrição em cache enquanto /start é executado. Ele não
substitui a identidade do runtime. O identificador retornado por /start é
a fonte de verdade e substitui um valor inicial desatualizado.
opencode_session_id é o nome obsoleto de runtime_session_id. Ambos
contêm o mesmo valor; leia o nome antigo apenas como o fallback acima.
Não aceite esse valor de um seletor de tenant não confiável. Não crie uma sessão do runtime no host. A Beeuni cria e persiste a sessão raiz. Os caches de consultas do runtime e os controladores de transcrição têm escopo restrito ao runtime do sandbox, portanto ids de sessão do runtime iguais em sandboxes diferentes não compartilham entradas de cache.
As novas tentativas de mensagens pendentes mantêm a URL do sandbox de origem quando você troca de
sessão. Uma resposta de mensagem 404 ou 410 interrompe as novas tentativas automáticas sem
limpar a transcrição. Uma reconciliação explícita pode tentar a leitura novamente e
restaurar a sincronização normal após uma resposta bem-sucedida.
Envie um prompt
s.setModel({ providerID, modelID }); // sticky for later send() calls
s.setAgent('build'); // sticky for later send() calls
await s.send('Refactor the auth module');
await s.send('One-off task', { model, agent }); // overrides for this call only
await s.abort(); // stop the current run
send() coloca o prompt na caixa de entrada durável de prompts da sessão
(POST .../prompts), o caminho usado pelo editor web e pela CLI. A chamada é concluída
quando o prompt é armazenado, com a linha da caixa de entrada (prompt_id, state,
message_id), não quando o turno termina. Leia a resposta em s.stream() ou na
transcrição.
Para sessões runtime REST, o primeiro send() em um handle lê o modelo e o
agente persistidos na sessão Beeuni. Isso impede que uma sessão
runtime herdada de um snapshot reutilize os padrões desatualizados do snapshot.
A precedência de escolha do prompt é:
- A chamada
send(). - O valor
setModel()ousetAgent()do handle. - O padrão persistido da sessão Beeuni.
setModel apenas escolhe o que o próximo send local solicita — esse valor nunca sai do
handle. Para persistir um novo modelo no servidor para uma sessão em execução, use
changeModel:
const { applied_live } = await s.changeModel('anthropic/claude-opus-4-8');
A alteração entra em vigor ao reiniciar o runtime, então um turno em andamento é encerrado.
applied_live é true quando uma sessão em execução aplicou a alteração imediatamente, e false quando ela
se aplica na próxima inicialização. Apenas o proprietário da sessão ou um chamador com permissões de gerente de
projeto pode alterar o modelo; qualquer outra pessoa recebe 403.
send() resolve o runtime e então envia um prompt a ele. abort() interrompe a execução
atual sem excluir a sessão.
Escopo e custo da sessão
Leia o narrowing do segredo armazenado e as vinculações de conexão materializadas.
secrets_allowlist: null significa que a concessão de segredo do agente se aplica:
const scope = await s.scope();
scope.connector_bindings_configured; // false = inherits the project defaults
connector_bindings é o mapa RESOLVED, portanto tem a mesma aparência para uma sessão
que substituiu seus conectores e para uma que herda os padrões do projeto. Leia
connector_bindings_configured para diferenciá-las antes de renderizar o escopo ou
enviá-lo de volta.
Substitua um ou ambos os campos de escopo:
await s.rescope({
secrets: ['DATABASE_URL'],
connector_bindings: {
github: { connection_id: connectionId },
},
});
Cada campo fornecido substitui completamente seu valor anterior. Omita um campo para deixá-lo inalterado. As alterações de conexão se aplicam à próxima chamada de ferramenta. A remoção de um segredo interrompe a entrega futura, mas não pode remover um valor já divulgado do contexto do modelo ou de um processo existente.
Ambos os eixos têm uma forma explícita de voltar ao padrão. Eles não são iguais a um valor vazio:
await s.rescope({
secrets: null, // inherit the agent's secret grant
connector_bindings: null, // drop the override; inherit the project defaults
});
secrets: [] e connector_bindings: {} são a instrução oposta: um
“nenhum segredo do projeto” explícito e “nenhum conector”; isso também inclui os padrões do projeto. Uma sessão que envia {} quando queria dizer null falha de forma segura em todos os
aliases que não nomeou.
Leia o registro de custo unificado:
const cost = await s.cost();
O registro combina o custo final do LLM, o custo cobrado de computação do sandbox, o uso do modelo,
totais de tokens, duração da computação e entradas do ledger. s.cost() não
chama ensureReady().
Status do runtime e prévias
| Método | Retorna | Uso |
|---|---|---|
s.health(init?) |
{ status, ok, health, body } |
Verificar se o runtime está ativo |
s.previewUrl(port, path?) |
string |
Obter uma URL de proxy para uma porta exposta pelo agente |
s.proxyUrl(url?) |
string | undefined |
Reescrever uma URL localhost exibida pelo agente |
const { ok, health } = await s.health();
const url = s.previewUrl(3000, '/docs');
s.health() nunca gera uma exceção. Chame-o a qualquer momento, mesmo antes de a sessão ter um
runtime. s.previewUrl() e s.proxyUrl() precisam de um runtime resolvido — chame
s.ensureReady() primeiro, ou eles lançarão SessionNotReadyError. Consulte
Erros de prontidão da sessão.
O que o runtime oferece
health.capabilities lista os recursos da sessão que o runtime oferece. Leia um
deles com runtimeSupports(capabilities, capability) e oculte o controle quando ele
retornar false. Uma sessão runtime oferece todos os dez; uma sessão pi oferece
apenas session.subagents e responde aos demais com 501 feature_not_supported
(404 para o documento de configuração). Harnesses tem a
matriz completa de runtime e pi.
| Recurso | Funcionalidade |
|---|---|
session.rewind |
Reverter para uma mensagem e restaurá-la |
session.compact |
Resumir a conversa sob demanda |
session.commands |
Comandos de barra do projeto |
session.fork |
Bifurcar uma sessão em uma mensagem |
session.subagents |
Sessões filhas de subagentes |
session.mcp |
Servidores MCP aos quais o runtime se conecta |
session.todo |
A lista de tarefas do runtime |
session.shell |
Um comando de shell executado como um turno |
session.attach |
Anexar o próprio cliente de terminal do harness |
session.config |
Um documento de configuração do runtime (/global/config); os agentes e modelos vêm do projeto de qualquer forma |
import { runtimeSupports } from '@beeuni/sdk';
const { health } = await s.health();
if (!runtimeSupports(health?.capabilities, 'session.compact')) hideCompact();
runtimeSupports retorna true antes de chegar uma resposta de integridade e para um
runtime que não lista nenhuma capacidade session.*: um daemon compilado antes da existência das capacidades executa runtime, que oferece todos os recursos. No React, use
useRuntimeSupports.
A resposta de start também carrega a lista: SessionStartResult.capabilities é
definido com stage: 'ready'. useSession a registra, portanto uma verificação de capacidade
fica correta antes de a primeira sonda de integridade responder.
Palavras de status
Todo host lê um único vocabulário, portanto um estado tem uma única palavra em todas as telas.
| Exportação | Uso |
|---|---|
sessionListStatus(session, reviewCount?) |
Uma sessão em uma lista: needs-you, starting, running, done, stopped, failed, legacy |
SESSION_LIST_STATUS[status] |
{ label, tone, description } para esse status |
sessionConnectionLabel(connection) |
Um rótulo curto para o computador da sessão, ou null para unknown e live |
SESSION_NOTICE |
A frase do compositor enquanto o computador não está pronto |
turnRetryLabel(secondsLeft) |
Retrying in 5s, Retrying now ou Waiting to retry quando não há contagem regressiva conhecida |
Os tons são actionable, live, progress, muted e danger. Verde é usado apenas para
live e actionable; uma sessão concluída é muted. Um status enviado por uma API
mais recente que esta versão não conhece é lido como stopped, nunca como failed.
Streaming
Use s.stream() para receber eventos ao vivo em um script ou servidor. Em um aplicativo React,
use useSession em vez disso — ele gerencia todo o
ciclo de vida da sessão para você.
s.stream() é o fluxo de eventos de compatibilidade com a API REST do runtime. A API Beeuni
faz proxy dele a partir do sandbox. Não existe um endpoint WebSocket separado. O
transporte é fetch com um corpo de resposta em streaming, lido por meio de
ReadableStream e TextDecoderStream. O SDK gerencia a reconexão,
o backoff e uma verificação de heartbeat.
Faça streaming de uma sessão:
- Chame
ensureReady()primeiro. O runtime não existe até o sandbox iniciar. - Abra o fluxo antes de enviar uma mensagem, para não perder os primeiros eventos.
- Envie a mensagem.
- Feche o fluxo quando vir
session.idle.
const session = beeuni.session(projectId, sessionId);
const { runtimeSessionId } = await session.ensureReady();
const stream = await session.stream({
onEvent: (event) => {
if (event.type === 'session.idle' && event.properties.sessionID === runtimeSessionId) {
onTurnDone();
stream.close();
}
},
});
await session.send('Refactor the auth module');
O fluxo entrega eventos em lotes, com 16 ms entre eles. Eventos message.part.delta consecutivos
para um campo de parte em um lote chegam como um único evento.
Seu properties.delta é o texto deles, concatenado na ordem. Seu id é o ID do último
evento. Seu array coalesced lista os eventos que ele substituiu. Anexe delta
ao campo da parte, como faria para um delta único.
O streaming precisa de fetch com um corpo ReadableStream real e
TextDecoderStream. Navegadores, Node 18 e posteriores, Bun e Cloudflare Workers
todos oferecem suporte.
Um host cujo fetch não pode fazer streaming fornece seu próprio transporte. O SDK o chama
uma vez por conexão e mantém a reconexão, o resume de Last-Event-ID, o backoff e
o heartbeat por conta própria:
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
O React Native não tem fetch de streaming por padrão. O aplicativo móvel Beeuni usa
esta interface com react-native-sse. createHttpSessionSyncController continua
disponível para um host que precisa apenas de histórico limitado e
sincronização de status.
O controlador carrega primeiro as 10 mensagens mais recentes. loadOlder() segue o
cursor do servidor. loadHttpSessionHistory() segue cada cursor para
exportações explícitas.
Tipos de evento
Cada evento tem um type e um objeto properties que contém seus dados, por
exemplo event.properties.sessionID.
type |
Quando é disparado |
|---|---|
message.updated / message.removed |
Uma mensagem foi alterada ou excluída. |
message.part.updated / message.part.removed |
Uma parte (texto, chamada de ferramenta, arquivo) cresceu ou foi removida. |
session.status |
O estado de ocupação da sessão mudou. |
session.idle |
O turno terminou. |
session.error |
O turno falhou. O evento contém o erro. |
question.asked |
O agente solicitou uma entrada. |
question.replied / question.rejected |
A resposta a uma pergunta chegou. |
Transforme mensagens e partes brutas em uma saída renderizável com classifyTurn. Veja
Referência do SDK.
Tentar novamente em uma inicialização a frio
ensureReady() consulta periodicamente o endpoint /start da sessão — cada chamada faz long polling por até
30 s — até que o runtime alcance um estágio terminal ready/failed/stopped
ou seu prazo (readyTimeoutMs, padrão de ~180 s) expire. Em uma sessão já ativa,
a primeira consulta retorna ready imediatamente. Em uma inicialização a frio, continua consultando
enquanto o sandbox informa retriable: true, então uma inicialização lenta apenas demora mais,
em vez de lançar um erro. Só lança um ApiError com code: 'RUNTIME_UNAVAILABLE' if the runtime is still not ready quando o prazo
expira.
ensureReady() é idempotente, então chamadas simultâneas para a mesma sessão compartilham
uma única solicitação /start em vez de enviar várias. O auxiliar retryUntilReady
abaixo agora é opcional — ensureReady() já tenta novamente internamente — mas continua
útil se você quiser um tempo total maior que o readyTimeoutMs padrão.
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));
}
}
}
Veja Classes de erro para a estrutura completa de ApiError. No React,
useSession tenta /start novamente por você, então você não precisa
desse padrão.
O que /start informa
Cada resposta de /start descreve aquela chamada, não o histórico acumulado
da linha. Quatro campos contêm essas informações.
| Campo | Significado |
|---|---|
observed_at |
Um único relógio para toda a resposta. |
action |
O que o servidor fez: inspected, checked_provider, resumed, provisioned, restored, reconciled, awaited_wake, cooling_down. |
observation |
O que o servidor verificou. known: false significa não verificado nesta chamada — nunca “verificado e nada encontrado”. |
boot |
phase (provisioning / resuming / booting / ready / parked / failed), since e actively_starting. |
boot.actively_starting responde “há uma operação do provedor em execução para esta
sessão agora?”. Um payload starting com actively_starting: false significa
que o servidor está aguardando o intervalo antes de uma nova tentativa, não que uma máquina esteja inicializando.
{
"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"
}
}
}
Uma inicialização malsucedida é repetida automaticamente
Uma inicialização que falha registra um intervalo de espera, não um veredito permanente. O próximo
/start após o intervalo tenta reativar a sessão por conta própria. O intervalo aumenta
com falhas consecutivas (2 min, 5 min, 10 min). Após cinco falhas
consecutivas, /start responde stage: "failed" com a contagem de tentativas em
failure.message; esse veredito expira 30 minutos após a última falha, e
POST …/restart o limpa imediatamente.
retriable é derivado em cada chamada. Um estado que o servidor ainda pode tentar novamente
nunca contém retriable: false.
failure.evidence identifica a verificação que produziu o resultado negativo, quando ela foi executada e
quando o servidor tenta novamente. Toda falha de /start o contém.
Anexos do prompt
Envie arquivos locais quando os usuários os adicionarem a um editor de mensagens. Uploads privados pertencem ao projeto e não exigem uma sessão em execução.
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 }) é resolvido quando todos os uploads estão prontos, na ordem de ids.
É rejeitado quando um upload falha, é abortado ou removido, ou quando signal aborta.
submit(ids) transfere as entradas para esse envio: elas saem de attachments, deixam
de contar para os limites e continuam sendo enviadas após a desmontagem do editor.
Chame forget(ids) após o POST ser bem-sucedido, ou reclaim(ids) para devolvê-las ao
editor após uma falha no envio.
A parte de arquivo resolvida contém attachment_id, filename e mime.
Ela não contém URL nem bytes do arquivo. O servidor vincula e materializa o upload.
Todo arquivo chega ao computador da sessão, incluindo imagens e PDFs: antes de o
prompt ser executado, o servidor o grava em /workspace/uploads/.beeuni-inbox/ e o
prompt o referencia pelo caminho. Nenhum byte de arquivo é transportado no próprio prompt. Com
o histórico salvo ativado, o servidor também mantém uma cópia para a transcrição. Uma falha na cópia
nunca impede que um arquivo chegue ao computador. Um arquivo que o servidor não consegue gravar bloqueia o
prompt, que tenta novamente.
Use essas partes nas rotas de criação de sessão da plataforma, início de sessão já ativa ou caixa de entrada
de prompts. Não passe identificadores privados diretamente para o runtime sendParts.
Partes de arquivo legadas da plataforma com url continuam funcionando.
Cada item tem um id estável, a referência original file, receivedBytes e
um status: pending, uploading, processing, ready, error ou aborted.
Os bytes enviados determinam o progresso. O controlador publica um retrato do progresso apenas
quando há uma mudança de percentual inteiro, no máximo dez vezes por segundo por upload. processing pode
permanecer em 100% até o servidor concluir o upload. Os limites são 50 MiB por
arquivo, 100 MiB por mensagem e 20 arquivos. Os arquivos não podem estar vazios. Dois arquivos
são enviados simultaneamente por padrão.
retry(id) retoma o mesmo identificador. Após attachment_size_mismatch ou
attachment_failed, o servidor não mantém nenhum identificador utilizável, então retry(id) envia o
File novamente como um novo anexo. Um upload expirado não pode ser repetido: retry(id)
lança um erro, e o erro do item contém o código attachment_expired. abort(id) cancela
o trabalho inacabado.
remove(id) remove a entrada, aborta seu upload e é resolvido imediatamente. O
DELETE no servidor é feito na medida do possível e nunca é rejeitado; um upload que ele não consegue excluir
expira após 24 horas.
forget(ids) libera as entradas sem excluir objetos do servidor.
O hook aborta o trabalho listado ao desmontar ou mudar de projeto e exclui seus uploads
na medida do possível: nenhum envio os retém, e os rascunhos não mantêm identificadores. Os uploads transferidos
continuam.
As seleções ficam apenas na memória. Nunca persista objetos File, URLs de blob, URLs assinadas ou identificadores de upload em um rascunho. Uploads abandonados expiram após 24 horas.
Hosts sem framework usam beeuni.project(projectId).attachments.createController()
com os mesmos métodos. Inscreva-se por meio de subscribe(listener) e leia
getSnapshot(). Chame dispose() quando o editor fechar.
Chamadores diretos podem usar attachments.upload(file, { signal, onProgress, onUpload, resume }) and attachments.delete(attachmentId).
Retenha os metadados onUpload para retomada manual com o mesmo ID.
O servidor seleciona o transporte no campo upload do identificador:
kind: 'direct'é o padrão. O SDK envia o arquivo inteiro em um únicoPUTparaupload.urlcomupload.headerse sem cabeçalho Authorization. ComXMLHttpRequest(navegadores, React Native), o progresso informa os bytes enviados. Comfetch, o progresso informa 0 e depois o tamanho total. Uma URL expirada, ou uma que o Storage recuse com 400, 401 ou 403, é assinada novamente uma vez para o mesmoattachment_id. Um409do Storage significa que uma tentativa anterior armazenou o arquivo.kind: 'chunked'enviaPUTs autenticados sequenciais deupload.chunk_sizebytes. O SDK aceita qualquerchunk_sizepositivo. Somente uma implantação cuja edge descarte corpos de requisição grandes seleciona este modo.
A conclusão verifica o tamanho armazenado e o SHA-256. Se a conclusão responder 409 attachment_not_uploaded, onUpload informará o identificador direto com
received_bytes: 0, para que retry(id) ou uma retomada envie o arquivo novamente.
Os tempos limite de inicialização, upload e conclusão, erros de rede, 429 e
5xx são repetidos com recuo exponencial com jitter. O orçamento é de 60
segundos a partir da primeira falha, portanto um upload longo que falhar no fim ainda será repetido. A conclusão também repete
attachment_processing, com um orçamento de cinco minutos. A inicialização nunca repete 402
(um BillingError: a conta não pode executar) nem 429 attachment_budget_exceeded (40
uploads não concluídos ou 500 MiB de uploads não enviados para o usuário; uploads não utilizados
expiram em 24 horas). O servidor responde ou recusa uma conclusão em até 105
segundos. Cada requisição de conclusão permite 120 segundos. Abortos do chamador nunca são repetidos.
A referência de um anexo enviado é liberada 1 hora após a entrega do prompt,
e quando a sessão ou o projeto é excluído. A próxima varredura de manutenção
remove o arquivo, e seu attachment_id não pode mais ser enviado.
Arquivos
s.files lê e grava no sandbox da sessão: list, read, readBlob,
status, findFiles, findText, upload, create, copy, remove,
mkdir, rename. Cada chamada resolve primeiro o runtime e sempre tem como destino
apenas o sandbox desta sessão. Consulte a referência do SDK para
ver a lista completa de métodos.
Verbos da sessão
O identificador lê o runtime e responde por você. Cada verbo provisiona a sessão primeiro e tem como destino o sandbox desta sessão, independentemente do harness que o execute.
// 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, o cliente de compatibilidade REST bruto, está obsoleto. Use os verbos
acima com send, abort, rewind e stream.
Preparar uma sessão do projeto
Chame ensureWarm() quando uma página inicial do projeto precisar de um runtime pronto antes
do primeiro prompt.
const project = beeuni.project(projectId);
const warm = await project.sessions.ensureWarm();
// An ORDINARY session. Prompt it like any other.
await beeuni.session(projectId, warm.session.session_id).send("Build me a widget");
ensureWarm() cria ou retorna uma sessão não utilizada para o usuário atual. É
a mesma criação que sessions.create() executa, com os padrões do projeto: o mesmo
bloqueio de cobrança e os mesmos requisitos de conectores. A única
diferença é metadata.warm, que oculta a sessão de
sessions.list() até a chegada do primeiro prompt.
Trate-a como especulativa. Um 409 WARM_SESSION_UNAVAILABLE significa que o projeto
não pode ser preparado agora — prossiga para sessions.create(), que informa
o motivo real.
A sessão preparada inclui o agente DEFAULT e o sandbox do projeto. Se o usuário
escolher outro, abandone-a e chame sessions.create(): uma sessão preparada não utilizada
fica oculta e é coletada automaticamente.
Tratamento de erros
Cada chamada por meio de createBeeuni é rejeitada com uma subclasse tipada de Error,
nunca com um objeto simples. Capture o erro, verifique instanceof e tome decisões com base em
.status ou .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;
}
}
Classes de erro
| Classe | Estende | Quando é lançada | Campos principais |
|---|---|---|---|
ApiError |
Error |
Padrão para qualquer requisição com falha: status inválido, falha de rede, tempo limite ou aborto | status, code, detail, response, url, endpoint, timeout |
AuthError |
ApiError |
getToken retornou null. O Beeuni nunca enviou a requisição |
code é sempre 'NO_SESSION' |
BillingError |
ApiError |
HTTP 402. A única classe de erro de cobrança |
status (402), code, detail.message |
RequestTooLargeError |
ApiError |
HTTP 431. Geralmente, arquivos demais em uma única requisição |
detail.suggestion |
SessionNotReadyError |
Error |
Um acessador de runtime foi executado antes de ensureReady() |
name é 'SessionNotReadyError' |
ApiError.name é 'ApiError' por padrão. Dois casos o substituem:
name: 'AbortError',code: 'ABORTED'— a requisição foi cancelada, por exemplo pela navegação. Isso não é uma falha. Ignore-a.code: 'TIMEOUT'— o tempo limite da própria requisição expirou.url,endpointetimeoutmostram o que atingiu o tempo limite.
Para qualquer outra falha, status contém o código de status HTTP. code vem de error_code do backend ou usa o status como string. message é uma propriedade própria enumerável em ApiError, portanto sobrevive a JSON.stringify e à expansão de objeto.
O Beeuni envia novamente uma requisição que recebe 401, uma vez, com um token novo de getToken(), quando esse token for diferente do enviado. Uma requisição com corpo de stream de uso único não é reenviada.
O Beeuni repete algumas requisições antes que seu código veja um erro. Se uma requisição GET ou HEAD retornar 502, 503 ou 504, o Beeuni a repetirá até 2 vezes, com atrasos de 250 ms e depois 500 ms. Uma falha transitória de transporte em uma requisição GET ou HEAD — um erro de rede, não um código de status — é repetida da mesma forma. Uma repetição bem-sucedida nunca chega a onError. O Beeuni nunca repete requisições POST, PUT, PATCH ou DELETE, nem uma resposta 500.
O Beeuni lança AuthError no cliente, antes de enviar uma requisição, quando getToken() retorna null. AuthError estende ApiError, portanto err instanceof ApiError ainda corresponde. Verifique err instanceof AuthError ou err.code === 'NO_SESSION' para tratar “não conectado” como um caso separado de uma falha do backend.
O Beeuni lança BillingError para toda resposta HTTP 402: créditos esgotados, limite do plano excedido ou outro bloqueio de cobrança. detail.message contém o motivo do backend, e code contém o código de máquina do backend (por exemplo, app_account_unfunded). BillingError estende ApiError, portanto verifique BillingError primeiro ao tratar ambos.
O Beeuni lança RequestTooLargeError para HTTP 431. Isso geralmente significa que a requisição continha arquivos demais. detail.suggestion contém uma dica pronta para mostrar ao usuário.
Erros de prontidão da sessão
Dois erros significam que o sandbox da sessão ainda não está pronto. Trate cada um deles de forma diferente.
SessionNotReadyError lança um erro de forma síncrona quando você chama um acessor do runtime — session.previewUrl() ou session.proxyUrl() — antes de este handle de sessão ter resolvido seu sandbox. Um handle de sessão resolve apenas seu próprio sandbox; ele nunca recorre ao sandbox de outra sessão.
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();
}
}
Chame await session.ensureReady() primeiro ou chame send(), que prepara a sessão internamente. session.health() é o único acessor que nunca lança esse erro, então você pode consultá-lo periodicamente antes de a sessão iniciar.
RUNTIME_UNAVAILABLE é o segundo erro — significa que o próprio ensureReady() excedeu o tempo limite de espera por uma inicialização a frio. Veja Tentar novamente em uma inicialização a frio para o padrão completo. No React, useSession faz novas tentativas por você e expõe esse erro pelo valor phase em vez de lançá-lo.
Funções auxiliares
| Função auxiliar | Assinatura | O que faz |
|---|---|---|
parseBillingError(error) |
(error) => Error |
Encapsula uma resposta 402 em um BillingError. Retorna outros erros sem alterações |
isBillingError(error) |
(error) => boolean |
Retorna error instanceof BillingError |
formatBillingErrorForUI(error) |
(error) => BillingErrorUI | null |
Retorna null para erros não relacionados à cobrança. Caso contrário, retorna { alertTitle, alertSubtitle } para um modal de upgrade |
isRuntimeNotReadyResponse(error) |
(error) => boolean |
Verdadeiro para o 503 do daemon do sandbox enquanto o runtime da sessão ainda não pode receber uma requisição. A requisição não foi encaminhada, então é seguro tentar novamente. O mesmo resultado no runtime e no pi |
isSandboxNotReadyError(error) |
(error) => boolean |
Verdadeiro para o resultado acima e para um sandbox parado, iniciando ou estacionado. Exiba-o como “despertando” e continue consultando periodicamente |
isRuntimeStartingError(error) |
(error) => boolean |
Verdadeiro para os dois resultados acima e para uma URL de runtime que ainda não está fixada. Um limite de erro faz novas tentativas nesse caso em vez de mostrar uma falha |
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);
}
Em @beeuni/sdk/react
@beeuni/sdk/react reexporta BillingError, RequestTooLargeError, parseBillingError, isBillingError e formatBillingErrorForUI. Ele não reexporta ApiError nem AuthError — importe-os de @beeuni/sdk.
useSession classifica cada falha de send, answerQuestion, answerPermission e rejectQuestion em um único objeto sendError, para que você não precise escrever verificações instanceof manualmente:
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);
}
Veja Hooks do React para o restante de useSession.