# Sessões

Execute uma sessão, transmita seus eventos e trate os erros que ela pode gerar.

Canonical page: https://beeuni.ai/docs/pt/sdk/sessions

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.

```ts
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](/docs/pt/work/sessions) para entender o conceito.

<a id="session-lifecycle"></a>

## 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](/docs/pt/work/notifications) 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 |

> `s.delete()` exclui a sessão e seu runtime. Isso não pode ser desfeito. Para pausar uma sessão
> sem perdê-la, chame `s.stop()`.

<a id="labels-and-metadata"></a>

## 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.

```ts
// 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.

```ts
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:

```ts
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.

```ts
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](/docs/pt/work/runtime#config-convergence).

`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](/docs/pt/connect/reminders).
  `.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: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.

<a id="files-a-session-stored"></a>

### 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.

```ts
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.

<a id="who-wrote-each-message"></a>

## 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.

```ts
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) |

<a id="which-model-answered-a-turn"></a>

## Qual modelo respondeu a um turno

Uma mensagem da transcrição identifica o modelo solicitado pelo seu turno. Quando a
[cadeia de fallback](/docs/pt/project/models) 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.

```ts
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 id="readiness-is-a-handshake"></a>

## 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.

```ts
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](#retry-on-a-cold-boot) para saber o que isso significa e como
tentar novamente.

`s.send()` e `s.abort()` chamam `ensureReady()` para você.

<a id="seed-a-server-authorized-runtime-session-pin"></a>

### 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:

```tsx
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.

<a id="send-a-prompt"></a>

## Envie um prompt

```ts
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`:

```ts
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.

<a id="session-scope-and-cost"></a>

## 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:

```ts
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:

```ts
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:

```ts
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:

```ts
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()`.

<a id="runtime-status-and-previews"></a>

## 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    |

```ts
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](#session-readiness-errors).

<a id="what-the-runtime-supports"></a>

### 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](/docs/pt/work/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 |

```ts
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`](/docs/pt/sdk/react).

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.

<a id="status-words"></a>

## 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`](/docs/pt/sdk/react) 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`.

```ts
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:

```ts
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.

<a id="event-types"></a>

### 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](/docs/pt/sdk/reference).

<a id="retry-on-a-cold-boot"></a>

## 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.

```ts
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](#error-classes) para a estrutura completa de `ApiError`. No React,
[`useSession`](/docs/pt/sdk/react) tenta `/start` novamente por você, então você não precisa
desse padrão.

<a id="what-start-tells-you"></a>

## 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.

```jsonc
{
  "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"
    }
  }
}
```

<a id="a-failed-start-is-retried-for-you"></a>

### 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.

<a id="prompt-attachments"></a>

## 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.

```tsx
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 `PUT`s 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.

<a id="files"></a>

## 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](/docs/pt/sdk/reference) para
ver a lista completa de métodos.

<a id="session-verbs"></a>

## 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.

```ts
// 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`.

<a id="warm-a-project-session"></a>

## Preparar uma sessão do projeto

Chame `ensureWarm()` quando uma página inicial do projeto precisar de um runtime pronto antes
do primeiro prompt.

```ts
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.

> `claimWarm()` está obsoleto. Uma sessão preparada é uma sessão comum, portanto não há
> nada a reivindicar — navegue até ela e envie um prompt. A chamada ainda funciona para
> consumidores fixados no formato antigo e será removida na próxima versão principal.

<a id="handling-errors"></a>

## 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`.

```ts
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;
  }
}
```

<a id="error-classes"></a>

### 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.

<a id="session-readiness-errors"></a>

### 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.

```ts
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](#retry-on-a-cold-boot) 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.

<a id="helpers"></a>

### 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 |

```ts
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);
}
```

<a id="in-beeunisdkreact"></a>

### 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:

```ts
interface BeeuniSendError {
  kind: 'billing' | 'runtime-not-ready' | 'runtime-error';
  message: string;
  billing?: BillingError; // set when kind is 'billing'
  cause: unknown;
}
```

```tsx
const s = useSession(projectId, sessionId);

if (s.sendError?.kind === 'billing') {
  const ui = formatBillingErrorForUI(s.sendError.billing);
}
```

Veja [Hooks do React](/docs/pt/sdk/react) para o restante de `useSession`.
