Saltar para o conteúdo
BeeuniBeeuni
Português
Esc
↑↓navegar↵abrir⌘Jpré-visualizar
Nesta página

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 com 400. 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 com getPublicSessionShareMessages(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 um Blob sem 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. Passe limit (no máximo 500) para obter a janela mais recente, e o next_cursor de uma janela como before para 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, e next_cursor então identifica a mais antiga que foi mantida. Passe child (o id ses_… 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 com available: true, complete: true, total: 0 e 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 workers ask_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 é:

  1. A chamada send().
  2. O valor setModel() ou setAgent() do handle.
  3. 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:

  1. Chame ensureReady() primeiro. O runtime não existe até o sandbox iniciar.
  2. Abra o fluxo antes de enviar uma mensagem, para não perder os primeiros eventos.
  3. Envie a mensagem.
  4. 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 único PUT para upload.url com upload.headers e sem cabeçalho Authorization. Com XMLHttpRequest (navegadores, React Native), o progresso informa os bytes enviados. Com fetch, 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 mesmo attachment_id. Um 409 do Storage significa que uma tentativa anterior armazenou o arquivo.
  • kind: 'chunked' envia PUTs autenticados sequenciais de upload.chunk_size bytes. O SDK aceita qualquer chunk_size positivo. 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, endpoint e timeout mostram 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.

Esta página foi útil?